RSS

(root)/calliope : 452

Sam Thursfield
2009-09-25 00:26:24
Revision ID: sam@candylion-20090925002624-0egk6oywo1f9gxcy
Library passes all source tests.

collapse all collapse all

added added

removed removed

59
 
59
 
60
* Don't steal focus when you start! Esp. in the tests !!!
60
* Don't steal focus when you start! Esp. in the tests !!!
61
 
61
 
 
 
62
  => Problem #9020: I am confused about the removal queue and count_references. See library.c:2054.
 
 
63
 
62
* Replace 'process' system with GTask which is hopefully less crappy
64
* Replace 'process' system with GTask which is hopefully less crappy
63
  -> GTask is conceptually totally different from Process.
65
  -> GTask is conceptually totally different from Process.
64
     - A GTask is one work item, so to process 10k songs, we would need 10k GObjects * 3 or 4 task
66
     - A GTask is one work item, so to process 10k songs, we would need 10k GObjects * 3 or 4 task
73
 
75
 
74
----------------------------------------------------------
76
----------------------------------------------------------
75
 
77
 
76
* Make library work with caching
 
 
77
 
 
 
78
* Make library work without caching.
78
* Make library work without caching.
79
  => Problem #9017: query a track from library. Unref it. The recording and file entries hold
79
  => Problem #9017: query a track from library. Unref it. The recording and file entries hold
80
     circular refs and won't be freed, so now you have entries left over. How can you deal with
80
     circular refs and won't be freed, so now you have entries left over. How can you deal with
55
//#define ENTRY_TRACE
55
//#define ENTRY_TRACE
56
 
56
 
57
/* Log add/remove_entry etc. */
57
/* Log add/remove_entry etc. */
58
#define MUSIC_SOURCE_TRACE_EDITING
58
//#define MUSIC_SOURCE_TRACE_EDITING
59
 
59
 
60
/* Log the entry notifications emitted by music sources. */
60
/* Log the entry notifications emitted by music sources. */
61
//#define MUSIC_SOURCE_TRACE_NOTIFY
61
//#define MUSIC_SOURCE_TRACE_NOTIFY
160
        };
160
        };
161
};
161
};
162
 
162
 
163
static void check_entry (Entry *entry) {
163
static void process_empty_properties (Entry *entry) {
164
        // Default entries
164
        // Default entries
165
        //
165
        //
166
        switch(entry->type) {
166
        switch(entry->type) {
786
                        g_warning("Adding %s %i - already owned by another source", ENTRY_PF(entry));
786
                        g_warning("Adding %s %i - already owned by another source", ENTRY_PF(entry));
787
        };
787
        };
788
 
788
 
789
        check_entry (entry);
789
        process_empty_properties (entry);
 
 
790
        entry_check (entry);
790
 
791
 
791
        // Go through every child entry and add/update this too. (Unless it was the caller)
792
        // Go through every child entry and add/update this too. (Unless it was the caller)
792
        //
793
        //
958
static gboolean update_shadowing_property(MusicSource *self, Entry *shadower, int
959
static gboolean update_shadowing_property(MusicSource *self, Entry *shadower, int
959
                                          shadowing_property_id, void *old_shadowee_value,
960
                                          shadowing_property_id, void *old_shadowee_value,
960
                                          void *new_shadowee_value, Entry **p_duplicate) {
961
                                          void *new_shadowee_value, Entry **p_duplicate) {
961
        //printf ("update_shadowing_property: %s[%i].%s.\n", ENTRY_PF(shadower),
962
        //printf ("update_shadowing_property: %s[%i] %x.%s.\n", ENTRY_PF(shadower), shadower,
962
        //        schema[shadower->type][shadowing_property_id].name); fflush (stdout);
963
        //        schema[shadower->type][shadowing_property_id].name); fflush (stdout);
963
        g_return_val_if_fail (shadowing_property_id >= 0
964
        g_return_val_if_fail (shadowing_property_id >= 0
964
                               && shadowing_property_id < entry_n_properties[shadower->type], FALSE);
965
                               && shadowing_property_id < entry_n_properties[shadower->type], FALSE);
988
 *                   checked out. This is a horrible function in every way. */
989
 *                   checked out. This is a horrible function in every way. */
989
static void update_shadowers (MusicSource *self, Entry *old_entry, Entry *new_entry,
990
static void update_shadowers (MusicSource *self, Entry *old_entry, Entry *new_entry,
990
                              gboolean types_already_updated[]) {
991
                              gboolean types_already_updated[]) {
 
 
992
        editing_trace ("\tupdate_shadowers [%s %i]\n", ENTRY_PF(old_entry));
991
        for (GSList *node=entry_type_info[new_entry->type].shadowees; node; node=node->next) {
993
        for (GSList *node=entry_type_info[new_entry->type].shadowees; node; node=node->next) {
992
                EntryType foreign_type = APID_GET_TYPE(GPOINTER_TO_UINT(node->data));
994
                EntryType foreign_type = APID_GET_TYPE(GPOINTER_TO_UINT(node->data));
993
                if (types_already_updated[foreign_type]) continue;
995
                if (types_already_updated[foreign_type]) continue;
1010
                // FIXME: this is really slow, and we do it a lot
1012
                // FIXME: this is really slow, and we do it a lot
1011
 
1013
 
1012
                GSList *relations = music_source_query_relations(self, new_entry->type, new_entry->id,
1014
                GSList *relations = music_source_query_relations(self, new_entry->type, new_entry->id,
1013
                                                                                                                 MAKE_APID(foreign_type, p), "checkin");
1015
                                                                 MAKE_APID(foreign_type, p), "checkin");
1014
 
1016
 
1015
                // We assume apids of the same type in the shadowee list are contiguous, so we can do all
1017
                // We assume apids of the same type in the shadowee list are contiguous, so we can do all
1016
                // the properties for one type in a single pass (quite a significant optimisation :).
1018
                // the properties for one type in a single pass (quite a significant optimisation :).
1041
                                                additional_properties = i; continue;
1043
                                                additional_properties = i; continue;
1042
                                        };
1044
                                        };
1043
                                } else if (++i > additional_properties) break;
1045
                                } else if (++i > additional_properties) break;
 
 
1046
 
 
 
1047
                                // FIXME: this is kind of a hack. We update the source's entry before changes to the
 
 
1048
                                // shadowee entry are committed. So, we pass the new value of the shadowing property
 
 
1049
                                // manually. But we can only pass one at a time, so we have to update the entry
 
 
1050
                                // several times. This code is becoming a bit of a nightmare.
 
 
1051
                                if (MUSIC_SOURCE_GET_CLASS(self)->update_entry_internal != NULL)
 
 
1052
                                        MUSIC_SOURCE_GET_CLASS(self)->update_entry_internal (self, shadower, i, new_val);
1044
                        } while (cur_node);
1053
                        } while (cur_node);
1045
 
1054
 
1046
                        if (changed) {
1055
                        if (changed) {
1047
                                g_warn_if_fail (duplicate!=NULL);
1056
                                g_warn_if_fail (duplicate!=NULL);
 
 
1057
                                editing_trace ("\tupdated: %s %i.\n", ENTRY_PF(shadower));
1048
                                //printf ("Queuing %s %i, %s %ip\n", ENTRY_PF(duplicate), ENTRY_PF(shadower)); fflush (stdout);
1058
                                //printf ("Queuing %s %i, %s %ip\n", ENTRY_PF(duplicate), ENTRY_PF(shadower)); fflush (stdout);
1049
                                _music_source_queue_notify (self, duplicate, shadower);
1059
                                _music_source_queue_notify (self, duplicate, shadower);
1050
                        };
1060
                        };
1242
                _unref_checkout_copy_recursive (self, jetsam_entry->type, jetsam_entry->id, TRUE, -1, -1);
1252
                _unref_checkout_copy_recursive (self, jetsam_entry->type, jetsam_entry->id, TRUE, -1, -1);
1243
        };
1253
        };
