RSS

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

Line Revision Contents
1 17 /*  Calliope Music Player
2 185  *  Copyright 2005-09 Sam Thursfield <ssssam gmail.com>
3 17  *
4  *  This program is free software: you can redistribute it and/or modify
5  *  it under the terms of the GNU General Public License as published by
6  *  the Free Software Foundation, either version 3 of the License, or
7  *  (at your option) any later version.
8  *
9  *  This program is distributed in the hope that it will be useful,
10  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
11  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12  *  GNU General Public License for more details.
13  *
14  *  You should have received a copy of the GNU General Public License
15 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 429 static void check_entry (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         check_entry (entry);
790
791         // Go through every child entry and add/update this too. (Unless it was the caller)
792         //
793         for (int i=0; i<entry_n_properties[entry->type]; i++) {
794                 if (IS_FOREIGN_KEY(schema[entry->type][i].type)) {
795                         Entry *child = entry_get_property(entry, i);
796                         if (child!=NULL && (child->type!=caller->type || child->id!=caller->id)) {
797                                 // If ID is 0 we need to add this entry to the source, otherwise just update.
798                                 //
799                                 if (child->id==0) {
800                                         child = _music_source_add_entry_recursive (self, child, try_to_merge, entry);
801                                         // Memory location could change if was merged
802                                         entry_take_property (entry, i, child);
803                                 }
804                         };
805                 };
806         };
807
808         // Sort out shadow properties now, because the entries we compare with for merging will
809         // have them filled in.
810         if (entry_type_info[entry->type].has_shadowing_properties)
811                 fill_entry_shadow_properties(entry);
812
813         // Search for an existing entry to merge with. FIXME: make this faster ! indexing.
814         //
815         Entry *existing_entry = try_to_merge? try_merge (self, entry): NULL;
816
817         gboolean merged = TRUE;
818         if (existing_entry==NULL) {
819                 merged = FALSE;
820                 MUSIC_SOURCE_GET_CLASS(self)->add_entry_internal (self, entry);
821         } else {
822                 entry_take_last_ref (existing_entry, "_music_source_add_entry_recursive");
823                 entry = existing_entry;
824         };
825
826         if (merged==FALSE)
827                 _music_source_queue_notify (self, NULL, entry);
828         // FIXME: notify on change due to merge
829
830         return entry;
831 }
832
833
834 360 typedef struct {
835         Entry *entry;
836
837         /* Duplicates are versioned, so entries can be edited during their change notifications. */
838         int version;
839
840         gboolean shadowing[ENTRY_PROPERTY_MAX];
841         #ifdef ENTRY_TRACK_REFERENCE_OWNERS
842         const char *file; int line;
843         #endif
844 } EntryDuplicate;
845
846 /* _music_source_unref_checkout_copy: Unref duplicates created when entry was checked out. There can
847  *                                    be several different versioned duplicates of the same entry.
848  *     recurse: Unref tree - used by jetsam because their children need unreffing too. */
849 void _unref_checkout_copy_recursive (MusicSource *self, EntryType type, int id, gboolean recurse,
850                                      EntryType caller_type, int caller_id) {
851         Entry *entry = NULL;
852         for (GSList *node=SP->checkout_entry_store; node; ) {
853                 EntryDuplicate *dup = node->data; entry = dup->entry;
854                 if (entry->type==type && entry->id==id) {
855 451                         editing_trace ("_unref_checkout_copy: Unreffing %s %i v%i [%x] (refs %i).\n",
856                                        ENTRY_PF(dup->entry), dup->version, dup->entry, dup->entry->ref_count);
857 360
858                         // We also unref children of jetsam entries. Because checkout copies are refcounted and
859                         // only removed from the checkout store when their refcount==0, this doesn't cause any
860                         // problems where the entry is also referenced somewhere else in the checkout tree.
861                         if (recurse)
862                                 for (int p=0; p<entry_n_properties[type]; p++)
863                                         if (IS_FOREIGN_KEY(schema[type][p].type)) {
864                                                 Entry *foreign = entry_get_property (entry, p);
865                                                 if (foreign!=NULL && foreign->id!=caller_id && foreign->type!=caller_type)
866                                                         _unref_checkout_copy_recursive (self, foreign->type, foreign->id, TRUE,
867                                                                                         type, id);
868                                         };
869
870                         // Unref entry and remove from dup list if ref count of entry reaches 0.
871                         if (entry_unref(entry, _CHECKOUT_STORE_OWNER_ID)) {
872                                 g_slice_free (EntryDuplicate, dup);
873                                 GSList *_node = node; node = node->next;
874                                 SP->checkout_entry_store = g_slist_remove_link(SP->checkout_entry_store, _node);
875 361                         } else
876 360                                 node=node->next;
877                 } else node=node->next;
878         };
879
880         g_return_if_fail (entry != NULL);
881 };
882
883 445 gboolean _music_source_entry_is_checked_out (MusicSource *self, EntryType type, int id) {
884         for (GSList *node=SP->checkout_entry_store; node; node=node->next) {
885                 EntryDuplicate *dup   = node->data;
886                 Entry          *entry = dup->entry;
887                 if (entry->type == type && entry->id == id)
888                         return TRUE;
889         };
890         return FALSE;
891 }
892
893 451 GSList *_music_source_list_checked_out_entries (MusicSource *self) {
894         GSList *result = NULL;
895         for (GSList *node=SP->checkout_entry_store; node; node=node->next) {
896                 EntryDuplicate *dup = node->data;
897                 if (dup->version == 0)
898                         result = g_slist_prepend (result, dup->entry);
899         };
900         return result;
901 };
902
903 360 void _music_source_unref_checkout_copies (MusicSource *self, EntryType type, int id) {
904         _unref_checkout_copy_recursive (self, type, id, FALSE, -1, -1);
905 };
906
907 445 /* _music_source_count_checkout_duplicate_references: Return number of references on a real entry
908  *                                                    that are held by duplicate copies of
909  *                                                    checked-out entries.
910  *
911  *    If a real entry is only referenced by duplicates it needs removing from the source.
912  */
913 int _music_source_count_checkout_duplicate_references (MusicSource *self, Entry *entry) {
914         // FIXME: would be faster to cache these values in a hash table and update them when we make the
915         // duplicates.
916         int count = 0;
917         GSList *reference_holder_list = entry_type_info[entry->type].foreign_key_owners;
918         for (GSList *rh_node=reference_holder_list; rh_node; rh_node=rh_node->next) {
919                 const guint32 apid = GPOINTER_TO_INT(rh_node->data);
920                 for (GSList *node=SP->checkout_entry_store; node; node=node->next) {
921                         EntryDuplicate *duplicate = node->data;
922                         if (duplicate->entry->type != APID_GET_TYPE(apid))
923                                 continue;
924                         if (entry_get_property (duplicate->entry, APID_GET_PROPERTY_ID(apid)) == entry)
925                                 count++;
926                 }
927         };
928         return count;
929 }
930
931 360 // Free any entries which are still checked out - called from finalize().
932 static void checkout_store_finalise (MusicSource *self) {
933 364         gboolean printed_header = FALSE; int total = 0;
934 360         for (GSList *node=SP->checkout_entry_store; node; node=node->next) {
935                 EntryDuplicate *duplicate = node->data;
936                 if (!printed_header) {
937                         g_message ("\n%s: %i outstanding checkouts:", SP->name!=NULL? SP->name: "<no src name>",
938                                    g_slist_length(SP->checkout_entry_store));
939                         printed_header = TRUE;
940                 };
941
942 364                 total ++;
943 372                 g_message ("Entry %s %i v%i:\n", ENTRY_PF(duplicate->entry), duplicate->version);
944 360                 entry_dump (duplicate->entry);
945                 entry_unref (duplicate->entry, _CHECKOUT_STORE_OWNER_ID);
946         };
947 372
948 364         if (total != 0)
949                 g_warning ("%i entries still checked out when the source was finalised.\n", total);
950 372
951 360         g_slist_free(SP->checkout_entry_store);
952 361 };
953 360
954 372 /* update_shadowing_property:
955  *    shadower_old: If this is a non-NULL pointer to a NULL pointer, the entry will be duplicated
956  *                  before the property gets updated.
957  *         returns: TRUE if property was changed. */
958 static gboolean update_shadowing_property(MusicSource *self, Entry *shadower, int
959                                           shadowing_property_id, void *old_shadowee_value,
960                                           void *new_shadowee_value, Entry **p_duplicate) {
961         //printf ("update_shadowing_property: %s[%i].%s.\n", ENTRY_PF(shadower),
962         //        schema[shadower->type][shadowing_property_id].name); fflush (stdout);
963         g_return_val_if_fail (shadowing_property_id >= 0
964                                && shadowing_property_id < entry_n_properties[shadower->type], FALSE);
965         if (entry_value_match(schema[shadower->type][shadowing_property_id].type, old_shadowee_value,
966                                                   entry_get_property(shadower, shadowing_property_id))) {
967                 if (p_duplicate==NULL)
968                         entry_set_property (shadower, shadowing_property_id, new_shadowee_value);
969                 else {
970                         if ((*p_duplicate)==NULL) {
971                                 *p_duplicate = entry_duplicate(shadower, _CHECKOUT_STORE_OWNER_ID);
972                                 _entry_set_id (*p_duplicate, shadower->id);
973
974                                 // FIXME: we don't actually need to do this for any reason other than notify expects
975                                 // to remove it from here again.
976                                 EntryDuplicate *dup = g_slice_new(EntryDuplicate);
977                                 dup->entry = *p_duplicate;
978                                 SP->checkout_entry_store = g_slist_append(SP->checkout_entry_store, dup);
979                         };
980                         entry_set_property (shadower, shadowing_property_id, new_shadowee_value);
981                 };
982                 return TRUE;
983         };
984         return FALSE;
985 };
986
987 /* update_shadowers: Update entries that shadow an entry that was checked out, but aren't themselves
988  *                   checked out. This is a horrible function in every way. */
989 static void update_shadowers (MusicSource *self, Entry *old_entry, Entry *new_entry,
990                               gboolean types_already_updated[]) {
991         for (GSList *node=entry_type_info[new_entry->type].shadowees; node; node=node->next) {
992                 EntryType foreign_type = APID_GET_TYPE(GPOINTER_TO_UINT(node->data));
993                 if (types_already_updated[foreign_type]) continue;
994
995                 // Search for foreign key in foreign type
996                 // FIXME: now shadowing entries must hold a foreign key to the entry they shadow.
997                 // We should do this a much more complicated way ..
998                 int p;
999                 for (p=0; p<entry_n_properties[foreign_type]; p++)
1000                         if (schema[foreign_type][p].type == new_entry->type) break;
1001
1002                 if (p>=entry_n_properties[foreign_type]) {
1003                         g_warning ("_music_source_checkin_entry_internal: Unable to connect %s %i with "
1004                                            "%s. Note that this code really needs a better implementation, I'm "
1005                                            "sorry it's not been done already :(", ENTRY_PF(new_entry),
1006                                            entry_type_name[foreign_type]);
1007                         break;
1008                 };
1009
1010                 // FIXME: this is really slow, and we do it a lot
1011
1012                 GSList *relations = music_source_query_relations(self, new_entry->type, new_entry->id,
1013                                                                                                                  MAKE_APID(foreign_type, p), "checkin");
1014
1015                 // We assume apids of the same type in the shadowee list are contiguous, so we can do all
1016                 // the properties for one type in a single pass (quite a significant optimisation :).
1017                 int additional_properties = -1;
1018
1019                 for (GSList *rel_node=relations; rel_node; rel_node=rel_node->next) {
1020                         // update_shadowing_property will duplicate shadower_old into shadower_new if it updates
1021                         // a property, or if shadower_new!=NULL it will update the existing duplicate there.
1022                         Entry *shadower = rel_node->data, *duplicate = NULL;
1023
1024                         int i=0; GSList *cur_node = node; gboolean changed = FALSE;
1025                         do {
1026                                 const guint32 shadower_apid = GPOINTER_TO_UINT(cur_node->data);
1027                                 int p = APID_GET_PROPERTY_ID(APID_GET_PROPERTY(shadower_apid).shadow_property_apid);
1028
1029                                 void *old_val = entry_get_property(old_entry, p),
1030                                      *new_val = entry_get_property(new_entry, p);
1031                                 changed |= update_shadowing_property(self, shadower,
1032                                                                      APID_GET_PROPERTY_ID(shadower_apid), old_val,
1033                                                                      new_val, &duplicate);
1034
1035                                 // Count the number of extra properties the first time round.
1036                                 cur_node = cur_node->next;
1037                                 if (additional_properties == -1) {
1038                                         if (cur_node == NULL
1039                                               || APID_GET_TYPE(GPOINTER_TO_UINT(cur_node->data))!=shadower->type) {
1040                                                 //printf ("Found %i additional properties.\n"); fflush (stdout);
1041                                                 additional_properties = i; continue;
1042                                         };
1043                                 } else if (++i > additional_properties) break;
1044                         } while (cur_node);
1045
1046                         if (changed) {
1047                                 g_warn_if_fail (duplicate!=NULL);
1048                                 //printf ("Queuing %s %i, %s %ip\n", ENTRY_PF(duplicate), ENTRY_PF(shadower)); fflush (stdout);
1049                                 _music_source_queue_notify (self, duplicate, shadower);
1050                         };
1051
1052                         // If the entry's value for property
1053                         entry_unref (shadower, "checkin");
1054                 };
1055
1056                 types_already_updated[foreign_type] = TRUE;
1057         };
1058 };
1059 360
1060 413
1061 451 /* _music_source_checkout_entry_recursive: This function is called by the checkout_entry methods of
1062 360  *     subclasses. It duplicates the entire entry tree as it is being checked out, for currently
1063  *     three purposes:
1064  *       -> so shadow properties can be updated from their shadowee, if they were matching before
1065  *       -> so entries that were referenced as foreign keys but aren't any more can be detected
1066  *          and removed from the source (if not referenced elsewhere).
1067  *       -> so we can notify entry-changed with both old and new entries (which in turn is for the
1068  *          benefit of genericview, which needs to find both the old and the new positions to emit
1069  *          rows-reordered correctly).
1070  *     Each entry is duplicated seperately rather than as a tree, so that the pointer values of the
1071  *     foreign keys don't change in the duplicated entries.
1072  */
1073 // FIXME: make checking in and checking out fast .. even though we do a lot of shit. Perhaps even
1074 // provide 'lightweight' ones!
1075 451 void _music_source_checkout_entry_recursive (MusicSource *self, Entry *entry) {
1076         editing_trace ("Checking out: %s %i (storing v%i)\n", ENTRY_PF(entry),
1077                        SP->notifying_on_entry_version);
1078 360
1079         // If current version is already checked out and duplicated, just reference the existing entry.
1080         for (GSList *node=SP->checkout_entry_store; node; node=node->next) {
1081                 EntryDuplicate *dup = node->data; Entry *stored_entry = dup->entry;
1082
1083 451                 if (SP->notifying_on_entry != NULL
1084                       && (entry->type==SP->notifying_on_entry->type &&
1085                           entry->id==SP->notifying_on_entry->id)
1086                       && dup->version!=SP->notifying_on_entry_version) {
1087                         editing_trace ("\tignoring stored v%i.\n", SP->notifying_on_entry_version);
1088 360                         continue;
1089 451                 }
1090 360
1091                 if (stored_entry->type==entry->type && stored_entry->id==entry->id) {
1092 451                         editing_trace ("\tusing stored version [dup is v%i].\n", dup->version);
1093 360                         entry_ref (stored_entry, _CHECKOUT_STORE_OWNER_ID);
1094                         return;
1095                 };
1096         };
1097 361
1098 360         EntryDuplicate *duplicate = g_slice_new0(EntryDuplicate);
1099         duplicate->entry = entry_duplicate(entry, _CHECKOUT_STORE_OWNER_ID);
1100         _entry_set_id (duplicate->entry, entry->id);
1101
1102         duplicate->version = SP->notifying_on_entry_version;
1103
1104         SP->checkout_entry_store = g_slist_prepend(SP->checkout_entry_store, duplicate);
1105
1106         for (int i=0; i<entry_n_properties[entry->type]; i++) {
1107                 const EntryProperty *prop = &schema[entry->type][i];
1108
1109                 if (prop->flags & PROPERTY_SHADOW) {
1110                         // Check if property is currently shadowing (no need to do anything on checkin if not.)
1111                 const void *shadowee_value = entry_get_distant_property(entry,
1112                                                                             prop->shadow_property_apid);
1113            if (entry_value_match(prop->type, entry_get_property(entry, i), shadowee_value))
1114                            duplicate->shadowing[i] = TRUE;
1115                 };
1116
1117                 if (IS_FOREIGN_KEY(prop->type)) {
1118                         Entry *value = entry_get_property(entry, i);
1119                         // It's actually valid for entries to be being checked out that haven't been stored in
1120                         // the source yet. It happens when for example an 'entry-added' hook uses
1121                         // music_source_update_entry_property. FIXME: I'm not sure it should, that's why we queue
1122                         // signals until the end of the transaction ....
1123                         if (value!=NULL && value->id > 0)
1124 451                                 _music_source_checkout_entry_recursive (self, value);
1125                 };
1126         };
1127 };
1128
1129
1130 static void update_new_entry_from_duplicate (MusicSource *self, EntryDuplicate *duplicate,
1131                                              GSList **p_jetsam, Entry *new_entry) {
1132         Entry *old_entry = duplicate->entry;
1133         g_return_if_fail (old_entry->type == new_entry->type);
1134
1135         // Found the right entry duplicate - process it and break.
1136         for (int p=0; p<entry_n_properties[new_entry->type]; p++) {
1137                 const EntryProperty *prop = &schema[new_entry->type][p];
1138
1139                 // Update shadowing properties in this entry. This only needs doing if on checkout, the
1140                 // shadow property was == to its shadowee. Any entries which shadow a checked-out entry
1141                 // but aren't themselves checked out are updated below, but this way is faster.
1142                 if ((prop->flags & PROPERTY_SHADOW) && duplicate->shadowing[p]) {
1143                         editing_trace ("\tupdating shadowing property: %s.%s.\n",
1144                                        entry_type_name[new_entry->type], prop->name);
1145                         guint32 shadow_apid = prop->shadow_property_apid;
1146                         update_shadowing_property (self, new_entry, p, entry_get_property(old_entry, p),
1147                                                    entry_get_distant_property(new_entry, shadow_apid), NULL);
1148                 };
1149
1150                 // Check for entries that were referenced but now aren't. If any foreign keys have
1151                 // changed, we store the old value in 'jetsam'.
1152                 if (IS_FOREIGN_KEY(prop->type)) {
1153                         Entry *old_foreign_entry = entry_get_property (old_entry, p),
1154                               *new_foreign_entry = entry_get_property (new_entry, p);
1155
1156                         if (old_foreign_entry != NULL &&
1157                              (new_foreign_entry == NULL || old_foreign_entry->id != new_foreign_entry->id)) {
1158                                 if (g_slist_find ((*p_jetsam), old_foreign_entry)==NULL) {
1159                                         editing_trace ("\tadding to jetsam: %s %i [new %s %i].\n",
1160                                                        ENTRY_PF(old_foreign_entry), ENTRY_PF(entry_get_property(new_entry, p)));
1161                                         (*p_jetsam) = g_slist_prepend((*p_jetsam), old_foreign_entry);
1162                                 }
1163                         };
1164                 };
1165         };
1166
1167         // Update shadowee properties in this entry that are shadowed in entries that weren't
1168         // checked out.
1169         //
1170         // We need to make sure the notify is emitted for all entries that are modified - for
1171         // example, if you change a composition name, the composition may well not be in
1172         // musicsourceview's config, but the recording will probably need updating and might not
1173         // have been checked out
1174
1175         // Ignore types that have already been checked out. FIXME: a better way to do this ?
1176         gboolean types_already_updated[ENTRY_TYPE_COUNT] = {0};
1177         types_already_updated[new_entry->type] = TRUE;
1178         for (GSList *node=SP->checkout_entry_store; node; node=node->next) {
1179                 EntryDuplicate *dup = node->data;
1180                 types_already_updated[dup->entry->type] = TRUE;
1181         };
1182
1183         update_shadowers (self, old_entry, new_entry, types_already_updated);
1184 };
1185
1186 static Entry *checkin_relation (MusicSource *self, Entry *parent_entry, Entry *entry,
1187                                 GSList **p_jetsam, Entry *caller) {
1188         if (entry->id==0)
1189                 // This is a new entry. Add it to the database.
1190                 return _music_source_add_entry_recursive(self, entry, TRUE, caller);
1191         else {
1192                 // Checkin this entry, if it's checked out.
1193                 //const gboolean trivial = (schema[new_entry->type][i].flags & PROPERTY_TRIVIAL);
1194                 const gboolean checked_out = _music_source_entry_is_checked_out
1195                                                (self, parent_entry->type, parent_entry->id);
1196                 //if (!trivial) g_warn_if_fail (checked_out);
1197                 //if (!trivial)*/
1198                 if (checked_out)
1199                         return _music_source_checkin_entry_recursive (self, entry, p_jetsam,
1200                                                                       parent_entry);
1201                 else {
1202                         return entry;
1203                 }
1204                 /*else
1205                         child = __music_source_checkin_entry_recursive (self, child, p_jetsam, TRUE,
1206                                                                                                                    new_entry);*/
1207         }
1208 };
1209
1210 /* FIXME: kill 'touch_only'. */
1211 Entry *__music_source_checkin_entry_recursive (MusicSource *self, Entry *new_entry,
1212                                               GSList **p_jetsam, gboolean touch_only,
1213                                               Entry *caller) {
1214         editing_trace ("music_source_checkin_entry_recursive: %s %i called by %s %i\n",
1215                        ENTRY_PF(new_entry), ENTRY_PF(caller));
1216
1217 360         Entry *old_entry = NULL;
1218 451         for (GSList *node = SP->checkout_entry_store; node; node = node->next) {
1219                 EntryDuplicate *duplicate = node->data;
1220
1221                 if (new_entry == SP->notifying_on_entry
1222                       && duplicate->version != SP->notifying_on_entry_version)
1223                         continue;
1224
1225                 if (new_entry->type != duplicate->entry->type || new_entry->id != duplicate->entry->id)
1226                         continue;
1227 372
1228                 // Removal from the checkout entry store will be done when the notify is emitted.
1229 451                 update_new_entry_from_duplicate (self, duplicate, p_jetsam, new_entry);
1230                 old_entry = duplicate->entry;
1231 372                 break;
1232 360         };
1233
1234         // A jetsam entry is no longer referenced anywhere in the checked-out tree of entries. It should
1235         // be 'touched' in the subclass to see if it now needs deleting from the source - it won't be
1236         // checked back in (it shouldn't have changed, because it's no longer part of the tree that was
1237         // checked out) so we need to remove its checkout duplicate specially.
1238 358         //
1239 360         for (GSList *node=*p_jetsam; node; node=node->next) {
1240 451                 Entry *jetsam_entry = node->data;
1241                 editing_trace ("\t%s %i is jetsam.\n", ENTRY_PF(jetsam_entry));
1242                 _unref_checkout_copy_recursive (self, jetsam_entry->type, jetsam_entry->id, TRUE, -1, -1);
1243 360         };
1244
1245         check_entry (new_entry);
1246
1247         // All the 'jetsam' (entries that we referenced by foreign keys when checked out, but have now
1248         // been replaced with something else) are still checked out - we've remove all their stored
1249         // properties (which is all it takes to effectively check them back in) and we need to test if
1250         // they are still referenced anywhere, and delete them if they aren't. This is the source's job.
1251         // FIXME: this must slow checkins down a LOT.
1252         //      => Perhaps we could defer things even later - the reason we remove dead entries straight
1253         //         away is so the view doesn't have to check if each node has children, remember. So this
1254         //         code only needs to be run next time the view updates, which mean we check some entries
1255         //         fewer times.
1256         //      => Also, for 'general' entries (ie. artist) it already has to check each node for children,
1257         //         so we don't actually have to worry about pruning the dead ones - we could do it any time
1258         //         while idle or whatever.
1259
1260         if (old_entry!=NULL)
1261                 g_warn_if_fail (old_entry->type==new_entry->type && old_entry->id==new_entry->id);
1262
1263 451         if (new_entry->source!=self && new_entry->source!=NULL)
1264                 g_warning ("music-source-importing::checkin-entry: checking in %s %i but belongs to a "
1265                            "different source!", ENTRY_PF(new_entry));
1266
1267         // Add/update every entry this one references.
1268         //
1269         for (int i=0; i < entry_n_properties[new_entry->type]; i++) {
1270                 if (IS_FOREIGN_KEY(schema[new_entry->type][i].type)) {
1271                         Entry *child = entry_get_property(new_entry, i);
1272
1273                         if (child==NULL)
1274                                 continue;
1275
1276                         if (child->type==caller->type && child->id==caller->id) {
1277                                 // This entry called us. All we need to do is lower the ref count of the duplicate, if
1278                                 // there is one.
1279                                 if (old_entry!=NULL) {
1280                                         Entry *old_child = entry_get_property (old_entry, i);
1281                                         if (old_child != NULL)
1282                                                 _music_source_unref_checkout_copies (self, old_child->type, old_child->id);
1283                                 };
1284
1285                                 continue;
1286                         }
1287
1288                         child = checkin_relation (self, new_entry, child, p_jetsam, caller);
1289
1290                         entry_check (new_entry);
1291                         // Update the reference, in case the child entry was merged with an existing one.
1292                         entry_dump (child);
1293                         entry_take_property (new_entry, i, child);
1294                 };
1295         };
1296
1297         // See if the updated entry now matches an existing one. If we do merge, we need to change all
1298         // references to old entry to point to the new one (no signals since externally nothing has
1299         // changed).
1300         Entry *existing_entry = try_merge (self, new_entry);
1301         if (existing_entry != NULL) {
1302                 MUSIC_SOURCE_GET_CLASS(self)->update_foreign_keys (self, new_entry, existing_entry);
1303
1304                 // Remove from the source, and remove the duplicates of it.
1305                 editing_trace ("_music_source_checkin_entry_recursive: Merged %s %i to %s %i.\n",
1306                                ENTRY_PF(new_entry), ENTRY_PF(existing_entry));
1307                 MUSIC_SOURCE_GET_CLASS(self)->remove_entry_internal (self, new_entry->type, new_entry->id);
1308
1309                 new_entry = existing_entry;
1310                 entry_unref (new_entry, "_music_source_try_merge");
1311         }
1312
1313         // Sources such as Library update their stored representation of the entry now. We do this if a
1314         // merge happened too in case any mergeable properties were changed in the process.
1315         if (MUSIC_SOURCE_GET_CLASS(self)->update_entry_internal != NULL)
1316                 MUSIC_SOURCE_GET_CLASS(self)->update_entry_internal (self, new_entry);
1317
1318         g_warn_if_fail (new_entry->id > 0);
1319
1320         // If old_entry is NULL, entry->id==0 so the entry will have been added instead, and entry-added
1321         // emitted.
1322         if (old_entry!=NULL)
1323                 // FIXME: only emit if entry was actually changed, I think
1324                 _music_source_queue_notify (self, old_entry, new_entry);
1325
1326         // We return the entry for add_recursive to keep track, since any entries being added won't be
1327         // in the db yet so won't have been updated
1328         return new_entry;
1329 };
1330
1331 /* FIXME: wrapper to not expose 'touch_only' flag (see above). */
1332 Entry *_music_source_checkin_entry_recursive (MusicSource *self, Entry *new_entry,
1333                                               GSList **p_jetsam, Entry *caller) {
1334         return __music_source_checkin_entry_recursive (self, new_entry, p_jetsam, FALSE, caller);
1335 };
1336 413
1337 360 /***************************************************************************************************
1338  * Change notification
1339  */
1340
1341 typedef struct {
1342         int signal_id;
1343 //      Entry *entry;
1344         Entry *old_entry, *entry;
1345 } SignalQueueEntry;
1346
1347
1348 358 typedef struct {
1349 393         MusicSource *source;
1350 358         EntryNotifyFunc callback;
1351         EntryType detail;
1352 393         void *user_data; gboolean is_object;
1353 358
1354         int current_entry_version;
1355 } EntryNotifyClosure;
1356
1357 393 static EntryNotifyClosure *connect_entry_notify (MusicSource *self, EntryType detail,
1358 413                                                  const gboolean watch_only,
1359 393                                                  EntryNotifyFunc callback, void *user_data) {
1360 358         EntryNotifyClosure *closure = g_slice_new(EntryNotifyClosure);
1361 393         closure->source = self;
1362 358         closure->callback = callback;
1363         closure->detail = detail;
1364         closure->user_data = user_data;
1365 393         closure->is_object = FALSE;
1366 358
1367         closure->current_entry_version = 0;
1368
1369         if (!watch_only) SP->notify_handlers = g_slist_append(SP->notify_handlers,       closure);
1370         else       SP->notify_watch_handlers = g_slist_append(SP->notify_watch_handlers, closure);
1371 393         return closure;
1372 413 };
1373 393
1374 // FIXME: this function is in wrong place in file ..
1375 void music_source_connect_entry_notify (MusicSource *self, EntryType detail,
1376                                         const gboolean watch_only, EntryNotifyFunc callback,
1377                                         void *user_data) {
1378 413         connect_entry_notify (self, detail, watch_only, callback, user_data);
1379 393 };
1380
1381 static void destroy_notify_function(EntryNotifyClosure *closure, GObject *where_the_object_was) {
1382         // Stop the weak ref being removed as its already gone
1383         closure->is_object = FALSE;
1384 413
1385 393         music_source_disconnect_entry_notify (closure->source, closure->detail, closure->callback,
1386 429                                               closure->user_data);
1387 413 };
1388
1389 void music_source_disconnect_entry_notify (MusicSource *self, EntryType detail,
1390 393                                            EntryNotifyFunc callback, void *user_data) {
1391         int closure_match (EntryNotifyClosure *closure) {
1392 413                 if (closure->callback==callback && closure->detail==detail
1393 393                       && closure->user_data==user_data)
1394                         return 0;
1395                 return 1;
1396         };
1397 413
1398         GSList *node = NULL;
1399 393         node = g_slist_find_custom(SP->notify_handlers, NULL, (GCompareFunc)closure_match);
1400         if (node==NULL) {
1401                 node = g_slist_find_custom(SP->notify_watch_handlers, NULL, (GCompareFunc)closure_match);
1402                 SP->notify_watch_handlers = g_slist_remove_link(SP->notify_watch_handlers, node);
1403         } else
1404                 SP->notify_handlers = g_slist_remove_link(SP->notify_handlers, node);
1405 413
1406 393         if (node==NULL) {
1407                 g_warning ("music_source_disconnect_entry_notify: Unable to find callback.");
1408                 return;
1409         };
1410 413
1411         EntryNotifyClosure *closure = node->data;
1412 393         if (closure->is_object)
1413                 g_object_weak_unref (closure->user_data, (GWeakNotify)destroy_notify_function, closure);
1414         g_slice_free (EntryNotifyClosure, closure);
1415 };
1416
1417 void music_source_connect_entry_notify_object (MusicSource *self, EntryType detail,
1418                                                const gboolean watch_only, EntryNotifyFunc callback,
1419                                                GObject *object) {
1420 413         g_return_if_fail (G_IS_OBJECT(object));
1421 393         EntryNotifyClosure *closure = connect_entry_notify(self, detail, watch_only, callback, object);
1422         closure->is_object = TRUE;
1423         g_object_weak_ref (object, (GWeakNotify)destroy_notify_function, closure);
1424 413 };
1425 393
1426 358
1427 /* _music_source_notify: Signal to all notify handlers that 'old_entry' has changed, or that
1428  *                       old_entry has been removed or new_entry has been added.
1429  *
1430  *    The first lot of signal handlers can modify the entry further - we watch for checkins, and if
1431  *    this happens the changed signal is reemitted to handlers which have already been called.
1432  *    Handlers which weren't yet called are still only called once but are passed the new changes.
1433  *    Handlers which don't change entries are called last, so they only ever recieve one changed
1434  *    notification. */
1435 void _music_source_notify (MusicSource *self, Entry *old_entry, Entry *current_entry) {
1436         g_return_if_fail (old_entry!=NULL || current_entry!=NULL);
1437         if (old_entry!=NULL && current_entry!=NULL)
1438                 g_return_if_fail (old_entry->type==current_entry->type);
1439         EntryType type = current_entry==NULL? old_entry->type: current_entry->type;
1440
1441         // This does nothing on removals, which is okay because surely nobody will be modifying an entry
1442         // that's about to be removed ...
1443
1444 361         SP->notifying_on_entry = current_entry==NULL? old_entry: current_entry;
1445 358         SP->notifying_on_entry_version = 1;
1446
1447 445         notify_trace ("Notify: %s %i.\n", ENTRY_PF(SP->notifying_on_entry));
1448 358
1449         // Emit notify and store the handler's current state; every time the entry is modified start
1450         // again and notify handlers that have already been called of the new changes.
1451 372
1452 358 restart:;
1453         int current_version = SP->notifying_on_entry_version;
1454         for (GSList *node=SP->notify_handlers; node; node=node->next) {
1455                 EntryNotifyClosure *closure = node->data;
1456
1457                 if (closure->detail!=-1 && closure->detail!=type)
1458                         continue;
1459
1460                 Entry *specific_old_entry = NULL;
1461                 if (closure->current_entry_version == SP->notifying_on_entry_version)
1462                         continue;
1463
1464                 if (closure->current_entry_version == 0)
1465                         specific_old_entry = old_entry;
1466                 else {
1467                         // specific_old_entry is closure->current_entry_version of entry in checkout_store
1468                         // FIXME: speed up
1469                         for (GSList *node=SP->checkout_entry_store; node; node=node->next) {
1470                                 EntryDuplicate *duplicate = node->data;
1471 451                                 notify_trace ("Looking at %s %i v%i, want %s %i.\n", ENTRY_PF(duplicate->entry),
1472                                               duplicate->version, ENTRY_PF(old_entry));
1473 358                                 if (duplicate->entry->type==old_entry->type && duplicate->entry->id==old_entry->id
1474                                       && duplicate->version == closure->current_entry_version) {
1475                                         specific_old_entry = duplicate->entry;
1476                                         break;
1477                                 };
1478                         };
1479                         if (specific_old_entry==NULL) {
1480                                 g_warning ("Unable to find %s %i v%i in checkout store [notifying on v%i].\n",
1481                                            ENTRY_PF(old_entry), closure->current_entry_version,
1482                                            SP->notifying_on_entry_version);
1483                                 return;
1484                         };
1485                 };
1486
1487 361                 notify_trace ("Calling %x: %x %s %i v%i -> %x %s %i v%i\n", closure->callback,
1488                               specific_old_entry, ENTRY_PF(specific_old_entry),
1489                               closure->current_entry_version, current_entry, ENTRY_PF(current_entry),
1490                               SP->notifying_on_entry_version);
1491 358                 closure->callback (self, specific_old_entry, current_entry, closure->user_data);
1492                 closure->current_entry_version = SP->notifying_on_entry_version;
1493
1494                 // Entry has been modified further
1495                 if (SP->notifying_on_entry_version > current_version)
1496                         goto restart;
1497         };
1498
1499         // Reset current states for next execution.
1500         for (GSList *node=SP->notify_handlers; node; node=node->next) {
1501                 EntryNotifyClosure *closure = node->data;
1502                 closure->current_entry_version = 0;
1503         };
1504
1505         // These shouldn't modify entry state
1506         for (GSList *node=SP->notify_watch_handlers; node; node=node->next) {
1507                 EntryNotifyClosure *closure = node->data;
1508                 if (closure->detail!=-1 && closure->detail!=type)
1509                         continue;
1510
1511 361                 notify_trace ("Calling watch-only %x: %x %s %i v%i -> %x %s %i v%i\n", closure->callback,
1512                               old_entry, ENTRY_PF(old_entry), closure->current_entry_version,
1513                               current_entry, ENTRY_PF(current_entry), SP->notifying_on_entry_version);
1514 358                 closure->callback (self, old_entry, current_entry, closure->user_data);
1515
1516                 g_warn_if_fail (current_version == SP->notifying_on_entry_version);
1517         };
1518
1519         // Unref all versions of entry in the checkout store, if there will be any.
1520 445         notify_trace ("current %x, old %x, ver %i\n", current_entry, old_entry, SP->notifying_on_entry_version);
1521 451         if (current_entry!=NULL && (old_entry!=NULL || SP->notifying_on_entry_version > 1))
1522 360                 _music_source_unref_checkout_copies (self, current_entry->type, current_entry->id);
1523 358
1524         SP->notifying_on_entry = NULL;
1525         SP->notifying_on_entry_version = 0;
1526 445         notify_trace ("_music_source_notify: complete - [%s %i].\n\n", ENTRY_PF(current_entry));
1527 358 };
1528
1529
1530 typedef struct {
1531         Entry *old_entry, *current_entry;
1532 } NotifyQueueEntry;
1533
1534 void _music_source_queue_notify (MusicSource *self, Entry *old_entry, Entry *current_entry) {
1535 361         notify_trace ("queue notify: %s %i -> %s %i [%x]\n", ENTRY_PF(old_entry),
1536                       ENTRY_PF(current_entry), SP->notifying_on_entry);
1537 451         if (SP->notifying_on_entry != NULL
1538              && ((current_entry != NULL
1539                    && (SP->notifying_on_entry->type == current_entry->type &&
1540                        SP->notifying_on_entry->id   == current_entry->id))     ||
1541                  (old_entry != NULL
1542                    && (SP->notifying_on_entry->type == old_entry->type &&
1543                        SP->notifying_on_entry->id   == old_entry->id)))) {
1544 358                 notify_trace ("Already emitting (at ver %i).\n", SP->notifying_on_entry_version);
1545                 // Currently emitting notify on this entry - register the fact that it has changed, but
1546                 // don't queue a new signal.
1547 445                 SP->notifying_on_entry_version ++;
1548 358                 return;
1549         };
1550
1551 361         // Remove duplicate signals. We also need to remove the reference that normally
1552         // _music_source_notify would have removed after emission.
1553         for (GSList *node=SP->notify_queue; node; node=node->next) {
1554                 NotifyQueueEntry *s = node->data;
1555                 if (s->old_entry==old_entry && s->current_entry==current_entry) {
1556                         notify_trace ("\tthis is already queued.\n");
1557 451
1558 361                         if (current_entry!=NULL)
1559                                 _music_source_unref_checkout_copies (self, current_entry->type, current_entry->id);
1560                         return;
1561                 };
1562         };
1563
1564 358         NotifyQueueEntry *s = g_slice_new(NotifyQueueEntry);
1565         s->old_entry = old_entry;
1566         s->current_entry = current_entry;
1567         SP->notify_queue = g_slist_append(SP->notify_queue, s);
1568 451
1569         /*if (current_entry != NULL)
1570                 entry_ref (current_entry, "MusicSource notify queue");*/
1571 358 };
1572
1573 445 // FIXME: returns true when notifys are queued but not active, so the name is a little inaccurate..
1574 gboolean _music_source_inside_notify (MusicSource *self) {
1575         return (SP->notify_queue != NULL) || (SP->notifying_on_entry != NULL);
1576 };
1577
1578 361 // FIXME: can we optimise the queue more before we emit it?
1579 451 /*    caller: used to prevent an entry in the process of removal being touched and notified again.
1580  *   Returns: TRUE if notify queue is now entry (therefore, FALSE means the caller was called from
1581  *            inside a notification function.)
1582 361  */
1583 451 gboolean _music_source_emit_queued_notifications (MusicSource *self, Entry *caller) {
1584 358         if (SP->notifying_on_entry != NULL)
1585 451                 return FALSE;
1586
1587 361         //gboolean caller_emitted = FALSE;
1588 358
1589         do {
1590                 GSList *queue_copy = g_slist_copy(SP->notify_queue);
1591                 g_slist_free (SP->notify_queue); SP->notify_queue = NULL;
1592
1593                 for (GSList *node=queue_copy; node; node=node->next) {
1594                         NotifyQueueEntry *s = node->data;
1595 361
1596                         // Check old_entry because this is only useful to removal notifications.
1597                         /*printf ("old_entry: %x caller %x\n", s->old_entry, caller); fflush (stdout);
1598                         if (caller!=NULL && s->old_entry==caller) {
1599                                 if (caller_emitted) goto skip;
1600                                 caller_emitted = TRUE;
1601                         };*/
1602 358                         _music_source_notify (self, s->old_entry, s->current_entry);
1603 361
1604 451                         /*if (s->current_entry != NULL) {
1605                                 editing_trace ("\tcurrent entry %s %i [%x]: %i refs.\n", ENTRY_PF(s->current_entry),
1606                                                s->current_entry, s->current_entry->ref_count);
1607                                 entry_dump (s->current_entry);
1608                                 entry_unref (s->current_entry, "MusicSource notify queue");
1609                         }*/
1610 361 //skip:
1611 358                         g_slice_free (NotifyQueueEntry, s);
1612                 };
1613 361                 g_slist_free (queue_copy);
1614 445         } while (SP->notify_queue != NULL);
1615
1616 361         notify_trace ("emit_queued_notifications: Done.\n");
1617 451         return TRUE;
1618 358 };
1619
1620
1621 360 void _music_source_emit(MusicSource *self, MusicSourceSignal signal_id, GQuark detail, ...) {
1622         va_list ap; va_start (ap, detail);
1623         g_signal_emit_valist (self, signal_index[signal_id], detail, ap);
1624         va_end (ap);
1625
1626         // FIXME: reimplement is-empty toggle, in a better way.
1627         // FIXME: we could do this more efficiently by only notifying is-empty when
1628         // the state has actually changed.
1629         //
1630         //GSignalQuery signal_query; g_signal_query(signal_index[signal_id], &signal_query);
1631         //printf("music_source_emit: %s\n", signal_query.signal_name);fflush(stdout);
1632         //if (signal_id==MUSIC_SOURCE_SIGNAL_RECORDING_ADDED || signal_id==MUSIC_SOURCE_SIGNAL_RECORDING_REMOVED)
1633         //      g_object_notify(G_OBJECT(self), "is-empty");
1634         //printf("music_source_emit: done\n");fflush(stdout);
1635 };
1636
1637
1638 358 /***************************************************************************************************
1639  * Debugging
1640  */
1641
1642 360 static void editing_trace (const char *format, ...) {
1643         #ifdef MUSIC_SOURCE_TRACE_EDITING
1644         va_list va; va_start (va, format);
1645 372         vprintf (format, va); fflush (stdout);
1646 360         va_end (va);
1647         #endif
1648 };
1649
1650 358 static void notify_trace (const char *format, ...) {
1651         #ifdef MUSIC_SOURCE_TRACE_NOTIFY
1652         va_list va; va_start (va, format);
1653         vprintf (format, va); fflush (stdout);
1654         va_end (va);
1655         #endif
1656 };

Loggerhead is a web-based interface for Bazaar branches