RSS

(root)/calliope : /src/core/library.c (revision 451)

Line Revision Contents
1 17 /*  Calliope Music Player
2 185  *  Copyright 2005-09 Sam Thursfield <ssssam gmail.com>
3 17  *
4  *  This program is free software: you can redistribute it and/or modify
5  *  it under the terms of the GNU General Public License as published by
6  *  the Free Software Foundation, either version 3 of the License, or
7  *  (at your option) any later version.
8  *
9  *  This program is distributed in the hope that it will be useful,
10  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
11  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12  *  GNU General Public License for more details.
13  *
14  *  You should have received a copy of the GNU General Public License
15 269  *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
16 17  */
17 269
18 449 /* library: a music_source which stores metadata in an SQL database.
19  *
20  *   * Entries are cached because querying one can mean querying a whole bunch of entries and it
21  *     ends up working very slowly. One possible is to filter which properties we return, but at
22  *     the moment its easiest to just implement a cache for entries so that methods like
23  *     music_source::track-added, which is called every time a track is added and immediately
24  *     queries everything that was just added, don't run slow to the point of death.
25  */
26 17
27 350 /* Strange way of getting strptime */
28 #define _GNU_SOURCE
29 #include <time.h>
30
31 17 #include <string.h>
32 #include <stdarg.h>
33 #include <stdlib.h>
34 #include <glib.h>
35 #include <glib/gstdio.h>
36 #include <gtk/gtk.h>
37 #include <sqlite3.h>
38 #include "format.h"
39 #include "main.h"
40 #include "schema.h"
41 #include "conftool.h"
42 #include "library.h"
43 451 #include "musicsource-private.h"
44 #include "library-private.h"
45 24 #include "libraryview.h"
46 17
47 356 #ifndef LIBRARY_DISABLE
48
49 201 #define DB_MAJOR_VERSION 1
50
51 451 typedef struct {
52         EntryType type; int id;
53 } _RemovalQueueEntry;
54
55 17 struct LibraryPrivate {
56 269         sqlite3 *db;
57 17         char *file_name;
58
59 269         // This is here because if the constructor fails we can't return null or anything nice,
60 95         // we have to go right through with init.
61 17         gboolean failed;
62 269
63 95         // Precalculated fill-entry queries for each entry type.
64 164         sqlite3_stmt **fill_entry_query;
65 17
66 451         GSList *removal_queue;
67
68 17         // FIXME: in fact the cache could be a lot more advanced. For example, there's no real need to cache all of the file entries, or probably
69         // the track entries really. It should be a fixed size to save running out of memory on huge libraries. Etc.
70         GHashTable *cache[ENTRY_TYPE_COUNT];
71 };
72
73 449 enum {  PROP_0, PROP_FILE_NAME  };
74 17
75 static GObject *constructor(GType type, guint n_construct_properties, GObjectConstructParam *construct_params);
76 static void finalize(GObject *self);
77 static void set_property(GObject *self, guint prop_id, const GValue *value, GParamSpec *pspec);
78
79 static void db_init(Library *self);
80 static void db_free(Library *self);
81 static void db_action(Library *db, GError **perror, const char *text);
82 50 void db_action_printf(Library *db, GError **perror, const char *format, ...);
83 17 static char **db_query(Library *db, int *rows, int *columns, GError **perror, const char *text);
84 static gint64 db_expression(Library *db, GError **perror, const char *text);
85 144 gint64 db_expression_printf(Library *db, GError **perror, const char *format, ...);
86 17 static void db_update_references(Library *self, int type, int from_id, int to_id);
87 static void db_move_row(Library *self, int table_id, int from_id, int to_id);
88 static void db_create(Library *db);
89 static void db_update(Library *db, int ver);
90 static void db_maintain_ids(Library *db);
91
92 274 static void cache_init (Library *self);
93 static void cache_free (Library *self);
94 283 static void *cache_store_entry (Library *self, Entry *entry);
95 274 static void cache_flush (Library *self);
96 451 static void cache_check (Library *self);
97 17
98 static char *make_property_match_string_except(Library *self, Entry *entry, int property, int ignored_apid);
99 static GSList *find_entries_matching_except(Library *self, Entry *entry, int ignored_apid, Entry *caller);
100 451 static int find_entry (MusicSource *music_source, Entry *entry, Entry *caller);
101 304 static inline Entry *fill_entry (Library *self, int entry_type, int entry_id, Entry *caller);
102 17
103 static gboolean is_empty(MusicSource *source);
104 static gboolean get_loaded(MusicSource *source);
105 static char *get_summary(MusicSource *self);
106 449 static int get_n_entries(MusicSource *music_source, EntryType type, int *highest_id, int *n_dead);
107 17 static gboolean is_valid_id(MusicSource *source, EntryType type, int id);
108 static Entry *query_entry(MusicSource *source, EntryType type, int id);
109 269 static int query_n_relations (MusicSource *music_source, int local_id, EntryType foreign_type,
110 102                               int foreign_property_id, int limit);
111 449 static GSList *query_entry_children(MusicSource *music_source, int parent_id, EntryType child_entry_type, int child_property);
112 static GSList *query_entry_children_ids(MusicSource *music_source, int parent_id, EntryType child_entry_type, int child_property);
113 static GSList *query_relations(MusicSource *music_source, EntryType local_type, int local_id, int relation_apid);
114 17 static GSList *query_ids(MusicSource *source, EntryType entry_type);
115 449 static GSList *query_matching_except(MusicSource *music_source, Entry *entry, int ignored_apid);
116 92
117 449 static MusicSourceView *create_view(MusicSource *music_source, ViewConfig *config);
118 92
119 451 static void add_entry_internal    (MusicSource *music_source, Entry *entry);
120 static void update_entry_internal (MusicSource *music_source, Entry *entry);
121 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
124 17 static void begin_transaction(MusicSource *self);
125 static void end_transaction(MusicSource *self);
126 451 //static void update_entry_property (MusicSource *self, EntryType entry_type, int entry_id,
127 //                                   int property_id, const void *value);
128 449 static int add_entry(MusicSource *music_source, Entry *entry);
129 static void remove_entry(MusicSource *music_source, EntryType type, int id);
130 static Entry *checkout_entry(MusicSource *music_source, EntryType type, int id);
131 static void checkin_entry(MusicSource *music_source, Entry *entry);
132 static void flush(MusicSource *music_source);
133 269
134 449 static void dump (MusicSource *music_source, gboolean detailed);
135 17
136 269 //Entry *add_recursive(Library *self, Entry *entry, Entry *caller);
137 //Entry *checkin_recursive(Library *self, Entry *entry, Entry *caller, gboolean touch_only,
138 //                        /);
139 17
140 static void method_track_enter(const char *text, ...);
141 static void method_track_leave(const char *text, ...);
142 static void method_track_printf(const char *text, ...);
143 274 static void cache_trace (const char *text, ...);
144 17
145 G_DEFINE_TYPE(Library, library, MUSIC_SOURCE_TYPE);
146
147 static void library_class_init(LibraryClass *cl) {
148         library_parent_class=g_type_class_peek_parent(G_OBJECT_CLASS(cl));
149         G_OBJECT_CLASS(cl)->constructor=constructor;
150         G_OBJECT_CLASS(cl)->finalize=finalize;
151         G_OBJECT_CLASS(cl)->set_property=set_property;
152 269
153         MusicSourceClass *music_source_class = MUSIC_SOURCE_CLASS(cl);
154 451         music_source_class->find_entry            = find_entry;
155 269         music_source_class->is_empty=is_empty;
156         music_source_class->get_loaded=get_loaded;
157         music_source_class->get_summary=get_summary;
158         music_source_class->get_n_entries=get_n_entries;
159         music_source_class->is_valid_id=is_valid_id;
160         music_source_class->query_entry=query_entry;
161         music_source_class->query_n_relations = query_n_relations;
162         music_source_class->query_entry_children=query_entry_children;
163         music_source_class->query_entry_children_ids=query_entry_children_ids;
164         music_source_class->query_relations = query_relations;
165         music_source_class->query_ids=query_ids;
166         music_source_class->query_matching_except=query_matching_except;
167         music_source_class->create_view=create_view;
168 451         music_source_class->add_entry_internal    = add_entry_internal;
169         music_source_class->update_entry_internal = update_entry_internal;
170         music_source_class->remove_entry_internal = remove_entry_internal;
171         music_source_class->update_foreign_keys   = update_foreign_keys;
172 269         music_source_class->begin_transaction=begin_transaction;
173         music_source_class->end_transaction=end_transaction;
174 451         //music_source_class->update_entry_property=update_entry_property;
175         music_source_class->add_entry             = add_entry;
176 269         music_source_class->remove_entry=remove_entry;
177         music_source_class->checkout_entry=checkout_entry;
178         music_source_class->checkin_entry=checkin_entry;
179         music_source_class->flush = flush;
180         music_source_class->dump = dump;
181 17
182         g_object_class_install_property(G_OBJECT_CLASS(cl), PROP_FILE_NAME,
183       g_param_spec_string("filename", "File name", "Database filename",
184       NULL, G_PARAM_CONSTRUCT_ONLY|G_PARAM_WRITABLE|STATICSTRS));
185
186         g_type_class_add_private(cl, sizeof(LibraryPrivate));
187 }
188
189 449 static void library_init (Library *self) {
190         SP = G_TYPE_INSTANCE_GET_PRIVATE(self, LIBRARY_TYPE, LibraryPrivate);
191 17
192 449         SP->file_name=NULL;
193 17         SP->failed=FALSE;
194
195 451         SP->removal_queue = NULL;
196
197 269         cache_init(self);
198 17 }
199
200 449 static GObject *constructor (GType type, guint n_construct_properties,
201                              GObjectConstructParam *construct_params) {
202 109         GObject *object = ((GObjectClass *)library_parent_class)->constructor(type, n_construct_properties, construct_params);
203         Library *self = LIBRARY(object);
204
205         // FIXME: would be nice if they didn't all have the same name.
206         _music_source_set_name (MUSIC_SOURCE(self), "Music Library", FALSE);
207 269
208 17         // Must be called after filename property is set
209         db_init(self);
210 269         if (SP->failed)
211                 return object;
212
213 95         // Precalculate fill-entry queries.
214         //
215 164         SP->fill_entry_query = g_new(sqlite3_stmt *, ENTRY_TYPE_COUNT);
216 95         for (int i=0; i<ENTRY_TYPE_COUNT; i++) {
217 164                 char *sql;
218 269
219 451                 if (entry_type_info[i].has_shadowing_properties) {
220 95                         GString *projection = g_string_new(NULL);
221                         GString *joins = g_string_new(NULL);
222                         gboolean join_flags[ENTRY_TYPE_COUNT] = {0};
223 168                         g_string_append_printf (projection, "_%s.id", entry_type_name[i]);
224 97                         for (int p=0; p<entry_n_properties[i]; p++)
225 104                                 _db_append_property_query(i, p, projection, TRUE, joins, join_flags);
226 95
227 168                         sql = g_strdup_printf("SELECT %s FROM _%s%s WHERE _%s.id=$id",
228 269                                           projection->str, entry_type_name[i],
229 449                                           joins->str, entry_type_name[i]);
230 95                         g_string_free (projection, TRUE);
231 451                 } else
232                         sql = g_strdup_printf("SELECT * FROM _%s WHERE id=$id", entry_type_name[i]);
233 269
234                 sqlite3_prepare_v2 (SP->db, sql, -1, &SP->fill_entry_query[i], NULL);
235 164                 g_free (sql);
236 95         };
237 269
238 17         return object;
239 };
240
241 449 static void finalize (GObject *object) {
242         Library *self = LIBRARY(object);
243 269
244 95         for (int i=0; i<ENTRY_TYPE_COUNT; i++)
245 164                 sqlite3_finalize (SP->fill_entry_query[i]);
246 95         g_free (SP->fill_entry_query);
247 269
248 17         cache_free(self);
249 451
250         if (SP->removal_queue != NULL)
251                 g_warning ("library: removal queue not empty at finalize: %i entries.\n",
252                            g_slist_length (SP->removal_queue));
253
254 17         db_free(self);
255 269
256 17         G_OBJECT_CLASS(library_parent_class)->finalize(object);
257 };
258
259 static void set_property(GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec) {
260         Library *self=LIBRARY(object);
261 449         switch(prop_id) {
262 17             case PROP_FILE_NAME: {
263                 const char *file_name=g_value_get_string(value);
264                         if (g_path_is_absolute(file_name))
265                                 SP->file_name=g_strdup(file_name);
266 269                         else SP->file_name=g_build_filename(g_get_current_dir(), file_name, NULL);
267 17                 break;
268             };
269 269
270 17             default:
271                 G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec); break;
272     };
273 };
274
275 449 MusicSource *library_new (const char *file_name) {
276         Library *self = g_object_new (LIBRARY_TYPE, "filename", file_name, NULL);
277 269
278 17         if (SP->failed) {
279 449                 g_object_ref_sink (self); g_object_unref (self);
280 17                 return NULL;
281         } else {
282 449                 return MUSIC_SOURCE (self);
283 17         };
284 };
285
286 ////////////////////////////////////////////////////////
287 // Generic SQL database methods
288 //
289 // FIXME: remove the gerrors, or make them work again
290
291 //#define TRACE_DB
292
293 164 sqlite3 *_library_get_db (Library *self) {
294         return SP->db;
295 269 };
296 164
297 449 static void db_init (Library *self) {
298 17         int result=sqlite3_open(SP->file_name, &SP->db);
299 269
300 17         if (result!=SQLITE_OK) {
301                 // FIXME: I hate monolog boxes
302                 // FIXME: test in create code if creating file will fail for some obvious
303                 // reason (eg dir read only) and give user a more descriptive message.
304 269                 GtkWidget *dialog=gtk_message_dialog_new_with_markup(NULL, GTK_DIALOG_MODAL,
305                   GTK_MESSAGE_ERROR, GTK_BUTTONS_OK,
306 17                   "<b>Unable to access database %s</b>\n\n %s.\n", SP->file_name, sqlite3_errmsg(SP->db));
307 201                 gtk_dialog_run (GTK_DIALOG(dialog)); gtk_widget_destroy (dialog);
308                 SP->failed = TRUE; return;
309 17         };
310 65
311         //db_action(SP->db, NULL, "PRAGMA legacy_file_format = OFF");
312
313 17         // FIXME: versioning doesn't really work
314 201         int major_version = 0, minor_version = 0;
315 17         char **data; int rows, columns;
316         GError *silencer=NULL;
317 269         data = db_query(self, &rows, &columns, &silencer,
318 201                         "SELECT major_version, minor_version FROM meta");
319 17         if (data!=NULL) {
320 269                 if (rows > 1 || columns!=2)  {
321 201                         // FIXME: I hate monolog boxes
322                         GtkWidget *dialog = gtk_message_dialog_new_with_markup
323 269                                               (NULL, GTK_DIALOG_MODAL, GTK_MESSAGE_ERROR, GTK_BUTTONS_OK,
324 201                                            "<b>Unable to load database %s</b>\n\n "
325                                                                    "The version information is invalid.\n", SP->file_name);
326                         gtk_dialog_run (GTK_DIALOG(dialog)); gtk_widget_destroy (dialog);
327 269                         SP->failed = TRUE; return;
328 201                 } else if (rows==1) {
329                         major_version = atoi(data[2]);
330                         minor_version = atoi(data[3]);
331                 };
332 17                 sqlite3_free_table(data);
333         };
334 269
335 17         begin_transaction(MUSIC_SOURCE(self));
336         // FIXME: this code is a little messy
337 201         if (major_version==0 && minor_version==0)
338 156                 db_create (self);
339 201         else if (major_version != DB_MAJOR_VERSION) {
340                 // FIXME: I hate monolog boxes
341                 GtkWidget *dialog = gtk_message_dialog_new_with_markup
342 269                                                           (NULL, GTK_DIALOG_MODAL, GTK_MESSAGE_ERROR, GTK_BUTTONS_OK,
343 201                                                            "<b>Database %s is from an incompatible version of Calliope</b>\n\n "
344                                                            "I can only read version %i, but this library is version %i.",
345                                                            SP->file_name, DB_MAJOR_VERSION, major_version);
346                 gtk_dialog_run (GTK_DIALOG(dialog)); gtk_widget_destroy (dialog);
347 269                 SP->failed = TRUE; return;
348 201         } else if (minor_version < SCHEMA_VERSION)
349                 db_update (self, minor_version);
350 269         else if (minor_version!=SCHEMA_VERSION)
351 154                 g_error("FIXME: broken db");
352 156         end_transaction (MUSIC_SOURCE(self));
353 269
354
355 156         db_maintain_ids (self);
356 17 };
357
358 222 static void db_free (Library *self) {
359         g_free (SP->file_name);
360 269
361 222         // sqlite3_close fails when prepared statements haven't been freed, for example.
362         int sqlite3_close_result = sqlite3_close(SP->db);
363         g_warn_if_fail (sqlite3_close_result==SQLITE_OK);
364 17 };
365
366 269 // Make these sqlite callers a little more concise. Note _F is executed on each loop
367 144 // iteration, which is exactly what we want.
368 #define SQLITE_WRAPPER(_F)                                                 G_STMT_START { \
369                 GTimer *timer = g_timer_new(); g_timer_start (timer);                             \
370                 while (1) {                                                                       \
371                         result = _F;                                                                  \
372                         if (result==SQLITE_BUSY || result==SQLITE_LOCKED) {                           \
373 154                                 gboolean retry = FALSE;                                                   \
374 346                                 _music_source_emit (MUSIC_SOURCE(self), MUSIC_SOURCE_SIGNAL_BUSY, 0,      \
375 144                                                    g_timer_elapsed(timer, NULL), SP->file_name, &retry);  \
376                                 if (retry) continue;                                                      \
377                                 else {                                                                    \
378                                         if (perror==NULL) g_warning ("Library: unhandled busy.");             \
379                                         break;                                                                \
380                                 };                                                                        \
381                         };                                                                            \
382                         if (result!=SQLITE_OK)                                                        \
383                                 if (perror==NULL)                                                         \
384                                         g_warning ("Library: SQL action failed: %s.\nCommand: %s",            \
385                                                error, text);                                              \
386                         break;                                                                        \
387                 };  g_timer_destroy (timer);                                        } G_STMT_END  \
388 269
389 144
390         // You can use this to test busy handling.
391 269         //              if (g_random_int()&2) result=SQLITE_LOCKED;
392 144
393 static void db_action (Library *self, GError **perror, const char *text) {
394 269         g_return_if_fail (self!=NULL);
395 144         int result; char *error;
396         SQLITE_WRAPPER (sqlite3_exec (SP->db, text, NULL, NULL, &error));
397 269 };
398 17
399 269 static char **db_query (Library *self, int *rows, int *columns, GError **perror,
400 144                         const char *text) {
401 269         g_return_val_if_fail (self!=NULL, NULL);
402 144         int result; char **data, *error;
403 17
404 132         #ifdef LIBRARY_TRACE_QUERIES
405 92                 printf("Query: %s\n", text); fflush(stdout);
406         #endif
407 269
408 144         SQLITE_WRAPPER (sqlite3_get_table(SP->db, text, &data, rows, columns, &error));
409 269
410 132         #ifdef LIBRARY_TRACE_QUERIES
411 144                 printf ("Rows %i columns %i\n", *rows, *columns); fflush(stdout);
412 269         #endif
413
414 17         return data;
415 };
416
417 144 // Note that the 'text' parameter is a printf format! Any non-format %'s in text must be escaped as %% !!
418 static gint64 db_expression (Library *self, GError **perror, const char *text) {
419 269         g_return_val_if_fail (self!=NULL, 0);
420 144         int result; char **data, *error; int rows, columns;
421         SQLITE_WRAPPER (sqlite3_get_table(SP->db, text, &data, &rows, &columns, &error));
422 269
423 144         gint64 value = 0;
424         if (result==SQLITE_OK) {
425                 // Sometimes sqlite lies and gives us rows=1 when all we have is the column
426                 // header.
427 269                 if (rows > 0 && columns > 0 && data[1]!=NULL)
428 144                         value = atoll(data[1]);
429         };
430         sqlite3_free_table (data);
431         return value;
432 };
433
434 // Remember any non-format %'s in format need escaping as %%
435 void db_action_printf(Library *self, GError **perror, const char *format, ...) {
436         char *buf;
437         va_list ap; va_start(ap, format); g_vasprintf(&buf, format, ap); va_end(ap);
438         db_action(self, perror, buf);
439         g_free(buf);
440 };
441
442 17 // Remember any non-format %'s in format need escaping as %%
443 24 char **db_query_printf(Library *self, int *rows, int *columns, GError **perror, const char *format, ...) {
444 144         char *buf; va_list ap; va_start(ap, format); g_vasprintf(&buf, format, ap); va_end(ap);
445 17         char **result=db_query(self, rows, columns, perror, buf);
446         g_free(buf);
447         return result;
448 };
449
450 // Remember any non-format %'s in format need escaping as %%
451 144 gint64 db_expression_printf(Library *self, GError **perror, const char *format, ...) {
452 17         char *buf;
453         va_list ap; va_start(ap, format); g_vasprintf(&buf, format, ap); va_end(ap);
454         gint64 result=db_expression(self, perror, buf);
455         g_free(buf);
456         return result;
457 };
458
459
460 // FIXME: use the precalculated reference matrix !
461 static void db_update_references(Library *self, int type, int from_id, int to_id) {
462         // This function updates all relations to an entry whose ID has changed.
463         for (int table=0;table<ENTRY_TYPE_COUNT;table++) {
464                 // FIXME: remove this
465 269                 if (str_match(entry_type_name[table], "FIXME")) continue;
466 17                 // FIXME: I guess this will be a problem if we ever have tables that reference themselves
467                 if (table==type) continue;
468 269
469 17                 // Find and update any columns which could reference the row being moved.
470 269                 //
471 17                 for (int col=0;col<entry_n_properties[table];col++) {
472                         if (schema[table][col].type==type) {
473 168                                 db_action_printf (self, NULL, "UPDATE _%s SET %s_id=%i WHERE %s_id=%i",
474                                                   entry_type_name[table], schema[table][col].name, to_id,
475 269                                                   schema[table][col].name, from_id);
476 //                              printf("%s.%s references %s\n", entry_type_name[table],
477 17 //                                schema[table][col].name, entry_type_name[table_id]);
478                         }
479                 }
480         };
481 };
482
483 static void db_move_row(Library *self, int table_id, int from_id, int to_id) {
484 269         db_action_printf (self, NULL, "UPDATE _%s SET id=%i WHERE id=%i",
485                           entry_type_name[table_id], to_id, from_id);
486 168         db_update_references (self, table_id, from_id, to_id);
487 17 };
488
489 static GString *make_column_def(int entry_type, int property_id) {
490 285         GString *column_def = g_string_new(NULL);
491         const EntryProperty *property = &schema[entry_type][property_id];
492         switch(property->type) {
493 17                 case PROPERTY_TYPE_INT:
494 285                         g_string_append(column_def, property->name);
495 17                         g_string_append(column_def, " INTEGER"); break;
496                 case PROPERTY_TYPE_FLOAT:
497 285                         g_string_append(column_def, property->name);
498 17                         g_string_append(column_def, " REAL"); break;
499                 case PROPERTY_TYPE_STRING:
500 285                         g_string_append(column_def, property->name);
501 17                         g_string_append(column_def, " VARCHAR"); break;
502                 case PROPERTY_TYPE_NAME:
503 285                         g_string_append(column_def, property->name);
504 67                         g_string_append(column_def, " VARCHAR"); break;
505 17                 case PROPERTY_TYPE_DATE:
506 285                         g_string_append(column_def, property->name);
507 17                         g_string_append(column_def, " DATE"); break;
508                 case PROPERTY_TYPE_DATE_TIME:
509 285                         g_string_append(column_def, property->name);
510 269                         g_string_append(column_def, " DATETIME"); break;
511 17                 default:        // Link to another table
512 285                         g_string_append_printf(column_def, "%s_id INTEGER", property->name);
513 17         };
514 286
515 285         if (property->flags & PROPERTY_UNIQUE)   g_string_append (column_def, " UNIQUE");
516         if (property->flags & PROPERTY_NOT_NULL) g_string_append (column_def, " NOT NULL");
517 17         return column_def;
518 };
519
520 static void db_create(Library *self) {
521         for (int i=0;i<ENTRY_TYPE_COUNT;i++) {
522                 // FIXME: remove this
523                 if (str_match(entry_type_name[i], "FIXME")) continue;
524 269
525 17                 GString *code=g_string_new(NULL);
526 269                 g_string_append_printf(code, "CREATE TABLE _%s (id INTEGER PRIMARY KEY ", entry_type_name[i]);
527
528 17                 for (int j=0;j<entry_n_properties[i];j++) {
529                         GString *column_def=make_column_def(i, j);
530                         g_string_append(code, ", ");
531                         g_string_append(code, column_def->str);
532                         g_string_free(column_def, TRUE);
533                 };
534                 g_string_append(code, ")");
535                 db_action(self, NULL, code->str);
536                 g_string_free(code, TRUE);
537 269         }
538 17
539 201         db_action (self, NULL, "CREATE TABLE meta (major_version INTEGER, minor_version INTEGER)");
540         db_action_printf (self, NULL, "INSERT INTO meta (major_version, minor_version) VALUES (%i, %i)",
541                           DB_MAJOR_VERSION, SCHEMA_VERSION);
542 269
543 201         db_action_printf (self, NULL, "INSERT INTO _artist (id, name, sortname) "
544 269                               "VALUES (%i, 'Unknown Artist', 'ZZZZUnknown Artist')", ARTIST_ID_UNKNOWN);
545 201         db_action_printf (self, NULL, "INSERT INTO _artist (id, name, sortname) "
546                               "VALUES (%i, 'Various Artists', 'ZZZZZVarious Artists')", ARTIST_ID_VARIOUS);
547 17
548 269         // FIXME: do these still help now code is rearranged? What others can we make ??
549 17         // FIXME: can we make more of these ??
550 449         // These are dropped when a transaction starts and created when it ends, in the music_source code below.
551 17         //db_action(self, NULL, "CREATE INDEX track_idx ON track(release_id, number);");
552         //db_action(self, NULL, "CREATE INDEX release_idx ON release (album_id, date);");
553 };
554
555 // Updates from current_version to SCHEMA_VERSION
556 156 static void db_update (Library *self, int current_version) {
557 17         printf("Updating database of version %i to version %i\n", current_version, SCHEMA_VERSION);
558 168
559         if (current_version < 3) {
560                 // Change table names from %s to _%s
561                 //
562 188                 for (int i=0; i<ENTRY_TYPE_COUNT; i++) {
563                         if (str_match(entry_type_name[i], "FIXME")) continue;
564 168                         db_expression_printf (self, NULL, "ALTER TABLE %s RENAME TO _%s",
565 188                                               entry_type_name[i], entry_type_name[i]);
566                 };
567 168         };
568 269
569 17         for (int i=0;i<ENTRY_TYPE_COUNT;i++) {
570                 // FIXME: remove this
571                 if (str_match(entry_type_name[i], "FIXME")) continue;
572 269
573 17                 for (int j=0;j<entry_n_properties[i];j++) {
574                         if (schema[i][j].initial_version>current_version) {
575                                 GString *column_def=make_column_def(i, j);
576 168                                 db_action_printf(self, NULL, "ALTER TABLE _%s ADD %s", entry_type_name[i], column_def->str);
577 17                                 g_string_free(column_def, TRUE);
578                         };
579                 };
580 269         }
581 201         db_action_printf(self, NULL, "UPDATE meta SET minor_version = %i", SCHEMA_VERSION);
582 17 };
583
584 static void db_maintain_ids(Library *self) {
585 269         // Ensures all of the rowids are consecutive and start at 1.
586 153         // FIXME: a bit of a slowdown on startup.
587 154         gboolean changed = FALSE;
588 269
589 17         for (int table=0;table<ENTRY_TYPE_COUNT;table++) {
590 168                 const char *table_name = entry_type_name[table];
591 269                 // FIXME: should not need this
592                 if (strcmp(table_name, "FIXME")==0) continue;
593
594 17                 int rows, columns; char **result;
595 168                 result=db_query_printf(self, &rows, &columns, NULL, "SELECT id FROM _%s ORDER BY id", table_name);
596 17                 if (rows==0 || result==NULL) continue;
597 269
598 17                 // Since order is not significant, the plan is to take any duplicates
599                 // etc to the bottom of the table and to fill any gaps encountered from
600                 // the bottom of the table.
601                 //
602 269                 // This bottom is tracked using three vars:
603                 //   highest_id points to the bottom row.
604 17                 //   highest_pos holds the actual row number of this row
605                 // When we are moving duplicates or id's <1 it is simple to append them
606                 // on the table. When we want to fill from the end it is more complex;
607                 // to find the id of original rows we can check result, but rows we have
608                 // added are not listed there. Therefore we also track highest_original_row.
609                 //
610                 int errors=0, gaps=0;
611                 int cur_valid_id=1, highest_original_row=rows, highest_pos=rows;
612                 int highest_id=atoi(result[highest_pos]);
613 269
614                 for (int cur_pos=1;cur_pos<=rows;) {
615 17                         int cur_id=atoi(result[cur_pos]);
616 269                         if (cur_id<cur_valid_id) {
617 17                                 errors++;
618                                 g_warning("Error at %i/%i: moving %i to %i\n", cur_pos, rows, cur_id, highest_id);
619                                 g_warning("\tHighest is %i pos %i\n", highest_id, highest_pos);
620                                 highest_pos++; highest_id++;
621                                 db_move_row(self, table, cur_id, highest_id);
622 269
623 17                                 cur_pos++;
624                         } else if (cur_id>cur_valid_id) {
625 269                                 gaps++;
626 17                                 //printf("Found gap at pos %i/%i: id %i should be %i\n", cur_pos, rows, cur_id, cur_valid_id);
627                                 //printf("\tHighest is %i pos %i, clean rows %i\n", highest_id, highest_pos, clean_rows);
628                                 // Move a row from the end.
629                                 db_move_row(self, table, highest_id, cur_valid_id);
630
631                                 highest_pos--;
632                                 if (highest_pos>highest_original_row) highest_id--;
633                                 else {
634                                         highest_id=atoi(result[highest_pos]);
635                                         highest_original_row=highest_pos;
636                                 };
637 269
638 17                                 cur_valid_id++;
639                         } else {
640                                 cur_valid_id++;
641                                 cur_pos++;
642                         };
643 269
644                         // Stop a gap fill overrunning if we are at the end of the table.
645 17                         if (highest_pos<cur_pos) break;
646 269                 }
647 24
648                 sqlite3_free_table(result);
649 269
650 17                 //printf("Table %s - highest id %i\n", table_name, highest_id);
651 269                 // We no longer lower sqlite_sequence, because id's aren't AUTOINCREMENT anymore.
652 167                 // Without autoincrement the primary keys are based on highest id+1 which is what we want anyway.
653 269                 //db_action_printf(self, NULL, "UPDATE sqlite_sequence SET seq=%i WHERE name=\"%s\"", highest_id, table_name);
654
655 17                 if (errors || gaps) {
656 269                         printf("Fixed %i errors and %i gaps in table _%s\n",
657 17                           errors, gaps, table_name);
658                         fflush(stdout);
659 154                         changed = TRUE;
660 269                 };
661         };
662
663 154         // Vacuum if we needed to do anything. I imagine this matches closely with when the
664         // db needs vacuuming (mostly when things have been removed I believe) so this should
665         // be the only place we need to call VACUUM.
666         if (changed == TRUE)
667                 db_action (self, NULL, "VACUUM");
668 17 };
669
670 104 void _db_append_join (EntryType local_type, int local_property_id, EntryType foreign_type,
671                       GString *joins, gboolean *join_flags) {
672 168         g_string_append_printf (joins, " INNER JOIN _%s ON _%s.%s_id = _%s.id",
673 269                                                         entry_type_name[foreign_type], entry_type_name[local_type],
674 104                                                         schema[local_type][local_property_id].name,
675                                                         entry_type_name[foreign_type]);
676         if (join_flags!=NULL)
677                 join_flags[foreign_type] = TRUE;
678 };
679
680 97 // Used constructing queries.
681 //  projection: expression to return property is appended
682 //       joins: any necessary joins are appended, taking into account -
683 //  join_flags: if TRUE for entry type, the table is considered already joined. join_flags is
684 //              updated when a join is appended by this function.
685 269 void _db_append_property_query (EntryType entry_type, int property_id, GString *projection,
686 104                                 gboolean comma_flag, GString *joins, gboolean *join_flags) {
687 222         g_return_if_fail (entry_type >= 0 && entry_type < ENTRY_TYPE_COUNT);
688         g_return_if_fail (property_id >= 0 && property_id < entry_n_properties[entry_type]);
689 97         EntryProperty column = schema[entry_type][property_id];
690
691         if (comma_flag)
692                 g_string_append_c (projection, ',');
693
694 269         if (column.flags & PROPERTY_SHADOW) {
695                 EntryType shadowee_type = APID_GET_TYPE(column.shadow_property_apid);
696                 g_string_append_printf (projection, " COALESCE(_%s.%s%s, _%s.%s%s)",
697                                         entry_type_name[entry_type],
698 97                                         column.name, IS_FOREIGN_KEY(column.type)?"_id":"",
699                                                             entry_type_name[shadowee_type],
700                                                             APID_GET_PROPERTY(column.shadow_property_apid).name,
701                                                             IS_FOREIGN_KEY(APID_GET_PROPERTY(column.shadow_property_apid).type)?"_id":"");
702 269
703 97                 if (shadowee_type!=entry_type && (join_flags!=NULL && !join_flags[shadowee_type])) {
704 269                         // Join the table with shadowee in to this one for the query.
705 97                         int join_property_id = -1;
706                         for (int j=0; j<entry_n_properties[entry_type]; j++)
707                                 if (schema[entry_type][j].type==shadowee_type) {
708                                         join_property_id = j; break;
709                                 };
710 269                         if (join_property_id==-1)
711 97                                 // FIXME: try harder
712 168                                 g_warning ("Unable to connect _%s to _%s for shadow property %s.\n",
713 97                                                    entry_type_name[entry_type], entry_type_name[shadowee_type],
714                                                    column.name);
715                         else {
716 104                                 //printf("Appending join from %s to %s\n", entry_type_name[entry_type], entry_type_name[shadowee_type]); fflush(stdout);
717                                 _db_append_join (entry_type, join_property_id, shadowee_type, joins, join_flags);
718 97                         };
719                 };
720         } else
721 168                 g_string_append_printf (projection, " _%s.%s%s", entry_type_name[entry_type],
722                                                                 column.name, IS_FOREIGN_KEY(column.type)?"_id":"");
723 97 };
724
725 274
726 /***************************************************************************************************
727  *   CACHE
728  *  -------
729  *
730 344  * The cache is mainly to reduce the cost of querying entries several times in a row - it's easier
731 274  * to implement a cache than to make all the location code, all the file import code etc. avoid
732  * querying entries multiple times.
733  *
734  * FIXME: have a think. Could entries knowing who refers to them be useful in other areas as well?
735  * FIXME: would btree be faster than a hashtable? GTree could do this! I reckon it might be faster!
736  */
737
738 /*
739  * An entry is tracked in along with all of the other entries which have foreign keys pointing to
740  * it. These hold references on the entry, so to make sure it's freed when we have to remove them
741  * from the cache too. This also optimises retrieving from the cache, because when we query an entry
742  * we don't have to worry about whether its foreign keys are still valid.
743 451  *
744  *  FIXME: genericsource uses a much simpler system - it just zeroes foreign key properties of an
745  *  entry before it unrefs it. Couldn't we just do that?
746 274  */
747 typedef struct {
748         Entry *entry;
749
750         /* local_references stores properties in this entry that reference foreign entries. We need to
751          * store these so we can update the foreign entry's list of referencing entries when this entry
752 451          * changes to point to a different entry, or gets removed from the cache.
753          * FIXME: surely it would be just as easy to iterate through the entry's foreign key properties
754          *        each time we needed to know this. The extra time looking would probably balance out
755          *        with the time saved maintaining the linked list. */
756 274         GSList *local_references,
757
758         /* foreign_references is the entries with properties that are foreign keys on this entry. These
759          * need to be unreffed before this entry can be unreffed - or it won't be freed, the other
760          * entries will keep it in memory. */
761                *foreign_references;
762 } CacheEntry;
763
764 typedef struct {
765         int property_id;
766         CacheEntry *value;
767 } CacheEntryProperty;
768
769 451 static gint reference_list_find_cache_entry (gconstpointer a, gconstpointer b) {
770         const CacheEntryProperty *property = a;
771         const CacheEntry         *target   = b;
772         if (property->value == target) return 0;
773         return 1;
774 };
775
776 283 static void cache_remove_cache_entry (Library *self, CacheEntry *cache_entry, CacheEntry *caller);
777 274
778 /* cache_entry_remove_foreign: former_key_holder, which points to cache_entry, is being removed so
779  *                             should be taken out of cache_entry->foreign_references. */
780 static void cache_entry_remove_foreign (CacheEntry *cache_entry, CacheEntry *former_key_holder) {
781         g_return_if_fail (cache_entry!=NULL);
782
783         for (GSList *node=cache_entry->foreign_references; node; node=node->next) {
784                 CacheEntryProperty *cp = node->data;
785                 if (cp->value == former_key_holder) {
786                         g_slice_free (CacheEntryProperty, cp);
787                         cache_entry->foreign_references = g_slist_remove_link (cache_entry->foreign_references,
788                                                                                node);
789 280                         cache_trace ("removed %s %i from %s %i. %s's foreign list now %i [ref count %i].\n",
790 274                                      ENTRY_PF(former_key_holder->entry), ENTRY_PF(cache_entry->entry),
791 278                                      entry_type_name[cache_entry->entry->type],
792 280                                      g_slist_length(cache_entry->foreign_references),
793                                      cache_entry->entry->ref_count);
794 278
795 274                         return;
796                 };
797         };
798
799 278         g_warn_if_reached (); // Should have found former_key_holder in the foreign references, or we
800                               // have a cache inconsistency.
801 274 };
802
803 /* cache_entry_new: Wrap an entry in a CacheEntry * for insertion in the cache. */
804 static CacheEntry *cache_entry_new (Library *self, Entry *entry) {
805         CacheEntry *cache_entry = g_slice_new(CacheEntry);
806         cache_entry->entry = entry;
807         cache_entry->local_references = cache_entry->foreign_references = NULL;
808
809         for (int p=0; p<entry_n_properties[entry->type]; p++) {
810                 if (IS_FOREIGN_KEY(schema[entry->type][p].type)) {
811                         // This entry points to another. Look it up and register us with it. If it's not cached,
812                         // store it too.
813                         Entry *foreign_entry = entry_get_property(entry, p);
814                         if (foreign_entry==NULL) continue;
815
816                         CacheEntry *foreign_cache_entry = g_hash_table_lookup(SP->cache[foreign_entry->type],
817                                                                               &foreign_entry->id);
818 283                         if (foreign_cache_entry==NULL)
819                                 foreign_cache_entry = cache_store_entry (self, foreign_entry);
820 274
821 283                         //cache_trace ("!!! Storing foreign from %s %i to %s %i.\n", ENTRY_PF(entry), ENTRY_PF(foreign_entry));
822 274                         CacheEntryProperty *foreign_property = g_slice_new (CacheEntryProperty);
823                         foreign_property->property_id = p;
824                         foreign_property->value = cache_entry;
825                         foreign_cache_entry->foreign_references =
826                           g_slist_prepend(foreign_cache_entry->foreign_references, foreign_property);
827
828                         CacheEntryProperty *local_property = g_slice_new (CacheEntryProperty);
829                         local_property->property_id = p;
830 278                         local_property->value = foreign_cache_entry;
831 274                         cache_entry->local_references = g_slist_prepend(cache_entry->local_references,
832 278                                                                         local_property);
833                         //printf ("Storing for %s %i: %
834 274                 };
835         };
836
837         return cache_entry;
838 };
839
840 /* cache_entry_free: DestroyNotify for hash table. cache_entry should have all its references
841  *                   removed already (handled by cache_remove_entry). We can't do it here because
842  *                   we need a pointer to the whole cache. */
843 static void cache_entry_free (CacheEntry *cache_entry) {
844 278         // Don't check that ref_count is 1 - on checkins the entry by the caller isn't freed until after
845         // checking is all done. Let's just hope nobody keeps around entries that are owned by a source
846 274
847         // Entries that point to this one should already have been removed
848         g_warn_if_fail (cache_entry->foreign_references==NULL);
849
850         for (GSList *node=cache_entry->local_references; node; node=node->next)
851                 g_slice_free (CacheEntryProperty, node->data);
852         g_slist_free (cache_entry->local_references);
853
854         entry_unref (cache_entry->entry, "library cache");
855         g_slice_free (CacheEntry, cache_entry);
856 };
857
858 278 /* cache_entry_update: called when entry is checked in, in case it points to different entries than
859  *                     it used to .. */
860 // FIXME: I wonder if it would be quicker to just remove from cache on checkout and add again on
861 // checking back in ...
862 static void cache_entry_update (Library *self, CacheEntry *cache_entry) {
863 283         /*Entry *entry = cache_entry->entry; entry_ref (entry, "foo");
864         cache_remove_cache_entry (self, cache_entry, NULL);
865         cache_store_entry (self, entry); entry_unref (entry, "foo");
866         return;*/
867
868         //#ifdef YOU_LIKE_TO_MAKE_LIFE_HARD_FOR_YOURSELF
869 278         cache_trace ("cache_entry_update: %s %i.\n", ENTRY_PF(cache_entry->entry));
870         int find_by_property (CacheEntryProperty *property, int property_id) {
871                 if (property->property_id == property_id)
872                         return 0;
873                 return 1;
874         };
875
876         Entry *entry = cache_entry->entry;
877         for (int p=0; p<entry_n_properties[entry->type]; p++) {
878                 if (IS_FOREIGN_KEY(schema[entry->type][p].type)) {
879                         Entry *foreign_entry = entry_get_property(entry, p);
880
881                         GSList *node = g_slist_find_custom(cache_entry->local_references, GINT_TO_POINTER(p),
882                                                            (GCompareFunc)find_by_property);
883
884                         CacheEntryProperty *local_cp = NULL;
885                         if (node!=NULL) {
886                                 local_cp = node->data;
887                                 g_return_if_fail (local_cp->property_id==p);
888
889                                 // Remove us from reference list of old entry
890 280                                 if (foreign_entry!=local_cp->value->entry)
891 278                                         cache_entry_remove_foreign (local_cp->value, cache_entry);
892
893 280                                 if (foreign_entry==NULL) {
894                                         // Update local references, if now NULL
895 278                                         cache_entry->local_references =
896                                           g_slist_remove_link(cache_entry->local_references, node);
897                                 };
898 280                         } else {
899 283                                 // Property was NULL - it must be trivial, and if it's still NULL we have nothing to
900                                 // do.
901                                 //
902                                 // FIXME: there is a slightly confusing situation with shadow properties - on add,
903                                 //        this function can get called on an entry which hasn't had its shadow
904                                 //        properties filled yet. So for a property to be a NULL shadow property is
905                                 //        not (always) invalid.
906                                 // FIXME: track.release is allowed to be NULL for some unit tests but you could just
907                                 //        set a release for all of the tracks ....
908                                 g_return_if_fail ((schema[entry->type][p].flags & PROPERTY_TRIVIAL) ||
909                                                   (schema[entry->type][p].flags & PROPERTY_SHADOW) ||
910                                                                   (entry->type==ENTRY_TYPE_TRACK && p==TRACK_RELEASE));
911 280                                 if (foreign_entry==NULL) continue;
912                         };
913
914                         cache_trace ("Property: %s [%x] - old %x, new %x.\n", schema[entry->type][p].name,
915                                      local_cp, local_cp==NULL?0:local_cp->value->entry, foreign_entry);
916
917                         if (local_cp==NULL || (foreign_entry!=local_cp->value->entry)) {
918                                 CacheEntry *foreign_cache_entry =
919                                   g_hash_table_lookup(SP->cache[foreign_entry->type], &foreign_entry->id);
920                                 if (foreign_cache_entry==NULL) cache_store_entry (self, foreign_entry);
921
922                                 // Add/update local references
923                                 if (local_cp==NULL) {
924                                         local_cp = g_slice_new(CacheEntryProperty);
925 283                                         cache_entry->local_references = g_slist_prepend(cache_entry->local_references,
926                                                                                         local_cp);
927 280                                         local_cp->property_id = p;
928                                 };
929                                 local_cp->value = foreign_cache_entry;
930
931                                 // Add us to reference list of new entry
932 283                                 //cache_trace ("cache_entry|update: Adding %s %i to foreign refs of %s %i.\n",
933                                 //             ENTRY_PF(cache_entry->entry), ENTRY_PF(foreign_cache_entry->entry));
934 280                                 CacheEntryProperty *foreign_cp = g_slice_new(CacheEntryProperty);
935                                 foreign_cp->property_id = p;
936                                 foreign_cp->value = cache_entry;
937                                 foreign_cache_entry->foreign_references =
938                                   g_slist_prepend(foreign_cache_entry->foreign_references, foreign_cp);
939 278                         };
940                 };
941         };
942 283         //#endif
943 278 };
944
945 274
946 static void cache_init (Library *self) {
947         for (int i=0; i<ENTRY_TYPE_COUNT; i++)
948 273                 SP->cache[i] = g_hash_table_new_full(g_int_hash, g_int_equal, (GDestroyNotify)g_free,
949 274                                                      (GDestroyNotify)cache_entry_free);
950 17 };
951
952 274 static void cache_free (Library *self) {
953         // This empties the cache - we can't just unref all the entries, we need to be cleverer because
954         // some entries point to each other etc.
955         cache_flush (self);
956 273
957 269         for (int i=0;i<ENTRY_TYPE_COUNT;i++)
958 274                 g_hash_table_unref (SP->cache[i]);
959 };
960
961 280 static Entry *cache_lookup_entry (Library *self, int entry_type, int entry_id) {
962 274         CacheEntry *cache_entry = g_hash_table_lookup(SP->cache[entry_type], &entry_id);
963
964         if (cache_entry!=NULL) {
965                 if (cache_entry->entry->id!=entry_id || cache_entry->entry->type!=entry_type)
966                         g_warning ("cache-lookup: cache inconsistency! entry listed %i (%X) type %s is %s %i\n",
967                                    entry_id, (guint)&entry_id, entry_type_name[entry_type],
968                                    ENTRY_PF(cache_entry->entry));
969 280                 else
970                         entry_ref (cache_entry->entry, "library::cache-lookup");
971 274
972 451                 cache_trace ("cache-lookup: returning %s %i from cache.\n", ENTRY_PF(cache_entry->entry));
973 274                 return cache_entry->entry;
974         }
975
976         cache_trace ("cache-lookup: CACHE MISS on %s %i\n", entry_type_name[entry_type], entry_id);
977         return NULL;
978 };
979
980 279 static void cache_remove_cache_entry (Library *self, CacheEntry *cache_entry, CacheEntry *caller) {
981 280         g_return_if_fail (cache_entry!=NULL);
982
983 283         cache_trace ("cache_remove: %s %i [ refs %i ] - local %i, foreign %i.\n",
984                      ENTRY_PF(cache_entry->entry), cache_entry->entry->ref_count,
985                      g_slist_length(cache_entry->local_references),
986                      g_slist_length(cache_entry->foreign_references));
987 274         // Remove ourself from foreign_references list of all the entries we point to.
988 283         while (cache_entry->local_references) {
989                 CacheEntryProperty *cp = cache_entry->local_references->data;
990 280
991 283                 //printf ("%s %i: Freeing ptr on to %s %i (local %i)\n", ENTRY_PF(cache_entry->entry),
992                 //        ENTRY_PF(cp->value->entry), g_slist_length(cp->value->local_references)); fflush (stdout);
993 280                 // This releases the reference the current entry holds on the foreign entry. Very important!
994                 entry_set_property (cache_entry->entry, cp->property_id, NULL);
995
996 274                 cache_entry_remove_foreign (cp->value, cache_entry);
997 283                 cache_entry->local_references = g_slist_remove_link(cache_entry->local_references,
998                                                                     cache_entry->local_references);
999 274         };
1000
1001 278         // Now, remove entries that point to us. remove_cache_entry removes the list node so we've no
1002         // need to iterate.
1003 283         //cache_trace ("cache_remove: %s %i has %i foreign references - ref count %i.\n",
1004         //             ENTRY_PF(cache_entry->entry), g_slist_length (cache_entry->foreign_references),
1005         //                       cache_entry->entry->ref_count);
1006 278         while (cache_entry->foreign_references) {
1007                 CacheEntryProperty *cp = cache_entry->foreign_references->data;
1008 280
1009 279                 if (cp->value!=caller)
1010                         cache_remove_cache_entry (self, cp->value, cache_entry);
1011 280                 else {
1012                         g_warn_if_fail (schema[cp->value->entry->type][cp->property_id].flags&PROPERTY_TRIVIAL);
1013                         cache_entry->foreign_references = g_slist_remove(cache_entry->foreign_references, cp);
1014 283                         g_slice_free (CacheEntryProperty, cp);
1015 280                 };
1016 274         };
1017
1018         gboolean found = g_hash_table_remove(SP->cache[cache_entry->entry->type],
1019                                              &cache_entry->entry->id);
1020         g_warn_if_fail (found);
1021 };
1022
1023 static void cache_remove_entry (Library *self, EntryType type, int id) {
1024         CacheEntry *cache_entry = g_hash_table_lookup(SP->cache[type], &id);
1025 280         if (cache_entry!=NULL)
1026                 cache_remove_cache_entry (self, cache_entry, NULL);
1027 274 };
1028
1029 283 static void *cache_store_entry (Library *self, Entry *entry) {
1030 274         cache_trace ("cache-store: storing %X %s %i in cache refs %i\n", entry, ENTRY_PF(entry),
1031                      entry->ref_count);
1032
1033 283         CacheEntry *cache_entry = g_hash_table_lookup(SP->cache[entry->type], &entry->id);
1034         if (cache_entry) {
1035 280                 cache_trace ("cache-store: needless call: already stored.\n");
1036 283                 return cache_entry;
1037 280         };
1038
1039 17         // FIXME: these numbers are pulled out of my ass, should do some statistical analysis on cache reads
1040         // FIXME: also, scale by amount of memory we can use for the cache.
1041         // As a reference, these numbers give a total footprint of about 36MB on my system.
1042 283         // Really it's good to have lots of space for entries that are used a lot, like artists, but
1043         // it's also good to have lots of space for entries we have lots of, like files ..
1044         // FIXME: if you ever make these configurable, below like 100 entries the cache is probably useless,
1045         // and below 10 worse than useless. It shouldn't crash however!
1046         // FIXME: There is no code to check if an entry tree is too big for the cache, which is a serious
1047         // corner case but not impossible!
1048 269         static const int cache_max[ENTRY_TYPE_COUNT]=
1049 17                 {       1024,   // Artist
1050                         512,    // Composition
1051                         512,    // Recording
1052                         0,              // FIXME table (I forget what this is even for)
1053 283                         512,    // File
1054 17                         256,    // Album
1055                         256,    // Release
1056 283                         512,    // Track
1057 17                 };
1058 269
1059         // Make sure we don't cane all of the memory.
1060 17         // FIXME: this could be done so much more cleverly by removing entries that are least accessed.
1061 283         g_warn_if_fail (cache_max[entry->type] > 1);
1062         if (g_hash_table_size(SP->cache[entry->type]) > cache_max[entry->type]-1) {
1063 17                 // FIXME: also, is there a faster way ? I'm doing this because I hope its the oldest entry that's
1064                 // returned first but I dunno really.
1065                 GHashTableIter iter; g_hash_table_iter_init(&iter, SP->cache[entry->type]);
1066 351                 void *pointer; CacheEntry *cache_entry;
1067                 g_hash_table_iter_next(&iter, NULL, &pointer); cache_entry = pointer;
1068 273
1069 351                 cache_trace ("cache-store: removing %X %s %i from cache\n", cache_entry,
1070                              ENTRY_PF(cache_entry->entry));
1071                 cache_remove_cache_entry (self, cache_entry, NULL);
1072 17         };
1073 283
1074         // Our reference should be "invisible" - so it's not the first reference removed by entry_unref
1075         entry_ref_invisible(entry, "library cache");
1076
1077         cache_entry = cache_entry_new(self, entry);
1078         int *key = g_new(int, 1); *key = entry->id;
1079         g_hash_table_insert(SP->cache[entry->type], key, cache_entry);
1080         cache_trace ("cache-store: stored %X %s %i in cache refs %i\n", entry, ENTRY_PF(entry),
1081                      entry->ref_count);
1082
1083 451         entry_dump (entry);
1084
1085 283         return cache_entry;
1086 17 };
1087
1088 274
1089 451 /* cache_update_references: Called when entry merging is performed.
1090  *
1091  *   This can happen when entries are checked out, modified in such a way that they match another
1092  *   entry and checked back in. All foreign keys in cached entries that reference 'from' are changed
1093  *   to reference 'to'. */
1094 274 static void cache_change_references (Library *self, Entry *from, Entry *to) {
1095         g_return_if_fail (from!=NULL || to!=NULL);
1096 269         g_return_if_fail (from->type == to->type);
1097 274
1098 451         const EntryType type = from->type;
1099         cache_trace ("\ncache_change_references: updating all cache entries that point to %s %i to "
1100                      "point to %i\n", ENTRY_PF(from), to->id);
1101 274
1102         // If 'from' isn't cached, there's nothing to do - nothing that points to it should be cached
1103         // either.
1104         CacheEntry *from_cache_entry = g_hash_table_lookup (SP->cache[type], &from->id);
1105 451         if (from_cache_entry==NULL)
1106                 return;
1107 274
1108 451         // Cache 'to', so we have a CacheEntry object to store in reference lists.
1109 274         CacheEntry *to_cache_entry = g_hash_table_lookup (SP->cache[type], &to->id);
1110 451         if (to_cache_entry == NULL)
1111                 to_cache_entry = cache_store_entry (self, to);
1112 274
1113 451         int i = 0;
1114 274         for (GSList *node=from_cache_entry->foreign_references; node; node=node->next) {
1115                 CacheEntryProperty *cp = node->data;
1116                 CacheEntry *foreign_cache_entry = cp->value;
1117                 Entry *foreign_entry = foreign_cache_entry->entry;
1118
1119 451                 cache_trace ("\tupdating %s %i.\n", ENTRY_PF(foreign_entry));
1120 274                 g_warn_if_fail (entry_get_property(foreign_entry, cp->property_id)==from);
1121                 entry_set_property (foreign_entry, cp->property_id, to);
1122 451
1123                 // Update foreign_entry->local_references
1124                 GSList *rl_node = g_slist_find_custom (foreign_cache_entry->local_references,
1125                                                        from_cache_entry, reference_list_find_cache_entry);
1126                 g_return_if_fail (rl_node != NULL);
1127
1128                 CacheEntryProperty *foreign_cp = rl_node->data;
1129                 foreign_cp->value = to_cache_entry;
1130
1131                 // The CacheEntryProperty will be taken off 'from'; we now give it to 'to'. Recycling!
1132                 to_cache_entry->foreign_references = g_slist_prepend (to_cache_entry->foreign_references,
1133                                                                       cp);
1134
1135                 #ifdef LIBRARY_TRACE_CACHE
1136                 i++;
1137                 #endif
1138 269         };
1139 274
1140 451         g_slist_free (from_cache_entry->foreign_references);
1141         from_cache_entry->foreign_references = NULL;
1142  
1143         cache_trace ("...updated %i entries.\n\n", i);
1144         cache_check (self);
1145 269 };
1146
1147 95 static void cache_flush (Library *self) {
1148 132         #ifndef LIBRARY_DISABLE_CACHE
1149 278         cache_trace ("\ncache_flush ()\n");
1150 274         for (int i=0; i<ENTRY_TYPE_COUNT; i++) {
1151 351                 void *pointer; CacheEntry *cache_entry; GHashTableIter iter;
1152 278                 // FIXME: nasty way of doing things! Can't iterate though because arbitrary entries are
1153                 // being removed from the hash table :( .. would this be a problem  with a BTRee?
1154                 do {
1155                         g_hash_table_iter_init (&iter, SP->cache[i]);
1156 351                         if (!g_hash_table_iter_next(&iter, NULL, &pointer))
1157 278                                 break;
1158 351                         cache_entry = pointer;
1159 279                         cache_remove_cache_entry (self, cache_entry, NULL);
1160 278                 } while (1);
1161 274         };
1162 95         #endif
1163 };
1164 269
1165 451 static void cache_check (Library *self) {
1166         #if !defined(LIBRARY_DISABLE_CACHE) && defined(LIBRARY_CHECK_CACHE)
1167         for (int i=0; i<ENTRY_TYPE_COUNT; i++) {
1168                 void *pointer; GHashTableIter iter;
1169
1170                 g_hash_table_iter_init (&iter, SP->cache[i]);
1171                 while (g_hash_table_iter_next(&iter, NULL, &pointer)) {
1172                         CacheEntry *cache_entry = pointer;
1173                         Entry      *entry       = cache_entry->entry;
1174                         entry_check (entry);
1175
1176                         // Check local references. FIXME: the simplicity of this test kind of highlights the
1177                         // pointlessness of the local references list.
1178                         int local_reference_count = 0;
1179                         for (int p=0; p < entry_n_properties[entry->type]; p++) {
1180                                 if (!IS_FOREIGN_KEY(schema[entry->type][p].type))
1181                                         continue;
1182
1183                                 Entry      *foreign_entry       = entry_get_property (entry, p);
1184                                 CacheEntry *foreign_cache_entry = g_hash_table_lookup(SP->cache[foreign_entry->type],
1185                                                                                       &foreign_entry->id);
1186                                 g_assert (foreign_cache_entry != NULL);
1187
1188                                 // Check that we hold a cache entry for it.
1189                                 if (g_slist_find_custom (cache_entry->local_references, foreign_cache_entry,
1190                                                          reference_list_find_cache_entry) == NULL)
1191                                         g_warning ("cache_check: %s %i: local_references is missing %s %i.",
1192                                                    ENTRY_PF(entry), ENTRY_PF(foreign_entry));
1193
1194                                 // Check that it holds a cache entry for us.
1195                                 if (g_slist_find_custom (foreign_cache_entry->foreign_references, cache_entry,
1196                                                          reference_list_find_cache_entry) == NULL)
1197                                         g_warning ("cache_check: %s %i: we point to %s %i, but it doesn't point back.",
1198                                                    ENTRY_PF(entry), ENTRY_PF(foreign_entry));
1199
1200                                 local_reference_count ++;
1201                         }
1202                         g_assert_cmpint (g_slist_length (cache_entry->local_references), ==, local_reference_count);
1203
1204                         // Check foreign references. For each foreign cache entry, make sure it actually points to
1205                         // this one.
1206                         for (GSList *node=cache_entry->foreign_references; node; node=node->next) {
1207                                 CacheEntryProperty *foreign_property = node->data;
1208                                 CacheEntry *foreign_cache_entry      = foreign_property->value;
1209                                 Entry *foreign_entry                 = foreign_cache_entry->entry;
1210
1211                                 if (entry_get_property (foreign_entry, foreign_property->property_id) != entry)
1212                                         g_warning ("cache_check: %s %i: foreign_references stores %s %i, but this does "
1213                                                    "not reference the entry.", ENTRY_PF(entry), ENTRY_PF(foreign_entry));
1214                         }
1215                 }
1216         }
1217         #endif
1218 };
1219
1220 /*************************************************************************************************
1221  * Querying - internal functions.
1222  */
1223 17
1224 // FIXME: these functions are very similar to the ones below. I'm willing to
1225 // have some duplication though to keep things faster. Would be nice to merge
1226 // more though ..
1227 // FIXME: these functions could do with some tidying and organising
1228
1229 304
1230 static char *make_property_string(Library *self, Entry *entry, int property) {
1231 17         switch(schema[entry->type][property].type) {
1232                 case PROPERTY_TYPE_INT:
1233                         return g_strdup_printf("%i", (int)entry->property[property]);
1234
1235                 case PROPERTY_TYPE_FLOAT: {
1236                         //printf("Need to convert to float: %X", entry->property[property]);fflush(stdout);
1237 53                         float *value=(float *)(entry->property[property]);
1238                         if (value==NULL)
1239                                 return g_strdup("0.0");
1240                         return g_strdup_printf("%g", *value);
1241 17                 };
1242
1243                 case PROPERTY_TYPE_STRING: case PROPERTY_TYPE_NAME: {
1244                         if (entry->property[property]==NULL)
1245                                 return NULL;
1246 269
1247                         char *sqlite_string=sqlite3_mprintf("%Q", (const char *)entry->property[property]);
1248 17                         char *value=g_strdup(sqlite_string);
1249                         sqlite3_free(sqlite_string);
1250                         return value;
1251                 };
1252 269
1253 17                 case PROPERTY_TYPE_DATE: {
1254                         if (entry->property[property]==NULL || !g_date_valid(entry->property[property]))
1255                                 return NULL;
1256                         char *value=g_malloc(16);
1257                         g_date_strftime(value, 15, "'%Y-%m-%d'", (GDate *)entry->property[property]);
1258                         return value;
1259                 };
1260 269
1261 17                 case PROPERTY_TYPE_DATE_TIME: {
1262 283                         time_t *time_value = entry->property[property];
1263                         if (time_value==NULL) return NULL;
1264                         struct tm brokentime; localtime_r(time_value, &brokentime);
1265                         char *value = g_malloc(32);
1266                         strftime (value, 31, "'%Y-%m-%d %H:%M:%S'", &brokentime);
1267 17                         return value;
1268                 }
1269 269
1270 17                 default: {
1271                         Entry *child=entry->property[property];
1272 304
1273 17                         // 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
1275                         if (child==NULL) return NULL;
1276 304
1277 17                         int entry_id;
1278                         if (child->id!=0 && child->source==self)
1279                                 entry_id=child->id;
1280 451                         else entry_id = _music_source_find_entry(MUSIC_SOURCE(self), child, entry);
1281 304
1282 17                         return g_strdup_printf("%i", entry_id);
1283                 };
1284         };
1285 };
1286
1287
1288 269 // At the moment this makes the whole query (property=value) whereas
1289 17 // make_property_string just returns a value string, this is because where
1290 // entries are concerned we may need to match more than one ! make_property_string
1291 // could change easily though.
1292 // FIXME: I also don't like the names.
1293 // Note this should not return the given entry!
1294 //
1295 static char *make_property_match_string_except(Library *self, Entry *entry, int property, int ignored_apid) {
1296 269 // Builds a SQL query string to find matching entries, ignoring a certain property. This is used for example
1297 449 // (via music_source_query_matching_except) in music_source::track_added_callback
1298 17 // where we find all releases that match on everything but album artist.
1299 //
1300         if (entry->type==APID_GET_TYPE(ignored_apid) && property==APID_GET_PROPERTY_ID(ignored_apid)) return NULL;
1301         const char *property_name=schema[entry->type][property].name;
1302 269
1303 17         switch(schema[entry->type][property].type) {
1304                 case PROPERTY_TYPE_INT:
1305                         return g_strdup_printf("%s=%i", property_name, (int)entry->property[property]);
1306
1307                 case PROPERTY_TYPE_FLOAT:
1308                         //printf("Need to convert to float: %X", entry->property[property]);fflush(stdout);
1309 52                         return g_strdup_printf("%s=%g", property_name, *(float *)(entry->property[property]));
1310 17
1311                 case PROPERTY_TYPE_STRING: case PROPERTY_TYPE_NAME: {
1312                         if (entry->property[property]==NULL)
1313 269                                 return NULL;
1314                         char *sqlite_string=sqlite3_mprintf("%s=%Q", property_name, (const char *)entry->property[property]);
1315 17                         // FIXME: this is silly, should copy sqlite3_mprintf and rewrite using glib allocators. I'm sure they
1316                         // all boil down to libc anyway but it's best to be safe.
1317 269                         char *value=g_strdup(sqlite_string); sqlite3_free(sqlite_string);
1318 17                         return value;
1319                 };
1320 269
1321 17                 case PROPERTY_TYPE_DATE: {
1322 269                         if (entry->property[property]==NULL || !g_date_valid(entry->property[property]))
1323 17                                 return NULL;
1324                         char value[16];
1325                         g_date_strftime(value, 15, "%Y-%m-%d", (GDate *)entry->property[property]);
1326                         return g_strdup_printf("%s='%s'", property_name, value);
1327 269                 };
1328
1329 17                 case PROPERTY_TYPE_DATE_TIME: {
1330                         if (entry->property[property]==NULL) return NULL;
1331                         struct tm brokentime; localtime_r((time_t*)entry->property[property], &brokentime);
1332                         char value[32];
1333                         strftime(value, 31, "%Y-%m-%d %H:%M:%S", &brokentime);
1334                         return g_strdup_printf("%s='%s'", property_name, value);
1335 269                 };
1336
1337 17                 default: {
1338                         Entry *child=entry->property[property];
1339 269                         if (child==NULL) return NULL;
1340
1341 17                         GSList *matching_entry_list=find_entries_matching_except(self, child, ignored_apid, entry);
1342                         if (matching_entry_list==NULL) return NULL;
1343 269
1344 17                         GString *match_string=g_string_new(NULL);
1345                         for (GSList *node=matching_entry_list;node;node=node->next)
1346                                 g_string_append_printf(match_string, "OR %s_id=%i ", property_name, (int)node->data);
1347                         g_slist_free(matching_entry_list);
1348                         match_string->str[0]='('; match_string->str[1]=' '; match_string->str[match_string->len-1]=')';
1349                         return g_string_free(match_string, FALSE);
1350                 };
1351         };
1352 };
1353
1354 168 // FIXME: can we precalculate these queries?
1355 451 static int find_entry (MusicSource *music_source, Entry *entry, Entry *caller) {
1356         Library *self = LIBRARY(music_source);
1357 269         GString *clauses = g_string_new(NULL),
1358 97                 *joins = g_string_new(NULL);
1359         gboolean join_flags[ENTRY_TYPE_COUNT] = {0};
1360 269
1361 17         // FIXME: at the moment, if we search for a child entry which isn't found, find_entry will
1362         // return 0 for id's and this function will generate a query for an id of 0 which will
1363         // fail. However it would be nice to fail straight away if an id of 0 is returned for
1364         // any related entries.
1365 97         for (int i=0; i<entry_n_properties[entry->type]; i++) {
1366 285                 if (!(schema[entry->type][i].flags & PROPERTY_MERGEABLE) &&
1367                     !(schema[entry->type][i].flags & PROPERTY_TRIVIAL)) {
1368 269                     char *property_value_string = NULL;
1369 17                         if (caller!=NULL && schema[entry->type][i].type==caller->type) {
1370                                 // This is where we have an entry that points back up to the calling type.
1371                                 // If it has an id we can simply use that. If not we have to ignore it
1372                                 // pretty much.
1373 97                                 Entry *child = entry_get_property(entry, i);
1374 269                                 if (child!=NULL && child->id!=0)
1375 97                                         property_value_string = g_strdup_printf("%i", child->id);
1376 17                         } else
1377 269                                 property_value_string = make_property_string(self, entry, i/*, find_entry*/);
1378
1379 97                         if (property_value_string!=NULL) {
1380                                 g_string_append (clauses, " AND");
1381 104                                 _db_append_property_query (entry->type, i, clauses, FALSE, joins, join_flags);
1382 269                                 g_string_append_printf (clauses, "=%s", property_value_string);
1383 97                                 g_free (property_value_string);
1384 17                         };
1385                 };
1386         };
1387 269
1388         if (clauses->len>0) {
1389 97                 // Remove initial ' AND'
1390                 clauses->str[0] = clauses->str[1] = clauses->str[2] = clauses->str[3] = ' ';
1391 17         };
1392 269
1393 97         // FIXME: these query formats could actually be largely pre-generated, which would speed
1394         // up imports a bit ..
1395 269         char *query = g_strdup_printf("SELECT _%s.id FROM _%s %s %s %s",
1396                                       entry_type_name[entry->type], entry_type_name[entry->type],
1397 97                                                                   joins->str, clauses->len>0?"WHERE":"", clauses->str);
1398 269
1399 17         char **data; int rows, columns;
1400 97         data = db_query(self, &rows, &columns, NULL, query);
1401         method_track_printf ("find-entry: %s - %i %i\n", query, rows, columns);
1402 269
1403         if (rows==0 || data==NULL)
1404                 return 0;
1405 17         if (rows>1) g_warning("Multiple rows found for query %s", query);
1406 269
1407 97         int id = atoi(data[columns]);
1408 269
1409 17         sqlite3_free_table(data);
1410         g_free(query);
1411 97         g_string_free(joins, TRUE); g_string_free(clauses, TRUE);
1412 269
1413 17         return id;
1414 };
1415
1416
1417 static GSList *find_entries_matching_except(Library *self, Entry *entry, int ignored_apid, Entry *caller) {
1418 449 // Finds matching entries, ignoring a certain property. This is used for example is music_source::track_added_callback
1419 17 // where we find all releases that match on everything but album artist.
1420 //
1421         //printf("\tfind-entry: %s %i %X %i\n", entry_type_name[entry->type], entry->id, entry, entry->ref_count);fflush(stdout);
1422         GString *clauses=g_string_new(NULL);
1423         // FIXME: at the moment, if we search for a child entry which isn't found, find_entry will
1424         // return 0 for id's and this function will generate a query for an id of 0 which will
1425         // fail. However it would be nice to fail straight away if an id of 0 is returned for
1426         // any related entries.
1427         for (int i=0;i<entry_n_properties[entry->type];i++) {
1428                 if (!(schema[entry->type][i].flags & PROPERTY_MERGEABLE) && !(schema[entry->type][i].flags & PROPERTY_TRIVIAL)) {
1429                     char *string=NULL;
1430                         if (caller!=NULL && schema[entry->type][i].type==caller->type) {
1431                                 // FIXME: we don't do anything with backpointers, should we ?
1432                         } else
1433 269                                 string=make_property_match_string_except(self, entry, i, ignored_apid);
1434 17                         if (string!=NULL) {
1435                                 g_string_append_printf(clauses, " AND %s", string);
1436                                 g_free(string);
1437                         };
1438                 };
1439         };
1440 269
1441 17         if (clauses->len>0) {
1442                 clauses->str[0]=clauses->str[1]=clauses->str[2]=clauses->str[3]=' ';
1443         };
1444 269
1445         char *query=g_strdup_printf("SELECT id FROM _%s WHERE %s", entry_type_name[entry->type], clauses->str);
1446 17         char **data; int rows, columns; data=db_query(self, &rows, &columns, NULL, query);
1447         method_track_printf("find-matching-entries-except: %s - %i\n", query, rows);fflush(stdout);
1448 269
1449 17         GSList *result_list=NULL;
1450         if (data!=NULL) {
1451 269                 for (int i=0;i<rows;i++)
1452 17                         result_list=g_slist_prepend(result_list, (void *)atoi(data[(i+1)*columns]));
1453                 sqlite3_free_table(data);
1454         };
1455         g_free(query); g_string_free(clauses, TRUE);
1456 269
1457 17         return result_list;
1458 };
1459
1460 128 // This function doesn't check the cache, so make sure you have called cache_lookup before
1461 // you call this! In fact, you should really use query_entry.
1462 //
1463 304 // FIXME: is query_sql parameter used at all any more?
1464 static Entry *fill_entry (Library *self, int entry_type, int entry_id, Entry *caller) {
1465         method_track_enter ("library::fill-entry - _%s %i\n", entry_type_name[entry_type], entry_id);
1466 17         Entry *entry;
1467 269
1468 17         char *name;
1469 130         #ifdef ENTRY_TRACK_REFERENCE_OWNERS
1470 128         // FIXME: this is leaked - who should own entry reference strings, in fact?
1471 269         if (caller!=NULL)
1472 304                 name = g_strdup_printf("library::fill-entry [caller %s %i]", ENTRY_PF(caller));
1473 128         else name = "library::fill-entry";
1474         #endif
1475 95         entry = entry_new(entry_type, entry_id, MUSIC_SOURCE(self), name);
1476 17
1477 164         sqlite3_stmt *query = SP->fill_entry_query[entry_type];
1478         sqlite3_bind_int (query, 1, entry_id);
1479 269         int result = sqlite3_step (query);
1480 17
1481 164         if (result==SQLITE_DONE) {
1482                 sqlite3_reset (query);
1483 128                 method_track_leave ("done library::fill-entry - entry not found\n");
1484                 entry_unref (entry, name);
1485 17                 return NULL;
1486         };
1487 285         //printf ("%s.\n", sqlite3_sql(query)); fflush (stdout);
1488 269
1489 164         g_return_val_if_fail (result == SQLITE_ROW, NULL);
1490 17         //method_track_printf("here library::fill-entry - %s %i, %s\n", entry_type_name[entry->type], entry->id, query);
1491 269
1492 17         // Sqlite returns column names as the first row so this seeks to the end of that. If
1493         // we requested * (generic query) there will also be the id column to skip.
1494 164         //if (query==NULL) pos+=1;
1495 269
1496 286         int child_id_store[entry_n_properties[entry_type]];
1497
1498 164         if (sqlite3_column_count(query)!=entry_n_properties[entry_type]+1) {
1499                 g_warning ("library: Db query returned wrong number results, cols %i props %i - on %s %i\n"
1500 269                                    "This could well mean your database is out of date and the schema has changed.\n",
1501                                    sqlite3_column_count(query), entry_n_properties[entry_type],
1502 164                                    entry_type_name[entry->type], entry->id);
1503 286         } else {
1504                 g_warn_if_fail (sqlite3_column_int(query, 0) == entry_id);
1505
1506                 for (int p=0; p<entry_n_properties[entry_type]; p++) {
1507                         // if (filter!=NULL) printf("Value for current type: %i - bit %i=%i for prop %i\n",
1508                         //  filter[entry->type], 1<<i, filter[entry->type]&(1<<i), i);
1509                         //printf("Parsing property %i - %s\n", prop_id, schema[entry_type][prop_id].name);fflush(stdout);
1510
1511                         const int column = p+1, column_type = sqlite3_column_type(query, column);
1512                         const EntryProperty *property = &schema[entry_type][p];
1513
1514                         switch(property->type) {
1515                                 case PROPERTY_TYPE_INT:
1516                                         entry->property[p] = (void *)sqlite3_column_int(query, column);
1517                                         break;
1518
1519                                 case PROPERTY_TYPE_FLOAT:
1520                                         if (sqlite3_column_type(query, column)!=SQLITE_NULL) {
1521                                                 float *x = g_slice_new0 (float);
1522                                                 *x = sqlite3_column_double(query, column);
1523                                                 entry->property[p] = x;
1524                                         };
1525                                         break;
1526
1527                                 case PROPERTY_TYPE_STRING: case PROPERTY_TYPE_NAME:
1528                                         if (!(property->flags & PROPERTY_NOT_NULL) && column_type==SQLITE_NULL)
1529                                                 break;
1530                                         g_warn_if_fail (column_type==SQLITE_TEXT);
1531
1532 288                                         const guchar *value = sqlite3_column_text(query, column);
1533                                         g_warn_if_fail (value!=NULL);
1534                                         entry->property[p] = (void *)g_strdup((const gchar *)value);
1535 286                                         break;
1536
1537                                 case PROPERTY_TYPE_DATE:
1538                                         if (sqlite3_column_type(query, column)!=SQLITE_NULL) {
1539                                                 //printf ("Query %s\nColumn %i, type %i.\n", query->sql, p
1540                                                 entry->property[p] = g_date_new();
1541 288                                                 const guchar *text = sqlite3_column_text(query, column);
1542 286                                                 g_return_val_if_fail (text!=NULL, NULL);
1543                                                 // FIXME: this isn't brilliant
1544 288                                                 g_date_set_parse ((GDate *)entry->property[p], (const gchar *)text);
1545 286                                         }
1546                                         break;
1547
1548                                 case PROPERTY_TYPE_DATE_TIME: {
1549                                         if (sqlite3_column_type(query, column)!=SQLITE_NULL) {
1550 288                                                 const guchar *datetime = sqlite3_column_text(query, column);
1551 286                                                 // Catch some invalid results we get for some reason.
1552 288                                                 if (datetime!=NULL && strlen((const gchar *)datetime) > 1) {
1553 286                                                         struct tm brokentime;
1554                                                         #ifdef G_OS_WIN32
1555                                                                 // strptime is not available on mingw. This should be faster anyway, although dodgier
1556                                                                 int Y, m, d, H, M, S;
1557                                                                 sscanf (datetime, "%u-%u-%u %u:%u:%u",
1558                                                                                 &Y, &m, &d, &H, &M, &S);
1559                                                                 memset (&brokentime, 0, sizeof(brokentime));
1560                                                                 brokentime.tm_sec = S; brokentime.tm_min = M; brokentime.tm_hour = H;
1561                                                                 brokentime.tm_mday = d; brokentime.tm_mon = m-1; brokentime.tm_year = Y-1900;
1562                                                         #else
1563 350                                                                 // Need to cast for signedness.
1564 351                                                                 strptime ((const gchar *)datetime, "%Y-%m-%d %H:%M:%S",
1565 350                                                                           &brokentime);
1566 286                                                         #endif
1567                                                         time_t *tp = entry->property[p] = g_slice_new(time_t);
1568                                                         *tp = mktime(&brokentime);
1569                                                 }
1570                                         };
1571                                         break;
1572                                 };
1573
1574 288                                 default:
1575 286                                         child_id_store[p] = sqlite3_column_int(query, column);
1576 288                                         break;
1577 286                         };
1578                 };
1579         };
1580
1581         sqlite3_reset (query);
1582 269
1583 128         for (int p=0; p<entry_n_properties[entry_type]; p++) {
1584 286                 if (IS_FOREIGN_KEY(schema[entry_type][p].type)) {
1585                         const int child_id = child_id_store[p];
1586                         if (caller!=NULL && schema[entry_type][p].type==caller->type && child_id==caller->id) {
1587                                 // Avoid back pointers
1588                                 entry_set_property (entry, p, caller); break;
1589                         } else if (child_id!=0) {
1590                                 // Don't use cache_lookup to avoid getting another reference
1591                                 CacheEntry *child_cache_entry = NULL;
1592
1593                                 #ifndef LIBRARY_DISABLE_CACHE
1594                                 child_cache_entry = g_hash_table_lookup
1595                                                                           (SP->cache[schema[entry->type][p].type], &child_id);
1596                                 #endif
1597
1598                                 if (child_cache_entry==NULL) {
1599 304                                         Entry *child_entry = fill_entry(self, schema[entry->type][p].type, child_id,
1600                                                                         entry);
1601 286                                         entry_take_property (entry, p, child_entry);
1602                                 } else if (entry_get_property(entry, p)!=child_cache_entry->entry)
1603                                         entry_set_property (entry, p, child_cache_entry->entry);
1604                         }
1605 17                 };
1606         };
1607 269
1608 17         //method_track_printf("here library::fill-entry - %s %i, %s\n", entry_type_name[entry->type], entry->id, query);
1609
1610 132         #ifndef LIBRARY_DISABLE_CACHE
1611 274         cache_store_entry (self, entry);
1612 17         #endif
1613
1614 128         method_track_leave ("done library::fill-entry - %s %i, %s\n", entry_type_name[entry->type], entry->id, query);
1615 17         return entry;
1616 };
1617
1618 93 // Convert a query result into the entries it represents.
1619 269 static GSList *_query_entries (Library *self, EntryType entry_type, char **data, int rows,
1620 304                                int columns) {
1621 93         GSList *list = NULL;
1622 269
1623 17         for (int i=0;i<rows;i++) {
1624 93                 int pos = (i+1)*columns;
1625 304                 Entry *entry = fill_entry (self, entry_type, atoi(data[pos]), NULL);
1626 93                 list = g_slist_prepend(list, entry);
1627 17         }
1628 269
1629         // FIXME: do we need to do this ? Or could sql return the results sorted
1630 17         // backwards if order matters ?? Would be quicker to return an array instead
1631 269         // of a list anyway.
1632 93         list = g_slist_reverse(list);
1633 17         return list;
1634 };
1635
1636
1637 451 /*************************************************************************************************
1638  * Querying - MusicSource interface
1639  */
1640 17
1641 // FIXME: make all of this faster !!
1642 static gboolean is_empty(MusicSource *source) {
1643         return FALSE;
1644         //return !(db_expression(LIBRARY(source), NULL, "SELECT COUNT(id) FROM recording"));
1645 269 };
1646 17
1647 static gboolean get_loaded(MusicSource *source) {
1648         // Library just needs to connect to the db so it's "loaded" straight away.
1649         // FIXME: it never emits MusicSource::loaded which could cause problems in
1650         // the future
1651         // FIXME: the whole 'loaded' thing is actually pretty pointless these days.
1652         return TRUE;
1653 };
1654
1655 449 static char *get_summary(MusicSource *music_source) {
1656         Library *self=LIBRARY(music_source);
1657 175         int files=db_expression(self, NULL, "SELECT COUNT(id) FROM _file");
1658 17         // FIXME: due to the screwy deletion code at the moment it is possible to
1659         // have lots of orphan files, this prevents crash !(when there are files but no recordings I think)
1660 175         int recordings=db_expression(self, NULL, "SELECT COUNT(id) FROM _recording");
1661 269
1662         char *msg;
1663 17         if (files && recordings) {
1664 175                 gint64 size=db_expression(self, NULL, "SELECT SUM(size) FROM _file WHERE size>0");
1665 17                 char *size_str=format_bytes(size);
1666 269
1667 175                 int duration=db_expression(self, NULL, "SELECT SUM(length) FROM _recording WHERE length>0");
1668 17                 char *duration_str=format_seconds_literal(duration);
1669 269
1670 17                 msg=g_strdup_printf("%i files: %s in %s", files, duration_str, size_str);
1671                 g_free(duration_str); g_free(size_str);
1672         } else msg=g_strdup("No files");
1673         return msg;
1674 };
1675
1676 // FIXME: would be faster to track dead & highest ourself. Only a little though,
1677 449 // the main benefit is this code could go in music_source cos it would be the
1678 17 // same as importing's.
1679 168 // FIXME: precalculate these fellows (for a tiny exec time saving :)
1680 449 static int get_n_entries (MusicSource *music_source, EntryType type, int *highest_id, int *n_dead) {
1681         int _highest_id = db_expression_printf(LIBRARY(music_source), NULL,
1682 168                                                "SELECT MAX(id) FROM _%s", entry_type_name[type]);
1683 449         int n_entries = db_expression_printf(LIBRARY(music_source), NULL,
1684 168                                              "SELECT COUNT(id) FROM _%s", entry_type_name[type]);
1685         if (highest_id!=NULL) *highest_id = _highest_id;
1686         if (n_dead!=NULL) *n_dead = _highest_id - n_entries;
1687 17         return n_entries;
1688 269 };
1689 17
1690 168 static gboolean is_valid_id (MusicSource *source, EntryType type, int id) {
1691 17         // FIXME: is there a faster way to do this ??
1692         int rows, columns;
1693 269         char **data = db_query_printf(LIBRARY(source), &rows, &columns, NULL,
1694 168                                       "SELECT id FROM _%s WHERE id=%i", entry_type_name[type], id);
1695         sqlite3_free_table (data);
1696 17         if (rows==0) return FALSE;
1697 269         return TRUE;
1698 17 };
1699
1700 static Entry *query_entry(MusicSource *source, EntryType type, int id) {
1701 280         Entry *entry = cache_lookup_entry(LIBRARY(source), type, id);
1702 128         if (entry==NULL)
1703 304                 entry = fill_entry(LIBRARY(source), type, id, NULL);
1704 280         //entry_dump (entry);
1705 128         return entry;
1706 17 };
1707
1708 269 static int query_n_relations (MusicSource *music_source, int local_id, EntryType foreign_type,
1709 102                               int foreign_property_id, int limit) {
1710         return db_expression_printf(LIBRARY(music_source), NULL,
1711 168                                     "SELECT COUNT(_%s.id) FROM _%s WHERE _%s.%s_id=%i LIMIT %i",
1712 269                                     entry_type_name[foreign_type], entry_type_name[foreign_type],
1713                                     entry_type_name[foreign_type],
1714 168                                     schema[foreign_type][foreign_property_id].name, local_id,
1715                                     limit);
1716 102 };
1717
1718 109 // Return list of entries of child_entry_type where child_property = parent_id
1719 // FIXME: query_relations could do this.
1720 449 static GSList *query_entry_children (MusicSource *music_source, int parent_id,
1721 109                                      EntryType child_entry_type, int child_property) {
1722 17         int rows, columns; char **data;
1723 109         GSList *list = NULL;
1724 449         data = db_query_printf(LIBRARY(music_source), &rows, &columns, NULL,
1725 269                                "SELECT id FROM _%s WHERE %s_id=%i", entry_type_name[child_entry_type],
1726 109                                                    schema[child_entry_type][child_property].name, parent_id);
1727 449         list = _query_entries(LIBRARY(music_source), child_entry_type, data, rows, columns);
1728 109         sqlite3_free_table (data);
1729 17         return list;
1730 };
1731
1732 449 static GSList *query_entry_children_ids(MusicSource *music_source, int parent_id, EntryType child_entry_type, int child_property) {
1733 17         int rows, columns; char **data;
1734         GSList *list=NULL;
1735 449         data = db_query_printf(LIBRARY(music_source), &rows, &columns, NULL,
1736 269                                "SELECT id FROM _%s WHERE %s_id=%i", entry_type_name[child_entry_type],
1737 168                                                    schema[child_entry_type][child_property].name, parent_id);
1738 269
1739 17         if (data==NULL) {
1740                 g_warning("library::query_entry_children_ids: finding all %s for %s %i returned NULL data", entry_type_name[child_entry_type], entry_type_name[schema[child_entry_type][child_property].type], parent_id);
1741                 return NULL;
1742         };
1743 269
1744 17         for (int i=0;i<rows;i++) {
1745                 int pos=(i+1)*columns; list=g_slist_prepend(list, (void *)atoi(data[pos]));
1746         }
1747 269
1748         // FIXME: do we need to do this ? Or could sql return the results sorted
1749 17         // backwards if order matters ?? Would be quicker to return an array instead
1750 269         // of a list anyway.
1751         list=g_slist_reverse(list);
1752 17         sqlite3_free_table(data);
1753         return list;
1754 };
1755
1756 269 static GSList *query_relations (MusicSource *music_source, EntryType local_type,
1757 92                                 int local_id, int relation_apid) {
1758         int rows, columns; char **data; GSList *list = NULL;
1759 269
1760
1761 92         EntryType foreign_type;
1762         if (APID_GET_TYPE(relation_apid)==local_type) {
1763                 // * All entries that local refers to in a property:
1764                 foreign_type = APID_GET_PROPERTY(relation_apid).type;
1765 269                 data = db_query_printf(LIBRARY(music_source), &rows, &columns, NULL,
1766 280                                        "SELECT _%s.id FROM _%s INNER JOIN _%s ON _%s.%s_id=_%s.id "
1767                                        "WHERE _%s.id=%i", entry_type_name[foreign_type],
1768                                        entry_type_name[local_type], entry_type_name[foreign_type],
1769                                        entry_type_name[local_type], APID_GET_PROPERTY(relation_apid).name,
1770                                        entry_type_name[foreign_type], entry_type_name[local_type],
1771                                        local_id);
1772 269
1773 92         } else if (APID_GET_PROPERTY(relation_apid).type==local_type) {
1774                 // * All entries that refer to local in a property
1775 269                 foreign_type = APID_GET_TYPE(relation_apid);
1776                 data = db_query_printf(LIBRARY(music_source), &rows, &columns, NULL,
1777 168                                                            "SELECT _%s.id FROM _%s INNER JOIN _%s ON _%s.%s_id=_%s.id WHERE _%s.id=%i",
1778 92                                                            entry_type_name[APID_GET_TYPE(relation_apid)],
1779                                                            entry_type_name[APID_GET_TYPE(relation_apid)],
1780                                                            entry_type_name[local_type],
1781                                                            entry_type_name[APID_GET_TYPE(relation_apid)],
1782                                                            APID_GET_PROPERTY(relation_apid).name,
1783 93                                                            entry_type_name[local_type],
1784 92                                                            entry_type_name[local_type], local_id);
1785 269
1786 92         } else
1787                 g_return_val_if_reached(NULL);
1788 269
1789 304         list = _query_entries(LIBRARY(music_source), foreign_type, data, rows, columns);
1790 92         sqlite3_free_table (data);
1791         return list;
1792 };
1793
1794 17 // FIXME: there is a much faster way to do this ! It's called iterate through id's and call is_valid_id!!!! Why don't we remove this call
1795 // (maybe make the specific queries into a different function) and delete this !
1796 // Of course, I don't know if is_valid_id would be actually be faster in its present state.
1797 static GSList *query_ids(MusicSource *source, EntryType entry_type) {
1798         int rows, columns; char **data;
1799         GSList *list=NULL;
1800 269
1801         data = db_query_printf(LIBRARY(source), &rows, &columns, NULL,
1802 168                                    "SELECT id FROM _%s", entry_type_name[entry_type]);
1803 269
1804 17         if (data==NULL) {
1805                 g_warning("library::query_ids: NULL data for entry type %s", entry_type_name[entry_type]);
1806                 return NULL;
1807         };
1808 269
1809 17         for (int i=0;i<rows;i++) {
1810                 int pos=(i+1)*columns; list=g_slist_prepend(list, (void *)atoi(data[pos]));
1811         }
1812 269
1813         // FIXME: do we need to do this ? Or could sql return the results sorted
1814 17         // backwards if order matters ?? Would be quicker to return an array instead
1815 269         // of a list anyway.
1816 17         list=g_slist_reverse(list);
1817 269
1818 17         sqlite3_free_table(data);
1819 269
1820 17         return list;
1821 };
1822
1823 449 static GSList *query_matching_except (MusicSource *music_source, Entry *entry, int ignored_apid) {
1824 // Queries entries, ignoring a certain property. This is used for example is music_source::track_added_callback
1825 17 // where we find all releases that match on everything but album artist.
1826 //
1827         // FIXME: this needs to be really fast, because it is called on
1828         // releases for every single track add !!
1829 449         Library *self = LIBRARY(music_source);
1830 17         GSList *result=NULL;
1831         GSList *result_ids=find_entries_matching_except(self, entry, ignored_apid, entry);
1832         for (GSList *id_node=result_ids;id_node;id_node=id_node->next)
1833 449                 if (entry->source==music_source && (int)id_node->data!=entry->id)
1834                         result=g_slist_prepend(result, query_entry(music_source, entry->type, (int)id_node->data));
1835 17         g_slist_free(result_ids);
1836         return result;
1837 };
1838
1839 449 static MusicSourceView *create_view (MusicSource *music_source, ViewConfig *config) {
1840 451         /*MusicSourceView *view = g_object_new(LIBRARY_VIEW_TYPE, "source", music_source,
1841                                              "config", config, NULL);*/
1842         MusicSourceView *view = g_object_new(GENERIC_VIEW_TYPE, "source", music_source,
1843 215                                              "config", config, NULL);
1844 24         //printf("library_create_view done- %X\n", view);fflush(stdout);
1845         return view;
1846 };
1847
1848 451 /*************************************************************************************************
1849  * Editing - internal functions.
1850  */
1851
1852 static void add_entry_internal (MusicSource *music_source, Entry *entry) {
1853         Library *self = LIBRARY(music_source);
1854         GString *columns = g_string_new(NULL), *values = g_string_new(NULL);
1855         for (int i=0; i<entry_n_properties[entry->type]; i++) {
1856                 gboolean ignore_property = FALSE;
1857
1858                 // Don't store shadowing properties in the db - leave the value as null instead.
1859 269                 if (schema[entry->type][i].flags & PROPERTY_SHADOW) {
1860 451                         const guint32 shadow_property_apid = schema[entry->type][i].shadow_property_apid;
1861                         void *shadowee_value = entry_get_distant_property (entry, shadow_property_apid);
1862 269                         if (entry_value_match(schema[entry->type][i].type, entry_get_property(entry, i),
1863 451                                               shadowee_value))
1864                                 ignore_property = TRUE;
1865                 }
1866 269
1867 451                 if (!ignore_property) {
1868                         char *string = make_property_string (self, entry, i);
1869                         if (string != NULL) {
1870                                 g_string_append (columns, ", ");
1871                                 g_string_append (columns, schema[entry->type][i].name);
1872                                 if (IS_FOREIGN_KEY(schema[entry->type][i].type))
1873                                         g_string_append(columns, "_id");
1874                                 g_string_append (values, ", ");
1875                                 g_string_append (values, string);
1876                                 g_free (string);
1877                         }
1878 269                 };
1879         };
1880 451
1881 269         if (columns->len>0) columns->str[0]=' ';
1882         if (values->len>0) values->str[0]=' ';
1883
1884         db_action_printf (self, NULL, "INSERT INTO _%s (%s) VALUES(%s)", entry_type_name[entry->type],
1885                           columns->str, values->str);
1886 451         int entry_id = sqlite3_last_insert_rowid (SP->db);
1887 269
1888         g_string_free(columns, TRUE);
1889         g_string_free(values, TRUE);
1890
1891 451         _entry_set_id (entry, entry_id); entry->source = self;
1892
1893         #ifndef LIBRARY_DISABLE_CACHE
1894                 cache_store_entry (self, entry);
1895         #endif
1896 269 };
1897
1898 451 /* update_entry_internal: re-store properties of 'entry' in the database. */
1899 static void update_entry_internal (MusicSource *music_source, Entry *entry) {
1900         Library *self = LIBRARY(music_source);
1901
1902 269         GString *assignments = g_string_new(NULL);
1903 283         for (int i=0; i<entry_n_properties[entry->type]; i++) {
1904 269                 char *string = NULL;
1905 451
1906 269                 if (schema[entry->type][i].flags & PROPERTY_SHADOW) {
1907 283                         // Any shadow properties which are the same as their shadowee are explicitly set to NULL
1908 451                         // 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.
1910                         const guint32 shadow_property_apid = schema[entry->type][i].shadow_property_apid;
1911                         void *shadowee_value = entry_get_distant_property (entry, shadow_property_apid);
1912 269                         if (entry_value_match(schema[entry->type][i].type, entry_get_property(entry, i),
1913                                               shadowee_value))
1914                                 string = g_strdup("NULL");
1915 451                 }
1916
1917                 if (string == NULL)
1918 269                         string = make_property_string(self, entry, i);
1919
1920 451                 if (string != NULL) {
1921                         g_string_append (assignments, ", ");
1922                         g_string_append (assignments, schema[entry->type][i].name);
1923                         if (IS_FOREIGN_KEY(schema[entry->type][i].type))
1924                                 g_string_append(assignments, "_id");
1925                         g_string_append (assignments, "=");
1926                         g_string_append (assignments, string);
1927                         g_free (string);
1928                 }
1929 269         };
1930 451
1931         if (assignments->len>0)
1932                 assignments->str[0]=' ';
1933 269
1934         db_action_printf (self, NULL, "UPDATE _%s SET %s WHERE id=%i", entry_type_name[entry->type],
1935                           assignments->str, entry->id);
1936
1937         g_string_free(assignments, TRUE);
1938 451
1939         #ifndef LIBRARY_DISABLE_CACHE
1940         CacheEntry *cache_entry = g_hash_table_lookup (SP->cache[entry->type], &entry->id);
1941         if (cache_entry!=NULL)
1942                 cache_entry_update (self, cache_entry);
1943         #endif
1944 269 };
1945
1946 451 static void remove_entry_internal (MusicSource *music_source, EntryType type, int id) {
1947         Library *self = LIBRARY(music_source);
1948 269         db_action_printf (self, NULL, "DELETE FROM _%s WHERE id=%i", entry_type_name[type], id);
1949 451
1950         #ifndef LIBRARY_DISABLE_CACHE
1951         cache_remove_entry (self, type, id);
1952         #endif
1953 };
1954
1955 /* update_foreign_keys: used when entries are merged, to update properties that refer to the
1956  *                      removed one 'from_entry' and should point to the kept one 'to_entry' instead
1957  */
1958 static void update_foreign_keys (MusicSource *music_source, Entry *from_entry, Entry *to_entry) {
1959         Library *self = LIBRARY(music_source);
1960
1961         db_update_references (self, from_entry->type, from_entry->id, to_entry->id);
1962
1963         // Find any checked-out entries which reference from_entry.
1964         // FIXME: this function currently only returns the duplicate version 0. I'm almost certain this
1965         // is the wrong thing to do, consider these cases:
1966         //  - two versions of an entry reference different entries - two different duplicates need
1967         //    unreffing
1968         //  - five versions of an entry reference the same duplicate. Surely it needs to be unreffed
1969         //    five times. Or will there be five copies of it?
1970         // FIXME: we could do this much more quickly using the cache, when it is enabled.
1971         GSList *checkout_entry_list = _music_source_list_checked_out_entries (music_source);
1972         for (GSList *node=checkout_entry_list; node; node=node->next) {
1973                 Entry *co_entry = node->data;
1974                 for (int p=0; p<entry_n_properties[co_entry->type]; p++) {
1975                         if (schema[co_entry->type][p].type != from_entry->type)
1976                                 continue;
1977                         if (entry_get_property (co_entry, p) == from_entry)
1978                                 _music_source_unref_checkout_copies (music_source, from_entry->type, from_entry->id);
1979                 }
1980         }
1981         g_slist_free (checkout_entry_list);
1982
1983         #ifndef LIBRARY_DISABLE_CACHE
1984         cache_change_references (self, from_entry, to_entry);
1985         #endif
1986 };
1987
1988
1989 17
1990 270 /* count_references: internal routine used by 'touch'. Checks if entry is not reffed by anything,
1991 451  *                   taking into account the source's removal queue.
1992  *
1993  *   limit: can be used if the exact value isn't actually needed - rather than execute a long query
1994  *          for no reason, count_references will execute the query until it finds 'limit'
1995  *          references and then give up.
1996  *
1997  * FIXME: sloooow!!
1998  */
1999 static int count_references (Library *self, EntryType type, int id, int limit,
2000                              EntryType caller_type, int caller_id) {
2001 269         int ref_count = 0;
2002 451
2003 304         GSList *node = entry_type_info[type].foreign_key_owners;
2004 451         while(node != NULL) {
2005                 const guint32 apid = GPOINTER_TO_INT(node->data);
2006 269
2007                 // Find anything in the removal queue with the type being counted, and ignore the
2008                 // references those entries contain.
2009                 //
2010                 GString *exceptions = g_string_new (NULL);
2011 451                 for (GSList *q_node=SP->removal_queue; q_node; q_node=q_node->next) {
2012                         _RemovalQueueEntry *r = q_node->data;
2013                         if (r->type==APID_GET_TYPE(apid))
2014                                 g_string_append_printf (exceptions, "AND id!=%i ", r->id);
2015                 };
2016 269
2017 270                 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)],
2019                                                   APID_GET_PROPERTY(apid).name, id, exceptions->str, limit);
2020 269                 g_string_free (exceptions, TRUE);
2021                 node = node->next;
2022         };
2023
2024         return ref_count;
2025 };
2026
2027 451 /* touch: garbage collection. */
2028 static void touch (Library *self, EntryType type, int id, gboolean delete,
2029                    gboolean will_be_unreffed, GSList **p_jetsam, EntryType caller_type,
2030 344                    int caller_id) {
2031 451         if (ENTRY_IS_SPECIAL(type, id))
2032 269                 return;
2033
2034         if (delete) {
2035 451                 // 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.
2037 269                 //
2038                 for (int i=0; i<ENTRY_TYPE_COUNT; i++) {
2039                         if (i==type) continue;
2040 451
2041 269                         for (int p=0; p<entry_n_properties[i]; p++) {
2042                                 if (schema[i][p].type==type) {
2043                                         char **data; int rows, columns;
2044                                         data = db_query_printf (self, &rows, &columns, NULL,
2045 451                                                                 "SELECT id FROM _%s WHERE %s_id=%i",
2046                                                                 entry_type_name[i], schema[i][p].name, id);
2047 269                                         if (data==NULL) {
2048 451                                                 g_warning ("library::touch: NULL data finding %s entries linked to %s %i\n",
2049                                                            entry_type_name[i], entry_type_name[schema[i][p].type], id);
2050 269                                                 break;
2051                                         };
2052
2053 451                                         const gboolean delete_child = !(schema[i][p].flags & PROPERTY_TRIVIAL);
2054 269                                         for (int k=0; k<rows; k++) {
2055 451                                                 const int child_id = atoi(data[k+1]);
2056                                                 if (i==caller_type && child_id==caller_id)
2057                                                         continue;
2058
2059                                                 touch (self, i, child_id, delete_child, FALSE, p_jetsam, type, id);
2060 269                                         }
2061                                         sqlite3_free_table (data);
2062                                 };
2063                         };
2064                 };
2065         };
2066
2067         if (!delete) {
2068 451                 int ref_count = count_references(self, type, id, 2, caller_type, caller_id);
2069 269
2070 270                 // It is valid for an entry with no references to be touched with will_be_unreffed TRUE -
2071                 // all the entries that point to it could be in the removal queue, even though they can't
2072                 // yet have been removed.
2073                 g_warn_if_fail (ref_count >= 0);
2074 269                 if (will_be_unreffed) ref_count--;
2075
2076 270                 delete = (ref_count <= 0);
2077 269         };
2078
2079 344         // Touch all entries this one links to, to see if they now need removing or 'changed' emitting.
2080 269         for (int p=0; p<entry_n_properties[type]; p++) {
2081                 if (IS_FOREIGN_KEY(schema[type][p].type)) {
2082                         // Parent is eg a recording's composition
2083 451                         const int foreign_type = schema[type][p].type;
2084                         const int foreign_id = db_expression_printf
2085                                                  (self, NULL, "SELECT %s_id FROM _%s WHERE id=%i",
2086                                                   schema[type][p].name, entry_type_name[type], id);
2087 269                         if (foreign_id!=0 && (foreign_type!=caller_type || foreign_id!=caller_id))
2088 451                                 touch (self, foreign_type, foreign_id, FALSE, delete, p_jetsam, type, id);
2089 269                 };
2090         };
2091
2092         if (delete) {
2093 344                 // Add entry to removal queue. (We can't delete anything until all of the touching is done).
2094 451                 gboolean find (_RemovalQueueEntry *_r) { return !(_r->type==type && _r->id==id); };
2095                 if (!g_slist_find_custom(SP->removal_queue, NULL, (GCompareFunc)find)) {
2096                         _RemovalQueueEntry *removal = g_slice_new0 (_RemovalQueueEntry);
2097                         removal->type = type;
2098                         removal->id = id;
2099                         SP->removal_queue = g_slist_prepend (SP->removal_queue, removal);
2100
2101 346                         Entry *entry = music_source_query_entry (MUSIC_SOURCE(self), type, id, "touch");
2102 451                         _music_source_queue_notify (MUSIC_SOURCE(self), entry, NULL);
2103 346                         entry_unref (entry, "touch");
2104 271                 };
2105 344
2106                 // Also remove from jetsam.
2107                 if (p_jetsam != NULL)
2108                         for (GSList *node=*p_jetsam; node; node=node->next) {
2109                                 Entry *jetsam_entry = node->data;
2110                                 if (jetsam_entry->type==type && jetsam_entry->id==id) {
2111                                         (*p_jetsam) = g_slist_remove_link (*p_jetsam, node);
2112                                         break;
2113                                 };
2114                         };
2115 451         }
2116 269 }
2117
2118 451 static void execute_removal_queue (Library *self) {
2119         MusicSource *music_source = MUSIC_SOURCE(self);
2120         for (GSList *q_node=SP->removal_queue; q_node; q_node=q_node->next) {
2121                 _RemovalQueueEntry *r = q_node->data;
2122                 remove_entry_internal (music_source, r->type, r->id);
2123                 g_slice_free (_RemovalQueueEntry, r);
2124 269         };
2125 451         g_slist_free (SP->removal_queue);
2126         SP->removal_queue = NULL;
2127 269 };
2128
2129
2130 static void begin_transaction(MusicSource *self) {
2131         // FIXME: before or after?
2132         db_action(LIBRARY(self), NULL, "begin");
2133         //GError *silencer=NULL;
2134         //db_action(LIBRARY(self), &silencer, "DROP INDEX track_idx");
2135         //db_action(LIBRARY(self), &silencer, "DROP INDEX release_idx");
2136 };
2137
2138 static void end_transaction(MusicSource *self) {
2139         //db_action(LIBRARY(self), NULL, "CREATE INDEX track_idx ON track(release_id, number);");
2140         //db_action(LIBRARY(self), NULL, "CREATE INDEX release_idx ON release (album_id, date);");
2141         db_action(LIBRARY(self), NULL, "end");
2142 }
2143
2144 451 // FIXME: these functions could actually go in musicsource now, genericsource does more or less the
2145 // same.
2146 449 static int add_entry (MusicSource *music_source, Entry *entry) {
2147 451         entry_take_last_ref (entry, "library::add_entry");
2148         Entry *result = _music_source_add_entry_recursive (music_source, entry, TRUE, entry);
2149
2150         int result_id = result->id;
2151
2152         _music_source_emit_queued_notifications (music_source, NULL);
2153
2154         //entry_destroy (result);
2155         entry_unref (entry, "library::add_entry");
2156 269         if (result!=entry)
2157 451                 entry_unref(result, "_music_source_add_entry_recursive");
2158 269
2159 451         return result_id;
2160 17 };
2161
2162 451 // FIXME: reimplement this, we can do it faster than the base class.
2163 /*static void update_entry_property (MusicSource *self, EntryType entry_type, int entry_id,
2164 269                                    int property_id, const void *value) {
2165         if (schema[entry_type][property_id].flags & PROPERTY_MERGEABLE
2166             || schema[entry_type][property_id].flags & PROPERTY_TRIVIAL) {
2167                 // FIXME: can we do it without doing this ?
2168                 Entry *entry = music_source_query_entry(self, entry_type, entry_id,
2169                                                         "library::update-entry-property");
2170                 if (entry==NULL) {
2171                         g_warning ("library::update-entry-property called on invalid entry\n");
2172                         return;
2173                 };
2174
2175                 // Update in cache - since we just queried the entry, all we need to do is this since it
2176                 // will definately be in the cache.
2177                 entry_set_property (entry, property_id, value);
2178
2179                 // Update in DB
2180                 // FIXME: update_entry_property internal ...
2181                 //
2182                 GString *assignment = g_string_new(NULL);
2183                 char *string = make_property_string(LIBRARY(self), entry, property_id);
2184                 if (string!=NULL) {
2185                         g_string_append (assignment, schema[entry->type][property_id].name);
2186                         if (IS_FOREIGN_KEY(schema[entry->type][property_id].type)) g_string_append(assignment, "_id");
2187                         g_string_append(assignment, "="); g_string_append(assignment, string);
2188                         g_free (string);
2189                 };
2190                 db_action_printf (LIBRARY(self), NULL, "UPDATE _%s SET %s WHERE id=%i",
2191                                   entry_type_name[entry_type], assignment->str, entry_id);
2192                 g_string_free (assignment, TRUE);
2193
2194                 // FIXME: would be faster to do this work ourselves, that's the whole point of this method dumbass !
2195                 // WE only need to emit changed on the whole entry tree, not check merges.
2196                 music_source_checkin_entry (self, entry, "library::update-entry-property");
2197         } else {
2198                 Entry *entry = music_source_checkout_entry(self, entry_type, entry_id,
2199                                                            "library::update-entry-property");
2200
2201                 if (entry==NULL) {
2202                         g_warning ("entry %i is null, attemped to set property %i", entry_id, property_id);
2203                         return;
2204                 } else {
2205                         entry_set_property (entry, property_id, value);
2206                         music_source_checkin_entry (self, entry, "library::update-entry-property");
2207                 };
2208         };
2209
2210 449         // FIXME: should just be two neat methods on music_source really to block & unblock signals
2211 351         if (self->signal_blocking_stack==0 && self->signal_queue!=NULL)
2212 346                 _music_source_emit_queued_signals (self);
2213 451 };*/
2214 269
2215 449 static void remove_entry (MusicSource *music_source, EntryType type, int id) {
2216 451         Library *self = LIBRARY(music_source);
2217 126         method_track_enter ("library::remove-entry %s %i\n", entry_type_name[type], id);
2218 17
2219 449         MUSIC_SOURCE_CLASS(library_parent_class)->remove_entry (music_source, type, id);
2220 269
2221 451         g_warn_if_fail (SP->removal_queue == NULL);
2222
2223         touch (self, type, id, TRUE, FALSE, NULL, 0, 0);
2224
2225         _music_source_emit_queued_notifications (music_source, NULL);
2226         execute_removal_queue (self);
2227 17 };
2228
2229 269 static Entry *checkout_entry (MusicSource *music_source, EntryType root_type, int root_id) {
2230         method_track_printf ("library::checkout-entry %s %i\n", entry_type_name[root_type], root_id);
2231 344
2232         Entry *entry = query_entry(music_source, root_type, root_id);
2233
2234         if (entry != NULL)
2235 451                 _music_source_checkout_entry_recursive (music_source, entry);
2236
2237         // 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.
2239 17         return entry;
2240 };
2241
2242 269 static void checkin_entry (MusicSource *music_source, Entry *entry) {
2243 345         Library *self = LIBRARY(music_source);
2244 451         GSList *jetsam = NULL;
2245 269
2246 451         Entry *result = _music_source_checkin_entry_recursive (music_source, entry, &jetsam, entry);
2247 278
2248 17         // Original entry is unreffed in music_source_checkin_entry
2249 451         if (entry != result)
2250                 entry_unref (result, "_music_source_checkin_entry_recursive");
2251 269
2252         for (GSList *node=jetsam; node; node=node->next) {
2253                 Entry *entry = jetsam->data;
2254 451                 touch (self, entry->type, entry->id, FALSE, FALSE, &node->next, 0, 0);
2255 269         };
2256         g_slist_free (jetsam);
2257
2258 451         if (_music_source_emit_queued_notifications (music_source, NULL)) {
2259                 execute_removal_queue (self);
2260                 entry_destroy (entry);
2261         }
2262 17 };
2263
2264 269
2265
2266 17 /////////////////////////////////////////////////////////
2267 // Debugging methods
2268 //
2269
2270 95 // Empty caches
2271 static void flush(MusicSource *music_source) {
2272         Library *self = LIBRARY(music_source);
2273         cache_flush(self);
2274 };
2275
2276 17 // For debugging
2277 //
2278 206 static void dump (MusicSource *music_source, gboolean detailed) {
2279 207         if (detailed) {
2280                 #ifndef LIBRARY_DISABLE_CACHE
2281                 printf("\n                              LIBRARY: CONTENTS OF CACHE\n\n");
2282 269                 Library *self=LIBRARY(music_source);
2283 207                 for (int i=0;i<ENTRY_TYPE_COUNT;i++) {
2284                         printf("\n%s\n---------------\n", entry_type_name[i]);
2285                         GHashTableIter iter; g_hash_table_iter_init(&iter, SP->cache[i]);
2286 351                         void *pointer; Entry *entry;
2287                         while (g_hash_table_iter_next(&iter, NULL, &pointer)) {
2288                                 entry = pointer; entry_dump (entry);
2289 207                         };
2290                 };
2291                 fflush(stdout);
2292                 #endif
2293
2294                 printf("\n                              LIBRARY: ACTUAL CONTENTS\n\n");
2295                 for (int i=0;i<ENTRY_TYPE_COUNT;i++) {
2296 269                         if (str_match(entry_type_name[i], "FIXME")) continue;
2297 207                         printf("\n%s\n---------------\n", entry_type_name[i]);
2298                         int highest_id;
2299                         music_source_get_n_entries(music_source, i, &highest_id, NULL);
2300                         for (int j=0;j<highest_id;j++) {
2301                                 Entry *entry=music_source_query_entry(music_source, i, j, "dump");
2302                                 if (entry!=NULL) {
2303                                         entry_dump(entry);
2304                                         entry_unref(entry, "dump");
2305                                 };
2306 56                         };
2307                 };
2308         };
2309 17 };
2310 269
2311 132 #ifdef LIBRARY_TRACE_METHODS
2312 17 static int indent_counter=0;    // FIXME: assumes there is only one instance of library, but this only debugging anyway
2313 #endif
2314
2315 static void method_track_enter(const char *text, ...) {
2316 132         #ifdef LIBRARY_TRACE_METHODS
2317 269         for (int i=0;i<indent_counter*2;i+=2) { printf("  ");   };      indent_counter++;
2318 17         va_list ap; va_start(ap, text); vprintf(text, ap); va_end(ap); fflush(stdout);
2319         #endif
2320 };
2321
2322 static void method_track_leave(const char *text, ...) {
2323 132         #ifdef LIBRARY_TRACE_METHODS
2324 17         indent_counter--; for (int i=0;i<indent_counter*2;i+=2) {       printf("  ");   };
2325         va_list ap; va_start(ap, text); vprintf(text, ap); va_end(ap); fflush(stdout);
2326         #endif
2327 };
2328 static void method_track_printf(const char *text, ...) {
2329 132         #ifdef LIBRARY_TRACE_METHODS
2330 17         for (int i=0;i<indent_counter*2;i+=2) { printf("  ");   };
2331         va_list ap; va_start(ap, text); vprintf(text, ap); va_end(ap); fflush(stdout);
2332         #endif
2333 };
2334
2335 274 static void cache_trace (const char *text, ...) {
2336 132         #ifdef LIBRARY_TRACE_CACHE
2337         #ifdef LIBRARY_TRACE_METHODS
2338 17         for (int i=0;i<indent_counter*2;i+=2) { printf("  ");   };
2339         #endif
2340         va_list ap; va_start(ap, text); vprintf(text, ap); va_end(ap); fflush(stdout);
2341         #endif
2342 };
2343 356
2344 #endif

Loggerhead is a web-based interface for Bazaar branches