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