1/* MiniDLNA media server 2 * Copyright (C) 2008-2009 Justin Maggard 3 * 4 * This program is free software; you can redistribute it and/or modify 5 * it under the terms of the GNU General Public License as published by 6 * the Free Software Foundation; either version 2 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 * 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#include <stdio.h> 19#include <string.h> 20#include <stdlib.h> 21#include <unistd.h> 22#include <dirent.h> 23#include <locale.h> 24#include <libgen.h> 25#include <sys/stat.h> 26#include <sys/time.h> 27#include <sys/resource.h> 28 29#include <sqlite3.h> 30 31#include "upnpglobalvars.h" 32#include "metadata.h" 33#include "playlist.h" 34#include "utils.h" 35#include "sql.h" 36#include "scanner.h" 37#include "log.h" 38 39int valid_cache = 0; 40 41struct virtual_item 42{ 43 int objectID; 44 char parentID[64]; 45 char name[256]; 46}; 47 48sqlite_int64 49get_next_available_id(const char * table, const char * parentID) 50{ 51 char * sql; 52 char **result; 53 int ret, rows; 54 sqlite_int64 objectID = 0; 55 56 asprintf(&sql, "SELECT OBJECT_ID from %s where ID = " 57 "(SELECT max(ID) from %s where PARENT_ID = '%s')", table, table, parentID); 58 ret = sql_get_table(db, sql, &result, &rows, NULL); 59 if( rows ) 60 { 61 objectID = strtoll(strrchr(result[1], '$')+1, NULL, 16) + 1; 62 } 63 sqlite3_free_table(result); 64 free(sql); 65 66 return objectID; 67} 68 69long long int 70insert_container(const char * item, const char * rootParent, const char * refID, const char *class, 71 const char *artist, const char *genre, const char *album_art) 72{ 73 char **result; 74 char **result2; 75 char *sql; 76 int cols, rows, ret; 77 int parentID = 0, objectID = 0; 78 sqlite_int64 detailID = 0; 79 80 sql = sqlite3_mprintf("SELECT OBJECT_ID from OBJECTS" 81 " where PARENT_ID = '%s'" 82 " and NAME = '%q'" 83 " and CLASS = 'container.%s' limit 1", 84 rootParent, item, class); 85 ret = sql_get_table(db, sql, &result, &rows, &cols); 86 sqlite3_free(sql); 87 if( cols ) 88 { 89 parentID = strtol(rindex(result[1], '$')+1, NULL, 16); 90 objectID = get_next_available_id("OBJECTS", result[1]); 91 } 92 else 93 { 94 parentID = get_next_available_id("OBJECTS", rootParent); 95 if( refID ) 96 { 97 sql = sqlite3_mprintf("SELECT DETAIL_ID from OBJECTS where OBJECT_ID = %Q", refID); 98 ret = sql_get_table(db, sql, &result2, &rows, NULL); 99 if( ret == SQLITE_OK ) 100 { 101 if( rows ) 102 { 103 detailID = strtoll(result2[1], NULL, 10); 104 } 105 sqlite3_free_table(result2); 106 } 107 sqlite3_free(sql); 108 } 109 if( !detailID ) 110 { 111 detailID = GetFolderMetadata(item, NULL, artist, genre, album_art); 112 } 113 ret = sql_exec(db, "INSERT into OBJECTS" 114 " (OBJECT_ID, PARENT_ID, REF_ID, DETAIL_ID, CLASS, NAME) " 115 "VALUES" 116 " ('%s$%X', '%s', %Q, %lld, 'container.%s', '%q')", 117 rootParent, parentID, rootParent, refID, detailID, class, item); 118 } 119 sqlite3_free_table(result); 120 121 return (long long)parentID<<32|objectID; 122} 123 124void 125insert_containers(const char * name, const char *path, const char * refID, const char * class, long unsigned int detailID) 126{ 127 char *sql; 128 char **result; 129 int ret; 130 int cols, row; 131 sqlite_int64 container; 132 133 if( strstr(class, "imageItem") ) 134 { 135 char *date = NULL, *cam = NULL; 136 char date_taken[13], camera[64]; 137 static struct virtual_item last_date; 138 static struct virtual_item last_cam; 139 static struct virtual_item last_camdate; 140 static sqlite_int64 last_all_objectID = 0; 141 142 asprintf(&sql, "SELECT DATE, CREATOR from DETAILS where ID = %lu", detailID); 143 ret = sql_get_table(db, sql, &result, &row, &cols); 144 free(sql); 145 if( ret == SQLITE_OK ) 146 { 147 date = result[2]; 148 cam = result[3]; 149 } 150 151 if( date ) 152 { 153 strncpy(date_taken, date, 10); 154 date_taken[10] = '\0'; 155 } 156 else 157 { 158 strcpy(date_taken, "Unknown Date"); 159 } 160 if( valid_cache && strcmp(last_date.name, date_taken) == 0 ) 161 { 162 last_date.objectID++; 163 //DEBUG DPRINTF(E_DEBUG, L_SCANNER, "Using last date item: %s/%s/%X\n", last_date.name, last_date.parentID, last_date.objectID); 164 } 165 else 166 { 167 container = insert_container(date_taken, "3$12", NULL, "album.photoAlbum", NULL, NULL, NULL); 168 sprintf(last_date.parentID, "3$12$%llX", container>>32); 169 last_date.objectID = (int)container; 170 strcpy(last_date.name, date_taken); 171 //DEBUG DPRINTF(E_DEBUG, L_SCANNER, "Creating cached date item: %s/%s/%X\n", last_date.name, last_date.parentID, last_date.objectID); 172 } 173 sql_exec(db, "INSERT into OBJECTS" 174 " (OBJECT_ID, PARENT_ID, REF_ID, CLASS, DETAIL_ID, NAME) " 175 "VALUES" 176 " ('%s$%X', '%s', '%s', '%s', %lu, %Q)", 177 last_date.parentID, last_date.objectID, last_date.parentID, refID, class, detailID, name); 178 179 if( cam ) 180 { 181 strncpy(camera, cam, 63); 182 camera[63] = '\0'; 183 } 184 else 185 { 186 strcpy(camera, "Unknown Camera"); 187 } 188 if( !valid_cache || strcmp(camera, last_cam.name) != 0 ) 189 { 190 container = insert_container(camera, "3$13", NULL, "storageFolder", NULL, NULL, NULL); 191 sprintf(last_cam.parentID, "3$13$%llX", container>>32); 192 strncpy(last_cam.name, camera, 255); 193 last_camdate.name[0] = '\0'; 194 } 195 if( valid_cache && strcmp(last_camdate.name, date_taken) == 0 ) 196 { 197 last_camdate.objectID++; 198 //DEBUG DPRINTF(E_DEBUG, L_SCANNER, "Using last camdate item: %s/%s/%s/%X\n", camera, last_camdate.name, last_camdate.parentID, last_camdate.objectID); 199 } 200 else 201 { 202 container = insert_container(date_taken, last_cam.parentID, NULL, "album.photoAlbum", NULL, NULL, NULL); 203 sprintf(last_camdate.parentID, "%s$%llX", last_cam.parentID, container>>32); 204 last_camdate.objectID = (int)container; 205 strcpy(last_camdate.name, date_taken); 206 //DEBUG DPRINTF(E_DEBUG, L_SCANNER, "Creating cached camdate item: %s/%s/%s/%X\n", camera, last_camdate.name, last_camdate.parentID, last_camdate.objectID); 207 } 208 sql_exec(db, "INSERT into OBJECTS" 209 " (OBJECT_ID, PARENT_ID, REF_ID, CLASS, DETAIL_ID, NAME) " 210 "VALUES" 211 " ('%s$%X', '%s', '%s', '%s', %lu, %Q)", 212 last_camdate.parentID, last_camdate.objectID, last_camdate.parentID, refID, class, detailID, name); 213 /* All Images */ 214 if( !last_all_objectID ) 215 { 216 last_all_objectID = get_next_available_id("OBJECTS", "3$11"); 217 } 218 sql_exec(db, "INSERT into OBJECTS" 219 " (OBJECT_ID, PARENT_ID, REF_ID, CLASS, DETAIL_ID, NAME) " 220 "VALUES" 221 " ('3$11$%llX', '3$11', '%s', '%s', %lu, %Q)", 222 last_all_objectID++, refID, class, detailID, name); 223 } 224 else if( strstr(class, "audioItem") ) 225 { 226 asprintf(&sql, "SELECT ALBUM, CREATOR, GENRE, ALBUM_ART from DETAILS where ID = %lu", detailID); 227 ret = sql_get_table(db, sql, &result, &row, &cols); 228 free(sql); 229 if( ret != SQLITE_OK ) 230 return; 231 if( !row ) 232 { 233 sqlite3_free_table(result); 234 return; 235 } 236 char *album = result[4], *artist = result[5], *genre = result[6]; 237 char *album_art = result[7]; 238 static struct virtual_item last_album; 239 static struct virtual_item last_artist; 240 static struct virtual_item last_artistAlbum; 241 static struct virtual_item last_artistAlbumAll; 242 static struct virtual_item last_genre; 243 static struct virtual_item last_genreArtist; 244 static struct virtual_item last_genreArtistAll; 245 static sqlite_int64 last_all_objectID = 0; 246 247 if( album ) 248 { 249 if( valid_cache && strcmp(album, last_album.name) == 0 ) 250 { 251 last_album.objectID++; 252 //DEBUG DPRINTF(E_DEBUG, L_SCANNER, "Using last album item: %s/%s/%X\n", last_album.name, last_album.parentID, last_album.objectID); 253 } 254 else 255 { 256 strcpy(last_album.name, album); 257 container = insert_container(album, "1$7", NULL, "album.musicAlbum", artist, genre, album_art); 258 sprintf(last_album.parentID, "1$7$%llX", container>>32); 259 last_album.objectID = (int)container; 260 //DEBUG DPRINTF(E_DEBUG, L_SCANNER, "Creating cached album item: %s/%s/%X\n", last_album.name, last_album.parentID, last_album.objectID); 261 } 262 sql_exec(db, "INSERT into OBJECTS" 263 " (OBJECT_ID, PARENT_ID, REF_ID, CLASS, DETAIL_ID, NAME) " 264 "VALUES" 265 " ('%s$%X', '%s', '%s', '%s', %lu, %Q)", 266 last_album.parentID, last_album.objectID, last_album.parentID, refID, class, detailID, name); 267 } 268 if( artist ) 269 { 270 if( !valid_cache || strcmp(artist, last_artist.name) != 0 ) 271 { 272 container = insert_container(artist, "1$6", NULL, "person.musicArtist", NULL, genre, NULL); 273 sprintf(last_artist.parentID, "1$6$%llX", container>>32); 274 strcpy(last_artist.name, artist); 275 last_artistAlbum.name[0] = '\0'; 276 /* Add this file to the "- All Albums -" container as well */ 277 container = insert_container("- All Albums -", last_artist.parentID, NULL, "storageFolder", artist, genre, NULL); 278 sprintf(last_artistAlbumAll.parentID, "%s$%llX", last_artist.parentID, container>>32); 279 last_artistAlbumAll.objectID = (int)container; 280 } 281 else 282 { 283 last_artistAlbumAll.objectID++; 284 } 285 if( valid_cache && strcmp(album?album:"Unknown Album", last_artistAlbum.name) == 0 ) 286 { 287 last_artistAlbum.objectID++; 288 //DEBUG DPRINTF(E_DEBUG, L_SCANNER, "Using last artist/album item: %s/%s/%X\n", last_artist.name, last_artist.parentID, last_artist.objectID); 289 } 290 else 291 { 292 container = insert_container(album?album:"Unknown Album", last_artist.parentID, album?last_album.parentID:NULL, "album.musicAlbum", artist, genre, album_art); 293 sprintf(last_artistAlbum.parentID, "%s$%llX", last_artist.parentID, container>>32); 294 last_artistAlbum.objectID = (int)container; 295 strcpy(last_artistAlbum.name, album?album:"Unknown Album"); 296 //DEBUG DPRINTF(E_DEBUG, L_SCANNER, "Creating cached artist/album item: %s/%s/%X\n", last_artist.name, last_artist.parentID, last_artist.objectID); 297 } 298 sql_exec(db, "INSERT into OBJECTS" 299 " (OBJECT_ID, PARENT_ID, REF_ID, CLASS, DETAIL_ID, NAME) " 300 "VALUES" 301 " ('%s$%X', '%s', '%s', '%s', %lu, %Q)", 302 last_artistAlbum.parentID, last_artistAlbum.objectID, last_artistAlbum.parentID, refID, class, detailID, name); 303 sql_exec(db, "INSERT into OBJECTS" 304 " (OBJECT_ID, PARENT_ID, REF_ID, CLASS, DETAIL_ID, NAME) " 305 "VALUES" 306 " ('%s$%X', '%s', '%s', '%s', %lu, %Q)", 307 last_artistAlbumAll.parentID, last_artistAlbumAll.objectID, last_artistAlbumAll.parentID, refID, class, detailID, name); 308 } 309 if( genre ) 310 { 311 if( !valid_cache || strcmp(genre, last_genre.name) != 0 ) 312 { 313 container = insert_container(genre, "1$5", NULL, "genre.musicGenre", NULL, NULL, NULL); 314 sprintf(last_genre.parentID, "1$5$%llX", container>>32); 315 strcpy(last_genre.name, genre); 316 last_genreArtist.name[0] = '\0'; 317 /* Add this file to the "- All Artists -" container as well */ 318 container = insert_container("- All Artists -", last_genre.parentID, NULL, "storageFolder", NULL, genre, NULL); 319 sprintf(last_genreArtistAll.parentID, "%s$%llX", last_genre.parentID, container>>32); 320 last_genreArtistAll.objectID = (int)container; 321 } 322 else 323 { 324 last_genreArtistAll.objectID++; 325 } 326 if( valid_cache && strcmp(artist?artist:"Unknown Artist", last_genreArtist.name) == 0 ) 327 { 328 last_genreArtist.objectID++; 329 } 330 else 331 { 332 container = insert_container(artist?artist:"Unknown Artist", last_genre.parentID, artist?last_artist.parentID:NULL, "person.musicArtist", NULL, genre, NULL); 333 sprintf(last_genreArtist.parentID, "%s$%llX", last_genre.parentID, container>>32); 334 last_genreArtist.objectID = (int)container; 335 strcpy(last_genreArtist.name, artist?artist:"Unknown Artist"); 336 //DEBUG DPRINTF(E_DEBUG, L_SCANNER, "Creating cached genre/artist item: %s/%s/%X\n", last_genreArtist.name, last_genreArtist.parentID, last_genreArtist.objectID); 337 } 338 sql_exec(db, "INSERT into OBJECTS" 339 " (OBJECT_ID, PARENT_ID, REF_ID, CLASS, DETAIL_ID, NAME) " 340 "VALUES" 341 " ('%s$%X', '%s', '%s', '%s', %lu, %Q)", 342 last_genreArtist.parentID, last_genreArtist.objectID, last_genreArtist.parentID, refID, class, detailID, name); 343 sql_exec(db, "INSERT into OBJECTS" 344 " (OBJECT_ID, PARENT_ID, REF_ID, CLASS, DETAIL_ID, NAME) " 345 "VALUES" 346 " ('%s$%X', '%s', '%s', '%s', %lu, %Q)", 347 last_genreArtistAll.parentID, last_genreArtistAll.objectID, last_genreArtistAll.parentID, refID, class, detailID, name); 348 } 349 /* All Music */ 350 if( !last_all_objectID ) 351 { 352 last_all_objectID = get_next_available_id("OBJECTS", "1$4"); 353 } 354 sql_exec(db, "INSERT into OBJECTS" 355 " (OBJECT_ID, PARENT_ID, REF_ID, CLASS, DETAIL_ID, NAME) " 356 "VALUES" 357 " ('1$4$%llX', '1$4', '%s', '%s', %lu, %Q)", 358 last_all_objectID++, refID, class, detailID, name); 359 } 360 else if( strstr(class, "videoItem") ) 361 { 362 static sqlite_int64 last_all_objectID = 0; 363 364 /* All Videos */ 365 if( !last_all_objectID ) 366 { 367 last_all_objectID = get_next_available_id("OBJECTS", "2$8"); 368 } 369 sql_exec(db, "INSERT into OBJECTS" 370 " (OBJECT_ID, PARENT_ID, REF_ID, CLASS, DETAIL_ID, NAME) " 371 "VALUES" 372 " ('2$8$%llX', '2$8', '%s', '%s', %lu, %Q)", 373 last_all_objectID++, refID, class, detailID, name); 374 return; 375 } 376 else 377 { 378 return; 379 } 380 sqlite3_free_table(result); 381 valid_cache = 1; 382} 383 384int 385insert_directory(const char * name, const char * path, const char * base, const char * parentID, int objectID) 386{ 387 char * sql; 388 int rows, found = 0; 389 sqlite_int64 detailID = 0; 390 char * refID = NULL; 391 char class[] = "container.storageFolder"; 392 char * id_buf = NULL; 393 char * parent_buf = NULL; 394 char **result; 395 char *dir = NULL; 396 static char last_found[256] = "-1"; 397 398 if( strcmp(base, BROWSEDIR_ID) != 0 ) 399 asprintf(&refID, "%s%s$%X", BROWSEDIR_ID, parentID, objectID); 400 401 if( refID ) 402 { 403 dir = strdup(path); 404 dir = dirname(dir); 405 asprintf(&id_buf, "%s%s$%X", base, parentID, objectID); 406 asprintf(&parent_buf, "%s%s", base, parentID); 407 while( !found ) 408 { 409 if( strcmp(id_buf, last_found) == 0 ) 410 break; 411 if( sql_get_int_field(db, "SELECT count(*) from OBJECTS where OBJECT_ID = '%s'", id_buf) > 0 ) 412 { 413 strcpy(last_found, id_buf); 414 break; 415 } 416 /* Does not exist. Need to create, and may need to create parents also */ 417 sql = sqlite3_mprintf("SELECT DETAIL_ID from OBJECTS where OBJECT_ID = '%s'", refID); 418 if( (sql_get_table(db, sql, &result, &rows, NULL) == SQLITE_OK) && rows ) 419 { 420 detailID = atoi(result[1]); 421 } 422 sqlite3_free_table(result); 423 sqlite3_free(sql); 424 sql_exec(db, "INSERT into OBJECTS" 425 " (OBJECT_ID, PARENT_ID, REF_ID, DETAIL_ID, CLASS, NAME) " 426 "VALUES" 427 " ('%s', '%s', %Q, '%lld', '%s', '%q')", 428 id_buf, parent_buf, refID, detailID, class, rindex(dir, '/')+1); 429 if( rindex(id_buf, '$') ) 430 *rindex(id_buf, '$') = '\0'; 431 if( rindex(parent_buf, '$') ) 432 *rindex(parent_buf, '$') = '\0'; 433 if( rindex(refID, '$') ) 434 *rindex(refID, '$') = '\0'; 435 dir = dirname(dir); 436 } 437 free(refID); 438 free(parent_buf); 439 free(id_buf); 440 free(dir); 441 return 1; 442 } 443 444 detailID = GetFolderMetadata(name, path, NULL, NULL, NULL); 445 sql_exec(db, "INSERT into OBJECTS" 446 " (OBJECT_ID, PARENT_ID, REF_ID, DETAIL_ID, CLASS, NAME) " 447 "VALUES" 448 " ('%s%s$%X', '%s%s', %Q, '%lld', '%s', '%q')", 449 base, parentID, objectID, base, parentID, refID, detailID, class, name); 450 if( refID ) 451 free(refID); 452 453 return -1; 454} 455 456int 457insert_file(char * name, const char * path, const char * parentID, int object) 458{ 459 char class[32]; 460 char objectID[64]; 461 unsigned long int detailID = 0; 462 char base[8]; 463 char * typedir_parentID; 464 int typedir_objectID; 465 char * baseid; 466 char * orig_name = NULL; 467 468 if( is_image(name) ) 469 { 470 strcpy(base, IMAGE_DIR_ID); 471 strcpy(class, "item.imageItem.photo"); 472 detailID = GetImageMetadata(path, name); 473 } 474 else if( is_video(name) ) 475 { 476 orig_name = strdup(name); 477 strcpy(base, VIDEO_DIR_ID); 478 strcpy(class, "item.videoItem"); 479 detailID = GetVideoMetadata(path, name); 480 481 if( !detailID ) 482 strcpy(name, orig_name); 483 } 484 else if( is_playlist(name) ) 485 { 486 if( insert_playlist(path, name) == 0 ) 487 return 1; 488 } 489 if( !detailID && is_audio(name) ) 490 { 491 strcpy(base, MUSIC_DIR_ID); 492 strcpy(class, "item.audioItem.musicTrack"); 493 detailID = GetAudioMetadata(path, name); 494 } 495 if( orig_name ) 496 free(orig_name); 497 if( !detailID ) 498 { 499 DPRINTF(E_WARN, L_SCANNER, "Unsuccessful getting details for %s!\n", path); 500 return -1; 501 } 502 503 sprintf(objectID, "%s%s$%X", BROWSEDIR_ID, parentID, object); 504 505 506 sql_exec(db, "INSERT into OBJECTS" 507 " (OBJECT_ID, PARENT_ID, CLASS, DETAIL_ID, NAME) " 508 "VALUES" 509 " ('%s', '%s%s', '%s', %lu, '%q')", 510 objectID, BROWSEDIR_ID, parentID, class, detailID, name); 511 512 513 514 if( *parentID ) 515 { 516 typedir_objectID = 0; 517 typedir_parentID = strdup(parentID); 518 baseid = rindex(typedir_parentID, '$'); 519 if( baseid ) 520 { 521 sscanf(baseid+1, "%X", &typedir_objectID); 522 *baseid = '\0'; 523 } 524 insert_directory(name, path, base, typedir_parentID, typedir_objectID); 525 free(typedir_parentID); 526 } 527 528 529 sql_exec(db, "INSERT into OBJECTS" 530 " (OBJECT_ID, PARENT_ID, REF_ID, CLASS, DETAIL_ID, NAME) " 531 "VALUES" 532 " ('%s%s$%X', '%s%s', '%s', '%s', %lu, '%q')", 533 base, parentID, object, base, parentID, objectID, class, detailID, name); 534 535 insert_containers(name, path, objectID, class, detailID); 536 return 0; 537} 538 539int 540CreateDatabase(void) 541{ 542 int ret, i; 543 const char * containers[] = { "0","-1", "root", 544 "1", "0", "Music", 545 "1$4", "1", "All Music", 546 "1$5", "1", "Genre", 547 "1$6", "1", "Artist", 548 "1$7", "1", "Album", 549 MUSIC_DIR_ID, "1", "Folders", 550 MUSIC_PLIST_ID, "1", "Playlists", 551 "2", "0", "Video", 552 "2$8", "2", "All Video", 553 VIDEO_DIR_ID, "2", "Folders", 554 "3", "0", "Pictures", 555 "3$11", "3", "All Pictures", 556 "3$12", "3", "Date Taken", 557 "3$13", "3", "Camera", 558 IMAGE_DIR_ID, "3", "Folders", 559 "64", "0", "Browse Folders", 560 0 }; 561 562 ret = sql_exec(db, "CREATE TABLE OBJECTS ( " 563 "ID INTEGER PRIMARY KEY AUTOINCREMENT, " 564 "OBJECT_ID TEXT UNIQUE NOT NULL, " 565 "PARENT_ID TEXT NOT NULL, " 566 "REF_ID TEXT DEFAULT NULL, " 567 "CLASS TEXT NOT NULL, " 568 "DETAIL_ID INTEGER DEFAULT NULL, " 569 "NAME TEXT DEFAULT NULL" 570 ");"); 571 if( ret != SQLITE_OK ) 572 goto sql_failed; 573 ret = sql_exec(db, "CREATE TABLE DETAILS ( " 574 "ID INTEGER PRIMARY KEY AUTOINCREMENT, " 575 "PATH TEXT DEFAULT NULL, " 576 "SIZE INTEGER, " 577 "TITLE TEXT COLLATE NOCASE, " 578 "DURATION TEXT, " 579 "BITRATE INTEGER, " 580 "SAMPLERATE INTEGER, " 581 "ARTIST TEXT COLLATE NOCASE, " 582 "ALBUM TEXT COLLATE NOCASE, " 583 "GENRE TEXT COLLATE NOCASE, " 584 "COMMENT TEXT, " 585 "CHANNELS INTEGER, " 586 "TRACK INTEGER, " 587 "DATE DATE, " 588 "RESOLUTION TEXT, " 589 "THUMBNAIL BOOL DEFAULT 0, " 590 "CREATOR TEXT COLLATE NOCASE, " 591 "DLNA_PN TEXT, " 592 "MIME TEXT, " 593 "ALBUM_ART INTEGER DEFAULT 0, " 594 "DISC INTEGER, " 595 "TIMESTAMP INTEGER" 596 ")"); 597 if( ret != SQLITE_OK ) 598 goto sql_failed; 599 ret = sql_exec(db, "CREATE TABLE ALBUM_ART ( " 600 "ID INTEGER PRIMARY KEY AUTOINCREMENT, " 601 "PATH TEXT NOT NULL" 602 ")"); 603 if( ret != SQLITE_OK ) 604 goto sql_failed; 605 ret = sql_exec(db, "CREATE TABLE CAPTIONS (" 606 "ID INTEGER PRIMARY KEY, " 607 "PATH TEXT NOT NULL" 608 ")"); 609 if( ret != SQLITE_OK ) 610 goto sql_failed; 611 ret = sql_exec(db, "CREATE TABLE PLAYLISTS (" 612 "ID INTEGER PRIMARY KEY AUTOINCREMENT, " 613 "NAME TEXT NOT NULL, " 614 "PATH TEXT NOT NULL, " 615 "ITEMS INTEGER DEFAULT 0, " 616 "FOUND INTEGER DEFAULT 0" 617 ")"); 618 if( ret != SQLITE_OK ) 619 goto sql_failed; 620 ret = sql_exec(db, "CREATE TABLE SETTINGS (" 621 "UPDATE_ID INTEGER PRIMARY KEY DEFAULT 0, " 622 "FLAGS INTEGER DEFAULT 0" 623 ")"); 624 if( ret != SQLITE_OK ) 625 goto sql_failed; 626 ret = sql_exec(db, "INSERT into SETTINGS values (0, 0)"); 627 if( ret != SQLITE_OK ) 628 goto sql_failed; 629 for( i=0; containers[i]; i=i+3 ) 630 { 631 ret = sql_exec(db, "INSERT into OBJECTS (OBJECT_ID, PARENT_ID, DETAIL_ID, CLASS, NAME)" 632 " values " 633 "('%s', '%s', %lld, 'container.storageFolder', '%s')", 634 containers[i], containers[i+1], GetFolderMetadata(containers[i+2], NULL, NULL, NULL, NULL), containers[i+2]); 635 if( ret != SQLITE_OK ) 636 goto sql_failed; 637 } 638 sql_exec(db, "create INDEX IDX_OBJECTS_OBJECT_ID ON OBJECTS(OBJECT_ID);"); 639 sql_exec(db, "create INDEX IDX_OBJECTS_PARENT_ID ON OBJECTS(PARENT_ID);"); 640 sql_exec(db, "create INDEX IDX_OBJECTS_DETAIL_ID ON OBJECTS(DETAIL_ID);"); 641 sql_exec(db, "create INDEX IDX_OBJECTS_CLASS ON OBJECTS(CLASS);"); 642 sql_exec(db, "create INDEX IDX_DETAILS_PATH ON DETAILS(PATH);"); 643 sql_exec(db, "create INDEX IDX_DETAILS_ID ON DETAILS(ID);"); 644 sql_exec(db, "create INDEX IDX_ALBUM_ART ON ALBUM_ART(ID);"); 645 sql_exec(db, "create INDEX IDX_SCANNER_OPT ON OBJECTS(PARENT_ID, NAME, OBJECT_ID);"); 646 647sql_failed: 648 if( ret != SQLITE_OK ) 649 fprintf(stderr, "Error creating SQLite3 database!\n"); 650 return (ret != SQLITE_OK); 651} 652 653int 654filter_audio(const struct dirent *d) 655{ 656 return ( (*d->d_name != '.') && 657 ((d->d_type == DT_DIR) || 658 (d->d_type == DT_LNK) || 659 (d->d_type == DT_UNKNOWN) || 660 ((d->d_type == DT_REG) && 661 (is_audio(d->d_name) || 662 is_playlist(d->d_name) 663 ) 664 ) )); 665} 666 667int 668filter_video(const struct dirent *d) 669{ 670 return ( (*d->d_name != '.') && 671 ((d->d_type == DT_DIR) || 672 (d->d_type == DT_LNK) || 673 (d->d_type == DT_UNKNOWN) || 674 ((d->d_type == DT_REG) && 675 is_video(d->d_name) ) 676 ) ); 677} 678 679int 680filter_images(const struct dirent *d) 681{ 682 return ( (*d->d_name != '.') && 683 ((d->d_type == DT_DIR) || 684 (d->d_type == DT_LNK) || 685 (d->d_type == DT_UNKNOWN) || 686 ((d->d_type == DT_REG) && 687 is_image(d->d_name) ) 688 ) ); 689} 690 691int 692filter_media(const struct dirent *d) 693{ 694 return ( (*d->d_name != '.') && 695 ((d->d_type == DT_DIR) || 696 (d->d_type == DT_LNK) || 697 (d->d_type == DT_UNKNOWN) || 698 ((d->d_type == DT_REG) && 699 (is_image(d->d_name) || 700 is_audio(d->d_name) || 701 is_video(d->d_name) || 702 is_playlist(d->d_name) 703 ) 704 ) )); 705} 706 707void 708ScanDirectory(const char * dir, const char * parent, enum media_types dir_type) 709{ 710 struct dirent **namelist; 711 int i, n, startID=0; 712 char parent_id[PATH_MAX]; 713 char full_path[PATH_MAX]; 714 char * name = NULL; 715 static long long unsigned int fileno = 0; 716 enum file_types type; 717 718 setlocale(LC_COLLATE, ""); 719 if( chdir(dir) != 0 ) 720 return; 721 722 DPRINTF(parent?E_INFO:E_WARN, L_SCANNER, "Scanning %s\n", dir); 723 switch( dir_type ) 724 { 725 case ALL_MEDIA: 726 n = scandir(".", &namelist, filter_media, alphasort); 727 break; 728 case AUDIO_ONLY: 729 n = scandir(".", &namelist, filter_audio, alphasort); 730 break; 731 case VIDEO_ONLY: 732 n = scandir(".", &namelist, filter_video, alphasort); 733 break; 734 case IMAGES_ONLY: 735 n = scandir(".", &namelist, filter_images, alphasort); 736 break; 737 default: 738 n = -1; 739 break; 740 } 741 742 printf(" Number of files:%d\n", n); 743 744 if (n < 0) { 745 fprintf(stderr, "Error scanning %s [scandir]\n", dir); 746 return; 747 } 748 749 if( !parent ) 750 { 751 startID = get_next_available_id("OBJECTS", BROWSEDIR_ID); 752 } 753 754 for (i=0; i < n; i++) 755 { 756 type = TYPE_UNKNOWN; 757 sprintf(full_path, "%s/%s", dir, namelist[i]->d_name); 758 name = escape_tag(namelist[i]->d_name); 759 if( namelist[i]->d_type == DT_DIR ) 760 { 761 type = TYPE_DIR; 762 } 763 else if( namelist[i]->d_type == DT_REG ) 764 { 765 type = TYPE_FILE; 766 } 767 else 768 { 769 type = resolve_unknown_type(full_path, dir_type); 770 } 771 772 printf("%d: %s.\n", i, full_path); 773 774 if( type == TYPE_DIR ) 775 { 776 printf("insert dir.\n"); 777 insert_directory(name?name:namelist[i]->d_name, full_path, BROWSEDIR_ID, (parent ? parent:""), i+startID); 778 sprintf(parent_id, "%s$%X", (parent ? parent:""), i+startID); 779 ScanDirectory(full_path, parent_id, dir_type); 780 } 781 else if( type == TYPE_FILE ) 782 { 783 printf("insert file.\n"); 784 if( insert_file(name?name:namelist[i]->d_name, full_path, (parent ? parent:""), i+startID) == 0 ) 785 fileno++; 786 } 787 if( name ) 788 free(name); 789 free(namelist[i]); 790 } 791 free(namelist); 792 if( parent ) 793 { 794 chdir(dirname((char*)dir)); 795 } 796 else 797 { 798 DPRINTF(E_WARN, L_SCANNER, "Scanning %s finished (%llu files)!\n", dir, fileno); 799 } 800} 801 802void 803start_scanner() 804{ 805 struct media_dir_s * media_path = media_dirs; 806 807 printf("scanner starts.\n"); 808 809 if (setpriority(PRIO_PROCESS, 0, 15) == -1) 810 DPRINTF(E_WARN, L_INOTIFY, "Failed to reduce scanner thread priority\n"); 811 812#ifdef READYNAS 813 FILE * flag = fopen("/ramfs/.upnp-av_scan", "w"); 814 if( flag ) 815 fclose(flag); 816#endif 817 freopen("/dev/null", "a", stderr); 818 819 while( media_path ) 820 { 821 printf("Scan %s.\n", media_path->path); 822 ScanDirectory(media_path->path, NULL, media_path->type); 823 media_path = media_path->next; 824 } 825 826 printf("Direcotry Scanned.\n"); 827 828 freopen("/proc/self/fd/2", "a", stderr); 829#ifdef READYNAS 830 if( access("/ramfs/.rescan_done", F_OK) == 0 ) 831 system("/bin/sh /ramfs/.rescan_done"); 832 unlink("/ramfs/.upnp-av_scan"); 833#endif 834 /* Create this index after scanning, so it doesn't slow down the scanning process. 835 * This index is very useful for large libraries used with an XBox360 (or any 836 * client that uses UPnPSearch on large containers). */ 837 sql_exec(db, "create INDEX IDX_SEARCH_OPT ON OBJECTS(OBJECT_ID, CLASS, DETAIL_ID);"); 838 839 fill_playlists(); 840 841 //JM: Set up a db version number, so we know if we need to rebuild due to a new structure. 842 sql_exec(db, "pragma user_version = %d;", DB_VERSION); 843} 844