1244
 
1254
 
1245
        check_entry (new_entry);
1255
        process_empty_properties (new_entry);
 
 
1256
        entry_check (new_entry);
1246
 
1257
 
1247
        // All the 'jetsam' (entries that we referenced by foreign keys when checked out, but have now
1258
        // All the 'jetsam' (entries that we referenced by foreign keys when checked out, but have now
1248
        // been replaced with something else) are still checked out - we've remove all their stored
1259
        // been replaced with something else) are still checked out - we've remove all their stored
1289
 
1300
 
1290
                        entry_check (new_entry);
1301
                        entry_check (new_entry);
1291
                        // Update the reference, in case the child entry was merged with an existing one.
1302
                        // Update the reference, in case the child entry was merged with an existing one.
1292
                        entry_dump (child);
1303
                        //entry_dump (child);
1293
                        entry_take_property (new_entry, i, child);
1304
                        entry_take_property (new_entry, i, child);
1294
                };
1305
                };
1295
        };
1306
        };
1313
        // Sources such as Library update their stored representation of the entry now. We do this if a
1324
        // Sources such as Library update their stored representation of the entry now. We do this if a
1314
        // merge happened too in case any mergeable properties were changed in the process.
1325
        // merge happened too in case any mergeable properties were changed in the process.
1315
        if (MUSIC_SOURCE_GET_CLASS(self)->update_entry_internal != NULL)
1326
        if (MUSIC_SOURCE_GET_CLASS(self)->update_entry_internal != NULL)
1316
                MUSIC_SOURCE_GET_CLASS(self)->update_entry_internal (self, new_entry);
1327
                MUSIC_SOURCE_GET_CLASS(self)->update_entry_internal (self, new_entry, -1, NULL);
1317
 
1328
 
1318
        g_warn_if_fail (new_entry->id > 0);
1329
        g_warn_if_fail (new_entry->id > 0);
1319
 
1330
 
1581
 *            inside a notification function.)
1592
 *            inside a notification function.)
1582
 */
1593
 */
1583
gboolean _music_source_emit_queued_notifications (MusicSource *self, Entry *caller) {
1594
gboolean _music_source_emit_queued_notifications (MusicSource *self, Entry *caller) {
1584
        if (SP->notifying_on_entry != NULL)
1595
        if (SP->notifying_on_entry != NULL) {
 
 
1596
                //printf ("emit_queued_notifications: already notifying.\n"); fflush (stdout);
1585
                return FALSE;
1597
                return FALSE;
 
 
1598
        }
1586
 
1599
 
1587
        //gboolean caller_emitted = FALSE;
1600
        //gboolean caller_emitted = FALSE;
1588
 
1601
 
103
        MusicSourceView *(*create_view)(MusicSource *source, ViewConfig *config);
103
        MusicSourceView *(*create_view)(MusicSource *source, ViewConfig *config);
104
 
104
 
105
        void (*add_entry_internal)    (MusicSource *self, Entry *entry);
105
        void (*add_entry_internal)    (MusicSource *self, Entry *entry);
106
        void (*update_entry_internal) (MusicSource *self, Entry *entry);
106
        void (*update_entry_internal) (MusicSource *self, Entry *entry, int shadow_override_id,
 
 
107
                                   void *shadow_override_value);
107
        void (*remove_entry_internal) (MusicSource *self, EntryType type, int id);
108
        void (*remove_entry_internal) (MusicSource *self, EntryType type, int id);
108
        void (*update_foreign_keys)   (MusicSource *self, Entry *from_entry, Entry *to_entry);
109
        void (*update_foreign_keys)   (MusicSource *self, Entry *from_entry, Entry *to_entry);
109
 
110
 
516
        ref_count -= _music_source_count_checkout_duplicate_references (MUSIC_SOURCE(self), entry);
516
        ref_count -= _music_source_count_checkout_duplicate_references (MUSIC_SOURCE(self), entry);
517
 
517
 
518
        editing_trace ("Found %i refs for %s %i \n", ref_count, ENTRY_PF(entry));
518
        editing_trace ("Found %i refs for %s %i \n", ref_count, ENTRY_PF(entry));
 
 
519
        //printf ("genericsource: Count_references: %s %i = %i\n", ENTRY_PF(entry), ref_count); fflush (stdout);
519
        return ref_count;
520
        return ref_count;
520
};
521
};
521
 
522
 
668
                entry_unref(result, "_music_source_add_entry_recursive");
669
                entry_unref(result, "_music_source_add_entry_recursive");
669
 
670
 
670
        _music_source_emit_queued_notifications (music_source, NULL);
671
        _music_source_emit_queued_notifications (music_source, NULL);
 
 
672
        // FIXME: in library here we have to emit the removal queue, or tests like Album Notify 1 fail.
 
 
673
        // I don't know why we don't need to in genericsource.
671
        return result_id;
674
        return result_id;
672
};
675
};
673
 
676
 
117
static MusicSourceView *create_view(MusicSource *music_source, ViewConfig *config);
117
static MusicSourceView *create_view(MusicSource *music_source, ViewConfig *config);
118
 
118
 
119
static void add_entry_internal    (MusicSource *music_source, Entry *entry);
119
static void add_entry_internal    (MusicSource *music_source, Entry *entry);
120
static void update_entry_internal (MusicSource *music_source, Entry *entry);
120
static void update_entry_internal (MusicSource *music_source, Entry *entry, int shadow_override_id,
 
 
121
                                   void *shadow_override_value);
121
static void remove_entry_internal (MusicSource *music_source, EntryType type, int id);
122
static void remove_entry_internal (MusicSource *music_source, EntryType type, int id);
122
static void update_foreign_keys   (MusicSource *music_source, Entry *from_entry, Entry *to_entry);
123
static void update_foreign_keys   (MusicSource *music_source, Entry *from_entry, Entry *to_entry);
123
 
