a404088a0b2b5720d47b380cb15c9d8e0fc24b73
[lms] / lightmediascanner / src / lib / lightmediascanner_db_audio.c
1 #include <lightmediascanner_db.h>
2 #include "lightmediascanner_db_private.h"
3 #include <stdlib.h>
4 #include <stdio.h>
5
6 struct lms_db_audio {
7     sqlite3 *db;
8     sqlite3_stmt *insert_audio;
9     sqlite3_stmt *insert_artist;
10     sqlite3_stmt *insert_album;
11     sqlite3_stmt *insert_genre;
12     sqlite3_stmt *get_artist;
13     sqlite3_stmt *get_album;
14     sqlite3_stmt *get_genre;
15     unsigned int _references;
16     unsigned int _is_started:1;
17 };
18
19 static struct lms_db_cache _cache = {0, NULL};
20
21 static int
22 _db_create(sqlite3 *db, const char *name, const char *sql)
23 {
24     char *err;
25     int r;
26
27     r = sqlite3_exec(db, sql, NULL, NULL, &err);
28     if (r != SQLITE_OK) {
29         fprintf(stderr, "ERROR: could not create \"%s\": %s\n", name, err);
30         sqlite3_free(err);
31         return -1;
32     }
33
34     return 0;
35 }
36
37 static int
38 _db_table_updater_audios_0(sqlite3 *db, const char *table, unsigned int current_version, int is_last_run) {
39     int ret;
40
41     ret = _db_create(db, "audios",
42         "CREATE TABLE IF NOT EXISTS audios ("
43         "id INTEGER PRIMARY KEY, "
44         "title TEXT, "
45         "album_id INTEGER, "
46         "genre_id INTEGER, "
47         "trackno INTEGER, "
48         "rating INTEGER, "
49         "playcnt INTEGER"
50         ")");
51     if (ret != 0)
52         goto done;
53
54     ret = _db_create(db, "audios_title_idx",
55         "CREATE INDEX IF NOT EXISTS "
56         "audios_title_idx ON audios (title)");
57     if (ret != 0)
58         goto done;
59
60     ret = _db_create(db, "audios_album_idx",
61         "CREATE INDEX IF NOT EXISTS "
62         "audios_album_idx ON audios (album_id)");
63     if (ret != 0)
64         goto done;
65
66     ret = _db_create(db, "audios_genre_idx",
67         "CREATE INDEX IF NOT EXISTS "
68         "audios_genre_idx ON audios (genre_id)");
69     if (ret != 0)
70         goto done;
71
72     ret = _db_create(db, "audios_trackno_idx",
73         "CREATE INDEX IF NOT EXISTS "
74         "audios_trackno_idx ON audios (trackno)");
75     if (ret != 0)
76         goto done;
77
78     ret = _db_create(db, "audios_playcnt_idx",
79         "CREATE INDEX IF NOT EXISTS "
80         "audios_playcnt_idx ON audios (playcnt)");
81     if (ret != 0)
82         goto done;
83
84     ret = lms_db_create_trigger_if_not_exists(db,
85         "delete_audios_on_files_deleted "
86         "DELETE ON files FOR EACH ROW BEGIN"
87         "   DELETE FROM audios WHERE id = OLD.id; END;");
88     if (ret != 0)
89         goto done;
90
91     ret = lms_db_create_trigger_if_not_exists(db,
92         "delete_files_on_audios_deleted "
93         "DELETE ON audios FOR EACH ROW BEGIN"
94         " DELETE FROM files WHERE id = OLD.id; END;");
95
96   done:
97     return ret;
98 }
99
100 static lms_db_table_updater_t _db_table_updater_audios[] = {
101     _db_table_updater_audios_0
102 };
103
104 static int
105 _db_table_updater_audio_artists_0(sqlite3 *db, const char *table, unsigned int current_version, int is_last_run) {
106     int ret;
107
108     ret = _db_create(db, "audio_artists",
109         "CREATE TABLE IF NOT EXISTS audio_artists ("
110         "id INTEGER PRIMARY KEY, "
111         "name TEXT UNIQUE"
112         ")");
113     if (ret != 0)
114         goto done;
115
116     ret = _db_create(db, "audio_artists_name_idx",
117         "CREATE INDEX IF NOT EXISTS "
118         "audio_artists_name_idx ON audio_artists (name)");
119
120   done:
121     return ret;
122 }
123
124 static lms_db_table_updater_t _db_table_updater_audio_artists[] = {
125     _db_table_updater_audio_artists_0
126 };
127
128 static int
129 _db_table_updater_audio_albums_0(sqlite3 *db, const char *table, unsigned int current_version, int is_last_run) {
130     int ret;
131
132     ret = _db_create(db, "audio_albums",
133         "CREATE TABLE IF NOT EXISTS audio_albums ("
134         "id INTEGER PRIMARY KEY, "
135         "artist_id INTEGER, "
136         "name TEXT"
137         ")");
138     if (ret != 0)
139         goto done;
140
141     ret = _db_create(db, "audio_albums_name_idx",
142         "CREATE INDEX IF NOT EXISTS "
143         "audio_albums_name_idx ON audio_albums (name)");
144     if (ret != 0)
145         goto done;
146
147     ret = _db_create(db, "audio_albums_artist_idx",
148         "CREATE INDEX IF NOT EXISTS "
149         "audio_albums_artist_idx ON audio_albums (artist_id)");
150     if (ret != 0)
151         goto done;
152
153     ret = lms_db_create_trigger_if_not_exists(db,
154         "delete_audios_on_albums_deleted "
155         "DELETE ON audio_albums FOR EACH ROW BEGIN"
156         " DELETE FROM audios WHERE album_id = OLD.id; END;");
157     if (ret != 0)
158         goto done;
159
160     ret = lms_db_create_trigger_if_not_exists(db,
161         "delete_audio_albums_on_artists_deleted "
162         "DELETE ON audio_artists FOR EACH ROW BEGIN"
163         " DELETE FROM audio_albums WHERE artist_id = OLD.id; END;");
164
165   done:
166     return ret;
167 }
168
169 static lms_db_table_updater_t _db_table_updater_audio_albums[] = {
170     _db_table_updater_audio_albums_0
171 };
172
173 static int
174 _db_table_updater_audio_genres_0(sqlite3 *db, const char *table, unsigned int current_version, int is_last_run) {
175     int ret;
176
177     ret = _db_create(db, "audio_genres",
178         "CREATE TABLE IF NOT EXISTS audio_genres ("
179         "id INTEGER PRIMARY KEY, "
180         "name TEXT UNIQUE"
181         ")");
182     if (ret != 0)
183         goto done;
184
185     ret = _db_create(db, "audio_genres_name_idx",
186         "CREATE INDEX IF NOT EXISTS "
187         "audio_albums_name_idx ON audio_albums (name)");
188     if (ret != 0)
189         goto done;
190
191     ret = lms_db_create_trigger_if_not_exists(db,
192         "delete_audios_on_genres_deleted "
193         "DELETE ON audio_genres FOR EACH ROW BEGIN"
194         " DELETE FROM audios WHERE genre_id = OLD.id; END;");
195
196   done:
197     return ret;
198 }
199
200 static lms_db_table_updater_t _db_table_updater_audio_genres[] = {
201     _db_table_updater_audio_genres_0
202 };
203
204 #define _DB_T_UPDATE(db, name, array)                                   \
205     lms_db_table_update_if_required(db, name, LMS_ARRAY_SIZE(array), array)
206
207 static int
208 _db_create_tables_if_required(sqlite3 *db)
209 {
210     int ret;
211
212     ret = _DB_T_UPDATE(db, "audios", _db_table_updater_audios);
213     if (ret != 0)
214         goto done;
215
216     ret = _DB_T_UPDATE(db, "audio_artists", _db_table_updater_audio_artists);
217     if (ret != 0)
218         goto done;
219
220     ret = _DB_T_UPDATE(db, "audio_albums", _db_table_updater_audio_albums);
221     if (ret != 0)
222         goto done;
223
224     ret = _DB_T_UPDATE(db, "audio_genres", _db_table_updater_audio_genres);
225
226   done:
227     return ret;
228 }
229
230 #undef _DB_T_UPDATE
231
232 /**
233  * Create audio DB access tool.
234  *
235  * Creates or get a reference to tools to access 'audios' table in an
236  * optimized and easy way.
237  *
238  * This is usually called from plugin's @b setup() callback with the @p db
239  * got from @c ctxt.
240  *
241  * @param db database connection.
242  *
243  * @return DB access tool handle.
244  * @ingroup LMS_Plugins
245  */
246 lms_db_audio_t *
247 lms_db_audio_new(sqlite3 *db)
248 {
249     lms_db_audio_t *lda;
250     void *p;
251
252     if (lms_db_cache_get(&_cache, db, &p) == 0) {
253         lda = p;
254         lda->_references++;
255         return lda;
256     }
257
258     if (!db)
259         return NULL;
260
261     if (_db_create_tables_if_required(db) != 0) {
262         fprintf(stderr, "ERROR: could not create tables.\n");
263         return NULL;
264     }
265
266     lda = calloc(1, sizeof(lms_db_audio_t));
267     lda->_references = 1;
268     lda->db = db;
269
270     if (lms_db_cache_add(&_cache, db, lda) != 0) {
271         lms_db_audio_free(lda);
272         return NULL;
273     }
274
275     return lda;
276 }
277
278 /**
279  * Start audio DB access tool.
280  *
281  * Compile SQL statements and other initialization functions.
282  *
283  * This is usually called from plugin's @b start() callback.
284  *
285  * @param lda handle returned by lms_db_audio_new().
286  *
287  * @return On success 0 is returned.
288  * @ingroup LMS_Plugins
289  */
290 int
291 lms_db_audio_start(lms_db_audio_t *lda)
292 {
293     if (!lda)
294         return -1;
295     if (lda->_is_started)
296         return 0;
297
298     lda->insert_audio = lms_db_compile_stmt(lda->db,
299         "INSERT OR REPLACE INTO audios "
300         "(id, title, album_id, genre_id, trackno, rating, playcnt) "
301         "VALUES (?, ?, ?, ?, ?, ?, ?)");
302     if (!lda->insert_audio)
303         return -2;
304
305     lda->insert_artist = lms_db_compile_stmt(lda->db,
306         "INSERT INTO audio_artists (name) VALUES (?)");
307     if (!lda->insert_artist)
308         return -3;
309
310     lda->insert_album = lms_db_compile_stmt(lda->db,
311         "INSERT INTO audio_albums (artist_id, name) VALUES (?, ?)");
312     if (!lda->insert_album)
313         return -4;
314
315     lda->insert_genre = lms_db_compile_stmt(lda->db,
316         "INSERT INTO audio_genres (name) VALUES (?)");
317     if (!lda->insert_genre)
318         return -5;
319
320     lda->get_artist = lms_db_compile_stmt(lda->db,
321         "SELECT id FROM audio_artists WHERE name = ? LIMIT 1");
322     if (!lda->get_artist)
323         return -6;
324
325     lda->get_album = lms_db_compile_stmt(lda->db,
326         "SELECT id FROM audio_albums WHERE name = ? AND artist_id = ? LIMIT 1");
327     if (!lda->get_album)
328         return -7;
329
330     lda->get_genre = lms_db_compile_stmt(lda->db,
331         "SELECT id FROM audio_genres WHERE name = ? LIMIT 1");
332     if (!lda->get_genre)
333         return -8;
334
335     lda->_is_started = 1;
336     return 0;
337 }
338
339 /**
340  * Free audio DB access tool.
341  *
342  * Unreference and possible free resources allocated to access tool.
343  *
344  * This is usually called from plugin's @b finish() callback.
345  *
346  * @param lda handle returned by lms_db_audio_new().
347  *
348  * @return On success 0 is returned.
349  * @ingroup LMS_Plugins
350  */
351 int
352 lms_db_audio_free(lms_db_audio_t *lda)
353 {
354     int r;
355
356     if (!lda)
357         return -1;
358     if (lda->_references == 0) {
359         fprintf(stderr, "ERROR: over-called lms_db_audio_free(%p)\n", lda);
360         return -1;
361     }
362
363     lda->_references--;
364     if (lda->_references > 0)
365         return 0;
366
367     if (lda->insert_audio)
368         lms_db_finalize_stmt(lda->insert_audio, "insert_audio");
369
370     if (lda->insert_artist)
371         lms_db_finalize_stmt(lda->insert_artist, "insert_artist");
372
373     if (lda->insert_album)
374         lms_db_finalize_stmt(lda->insert_album, "insert_album");
375
376     if (lda->insert_genre)
377         lms_db_finalize_stmt(lda->insert_genre, "insert_genre");
378
379     if (lda->get_artist)
380         lms_db_finalize_stmt(lda->get_artist, "get_artist");
381
382     if (lda->get_album)
383         lms_db_finalize_stmt(lda->get_album, "get_album");
384
385     if (lda->get_genre)
386         lms_db_finalize_stmt(lda->get_genre, "get_genre");
387
388     r = lms_db_cache_del(&_cache, lda->db, lda);
389     free(lda);
390
391     return r;
392 }
393
394 static int
395 _db_get_id_by_name(sqlite3_stmt *stmt, const struct lms_string_size *name, int64_t *id)
396 {
397     int r, ret;
398
399     ret = lms_db_bind_text(stmt, 1, name->str, name->len);
400     if (ret != 0)
401         goto done;
402
403     r = sqlite3_step(stmt);
404     if (r == SQLITE_DONE) {
405         ret = 1;
406         goto done;
407     }
408
409     if (r != SQLITE_ROW) {
410         fprintf(stderr, "ERROR: could not get id by name: %s\n",
411                 sqlite3_errmsg(sqlite3_db_handle(stmt)));
412         ret = -2;
413         goto done;
414     }
415
416     *id = sqlite3_column_int64(stmt, 0);
417     ret = 0;
418
419   done:
420     lms_db_reset_stmt(stmt);
421
422     return ret;
423
424 }
425 static int
426 _db_insert_name(sqlite3_stmt *stmt, const struct lms_string_size *name, int64_t *id)
427 {
428     int r, ret;
429
430     ret = lms_db_bind_text(stmt, 1, name->str, name->len);
431     if (ret != 0)
432         goto done;
433
434     r = sqlite3_step(stmt);
435     if (r != SQLITE_DONE) {
436         fprintf(stderr, "ERROR: could not insert name: %s\n",
437                 sqlite3_errmsg(sqlite3_db_handle(stmt)));
438         ret = -2;
439         goto done;
440     }
441
442     *id = sqlite3_last_insert_rowid(sqlite3_db_handle(stmt));
443     ret = 0;
444
445   done:
446     lms_db_reset_stmt(stmt);
447
448     return ret;
449 }
450
451 static int
452 _db_get_artist(lms_db_audio_t *lda, const struct lms_audio_info *info, int64_t *artist_id)
453 {
454     return _db_get_id_by_name(lda->get_artist, &info->artist, artist_id);
455 }
456
457 static int
458 _db_insert_artist(lms_db_audio_t *lda, const struct lms_audio_info *info, int64_t *artist_id)
459 {
460     int r;
461
462     if (!info->artist.str) /* fast path for unknown artist */
463         return 1;
464
465     r =_db_get_artist(lda, info, artist_id);
466     if (r == 0)
467         return 0;
468     else if (r < 0)
469         return -1;
470
471     return _db_insert_name(lda->insert_artist, &info->artist, artist_id);
472 }
473
474 static int
475 _db_get_album(lms_db_audio_t *lda, const struct lms_audio_info *info, int64_t *artist_id, int64_t *album_id)
476 {
477     sqlite3_stmt *stmt;
478     int r, ret;
479
480     stmt = lda->get_album;
481
482     ret = lms_db_bind_text(stmt, 1, info->album.str, info->album.len);
483     if (ret != 0)
484         goto done;
485
486     ret = lms_db_bind_int64_or_null(stmt, 2, artist_id);
487     if (ret != 0)
488         goto done;
489
490     r = sqlite3_step(stmt);
491     if (r == SQLITE_DONE) {
492         ret = 1;
493         goto done;
494     }
495
496     if (r != SQLITE_ROW) {
497         fprintf(stderr, "ERROR: could not get album from table: %s\n",
498                 sqlite3_errmsg(lda->db));
499         ret = -2;
500         goto done;
501     }
502
503     *album_id = sqlite3_column_int64(stmt, 0);
504     ret = 0;
505
506   done:
507     lms_db_reset_stmt(stmt);
508
509     return ret;
510
511 }
512
513 static int
514 _db_insert_album(lms_db_audio_t *lda, const struct lms_audio_info *info, int64_t *album_id)
515 {
516     int r, ret, ret_artist;
517     int64_t artist_id;
518     sqlite3_stmt *stmt;
519
520     if (!info->album.str) /* fast path for unknown album */
521         return 1;
522
523     ret_artist = _db_insert_artist(lda, info, &artist_id);
524     if (ret_artist < 0)
525         return -1;
526
527     r =_db_get_album(lda, info,
528                      (ret_artist == 0) ? &artist_id : NULL,
529                      album_id);
530     if (r == 0)
531         return 0;
532     else if (r < 0)
533         return -1;
534
535     stmt = lda->insert_album;
536     ret = lms_db_bind_int64_or_null(stmt, 1,
537                                     (ret_artist == 0) ? &artist_id : NULL);
538     if (ret != 0)
539         goto done;
540
541     ret = lms_db_bind_text(stmt, 2, info->album.str, info->album.len);
542     if (ret != 0)
543         goto done;
544
545     r = sqlite3_step(stmt);
546     if (r != SQLITE_DONE) {
547         fprintf(stderr, "ERROR: could not insert audio album: %s\n",
548                 sqlite3_errmsg(lda->db));
549         ret = -3;
550         goto done;
551     }
552
553     *album_id = sqlite3_last_insert_rowid(lda->db);
554     ret = 0;
555
556   done:
557     lms_db_reset_stmt(stmt);
558
559     return ret;
560 }
561
562 static int
563 _db_get_genre(lms_db_audio_t *lda, const struct lms_audio_info *info, int64_t *genre_id)
564 {
565     return _db_get_id_by_name(lda->get_genre, &info->genre, genre_id);
566 }
567
568 static int
569 _db_insert_genre(lms_db_audio_t *lda, const struct lms_audio_info *info, int64_t *genre_id)
570 {
571     int r;
572
573     if (!info->genre.str) /* fast path for unknown genre */
574         return 1;
575
576     r =_db_get_genre(lda, info, genre_id);
577     if (r == 0)
578         return 0;
579     else if (r < 0)
580         return -1;
581
582     return _db_insert_name(lda->insert_genre, &info->genre, genre_id);
583 }
584
585 static int
586 _db_insert_audio(lms_db_audio_t *lda, const struct lms_audio_info *info, int64_t *album_id, int64_t *genre_id)
587 {
588     sqlite3_stmt *stmt;
589     int r, ret;
590
591     stmt = lda->insert_audio;
592     ret = lms_db_bind_int64(stmt, 1, info->id);
593     if (ret != 0)
594         goto done;
595
596     ret = lms_db_bind_text(stmt, 2, info->title.str, info->title.len);
597     if (ret != 0)
598         goto done;
599
600     ret = lms_db_bind_int64_or_null(stmt, 3, album_id);
601     if (ret != 0)
602         goto done;
603
604     ret = lms_db_bind_int64_or_null(stmt, 4, genre_id);
605     if (ret != 0)
606         goto done;
607
608     ret = lms_db_bind_int(stmt, 5, info->trackno);
609     if (ret != 0)
610         goto done;
611
612     ret = lms_db_bind_int(stmt, 6, info->rating);
613     if (ret != 0)
614         goto done;
615
616     ret = lms_db_bind_int(stmt, 7, info->playcnt);
617     if (ret != 0)
618         goto done;
619
620     r = sqlite3_step(stmt);
621     if (r != SQLITE_DONE) {
622         fprintf(stderr, "ERROR: could not insert audio info: %s\n",
623                 sqlite3_errmsg(lda->db));
624         ret = -8;
625         goto done;
626     }
627
628     ret = 0;
629
630   done:
631     lms_db_reset_stmt(stmt);
632
633     return ret;
634 }
635
636 /**
637  * Add audio file to DB.
638  *
639  * This is usually called from plugin's @b parse() callback.
640  *
641  * @param lda handle returned by lms_db_audio_new().
642  * @param info audio information to store.
643  *
644  * @return On success 0 is returned.
645  * @ingroup LMS_Plugins
646  */
647 int
648 lms_db_audio_add(lms_db_audio_t *lda, struct lms_audio_info *info)
649 {
650     int64_t album_id, genre_id;
651     int ret_album, ret_genre;
652
653     if (!lda)
654         return -1;
655     if (!info)
656         return -2;
657     if (info->id < 1)
658         return -3;
659
660     ret_album = _db_insert_album(lda, info, &album_id);
661     if (ret_album < 0)
662         return -4;
663
664     ret_genre = _db_insert_genre(lda, info, &genre_id);
665     if (ret_genre < 0)
666         return -5;
667
668     return _db_insert_audio(lda, info,
669                             (ret_album == 0) ? &album_id : NULL,
670                             (ret_genre == 0) ? &genre_id : NULL);
671 }