RSS

(root)/calliope : /src/base/genericview.c (revision 453)

Line Revision Contents
1 185 /*  Calliope Music Player
2  *  Copyright 2005-09 Sam Thursfield <ssssam gmail.com>
3  *
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 261  *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
16 185  */
17
18 /* genericview: presents a view on any source, using only musicsource base methods. */
19
20 #include <stdlib.h>
21 #include <string.h>
22 #include "main.h"
23 #include "musicsourceview.h"
24 #include "musicsourceview-private.h"
25 #include "genericview.h"
26 #include "genericview-private.h"
27
28 #define PP      MUSIC_SOURCE_VIEW(self)->priv
29
30 static GObject *constructor(GType type, guint n_construct_properties, GObjectConstructParam *construct_params);
31 static void finalize(GObject *self);
32
33 //static gboolean entry_is_orphan(MusicSourceView *self, int id);
34 static void initial_read(MusicSourceView *self, ViewNode *node);
35 261 static ViewPage *read_page_at (MusicSourceView *music_source_view, ViewPage *head,
36 185                                ViewPage *tail, int *e);
37 static void read_next_page(MusicSourceView *music_source_view, ViewPage *page);
38
39 361 static void entry_notify (MusicSource *source, Entry *old_entry, Entry *new_entry,
40                           MusicSourceView *self);
41 185
42 434 static void reading_trace (int level, const char *format, ...);
43 static void change_trace  (char *format, ...);
44 185
45 G_DEFINE_TYPE(GenericView, generic_view, MUSIC_SOURCE_VIEW_TYPE);
46
47 static void generic_view_class_init(GenericViewClass *class) {
48         generic_view_parent_class=g_type_class_peek_parent(class);
49 261
50 185         GObjectClass *object_class=(GObjectClass *) class;
51         object_class->constructor=constructor;
52         object_class->finalize=finalize;
53         //MUSIC_SOURCE_VIEW_CLASS(class)->entry_is_orphan=entry_is_orphan;
54         MUSIC_SOURCE_VIEW_CLASS(class)->initial_read=initial_read;
55         MUSIC_SOURCE_VIEW_CLASS(class)->read_page_at = read_page_at;
56         MUSIC_SOURCE_VIEW_CLASS(class)->read_next_page=read_next_page;
57         MUSIC_SOURCE_VIEW_CLASS(class)->get_path_for_id = get_path_for_id;
58         MUSIC_SOURCE_VIEW_CLASS(class)->get_path_for_id_begin = get_path_for_id_begin;
59         MUSIC_SOURCE_VIEW_CLASS(class)->get_path_for_id_step = get_path_for_id_step;
60         MUSIC_SOURCE_VIEW_CLASS(class)->get_path_for_id_end = get_path_for_id_end;
61 }
62
63 static void generic_view_init (GenericView *self) {
64         for (int i=0; i<ENTRY_TYPE_COUNT; i++)
65                 self->ignore_list[i] = NULL;
66 };
67
68 static GObject *constructor(GType type, guint n_construct_properties, GObjectConstructParam *construct_params) {
69         GObject *object = ((GObjectClass *)generic_view_parent_class)->constructor(type, n_construct_properties, construct_params);
70         GenericView *self = GENERIC_VIEW(object);
71
72         // Watch the source for changes
73 261         //
74 399         music_source_connect_entry_notify_object (PP->source, -1, TRUE, (EntryNotifyFunc)entry_notify,
75 393                                                   G_OBJECT(self));
76 185
77         return object;
78 };
79
80 static void finalize(GObject *object) {
81 261         //GenericView *self = GENERIC_VIEW(object);
82 431         //printf ("view:finalise .. \n"); fflush (stdout);
83 185         G_OBJECT_CLASS(generic_view_parent_class)->finalize (object);
84 };
85
86 438 /**************************************************************************************
87  * Utilities (used externally too, I wonder if this is the best place for them or not)
88  */
89 185
90 438 /* check_clnode_for_entry: TRUE if 'clnode' is on the same branch as 'entry'.
91  *
92  *   *branch_id, *branch_tested_n: state variables for recursive use. Pass pointers to variables of
93  *                                 the appropriate type, both set to 1.
94  * FIXME: put this in musicsource, along with the other viewconfig functions that require a source
95  *        I think. */
96 gboolean check_clnode_for_entry (MusicSource *source, ViewConfig *config, GList *clnode,
97                                  Entry *entry, guint32 *branch_id, int *branch_tested_n) {
98 388
99 384         // If we aren't at the orphaning join node, recurse forwards.
100         ViewConfigNode *cnode = CONFIG_NODE(clnode);
101
102         while (CONFIG_NODE(clnode)->branch_n > (*branch_tested_n)) {
103                 // Find the RHS entry of the orphaning join. 'entry' must be beyond the orphaning join
104                 // in the config, or there's no possibility of it's config node being on that node's
105                 // orphaning branch ........ FIXME: is this true? What about the artist preceeding the
106                 // recording on an orphan branch or something like that? :( :(
107                 //
108 388                 // To understand the function better, imagine the following:
109                 //      (artist[name]:album[name]:release:track:recording[name]:file[bitrate])
110                 //    / (artist[name]:recording:file[bitrate])
111                 //
112                 // 'clnode' must be of the same type of 'entry' of course, but could be on either branch.
113                 //
114
115                 // orphaning_clnode is the recording in the main branch.
116 438                 GList *orphaning_clnode = config->orphaning_join_node[(*branch_tested_n)-1];
117 388                 ViewConfigNode *orphaning_cnode = CONFIG_NODE(orphaning_clnode);
118
119                 // We need to use the horizontal position (n) of the nodes to see if we need to walk
120                 // forwards or backwards from clnode to reach the orphaned (one) type. If clnode is on the
121                 // orphan branch, these probably won't match up. We find the difference in their lengths
122                 // using the reference point of the one node's n in each branch. (The recording nodes in the
123                 // example above).
124                 int branch_n_delta = 0;
125                 if (!CLNODE_IS_ON_BRANCH(orphaning_clnode, clnode))
126                         branch_n_delta = orphaning_cnode->n - CONFIG_NODE(orphaning_cnode->orphanage)->n;
127 390                 //printf ("clnode branch id %i, orphaning %i - delta %i\n", CONFIG_NODE(clnode)->branch_id, orphaning_cnode->branch_id, branch_n_delta); fflush (stdout);
128 388
129 384                 Entry *one;
130 388                 if (entry->type == orphaning_cnode->type) {
131                         // clnode is the "one" node - the recording in the example above.
132                         g_warn_if_fail ((CONFIG_NODE(clnode)->n + branch_n_delta) == orphaning_cnode->n);
133 384                         one = entry;
134 388                 } else if ((CONFIG_NODE(clnode)->n + branch_n_delta) > orphaning_cnode->n) {
135 438                         // clnode is past the "one" node (the file in the above example). We can walk backwards
136                         // to find the 'one' entry.
137 431                         one = view_config_walk_backwards_specific
138 438                                 (clnode, source, entry, orphaning_cnode->type, NULL, NULL, NULL, NULL);
139 384                         g_return_val_if_fail (one!=NULL, FALSE);
140 388                 } else {
141 438                         // clnode is before the "one" node (imagine an artist in the example). This is a more
142                         // complicated case because the entry could appear on *both* branches - it's on a branch
143                         // if any one of its children is on that branch.
144                         //
145                         // In the context of the example, if 'entry' is of type 'artist', here
146                         // view_config_step_forward_fast() returns a list of that artist's 'album' entries, and
147                         // then a list of that artist's 'recording' entries. It is up to us to filter the
148                         // recording entries to see if any are actually orphans and will appear on that branch -
149                         // but we only need to check one, since if the 'artist' entry has any orphan children we
150                         // know it will appear on the branch.
151
152                         // view_config_step_forwards_fast() returns a list of entry lists, one for each branch.
153                         // We only need to check the first entry of each branch.
154                         // FIXME: we could actually be cleverer, because the results are in order of branch_id,
155                         // so we only need to test if branch 'branch_id' has any entries, I think. That's a job
156                         // for another commit however.
157                         GSList *rhs_branch_list = view_config_step_forwards_fast(source, config, clnode, entry);
158 388                         gboolean result = FALSE;
159 438                         for (GSList *rhs_list=rhs_branch_list; rhs_list; rhs_list=rhs_list->next) {
160                                 GSList *rhs_node = rhs_list->data;
161 444                                 if (rhs_node == NULL)
162                                         continue;
163
164 438                                 if (!result && check_clnode_for_entry(source, config, clnode->next, rhs_node->data,
165                                                                       branch_id, branch_tested_n))
166                                         result = TRUE;
167                                 entry_query_list_free (rhs_node, "walk-config");
168 388                         };
169 438                         g_slist_free (rhs_branch_list);
170 388                         return result;
171 384                 };
172
173                 const int rel_apid = orphaning_cnode->relation_apid;
174                 const int relation_count = music_source_query_n_relations
175 438                                              (source, one->id, APID_GET_TYPE(rel_apid),
176                                               APID_GET_PROPERTY_ID(rel_apid), 1);
177 384
178                 if (entry->type != orphaning_cnode->type)
179                         entry_unref (one, "walk-config");
180                 //printf ("%s %i on %s.%s: %i relations.\n", ENTRY_PF(one), APID_NAME(rel_apid),
181                 //        relation_count); fflush (stdout);
182
183                 if (relation_count==0)
184                         (*branch_id) |= (1<<(*branch_tested_n));
185                 (*branch_tested_n) ++;
186         };
187
188 390         //change_trace ("check_clnode_for_entry: new n %i, id %x (clnode %i, %x): result %i\n", *branch_tested_n,
189         //              *branch_id, cnode->branch_id, cnode->branch_n, (cnode->branch_id == (*branch_id)));
190 384         return (cnode->branch_id == (*branch_id));
191 };
192
193 263 GList *_find_clnode_for_entry (GenericView *self, Entry *entry) {
194 383         // config->index[entry->type] gives a list of clnodes for entries of the correct type. We then
195         // need to work out which of these is on the correct branch for 'entry'.
196 224         GSList *node_list = PP->config->index[entry->type];
197 261
198 224         if (node_list==NULL) {
199                 g_warning ("There is no node for %s in view config.", entry_type_name[entry->type]);
200 263                 return NULL;
201 224         };
202 261
203 383         if (node_list->next==NULL)
204 263                 return node_list->data;
205 261
206 383         // At this point we find the clnode on the correct branch for 'entry'. We test entry against
207         // each orphaning join up to clnode's branch_n; the results are stored in branch_id, while
208         // branch_queried_n stores the highest branch_n tested for so far.
209 384         guint32 entry_branch_id = 1; int branch_tested_n = 1;
210 224         for (GSList *node_list_node=node_list; node_list_node; node_list_node=node_list_node->next) {
211 383                 GList *clnode = node_list_node->data;
212
213                 // Check against the relevant orphaning joins.
214 438                 if (check_clnode_for_entry (PP->source, PP->config, clnode, entry,
215                                             &entry_branch_id, &branch_tested_n))
216 383                         return clnode;
217 261         };
218 383
219         return NULL;
220 261 };
221 185
222
223 384
224 185 /////////////////////////////////////////
225 // Page reading functions
226 //
227
228 typedef struct {
229 229         gboolean complete;  // TRUE when this is the clnode _append_entries wants.
230 261
231         GList *clnode;      // clnode this result represents.
232         int id;
233 216         int intermediate_n, *intermediate_ids;
234 185         int sort_group;
235 } _Result;
236
237 // Convert a list of entry id's (as returned by some music source query functions) into a
238 // list of _Result values.
239 //
240 217 static GSList *_id_list_to_result_list(GSList *in, GList *clnode) {
241 185         GSList *out = NULL;
242         for (GSList *node=in; node; node=node->next) {
243                 _Result *r = g_slice_new0(_Result);
244 227                 r->clnode = clnode;
245 185                 r->id = GPOINTER_TO_INT(node->data);
246                 out = g_slist_prepend(out, r);
247         };
248         g_slist_free (in);
249         return out;
250 };
251
252 // FIXME: this really is painfully slow
253 // FIXME: at the very least, it should be done internally to the source since then we wouldn't
254 // waste time refing and unreffing the entries, and for library would make things a whole
255 // lot faster when adding entries.
256 // As said elsewhere, once an api for sorted queries exists it makes libraryview
257 // seem less useful.
258 261 // FIXME: also, we could cache some of the properties in the _result node somehow ... or
259 185 // something.
260 261 GSList *_sort_result_list (MusicSourceView *self, GSList *list, EntryType type, int property,
261 219                            int flags) {
262         MusicSource *source = SP->source;
263 261
264 219         int compare (_Result *a_r, _Result *b_r) {
265                 Entry *a = music_source_query_entry(source, type, a_r->id, "musicsourceview::sort-id-list"),
266                           *b = music_source_query_entry(source, type, b_r->id, "musicsourceview::sort-id-list");
267 185                 g_return_val_if_fail (a!=NULL, 0);
268                 g_return_val_if_fail (b!=NULL, 0);
269                 int result = entry_compare_property(a, b, property);
270 453                 reading_trace (4, "compare: %s %i = %s, %s %i = %s: %i.\n", ENTRY_PF(a),
271                                entry_property_to_string (a, property), ENTRY_PF(b),
272                                entry_property_to_string (b, property), result);
273 185                 entry_unref (a, "musicsourceview::sort-id-list");
274                 entry_unref (b, "musicsourceview::sort-id-list");
275 261
276 185                 if (flags & VIEW_CONFIG_NODE_SORT_DESCENDING)
277                         return -result;
278                 return result;
279         };
280 261
281 453         reading_trace (3, "_sort_result_list: %s.%s\n",
282                        entry_type_name[type], schema[type][property].name);
283
284 185         // Implemented as quicksort.
285 453         GSList *result = g_slist_sort(list, (GCompareFunc)compare);
286         reading_trace (4, "\n");
287         return result;
288 185 };
289
290 217 // Finds entries of type described in prev->next_clnode, connected by prev->id
291 227 static GSList *_query_join (MusicSourceView *self, ViewNode *node, _Result *prev) {
292 216         GSList *result;
293
294         // Finding entries that link to the previous node (most common case).
295 261         GList *clnode = prev->id==-1? node->head: prev->clnode->next;
296         //GList *clnode = prev->clnode->next;
297 227         ViewConfigNode *cnode = CONFIG_NODE(clnode);
298 261
299 216         if (APID_GET_TYPE(cnode->relation_apid)==cnode->type) {
300 261                 // 1:many join - this is easy, we just find all entries with predecessor_id
301                 // as property rel_apid.
302 216                 GSList *result_ids = music_source_query_entry_children_ids
303 261                                                            (SP->source, prev->id, cnode->type,
304 216                                                                 APID_GET_PROPERTY_ID(cnode->relation_apid));
305 384                 result = _id_list_to_result_list(result_ids, clnode);
306 261
307         } else if (clnode->prev!=NULL
308 216                            && APID_GET_TYPE(cnode->relation_apid)==CONFIG_NODE(clnode->prev)->type) {
309                 // many:1 join - in this case, things are harder. eg. track:recording - here
310                 // predecessor_id is the track id, config_node->type=recording, but
311                 // rel_apid=track:recording_id - so we can't use query_entry_children_ids.
312 217                 g_warn_if_fail (cnode->flags & VIEW_CONFIG_NODE_MANY_TO_ONE_JOIN);
313 261
314 216                 // Find the id of the entry we want, by looking up rel_apid in
315                 // the predecessor entry.
316                 _Result *r = g_slice_new0(_Result);
317 261                 Entry *predecessor = music_source_query_entry(SP->source,
318                                                                                                           APID_GET_TYPE(cnode->relation_apid),
319 216                                                                                                           prev->id, "musicsourceview");
320 261                 r->clnode = clnode;
321                 r->id = ((Entry *)entry_get_property(predecessor,
322 216                                                                                          APID_GET_PROPERTY_ID(cnode->relation_apid)))->id;
323 261
324 220                 // It isn't done that way any more.
325                 // The new intermediate id is added to result. The existing intermediate ids are copied in
326                 // afterwards by the code in append_entries_step.
327                 r->intermediate_ids = g_new0(int, cnode->level_break_count);
328                 r->intermediate_n = prev->intermediate_n;
329                 r->intermediate_ids[r->intermediate_n++] = prev->id;
330 261
331 216                 entry_unref (predecessor, "musicsourceview");
332                 result = g_slist_append(NULL, r);
333 351         } else {
334 261                 g_warning ("Don't know how to query id's of %ss on property %s.%s :(\n",
335 227                                    entry_type_name[cnode->type], APID_NAME(cnode->relation_apid));
336 351                 result = NULL;
337         };
338 216
339 261         return result;
340 };
341 216
342 185 // Utils for append_entries.
343 // The strategy here is very simplistic. Its job is to identify all entries
344 261 // of config_node->type which are not referenced by the foreign key
345 185 // config_node->relation_apid.
346 217 static GSList *query_orphans(MusicSourceView *self, GList *source_clnode, GList *dest_clnode,
347                              _Result *prev) {
348 261         g_return_val_if_fail (source_clnode!=NULL, NULL);
349         g_return_val_if_fail (dest_clnode!=NULL, NULL);
350
351 185         GSList *result = NULL;
352 217         ViewConfigNode *source_cnode = CONFIG_NODE(source_clnode),
353                        *dest_cnode = CONFIG_NODE(dest_clnode);
354 261
355 185         // FIXME: this must be glacially slow.
356         // FIXME: this would be much (well, a bit) faster done within the source.
357
358 217         int orphan_type = source_cnode->type;
359         int parent_type = APID_GET_TYPE(source_cnode->relation_apid);
360 185
361         int orphan_highest_id = 0, parent_highest_id = 0;
362         music_source_get_n_entries (SP->source, orphan_type, &orphan_highest_id, NULL);
363         music_source_get_n_entries (SP->source, parent_type, &parent_highest_id, NULL);
364         char flag[orphan_highest_id]; memset(flag, 0, orphan_highest_id);
365 261
366 185         // FIXME: the most obvious optimisation I can think of is to work out the
367         // flags once per fill_node call - if the orphanage isn't the root then we
368 261         // can execute this code a lot.
369 185         for (int i=1; i<=parent_highest_id; i++) {
370 261                 Entry *parent_entry = music_source_query_entry(SP->source, parent_type, i,
371 217                                                                "musicsourceview");
372 185                 if (parent_entry!=NULL) {
373 261                         Entry *child = entry_get_property(parent_entry,
374 217                                                           APID_GET_PROPERTY_ID(source_cnode->relation_apid));
375 216                         flag[(child->id)-1] = TRUE;
376 185                         entry_unref (parent_entry, "musicsourceview");
377 261                 };
378 185         };
379 261
380 185         for (int i=1; i<=orphan_highest_id; i++) {
381                 if (flag[i-1]==FALSE && music_source_is_valid_id(SP->source, orphan_type, i)) {
382                         _Result *r = g_slice_new0(_Result);
383 216                         if (prev->id!=-1) {
384                                 // Orphanage isn't the root.
385 217                                 //g_return_val_if_fail (dest_clnode->prev!=NULL, NULL);
386 185
387 261                                 // This code is similar to below. FIXME: but even less
388 185                                 // efficient. It performs a select on the result set on
389                                 // orphan_relation_apid = predecessor_id.
390 220                                 //printf ("cnode rel apid: %s.%s, cnode type %s.\n", APID_NAME(dest_cnode->relation_apid),
391                                 //        entry_type_name[dest_cnode->type]);
392 217                                 if (APID_GET_TYPE(dest_cnode->relation_apid) == dest_cnode->type) {
393 261                                         Entry *entry = music_source_query_entry(SP->source, orphan_type, i,
394 216                                                                                 "musicsourceview"),
395 217                                               *foreign_entry = entry_get_property
396 261                                                                      (entry,
397 217                                                                       APID_GET_PROPERTY_ID(dest_cnode->relation_apid));
398 185                                         int foreign_id = foreign_entry->id;
399                                         entry_unref (entry, "musicsourceview");
400                                         if (foreign_id!=prev->id)
401                                                 continue;
402                                 } else {
403 261                                         g_warning ("config@%s: Orphan relation can't be many:1 (it is %s.%s)",
404                                                    entry_type_name[dest_cnode->type],
405 217                                                            APID_NAME(dest_cnode->relation_apid));
406 185                                         continue;
407                                 }
408                         }
409 227                         r->clnode = dest_clnode;
410 185                         r->id = i;
411                         result = g_slist_prepend(result, r);
412                 }
413 261         }
414         return result;
415 185 };
416
417 217 // Find all entries regarding 'prev'. Returns a list of result lists, where each result list
418 // shares its next_clnode.
419 227 static GSList *_append_entries_step (MusicSourceView *self, ViewNode *node, _Result *prev) {
420         GSList *result_set = NULL, *ids;
421 261
422         GList *clnode = prev->id==-1? node->head: prev->clnode->next;
423 216         ViewConfigNode *cnode = CONFIG_NODE(clnode);
424 261
425 434         reading_trace (2, "On %s [%s] .. %s.%s = %i. Intermediate n %i, sort group %i\n",
426                        CONFIG_TYPE_NAME(clnode), cnode->flat? "flat": "branching",
427                        APID_NAME(cnode->relation_apid), prev->id, prev->intermediate_n,
428                        prev->sort_group);
429 185
430 216         /* The correct way is: if current node branches, add for prev and then add for orphans regarding
431          * the orphaning join prev's branch references. */
432
433 434         if (prev->id==-1) {
434 185                 // At the head of the top level, looking for all entries of this type.
435 227                 ids = _id_list_to_result_list(music_source_query_ids(SP->source, cnode->type), clnode);
436 185         } else {
437 227                 ids = _query_join (self, node, prev);
438 216         };
439 217
440 227         if (ids!=NULL)
441                 result_set = g_slist_append(result_set, ids);
442 261
443 216         if (cnode->orphan_branch!=NULL) {
444 383                 int branch_n = CONFIG_NODE(cnode->orphan_branch)->branch_n;
445                 ids = query_orphans(self, PP->config->orphaning_join_node[branch_n-2],
446 223                                     cnode->orphan_branch, prev);
447 227                 if (ids!=NULL)
448                         result_set = g_slist_append (result_set, ids);
449 185         };
450
451 220         // Propogate intermediate ids to new results.
452 434         reading_trace (3, "...Prev [id %i] n %i, type %s, ids %x.\n", prev->id, prev->intermediate_n,
453                        CONFIG_TYPE_NAME(prev->clnode), prev->intermediate_ids);
454 219         if (prev->intermediate_n > 0) {
455 185                 int *int_ids = prev->intermediate_ids;
456 261                 for (GSList *result_set_node=result_set; result_set_node;
457 227                      result_set_node=result_set_node->next)
458                         for (GSList *n = result_set_node->data; n; n=n->next) {
459 220                                 _Result *r = n->data;
460
461                                 if (r->intermediate_ids==NULL) {
462                                         g_warn_if_fail (r->intermediate_n==0);
463                                         r->intermediate_n = prev->intermediate_n;
464
465                                         if (prev->intermediate_ids!=NULL) {
466                                                 // Reuse the previous array for the first result without an existing one
467 261                                                 r->intermediate_ids = int_ids;
468 220                                                 prev->intermediate_ids = NULL;
469                                                 continue;
470                                         } else
471                                                 r->intermediate_ids = g_new0(int, cnode->level_break_count);
472                                 } else
473                                         g_warn_if_fail (r->intermediate_n==prev->intermediate_n+1);
474 261
475 220                                 memcpy (r->intermediate_ids, int_ids, prev->intermediate_n * sizeof(int));
476 185                         };
477 261
478 220                 if (prev->intermediate_ids!=NULL) {
479 261                         // Array was not reused - possible if there are no results, or a m:1 join with no
480 220                         // orphans (because m:1 results already have an intermediate ids array to store the new
481                         // intermediate id created by that m:1 join).
482                         g_free (prev->intermediate_ids);
483                         prev->intermediate_ids = NULL;
484 185                 };
485 261         };
486
487 227         return result_set;
488 185 };
489
490 // This function takes the current node's results. If sort grouping is needed, it swallows them
491 217 // until the group is complete and then returns the whole sorted group. It only should be used
492 // on result lists of all one type (next_clnode always the same).
493 185 //
494 // Each _Result has a sort group property. This should be 0 for no sort group, or > 0.
495 //
496 261 static GSList *_append_entries_group_sorts (MusicSourceView *self, GSList *pids_node,
497                                             GSList *ungrouped, GSList **sort_group,
498 217                                                                                         int *current_sort_group_id) {
499 227         _Result *prev = pids_node->data;
500 261
501         // 'next_clnode' should be constant throughout the result list - this is the reason for
502 227         // _append_entries_step returning a set of lists, one for each branch, rather than combining all
503         // the results together.
504         ViewConfigNode *cnode = ((_Result *)ungrouped->data)->clnode->data;
505 261
506 434         reading_trace (3, "group_sorts: cnode %s, prev sg %i. %x, %x\n", entry_type_name[cnode->type],
507                        prev->sort_group, prev->clnode,
508                        pids_node->next? ((_Result *)pids_node->next->data)->clnode: 0);
509 261
510         GSList *grouped = ungrouped;
511 185         if (prev->sort_group==0) {
512                 // Previous node didn't need sort grouping. If this node is sorted, sort it. If not,
513                 // begin a new sort group for each entry of this node.
514                 //
515 217                 if (cnode->sort_property_id >= 0)
516 261                         grouped = _sort_result_list(self, ungrouped, cnode->type,
517 219                                                     cnode->sort_property_id, cnode->flags);
518 185                 else // Create new sort group.
519 261                         for (GSList *n=ungrouped; n; n=n->next) {
520                                 _Result *r = n->data;
521 185                                 r->sort_group = prev->id;
522                         };
523 261
524 185         } else {
525                 // Previous node was sort grouped! If this node is unsorted too, propogate the sort
526                 // group; if it is sorted, add to the current sort group or start a new one.
527                 //
528 261                 if (cnode->sort_property_id==-1) {
529 221                         // We are in the middle of an unsorted range, eg.
530                         //    artist[name]:composition:recording:file[path]
531                         //                    ^^^     or   ^^^
532 261                         // Here we just continue the sort groups from the previous node.
533 221                         //
534 185                         for (GSList *n=ungrouped; n; n=n->next) {
535                                 _Result *r = n->data;
536                                 r->sort_group = prev->sort_group;
537 261                         };
538                 } else {
539                         // We are at the end of un unsorted range, eg.
540 221                         //    artist[name]:composition:recording:file[path]
541                         //                                          ^^^
542 229                         // The current group is ended when
543                         //   a) prev->sort_group (in this example, the artist id) changes
544                         //   b) pids_node->next==NULL (in the example, at the recording result)
545                         //   c) pids_node->clnode==next pids_node->clnode
546                         const gboolean last_group = (pids_node->next==NULL ||
547                                                      prev->clnode!=((_Result *)pids_node->next->data)->clnode);
548                         if (*current_sort_group_id==prev->sort_group && !last_group) {
549 221                                 // Add this set of results to the current sort group, don't sort anything yet.
550 261                                 //
551 221                                 *sort_group = g_slist_concat(*sort_group, ungrouped);
552 453                                 reading_trace (4, "added %i results to current group %i, new length %i.\n",
553                                                g_slist_length (ungrouped), *current_sort_group_id,
554                                                g_slist_length (*sort_group));
555 221                                 grouped = NULL;
556                         } else {
557                                 // Sort current sort group ('sort_group'), and start a new one ('ungrouped'). This
558                                 // code path is also called on the last result, because the sort group needs to be
559                                 // emptied.
560 261                                 //
561 453                                 if (last_group && *current_sort_group_id==prev->sort_group) {
562 185                                         *sort_group = g_slist_concat(*sort_group, ungrouped);
563                                         ungrouped = NULL;
564                                 };
565 261
566 221                                 g_return_val_if_fail (cnode!=NULL, NULL);
567 453                                 reading_trace (4, "sorted and closing group %i, length %i.\n",
568                                                *current_sort_group_id, g_slist_length (*sort_group));
569 261                                 grouped = _sort_result_list(self, *sort_group, cnode->type,
570 453                                                             cnode->sort_property_id, cnode->flags);
571
572                                 if (last_group && ungrouped != NULL) {
573                                         // We won't get called again, so sort the last group now too.
574                                         ungrouped = _sort_result_list(self, ungrouped, cnode->type,
575                                                                       cnode->sort_property_id, cnode->flags);
576                                         grouped = g_slist_concat (grouped, ungrouped);
577                                         ungrouped = NULL;
578                                 }
579 221
580 185                                 *sort_group = ungrouped;
581                                 *current_sort_group_id = prev->sort_group;
582 261                         };
583 185                 }
584 453                 reading_trace (4, "\n"); 
585 185         }
586         return grouped;
587 };
588
589 269 /* _fill_node: the main function which populates 'node'. */
590 215 static void _fill_node(MusicSourceView *self, ViewNode *node) {
591 269         // Shouldn't have been read already.
592         g_return_if_fail (node->n_children==-1 && node->e_children==-1);
593 434         reading_trace (1, "fill_node: %x [%i]\n", (guint)node, node->depth);
594 261
595 229         // Leaf nodes have head=NULL, but we should never be asked to fill one.
596 216         g_return_if_fail (node->head!=NULL);
597 261         g_return_if_fail (node->tail!=NULL);
598
599 215         node->n_children = node->e_children = 0;
600
601 185         // Our method is essentially to iterate through previous ids, and at each node, find each
602         // entry at the next level and store this in ids. previous_ids then becomes ids, and the
603         // cycle repeats. sort_group is used to lookback so eg. release[name]:track:recording[name]
604         // doesn't group recordings arbitrarily by track.
605         //
606 261         GSList *ids = NULL, *sort_group;
607
608         // For this function only, id of -1 indicates we're at the root node. clnode is NULL for the
609 227         // root node but is special cased where necessary to node->head.
610 185         _Result *seed = g_slice_new0(_Result);
611 227         seed->id = (node==&SP->root? -1: node->id);
612         seed->clnode = node->head->prev;
613 261
614 185         GSList *previous_ids = g_slist_append(NULL, seed);
615
616         // Iterate through configuration. We go node by node through the whole of the level,
617 269         // ie. from one > join to the next. The nodes are actually added after the loop completes.
618 185         //
619         while (1) {
620 261
621 220                 //printf("\n\n---------------\n");
622 215
623 185                 // Iterate through the results of the previous node ('previous_ids') and store the new
624                 // info into 'ids'.
625 229                 gboolean finished = TRUE;
626 350                 sort_group = NULL;
627 231
628                 GSList *pids_node = previous_ids;
629                 if (pids_node==NULL)
630                         goto empty;
631                 //g_return_if_fail (pids_node!=NULL);
632
633 351                 int current_sort_group;
634 261                 current_sort_group = ((_Result *)pids_node->data)->sort_group;
635                 while (pids_node!=NULL) {
636                         // Query all the entries across prev's join. result_set contains a *list* of result
637 227                         // lists. Each result list has a common next_clnode (ie. is of the same branch). So for
638 261                         // example, at the second node of 'artist:(album ..)/(recording ..)' result_list will
639                         // contain a GSList of _Result entries of type 'album', and then a GSList of _Result
640 227                         // entries of type 'recording'.
641                         //
642 261                         _Result *prev = pids_node->data;
643
644                         // Some branches may finish before others. The results still need to be propogated so
645                         // that ordering is preserved.
646 229                         if (!prev->complete) {
647                                 finished = FALSE;
648                                 GSList *result_set = _append_entries_step(self, node, prev);
649 261
650 229                                 for (GSList *result_set_node=result_set; result_set_node;
651 261                                          result_set_node=result_set_node->next) {
652
653 269                                         // If there are no results of a certain type, it's wrong to return a list node
654                                         // of NULL in the result set - should be no entry at all.
655 229                                         g_warn_if_fail (result_set_node->data!=NULL);
656 261
657 434                                         #if defined(GENERIC_VIEW_TRACE_READING) && GENERIC_VIEW_TRACE_READING > 3
658                                         for (GSList *n=result_set_node->data; n; n=n->next) {
659 229                                                 _Result *r = n->data;
660 434                                                 reading_trace (4, "prev %i[%s]: cnode %s. Result id: %i, int %x. Sort group %i\n",
661 229                                                                 prev->id, prev->clnode==NULL?"":CONFIG_TYPE_NAME(prev->clnode),
662 261                                                                 r->clnode->next?CONFIG_TYPE_NAME(r->clnode):"end", r->id,
663 229                                                                 (guint)r->intermediate_ids, r->sort_group);
664                                         }
665 434                                         #endif
666 217
667 229                                         GSList *sorted = _append_entries_group_sorts(self, pids_node, result_set_node->data,
668                                                                                                                                  &sort_group, &current_sort_group);
669 261
670 229                                         if (sorted!=NULL) {
671                                                 // Determine if this is the type we want or if we need to step further.
672                                                 GList *next_clnode = ((_Result *)sorted->data)->clnode->next;
673                                                 if (next_clnode==NULL || !CONFIG_NODE(next_clnode->prev)->flat)
674                                                         for (GSList *n=sorted; n; n=n->next)
675                                                                 ((_Result *)n->data)->complete = TRUE;
676 261
677 217                                                 ids = g_slist_concat(ids, sorted);
678                                         };
679                                 };
680 229                                 g_warn_if_fail (prev->intermediate_ids==NULL);  // Should have been freed or passed on.
681 261
682 229                                 g_slice_free (_Result, prev);
683                         } else {
684                                 ids = g_slist_append(ids, prev);
685 216                         };
686 229
687 185                         pids_node = pids_node->next;
688                 };
689 261
690                 g_slist_free (previous_ids);
691
692 empty:
693 229                 if (sort_group!=NULL)
694 261                         g_warning ("_fill_node: Still in sort group: %i result(s)\n",
695 229                                    g_slist_length(sort_group));
696                         /*for (GSList *sg_node=sort_group; sg_node; sg_node=sg_node->next) {
697                                 _Result *r = sg_node->data;
698 261                                 printf ("\t%s %i.\n", CONFIG_TYPE_NAME(r->clnode), r->id); fflush (stdout);
699 229                         };*/
700 261
701
702 216                 //if (!cnode->flat)
703 261                 //      break;
704 216                 //
705 229                 if (finished)
706 185                         break;
707                 previous_ids = ids; ids = NULL;
708         };
709 261
710 269         // Add the entries we found to the node in pages. All the _e numbers are set to the same as the
711         // _n's because we have no need to estimate, we just queried everything.
712 185         //
713 269         int n_children = g_slist_length(ids);
714 261
715 216         ViewPage *page = node->children;
716 185         if (page==NULL) {
717 216                 node->children = page = _music_source_view_new_page(node, NULL, NULL);
718                 page->start_e = 0; page->start_n = 0;
719         } else while (page->next!=NULL) page = page->next;
720 185
721 216         int page_i = page->start_n + page->size;
722 185
723         for (GSList *result_node=ids; result_node; result_node=result_node->next) {
724                 _Result *r = result_node->data;
725 216                 if (page_i > MUSIC_SOURCE_VIEW_MIN_PAGE_SIZE) {
726                         page->chain_next = TRUE;
727                         page->next = _music_source_view_new_page(node, page, NULL);
728                         page->next->start_e = page->next->start_n = page->start_n+page->size;
729                         page = page->next;
730                         page_i = 0;
731 185                 };
732 261
733 434                 reading_trace (2, "Adding %s %i.\n\n", CONFIG_TYPE_NAME(r->clnode), r->id); fflush (stdout);
734 228                 ViewNode *child = _music_source_view_new_node(node->depth+1, r->clnode, NULL, NULL);
735 216                 child->parent_page = page;
736                 child->n = page_i;
737 261                 child->id = r->id;
738 216                 g_array_append_val (page->nodes, child);
739 185
740                 //printf ("n %i id %i ints %x\n", page_i, r->id, (guint)r->intermediate_ids); fflush (stdout);
741                 if (r->intermediate_ids!=NULL) {
742                         //printf("Setting int sot %x - %i\n", r->intermediate_ids, r->intermediate_ids[0]);fflush(stdout);
743                         child->intermediate_ids = r->intermediate_ids;
744                 };
745
746                 page->size++; page_i++;
747 269
748                 // 'Unknown Artist' and 'Various Artists' are persistant entries. Make sure we only show
749                 // them if they have any children.
750                 if (CONFIG_NODE(r->clnode)->type==ENTRY_TYPE_ARTIST && (r->id==1 || r->id==2)) {
751                         if (r->clnode->next==NULL)
752                                 // This config is just a list of artists - strange, but no reason to disallow it
753                                 break;
754
755                         _fill_node (self, child);
756                         g_warn_if_fail (child->n_children >= 0);
757                         if (child->n_children==0) {
758                                 // Empty - remove the node again.
759                                 g_array_remove_index (page->nodes, --page_i);
760                                 page->size--; n_children--;
761                         };
762                 };
763
764 185                 g_slice_free (_Result, r);
765         };
766 261
767 269         node->n_children = n_children; node->e_children = n_children;
768 185         g_slist_free(ids);
769 215
770 220         //printf ("n children: %i.\n", node->n_children); fflush (stdout);
771 215
772 185         //gtk_tree_path_free(path_base);
773
774         #ifdef CHECK_TREE
775                 music_source_view_check_tree(self);
776         #endif
777 };
778
779
780 /////////////////////////////////////////////////////////
781 // Internal musicsourceview routines !
782 //
783
784 // Read the entire source - it's probably empty anyway, and we assume in-memory access since
785 // it's not a library.
786 //
787 209 static void initial_read (MusicSourceView *self, ViewNode *node) {
788 261         g_return_if_fail (node!=NULL);
789 209         g_return_if_fail (node->e_children==-1);        // Should be uninitialised
790 261
791 216 //      if (SP->config_info->n_levels==0) return;
792 261
793 209         _fill_node (self, node);
794 185 };
795
796 261 static ViewPage *read_page_at (MusicSourceView *music_source_view, ViewPage *head,
797 209                                ViewPage *tail, int *e) {
798         g_warn_if_reached ();
799 185         return NULL;
800 };
801
802 209 static void read_next_page (MusicSourceView *music_source_view, ViewPage *page) {
803 261         g_warn_if_reached ();
804 185 };
805
806
807 362 /***************************************************************************************************
808  * View editing methods
809 369  *
810  *   Some assumptions central to this code:
811  *     1. if there is a change at a certain horizontal position across a row, the only possible
812  *        change in the view is the position of that range - not its length.
813 362  */
814
815 367 typedef enum {
816         INSERT, CHANGE_FIND_NEW,
817         CHANGE_FIND_OLD, REMOVE
818 } RowAlterationType;
819
820 typedef struct {
821         RowAlterationType type;
822         GList *clnode; Entry *entry;
823         ViewNode *parent_node;
824
825         /* Only used by INSERT */
826         int *intermediate_ids; int intermediate_n;
827 369
828 367         /* Created on CHANGE_FIND_OLD & used by CHANGE_FIND_NEW */
829 369         GSList **deltas;
830 367 } RowAlteration;
831
832 /* Change notification is actioned in two steps. The first step locates the existing rows for
833 378  * old_entry, and returns a list of RowDeltas (one for each row). The rows for all of the deltas are
834  * then removed from the node, to avoid confusing locate_entry on the second pass.
835 380  *
836  * The second step searches for where new_entry should go, finds the deltas in the list (based on
837  * entry id and intermediate ids) and sets new_n accordingly. The rows are reinserted at the new
838 378  * positions. From this, it's possible to emit rows-reordered and row-changed appropriate.
839 374  *
840  * We have to first find all the deltas and then process them all, because the change in the
841  * entry could have been something drastic involving m:1 joins. */
842 367 typedef struct {
843 378         ViewNode *node;
844 374
845         /* Extent of the delta in its original position */
846 378         int original_n, new_n;
847 367 } RowDelta;
848
849
850 // FIXME: merge these two into process_insert/process_remove ?
851 362 /* _insert_row_in_page: Insert a child in an existing page, pushing around other nodes as
852  *                      appropriate. Note that we assume the page is queried already - in practice,
853  *                      it will have by necessity have been read already to find where to insert. */
854 439 void _insert_row_in_page (MusicSourceView *self, ViewNode *parent_node, ViewPage *page,
855                           int page_i, ViewNode *node, gboolean signal) {
856 362         g_return_if_fail (page_i <= page->size);
857
858         node->parent_page = page;
859 185         node->n = page_i;
860 261
861 385         g_return_if_fail (page->size == page->nodes->len);
862 362         g_array_insert_val (page->nodes, page_i, node);
863 261
864 185         // Renumber subsequent nodes
865 362         for (int i=page_i+1; i<page->size+1; i++) {
866                 ViewNode *n = g_array_index(page->nodes, ViewNode *, i);
867 385                 n->n ++;
868 185                 //g_warn_if_fail (n->n==i+1);
869         };
870 261
871 185         // Move subsequent pages down.
872 362         for (ViewPage *p=page->next; p; p=p->next) {
873 261                 p->start_e++;
874 208                 if (p->start_n > -1)
875 185                         p->start_n++;
876         };
877
878         parent_node->e_children++;
879         if (parent_node->n_children!=-1)
880                 parent_node->n_children++;
881
882 362         page->size++;
883 261
884 362         ROW_MARKERS_ROW_INSERTED (MUSIC_SOURCE_VIEW(self), parent_node, page_i+page->start_e);
885 261
886 408         if (signal) {
887 419                 // signal is only false when the row is being inserted because it was reordered.
888                 
889 408                 GtkTreeIter iter;
890                 iter.stamp = 0;
891                 iter.user_data = parent_node;
892                 iter.user_data2 = page;
893                 iter.user_data3 = GINT_TO_POINTER(page_i);
894                 // FIXME: we could have built a path along the way
895                 GtkTreePath *path = gtk_tree_model_get_path(GTK_TREE_MODEL(self), &iter);
896                 change_trace ("EMIT: row_inserted @ page_i %i\n", page_i);
897                 gtk_tree_model_row_inserted(GTK_TREE_MODEL(self), path, &iter);
898 419
899 420                 if (parent_node->n_children==1 && parent_node->parent_page != NULL) {
900                         // Emit row-has-child-toggled, because this is the first child. (Except on the root).
901 419                         GtkTreeIter parent_iter;
902                         gtk_tree_model_iter_parent (GTK_TREE_MODEL(self), &parent_iter, &iter);
903                         gtk_tree_path_up (path);
904                         gtk_tree_model_row_has_child_toggled (GTK_TREE_MODEL(self), path, &parent_iter);
905                 };
906
907 408                 gtk_tree_path_free(path);
908         };
909 185 };
910
911 362 /* _remove_row_in_page: Remove row at page_i from page. Assumes parent_node has been read and page
912 388  *                      is definitely in memory.
913 385  *              signal: whether to emit row-changed signal.
914  *                free: whether to free node data. */
915 362 void _remove_row_in_page (GenericView *self, ViewNode *parent_node, ViewPage *page,
916 385                           int page_i, gboolean signal, gboolean free) {
917 269         g_return_if_fail (page_i >= 0 && page_i < page->size);
918
919         ViewNode *dead_node = g_array_index(page->nodes, ViewNode *, page_i);
920
921 385         if (free)
922                 _music_source_view_free_node (dead_node);
923 269
924         page->nodes = g_array_remove_index(page->nodes, page_i);
925         page->size--;
926
927         // Renumber the rest of the nodes in this page.
928         for (int i=page_i; i<page->size; i++) {
929                 ViewNode *node = g_array_index (page->nodes, ViewNode *, i);
930                 node->n--;
931         };
932
933         // Renumber the remaining pages.
934         for (ViewPage *p = page->next; p; p=p->next) {
935                 p->start_e--;
936                 if (p->start_n!=-1)
937                         p->start_n--;
938         };
939
940         parent_node->e_children--;
941         if (parent_node->n_children!=-1)
942                 parent_node->n_children--;
943
944         if (signal) {
945                 g_return_if_fail (page->start_e >= 0);
946
947                 GtkTreeIter iter;
948                 iter.stamp = 0;
949                 iter.user_data = parent_node;
950                 iter.user_data2 = page;
951                 iter.user_data3 = GINT_TO_POINTER(page_i+page->start_e);
952
953                 // FIXME: we could have built a path along the way before calling this function in some cases
954                 GtkTreePath *path = gtk_tree_model_get_path(GTK_TREE_MODEL(self), &iter);
955
956                 ROW_MARKERS_ROW_DELETED (MUSIC_SOURCE_VIEW(self), parent_node, page_i+page->start_e);
957
958                 gtk_tree_model_row_deleted(GTK_TREE_MODEL(self), path);
959 422
960                 // Don't emit row-has-child-toggled for rows that are about to be deleted. There is no
961                 // point. We only emit it for rows that are added because otherwise, GtkTreeView doesn't
962                 // display an expander.
963                 /*if (parent_node->n_children==0) {
964 419                         // Emit row-has-child-toggled, because this is the last child.
965                         // FIXME: this row should actually be removed now, we should ensure that it is somehow
966                         // FIXME: And, there's possibly no point emitted this signal, but we do it for 
967                         // consistency at the moment.
968                         GtkTreeIter parent_iter;
969                         gtk_tree_model_iter_parent (GTK_TREE_MODEL(self), &parent_iter, &iter);
970                         gtk_tree_path_up (path);
971 422                          gtk_tree_model_row_has_child_toggled (GTK_TREE_MODEL(self), path, &parent_iter);
972                 };*/
973 419
974 269                 gtk_tree_path_free(path);
975         };
976 442
977         // 'Special' entries persist and so we have to deal with hiding them when they have no children.
978         if (parent_node->parent_tail != NULL
979              && ENTRY_IS_SPECIAL(CONFIG_NODE(parent_node->parent_tail)->type, parent_node->id))
980                 if (parent_node->e_children == 0)
981                         _remove_row_in_page (self, parent_node->parent_page->parent_node,
982                                              parent_node->parent_page, parent_node->n, signal, free);
983 269 };
984
985
986 185
987 // Utility
988 // FIXME: put this in entry.c somehow ..
989 static void reference_entry_callback(Entry *entry) {
990         entry_ref(entry, "walk-config");
991 };
992
993 void dump_test_entry(Entry *entry) {
994         if (entry->type==ENTRY_TYPE_FILE)
995 261                 printf("'%s'\n", (const char *)entry_get_property(entry, FILE_PATH));
996 185         fflush(stdout);
997 };
998
999 431 /* partial_node_match: decide if row 'node' represents 'entry'.
1000  */
1001 static gboolean partial_node_match (ViewNode *view_node, GList *clnode, Entry *entry,
1002 388                                     int *intermediate_ids, int intermediate_n) {
1003 431         g_return_val_if_fail (CONFIG_NODE(clnode)->type == entry->type, FALSE);
1004
1005         // When clnode is the tail node of a row, we can use the normal match function.
1006         //
1007 374         if (!CONFIG_NODE(clnode)->flat) {
1008 431                 if (clnode != view_node->parent_tail)
1009 385                         return FALSE;
1010 431                 change_trace ("\tpartial_node_match[%s %i]: Matching whole [at cnode %s - vnode n %i id %i].\n",
1011                               ENTRY_PF(entry), CONFIG_TYPE_NAME(clnode), view_node->n, view_node->id);
1012                 return _music_source_view_match_node(view_node, entry->id, intermediate_ids);
1013 374         };
1014
1015 385         // Walk to tail and check with the node's parent tail.
1016         for (GList *tail_clnode=clnode; tail_clnode; tail_clnode=tail_clnode->next)
1017                 if (!CONFIG_NODE(tail_clnode)->flat) {
1018 431                         if (tail_clnode!=view_node->parent_tail) return FALSE;
1019 385                         break;
1020                 };
1021
1022 374         // Match partially on intermediate ids.
1023         //
1024         if (intermediate_n==0) {
1025 431                 change_trace ("\tpartial_node_match: No int ids yet.\n");
1026 374                 return TRUE;
1027         };
1028
1029 431         int *ii = view_node->intermediate_ids, *jj = intermediate_ids;
1030 374         g_warn_if_fail (ii != NULL && jj != NULL);
1031         for (int i=0; i<intermediate_n; i++) {
1032                 g_return_val_if_fail (*ii!=0 && *jj!=0, FALSE);
1033                 if (*ii != *jj)
1034                         return FALSE;
1035         };
1036         return TRUE;
1037 };
1038
1039 362
1040 365 static void process_row_insert (GenericView *self, GList *tail_clnode, Entry *tail_entry,
1041                                 ViewNode *parent_node, int *intermediate_ids, int intermediate_n,
1042                                 gboolean entry_matched, ViewPage *page, int n_min, int n_max) {
1043 362         if (entry_matched) {
1044                 // There are a couple of possibilities here (when _locate_entry appears to have
1045                 // found the entry that we haven't yet added to the view).
1046
1047                 // 0) This is impossible, since _locate_entry_range should have returned FALSE.
1048                 g_return_if_fail (n_min < n_max);
1049
1050                 // 1) Consider "album[name]:release:track:recording:file[path]". Add recording 2
1051                 //    and file 2, linked to album 1, release 1, track 2. Now add recording 2 and
1052                 //    file 2 again, but on album 2, release 2, track 1. insert-recursive will try to
1053                 //    add file 2 for both album 1 and album 2, and here is where we catch the fact
1054                 //    that file 2 has already been added once. We shouldn't actually have to because
1055                 //    the ignore list should sort the problem out.
1056                 //
1057                 //
1058                 if (n_min == n_max-1) {
1059                         ViewNode *child_node = g_array_index(page->nodes, ViewNode *,
1060                                                                                                  n_min - page->start_e);
1061                         if (child_node->id == tail_entry->id) {
1062                                 change_trace ("%s %i already added to view in this position.\n",
1063                                                           ENTRY_PF(tail_entry));
1064                                 //printf ("Already added\n"); entry_dump (link_entry);
1065                                 return;
1066                         };
1067                 };
1068
1069                 // 2) This level is sorted by a property which isn't very unique, such as
1070                 //    file.bitrate. It's not unlikely that an existing file has the same bitrate as
1071                 //    the one being added.
1072                 //
1073                 change_trace ("%s %i fits in range [%i, %i) - appending.\n", ENTRY_PF(tail_entry),
1074                                           n_min, n_max);
1075         };
1076
1077         if (n_min!=n_max || page==NULL) {
1078                 // If nowhere was found, just append ...
1079                 // FIXME: don't, this is never really a good thing, something must have gone wrong
1080                 for (page=parent_node->children; page->next!=NULL; page=page->next);
1081                 n_min = n_max==0? 0: n_max-1;
1082         };
1083
1084         /*if (n_min!=n_max)
1085                 g_warning ("Error finding index for %s %i - got between %i and %i\n",
1086                                    entry_type_name[link_entry->type], link_entry->id, n_min, n_max);
1087         g_warn_if_fail (page!=NULL);*/
1088
1089         change_trace ("Inserting node for %s %i at %i, n %i: parent_tail %s.\n\n\n",
1090                                   ENTRY_PF(tail_entry), parent_node->depth, n_min,
1091                                   CONFIG_TYPE_NAME(tail_clnode));
1092
1093         ViewNode *node = _music_source_view_new_node(parent_node->depth+1, tail_clnode,
1094                                                                                                  NULL, NULL);
1095         node->id = tail_entry->id;
1096         node->intermediate_ids = intermediate_ids;
1097
1098         _insert_row_in_page (MUSIC_SOURCE_VIEW(self), parent_node, page, n_min - page->start_e,
1099 408                                                  node, TRUE);
1100 362 };
1101
1102 397 /* process_row_change_old: create changed deltas from a row range. */
1103 374 static void process_row_change_old (GenericView *self, RowAlteration *alteration,
1104                                     GList *tail_clnode, Entry *tail_entry, gboolean entry_matched,
1105 367                                     ViewPage *page, int n_min, int n_max) {
1106 380         change_trace ("process_row_change_old: [%i, %i) tail %s %i\n", n_min, n_max,
1107                       ENTRY_PF(tail_entry));
1108 388
1109 431         if (page == NULL) {
1110 388                 page = alteration->parent_node->children;
1111 385                 n_min = 0; n_max = alteration->parent_node->e_children;
1112         };
1113 388
1114 431         // Sanity check
1115         g_return_if_fail (page->size == page->nodes->len);
1116
1117 374         // The range supplied could be way too broad. The entry that changed could be anywhere along the
1118         // config, and if it's in the middle of a lot of unsorted nodes the range will probably be way
1119 431         // too big. Or everything could be called "Unknown Artist - Unknown Title" at this point. For
1120         // this reason, we check all of the id's of each node, and only create a delta if it's exactly
1121         // what we want.
1122 438
1123         // We don't do this checking if entry_matched is true. This is more than just an optimisation:
1124         // on change_old propagation where the trigger isn't the tail we do matching with brute force
1125         // instead of the iterative binary search, which means alteration->intermediate_ids might not
1126         // be filled properly - thus breaking partial_node_match.
1127         g_return_if_fail (!entry_matched || n_min+1==n_max);
1128
1129 374         for (int n=n_min; n<n_max; n++) {
1130                 // Is current node a horizontal match? We don't use match_node because we only want to match
1131                 // as far as tail_clnode in the config, which might not be the end ..
1132 431                 g_return_if_fail (n - page->start_e < page->nodes->len);
1133
1134 380                 ViewNode *node = g_array_index(page->nodes, ViewNode *, n - page->start_e);
1135 431                 g_return_if_fail (node != NULL);
1136 374
1137 438                 if (entry_matched ||
1138                      (node->parent_tail == tail_clnode &&
1139                       partial_node_match(node, tail_clnode, tail_entry, alteration->intermediate_ids,
1140                                          alteration->intermediate_n))) {
1141 380                         change_trace ("\t%i has changed [%i].\n", n, node->id);
1142 378                         RowDelta *delta = g_slice_new(RowDelta);
1143 431                         _music_source_view_check_tree (MUSIC_SOURCE_VIEW(self), node);
1144 378                         delta->node = node;
1145                         delta->original_n = n;
1146 385                         delta->new_n = -1;
1147 378                         (*alteration->deltas) = g_slist_prepend(*alteration->deltas, delta);
1148 380                 } else {
1149                         change_trace ("\t%i hasn't changed [%i]\n", n, node->id);
1150 378                 };
1151 380
1152 378                 // FIXME: perhaps implement a map_row_range function ...
1153                 if (n_min - page->start_e > page->size) {
1154                         if (!page->chain_next)
1155                                 _music_source_view_read_next_page(MUSIC_SOURCE_VIEW(self), page);
1156                         page = page->next;
1157                 };
1158 374         };
1159 438
1160         change_trace ("\tpropagate_change_old: alteration has %i deltas.\n",
1161                       g_slist_length (*alteration->deltas));
1162 367 };
1163
1164 431 /* process_row_change_new: find existing delta for row, and store its new position.
1165  * 
1166  *   At this point the rows that have changed has been removed, and will be reinserted later.
1167  *   change_new is propagated all the way to the tail, so we locate the exact insertion point for
1168  *   the row.
1169 377  * */
1170 369 static void process_row_change_new (GenericView *self, RowAlteration *alteration,
1171                                     GList *tail_clnode, Entry *tail_entry, gboolean entry_matched,
1172                                     ViewPage *page, int n_min, int n_max) {
1173 431         change_trace ("process_row_change_new: tail %s %i, [%i, %i] .. %i deltas\n",
1174                       ENTRY_PF(tail_entry), n_min, n_max, g_slist_length (*alteration->deltas));
1175 367
1176 431         // Find the delta in our list whose view node represents 'tail_entry' at 'tail_clnode'.
1177 367         // FIXME: this could potentially be meaningfully slow - in flat views, lots of rows can change
1178         // at once ..
1179         RowDelta *delta = NULL;
1180 374         for (GSList *deltas_node=(*alteration->deltas); deltas_node; deltas_node=deltas_node->next) {
1181 378                 delta = deltas_node->data;
1182                 g_return_if_fail (delta->node != NULL);
1183 380
1184 431                 // At this point, 'node' is not inside any parent node.
1185                 if (partial_node_match (delta->node, tail_clnode, tail_entry,
1186                                         alteration->intermediate_ids, alteration->intermediate_n))
1187 378                         goto found;
1188 367         };
1189 369
1190 377         g_warning ("process_row_change_new: Unable to find delta for %s %i @ [%i, %i)\n",
1191                    ENTRY_PF(tail_entry), n_min, n_max);
1192         return;
1193
1194 found:;
1195 364
1196 374         int n = n_min;
1197 378         delta->new_n = n;
1198         change_trace ("Reinserting at n%i.\n", n);
1199         // node->n is updated by _insert_row_in_page
1200 388         _insert_row_in_page (MUSIC_SOURCE_VIEW(self), alteration->parent_node, page, n-page->start_e,
1201 408                              delta->node, FALSE);
1202 399         music_source_view_add_row_marker (MUSIC_SOURCE_VIEW(self), &delta->new_n,
1203                                           alteration->parent_node, TRUE);
1204 364 };
1205
1206
1207 380 /* process_row_delete: called by propagate methods to remove the row representing 'tail_entry'
1208  *                     in [n_min, n_max) on parent_node:page */
1209 445 static void process_row_delete (GenericView *self, RowAlteration *alteration, GList *tail_clnode,
1210                                 Entry *tail_entry, ViewNode *parent_node, gboolean entry_matched,
1211                                 ViewPage *page, int n_min, int n_max) {
1212 442         change_trace ("process_row_delete: %s %i, matched %i.\n", ENTRY_PF(tail_entry), entry_matched);
1213 362         if (!entry_matched) {
1214 416                 // We should have found the entry, but there is a possibility it was already removed:
1215                 // in config artist:album:release:track:recording:file, when an artist is removed we act on
1216                 // the track notify and the file notify so end up removing each row twice.
1217                 // FIXME: do something more robust than just ignoring failure to find the row.
1218                 // OLD: g_warning ("_remove_recursive: Unable to find %s %i in view..", ENTRY_PF(tail_entry));
1219 362                 return;
1220         };
1221
1222         // FIXME: test for this .. remove 1 file out of a bunch with same bitrate, we should use
1223         // brute force to find the entry.
1224         if (n_min < n_max+1) {
1225 445                 entry_matched = _locate_entry_brute_force (self, tail_clnode, tail_entry, alteration->entry, page,
1226                                                            n_min, n_max, &page, &n_min);
1227 362                 g_return_if_fail (entry_matched);
1228         };
1229
1230 385         _remove_row_in_page (self, parent_node, page, n_min - page->start_e, TRUE, TRUE);
1231 362 };
1232
1233 431 /* propogate_backwards and propogate_forwards: perform 'change' on every row referencing 'entry'.
1234  *
1235  *   This is a more complicated business than you'd think, mainly due to many:1 joins. Picture the
1236  *   config "artist:album:release:track:recording:file":
1237 362  *       a) The same recording can appear multiple times on different tracks, or in the orphanage,
1238  *          so if a file is added or removed this must also be done in multiple places.
1239  // FIXME: finish writing this :)
1240  *
1241  *  link_clnode, link_entry: This pair are walked backwards and then forwards through the config.
1242  *
1243  *      entry_clnode, entry: The entry that triggered the change. If it's being inserted or removed,
1244  *                           it must be a tail entry or the LHS of a m:1 join.
1245  *
1246  *              parent_node: Where all of this action is taking place.
1247  *
1248  *              accumulator: While walking backwards, the entries we pass through are stored in the
1249  *                           accumulator to make it much easier to walk forwards again.
1250  *
1251 381  *
1252  * Specific to propagate_forwards:
1253 399  *       past_trigger_entry: We could have been triggered by an entry in the middle of the level,
1254 397  *                           but we still need to find the tail entries to match them against rows.
1255 399  *                           past_trigger_entry indicates the accumulator has been exhausted, and we
1256  *                           should use view_config_step_forwards and find all of the subsequent
1257 397  *                           rows.
1258 381  *
1259 384  *               branch_id,  Once past_trigger_entry, we could be querying all entries across a join
1260  *          branch_tested_n: when in fact, the join is within an orphan branch and we should filter
1261  *                           out entries that don't belong on the branch.
1262 381  *
1263  *   As an example, picture the following config:
1264  *     (album[name]:release:track:recording:file[path])  /  (artist[name]:recording:file[path])
1265  *   Imagine we receive change notify on 'artist 3'. Because this is the trigger entry,
1266  *   propagate_forwards proceeds to call view_config_step_forwards, and gets all the recordings of
1267  *   that artist. Some of them aren't orphaned, however, so they don't actually appear where we are
1268 384  *   looking. We need to check them with check_clnode_for_entry.
1269  *
1270  *   This is only necessary when doing change_find_new. When doing a remove or insert the entry
1271  *   could in fact have moved to a different clnode, so the code will get really confused.
1272 374  *
1273  * Terminology here is specific: a 'change' means specifically an entry that this row represents has
1274 365  * changed. An 'alteration' is a row being removed, inserted or changed.
1275  */
1276 438
1277 static int propagate_forwards (GenericView *self, RowAlteration *alteration, GList *link_clnode,
1278                                Entry *link_entry, GSList *accumulator_node,
1279                                gboolean past_trigger_entry, guint32 branch_id, int branch_tested_n,
1280                                ViewPage *_page, int _n_min, int _n_max);
1281
1282 static int propagate_forwards_recurse (GenericView *self, RowAlteration *alteration,
1283                                        GList *link_clnode, Entry *link_entry, guint32 branch_id,
1284                                        int branch_tested_n, ViewPage *page, int n_min, int n_max)
1285   {
1286         // This is a utility function called when the trigger entry is not the tail. In most cases, we
1287         // then recurse through every entry of type link->next that has a foreign key on link_entry.
1288         GList *prev_link_clnode = link_clnode->prev;
1289
1290         if (alteration->type == CHANGE_FIND_OLD) {
1291                 // When looking for entries that have changed in their old position, we can't use the above
1292                 // tactic because an entry past the trigger entry might have changed too (and still be
1293                 // waiting in the notify queue). This might prevent us from finding it using the binary
1294                 // search. Instead, we use brute force search for rows that represent the trigger entry.
1295                 change_trace ("propagate_forwards_recurse: brute forcing: link %s %i, trigger %s %i.\n",
1296                               ENTRY_PF(link_entry), ENTRY_PF(alteration->entry));
1297
1298                 // We don't recurse further, so we must be at the trigger entry and not past it.
1299                 g_return_val_if_fail (link_entry == alteration->entry, 0);
1300
1301                 ViewPage *search_page; int search_n;
1302                 int rows = 0;
1303 445                 while (_locate_entry_brute_force (self, prev_link_clnode, link_entry, NULL, page,
1304                                                   n_min, n_max, &search_page, &search_n)) {
1305 438                         ViewNode  *child_node      = g_array_index(search_page->nodes, ViewNode *, search_n);
1306                         EntryType  tail_entry_type = CONFIG_NODE(child_node->parent_tail)->type;
1307                         Entry     *tail_entry      = music_source_query_entry (PP->source, tail_entry_type,
1308                                                                                child_node->id, "propagate");
1309                         process_row_change_old (self, alteration, child_node->parent_tail, tail_entry, TRUE,
1310                                                 page, search_n, search_n+1);
1311                         entry_unref (tail_entry, "propagate");
1312                         page = search_page; n_min = search_n + 1;
1313
1314                         if (n_min == n_max) break;
1315                 }
1316                 return rows;
1317         }
1318
1319         change_trace ("propagate_forwards_recurse: link is %s %i, trigger %s %i.\n",
1320                       ENTRY_PF(link_entry), ENTRY_PF(alteration->entry));
1321
1322         // view_config_step_forwards_fast() returns a list of entry lists, for each branch at
1323         // link_clnode->prev. The list of entries for each branch match the type of the config node,
1324         // but aren't checked for if they actually belong on that branch (that's the 'fast' part of the
1325         // name). For efficiency, we don't check the branches straight away: we wait and see if we reach
1326         // orphaned entry type in the config and check then. If it's not reached we check at the tail
1327         // node.
1328         //
1329         // FIXME: in the case of small range, would it be quicker to actually use the above method?
1330         // instead of all the entry queries i binary search?
1331         int row_count = 0;
1332         GSList *link_branch_list = view_config_step_forwards_fast
1333                                      (PP->source, PP->config, prev_link_clnode, link_entry);
1334         for (GSList *link_list=link_branch_list; link_list; link_list=link_list->next) {
1335                 g_return_val_if_fail (link_clnode != NULL, 0);
1336                 change_trace ("\tpropagate_forwards_recurse: at %s: %i entries on branch %i (%s) from %s %i"
1337                               ".\n", CONFIG_TYPE_NAME(prev_link_clnode), g_slist_length (link_list),
1338                               CONFIG_NODE(link_clnode)->branch_id, CONFIG_TYPE_NAME(link_clnode),
1339                               ENTRY_PF(link_entry));
1340                 for (GSList *node=link_list->data; node; node=node->next) {
1341                         Entry *entry = node->data;
1342                         change_trace ("\t\tpropagate_forwards_recurse: %s %i.\n", ENTRY_PF(entry));
1343
1344                         guint32 bid = branch_id; int btn = branch_tested_n;
1345
1346                         if (CONFIG_NODE(link_clnode)->orphaning_join != NULL
1347                                  && (alteration->type==CHANGE_FIND_OLD || alteration->type==CHANGE_FIND_NEW))
1348                                 // We are at the type of this branch's orphaning join. Check if the entry belongs on
1349                                 // the branch or if it's actually.
1350                                 //
1351                                 // FIXME: at the moment there is only one possible orphan branch, but if two or more
1352                                 // were possible, and the orphaned join entry was inside another orphan branch, this
1353                                 // code wouldn't trigger and things might break horribly. We would need to maintain
1354                                 // a list of past orphaning joins in the cnode, perhaps, or pass one along
1355                                 // propagate_forwards.
1356                                 if (!check_clnode_for_entry(PP->source, PP->config, link_clnode, entry, &bid, &btn))
1357                                         continue;
1358
1359                         // FIXME: Filter entry by branch if it is the type of one of the  ...
1360
1361                         // intermediate_ids array is shared, which is fine because the calls are sequential.
1362                         const int intermediate_n = alteration->intermediate_n;
1363                         int rows = propagate_forwards(self, alteration, link_clnode, entry, NULL,
1364                                                                                   TRUE, bid, btn, page, n_min, n_max);
1365
1366                         switch (alteration->type) {
1367                                 case INSERT: case CHANGE_FIND_NEW:  n_max += rows; break;
1368                                 case REMOVE:                        n_max -= rows; break;
1369                         };
1370                         row_count += rows;
1371                         alteration->intermediate_n = intermediate_n;
1372                 }
1373                 entry_query_list_free (link_list->data, "walk-config");
1374
1375                 // Update the config node to point to the orphan branch.
1376                 link_clnode = CONFIG_NODE(link_clnode)->orphan_branch;
1377         }
1378         g_slist_free (link_branch_list);
1379
1380         // FIXME: I commented this out and it didn't seem to break anything.
1381         //if (link_entry != alteration->entry)
1382         //      entry_unref (link_entry, "walk-config");
1383         return row_count;
1384 }
1385
1386 365 static int propagate_forwards (GenericView *self, RowAlteration *alteration, GList *link_clnode,
1387                                Entry *link_entry, GSList *accumulator_node,
1388 384                                gboolean past_trigger_entry, guint32 branch_id, int branch_tested_n,
1389 381                                ViewPage *_page, int _n_min, int _n_max) {
1390 384         change_trace ("propagate_forwards - [%i, %i) - for %s %i, link %s %i @ %s\n", _n_min, _n_max,
1391                       ENTRY_PF(alteration->entry), ENTRY_PF(link_entry), CONFIG_TYPE_NAME(link_clnode));
1392 185         ViewPage *page = _page; int n_min = _n_min, n_max = _n_max;
1393 261
1394 438         // locate_entry normally reads entries from the source, using the id's stored in the view.
1395         // During a change notification we might want to find an entry not in the source (in particular,
1396         // the previous version of an entry).
1397         Entry *search_substitute_entry = past_trigger_entry? link_entry: alteration->entry;
1398 237         //printf("\tThe accumulator has: ");
1399         //for (GSList *node=accumulator_node; node; node=node->next) {
1400         //      Entry *entry = node->data;
1401         //      printf(" [%x]", (guint)entry); fflush(stdout);
1402         //      printf(" %s %i", ENTRY_PF(entry)); fflush(stdout);
1403         //};
1404         //printf("\n"); fflush(stdout);
1405 261
1406 374         // Walk forwards through the config. Current position is link_entry representing link_clnode. On
1407         // each iteration we try and narrow the range [n_min, n_max).
1408 209         gboolean entry_matched = FALSE;
1409 384         ViewConfigNode *link_cnode = link_clnode->data;
1410 185         do {
1411 226                 g_warn_if_fail (link_clnode!=NULL);
1412 261                 g_warn_if_fail (link_cnode->type==link_entry->type);
1413
1414 374                 // If the config node is sorted, narrow the range using binary search.
1415                 //
1416 380                 if ((n_min!=n_max || page==NULL) && link_cnode->sort_property_id!=-1) {
1417 438                         entry_matched = _locate_entry_range(self, link_clnode, link_entry,
1418                                                             search_substitute_entry, alteration->parent_node,
1419                                                             &page, &n_min, &n_max);
1420 380                         change_trace ("\ton %s: new range [%i, %i)\n", CONFIG_TYPE_NAME(link_clnode), n_min,
1421                                       n_max);
1422                 };
1423 261
1424 374                 if (!link_cnode->flat)
1425 185                         break;
1426 431                 if (!link_clnode->next) {
1427                         g_warning ("Unexpected end of config, at node %s.\n",
1428                                    entry_type_name[link_cnode->type]);
1429                         break;
1430                 }
1431 261
1432 383                 // Move to next node in config - going down an orphan branch if alteration->clnode (trigger)
1433                 // is down that branch.
1434                 //
1435 261                 link_clnode = link_clnode->next;
1436 383                 if (CONFIG_NODE(link_clnode)->orphan_branch!=NULL
1437                           && CLNODE_IS_ON_BRANCH(alteration->clnode, CONFIG_NODE(link_clnode)->orphan_branch))
1438                         link_clnode = CONFIG_NODE(link_clnode)->orphan_branch;
1439                 link_cnode = CONFIG_NODE(link_clnode);
1440 261
1441 185                 // many:1 joins are flagged on the 1 side, so we look ahead for them.
1442                 //
1443 365                 // FIXME: I don't think this works, because we should be storing the id of the entry *before*
1444                 // the node flagged as many:1. Doesn't seem to be tested yet.
1445 374                 if (alteration->type==INSERT || alteration->type==CHANGE_FIND_OLD
1446                       || alteration->type==CHANGE_FIND_NEW)
1447 383                         if (link_cnode->flags & VIEW_CONFIG_NODE_MANY_TO_ONE_JOIN) {
1448 365                                 g_return_val_if_fail (alteration->intermediate_ids!=NULL, 0);
1449                                 alteration->intermediate_ids[alteration->intermediate_n++] = link_entry->id;
1450 362                         };
1451 261
1452 365                 if (link_entry==alteration->entry)
1453 362                         past_trigger_entry = TRUE;
1454 185
1455                 if (accumulator_node!=NULL) {
1456                         // If we are still working towards the entry type which was added
1457                         //
1458 281                         //printf ("for %s %i: inp: Unref %s %i.\n", ENTRY_PF(entry), ENTRY_PF(link_entry)); fflush (stdout);
1459 185                         entry_unref (link_entry, "walk-config");
1460 261                         link_entry = accumulator_node->data;
1461 185                         accumulator_node = accumulator_node->next;
1462 365                 } else if (!past_trigger_entry && link_entry!=alteration->entry) {
1463                         // After the accumulator comes trigger itself.
1464 185                         //
1465                         entry_unref (link_entry, "walk-config");
1466 365                         link_entry = alteration->entry;
1467 185                 } else {
1468 438                         // If we are here, we have got to the trigger entry, and it's not the tail join. We need
1469                         // (probably) to propagate further, by recursing to each entry that has a foreign key
1470                         // to the current entry.
1471                         return propagate_forwards_recurse (self, alteration, link_clnode, link_entry,
1472                                                            branch_id, branch_tested_n, page, n_min, n_max);
1473 185                 };
1474 226         } while (link_clnode!=NULL);
1475 300
1476 384         g_return_val_if_fail (link_clnode!=NULL && !(link_cnode->flat), 0);
1477 374
1478         // Notice link_clnode/entry is used rather than alteration->entry. This trigger entry
1479         // might have been an m:1 join entry rather than a tail node, whereas the link is now
1480         // definitely a tail node because we just walked it to the tail of the level.
1481         //
1482         if (!past_trigger_entry) {
1483 384                 g_warn_if_fail (link_cnode->type==CONFIG_NODE(alteration->clnode)->type);
1484                 g_warn_if_fail (link_cnode->type==alteration->entry->type);
1485 374         };
1486
1487 383         // If past the trigger entry, link_entry might not apply to link_clnode - it could be in a
1488         // different branch. (This is because of the way view_config_step_forwards works).
1489 384         change_trace ("past %i - tested n %i link %i\n", past_trigger_entry, branch_tested_n,
1490                       link_cnode->branch_n);
1491         if (past_trigger_entry && alteration->type==CHANGE_FIND_NEW) {
1492                 if (branch_tested_n < link_cnode->branch_n)
1493 438                         if (!check_clnode_for_entry(PP->source, PP->config, link_clnode, link_entry, &branch_id,
1494 384                                                     &branch_tested_n))
1495 383                                 return 0;
1496 384
1497                 // Branch id can only go wrong when we are past the trigger entry, because of the use of
1498                 // view_config_step_forwards.
1499                 g_return_val_if_fail (branch_id == link_cnode->branch_id, 0);
1500         };
1501 381
1502 377         // Int ids is expected to be null terminated correctly
1503         if (alteration->type==INSERT || alteration->type==CHANGE_FIND_NEW)
1504 378                 if (alteration->intermediate_ids != NULL)
1505                         alteration->intermediate_ids[alteration->intermediate_n] = 0;
1506 377
1507 374         switch (alteration->type) {
1508                 case INSERT:
1509                         process_row_insert (self, link_clnode, link_entry, alteration->parent_node,
1510                                                                 alteration->intermediate_ids, alteration->intermediate_n,
1511                                                                 entry_matched, page, n_min, n_max);
1512                         break;
1513
1514                 case CHANGE_FIND_OLD:
1515                         process_row_change_old (self, alteration, link_clnode, link_entry, entry_matched,
1516                                                                         page, n_min, n_max);
1517                         break;
1518
1519                 case CHANGE_FIND_NEW:
1520                         process_row_change_new (self, alteration, link_clnode, link_entry, entry_matched,
1521                                                                         page, n_min, n_max);
1522                         break;
1523
1524                 case REMOVE:
1525 445                         process_row_delete (self, alteration, link_clnode, link_entry, alteration->parent_node,
1526 374                                                                 entry_matched, page, n_min, n_max);
1527                         break;
1528                 default: g_warn_if_reached ();
1529         };
1530
1531         return 1;
1532 185 };
1533
1534 365 static void propagate_backwards (GenericView *self, RowAlteration *alteration, GList *link_clnode,
1535                                  Entry *link_entry, GSList **accumulator) {
1536 389         change_trace ("propagate_backwards: parent %x depth %i, n %i %s %i <%s>\n",
1537                       alteration->parent_node, alteration->parent_node->depth, alteration->parent_node->n,
1538                       ENTRY_PF(alteration->entry), CONFIG_TYPE_NAME(alteration->clnode));
1539 362         g_return_if_fail (CONFIG_NODE(link_clnode)->type == link_entry->type);
1540 261
1541 234         // Walk from current position to the previous break - the head join of the parent node, or the
1542         // *RHS* of the m:1 join.
1543 362         change_trace ("\twalk to join: from %s %i, [config %s.%i], ..", ENTRY_PF(link_entry),
1544 365                       CONFIG_TYPE_NAME(link_clnode), CONFIG_NODE(alteration->clnode)->branch_id);
1545 362
1546         link_entry = view_config_walk_backwards_specific(link_clnode, PP->source, link_entry,
1547 431                                                          VIEW_CONFIG_WALK_TO_BREAK, NULL, NULL,
1548 362                                                          &link_clnode, accumulator);
1549
1550         change_trace ("to %s %i [%s].\n", ENTRY_PF(link_entry), CONFIG_TYPE_NAME(link_clnode));
1551
1552         if (CONFIG_NODE(link_clnode)->flags & VIEW_CONFIG_NODE_MANY_TO_ONE_JOIN) {
1553 281                 // We're not at the head of the level yet, we hit a break. Find all the entries on the
1554 362                 // other side and run _insert_in_node to continue down each of those paths.
1555                 ViewConfigNode *link_cnode = link_clnode->data;
1556                 GSList *many = music_source_query_relations(PP->source, link_entry->type, link_entry->id,
1557                                                                                                         link_cnode->relation_apid, "entry-added");
1558 226                 g_return_if_fail (g_slist_length(many) > 0);
1559
1560                 for (GSList *many_node=many; many_node; many_node=many_node->next) {
1561                         Entry *many_entry = many_node->data;
1562 185
1563 261                         // Copy the accumulator for each iteration (because it needs to be different for
1564 226                         // each one), but use actual accumulator on last go.
1565                         // FIXME: couldn't you just remove relation from it and add new relation each time?
1566 281                         // the recursion runs in sequence, not in parallel
1567 185                         GSList **accumulator_to_use = accumulator, *copied_accumulator = NULL;
1568 226                         if (many_node->next!=NULL) {
1569 185                                 copied_accumulator = g_slist_copy(*accumulator);
1570 226                                 g_slist_foreach (copied_accumulator, (GFunc)reference_entry_callback, NULL);
1571 185                                 accumulator_to_use = &copied_accumulator;
1572                         };
1573
1574 226                         entry_ref (many_entry, "walk-config");
1575                         *accumulator_to_use = g_slist_prepend(*accumulator_to_use, many_entry);
1576 261
1577 365                         propagate_backwards (self, alteration, link_clnode->prev, many_entry,
1578                                              accumulator_to_use);
1579 185                         //printf("Set to ignore %s %i\n", entry_type_name[relation->type], relation->id); fflush(stdout);
1580 261
1581 365                         if (alteration->type==INSERT) {
1582                                 GSList **ignore_list = &self->ignore_list[many_entry->type];
1583                                 (*ignore_list) = g_slist_prepend(*ignore_list, GINT_TO_POINTER(many_entry->id));
1584                         };
1585 261
1586 185                         if (copied_accumulator!=NULL)
1587                                 g_slist_free (copied_accumulator);
1588                 };
1589 261
1590                 entry_query_list_free (many, "entry-added");
1591 365         } else if (link_clnode==alteration->parent_node->head || link_clnode->prev==NULL) {
1592 300                 // We hit the head - now we go back the way we came, pinpointing the insert position,
1593 281                 // using the forwards-recursive insert_recursive.
1594 185                 //
1595 442                 change_trace ("\tpropagate_backwards: parent (%s %i) has %i children\n",
1596                               CONFIG_TYPE_NAME(alteration->parent_node->parent_tail),
1597                               alteration->parent_node->id, alteration->parent_node->e_children);
1598 438                 ViewPage *page = alteration->parent_node->children;
1599                 int n_min = 0, n_max = alteration->parent_node->e_children;
1600 261
1601 367                 // Per-alteration init for forwards
1602                 switch (alteration->type) {
1603 374                         case INSERT: case CHANGE_FIND_OLD: case CHANGE_FIND_NEW: {
1604 367                                 alteration->intermediate_ids = NULL, alteration->intermediate_n = 0;
1605                                 ViewConfigNode *level_head = CONFIG_NODE(alteration->parent_node->head);
1606                                 if (level_head->level_break_count > 1)
1607                                         alteration->intermediate_ids = g_new0(int, level_head->level_break_count);
1608                                 break;
1609                         };
1610 365                 };
1611 185
1612 281                 // First entry of accumulator == new_entry
1613 300                 GSList *accumulator_node = *accumulator!=NULL? (*accumulator)->next: NULL;
1614
1615 365                 propagate_forwards (self, alteration, link_clnode, link_entry, accumulator_node, FALSE,
1616 384                             1, 1, page, n_min, n_max);
1617 185         };
1618 365 };
1619
1620 static void propagate_alteration (GenericView *self, RowAlterationType type, GList *clnode,
1621                                   Entry *entry, ViewNode *parent_node) {
1622         g_return_if_fail (CONFIG_NODE(clnode)->type == entry->type);
1623
1624 369         // Use propogate_change for change notifications.
1625         g_return_if_fail (type==INSERT || type==REMOVE);
1626
1627 365         RowAlteration *alteration = g_slice_new(RowAlteration);
1628         alteration->type = type;
1629         alteration->clnode = clnode;
1630         alteration->entry = entry;
1631         alteration->parent_node = parent_node;
1632
1633         GSList *accumulator = NULL;
1634
1635         propagate_backwards (self, alteration, clnode, entry, &accumulator);
1636
1637         // propagate_forwards unrefs entry and the rest of the accumulator, but doesn't free the list.
1638         g_slist_free (accumulator);
1639         g_slice_free (RowAlteration, alteration);
1640 185 };
1641
1642 431 /* FIXME: this is an extra-confusing name. You should say 'update' instead of 'change', since it's
1643  * a slightly less broad term. */
1644 369 static void propagate_change (GenericView *self, GList *clnode, Entry *old_entry, Entry *new_entry,
1645                               ViewNode *parent_node, GSList **deltas) {
1646         g_return_if_fail (CONFIG_NODE(clnode)->type == old_entry->type);
1647         g_return_if_fail (CONFIG_NODE(clnode)->type == new_entry->type);
1648
1649 438         change_trace ("propagate_change: %s.\n", CONFIG_TYPE_NAME(clnode));
1650
1651 369         RowAlteration *alteration = g_slice_new(RowAlteration);
1652         alteration->parent_node = parent_node;
1653         alteration->clnode = clnode;
1654         alteration->deltas = deltas;
1655
1656         GSList *accumulator = NULL;
1657
1658         /* FIXME: do we only need to walk backwards once, and go forwards twice, I think ?? */
1659
1660 438         // Find every row that represents old_entry and create a RowDelta entry for it (holding its
1661         // old position). This is harder than it would seem because the musicsource holds the new
1662         // version of the entry, so if the search functions query the source for it they get misled.
1663 369         alteration->type = CHANGE_FIND_OLD;
1664         alteration->entry = old_entry;
1665         propagate_backwards (self, alteration, clnode, old_entry, &accumulator);
1666
1667         g_slist_free (accumulator);
1668 375
1669         // After all the deltas are found we have to remove the existing rows, so
1670 377         // locate_entry_range doesn't get confused by entries in the wrong place when finding the new
1671 375         // insertion points. Instead of using markers we sort the list and remove from top down.
1672         // FIXME: which way would be faster?
1673         // FIXME: it's really inefficient to remove every entry that's had a property changed just
1674         // to reinsert it in the same place. Surely it will only have moved if the property it's
1675         // sorted on has changed.
1676         int delta_compare_func(RowDelta *a, RowDelta *b) {
1677 378                 return a->original_n < b->original_n? -1:
1678                        a->original_n > b->original_n?  1: 0;
1679 375         };
1680         *deltas = g_slist_sort(*deltas, (GCompareFunc)delta_compare_func);
1681         ViewPage *page = parent_node->children;
1682         int rows_removed = 0;
1683 438         GSList *previous_delta_node = NULL; int previous_delta_original_n = -1;
1684 375         for (GSList *deltas_node=*deltas; deltas_node; deltas_node=deltas_node->next) {
1685 377                 RowDelta *delta = deltas_node->data;
1686
1687 438                 // There is a chance of getting duplicate deltas, when m:1 joins are at play. It seems that
1688                 // the most efficient way to deal is to remove them here once the list is sorted.
1689                 // FIXME: of course, a more efficient way might be to make the list a btree.
1690                 if (previous_delta_node != NULL && delta->original_n == previous_delta_original_n) {
1691                         g_slice_free (RowDelta, delta);
1692                         deltas_node = g_slist_remove_link (previous_delta_node, deltas_node);
1693                         continue;
1694                 }
1695
1696 375                 // FIXME: this is a bit of a hack. but, i don't think we should have to query any more pages
1697                 // .. this assumption needs some testing :) If this sort of thing does happen, we need to
1698                 // add all of the delta ranges as markers.
1699 378                 int n = delta->original_n - (rows_removed++);
1700                 page = _music_source_view_find_page_for_row(MUSIC_SOURCE_VIEW(self), parent_node, &n,
1701 375                                                             page);
1702 378                 change_trace ("Removing at %i.\n", n);
1703 385                 _remove_row_in_page (self, alteration->parent_node, page, n - page->start_e, FALSE, FALSE);
1704 438
1705                 previous_delta_original_n = delta->original_n;
1706                 previous_delta_node = deltas_node;
1707 375         };
1708 377
1709 369         accumulator = NULL;
1710 380
1711 390         //printf ("\n\nGot %i deltas. Node size %i\n\n", g_slist_length(*deltas),
1712         //        parent_node->e_children); fflush (stdout);
1713 438         // If any rows have changed: propagate from the new entry
1714 374         if (g_slist_length(*deltas) > 0) {
1715                 alteration->type = CHANGE_FIND_NEW;
1716                 alteration->entry = new_entry;
1717                 propagate_backwards (self, alteration, clnode, new_entry, &accumulator);
1718         };
1719 369
1720         g_slice_free (RowAlteration, alteration);
1721 };
1722 185
1723 380 // Check if 'entry' is adopting any currently orphaned entries.
1724 389 static void entry_notify_process_adoption (GenericView *self, GList *clnode, Entry *entry) {
1725 380         // Find the 1 side of the join, assuming it is a normal many:1 join.
1726         //
1727         ViewConfigNode *rhs = CONFIG_NODE(clnode->next);
1728         g_return_if_fail (entry->type == APID_GET_TYPE(rhs->relation_apid));
1729         g_return_if_fail (APID_GET_PROPERTY(rhs->relation_apid).type == rhs->type);
1730         Entry *one = entry_get_property(entry, APID_GET_PROPERTY_ID(rhs->relation_apid));
1731
1732         // Is this the only parent of the one entry (the recording)?
1733         int relation_count = music_source_query_n_relations
1734                                                    (PP->source, one->id, APID_GET_TYPE(rhs->relation_apid),
1735                                                         APID_GET_PROPERTY_ID(rhs->relation_apid), 2);
1736         g_warn_if_fail (relation_count > 0);
1737
1738         if (relation_count==1) {
1739                 // Turns out we are adopting the orphan - remove it from the orphanage.
1740                 change_trace ("process_adoption: Entry %s %i is adopting orphan %s %i.\n", ENTRY_PF(entry),
1741                                            ENTRY_PF(one));
1742
1743                 // Find the clnode representing the orphan.
1744                 GList *orphan_clnode = rhs->orphanage;
1745                 g_return_if_fail (orphan_clnode!=NULL);
1746
1747 439                 GList *parent_list = _find_parent_nodes(self, orphan_clnode, one, FIND_PARENT_NODES_WARN);
1748 380                 for (GList *parent_list_node=parent_list; parent_list_node!=NULL;
1749 439                      parent_list_node=parent_list_node->next) {
1750 380                         ViewNode *parent_node = parent_list_node->data;
1751 389
1752 380                         if (parent_node->e_children==-1) {
1753                                 // Node has not been read so nothing to do.
1754                                 change_trace ("process_adoption: Parent node %i[%i] (%x) still empty!!\n\n",
1755                                                           parent_node->depth, parent_node->n, (guint)parent_node);
1756                                 continue;
1757 389                         };
1758
1759                         // If the node was read since we started the notify function, it won't actually have
1760                         // the orphan entry in after all (it won't be stale).
1761                         if (g_slist_find(_music_source_view_get_initial_read_watch_list
1762                                            (MUSIC_SOURCE_VIEW(self)), parent_node)) {
1763                                 change_trace ("process_adoption: Parent node %x read during notify.\n",
1764                                               (guint)parent_node);
1765                                 continue;
1766                         };
1767
1768                         change_trace ("process_adoption: Removing %s %i from orphanage\n", ENTRY_PF(one));
1769                         propagate_alteration (self, REMOVE, orphan_clnode, one, parent_node);
1770 380                 };
1771                 g_list_free(parent_list);
1772 389         } else
1773                 change_trace ("process_adoption: Entry %s %i has %i relations.\n", ENTRY_PF(entry));
1774 380 };
1775 396
1776 431 /* entry_notify_action_deltas: emits 'rows-reordered' and 'row-changed' based on 'deltas'
1777  *
1778  *   deltas: a list of RowDelta structs, which represent rows in parent_node that have changed.
1779  *           A RowDelta stores old and new row number, and a pointer to the node. old_n sometimes
1780  *           == new_n.
1781  */
1782 // FIXME: do we need to emit row_changed when we emit rows_reordered ??
1783 // I assume we do have to.
1784 396 static void entry_notify_action_deltas (GenericView *self, ViewNode *parent_node, GSList *deltas_a) {
1785         g_return_if_fail (deltas_a != NULL);
1786 399
1787 396         GtkTreeModel *tree_model = GTK_TREE_MODEL(self);
1788 431
1789         // To emit rows-reordered, we need to create an array 'new_order' of the format:
1790         //   new_order[newpos] = old_pos
1791
1792
1793         // We work with two lists of the deltas:
1794         //  deltas_a: sorted by original n (which is done earlier and passed to us), and
1795         //  deltas_b: sorted by new n (which we do here)
1796 396         // FIXME: in this and the other place deltas list is sorted, does g_slist_sort have the most
1797         // appropriate sorting algorithm?
1798 431         int delta_new_n_compare_func (RowDelta *a, RowDelta *b) {
1799 396                 return a->new_n < b->new_n? -1:
1800                            a->new_n > b->new_n?  1: 0;
1801         };
1802         GSList *deltas_b = g_slist_copy(deltas_a);
1803         deltas_b = g_slist_sort(deltas_b, (GCompareFunc)delta_new_n_compare_func);
1804 399
1805 438         change_trace ("\tentry_notify_action_deltas: a %i, b %i. range: 0-%i\n",
1806                       g_slist_length (deltas_a), g_slist_length (deltas_b), parent_node->e_children);
1807 431
1808         RowDelta *next_reordered_delta (GSList **node) {
1809 396                 if (*node==NULL) return NULL;
1810                 RowDelta *delta = (*node)->data;
1811                 while (delta!=NULL && delta->original_n==delta->new_n) {
1812 431                         change_trace ("\t\tSkipping delta %x. (%i == %i)\n", delta, delta->original_n, delta->new_n);
1813 399                         *node = (*node)->next;
1814 396                         delta = *node!=NULL? (*node)->data: NULL;
1815                 };
1816                 return delta;
1817 399         };
1818
1819 431         // Create 'new_order' array to pass to rows_reordered, where new_order[newpos] = oldpos.
1820 396         int *new_order = g_new(int, parent_node->e_children);
1821 399         int shift = 0;
1822
1823         GSList *a_node = deltas_a, *b_node = deltas_b;
1824 431         const RowDelta *a_delta = next_reordered_delta(&a_node),
1825                        *b_delta = next_reordered_delta(&b_node);
1826 396         if (a_delta==NULL && b_delta==NULL) goto no_movements;
1827 399
1828         for (int i=0; i<parent_node->e_children; i++) {
1829                 while (b_delta!=NULL && i==b_delta->new_n) {
1830 438                         //printf ("\tdelta B %i->%i", b_delta->original_n, b_delta->new_n); fflush (stdout);
1831 396                         new_order[i] = b_delta->original_n;
1832 399                         shift --;
1833 431                         b_node = b_node->next; b_delta = next_reordered_delta(&b_node);
1834 396                 };
1835 399
1836                 while (a_delta!=NULL && (shift>0?(i+shift):i)==a_delta->original_n) {
1837 438                         //printf ("\tdelta A %i->%i", a_delta->original_n, a_delta->new_n); fflush (stdout);
1838 396                         shift ++;
1839 431                         a_node = a_node->next; a_delta = next_reordered_delta(&a_node);
1840 396                 };
1841 399
1842 431                 //printf ("\tshift %i:\n", shift); fflush (stdout);
1843 399
1844                 new_order[i] = i + shift;
1845 396         };
1846 399
1847 431         // The number of deltas (old and new) should be the same length. If we didn't reach the end of
1848         // either list, something is wrong.
1849         g_warn_if_fail (a_node==NULL && b_node==NULL);
1850 399
1851 406         // Pass NULL as iter if it's the root node.
1852         GtkTreeIter real_parent_iter, *parent_iter = NULL;
1853         if (parent_node->parent_page != NULL) {
1854                 real_parent_iter.stamp = 0;
1855                 real_parent_iter.user_data = parent_node->parent_page->parent_node;
1856                 real_parent_iter.user_data2 = parent_node->parent_page;
1857                 real_parent_iter.user_data3 = GINT_TO_POINTER(parent_node->n);
1858                 parent_iter = &real_parent_iter;
1859         };
1860         GtkTreePath *parent_path = parent_iter? gtk_tree_model_get_path(tree_model, parent_iter):
1861                                                 gtk_tree_path_new();
1862         gtk_tree_model_rows_reordered (tree_model, parent_path, parent_iter, new_order);
1863 396         gtk_tree_path_free (parent_path);
1864
1865 no_movements:
1866         g_free (new_order);
1867
1868         // Emit row-changed for all the deltas, and free them all
1869         for (GSList *node = deltas_a; node; node=node->next) {
1870                 RowDelta *delta = node->data;
1871                 g_return_if_fail (delta->new_n >= 0);
1872
1873                 //printf ("Delta: %i -> %i\n", delta->original_n, delta->new_n); fflush (stdout);
1874                 //if (delta->original_n == delta->new_n) {
1875                 ViewNode *node = delta->node;
1876                 GtkTreeIter iter; GtkTreePath *path;
1877                 iter.user_data = parent_node;
1878                 iter.user_data2 = node->parent_page;
1879                 iter.user_data3 = GINT_TO_POINTER(delta->new_n - node->parent_page->start_e);
1880
1881                 path = gtk_tree_model_get_path (tree_model, &iter);
1882                 gtk_tree_model_row_changed (tree_model, path, &iter);
1883                 gtk_tree_path_free (path);
1884 399
1885 397                 music_source_view_remove_row_marker (MUSIC_SOURCE_VIEW(self), &delta->new_n);
1886 396
1887                 g_slice_free (RowDelta, delta);
1888         };
1889 };
1890
1891 431 /* entry_notify: responds to changes in the source and updates the view accordingly. */
1892 185 // FIXME: these are always going to be too slow, which isn't the end of the world as they're
1893 // only for adding/changing data rather than playing, but you should still optimise as much
1894 // as you can.
1895 //
1896 361 // FIXME: one idea is, don't emit row-changed straight away - group them instead and do it in an
1897 // idle, so we don't emit ten for the same row. It's dangerous to do that with row-inserted and
1898 // row-deleted because row references would then be wrong, and all sorts of tiny fast bugs could
1899 // creep in, but it should be okay to do this for row-changed.
1900 //
1901 185 // FIXME: balance max page sizes. (though it won't actually matter if it's one huge page ...)
1902 // The order of the day for this function is as follows:
1903 //  find parent node(s) using find_parent_nodes
1904 261 //    for each many:1 join in the level, and then for the tail join,
1905 //
1906 361 //
1907 static void entry_notify (MusicSource *source, Entry *old_entry, Entry *new_entry,
1908                           MusicSourceView *music_source_view) {
1909 185         GenericView *self = GENERIC_VIEW(music_source_view);
1910 374         change_trace ("\nGenericView::entry-notify: %s %i -> %s %i\n", ENTRY_PF(old_entry),
1911 361                       ENTRY_PF(new_entry));
1912
1913 333         if (PP->config==NULL) return;
1914 361
1915         GList *old_clnode = NULL, *new_clnode = NULL;
1916         GList *old_parents = NULL, *new_parents = NULL;
1917
1918         // FIXME: These are calculated in a slow way, but it doesn't really matter because the lists
1919         // are probably == and of length 1 ...
1920         GList *inserted_list = NULL,  // * Row-inserted in all of: new_parents - old_parents
1921               *changed_list = NULL,   // * Row-changed/rows-reordered in: old_parents U new_parents
1922                   *deleted_list = NULL;   // * Row-deleted from all of: old_parents - new_parents
1923
1924         if (old_entry == NULL) {
1925 431                 // This new_entry is being added - we only need to trigger if it is a break: the last node
1926                 // on a level, or the many side of a many:1 join.
1927 361                 if (!PP->config->is_break[new_entry->type]) return;
1928
1929                 // Look through the ignore list
1930                 // FIXME: why is there an ignore list again?
1931                 // FIXME: is a linked list fast enough?
1932                 // FIXME: also, empty the list on end transaction, it will have cruft in.
1933                 // FIXME: also, do this after we reject on entry type?
1934                 GSList *ignore_lnode = g_slist_find(self->ignore_list[new_entry->type],
1935                                                     GINT_TO_POINTER(new_entry->id));
1936                 if (ignore_lnode!=NULL) {
1937                         self->ignore_list[new_entry->type] =
1938                           g_slist_remove_link(self->ignore_list[new_entry->type], ignore_lnode);
1939                         //printf("entry-added: ignoring %s %i\n",  entry_type_name[type], id); fflush(stdout);
1940                         return;
1941                 };
1942         } else {
1943 431                 // This entry has changed - we always need to trigger in this case, to see if the row needs
1944                 // to be reordered.
1945 361                 if (PP->config->index[old_entry->type]==NULL)
1946                         return;
1947 390         };
1948
1949         if (new_entry == NULL) {
1950                 // Entry removal.
1951                 if (!PP->config->is_break[old_entry->type])
1952                         return;
1953         };
1954
1955         _music_source_view_start_initial_read_watch (music_source_view);
1956
1957         if (old_entry != NULL) {
1958 418                 // Changed and removed notifications pass 'old_entry'. We are permissive of inconsistency
1959                 // here to allow for the following eventuality: a row such as "track:recording", which
1960                 // will receive a remove notify for the recording and be deleted, but then receive one for
1961                 // the track and we will try to remove it again. 
1962                 // FIXME: is there a better way of dealing with this issue?
1963 361                 old_clnode = _find_clnode_for_entry(self, old_entry);
1964 439                 old_parents = _find_parent_nodes(self, old_clnode, old_entry, FIND_PARENT_NODES_IGNORE);
1965 361         }
1966
1967 390         if (new_entry != NULL) {
1968 361                 // Changed and added notifications pass 'new_entry'. Note the config node could have changed on
1969                 // changed notifications, if an entry was an orphan but is now not.
1970 441                 
1971                 // FIXME: policy should be _WARN unless parent entry is special. Really, find_parent_nodes
1972                 // should be the one to deal at the moment.
1973 431                 new_clnode = _find_clnode_for_entry(self, new_entry);
1974 439                 new_parents = _find_parent_nodes(self, new_clnode, new_entry, FIND_PARENT_NODES_CREATE);
1975 361         };
1976
1977         if (old_clnode != new_clnode) {
1978                 // If adding or removing, or the entry is now in a different part of the config, all parents
1979                 // will have changed.
1980                 inserted_list = new_parents; deleted_list = old_parents;
1981         } else {
1982                 for (GList *old_node=old_parents; old_node; old_node=old_node->next) {
1983                         GList *new_node = g_list_find(new_parents, old_node->data);
1984                         if (new_node != NULL) {
1985                                 // This node is in old_parents U new_parents
1986                                 changed_list = g_list_prepend (changed_list, old_node->data);
1987                                 new_parents = g_list_remove(new_parents, old_node->data);
1988                         } else
1989                                 // This node has gone - it's in old_parents - new_parents
1990                                 deleted_list = g_list_prepend (deleted_list, old_node->data);
1991                 };
1992                 inserted_list = new_parents;
1993                 g_list_free (old_parents);
1994         };
1995
1996 354         // FIXME: what's the optimal order from gtktreeview's point of view to emit these? Not that it
1997         // really matters.
1998
1999 374         change_trace ("\n\tInserted in %i, changed in %i, deleted in %i\n", g_list_length(inserted_list),
2000 361                       g_list_length(changed_list), g_list_length(deleted_list));
2001
2002 354         // Delete old rows!
2003 361         // FIXME: broken for m:1 joins ..
2004 354         for (GList *node=deleted_list; node; node=node->next) {
2005 361                 ViewNode *parent_node = node->data;
2006 442                 
2007 361                 if (parent_node->e_children==-1) {
2008 442                         // If the node isn't yet read, there's no need to do anything, unless it is a special
2009                         // entry: in this case, we read it to see if it's now empty and in need of hiding.
2010                         change_trace ("Remving: parent_tail %s.\n", CONFIG_TYPE_NAME(parent_node->parent_tail));
2011                         if (parent_node->parent_tail != NULL
2012                              && ENTRY_IS_SPECIAL(CONFIG_NODE(parent_node->parent_tail)->type, parent_node->id)) {
2013                                 // Remember that this is reading the *current* state, so we don't need to propagate
2014                                 // the entry removal inside this node.
2015                                 _music_source_view_initial_read (MUSIC_SOURCE_VIEW(self), parent_node);
2016                                 if (parent_node->n_children==0) 
2017                                         _remove_row_in_page (self, parent_node->parent_page->parent_node,
2018                                                              parent_node->parent_page, parent_node->n, TRUE, TRUE);
2019                         } else
2020                                 change_trace ("entry-notify: deleted list: Node has not yet been read.\n\n");
2021 361                         continue;
2022 365                 };
2023
2024                 propagate_alteration (self, REMOVE, old_clnode, old_entry, parent_node);
2025 354         };
2026
2027         // Insert new rows!
2028         for (GList *node=inserted_list; node; node=node->next) {
2029 361                 ViewNode *parent_node = node->data;
2030 389                 if (parent_node->e_children!=-1)
2031                         propagate_alteration (self, INSERT, new_clnode, new_entry, parent_node);
2032 420                 else {
2033                         // Node has not been read yet; row-inserted is not triggered in this case; this is fine
2034                         // because only the treeview and row references will connect to that signal anyway.
2035 389                         change_trace ("entry-notify[add %s %d]: Parent node %i[%i] (%x) still empty!!\n\n",
2036 420                                       ENTRY_PF(new_entry), parent_node->depth, parent_node->n, 
2037                                       (guint)parent_node);
2038
2039                         // We do however need to emit row-has-child-toggled if we hadn't already.
2040                         if (!parent_node->has_child_toggled_emitted) {
2041                                 // FIXME: make an easier (= more concise) way to do this. ..
2042                                 GtkTreeIter iter;
2043                                 iter.user_data = parent_node->parent_page->parent_node;
2044                                 iter.user_data2 = parent_node->parent_page;
2045                                 iter.user_data3 = GINT_TO_POINTER(parent_node->n);
2046                                 GtkTreePath *path = gtk_tree_model_get_path(GTK_TREE_MODEL(self), &iter);
2047                                 change_trace ("entry-notify: emitting has-child-toggled, at: %s.\n",
2048                                               gtk_tree_path_to_string(path));
2049                                 gtk_tree_model_row_has_child_toggled (GTK_TREE_MODEL(self), path, &iter);
2050                                 gtk_tree_path_free (path);
2051 422                                 parent_node->has_child_toggled_emitted = TRUE;
2052 420                         }
2053                 };
2054 380
2055                 // If this is a m:1 join entry, see if it's adopted any orphans (ie. see if this is the
2056                 // first track for a recording which previously had none - the recording now needs removing
2057                 // from the orphanage.
2058                 if (new_clnode->next
2059                       && CONFIG_NODE(new_clnode->next)->flags & VIEW_CONFIG_NODE_MANY_TO_ONE_JOIN)
2060 389                         entry_notify_process_adoption (self, new_clnode, new_entry);
2061 354         };
2062
2063 389         _music_source_view_end_initial_read_watch (music_source_view);
2064
2065 361         // Update rows that still exist! This is more complicated than it seems on the surface. Here are
2066         // the assumptions the code makes:
2067         //   * an entry can have more than one row inside each node  - eg, a recording on two albums
2068         //   * the number of rows within a node *can* change when the entry changes - if a file is set
2069         //     to point to a different recording, for example.
2070         //
2071         // Here is what could have happened:
2072         //
2073         // The method (for each parent node):
2074 369         if (changed_list != NULL) {
2075 354                 g_warn_if_fail (old_clnode == new_clnode);
2076 369
2077                 for (GList *node=changed_list; node; node=node->next) {
2078                         GSList *deltas = NULL;
2079                         ViewNode *parent_node = node->data;
2080                         if (parent_node->e_children==-1) {
2081                                 // Node has not been read yet; nothing to do (obviously it's not displayed yet).
2082                                 change_trace ("entry-notify: Parent node %i[%i] (%x) still empty!!\n\n",
2083                                                           parent_node->depth, parent_node->n, (guint)parent_node);
2084                                 continue;
2085                         }
2086
2087                         propagate_change (self, old_clnode, old_entry, new_entry, parent_node, &deltas);
2088 399
2089 396                         if (deltas != NULL) {
2090                                 entry_notify_action_deltas (self, parent_node, deltas);
2091                                 g_slist_free (deltas);
2092 378                         };
2093 369                 };
2094 364         };
2095 361
2096 369
2097 361         _music_source_view_check_tree (MUSIC_SOURCE_VIEW(self), NULL);
2098         g_list_free (deleted_list); g_list_free (inserted_list); g_list_free (changed_list);
2099 185 };
2100
2101 ///////////////////////////////////////
2102 // Debugging
2103 //
2104
2105 434 static void reading_trace (int level, const char *format, ...) {
2106         #if defined(GENERIC_VIEW_TRACE_READING) && GENERIC_VIEW_TRACE_READING > 0
2107         va_list va; va_start (va, format);
2108 436         TRACE(GENERIC_VIEW_TRACE_READING, level, format, va);
2109 434         va_end (va);
2110         #endif
2111 };
2112
2113 206 static void change_trace (char *format, ...) {
2114         #ifdef GENERIC_VIEW_TRACE_CHANGES
2115         va_list va; va_start (va, format);
2116         vprintf (format, va);
2117         fflush (stdout);
2118         va_end (va);
2119         #endif
2120 };

Loggerhead is a web-based interface for Bazaar branches