124
 
393
static void db_action (Library *self, GError **perror, const char *text) {
394
static void db_action (Library *self, GError **perror, const char *text) {
394
        g_return_if_fail (self!=NULL);
395
        g_return_if_fail (self!=NULL);
395
        int result; char *error;
396
        int result; char *error;
 
 
397
        
 
 
398
        #ifdef LIBRARY_TRACE_QUERIES
 
 
399
        printf("=> a %s\n", text); fflush(stdout);
 
 
400
        #endif
 
 
401
 
396
        SQLITE_WRAPPER (sqlite3_exec (SP->db, text, NULL, NULL, &error));
402
        SQLITE_WRAPPER (sqlite3_exec (SP->db, text, NULL, NULL, &error));
397
};
403
};
398
 
404
 
402
        int result; char **data, *error;
408
        int result; char **data, *error;
403
 
409
 
404
        #ifdef LIBRARY_TRACE_QUERIES
410
        #ifdef LIBRARY_TRACE_QUERIES
405
                printf("Query: %s\n", text); fflush(stdout);
411
        printf("=> q %s.. ", text); fflush(stdout);
406
        #endif
412
        #endif
407
 
413
 
408
        SQLITE_WRAPPER (sqlite3_get_table(SP->db, text, &data, rows, columns, &error));
414
        SQLITE_WRAPPER (sqlite3_get_table(SP->db, text, &data, rows, columns, &error));
409
 
415
 
410
        #ifdef LIBRARY_TRACE_QUERIES
416
        #ifdef LIBRARY_TRACE_QUERIES
411
                printf ("Rows %i columns %i\n", *rows, *columns); fflush(stdout);
417
        printf ("rows %i columns %i\n", *rows, *columns); fflush(stdout);
412
        #endif
418
        #endif
413
 
419
 
414
        return data;
420
        return data;
418
static gint64 db_expression (Library *self, GError **perror, const char *text) {
424
static gint64 db_expression (Library *self, GError **perror, const char *text) {
419
        g_return_val_if_fail (self!=NULL, 0);
425
        g_return_val_if_fail (self!=NULL, 0);
420
        int result; char **data, *error; int rows, columns;
426
        int result; char **data, *error; int rows, columns;
 
 
427
 
 
 
428
        #ifdef LIBRARY_TRACE_QUERIES
 
 
429
        printf("=> e %s ", text); fflush(stdout);
 
 
430
        #endif
 
 
431
 
421
        SQLITE_WRAPPER (sqlite3_get_table(SP->db, text, &data, &rows, &columns, &error));
432
        SQLITE_WRAPPER (sqlite3_get_table(SP->db, text, &data, &rows, &columns, &error));
422
 
433
 
423
        gint64 value = 0;
434
        gint64 value = 0;
428
                        value = atoll(data[1]);
439
                        value = atoll(data[1]);
429
        };
440
        };
430
        sqlite3_free_table (data);
441
        sqlite3_free_table (data);
 
 
442
 
 
 
443
        #ifdef LIBRARY_TRACE_QUERIES
 
 
444
        printf ("%" G_GINT64_FORMAT "\n", value); fflush (stdout);
 
 
445
        #endif
431
        return value;
446
        return value;
432
};
447
};
433
 
448
 
860
// FIXME: I wonder if it would be quicker to just remove from cache on checkout and add again on
875
// FIXME: I wonder if it would be quicker to just remove from cache on checkout and add again on
861
// checking back in ...
876
// checking back in ...
862
static void cache_entry_update (Library *self, CacheEntry *cache_entry) {
877
static void cache_entry_update (Library *self, CacheEntry *cache_entry) {
863
        /*Entry *entry = cache_entry->entry; entry_ref (entry, "foo");
878
        cache_trace ("cache_entry_update: %s %i [%x].\n", ENTRY_PF(cache_entry->entry),
864
        cache_remove_cache_entry (self, cache_entry, NULL);
879
                     cache_entry->entry);
865
        cache_store_entry (self, entry); entry_unref (entry, "foo");
 
 
866
        return;*/
 
 
867
 
 
 
868
        //#ifdef YOU_LIKE_TO_MAKE_LIFE_HARD_FOR_YOURSELF
 
 
869
        cache_trace ("cache_entry_update: %s %i.\n", ENTRY_PF(cache_entry->entry));
 
 
870
        int find_by_property (CacheEntryProperty *property, int property_id) {
880
        int find_by_property (CacheEntryProperty *property, int property_id) {
871
                if (property->property_id == property_id)
881
                if (property->property_id == property_id)
872
                        return 0;
882
                        return 0;
911
                                if (foreign_entry==NULL) continue;
921
                                if (foreign_entry==NULL) continue;
912
                        };
922
                        };
913
 
923
 
914
                        cache_trace ("Property: %s [%x] - old %x, new %x.\n", schema[entry->type][p].name,
924
                        cache_trace ("Property: %s [%x] - old value %x, new %x.\n", schema[entry->type][p].name,
915
                                     local_cp, local_cp==NULL?0:local_cp->value->entry, foreign_entry);
925
                                     local_cp, local_cp==NULL?0:local_cp->value->entry, foreign_entry);
916
 
926
 
917
                        if (local_cp==NULL || (foreign_entry!=local_cp->value->entry)) {
927
                        if (local_cp==NULL || (foreign_entry!=local_cp->value->entry)) {
939
                        };
949
                        };
940
                };
950
                };
941
        };
951
        };
942
        //#endif
952
 
 
 
953
        // Don't do a cache check here. This function gets called once per entry as entries are checked
 
 
954
        // in, so the cache could be in an inconsistent state because it's in the middle of being
 
 
955
        // updated.
 
 
956
        //cache_check (self);
943
};
957
};
944
 
958
 
945
 
959
 
969
                else
983
                else
970
                        entry_ref (cache_entry->entry, "library::cache-lookup");
984
                        entry_ref (cache_entry->entry, "library::cache-lookup");
971
 
985
 
972
                cache_trace ("cache-lookup: returning %s %i from cache.\n", ENTRY_PF(cache_entry->entry));
986
                cache_trace ("cache-lookup: returning %s %i [%x] from cache.\n",
 
 
987
                             ENTRY_PF(cache_entry->entry), cache_entry->entry);
973
                return cache_entry->entry;
988
                return cache_entry->entry;
974
        }
989
        }
975
 
990
 
1080
        cache_trace ("cache-store: stored %X %s %i in cache refs %i\n", entry, ENTRY_PF(entry),
1095
        cache_trace ("cache-store: stored %X %s %i in cache refs %i\n", entry, ENTRY_PF(entry),
1081
                     entry->ref_count);
1096
                     entry->ref_count);
1082
 
1097
 
1083
        entry_dump (entry);
1098
        //entry_dump (entry);
1084
 
1099
 
1085
        return cache_entry;
1100
        return cache_entry;
