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