RSS

(root)/calliope : /src/base/musicsource.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 339 #include <glib.h>
19 17 #include "main.h"
20 24 #include "misc.h"
21 17 #include "musicsource.h"
22 451 #include "musicsource-private.h"
23 17
24 95
25 212 // FIXME: ALSO STORED IN tests/filesource-test.c !!!
26 17 struct MusicSourcePrivate {
27 109         char *name;
28 264
29 429         /* Duplicates of the entries that are currently checked out. */
30 336         GSList *checkout_entry_store;
31 358
32         GSList *notify_handlers, *notify_watch_handlers;
33         GSList *notify_queue;
34
35 361         /* Current notification status. 'notifying_on_entry' and 'notifying_on_entry_version'
36 429          * track the entry that we are emitting notify for, in case it gets modified during its own
37 361          * notify. */
38 358         Entry *notifying_on_entry; int notifying_on_entry_version;
39 17 };
40
41 60 static guint signal_index[MUSIC_SOURCE_SIGNAL_COUNT]={0};
42 17
43 429 enum {  PROP_0,
44         PROP_NAME,
45         PROP_IS_EMPTY   };
46
47 static void finalize     (GObject *self);
48 static void get_property (GObject *object, guint prop_id, GValue *value, GParamSpec *pspec);
49
50 static void begin_transaction     (MusicSource *self);
51 static void end_transaction       (MusicSource *self);
52 static void update_entry_property (MusicSource *musicsource, EntryType entry_type, int entry_id,
53                                    int property_id, const void *value);
54 static void remove_entry          (MusicSource *self, EntryType type, int id);
55 95 //static Entry *checkout_entry(MusicSource *self, EntryType type, int id);
56 429 static void export_recordings     (MusicSource *self, GSList *recording_list, MusicSource *destination);
57 17
58 445 static void file_notify  (MusicSource *self, Entry *old_entry, Entry *new_entry, void *user_data);
59 static void track_notify (MusicSource *self, Entry *old_entry, Entry *new_entry, void *user_data);
60 358
61 360 static void checkout_store_finalise (MusicSource *self);
62 17
63 360 static void editing_trace (const char *format, ...);
64 429 static void notify_trace  (const char *format, ...);
65 358
66 17 G_DEFINE_TYPE(MusicSource, music_source, G_TYPE_OBJECT);
67
68 static void music_source_class_init(MusicSourceClass *cl) {
69 429         music_source_parent_class = g_type_class_peek_parent(cl);
70         G_OBJECT_CLASS(cl)->finalize     = finalize;
71         G_OBJECT_CLASS(cl)->get_property = get_property;
72         cl->begin_transaction     = begin_transaction;
73         cl->end_transaction       = end_transaction;
74         cl->update_entry_property = update_entry_property;
75         cl->remove_entry          = remove_entry;
76         cl->export_recordings     = export_recordings;
77         g_type_class_add_private (cl, sizeof(MusicSourcePrivate));
78 17
79 429         signal_index[MUSIC_SOURCE_SIGNAL_STATUS_CHANGED] =
80           g_signal_new("status-changed", MUSIC_SOURCE_TYPE,
81                        G_SIGNAL_RUN_LAST, 0, NULL, NULL, g_cclosure_marshal_VOID__STRING,
82                        G_TYPE_NONE, 1, G_TYPE_STRING);
83 264
84         signal_index[MUSIC_SOURCE_SIGNAL_BUSY] =
85 144           g_signal_new("busy", MUSIC_SOURCE_TYPE, G_SIGNAL_RUN_LAST, 0,
86 264                        g_signal_accumulator_true_handled, NULL, marshal_BOOL__DOUBLE_STRING,
87 429                        G_TYPE_BOOLEAN, 2, G_TYPE_DOUBLE, G_TYPE_STRING);
88 264
89 429         // FIXME: wiped signal can probably go, I don't think anyone connects to it anymore
90 264         signal_index[MUSIC_SOURCE_SIGNAL_WIPED]=g_signal_new("wiped", MUSIC_SOURCE_TYPE,
91           G_SIGNAL_RUN_LAST, 0, NULL, NULL, g_cclosure_marshal_VOID__VOID,
92           G_TYPE_NONE, 0);
93
94 109         g_object_class_install_property (G_OBJECT_CLASS(cl), PROP_NAME,
95 429           g_param_spec_string ("name", "Name", "Name", "[Unnamed music source]",
96                                G_PARAM_READABLE | G_PARAM_STATIC_STRINGS));
97 109         g_object_class_install_property (G_OBJECT_CLASS(cl), PROP_IS_EMPTY,
98 429           g_param_spec_boolean ("is-empty", "Is empty", "Is empty", TRUE,
99                                 G_PARAM_READABLE | G_PARAM_STATIC_STRINGS));
100 17 };
101
102 static void music_source_init(MusicSource *self) {
103 429         SP = G_TYPE_INSTANCE_GET_PRIVATE(self, MUSIC_SOURCE_TYPE, MusicSourcePrivate);
104 264
105 109         SP->name = NULL;
106 336         SP->checkout_entry_store = NULL;
107 264
108 358         SP->notify_handlers = SP->notify_watch_handlers = NULL;
109         SP->notify_queue = NULL;
110
111         SP->notifying_on_entry = NULL; SP->notifying_on_entry_version = 0;
112
113 445         music_source_connect_entry_notify (self, ENTRY_TYPE_FILE,  FALSE, file_notify,  NULL);
114         music_source_connect_entry_notify (self, ENTRY_TYPE_TRACK, FALSE, track_notify, NULL);
115 17 };
116
117 95 static void finalize(GObject *object) {
118 264         MusicSource *self = MUSIC_SOURCE(object);
119
120 360         checkout_store_finalise (self);
121 264
122 109         g_free (SP->name);
123 264
124 429         G_OBJECT_CLASS(music_source_parent_class)->finalize (object);
125 95 };
126
127 109 static void get_property (GObject *object, guint prop_id, GValue *value, GParamSpec *pspec) {
128         MusicSource *self = MUSIC_SOURCE(object);
129 264
130         switch (prop_id) {
131 429                 case PROP_NAME:
132                         g_value_set_string (value, SP->name);
133                         break;
134 264
135                 case PROP_IS_EMPTY:
136 429                         g_value_set_boolean (value, music_source_is_empty(self));
137                         break;
138
139                 default:
140                         G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
141 17         }
142 }
143
144 // FIXME: more specific methods should be used in the actual source classes I think.
145 429 static void update_entry_property (MusicSource *self, EntryType entry_type, int entry_id,
146                                    int property_id, const void *value) {
147         Entry *entry = music_source_checkout_entry (self, entry_type, entry_id,
148                                                     "musicsource::update-entry-property");
149 17
150         if (entry==NULL) {
151 429                 g_warning ("entry %i is null, attemped to set property %i", entry_id, property_id);
152 17                 return;
153         } else {
154 269                 entry_set_property (entry, property_id, value);
155
156 17                 //entry_dump(entry);
157                 // In more specific code, an obvious optimisation is to not try to merge on checkin if the property being
158                 // changed is trivial or mergeable.
159 269                 music_source_checkin_entry (self, entry, "musicsource::update-entry-property");
160 17         };
161 };
162
163 452 static void process_empty_properties (Entry *entry) {
164 17         // Default entries
165         //
166         switch(entry->type) {
167                 case ENTRY_TYPE_ARTIST:
168                         if (entry_get_property(entry, ARTIST_NAME)==NULL) {
169 429                                 entry_set_property (entry, ARTIST_NAME, "Unknown Artist");
170 17                                 //// This is kind of a hack but it might be okay .. the point is
171                                 //// if we change the name of unknown artist (reading tabs or
172                                 //// whatever) then it changes the unknown entry ! Which isn't
173                                 //// what we want. FIXME: this renders the precreated fields
174                                 //// pretty much useless.
175                                 //do_not_merge=TRUE;
176                         }
177                         break;
178                 case ENTRY_TYPE_COMPOSITION:
179                         if (entry_get_property(entry, COMPOSITION_NAME)==NULL)
180 429                                 entry_set_property (entry, COMPOSITION_NAME, "Unknown Title");
181 17                         break;
182                 case ENTRY_TYPE_RECORDING:
183 264                         if ((int)entry_get_property(entry, RECORDING_LENGTH)==0)
184 429                                 entry_set_property (entry, RECORDING_LENGTH, (void *)-1);
185 17                         break;
186                 case ENTRY_TYPE_ALBUM:
187                         if (entry_get_property(entry, ALBUM_NAME)==NULL)
188 429                                 entry_set_property (entry, ALBUM_NAME, "Unknown Album");
189 17                         break;
190         };
191 286
192 285         entry_check (entry);
193 17 }
194
195 429 /* Default implementation of transactions. */
196 17 static void begin_transaction(MusicSource *self) {
197         return;
198 }
199
200 static void end_transaction(MusicSource *self) {
201         return;
202 };
203
204 336 /* remove_entry: called by remove_entry in subclasses. */
205 static void remove_entry (MusicSource *self, EntryType type, int id) {
206         GSList *node = SP->checkout_entry_store;
207 429         while (node != NULL) {
208 336                 Entry *entry = node->data;
209 269                 gboolean warned = FALSE;
210 336                 if (entry->type==type && entry->id==id) {
211 269                         if (!warned)
212                                 g_warning ("Entry %s %i was removed while still checked out. If the entry is no "
213                                            "longer any use, it will be removed automatically - just check in as "
214 336                                                    "normal.", ENTRY_PF(entry));
215 342
216 336                         entry_unref (entry, _CHECKOUT_STORE_OWNER_ID);
217 212                         GSList *_node = node; node = node->next;
218 336                         SP->checkout_entry_store = g_slist_remove_link(SP->checkout_entry_store, _node);
219 212                 } else
220                         node = node->next;
221         };
222 413
223 17         return;
224 };
225
226 339
227 429 /* This works for the file-based sources such filesource and library, others
228  * need to replace this. eg. in cdsource this function is for cd ripping. */
229 static void export_recordings (MusicSource *music_source, GSList *recording_list,
230                                MusicSource *destination) {
231 17 // FIXME: should up the signal block stack for this whole thing I think
232 429         GSList *recording = recording_list;
233         while(recording != NULL) {
234 17                 // FIXME: the recording gets sent to the source a whole bunch of times
235                 // this way, it is a big waste !
236 264
237 17                 // No point sending a recording with no files anyway.
238 429                 int recording_id = (int)recording->data;
239 17                 /*Entry *recording_entry=music_source_query_entry(musicsource, ENTRY_TYPE_RECORDING, recording_id);
240                 music_source_add_entry(destination, recording_entry);
241                 entry_unref(recording_entry);*/
242 264
243 429                 GSList *recording_files_list =
244                   music_source_query_entry_children (music_source, recording_id, ENTRY_TYPE_FILE,
245                                                      FILE_RECORDING, "musicsource::export-recordings");
246                 for (GSList *file_node=recording_files_list; file_node!=NULL; file_node=file_node->next) {
247                         Entry *dup = entry_duplicate_tree(file_node->data, "musicsource::export-recordings");
248                         music_source_add_entry (destination, dup);
249                         entry_unref (file_node->data, "musicsource::export-recordings");
250                         entry_unref (dup, "musicsource::export-recordings");
251 17                 };
252 429                 g_slist_free (recording_files_list);
253 17
254 429                 GSList *recording_tracks_list = music_source_query_entry_children(music_source, recording_id, ENTRY_TYPE_TRACK, TRACK_RECORDING, "musicsource::export-recordings");
255 17                 for (GSList *track_node=recording_tracks_list;track_node!=NULL;track_node=track_node->next) {
256 264                         Entry *dup=entry_duplicate_tree(track_node->data, "musicsource::export-recordings");
257 17                         music_source_add_entry(destination, dup);
258 264                         entry_unref((Entry *)track_node->data, "musicsource::export-recordings");
259 17                         entry_unref(dup, "musicsource::export-recordings");
260                 };
261                 g_slist_free(recording_tracks_list);
262 264
263 17                 recording=recording->next;
264         };
265 };
266
267 360 /***************************************************************************************************
268 429  *  Hooks for new entries
269 360  */
270
271 354 // FIXME: this slows down importing a bit, quite a lot of searching when we checkin the entry in
272 346 // update_entry_property
273 358 // FIXME: at the moment we choose defaults at random, it should really be done by highest bitrate +
274 // most suitable number of channels, and maybe preferred format (128kbps mp3 sounds worse than
275 // 128kbps vorbis, but this is quite subjective).
276 static void file_notify (MusicSource *source, Entry *old_entry, Entry *new_entry, void *user_data) {
277         if (new_entry==NULL) {
278                 // File is being removed - check it's not its recording's default file.
279                 Entry *recording = entry_get_property(old_entry, FILE_RECORDING);
280 360
281 361                 if (recording==NULL)
282 360                         // This can happen when recording has already been deleted - in which case of course
283                         // it doesn't matter.
284                         return;
285 361
286                 // If we were the default it could now have been set to NULL.
287                 if (entry_get_property(recording, RECORDING_DEFAULT_FILE)!=old_entry
288                       && entry_get_property(recording, RECORDING_DEFAULT_FILE)!=NULL)
289 358                         return;
290
291                 // If it is, need to find a new one.
292 361                 GSList *recording_file_ids = music_source_query_entry_children_ids
293                                                (source, recording->id, ENTRY_TYPE_FILE, FILE_RECORDING);
294
295                 // Take the first one we find because I suck.
296                 int default_file_id = GPOINTER_TO_INT(recording_file_ids->data);
297                 if (recording_file_ids==NULL)
298                         goto no_file;
299                 if (default_file_id == old_entry->id) {
300                         if (recording_file_ids->next==NULL)
301                                 goto no_file;
302                         default_file_id = GPOINTER_TO_INT(recording_file_ids->next->data);
303                 }
304
305                 Entry *new_default_file = music_source_query_entry(source, ENTRY_TYPE_FILE, default_file_id,
306                                                                    "musicsource::file-notify");
307                 recording = music_source_checkout_entry(source, ENTRY_TYPE_RECORDING, recording->id,
308                                                         "musicsource::file-notify");
309                 entry_take_property (recording, RECORDING_DEFAULT_FILE, new_default_file);
310                 music_source_checkin_entry (source, recording, "musicsource::file-notify");
311
312 no_file:
313                 g_slist_free (recording_file_ids);
314 358         }
315
316         if (old_entry==NULL) {
317                 // File is being added - check if its recording has a default file
318                 Entry *recording = entry_get_property(new_entry, FILE_RECORDING);
319 361
320 358                 if (entry_get_property(recording, RECORDING_DEFAULT_FILE)!=NULL)
321                         return;
322
323                 recording = music_source_checkout_entry(source, ENTRY_TYPE_RECORDING, recording->id,
324                                                         "musicsource::file-notify");
325                 entry_set_property (recording, RECORDING_DEFAULT_FILE, new_entry);
326                 music_source_checkin_entry (source, recording, "musicsource::file-notify");
327         };
328 };
329
330 17
331 445 // Automatically set album artist to 'various artists' when it is.
332 17 //
333 445 static void track_notify (MusicSource *self, Entry *old_entry, Entry *new_entry,
334                           void *user_data) {
335         const char *ref_id = "source track-notify";
336         if (old_entry == NULL) {
337                 // Just added a new entry. If the album isn't VA, see if it now should be.
338                 Entry *release = entry_get_property (new_entry, TRACK_RELEASE);
339                 if (release==NULL) return; // Don't warn on this, because we do it for unit tests sometimes.
340
341                 Entry *album        = entry_get_property (release, RELEASE_ALBUM);
342                 Entry *album_artist = entry_get_property (album,   ALBUM_ARTIST);
343                 if (album_artist->id == ARTIST_ID_VARIOUS)
344                         return;
345
346                 // Find other releases which match this track's, except by album artist. There should be a
347                 // maximum of one: the existing VA release. The one just added won't be returned.
348                 GSList *match_releases = music_source_query_matching_except (self, release, _ALBUM_ARTIST,
349                                                                              ref_id);
350 451                 /*for (GSList *node=match_releases; node; node=node->next) {
351 445                         entry_dump (node->data);
352 451                 }*/
353 445
354                 if (g_slist_length (match_releases) == 0)
355                         return;
356                 g_return_if_fail (g_slist_length (match_releases) == 1);
357
358                 Entry *va_release = match_releases->data;
359
360                 album = entry_get_property (va_release, RELEASE_ALBUM);
361                 album_artist = entry_get_property (album, ALBUM_ARTIST);
362                 if (album_artist->id != ARTIST_ID_VARIOUS) {
363                         album = music_source_checkout_entry (self, ENTRY_TYPE_ALBUM, album->id, ref_id);
364                         album_artist = music_source_query_entry (self, ENTRY_TYPE_ARTIST, ARTIST_ID_VARIOUS,
365                                                                  ref_id);
366                         entry_take_property (album, ALBUM_ARTIST, album_artist);
367                         music_source_checkin_entry (self, album, ref_id);
368                 }
369 451                 //entry_dump (va_release);
370 445
371                 Entry *track = music_source_checkout_entry (self, ENTRY_TYPE_TRACK, new_entry->id, ref_id);
372                 entry_set_property (track, TRACK_RELEASE, va_release);
373                 //entry_dump (track);
374                 music_source_checkin_entry (self, track, ref_id);
375
376                 entry_query_list_free (match_releases, ref_id);
377         } else {
378 451                 //entry_dump (old_entry);
379 445                 Entry *release = entry_get_property (old_entry, TRACK_RELEASE);
380                 if (release==NULL) return; // Don't warn on this, because we do it for unit tests sometimes.
381
382                 Entry *album        = entry_get_property (release, RELEASE_ALBUM);
383                 g_return_if_fail (album != NULL);
384
385                 Entry *album_artist = entry_get_property (album,   ALBUM_ARTIST);
386                 g_return_if_fail (album_artist != NULL);
387
388                 // Removing a track can't make an album become VA.
389                 if (album_artist->id != ARTIST_ID_VARIOUS && new_entry == NULL)
390                         return;
391
392                 Entry *first_artist = NULL;
393                 gboolean is_va = FALSE;
394
395                 GSList *track_list = music_source_query_relations (self, ENTRY_TYPE_RELEASE, release->id,
396                                                                    _TRACK_RELEASE, ref_id);
397
398                 // We could get an empty track list, if we are being notified that the only track on this
399                 // release has changed to a different release.
400                 if (!track_list)
401                         return;
402
403                 for (GSList *node=track_list; node; node=node->next) {
404                         Entry *track     = node->data,
405                               *recording = entry_get_property (track,     TRACK_RECORDING),
406                               *artist    = entry_get_property (recording, RECORDING_ARTIST);
407                         if (first_artist == NULL)
408                                 first_artist = artist;
409                         else if (first_artist != artist) {
410                                 is_va = TRUE;
411                                 break;
412                         }
413                 }
414
415                 if (is_va) {
416                         if (album_artist->id != ARTIST_ID_VARIOUS) {
417                                 album = music_source_checkout_entry (self, ENTRY_TYPE_ALBUM, album->id, ref_id);
418                                 album_artist = music_source_query_entry (self, ENTRY_TYPE_ARTIST, ARTIST_ID_VARIOUS,
419                                                                          ref_id);
420                                 entry_take_property (album, ALBUM_ARTIST, album_artist);
421                                 music_source_checkin_entry (self, album, ref_id);
422                         }
423                 } else {
424                         if (album_artist != first_artist) {
425                                 album = music_source_checkout_entry (self, ENTRY_TYPE_ALBUM, album->id, ref_id);
426                                 entry_set_property (album, ALBUM_ARTIST, first_artist);
427                                 music_source_checkin_entry (self, album, ref_id);
428                         }
429                 }
430
431                 entry_query_list_free (track_list, ref_id);
432         }
433 };
434 17
435 358 /***************************************************************************************************
436  * Virtual functions
437  */
438 17
439 gboolean music_source_is_empty(MusicSource *self) {
440         return MUSIC_SOURCE_GET_CLASS(self)->is_empty(self);
441 };
442
443 gboolean music_source_get_loaded(MusicSource *self) {
444         return MUSIC_SOURCE_GET_CLASS(self)->get_loaded(self);
445 };
446
447 char *music_source_get_summary(MusicSource *self) {
448         return MUSIC_SOURCE_GET_CLASS(self)->get_summary(self);
449 }
450
451 int music_source_get_n_entries(MusicSource *self, EntryType type, int *highest_id, int *n_dead) {
452         return MUSIC_SOURCE_GET_CLASS(self)->get_n_entries(self, type, highest_id, n_dead);
453 };
454
455
456 gboolean music_source_is_valid_id(MusicSource *self, EntryType type, int id) {
457         return MUSIC_SOURCE_GET_CLASS(self)->is_valid_id(self, type, id);
458 };
459
460 130 #ifdef ENTRY_TRACK_REFERENCE_OWNERS
461 17         Entry *music_source_query_entry_tracking(MusicSource *self, EntryType type, int id, const char *owner_id, const char *file, int line_no) {
462                 Entry *entry=MUSIC_SOURCE_GET_CLASS(self)->query_entry(self, type, id);
463 102                 if (entry!=NULL)
464 288                         entry_take_last_ref_tracking(entry, owner_id, file, line_no);
465 17                 return entry;
466         };
467
468         GSList *music_source_query_entry_children_tracking(MusicSource *self, int parent_id, EntryType child_entry_type, int child_property, const char *owner_id, const char *file, int line_no) {
469                 GSList *list=MUSIC_SOURCE_GET_CLASS(self)->query_entry_children(self, parent_id, child_entry_type, child_property);
470                 for (GSList *node=list;node!=NULL;node=node->next)
471 288                         entry_take_last_ref_tracking((Entry *)node->data, owner_id, file, line_no);
472 17                 return list;
473         };
474
475 92         GSList *music_source_query_relations_tracking(MusicSource *self, EntryType local_type, int local_id, int relation_apid, const char *owner_id, const char *file, int line_no) {
476                 GSList *list = MUSIC_SOURCE_GET_CLASS(self)->query_relations(self, local_type, local_id,
477                                                                              relation_apid);
478                 for (GSList *node=list; node!=NULL; node=node->next)
479 288                         entry_take_last_ref_tracking ((Entry *)node->data, owner_id, file, line_no);
480 92                 return list;
481         };
482
483 17         GSList *music_source_query_matching_except_tracking(MusicSource *self, Entry *entry, int ignored_property_absolute_id, const char *owner_id, const char *file, int line_no) {
484                 GSList *list=MUSIC_SOURCE_GET_CLASS(self)->query_matching_except(self, entry, ignored_property_absolute_id);
485                 for (GSList *node=list;node!=NULL;node=node->next)
486 288                         entry_take_last_ref_tracking ((Entry *)node->data, owner_id, file, line_no);
487 17                 return list;
488         };
489
490         Entry *music_source_checkout_entry_tracking(MusicSource *self, EntryType type, int id, const char *owner_id, const char *file, int line_no) {
491                 Entry *entry=MUSIC_SOURCE_GET_CLASS(self)->checkout_entry(self, type, id);
492 288                 entry_take_last_ref_tracking(entry, owner_id, file, line_no);
493 17                 return entry;
494         };
495
496         void music_source_checkin_entry_tracking(MusicSource *self, Entry *entry, const char *owner_id, const char *file, int line_no) {
497 451                 MUSIC_SOURCE_GET_CLASS(self)->checkin_entry (self, entry);
498 288                 entry_unref_tracking (entry, owner_id, file, line_no);
499 17         };
500
501 #else
502         Entry *music_source_query_entry_internal(MusicSource *self, EntryType type, int id) {
503                 return MUSIC_SOURCE_GET_CLASS(self)->query_entry(self, type, id);
504         };
505
506         GSList *music_source_query_entry_children_internal(MusicSource *self, int parent_id, EntryType child_entry_type, int child_property) {
507                 return MUSIC_SOURCE_GET_CLASS(self)->query_entry_children(self, parent_id, child_entry_type, child_property);
508         };
509
510 92         GSList *music_source_query_relations_internal(MusicSource *self, EntryType local_type, int local_id, int relation_apid) {
511                 return MUSIC_SOURCE_GET_CLASS(self)->query_relations (self, local_type, local_id,
512                                                                       relation_apid);
513         };
514
515 17         GSList *music_source_query_matching_except_internal(MusicSource *self, Entry *entry, int ignored_property_absolute_id) {
516                 return MUSIC_SOURCE_GET_CLASS(self)->query_matching_except(self, entry, ignored_property_absolute_id);
517         };
518
519         Entry *music_source_checkout_entry_internal(MusicSource *self, EntryType type, int id) {
520                 return MUSIC_SOURCE_GET_CLASS(self)->checkout_entry(self, type, id);
521         };
522
523         void music_source_checkin_entry_internal(MusicSource *self, Entry *entry) {
524                 MUSIC_SOURCE_GET_CLASS(self)->checkin_entry(self, entry);
525                 entry_unref(entry, NULL);
526         };
527
528 #endif
529
530 264 int music_source_query_n_relations (MusicSource *music_source, int local_id,
531 102                                     EntryType foreign_type, int foreign_property_id, int limit) {
532 264         return MUSIC_SOURCE_GET_CLASS(music_source)->query_n_relations(music_source, local_id,
533                                                                        foreign_type,
534 102                                                                        foreign_property_id, limit);
535 };
536
537 17 GSList *music_source_query_entry_children_ids(MusicSource *self, int parent_id, EntryType child_entry_type, int child_property) {
538         return MUSIC_SOURCE_GET_CLASS(self)->query_entry_children_ids(self, parent_id, child_entry_type, child_property);
539 };
540
541 GSList *music_source_query_ids(MusicSource *self, int entry_type) {
542         return MUSIC_SOURCE_GET_CLASS(self)->query_ids(self, entry_type);
543 };
544 264
545 215 MusicSourceView *music_source_create_view(MusicSource *self, ViewConfig *config) {
546 24         return MUSIC_SOURCE_GET_CLASS(self)->create_view(self, config);
547 };
548 264
549 17 void music_source_begin_transaction(MusicSource *self) {
550         MUSIC_SOURCE_GET_CLASS(self)->begin_transaction(self);
551 };
552
553 void music_source_end_transaction(MusicSource *self) {
554         MUSIC_SOURCE_GET_CLASS(self)->end_transaction(self);
555 };
556
557 void music_source_update_entry_property(MusicSource *self, EntryType entry_type, int entry_id, int property_id, const void *value) {
558         MUSIC_SOURCE_GET_CLASS(self)->update_entry_property(self, entry_type, entry_id, property_id, value);
559 };
560
561 int music_source_add_entry(MusicSource *self, Entry *entry) {
562         return MUSIC_SOURCE_GET_CLASS(self)->add_entry(self, entry);
563 };
564
565 void music_source_remove_entry(MusicSource *self, EntryType type, int id) {
566         MUSIC_SOURCE_GET_CLASS(self)->remove_entry(self, type, id);
567 };
568
569 95 void music_source_flush(MusicSource *self) {
570         MUSIC_SOURCE_GET_CLASS(self)->flush(self);
571 };
572
573 17 void music_source_wipe(MusicSource *self) {
574         MUSIC_SOURCE_GET_CLASS(self)->wipe(self);
575 };
576
577 void music_source_export_recordings(MusicSource *self, GSList *recording_list, MusicSource *destination) {
578         MUSIC_SOURCE_GET_CLASS(self)->export_recordings(self, recording_list, destination);
579 };
580
581 206 void _music_source_dump (MusicSource *self, gboolean detailed) {
582         MUSIC_SOURCE_GET_CLASS(self)->dump (self, detailed);
583 17 };
584
585
586 95 //////////////////////////////
587 109 // Non-virtual
588 //
589
590 const char *music_source_get_title (MusicSource *self) {
591         return SP->name;
592 };
593
594
595 451 /***************************************************************************************************
596  * Private methods
597  */
598
599 int _music_source_find_entry (MusicSource *self, Entry *entry, Entry *caller) {
600         return MUSIC_SOURCE_GET_CLASS(self)->find_entry (self, entry, caller);
601 }
602
603 109 //////////////////////////////
604 95 // Private functions
605 //
606
607 109 void _music_source_set_name (MusicSource *self, const char *name, gboolean notify) {
608         g_free (SP->name);
609         SP->name = g_strdup(name);
610 264
611 109         if (notify)
612                 g_object_notify (G_OBJECT(self), "name");
613 };
614
615 360 /***************************************************************************************************
616  * Entry editing
617 361  *
618 360  *   A quick word on how this works. Entries are edited by checking them out, modifying the entries
619  *   that are returning and then checking them back in. Internally, the source takes a copy of each
620  *   entry as it's checked out and uses this for various things on checkin (such as managing shadow
621 361  *   properties and removing any entries no longer referenced anywhere). Notify handlers are then
622  *   called, and passed the old and new entries (the views are the main reason we pass the old
623  *   entry).
624  *
625  *   A notify handler can then modify the entry further - giving us a lot of extra work to do! To
626 360  *   this end, we restart the notify signal queue each time the entry in question is changed. For
627  *   to work we also need to be able to store multiple versions of the entry - think about it.
628 361  *
629 360  */
630
631 451 static gboolean can_merge (Entry *entry) {
632 17         gboolean can_merge=TRUE;
633 264
634 17         // I think we actually do want unknown artists to merge since if I create a
635 264         // new entry titled "Unknown Artist" it should be removed & replaced with
636 17         // artist id 1 which is the actual unknown artist entry.
637         //
638         //      if (entry->type==ENTRY_TYPE_ARTIST) {
639         //              if (entry->id==1 || entry->id==2) return FALSE;
640         //      };
641 264
642 17         // FIXME: would it be easier just to have an UNKNOWN_COMPOSITION entry
643         // which we would know never to merge ???
644         // I don't know about all of this any more, duplicating compositions when they are actually the same is harmless
645         // though because if it's the same file, when the file entry is merged the duplication will be caught.
646         if (entry->type==ENTRY_TYPE_COMPOSITION) {
647                 Entry *artist=entry_get_property(entry, COMPOSITION_ARTIST);
648                 if (artist==NULL) return FALSE;
649 264
650 17                 ///if (artist==self->unknown_artist_entry)
651                 if (artist->id==ARTIST_ID_UNKNOWN)
652                         can_merge=FALSE;
653                 else {
654                         char *title=entry_get_property(entry, COMPOSITION_NAME);
655                         //printf("Title %s\n", title);
656                         if (title==NULL || str_match(title, "Unknown Title"))
657                                 can_merge=FALSE;
658                 };
659                 //printf("%i\n", can_merge);
660         };
661 264
662 17         if (entry->type==ENTRY_TYPE_RECORDING) {
663                 // FIXME: I can't really think of a better way to do this. Basically,
664                 // different recordings of the same composition which are the same length
665                 // (not that rare) are merged when they shouldn't be. However, doing
666                 // exports and whatnot the same recording can be added to a source like
667                 // 5 times. To check if its the same song the default-file property is
668                 // used.
669                 can_merge=TRUE;
670 264
671 17                 // FIXME: never merging recordings means there is only ever one file
672                 // for each recording ..
673                 //can_merge=FALSE;
674         };
675         return can_merge;
676 };
677
678 451 // Checks if an entry is already present in the database. No duplicates allowed.
679 //
680 static Entry *try_merge (MusicSource *self, Entry *new_entry) {
681         editing_trace ("\ttry_merge: %s %i: ", ENTRY_PF(new_entry));
682         if (!can_merge (new_entry)) {
683                 editing_trace ("no (never merge this type).\n");
684                 return NULL;
685         }
686
687         int current_entry_id = _music_source_find_entry (self, new_entry, new_entry);
688
689         if (current_entry_id==new_entry->id || current_entry_id==0) {
690                 editing_trace ("no (no matches found).\n");
691                 return NULL;
692         };
693
694         Entry *current_entry = music_source_query_entry (self, new_entry->type, current_entry_id,
695                                                          "_music_source_try_merge");
696         editing_trace ("%s %i matches.\n", ENTRY_PF(current_entry));
697
698         for (int p=0; p<entry_n_properties[new_entry->type]; p++) {
699                 // FIXME: here's something, if an entry is found with a trivial field not set, and this
700                 // is set in the new entry, should we set it ??
701
702                 if ((schema[new_entry->type][p].flags & PROPERTY_MERGEABLE)) {
703
704                         if (schema[new_entry->type][p].type==PROPERTY_TYPE_STRING) {
705                                 // Concatenate mergeable strings
706                                 // FIXME: should this be unnecessary eventually ? Its only used for stuff like
707                                 // comments at the moment.
708                                 const char *current_value = entry_get_property(current_entry, p);
709                                 const char *new_value = entry_get_property(new_entry, p);
710                                 if (new_value != NULL) {
711                                         if (current_value==NULL) entry_set_property(current_entry, p, new_value);
712                                         else {
713                                                 char *string = g_strconcat(current_value, " ", new_value, NULL);
714                                                 // FIXME: can we do this a faster way ?
715                                                 music_source_update_entry_property (self, current_entry->type,
716                                                                                     current_entry->id, p, string);
717                                                 g_free (string);
718                                         };
719                                 };
720                         } else if (IS_FOREIGN_KEY(schema[new_entry->type][p].type)) {
721                                 // Choose newest out of mergeable entries
722                                 Entry *current_value = entry_get_property(current_entry, p);
723                                 Entry *new_value = entry_get_property(new_entry, p);
724                                 if (!entry_match(current_value, new_value)) {
725                                         editing_trace ("Merging with %s %i - on property %s values do not match!"
726                                                        "Will take new values! Old %i new %i\n", ENTRY_PF(current_entry),
727                                                        schema[new_entry->type][p].name,
728                                                        current_value->id, new_value->id);
729
730                                         // FIXME: can we do this a faster way?
731                                         music_source_update_entry_property (self, current_entry->type,
732                                                                             current_entry->id, p, new_value);
733
734                                         // The new_value entry will have changed emitted on it, because it will be emitted in the
735                                         // above update_unique_entry_property for its child (our current_entry).
736                                         // We need to emit changed on the old value, since its children will have changed.
737                                         // In particular, for recordings (the only mergeable entry at the moment anyway) if they
738                                         // have no children any more they are deleted in music_source::recording-changed. FIXME:
739                                         // should we do this for all types of entry?
740
741                                         // FIXME: sort this out.
742                                         //_music_source_queue_signal (music_source, MUSIC_SOURCE_SIGNAL_ENTRY_CHANGED,
743                                         //                                                  current_value->type, current_value->id);
744                                 };
745                         } else {
746                                 g_warning ("Schema error in %s.%s - this property type cannot be merged.\n",
747                                            entry_type_name[new_entry->type], schema[new_entry->type][p].name);
748                         };
749                 };
750         };
751
752         return current_entry;
753 };
754
755
756 358 // Given an entry where the shadow properties are NULL, fills them with their shadowee's
757 // value.
758 //
759 451 static void fill_entry_shadow_properties (Entry *entry) {
760 358         for (int i=0; i<entry_n_properties[entry->type]; i++) {
761                 const EntryProperty *prop = &schema[entry->type][i];
762                 if (prop->flags & PROPERTY_SHADOW) {
763                         if (entry_get_property(entry, i)!=NULL)
764                                 // Shadow field has its own value - no need to do anything.
765                                 continue;
766
767                         void *shadow_value = entry_get_distant_property(entry, prop->shadow_property_apid);
768
769                         entry_set_property (entry, i, shadow_value);
770                 };
771         };
772 };
773
774 451 /* _music_source_add_recursive: add/update 'entry' in source and call add_recursive on each entry it
775  *                              references.
776  *
777  *  try_to_merge: check if entry can be merged. Should be TRUE, unless you already tried to merge.
778  *       Returns: 'entry' if the entry was not merged. The existing entry if it was merged.
779  */
780 Entry *_music_source_add_entry_recursive (MusicSource *self, Entry *entry, gboolean try_to_merge,
781                                           Entry *caller) {
782         if (entry->source !=NULL) {
783                 if (MUSIC_SOURCE (entry->source)==self)
784                         g_warning("Adding %s %i - already owned by this source.", ENTRY_PF(entry));
785                 else
786                         g_warning("Adding %s %i - already owned by another source", ENTRY_PF(entry));
787         };
788
789 452         process_empty_properties (entry);
790         entry_check (entry);
791 451
792         // Go through every child entry and add/update this too. (Unless it was the caller)
793         //
794         for (int i=0; i<entry_n_properties[entry->type]; i++) {
795                 if (IS_FOREIGN_KEY(schema[entry->type][i].type)) {
796                         Entry *child = entry_get_property(entry, i);
797                         if (child!=NULL && (child->type!=caller->type || child->id!=caller->id)) {
798                                 // If ID is 0 we need to add this entry to the source, otherwise just update.
799                                 //
800                                 if (child->id==0) {
801                                         child = _music_source_add_entry_recursive (self, child, try_to_merge, entry);
802                                         // Memory location could change if was merged
803                                         entry_take_property (entry, i, child);
804                                 }
805                         };
806                 };
807         };
808
809         // Sort out shadow properties now, because the entries we compare with for merging will
810         // have them filled in.
811         if (entry_type_info[entry->type].has_shadowing_properties)
812                 fill_entry_shadow_properties(entry);
813
814         // Search for an existing entry to merge with. FIXME: make this faster ! indexing.
815         //
816         Entry *existing_entry = try_to_merge? try_merge (self, entry): NULL;
817
818         gboolean merged = TRUE;
819         if (existing_entry==NULL) {
820                 merged = FALSE;
821                 MUSIC_SOURCE_GET_CLASS(self)->add_entry_internal (self, entry);
822         } else {
823                 entry_take_last_ref (existing_entry, "_music_source_add_entry_recursive");
824                 entry = existing_entry;
825         };
826
827         if (merged==FALSE)
828                 _music_source_queue_notify (self, NULL, entry);
829         // FIXME: notify on change due to merge
830
831         return entry;
832 }
833
834
835 360 typedef struct {
836         Entry *entry;
837
838         /* Duplicates are versioned, so entries can be edited during their change notifications. */
839         int version;
840
841         gboolean shadowing[ENTRY_PROPERTY_MAX];
842         #ifdef ENTRY_TRACK_REFERENCE_OWNERS
843         const char *file; int line;
844         #endif
845 } EntryDuplicate;
846
847 /* _music_source_unref_checkout_copy: Unref duplicates created when entry was checked out. There can
848  *                                    be several different versioned duplicates of the same entry.
849  *     recurse: Unref tree - used by jetsam because their children need unreffing too. */
850 void _unref_checkout_copy_recursive (MusicSource *self, EntryType type, int id, gboolean recurse,
851                                      EntryType caller_type, int caller_id) {
852         Entry *entry = NULL;
853         for (GSList *node=SP->checkout_entry_store; node; ) {
854                 EntryDuplicate *dup = node->data; entry = dup->entry;
855                 if (entry->type==type && entry->id==id) {
856 451                         editing_trace ("_unref_checkout_copy: Unreffing %s %i v%i [%x] (refs %i).\n",
857                                        ENTRY_PF(dup->entry), dup->version, dup->entry, dup->entry->ref_count);
858 360
859                         // We also unref children of jetsam entries. Because checkout copies are refcounted and
860                         // only removed from the checkout store when their refcount==0, this doesn't cause any
861                         // problems where the entry is also referenced somewhere else in the checkout tree.
862                         if (recurse)
863                                 for (int p=0; p<entry_n_properties[type]; p++)
864                                         if (IS_FOREIGN_KEY(schema[type][p].type)) {
865                                                 Entry *foreign = entry_get_property (entry, p);
866                                                 if (foreign!=NULL && foreign->id!=caller_id && foreign->type!=caller_type)
867                                                         _unref_checkout_copy_recursive (self, foreign->type, foreign->id, TRUE,
868                                                                                         type, id);
869                                         };
870
871                         // Unref entry and remove from dup list if ref count of entry reaches 0.
872                         if (entry_unref(entry, _CHECKOUT_STORE_OWNER_ID)) {
873                                 g_slice_free (EntryDuplicate, dup);
874                                 GSList *_node = node; node = node->next;
875                                 SP->checkout_entry_store = g_slist_remove_link(SP->checkout_entry_store, _node);
876 361                         } else
877 360                                 node=node->next;
878                 } else node=node->next;
879         };
880
881         g_return_if_fail (entry != NULL);
882 };
883
884 445 gboolean _music_source_entry_is_checked_out (MusicSource *self, EntryType type, int id) {
885         for (GSList *node=SP->checkout_entry_store; node; node=node->next) {
886                 EntryDuplicate *dup   = node->data;
887                 Entry          *entry = dup->entry;
888                 if (entry->type == type && entry->id == id)
889                         return TRUE;
890         };
891         return FALSE;
892 }
893
894 451 GSList *_music_source_list_checked_out_entries (MusicSource *self) {
895         GSList *result = NULL;
896         for (GSList *node=SP->checkout_entry_store; node; node=node->next) {
897                 EntryDuplicate *dup = node->data;
898                 if (dup->version == 0)
899                         result = g_slist_prepend (result, dup->entry);
900         };
901         return result;
902 };
903
904 360 void _music_source_unref_checkout_copies (MusicSource *self, EntryType type, int id) {
905         _unref_checkout_copy_recursive (self, type, id, FALSE, -1, -1);
906 };
907
908 445 /* _music_source_count_checkout_duplicate_references: Return number of references on a real entry
909  *                                                    that are held by duplicate copies of
910  *                                                    checked-out entries.
911  *
912  *    If a real entry is only referenced by duplicates it needs removing from the source.
913  */
914 int _music_source_count_checkout_duplicate_references (MusicSource *self, Entry *entry) {
915         // FIXME: would be faster to cache these values in a hash table and update them when we make the
916         // duplicates.
917         int count = 0;
918         GSList *reference_holder_list = entry_type_info[entry->type].foreign_key_owners;
919         for (GSList *rh_node=reference_holder_list; rh_node; rh_node=rh_node->next) {
920                 const guint32 apid = GPOINTER_TO_INT(rh_node->data);
921                 for (GSList *node=SP->checkout_entry_store; node; node=node->next) {
922                         EntryDuplicate *duplicate = node->data;
923                         if (duplicate->entry->type != APID_GET_TYPE(apid))
924                                 continue;
925                         if (entry_get_property (duplicate->entry, APID_GET_PROPERTY_ID(apid)) == entry)
926                                 count++;
927                 }
928         };
929         return count;
930 }
931
932 360 // Free any entries which are still checked out - called from finalize().
933 static void checkout_store_finalise (MusicSource *self) {
934 364         gboolean printed_header = FALSE; int total = 0;
935 360         for (GSList *node=SP->checkout_entry_store; node; node=node->next) {
936                 EntryDuplicate *duplicate = node->data;
937                 if (!printed_header) {
938                         g_message ("\n%s: %i outstanding checkouts:", SP->name!=NULL? SP->name: "<no src name>",
939                                    g_slist_length(SP->checkout_entry_store));
940                         printed_header = TRUE;
941                 };
942
943 364                 total ++;
944 372                 g_message ("Entry %s %i v%i:\n", ENTRY_PF(duplicate->entry), duplicate->version);
945 360                 entry_dump (duplicate->entry);
946                 entry_unref (duplicate->entry, _CHECKOUT_STORE_OWNER_ID);
947         };
948 372
949 364         if (total != 0)
950                 g_warning ("%i entries still checked out when the source was finalised.\n", total);
951 372
952 360         g_slist_free(SP->checkout_entry_store);
953 361 };
954 360
955 372 /* update_shadowing_property:
956  *    shadower_old: If this is a non-NULL pointer to a NULL pointer, the entry will be duplicated
957  *                  before the property gets updated.
958  *         returns: TRUE if property was changed. */
959 static gboolean update_shadowing_property(MusicSource *self, Entry *shadower, int
960                                           shadowing_property_id, void *old_shadowee_value,
961                                           void *new_shadowee_value, Entry **p_duplicate) {
962 452         //printf ("update_shadowing_property: %s[%i] %x.%s.\n", ENTRY_PF(shadower), shadower,
963 372         //        schema[shadower->type][shadowing_property_id].name); fflush (stdout);
964         g_return_val_if_fail (shadowing_property_id >= 0
965                                && shadowing_property_id < entry_n_properties[shadower->type], FALSE);
966         if (entry_value_match(schema[shadower->type][shadowing_property_id].type, old_shadowee_value,
967                                                   entry_get_property(shadower, shadowing_property_id))) {
968                 if (p_duplicate==NULL)
969                         entry_set_property (shadower, shadowing_property_id, new_shadowee_value);
970                 else {
971                         if ((*p_duplicate)==NULL) {
972                                 *p_duplicate = entry_duplicate(shadower, _CHECKOUT_STORE_OWNER_ID);
973                                 _entry_set_id (*p_duplicate, shadower->id);
974
975                                 // FIXME: we don't actually need to do this for any reason other than notify expects
976                                 // to remove it from here again.
977                                 EntryDuplicate *dup = g_slice_new(EntryDuplicate);
978                                 dup->entry = *p_duplicate;
979                                 SP->checkout_entry_store = g_slist_append(SP->checkout_entry_store, dup);
980                         };
981                         entry_set_property (shadower, shadowing_property_id, new_shadowee_value);
982                 };
983                 return TRUE;
984         };
985         return FALSE;
986 };
987
988 /* update_shadowers: Update entries that shadow an entry that was checked out, but aren't themselves
989  *                   checked out. This is a horrible function in every way. */
990 static void update_shadowers (MusicSource *self, Entry *old_entry, Entry *new_entry,
991                               gboolean types_already_updated[]) {
992 452         editing_trace ("\tupdate_shadowers [%s %i]\n", ENTRY_PF(old_entry));
993 372         for (GSList *node=entry_type_info[new_entry->type].shadowees; node; node=node->next) {
994                 EntryType foreign_type = APID_GET_TYPE(GPOINTER_TO_UINT(node->data));
995                 if (types_already_updated[foreign_type]) continue;
996
997                 // Search for foreign key in foreign type
998                 // FIXME: now shadowing entries must hold a foreign key to the entry they shadow.
999                 // We should do this a much more complicated way ..
1000                 int p;
1001                 for (p=0; p<entry_n_properties[foreign_type]; p++)
1002                         if (schema[foreign_type][p].type == new_entry->type) break;
1003
1004                 if (p>=entry_n_properties[foreign_type]) {
1005                         g_warning ("_music_source_checkin_entry_internal: Unable to connect %s %i with "
1006                                            "%s. Note that this code really needs a better implementation, I'm "
1007                                            "sorry it's not been done already :(", ENTRY_PF(new_entry),
1008                                            entry_type_name[foreign_type]);
1009                         break;
1010                 };
1011
1012                 // FIXME: this is really slow, and we do it a lot
1013
1014                 GSList *relations = music_source_query_relations(self, new_entry->type, new_entry->id,
1015 452                                                                  MAKE_APID(foreign_type, p), "checkin");
1016 372
1017                 // We assume apids of the same type in the shadowee list are contiguous, so we can do all
1018                 // the properties for one type in a single pass (quite a significant optimisation :).
1019                 int additional_properties = -1;
1020
1021                 for (GSList *rel_node=relations; rel_node; rel_node=rel_node->next) {
1022                         // update_shadowing_property will duplicate shadower_old into shadower_new if it updates
1023                         // a property, or if shadower_new!=NULL it will update the existing duplicate there.
1024                         Entry *shadower = rel_node->data, *duplicate = NULL;
1025
1026                         int i=0; GSList *cur_node = node; gboolean changed = FALSE;
1027                         do {
1028                                 const guint32 shadower_apid = GPOINTER_TO_UINT(cur_node->data);
1029                                 int p = APID_GET_PROPERTY_ID(APID_GET_PROPERTY(shadower_apid).shadow_property_apid);
1030
1031                                 void *old_val = entry_get_property(old_entry, p),
1032                                      *new_val = entry_get_property(new_entry, p);
1033                                 changed |= update_shadowing_property(self, shadower,
1034                                                                      APID_GET_PROPERTY_ID(shadower_apid), old_val,
1035                                                                      new_val, &duplicate);
1036
1037                                 // Count the number of extra properties the first time round.
1038                                 cur_node = cur_node->next;
1039                                 if (additional_properties == -1) {
1040                                         if (cur_node == NULL
1041                                               || APID_GET_TYPE(GPOINTER_TO_UINT(cur_node->data))!=shadower->type) {
1042                                                 //printf ("Found %i additional properties.\n"); fflush (stdout);
1043                                                 additional_properties = i; continue;
1044                                         };
1045                                 } else if (++i > additional_properties) break;
1046 452
1047                                 // FIXME: this is kind of a hack. We update the source's entry before changes to the
1048                                 // shadowee entry are committed. So, we pass the new value of the shadowing property
1049                                 // manually. But we can only pass one at a time, so we have to update the entry
1050                                 // several times. This code is becoming a bit of a nightmare.
1051                                 if (MUSIC_SOURCE_GET_CLASS(self)->update_entry_internal != NULL)
1052                                         MUSIC_SOURCE_GET_CLASS(self)->update_entry_internal (self, shadower, i, new_val);
1053 372                         } while (cur_node);
1054
1055                         if (changed) {
1056                                 g_warn_if_fail (duplicate!=NULL);
1057 452                                 editing_trace ("\tupdated: %s %i.\n", ENTRY_PF(shadower));
1058 372                                 //printf ("Queuing %s %i, %s %ip\n", ENTRY_PF(duplicate), ENTRY_PF(shadower)); fflush (stdout);
1059                                 _music_source_queue_notify (self, duplicate, shadower);
1060                         };
1061
1062                         // If the entry's value for property
1063                         entry_unref (shadower, "checkin");
1064                 };
1065
1066                 types_already_updated[foreign_type] = TRUE;
1067         };
1068 };
1069 360
1070 413
1071 451 /* _music_source_checkout_entry_recursive: This function is called by the checkout_entry methods of
1072 360  *     subclasses. It duplicates the entire entry tree as it is being checked out, for currently
1073  *     three purposes:
1074  *       -> so shadow properties can be updated from their shadowee, if they were matching before
1075  *       -> so entries that were referenced as foreign keys but aren't any more can be detected
1076  *          and removed from the source (if not referenced elsewhere).
1077  *       -> so we can notify entry-changed with both old and new entries (which in turn is for the
1078  *          benefit of genericview, which needs to find both the old and the new positions to emit
1079  *          rows-reordered correctly).
1080  *     Each entry is duplicated seperately rather than as a tree, so that the pointer values of the
1081  *     foreign keys don't change in the duplicated entries.
1082  */
1083 // FIXME: make checking in and checking out fast .. even though we do a lot of shit. Perhaps even
1084 // provide 'lightweight' ones!
1085 451 void _music_source_checkout_entry_recursive (MusicSource *self, Entry *entry) {
1086         editing_trace ("Checking out: %s %i (storing v%i)\n", ENTRY_PF(entry),
1087                        SP->notifying_on_entry_version);
1088 360
1089         // If current version is already checked out and duplicated, just reference the existing entry.
1090         for (GSList *node=SP->checkout_entry_store; node; node=node->next) {
1091                 EntryDuplicate *dup = node->data; Entry *stored_entry = dup->entry;
1092
1093 451                 if (SP->notifying_on_entry != NULL
1094                       && (entry->type==SP->notifying_on_entry->type &&
1095                           entry->id==SP->notifying_on_entry->id)
1096                       && dup->version!=SP->notifying_on_entry_version) {
1097                         editing_trace ("\tignoring stored v%i.\n", SP->notifying_on_entry_version);
1098 360                         continue;
1099 451                 }
1100 360
1101                 if (stored_entry->type==entry->type && stored_entry->id==entry->id) {
1102 451                         editing_trace ("\tusing stored version [dup is v%i].\n", dup->version);
1103 360                         entry_ref (stored_entry, _CHECKOUT_STORE_OWNER_ID);
1104                         return;
1105                 };
1106         };
1107 361
1108 360         EntryDuplicate *duplicate = g_slice_new0(EntryDuplicate);
1109         duplicate->entry = entry_duplicate(entry, _CHECKOUT_STORE_OWNER_ID);
1110         _entry_set_id (duplicate->entry, entry->id);
1111
1112         duplicate->version = SP->notifying_on_entry_version;
1113
1114         SP->checkout_entry_store = g_slist_prepend(SP->checkout_entry_store, duplicate);
1115
1116         for (int i=0; i<entry_n_properties[entry->type]; i++) {
1117                 const EntryProperty *prop = &schema[entry->type][i];
1118
1119                 if (prop->flags & PROPERTY_SHADOW) {
1120                         // Check if property is currently shadowing (no need to do anything on checkin if not.)
1121                 const void *shadowee_value = entry_get_distant_property(entry,
1122                                                                             prop->shadow_property_apid);
1123            if (entry_value_match(prop->type, entry_get_property(entry, i), shadowee_value))
1124                            duplicate->shadowing[i] = TRUE;
1125                 };
1126
1127                 if (IS_FOREIGN_KEY(prop->type)) {
1128                         Entry *value = entry_get_property(entry, i);
1129                         // It's actually valid for entries to be being checked out that haven't been stored in
1130                         // the source yet. It happens when for example an 'entry-added' hook uses
1131                         // music_source_update_entry_property. FIXME: I'm not sure it should, that's why we queue
1132                         // signals until the end of the transaction ....
1133                         if (value!=NULL && value->id > 0)
1134 451                                 _music_source_checkout_entry_recursive (self, value);
1135                 };
1136         };
1137 };
1138
1139
1140 static void update_new_entry_from_duplicate (MusicSource *self, EntryDuplicate *duplicate,
1141                                              GSList **p_jetsam, Entry *new_entry) {
1142         Entry *old_entry = duplicate->entry;
1143         g_return_if_fail (old_entry->type == new_entry->type);
1144
1145         // Found the right entry duplicate - process it and break.
1146         for (int p=0; p<entry_n_properties[new_entry->type]; p++) {
1147                 const EntryProperty *prop = &schema[new_entry->type][p];
1148
1149                 // Update shadowing properties in this entry. This only needs doing if on checkout, the
1150                 // shadow property was == to its shadowee. Any entries which shadow a checked-out entry
1151                 // but aren't themselves checked out are updated below, but this way is faster.
1152                 if ((prop->flags & PROPERTY_SHADOW) && duplicate->shadowing[p]) {
1153                         editing_trace ("\tupdating shadowing property: %s.%s.\n",
1154                                        entry_type_name[new_entry->type], prop->name);
1155                         guint32 shadow_apid = prop->shadow_property_apid;
1156                         update_shadowing_property (self, new_entry, p, entry_get_property(old_entry, p),
1157                                                    entry_get_distant_property(new_entry, shadow_apid), NULL);
1158                 };
1159
1160                 // Check for entries that were referenced but now aren't. If any foreign keys have
1161                 // changed, we store the old value in 'jetsam'.
1162                 if (IS_FOREIGN_KEY(prop->type)) {
1163                         Entry *old_foreign_entry = entry_get_property (old_entry, p),
1164                               *new_foreign_entry = entry_get_property (new_entry, p);
1165
1166                         if (old_foreign_entry != NULL &&
1167                              (new_foreign_entry == NULL || old_foreign_entry->id != new_foreign_entry->id)) {
1168                                 if (g_slist_find ((*p_jetsam), old_foreign_entry)==NULL) {
1169                                         editing_trace ("\tadding to jetsam: %s %i [new %s %i].\n",
1170                                                        ENTRY_PF(old_foreign_entry), ENTRY_PF(entry_get_property(new_entry, p)));
1171                                         (*p_jetsam) = g_slist_prepend((*p_jetsam), old_foreign_entry);
1172                                 }
1173                         };
1174                 };
1175         };
1176
1177         // Update shadowee properties in this entry that are shadowed in entries that weren't
1178         // checked out.
1179         //
1180         // We need to make sure the notify is emitted for all entries that are modified - for
1181         // example, if you change a composition name, the composition may well not be in
1182         // musicsourceview's config, but the recording will probably need updating and might not
1183         // have been checked out
1184
1185         // Ignore types that have already been checked out. FIXME: a better way to do this ?
1186         gboolean types_already_updated[ENTRY_TYPE_COUNT] = {0};
1187         types_already_updated[new_entry->type] = TRUE;
1188         for (GSList *node=SP->checkout_entry_store; node; node=node->next) {
1189                 EntryDuplicate *dup = node->data;
1190                 types_already_updated[dup->entry->type] = TRUE;
1191         };
1192
1193         update_shadowers (self, old_entry, new_entry, types_already_updated);
1194 };
1195
1196 static Entry *checkin_relation (MusicSource *self, Entry *parent_entry, Entry *entry,
1197                                 GSList **p_jetsam, Entry *caller) {
1198         if (entry->id==0)
1199                 // This is a new entry. Add it to the database.
1200                 return _music_source_add_entry_recursive(self, entry, TRUE, caller);
1201         else {
1202                 // Checkin this entry, if it's checked out.
1203                 //const gboolean trivial = (schema[new_entry->type][i].flags & PROPERTY_TRIVIAL);
1204                 const gboolean checked_out = _music_source_entry_is_checked_out
1205                                                (self, parent_entry->type, parent_entry->id);
1206                 //if (!trivial) g_warn_if_fail (checked_out);
1207                 //if (!trivial)*/
1208                 if (checked_out)
1209                         return _music_source_checkin_entry_recursive (self, entry, p_jetsam,
1210                                                                       parent_entry);
1211                 else {
1212                         return entry;
1213                 }
1214                 /*else
1215                         child = __music_source_checkin_entry_recursive (self, child, p_jetsam, TRUE,
1216                                                                                                                    new_entry);*/
1217         }
1218 };
1219
1220 /* FIXME: kill 'touch_only'. */
1221 Entry *__music_source_checkin_entry_recursive (MusicSource *self, Entry *new_entry,
1222                                               GSList **p_jetsam, gboolean touch_only,
1223                                               Entry *caller) {
1224         editing_trace ("music_source_checkin_entry_recursive: %s %i called by %s %i\n",
1225                        ENTRY_PF(new_entry), ENTRY_PF(caller));
1226
1227 360         Entry *old_entry = NULL;
1228 451         for (GSList *node = SP->checkout_entry_store; node; node = node->next) {
1229                 EntryDuplicate *duplicate = node->data;
1230
1231                 if (new_entry == SP->notifying_on_entry
1232                       && duplicate->version != SP->notifying_on_entry_version)
1233                         continue;
1234
1235                 if (new_entry->type != duplicate->entry->type || new_entry->id != duplicate->entry->id)
1236                         continue;
1237 372
1238                 // Removal from the checkout entry store will be done when the notify is emitted.
1239 451                 update_new_entry_from_duplicate (self, duplicate, p_jetsam, new_entry);
1240                 old_entry = duplicate->entry;
1241 372                 break;
1242 360         };
1243
1244         // A jetsam entry is no longer referenced anywhere in the checked-out tree of entries. It should
1245         // be 'touched' in the subclass to see if it now needs deleting from the source - it won't be
1246         // checked back in (it shouldn't have changed, because it's no longer part of the tree that was
1247         // checked out) so we need to remove its checkout duplicate specially.
1248 358         //
1249 360         for (GSList *node=*p_jetsam; node; node=node->next) {
1250 451                 Entry *jetsam_entry = node->data;
1251                 editing_trace ("\t%s %i is jetsam.\n", ENTRY_PF(jetsam_entry));
1252                 _unref_checkout_copy_recursive (self, jetsam_entry->type, jetsam_entry->id, TRUE, -1, -1);
1253 360         };
1254
1255 452         process_empty_properties (new_entry);
1256         entry_check (new_entry);
1257 360
1258         // All the 'jetsam' (entries that we referenced by foreign keys when checked out, but have now
1259         // been replaced with something else) are still checked out - we've remove all their stored
1260         // properties (which is all it takes to effectively check them back in) and we need to test if
1261         // they are still referenced anywhere, and delete them if they aren't. This is the source's job.
1262         // FIXME: this must slow checkins down a LOT.
1263         //      => Perhaps we could defer things even later - the reason we remove dead entries straight
1264         //         away is so the view doesn't have to check if each node has children, remember. So this
1265         //         code only needs to be run next time the view updates, which mean we check some entries
1266         //         fewer times.
1267         //      => Also, for 'general' entries (ie. artist) it already has to check each node for children,
1268         //         so we don't actually have to worry about pruning the dead ones - we could do it any time
1269         //         while idle or whatever.
1270
1271         if (old_entry!=NULL)
1272                 g_warn_if_fail (old_entry->type==new_entry->type && old_entry->id==new_entry->id);
1273
1274 451         if (new_entry->source!=self && new_entry->source!=NULL)
1275                 g_warning ("music-source-importing::checkin-entry: checking in %s %i but belongs to a "
1276                            "different source!", ENTRY_PF(new_entry));
1277
1278         // Add/update every entry this one references.
1279         //
1280         for (int i=0; i < entry_n_properties[new_entry->type]; i++) {
1281                 if (IS_FOREIGN_KEY(schema[new_entry->type][i].type)) {
1282                         Entry *child = entry_get_property(new_entry, i);
1283
1284                         if (child==NULL)
1285                                 continue;
1286
1287                         if (child->type==caller->type && child->id==caller->id) {
1288                                 // This entry called us. All we need to do is lower the ref count of the duplicate, if
1289                                 // there is one.
1290                                 if (old_entry!=NULL) {
1291                                         Entry *old_child = entry_get_property (old_entry, i);
1292                                         if (old_child != NULL)
1293                                                 _music_source_unref_checkout_copies (self, old_child->type, old_child->id);
1294                                 };
1295
1296                                 continue;
1297                         }
1298
1299                         child = checkin_relation (self, new_entry, child, p_jetsam, caller);
1300
1301                         entry_check (new_entry);
1302                         // Update the reference, in case the child entry was merged with an existing one.
1303 452                         //entry_dump (child);
1304 451                         entry_take_property (new_entry, i, child);
1305                 };
1306         };
1307
1308         // See if the updated entry now matches an existing one. If we do merge, we need to change all
1309         // references to old entry to point to the new one (no signals since externally nothing has
1310         // changed).
1311         Entry *existing_entry = try_merge (self, new_entry);
1312         if (existing_entry != NULL) {
1313                 MUSIC_SOURCE_GET_CLASS(self)->update_foreign_keys (self, new_entry, existing_entry);
1314
1315                 // Remove from the source, and remove the duplicates of it.
1316                 editing_trace ("_music_source_checkin_entry_recursive: Merged %s %i to %s %i.\n",
1317                                ENTRY_PF(new_entry), ENTRY_PF(existing_entry));
1318                 MUSIC_SOURCE_GET_CLASS(self)->remove_entry_internal (self, new_entry->type, new_entry->id);
1319
1320                 new_entry = existing_entry;
1321                 entry_unref (new_entry, "_music_source_try_merge");
1322         }
1323
1324         // Sources such as Library update their stored representation of the entry now. We do this if a
1325         // merge happened too in case any mergeable properties were changed in the process.
1326         if (MUSIC_SOURCE_GET_CLASS(self)->update_entry_internal != NULL)
1327 452                 MUSIC_SOURCE_GET_CLASS(self)->update_entry_internal (self, new_entry, -1, NULL);
1328 451
1329         g_warn_if_fail (new_entry->id > 0);
1330
1331         // If old_entry is NULL, entry->id==0 so the entry will have been added instead, and entry-added
1332         // emitted.
1333         if (old_entry!=NULL)
1334                 // FIXME: only emit if entry was actually changed, I think
1335                 _music_source_queue_notify (self, old_entry, new_entry);
1336
1337         // We return the entry for add_recursive to keep track, since any entries being added won't be
1338         // in the db yet so won't have been updated
1339         return new_entry;
1340 };
1341
1342 /* FIXME: wrapper to not expose 'touch_only' flag (see above). */
1343 Entry *_music_source_checkin_entry_recursive (MusicSource *self, Entry *new_entry,
1344                                               GSList **p_jetsam, Entry *caller) {
1345         return __music_source_checkin_entry_recursive (self, new_entry, p_jetsam, FALSE, caller);
1346 };
1347 413
1348 360 /***************************************************************************************************
1349  * Change notification
1350  */
1351
1352 typedef struct {
1353         int signal_id;
1354 //      Entry *entry;
1355         Entry *old_entry, *entry;
1356 } SignalQueueEntry;
1357
1358
1359 358 typedef struct {
1360 393         MusicSource *source;
1361 358         EntryNotifyFunc callback;
1362         EntryType detail;
1363 393         void *user_data; gboolean is_object;
1364 358
1365         int current_entry_version;
1366 } EntryNotifyClosure;
1367
1368 393 static EntryNotifyClosure *connect_entry_notify (MusicSource *self, EntryType detail,
1369 413                                                  const gboolean watch_only,
1370 393                                                  EntryNotifyFunc callback, void *user_data) {
1371 358         EntryNotifyClosure *closure = g_slice_new(EntryNotifyClosure);
1372 393         closure->source = self;
1373 358         closure->callback = callback;
1374         closure->detail = detail;
1375         closure->user_data = user_data;
1376 393         closure->is_object = FALSE;
1377 358
1378         closure->current_entry_version = 0;
1379
1380         if (!watch_only) SP->notify_handlers = g_slist_append(SP->notify_handlers,       closure);
1381         else       SP->notify_watch_handlers = g_slist_append(SP->notify_watch_handlers, closure);
1382 393         return closure;
1383 413 };
1384 393
1385 // FIXME: this function is in wrong place in file ..
1386 void music_source_connect_entry_notify (MusicSource *self, EntryType detail,
1387                                         const gboolean watch_only, EntryNotifyFunc callback,
1388                                         void *user_data) {
1389 413         connect_entry_notify (self, detail, watch_only, callback, user_data);
1390 393 };
1391
1392 static void destroy_notify_function(EntryNotifyClosure *closure, GObject *where_the_object_was) {
1393         // Stop the weak ref being removed as its already gone
1394         closure->is_object = FALSE;
1395 413
1396 393         music_source_disconnect_entry_notify (closure->source, closure->detail, closure->callback,
1397 429                                               closure->user_data);
1398 413 };
1399
1400 void music_source_disconnect_entry_notify (MusicSource *self, EntryType detail,
1401 393                                            EntryNotifyFunc callback, void *user_data) {
1402         int closure_match (EntryNotifyClosure *closure) {
1403 413                 if (closure->callback==callback && closure->detail==detail
1404 393                       && closure->user_data==user_data)
1405                         return 0;
1406                 return 1;
1407         };
1408 413
1409         GSList *node = NULL;
1410 393         node = g_slist_find_custom(SP->notify_handlers, NULL, (GCompareFunc)closure_match);
1411         if (node==NULL) {
1412                 node = g_slist_find_custom(SP->notify_watch_handlers, NULL, (GCompareFunc)closure_match);
1413                 SP->notify_watch_handlers = g_slist_remove_link(SP->notify_watch_handlers, node);
1414         } else
1415                 SP->notify_handlers = g_slist_remove_link(SP->notify_handlers, node);
1416 413
1417 393         if (node==NULL) {
1418                 g_warning ("music_source_disconnect_entry_notify: Unable to find callback.");
1419                 return;
1420         };
1421 413
1422         EntryNotifyClosure *closure = node->data;
1423 393         if (closure->is_object)
1424                 g_object_weak_unref (closure->user_data, (GWeakNotify)destroy_notify_function, closure);
1425         g_slice_free (EntryNotifyClosure, closure);
1426 };
1427
1428 void music_source_connect_entry_notify_object (MusicSource *self, EntryType detail,
1429                                                const gboolean watch_only, EntryNotifyFunc callback,
1430                                                GObject *object) {
1431 413         g_return_if_fail (G_IS_OBJECT(object));
1432 393         EntryNotifyClosure *closure = connect_entry_notify(self, detail, watch_only, callback, object);
1433         closure->is_object = TRUE;
1434         g_object_weak_ref (object, (GWeakNotify)destroy_notify_function, closure);
1435 413 };
1436 393
1437 358
1438 /* _music_source_notify: Signal to all notify handlers that 'old_entry' has changed, or that
1439  *                       old_entry has been removed or new_entry has been added.
1440  *
1441  *    The first lot of signal handlers can modify the entry further - we watch for checkins, and if
1442  *    this happens the changed signal is reemitted to handlers which have already been called.
1443  *    Handlers which weren't yet called are still only called once but are passed the new changes.
1444  *    Handlers which don't change entries are called last, so they only ever recieve one changed
1445  *    notification. */
1446 void _music_source_notify (MusicSource *self, Entry *old_entry, Entry *current_entry) {
1447         g_return_if_fail (old_entry!=NULL || current_entry!=NULL);
1448         if (old_entry!=NULL && current_entry!=NULL)
1449                 g_return_if_fail (old_entry->type==current_entry->type);
1450         EntryType type = current_entry==NULL? old_entry->type: current_entry->type;
1451
1452         // This does nothing on removals, which is okay because surely nobody will be modifying an entry
1453         // that's about to be removed ...
1454
1455 361         SP->notifying_on_entry = current_entry==NULL? old_entry: current_entry;
1456 358         SP->notifying_on_entry_version = 1;
1457
1458 445         notify_trace ("Notify: %s %i.\n", ENTRY_PF(SP->notifying_on_entry));
1459 358
1460         // Emit notify and store the handler's current state; every time the entry is modified start
1461         // again and notify handlers that have already been called of the new changes.
1462 372
1463 358 restart:;
1464         int current_version = SP->notifying_on_entry_version;
1465         for (GSList *node=SP->notify_handlers; node; node=node->next) {
1466                 EntryNotifyClosure *closure = node->data;
1467
1468                 if (closure->detail!=-1 && closure->detail!=type)
1469                         continue;
1470
1471                 Entry *specific_old_entry = NULL;
1472                 if (closure->current_entry_version == SP->notifying_on_entry_version)
1473                         continue;
1474
1475                 if (closure->current_entry_version == 0)
1476                         specific_old_entry = old_entry;
1477                 else {
1478                         // specific_old_entry is closure->current_entry_version of entry in checkout_store
1479                         // FIXME: speed up
1480                         for (GSList *node=SP->checkout_entry_store; node; node=node->next) {
1481                                 EntryDuplicate *duplicate = node->data;
1482 451                                 notify_trace ("Looking at %s %i v%i, want %s %i.\n", ENTRY_PF(duplicate->entry),
1483                                               duplicate->version, ENTRY_PF(old_entry));
1484 358                                 if (duplicate->entry->type==old_entry->type && duplicate->entry->id==old_entry->id
1485                                       && duplicate->version == closure->current_entry_version) {
1486                                         specific_old_entry = duplicate->entry;
1487                                         break;
1488                                 };
1489                         };
1490                         if (specific_old_entry==NULL) {
1491                                 g_warning ("Unable to find %s %i v%i in checkout store [notifying on v%i].\n",
1492                                            ENTRY_PF(old_entry), closure->current_entry_version,
1493                                            SP->notifying_on_entry_version);
1494                                 return;
1495                         };
1496                 };
1497
1498 361                 notify_trace ("Calling %x: %x %s %i v%i -> %x %s %i v%i\n", closure->callback,
1499                               specific_old_entry, ENTRY_PF(specific_old_entry),
1500                               closure->current_entry_version, current_entry, ENTRY_PF(current_entry),
1501                               SP->notifying_on_entry_version);
1502 358                 closure->callback (self, specific_old_entry, current_entry, closure->user_data);
1503                 closure->current_entry_version = SP->notifying_on_entry_version;
1504
1505                 // Entry has been modified further
1506                 if (SP->notifying_on_entry_version > current_version)
1507                         goto restart;
1508         };
1509
1510         // Reset current states for next execution.
1511         for (GSList *node=SP->notify_handlers; node; node=node->next) {
1512                 EntryNotifyClosure *closure = node->data;
1513                 closure->current_entry_version = 0;
1514         };
1515
1516         // These shouldn't modify entry state
1517         for (GSList *node=SP->notify_watch_handlers; node; node=node->next) {
1518                 EntryNotifyClosure *closure = node->data;
1519                 if (closure->detail!=-1 && closure->detail!=type)
1520                         continue;
1521
1522 361                 notify_trace ("Calling watch-only %x: %x %s %i v%i -> %x %s %i v%i\n", closure->callback,
1523                               old_entry, ENTRY_PF(old_entry), closure->current_entry_version,
1524                               current_entry, ENTRY_PF(current_entry), SP->notifying_on_entry_version);
1525 358                 closure->callback (self, old_entry, current_entry, closure->user_data);
1526
1527                 g_warn_if_fail (current_version == SP->notifying_on_entry_version);
1528         };
1529
1530         // Unref all versions of entry in the checkout store, if there will be any.
1531 445         notify_trace ("current %x, old %x, ver %i\n", current_entry, old_entry, SP->notifying_on_entry_version);
1532 451         if (current_entry!=NULL && (old_entry!=NULL || SP->notifying_on_entry_version > 1))
1533 360                 _music_source_unref_checkout_copies (self, current_entry->type, current_entry->id);
1534 358
1535         SP->notifying_on_entry = NULL;
1536         SP->notifying_on_entry_version = 0;
1537 445         notify_trace ("_music_source_notify: complete - [%s %i].\n\n", ENTRY_PF(current_entry));
1538 358 };
1539
1540
1541 typedef struct {
1542         Entry *old_entry, *current_entry;
1543 } NotifyQueueEntry;
1544
1545 void _music_source_queue_notify (MusicSource *self, Entry *old_entry, Entry *current_entry) {
1546 361         notify_trace ("queue notify: %s %i -> %s %i [%x]\n", ENTRY_PF(old_entry),
1547                       ENTRY_PF(current_entry), SP->notifying_on_entry);
1548 451         if (SP->notifying_on_entry != NULL
1549              && ((current_entry != NULL
1550                    && (SP->notifying_on_entry->type == current_entry->type &&
1551                        SP->notifying_on_entry->id   == current_entry->id))     ||
1552                  (old_entry != NULL
1553                    && (SP->notifying_on_entry->type == old_entry->type &&
1554                        SP->notifying_on_entry->id   == old_entry->id)))) {
1555 358                 notify_trace ("Already emitting (at ver %i).\n", SP->notifying_on_entry_version);
1556                 // Currently emitting notify on this entry - register the fact that it has changed, but
1557                 // don't queue a new signal.
1558 445                 SP->notifying_on_entry_version ++;
1559 358                 return;
1560         };
1561
1562 361         // Remove duplicate signals. We also need to remove the reference that normally
1563         // _music_source_notify would have removed after emission.
1564         for (GSList *node=SP->notify_queue; node; node=node->next) {
1565                 NotifyQueueEntry *s = node->data;
1566                 if (s->old_entry==old_entry && s->current_entry==current_entry) {
1567                         notify_trace ("\tthis is already queued.\n");
1568 451
1569 361                         if (current_entry!=NULL)
1570                                 _music_source_unref_checkout_copies (self, current_entry->type, current_entry->id);
1571                         return;
1572                 };
1573         };
1574
1575 358         NotifyQueueEntry *s = g_slice_new(NotifyQueueEntry);
1576         s->old_entry = old_entry;
1577         s->current_entry = current_entry;
1578         SP->notify_queue = g_slist_append(SP->notify_queue, s);
1579 451
1580         /*if (current_entry != NULL)
1581                 entry_ref (current_entry, "MusicSource notify queue");*/
1582 358 };
1583
1584 445 // FIXME: returns true when notifys are queued but not active, so the name is a little inaccurate..
1585 gboolean _music_source_inside_notify (MusicSource *self) {
1586         return (SP->notify_queue != NULL) || (SP->notifying_on_entry != NULL);
1587 };
1588
1589 361 // FIXME: can we optimise the queue more before we emit it?
1590 451 /*    caller: used to prevent an entry in the process of removal being touched and notified again.
1591  *   Returns: TRUE if notify queue is now entry (therefore, FALSE means the caller was called from
1592  *            inside a notification function.)
1593 361  */
1594 451 gboolean _music_source_emit_queued_notifications (MusicSource *self, Entry *caller) {
1595 452         if (SP->notifying_on_entry != NULL) {
1596                 //printf ("emit_queued_notifications: already notifying.\n"); fflush (stdout);
1597 451                 return FALSE;
1598 452         }
1599 451
1600 361         //gboolean caller_emitted = FALSE;
1601 358
1602         do {
1603                 GSList *queue_copy = g_slist_copy(SP->notify_queue);
1604                 g_slist_free (SP->notify_queue); SP->notify_queue = NULL;
1605
1606                 for (GSList *node=queue_copy; node; node=node->next) {
1607                         NotifyQueueEntry *s = node->data;
1608 361
1609                         // Check old_entry because this is only useful to removal notifications.
1610                         /*printf ("old_entry: %x caller %x\n", s->old_entry, caller); fflush (stdout);
1611                         if (caller!=NULL && s->old_entry==caller) {
1612                                 if (caller_emitted) goto skip;
1613                                 caller_emitted = TRUE;
1614                         };*/
1615 358                         _music_source_notify (self, s->old_entry, s->current_entry);
1616 361
1617 451                         /*if (s->current_entry != NULL) {
1618                                 editing_trace ("\tcurrent entry %s %i [%x]: %i refs.\n", ENTRY_PF(s->current_entry),
1619                                                s->current_entry, s->current_entry->ref_count);
1620                                 entry_dump (s->current_entry);
1621                                 entry_unref (s->current_entry, "MusicSource notify queue");
1622                         }*/
1623 361 //skip:
1624 358                         g_slice_free (NotifyQueueEntry, s);
1625                 };
1626 361                 g_slist_free (queue_copy);
1627 445         } while (SP->notify_queue != NULL);
1628
1629 361         notify_trace ("emit_queued_notifications: Done.\n");
1630 451         return TRUE;
1631 358 };
1632
1633
1634 360 void _music_source_emit(MusicSource *self, MusicSourceSignal signal_id, GQuark detail, ...) {
1635         va_list ap; va_start (ap, detail);
1636         g_signal_emit_valist (self, signal_index[signal_id], detail, ap);
1637         va_end (ap);
1638
1639         // FIXME: reimplement is-empty toggle, in a better way.
1640         // FIXME: we could do this more efficiently by only notifying is-empty when
1641         // the state has actually changed.
1642         //
1643         //GSignalQuery signal_query; g_signal_query(signal_index[signal_id], &signal_query);
1644         //printf("music_source_emit: %s\n", signal_query.signal_name);fflush(stdout);
1645         //if (signal_id==MUSIC_SOURCE_SIGNAL_RECORDING_ADDED || signal_id==MUSIC_SOURCE_SIGNAL_RECORDING_REMOVED)
1646         //      g_object_notify(G_OBJECT(self), "is-empty");
1647         //printf("music_source_emit: done\n");fflush(stdout);
1648 };
1649
1650
1651 358 /***************************************************************************************************
1652  * Debugging
1653  */
1654
1655 360 static void editing_trace (const char *format, ...) {
1656         #ifdef MUSIC_SOURCE_TRACE_EDITING
1657         va_list va; va_start (va, format);
1658 372         vprintf (format, va); fflush (stdout);
1659 360         va_end (va);
1660         #endif
1661 };
1662
1663 358 static void notify_trace (const char *format, ...) {
1664         #ifdef MUSIC_SOURCE_TRACE_NOTIFY
1665         va_list va; va_start (va, format);
1666         vprintf (format, va); fflush (stdout);
1667         va_end (va);
1668         #endif
1669 };

Loggerhead is a web-based interface for Bazaar branches