1086
};
1101
};
1227
// FIXME: these functions could do with some tidying and organising
1242
// FIXME: these functions could do with some tidying and organising
1228
 
1243
 
1229
 
1244
 
1230
static char *make_property_string(Library *self, Entry *entry, int property) {
1245
static char *make_property_string_from_value(Library *self, PropertyType type, void *value) {
1231
        switch(schema[entry->type][property].type) {
1246
        switch (type) {
1232
                case PROPERTY_TYPE_INT:
1247
                case PROPERTY_TYPE_INT:
1233
                        return g_strdup_printf("%i", (int)entry->property[property]);
1248
                        return g_strdup_printf("%i", (int)value);
1234
 
1249
 
1235
                case PROPERTY_TYPE_FLOAT: {
1250
                case PROPERTY_TYPE_FLOAT: {
1236
                        //printf("Need to convert to float: %X", entry->property[property]);fflush(stdout);
1251
                        //printf("Need to convert to float: %X", entry->property[property]);fflush(stdout);
1237
                        float *value=(float *)(entry->property[property]);
1252
                        float *float_value=(float *)value;
 
 
1253
                        if (float_value==NULL)
 
 
1254
                                return g_strdup("0.0");
 
 
1255
                        return g_strdup_printf("%g", *float_value);
 
 
1256
                };
 
 
1257
 
 
 
1258
                case PROPERTY_TYPE_STRING: case PROPERTY_TYPE_NAME: {
1238
                        if (value==NULL)
1259
                        if (value==NULL)
1239
                                return g_strdup("0.0");
 
 
1240
                        return g_strdup_printf("%g", *value);
 
 
1241
                };
 
 
1242
 
 
 
1243
                case PROPERTY_TYPE_STRING: case PROPERTY_TYPE_NAME: {
 
 
1244
                        if (entry->property[property]==NULL)
 
 
1245
                                return NULL;
1260
                                return NULL;
1246
 
1261
 
1247
                        char *sqlite_string=sqlite3_mprintf("%Q", (const char *)entry->property[property]);
1262
                        char *sqlite_string=sqlite3_mprintf("%Q", (const char *)value);
1248
                        char *value=g_strdup(sqlite_string);
1263
                        char *value_string=g_strdup(sqlite_string);
1249
                        sqlite3_free(sqlite_string);
1264
                        sqlite3_free(sqlite_string);
1250
                        return value;
1265
                        return value_string;
1251
                };
1266
                };
1252
 
1267
 
1253
                case PROPERTY_TYPE_DATE: {
1268
                case PROPERTY_TYPE_DATE: {
1254
                        if (entry->property[property]==NULL || !g_date_valid(entry->property[property]))
1269
                        if (value==NULL || !g_date_valid(value))
1255
                                return NULL;
1270
                                return NULL;
1256
                        char *value=g_malloc(16);
1271
                        char *date_string=g_malloc(16);
1257
                        g_date_strftime(value, 15, "'%Y-%m-%d'", (GDate *)entry->property[property]);
1272
                        g_date_strftime(value, 15, "'%Y-%m-%d'", (GDate *)value);
1258
                        return value;
1273
                        return date_string;
1259
                };
1274
                };
1260
 
1275
 
1261
                case PROPERTY_TYPE_DATE_TIME: {
1276
                case PROPERTY_TYPE_DATE_TIME: {
1262
                        time_t *time_value = entry->property[property];
1277
                        time_t *time_value = value;
1263
                        if (time_value==NULL) return NULL;
1278
                        if (time_value==NULL) return NULL;
1264
                        struct tm brokentime; localtime_r(time_value, &brokentime);
1279
                        struct tm brokentime; localtime_r(time_value, &brokentime);
1265
                        char *value = g_malloc(32);
1280
                        char *date_time_string = g_malloc(32);
1266
                        strftime (value, 31, "'%Y-%m-%d %H:%M:%S'", &brokentime);
1281
                        strftime (date_time_string, 31, "'%Y-%m-%d %H:%M:%S'", &brokentime);
1267
                        return value;
1282
                        return date_time_string;
1268
                }
1283
                }
1269
 
1284
 
1270
                default: {
1285
                default: {
1271
                        Entry *child=entry->property[property];
1286
                        Entry *child = value;
1272
 
1287
 
1273
                        // This value can't be entered in the query at all if it is 0, because if the id
1288
                        // This value can't be entered in the query at all if it is 0, because if the id
1274
                        // has a value of 0 instead of NULL all our COALESCE calls no longer work
1289
                        // has a value of 0 instead of NULL all our COALESCE calls no longer work
1277
                        int entry_id;
1292
                        int entry_id;
1278
                        if (child->id!=0 && child->source==self)
1293
                        if (child->id!=0 && child->source==self)
1279
                                entry_id=child->id;
1294
                                entry_id=child->id;
1280
                        else entry_id = _music_source_find_entry(MUSIC_SOURCE(self), child, entry);
1295
                        else entry_id = _music_source_find_entry(MUSIC_SOURCE(self), child, child);
1281
 
1296
 
1282
                        return g_strdup_printf("%i", entry_id);
1297
                        return g_strdup_printf("%i", entry_id);
1283
                };
1298
                };
1284
        };
1299
        };
1285
};
1300
};
1286
 
1301
 
 
 
1302
static char *make_property_string (Library *self, Entry *entry, int property) {
 
 
1303
        return make_property_string_from_value (self, schema[entry->type][property].type,
 
 
1304
                                                entry->property[property]);
 
 
1305
};
1287
 
1306
 
1288
// At the moment this makes the whole query (property=value) whereas
1307
// At the moment this makes the whole query (property=value) whereas
1289
// make_property_string just returns a value string, this is because where
1308
// make_property_string just returns a value string, this is because where
1478
        sqlite3_bind_int (query, 1, entry_id);
1497
        sqlite3_bind_int (query, 1, entry_id);
1479
        int result = sqlite3_step (query);
1498
        int result = sqlite3_step (query);
1480
 
1499
 
 
 
1500
        #ifdef LIBRARY_TRACE_QUERIES
 
 
1501
                printf("=> * %s\n", sqlite3_sql(query)); fflush(stdout);
 
 
1502
        #endif
 
 
1503
 
1481
        if (result==SQLITE_DONE) {
1504
        if (result==SQLITE_DONE) {
1482
                sqlite3_reset (query);
1505
                sqlite3_reset (query);
1483
                method_track_leave ("done library::fill-entry - entry not found\n");
1506
                method_track_leave ("done library::fill-entry - entry not found\n");
1620
                               int columns) {
1643
                               int columns) {
1621
        GSList *list = NULL;
1644
        GSList *list = NULL;
1622
 
1645
 
1623
        for (int i=0;i<rows;i++) {
1646
        for (int i=0; i<rows; i++) {
1624
                int pos = (i+1)*columns;
1647
                Entry     *entry    = NULL;
1625
                Entry *entry = fill_entry (self, entry_type, atoi(data[pos]), NULL);
1648
                const int  pos      = (i+1) * columns;
 
 
1649
                const int  entry_id = atoi (data[pos]);
 
 
1650
                
 
 
1651
                entry = cache_lookup_entry(self, entry_type, entry_id);
 
 
1652
                if (entry == NULL)
 
 
1653
                        entry = fill_entry (self, entry_type, entry_id, NULL);
1626
                list = g_slist_prepend(list, entry);
1654
                list = g_slist_prepend(list, entry);
1627
        }
1655
        }
1628
 
1656
 
1895
        #endif
1923
        #endif
1896
};
1924
};
1897
 
1925
 
1898
/* update_entry_internal: re-store properties of 'entry' in the database. */
1926
/* update_entry_internal: re-store properties of 'entry' in the database.
1899
static void update_entry_internal (MusicSource *music_source, Entry *entry) {
1927
 *
 
 
1928
 *   shadow_override: used in the offbeat situation where a property in this entry shadows
 
 
1929
 *                    another entry which has changed, but the changes haven't yet been committed.
 
 
1930
 *                    This actually happens quite often in musicsource.c:update_shadowers().
 
 
1931
 */
 
 
1932
static void update_entry_internal (MusicSource *music_source, Entry *entry, int shadow_override_id,
 
 
1933
                                   void *shadow_override_value) {
1900
        Library *self = LIBRARY(music_source);
1934
        Library *self = LIBRARY(music_source);
1901
 
1935
 
 
 
1936
        //entry_dump (entry);
 
 
1937
        //printf ("shadow override: %i. val %x.\n", shadow_override_id, shadow_override_value); fflush (stdout);
 
 
1938
 
1902
        GString *assignments = g_string_new(NULL);
1939
        GString *assignments = g_string_new(NULL);
1903
        for (int i=0; i<entry_n_properties[entry->type]; i++) {
1940
        for (int i=0; i<entry_n_properties[entry->type]; i++) {
1904
                char *string = NULL;
1941
                char *string = NULL;
1905
 
1942
 
 
 
1943
                void *shadowee_value = NULL;
1906
                if (schema[entry->type][i].flags & PROPERTY_SHADOW) {
1944
                if (schema[entry->type][i].flags & PROPERTY_SHADOW) {
1907
                        // Any shadow properties which are the same as their shadowee are explicitly set to NULL
1945
                        // Any shadow properties which are the same as their shadowee are explicitly set to NULL
1908
                        // here. This means that when a shadow property that was independent starts shadowing
1946
                        // here. This means that when a shadow property that was independent starts shadowing
1909
                        // again, it is no longer stored and will mirror the shadowee's value again.
1947
                        // again, it is no longer stored and will mirror the shadowee's value again.
1910
                        const guint32 shadow_property_apid = schema[entry->type][i].shadow_property_apid;
1948
 
1911
                        void *shadowee_value = entry_get_distant_property (entry, shadow_property_apid);
1949
                        if (i==shadow_override_id)
 
 
1950
                                shadowee_value = shadow_override_value;
 
 
1951
                        else {
 
 
1952
                                const guint32 shadow_property_apid = schema[entry->type][i].shadow_property_apid;
 
 
1953
                                shadowee_value = entry_get_distant_property (entry, shadow_property_apid);
 
 
1954
                        }
 
 
1955
 
1912
                        if (entry_value_match(schema[entry->type][i].type, entry_get_property(entry, i),
1956
                        if (entry_value_match(schema[entry->type][i].type, entry_get_property(entry, i),
1913
                                              shadowee_value))
1957
                                              shadowee_value))
1914
                                string = g_strdup("NULL");
1958
                                string = g_strdup("NULL");
1915
                }
1959
                }
1916
 
1960
 
1917
                if (string == NULL)
1961
                if (string == NULL) {
1918
                        string = make_property_string(self, entry, i);
1962
                        /*if (shadowee_value != NULL)
 
 
1963
                                string = make_property_string_from_value (self, schema[entry->type][i].type,
 
 
1964
                                                                          shadowee_value);
 
 
1965
                        else*/
 
 
1966
                        string = make_property_string (self, entry, i);
 
 
1967
                }
1919
 
1968
 
1920
                if (string != NULL) {
1969
                if (string != NULL) {
1921
                        g_string_append (assignments, ", ");
1970
                        g_string_append (assignments, ", ");
2008
                // references those entries contain.
2057
                // references those entries contain.
2009
                //
2058
                //
2010
                GString *exceptions = g_string_new (NULL);
2059
                GString *exceptions = g_string_new (NULL);
2011
                for (GSList *q_node=SP->removal_queue; q_node; q_node=q_node->next) {
2060
                // FIXME: the removal queue is actually ignored, because the will_be_unreffed flag
 
 
2061
                // does the job well enough. Why does musicsourceimporting need to do this but library
 
 
2062
                // does not?
 
 
2063
                /* for (GSList *q_node=SP->removal_queue; q_node; q_node=q_node->next) {
2012
                        _RemovalQueueEntry *r = q_node->data;
2064
                        _RemovalQueueEntry *r = q_node->data;
2013
                        if (r->type==APID_GET_TYPE(apid))
2065
                        if (r->type==APID_GET_TYPE(apid))
2014
                                g_string_append_printf (exceptions, "AND id!=%i ", r->id);
2066
                                g_string_append_printf (exceptions, "AND id!=%i ", r->id);
2015
                };
2067
                }; */
2016
 
2068
 
2017
                ref_count += db_expression_printf(self, NULL, "SELECT COUNT(id) FROM _%s WHERE %s_id=%i %s "
2069
                ref_count += db_expression_printf(self, NULL, "SELECT COUNT(id) FROM _%s WHERE %s_id=%i %s "
2018
                                                  "LIMIT %i", entry_type_name[APID_GET_TYPE(apid)],
2070
                                                  "LIMIT %i", entry_type_name[APID_GET_TYPE(apid)],
2021
                node = node->next;
2073
                node = node->next;
2022
        };
2074
        };
2023
 
2075
 
 
 
2076
        //printf ("library: Count_references: %s %i = %i\n", entry_type_name[type], id, ref_count);
2024
        return ref_count;
2077
        return ref_count;
2025
};
2078
};
2026
 
2079
 
2031
        if (ENTRY_IS_SPECIAL(type, id))
2084
        if (ENTRY_IS_SPECIAL(type, id))
2032
                return;
2085
                return;
2033
 
2086
 
 
 
2087
        //printf ("touch: %s %i - d%i u%i.\n", entry_type_name[type], id,
 
 
2088
        //        delete, will_be_unreffed); fflush (stdout);
 
 
2089
 
2034
        if (delete) {
2090
        if (delete) {
2035
                // Search for entries that point to given entry (unless the link is flagged 'trivial') and
2091
                // Search for entries that point to given entry (unless the link is flagged 'trivial') and
2036
                // delete those. For example, if removing a recording this deletes all of its files.
2092
                // delete those. For example, if removing a recording this deletes all of its files.
2085
                                                 (self, NULL, "SELECT %s_id FROM _%s WHERE id=%i",
2141
                                                 (self, NULL, "SELECT %s_id FROM _%s WHERE id=%i",
2086
                                                  schema[type][p].name, entry_type_name[type], id);
2142
                                                  schema[type][p].name, entry_type_name[type], id);
2087
                        if (foreign_id!=0 && (foreign_type!=caller_type || foreign_id!=caller_id))
2143
                        if (foreign_id!=0 && (foreign_type!=caller_type || foreign_id!=caller_id))
2088
                                touch (self, foreign_type, foreign_id, FALSE, delete, p_jetsam, type, id);
2144
                                touch (self, foreign_type, foreign_id, FALSE, delete /* FALSE */, p_jetsam, type, id);
2089
                };
2145
                };
2090
        };
2146
        };
2091
 
2147
 
2117
 
2173
 
2118
static void execute_removal_queue (Library *self) {
2174
static void execute_removal_queue (Library *self) {
2119
        MusicSource *music_source = MUSIC_SOURCE(self);
2175
        MusicSource *music_source = MUSIC_SOURCE(self);
 
 
2176
        //printf ("Library::execute_removal_queue: %i entries.\n", g_slist_length(SP->removal_queue)); fflush (stdout);
 
 
2177
 
2120
        for (GSList *q_node=SP->removal_queue; q_node; q_node=q_node->next) {
2178
        for (GSList *q_node=SP->removal_queue; q_node; q_node=q_node->next) {
2121
                _RemovalQueueEntry *r = q_node->data;
2179
                _RemovalQueueEntry *r = q_node->data;
2122
                remove_entry_internal (music_source, r->type, r->id);
2180
                remove_entry_internal (music_source, r->type, r->id);
2149
 
2207
 
2150
        int result_id = result->id;
2208
        int result_id = result->id;
2151
 
2209
 
2152
        _music_source_emit_queued_notifications (music_source, NULL);
2210
        if (_music_source_emit_queued_notifications (music_source, NULL)) {
 
 
2211
                // Entries can be removed during an add, mostly by notify callbacks.
 
 
2212
                execute_removal_queue (LIBRARY (music_source));
 
 
2213
                //entry_destroy (entry);
 
 
2214
        }
2153
 
2215
 
2154
        //entry_destroy (result);
2216
        //entry_destroy (result);
2155
        entry_unref (entry, "library::add_entry");
2217
        entry_unref (entry, "library::add_entry");
2231
 
2293
 
2232
        Entry *entry = query_entry(music_source, root_type, root_id);
2294
        Entry *entry = query_entry(music_source, root_type, root_id);
2233
 
2295
 
 
 
2296
        //printf ("library:checkout_entry: 1: removal queue length %i.\n", g_slist_length(LIBRARY(music_source)->priv->removal_queue)); fflush (stdout);
 
 
2297
 
2234
        if (entry != NULL)
2298
        if (entry != NULL)
2235
                _music_source_checkout_entry_recursive (music_source, entry);
2299
                _music_source_checkout_entry_recursive (music_source, entry);
2236
 
2300
 
 
 
2301
        //printf ("library:checkout_entry: 2: removal queue length %i.\n", g_slist_length(LIBRARY(music_source)->priv->removal_queue)); fflush (stdout);
 
 
2302
 
2237
        // No need to worry about the cache since it points directly to the entry that will be being
2303
        // No need to worry about the cache since it points directly to the entry that will be being
2238
        // modified, and it can't be queried while checked out anyway.
2304
        // modified, and it can't be queried while checked out anyway.
2239
        return entry;
2305
        return entry;
2243
        Library *self = LIBRARY(music_source);
2309
        Library *self = LIBRARY(music_source);
2244
        GSList *jetsam = NULL;
2310
        GSList *jetsam = NULL;
2245
 
2311
 
 
 
2312
        //printf ("library:checkin_entry: 1: removal queue length %i.\n", g_slist_length(SP->removal_queue)); fflush (stdout);
 
 
2313
 
2246
        Entry *result = _music_source_checkin_entry_recursive (music_source, entry, &jetsam, entry);
2314
        Entry *result = _music_source_checkin_entry_recursive (music_source, entry, &jetsam, entry);
2247
 
2315
 
 
 
2316
        //printf ("library:checkin_entry: 2: removal queue length %i.\n", g_slist_length(SP->removal_queue)); fflush (stdout);
 
 
2317
 
2248
        // Original entry is unreffed in music_source_checkin_entry
2318
        // Original entry is unreffed in music_source_checkin_entry
2249
        if (entry != result)
2319
        if (entry != result)
2250
                entry_unref (result, "_music_source_checkin_entry_recursive");
2320
                entry_unref (result, "_music_source_checkin_entry_recursive");
2255
        };
2325
        };
2256
        g_slist_free (jetsam);
2326
        g_slist_free (jetsam);
2257
 
2327
 
 
 
2328
        //printf ("end of checkin %s %i.\n", ENTRY_PF(entry)); fflush (stdout);
2258
        if (_music_source_emit_queued_notifications (music_source, NULL)) {
2329
        if (_music_source_emit_queued_notifications (music_source, NULL)) {
2259
                execute_removal_queue (self);
2330
                execute_removal_queue (self);
2260
                entry_destroy (entry);
2331
                //entry_destroy (entry);
2261
        }
2332
        }
2262
};
2333
};
2263
 
2334
 
152
        g_assert (id == 1);
152
        g_assert (id == 1);
153
        g_assert (test_state == TEST_CHANGING);   // Callback should have advanced state
153
        g_assert (test_state == TEST_CHANGING);   // Callback should have advanced state
154
 
154
 
155
        printf ("\n\n\n\n\n\n\nPart 3.\n\n"); fflush (stdout);
 
 
156
        artist = music_source_checkout_entry(source, ENTRY_TYPE_ARTIST, 3, "test");
155
        artist = music_source_checkout_entry(source, ENTRY_TYPE_ARTIST, 3, "test");
157
        entry_set_property (artist, ARTIST_NAME, "Robot Faces");
156
        entry_set_property (artist, ARTIST_NAME, "Robot Faces");
158
        music_source_checkin_entry (source, artist, "test");
157
        music_source_checkin_entry (source, artist, "test");
250
        music_source_end_transaction (source);
249
        music_source_end_transaction (source);
251
 
250
 
252
        music_source_connect_entry_notify (source, -1, TRUE, notify, NULL);
251
        music_source_connect_entry_notify (source, -1, TRUE, notify, NULL);
253
        printf("\n\n\n\n\n\n\n\n\nIt starts!\n\n"); fflush (stdout);
 
 
254
        music_source_remove_entry (source, ENTRY_TYPE_FILE, 1);
252
        music_source_remove_entry (source, ENTRY_TYPE_FILE, 1);
255
        g_assert_cmpint (notify_received, ==, 1);
253
        g_assert_cmpint (notify_received, ==, 1);
256
 
254
 
394
        test_add_song (source, 1, 1, 2, 1, 1);
392
        test_add_song (source, 1, 1, 2, 1, 1);
395
        music_source_end_transaction (source);
393
        music_source_end_transaction (source);
396
 
394
 
397
        // Check out the same entry twice in two seperate checkouts!
395
        // Check out the same entry twice in two seperate checkouts! (The two files have the same
 
 
396
        // recording and composition).
398
        Entry *file_1 = music_source_checkout_entry (source, ENTRY_TYPE_FILE, 1, "test");
397
        Entry *file_1 = music_source_checkout_entry (source, ENTRY_TYPE_FILE, 1, "test");
399
        g_assert (file_1!=NULL);
398
        g_assert (file_1!=NULL);
400
 
399
 
401
        Entry *file_2 = music_source_checkout_entry (source, ENTRY_TYPE_FILE, 2, "test");
400
        Entry *file_2 = music_source_checkout_entry (source, ENTRY_TYPE_FILE, 2, "test");
402
        g_assert (file_2!=NULL);
401
        g_assert (file_2!=NULL);
403
 
402
 
404
        // Set a new recording for one of them.
403
        // Set a new recording for one of the files.
405
        Entry *old_recording   = entry_get_property (file_1, FILE_RECORDING),
404
        Entry *old_recording   = entry_get_property (file_1, FILE_RECORDING),
406
              *old_composition = entry_get_property (old_recording, RECORDING_COMPOSITION),
405
              *old_composition = entry_get_property (old_recording, RECORDING_COMPOSITION),
407
              *new_recording   = entry_new (ENTRY_TYPE_RECORDING, 0, NULL, "test");
406
              *new_recording   = entry_new (ENTRY_TYPE_RECORDING, 0, NULL, "test");
409
 
408
 
410
        entry_take_property (file_1, FILE_RECORDING, new_recording);
409
        entry_take_property (file_1, FILE_RECORDING, new_recording);
411
 
410
 
412
        // Here a poor implementation might confuse itself terribly.
411
        // Here a poor implementation might confuse itself terribly. file 2 is still checked out and its
 
 
412
        // recording is still recording 1.
413
        music_source_checkin_entry (source, file_1, "test");
413
        music_source_checkin_entry (source, file_1, "test");
414
 
414
 
 
 
415
        // Check file 1 hasn't been mangled already.
 
 
416
        file_1 = music_source_query_entry (source, ENTRY_TYPE_FILE, 1, "test");
 
 
417
        entry_check (file_1);
 
 
418
        entry_unref (file_1, "test");
 
 
419
 
415
        Entry *composition_2 = entry_get_distant_property(file_2, _RECORDING_COMPOSITION);
420
        Entry *composition_2 = entry_get_distant_property(file_2, _RECORDING_COMPOSITION);
416
 
421
 
417
        // Let's do some shadowing to make things even harder.
422
        // Let's do some shadowing to make things even harder.
418
        entry_set_property (composition_2, COMPOSITION_NAME, "Test Test");
423
        entry_set_property (composition_2, COMPOSITION_NAME, "Test Test");
 
 
424
 
419
        music_source_checkin_entry (source, file_2, "test");
425
        music_source_checkin_entry (source, file_2, "test");
420
 
426
 
421
        g_object_unref (source);
427
        g_object_unref (source);
463
 
469
 
464
        Entry *artist_1, *artist_2;
470
        Entry *artist_1, *artist_2;
465
 
471
 
466
        void assert_state(gboolean artist_two_sortname_changed,
472
        void assert_state(gboolean artist_two_sortname_changed, gboolean artist_names_changed,
467
                                 gboolean artist_names_changed,
473
                          gboolean recording_2_artist_changed) {
468
                                                         gboolean recording_2_artist_changed) {
 
 
469
                // Test that shadow properties are filled properly.
474
                // Test that shadow properties are filled properly.
470
                artist_1 = music_source_query_entry(source, ENTRY_TYPE_ARTIST, 3, "test");
475
                artist_1 = music_source_query_entry(source, ENTRY_TYPE_ARTIST, 3, "test");
471
                g_assert_cmpstr (entry_get_property(artist_1, ARTIST_NAME), ==,
476
                g_assert_cmpstr (entry_get_property(artist_1, ARTIST_NAME), ==,
523
        entry_set_property(artist_2, ARTIST_SORTNAME, "Two, Test Artist");
528
        entry_set_property(artist_2, ARTIST_SORTNAME, "Two, Test Artist");
524
        music_source_checkin_entry(source, artist_2, "test");
529
        music_source_checkin_entry(source, artist_2, "test");
525
 
530
 
 
 
531
        //printf ("\nArtist 2 sortname changed, no longer shadowing."); fflush (stdout);
526
        assert_state(TRUE, FALSE, FALSE);
532
        assert_state(TRUE, FALSE, FALSE);
527
        music_source_flush (source);
533
        music_source_flush (source);
 
 
534
        //printf ("\nFlushed cache (Artist 2 sortname no longer shadowing)"); fflush (stdout);
528
        assert_state(TRUE, FALSE, FALSE);
535
        assert_state(TRUE, FALSE, FALSE);
529
 
536
 
530
        // Change the names of both artists. Artist 1's sortname should follow, since it's still
537
        // Change the names of both artists. Artist 1's sortname should follow, since it's still
537
        entry_set_property(artist_2, ARTIST_NAME, "Test Artist Two");
544
        entry_set_property(artist_2, ARTIST_NAME, "Test Artist Two");
538
        music_source_checkin_entry(source, artist_2, "test");
545
        music_source_checkin_entry(source, artist_2, "test");
539
 
546
 
 
 
547
        //printf ("\nBoth artist names changed."); fflush (stdout);
540
        assert_state(TRUE, TRUE, FALSE);
548
        assert_state(TRUE, TRUE, FALSE);
541
        /*music_source_flush (source);
549
        music_source_flush (source);
 
 
550
        //printf ("\nFlushed cache (Both artist names changed)"); fflush (stdout);
542
        assert_state(TRUE, TRUE, FALSE);
551
        assert_state(TRUE, TRUE, FALSE);
543
 
552
 
544
        // Make song two be recorded by a different artist to its composer.
553
        // Make song two be recorded by a different artist to its composer.
550
 
559
 
551
        assert_state(TRUE, TRUE, TRUE);
560
        assert_state(TRUE, TRUE, TRUE);
552
        music_source_flush (source);
561
        music_source_flush (source);
553
        assert_state(TRUE, TRUE, TRUE);*/
562
        assert_state(TRUE, TRUE, TRUE);
554
 
563
 
555
        g_object_unref (source);
564
        g_object_unref (source);
556
        _entry_cleanup();
565
        _entry_cleanup();
606
 
615
 
607
        // Remove file 1 and see that file 2 is now the default.
616
        // Remove file 1 and see that file 2 is now the default.
608
        music_source_remove_entry (source, ENTRY_TYPE_FILE, 1);
617
        music_source_remove_entry (source, ENTRY_TYPE_FILE, 1);
609
        recording = music_source_query_entry(source, ENTRY_TYPE_RECORDING, 1, "test");
618
        recording = music_source_query_entry (source, ENTRY_TYPE_RECORDING, 1, "test");
 
 
619
        g_assert (recording != NULL);
 
 
620
 
610
        default_file = entry_get_property(recording, RECORDING_DEFAULT_FILE);
621
        default_file = entry_get_property(recording, RECORDING_DEFAULT_FILE);
611
        g_assert (default_file != NULL);
622
        g_assert (default_file != NULL);
612
        g_assert_cmpint (default_file->id, ==, 2);
623
        g_assert_cmpint (default_file->id, ==, 2);
632
        g_assert_cmpint (album_artist->id, ==, ARTIST_ID_VARIOUS);
643
        g_assert_cmpint (album_artist->id, ==, ARTIST_ID_VARIOUS);
633
        entry_unref (album, "test");
644
        entry_unref (album, "test");
634
 
645
 
 
 
646
        // And, there should be only one album entry.
 
 
647
        g_assert_cmpint (music_source_get_n_entries (source, ENTRY_TYPE_ALBUM, NULL, NULL), ==, 1);
 
 
648
 
 
 
649
 
 
 
650
        // Give track 2 the same artist name as track 1. Now artists 3 & 4 should merge, and
 
 
651
        // the album name should change from 'Various Artists' back to 'Test Artist 000002'.
635
        Entry *track     = music_source_checkout_entry (source, ENTRY_TYPE_TRACK, 2, "test"),
652
        Entry *track     = music_source_checkout_entry (source, ENTRY_TYPE_TRACK, 2, "test"),
636
              *recording = entry_get_property (track,     TRACK_RECORDING),
653
              *recording = entry_get_property (track,     TRACK_RECORDING),
637
              *artist    = entry_get_property (recording, RECORDING_ARTIST);
654
              *artist    = entry_get_property (recording, RECORDING_ARTIST);
639
        entry_set_property (artist, ARTIST_NAME, "Test Artist 000002");
656
        entry_set_property (artist, ARTIST_NAME, "Test Artist 000002");
640
        music_source_checkin_entry (source, track, "test");
657
        music_source_checkin_entry (source, track, "test");
641
 
658
 
642
        // Check that the merging went okay. Track 2's artist should now be #3 - #4 should have been
659
        // Check that the artists merged, now that their names are the same.
643
        // merged with #3 on checkin.
 
 
644
        recording = music_source_query_entry (source, ENTRY_TYPE_RECORDING, 2, "test");
660
        recording = music_source_query_entry (source, ENTRY_TYPE_RECORDING, 2, "test");
645
        artist    = entry_get_property (recording, RECORDING_ARTIST);
661
        artist    = entry_get_property (recording, RECORDING_ARTIST);
646
        g_assert_cmpint (artist->id, ==, 3);
662
        g_assert_cmpint (artist->id, ==, 3);
647
        entry_unref (recording, "test");
663
        entry_unref (recording, "test");
648
 
664
 
649
        // Now they are both the same artist. The album artist should change from 'Various Artists'
665
        // Check that the album has changed from VA back to being a single artist.
650
        // back to 'Test Artist 000002'.
 
 
651
        album        = music_source_query_entry (source, ENTRY_TYPE_ALBUM, 1, "test");
666
        album        = music_source_query_entry (source, ENTRY_TYPE_ALBUM, 1, "test");
652
        album_artist = entry_get_property       (album, ALBUM_ARTIST);
667
        album_artist = entry_get_property       (album, ALBUM_ARTIST);
653
        g_assert_cmpint (album_artist->id, ==, 3);
668
        g_assert_cmpint (album_artist->id, ==, 3);
669
        g_test_add_func(PATH_PRINTF("/%s/Notify 1", root), test_notify_1);
684
        g_test_add_func(PATH_PRINTF("/%s/Notify 1", root), test_notify_1);
670
        g_test_add_func(PATH_PRINTF("/%s/Notify 2", root), test_notify_2);
685
        g_test_add_func(PATH_PRINTF("/%s/Notify 2", root), test_notify_2);
671
        g_test_add_func(PATH_PRINTF("/%s/Notify 3", root), test_notify_3);
686
        g_test_add_func(PATH_PRINTF("/%s/Notify 3", root), test_notify_3);
672
        //g_test_add_func(PATH_PRINTF("/%s/Notify 4", root), test_notify_4);
687
        g_test_add_func(PATH_PRINTF("/%s/Notify 4", root), test_notify_4);
673
        g_test_add_func(PATH_PRINTF("/%s/Removal", root), test_removal);
688
        g_test_add_func(PATH_PRINTF("/%s/Removal", root), test_removal);
674
        g_test_add_func(PATH_PRINTF("/%s/Removal Notify", root), test_removal_notify);
689
        g_test_add_func(PATH_PRINTF("/%s/Removal Notify", root), test_removal_notify);
675
        g_test_add_func(PATH_PRINTF("/%s/Pruning 1", root), test_pruning_1);
690
        g_test_add_func(PATH_PRINTF("/%s/Pruning 1", root), test_pruning_1);
676
        //g_test_add_func(PATH_PRINTF("/%s/Pruning 2", root), test_pruning_2);
691
        g_test_add_func(PATH_PRINTF("/%s/Pruning 2", root), test_pruning_2);
677
        g_test_add_func(PATH_PRINTF("/%s/Pruning 3", root), test_pruning_3);
692
        g_test_add_func(PATH_PRINTF("/%s/Pruning 3", root), test_pruning_3);
678
        g_test_add_func(PATH_PRINTF("/%s/Shadowing", root), test_shadowing);
693
        g_test_add_func(PATH_PRINTF("/%s/Shadowing", root), test_shadowing);
679
        g_test_add_func(PATH_PRINTF("/%s/Relations", root), test_relations);
694
        g_test_add_func(PATH_PRINTF("/%s/Relations", root), test_relations);
680
        //g_test_add_func(PATH_PRINTF("/%s/Recording Watch", root), test_recording_watch);
695
        g_test_add_func(PATH_PRINTF("/%s/Recording Watch", root), test_recording_watch);
681
        //g_test_add_func(PATH_PRINTF("/%s/Album Watch 1", root), test_album_watch_1);
696
        g_test_add_func(PATH_PRINTF("/%s/Album Watch 1", root), test_album_watch_1);
682
};
697
};

Loggerhead is a web-based interface for Bazaar branches