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/param.h> 27#include <sys/stat.h> 28#include <sys/time.h> 29#include <sys/resource.h> 30 31#include "config.h" 32 33#ifdef ENABLE_NLS 34#include <libintl.h> 35#endif 36#include <sqlite3.h> 37#include "libav.h" 38 39#include "scanner_sqlite.h" 40#include "upnpglobalvars.h" 41#include "metadata.h" 42#include "playlist.h" 43#include "utils.h" 44#include "sql.h" 45#include "scanner.h" 46#include "albumart.h" 47#include "containers.h" 48#include "log.h" 49#include "inotify.h" 50 51#if SCANDIR_CONST 52typedef const struct dirent scan_filter; 53#else 54typedef struct dirent scan_filter; 55#endif 56#ifndef AV_LOG_PANIC 57#define AV_LOG_PANIC AV_LOG_FATAL 58#endif 59 60int valid_cache = 0; 61 62struct virtual_item 63{ 64 int64_t objectID; 65 char parentID[64]; 66 char name[256]; 67}; 68 69int64_t 70get_next_available_id(const char *table, const char *parentID) 71{ 72 char *ret, *base; 73 int64_t objectID = 0; 74 75 ret = sql_get_text_field(db, "SELECT OBJECT_ID from %s where ID = " 76 "(SELECT max(ID) from %s where PARENT_ID = '%s')", 77 table, table, parentID); 78 if( ret ) 79 { 80 base = strrchr(ret, '$'); 81 if( base ) 82 objectID = strtoll(base+1, NULL, 16) + 1; 83 sqlite3_free(ret); 84 } 85 86 return objectID; 87} 88 89int 90insert_container(const char *item, const char *rootParent, const char *refID, const char *class, 91 const char *artist, const char *genre, const char *album_art, int64_t *objectID, int64_t *parentID) 92{ 93 char *result; 94 char *base; 95 int ret = 0; 96 97 result = sql_get_text_field(db, "SELECT OBJECT_ID from OBJECTS o " 98 "left join DETAILS d on (o.DETAIL_ID = d.ID)" 99 " where o.PARENT_ID = '%s'" 100 " and o.NAME like '%q'" 101 " and d.ARTIST %s %Q" 102 " and o.CLASS = 'container.%s' limit 1", 103 rootParent, item, artist?"like":"is", artist, class); 104 if( result ) 105 { 106 base = strrchr(result, '$'); 107 if( base ) 108 *parentID = strtoll(base+1, NULL, 16); 109 else 110 *parentID = 0; 111 *objectID = get_next_available_id("OBJECTS", result); 112 } 113 else 114 { 115 int64_t detailID = 0; 116 *objectID = 0; 117 *parentID = get_next_available_id("OBJECTS", rootParent); 118 if( refID ) 119 { 120 result = sql_get_text_field(db, "SELECT DETAIL_ID from OBJECTS where OBJECT_ID = %Q", refID); 121 if( result ) 122 detailID = strtoll(result, NULL, 10); 123 } 124 if( !detailID ) 125 { 126 detailID = GetFolderMetadata(item, NULL, artist, genre, (album_art ? strtoll(album_art, NULL, 10) : 0)); 127 } 128 ret = sql_exec(db, "INSERT into OBJECTS" 129 " (OBJECT_ID, PARENT_ID, REF_ID, DETAIL_ID, CLASS, NAME) " 130 "VALUES" 131 " ('%s$%llX', '%s', %Q, %lld, 'container.%s', '%q')", 132 rootParent, (long long)*parentID, rootParent, 133 refID, (long long)detailID, class, item); 134 } 135 sqlite3_free(result); 136 137 return ret; 138} 139 140static void 141insert_containers(const char *name, const char *path, const char *refID, const char *class, int64_t detailID) 142{ 143 char sql[128]; 144 char **result; 145 int ret; 146 int cols, row; 147 int64_t objectID, parentID; 148 149 if( strstr(class, "imageItem") ) 150 { 151 char *date_taken = NULL, *camera = NULL; 152 static struct virtual_item last_date; 153 static struct virtual_item last_cam; 154 static struct virtual_item last_camdate; 155 static long long last_all_objectID = 0; 156 157 snprintf(sql, sizeof(sql), "SELECT DATE, CREATOR from DETAILS where ID = %lld", (long long)detailID); 158 ret = sql_get_table(db, sql, &result, &row, &cols); 159 if( ret == SQLITE_OK ) 160 { 161 date_taken = result[2]; 162 camera = result[3]; 163 } 164 if( date_taken ) 165 date_taken[10] = '\0'; 166 else 167 date_taken = _("Unknown Date"); 168 if( !camera ) 169 camera = _("Unknown Camera"); 170 171 if( valid_cache && strcmp(last_date.name, date_taken) == 0 ) 172 { 173 last_date.objectID++; 174 //DEBUG DPRINTF(E_DEBUG, L_SCANNER, "Using last date item: %s/%s/%X\n", last_date.name, last_date.parentID, last_date.objectID); 175 } 176 else 177 { 178 insert_container(date_taken, IMAGE_DATE_ID, NULL, "album.photoAlbum", NULL, NULL, NULL, &objectID, &parentID); 179 sprintf(last_date.parentID, IMAGE_DATE_ID"$%llX", (unsigned long long)parentID); 180 last_date.objectID = objectID; 181 strncpyt(last_date.name, date_taken, sizeof(last_date.name)); 182 //DEBUG DPRINTF(E_DEBUG, L_SCANNER, "Creating cached date item: %s/%s/%X\n", last_date.name, last_date.parentID, last_date.objectID); 183 } 184 sql_exec(db, "INSERT into OBJECTS" 185 " (OBJECT_ID, PARENT_ID, REF_ID, CLASS, DETAIL_ID, NAME) " 186 "VALUES" 187 " ('%s$%llX', '%s', '%s', '%s', %lld, %Q)", 188 last_date.parentID, (long long)last_date.objectID, last_date.parentID, refID, class, (long long)detailID, name); 189 190 if( !valid_cache || strcmp(camera, last_cam.name) != 0 ) 191 { 192 insert_container(camera, IMAGE_CAMERA_ID, NULL, "storageFolder", NULL, NULL, NULL, &objectID, &parentID); 193 sprintf(last_cam.parentID, IMAGE_CAMERA_ID"$%llX", (long long)parentID); 194 strncpyt(last_cam.name, camera, sizeof(last_cam.name)); 195 /* Invalidate last_camdate cache */ 196 last_camdate.name[0] = '\0'; 197 } 198 if( valid_cache && strcmp(last_camdate.name, date_taken) == 0 ) 199 { 200 last_camdate.objectID++; 201 //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); 202 } 203 else 204 { 205 insert_container(date_taken, last_cam.parentID, NULL, "album.photoAlbum", NULL, NULL, NULL, &objectID, &parentID); 206 sprintf(last_camdate.parentID, "%s$%llX", last_cam.parentID, (long long)parentID); 207 last_camdate.objectID = objectID; 208 strncpyt(last_camdate.name, date_taken, sizeof(last_camdate.name)); 209 //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); 210 } 211 sql_exec(db, "INSERT into OBJECTS" 212 " (OBJECT_ID, PARENT_ID, REF_ID, CLASS, DETAIL_ID, NAME) " 213 "VALUES" 214 " ('%s$%llX', '%s', '%s', '%s', %lld, %Q)", 215 last_camdate.parentID, last_camdate.objectID, last_camdate.parentID, refID, class, (long long)detailID, name); 216 /* All Images */ 217 if( !last_all_objectID ) 218 { 219 last_all_objectID = get_next_available_id("OBJECTS", IMAGE_ALL_ID); 220 } 221 sql_exec(db, "INSERT into OBJECTS" 222 " (OBJECT_ID, PARENT_ID, REF_ID, CLASS, DETAIL_ID, NAME) " 223 "VALUES" 224 " ('"IMAGE_ALL_ID"$%llX', '"IMAGE_ALL_ID"', '%s', '%s', %lld, %Q)", 225 last_all_objectID++, refID, class, (long long)detailID, name); 226 } 227 else if( strstr(class, "audioItem") ) 228 { 229 snprintf(sql, sizeof(sql), "SELECT ALBUM, ARTIST, GENRE, ALBUM_ART from DETAILS where ID = %lld", (long long)detailID); 230 ret = sql_get_table(db, sql, &result, &row, &cols); 231 if( ret != SQLITE_OK ) 232 return; 233 if( !row ) 234 { 235 sqlite3_free_table(result); 236 return; 237 } 238 char *album = result[4], *artist = result[5], *genre = result[6]; 239 char *album_art = result[7]; 240 static struct virtual_item last_album; 241 static struct virtual_item last_artist; 242 static struct virtual_item last_artistAlbum; 243 static struct virtual_item last_artistAlbumAll; 244 static struct virtual_item last_genre; 245 static struct virtual_item last_genreArtist; 246 static struct virtual_item last_genreArtistAll; 247 static long long last_all_objectID = 0; 248 249 if( album ) 250 { 251 if( valid_cache && strcmp(album, last_album.name) == 0 ) 252 { 253 last_album.objectID++; 254 //DEBUG DPRINTF(E_DEBUG, L_SCANNER, "Using last album item: %s/%s/%X\n", last_album.name, last_album.parentID, last_album.objectID); 255 } 256 else 257 { 258 strncpyt(last_album.name, album, sizeof(last_album.name)); 259 insert_container(album, MUSIC_ALBUM_ID, NULL, "album.musicAlbum", artist, genre, album_art, &objectID, &parentID); 260 sprintf(last_album.parentID, MUSIC_ALBUM_ID"$%llX", (long long)parentID); 261 last_album.objectID = objectID; 262 //DEBUG DPRINTF(E_DEBUG, L_SCANNER, "Creating cached album item: %s/%s/%X\n", last_album.name, last_album.parentID, last_album.objectID); 263 } 264 sql_exec(db, "INSERT into OBJECTS" 265 " (OBJECT_ID, PARENT_ID, REF_ID, CLASS, DETAIL_ID, NAME) " 266 "VALUES" 267 " ('%s$%llX', '%s', '%s', '%s', %lld, %Q)", 268 last_album.parentID, last_album.objectID, last_album.parentID, refID, class, (long long)detailID, name); 269 } 270 if( artist ) 271 { 272 if( !valid_cache || strcmp(artist, last_artist.name) != 0 ) 273 { 274 insert_container(artist, MUSIC_ARTIST_ID, NULL, "person.musicArtist", NULL, genre, NULL, &objectID, &parentID); 275 sprintf(last_artist.parentID, MUSIC_ARTIST_ID"$%llX", (long long)parentID); 276 strncpyt(last_artist.name, artist, sizeof(last_artist.name)); 277 last_artistAlbum.name[0] = '\0'; 278 /* Add this file to the "- All Albums -" container as well */ 279 insert_container(_("- All Albums -"), last_artist.parentID, NULL, "album", artist, genre, NULL, &objectID, &parentID); 280 sprintf(last_artistAlbumAll.parentID, "%s$%llX", last_artist.parentID, (long long)parentID); 281 last_artistAlbumAll.objectID = objectID; 282 } 283 else 284 { 285 last_artistAlbumAll.objectID++; 286 } 287 if( valid_cache && strcmp(album?album:_("Unknown Album"), last_artistAlbum.name) == 0 ) 288 { 289 last_artistAlbum.objectID++; 290 //DEBUG DPRINTF(E_DEBUG, L_SCANNER, "Using last artist/album item: %s/%s/%X\n", last_artist.name, last_artist.parentID, last_artist.objectID); 291 } 292 else 293 { 294 insert_container(album?album:_("Unknown Album"), last_artist.parentID, album?last_album.parentID:NULL, 295 "album.musicAlbum", artist, genre, album_art, &objectID, &parentID); 296 sprintf(last_artistAlbum.parentID, "%s$%llX", last_artist.parentID, (long long)parentID); 297 last_artistAlbum.objectID = objectID; 298 strncpyt(last_artistAlbum.name, album ? album : _("Unknown Album"), sizeof(last_artistAlbum.name)); 299 //DEBUG DPRINTF(E_DEBUG, L_SCANNER, "Creating cached artist/album item: %s/%s/%X\n", last_artist.name, last_artist.parentID, last_artist.objectID); 300 } 301 sql_exec(db, "INSERT into OBJECTS" 302 " (OBJECT_ID, PARENT_ID, REF_ID, CLASS, DETAIL_ID, NAME) " 303 "VALUES" 304 " ('%s$%llX', '%s', '%s', '%s', %lld, %Q)", 305 last_artistAlbum.parentID, last_artistAlbum.objectID, last_artistAlbum.parentID, refID, class, (long long)detailID, name); 306 sql_exec(db, "INSERT into OBJECTS" 307 " (OBJECT_ID, PARENT_ID, REF_ID, CLASS, DETAIL_ID, NAME) " 308 "VALUES" 309 " ('%s$%llX', '%s', '%s', '%s', %lld, %Q)", 310 last_artistAlbumAll.parentID, last_artistAlbumAll.objectID, last_artistAlbumAll.parentID, refID, class, (long long)detailID, name); 311 } 312 if( genre ) 313 { 314 if( !valid_cache || strcmp(genre, last_genre.name) != 0 ) 315 { 316 insert_container(genre, MUSIC_GENRE_ID, NULL, "genre.musicGenre", NULL, NULL, NULL, &objectID, &parentID); 317 sprintf(last_genre.parentID, MUSIC_GENRE_ID"$%llX", (long long)parentID); 318 strncpyt(last_genre.name, genre, sizeof(last_genre.name)); 319 /* Add this file to the "- All Artists -" container as well */ 320 insert_container(_("- All Artists -"), last_genre.parentID, NULL, "person", NULL, genre, NULL, &objectID, &parentID); 321 sprintf(last_genreArtistAll.parentID, "%s$%llX", last_genre.parentID, (long long)parentID); 322 last_genreArtistAll.objectID = objectID; 323 } 324 else 325 { 326 last_genreArtistAll.objectID++; 327 } 328 if( valid_cache && strcmp(artist?artist:_("Unknown Artist"), last_genreArtist.name) == 0 ) 329 { 330 last_genreArtist.objectID++; 331 } 332 else 333 { 334 insert_container(artist?artist:_("Unknown Artist"), last_genre.parentID, artist?last_artist.parentID:NULL, 335 "person.musicArtist", NULL, genre, NULL, &objectID, &parentID); 336 sprintf(last_genreArtist.parentID, "%s$%llX", last_genre.parentID, (long long)parentID); 337 last_genreArtist.objectID = objectID; 338 strncpyt(last_genreArtist.name, artist ? artist : _("Unknown Artist"), sizeof(last_genreArtist.name)); 339 //DEBUG DPRINTF(E_DEBUG, L_SCANNER, "Creating cached genre/artist item: %s/%s/%X\n", last_genreArtist.name, last_genreArtist.parentID, last_genreArtist.objectID); 340 } 341 sql_exec(db, "INSERT into OBJECTS" 342 " (OBJECT_ID, PARENT_ID, REF_ID, CLASS, DETAIL_ID, NAME) " 343 "VALUES" 344 " ('%s$%llX', '%s', '%s', '%s', %lld, %Q)", 345 last_genreArtist.parentID, last_genreArtist.objectID, last_genreArtist.parentID, refID, class, (long long)detailID, name); 346 sql_exec(db, "INSERT into OBJECTS" 347 " (OBJECT_ID, PARENT_ID, REF_ID, CLASS, DETAIL_ID, NAME) " 348 "VALUES" 349 " ('%s$%llX', '%s', '%s', '%s', %lld, %Q)", 350 last_genreArtistAll.parentID, last_genreArtistAll.objectID, last_genreArtistAll.parentID, refID, class, (long long)detailID, name); 351 } 352 /* All Music */ 353 if( !last_all_objectID ) 354 { 355 last_all_objectID = get_next_available_id("OBJECTS", MUSIC_ALL_ID); 356 } 357 sql_exec(db, "INSERT into OBJECTS" 358 " (OBJECT_ID, PARENT_ID, REF_ID, CLASS, DETAIL_ID, NAME) " 359 "VALUES" 360 " ('"MUSIC_ALL_ID"$%llX', '"MUSIC_ALL_ID"', '%s', '%s', %lld, %Q)", 361 last_all_objectID++, refID, class, (long long)detailID, name); 362 } 363 else if( strstr(class, "videoItem") ) 364 { 365 static long long last_all_objectID = 0; 366 367 /* All Videos */ 368 if( !last_all_objectID ) 369 { 370 last_all_objectID = get_next_available_id("OBJECTS", VIDEO_ALL_ID); 371 } 372 sql_exec(db, "INSERT into OBJECTS" 373 " (OBJECT_ID, PARENT_ID, REF_ID, CLASS, DETAIL_ID, NAME) " 374 "VALUES" 375 " ('"VIDEO_ALL_ID"$%llX', '"VIDEO_ALL_ID"', '%s', '%s', %lld, %Q)", 376 last_all_objectID++, refID, class, (long long)detailID, name); 377 return; 378 } 379 else 380 { 381 return; 382 } 383 sqlite3_free_table(result); 384 valid_cache = 1; 385} 386 387int64_t 388insert_directory(const char *name, const char *path, const char *base, const char *parentID, int objectID) 389{ 390 int64_t detailID = 0; 391 char class[] = "container.storageFolder"; 392 char *result, *p; 393 static char last_found[256] = "-1"; 394 395 if( strcmp(base, BROWSEDIR_ID) != 0 ) 396 { 397 int found = 0; 398 char id_buf[64], parent_buf[64], refID[64]; 399 char *dir_buf, *dir; 400 401 dir_buf = strdup(path); 402 dir = dirname(dir_buf); 403 snprintf(refID, sizeof(refID), "%s%s$%X", BROWSEDIR_ID, parentID, objectID); 404 snprintf(id_buf, sizeof(id_buf), "%s%s$%X", base, parentID, objectID); 405 snprintf(parent_buf, sizeof(parent_buf), "%s%s", base, parentID); 406 while( !found ) 407 { 408 if( valid_cache && strcmp(id_buf, last_found) == 0 ) 409 break; 410 if( sql_get_int_field(db, "SELECT count(*) from OBJECTS where OBJECT_ID = '%s'", id_buf) > 0 ) 411 { 412 strcpy(last_found, id_buf); 413 break; 414 } 415 /* Does not exist. Need to create, and may need to create parents also */ 416 result = sql_get_text_field(db, "SELECT DETAIL_ID from OBJECTS where OBJECT_ID = '%s'", refID); 417 if( result ) 418 { 419 detailID = strtoll(result, NULL, 10); 420 sqlite3_free(result); 421 } 422 sql_exec(db, "INSERT into OBJECTS" 423 " (OBJECT_ID, PARENT_ID, REF_ID, DETAIL_ID, CLASS, NAME) " 424 "VALUES" 425 " ('%s', '%s', %Q, %lld, '%s', '%q')", 426 id_buf, parent_buf, refID, detailID, class, strrchr(dir, '/')+1); 427 if( (p = strrchr(id_buf, '$')) ) 428 *p = '\0'; 429 if( (p = strrchr(parent_buf, '$')) ) 430 *p = '\0'; 431 if( (p = strrchr(refID, '$')) ) 432 *p = '\0'; 433 dir = dirname(dir); 434 } 435 free(dir_buf); 436 return 0; 437 } 438 439 detailID = GetFolderMetadata(name, path, NULL, NULL, find_album_art(path, NULL, 0)); 440 sql_exec(db, "INSERT into OBJECTS" 441 " (OBJECT_ID, PARENT_ID, DETAIL_ID, CLASS, NAME) " 442 "VALUES" 443 " ('%s%s$%X', '%s%s', %lld, '%s', '%q')", 444 base, parentID, objectID, base, parentID, detailID, class, name); 445 446 return detailID; 447} 448 449int 450insert_file(char *name, const char *path, const char *parentID, int object, media_types types) 451{ 452 char class[32]; 453 char objectID[64]; 454 int64_t detailID = 0; 455 char base[8]; 456 char *typedir_parentID; 457 char *baseid; 458 char *orig_name = NULL; 459 460 if( (types & TYPE_IMAGES) && is_image(name) ) 461 { 462 if( is_album_art(name) ) 463 return -1; 464 strcpy(base, IMAGE_DIR_ID); 465 strcpy(class, "item.imageItem.photo"); 466 detailID = GetImageMetadata(path, name); 467 } 468 else if( (types & TYPE_VIDEO) && is_video(name) ) 469 { 470 orig_name = strdup(name); 471 strcpy(base, VIDEO_DIR_ID); 472 strcpy(class, "item.videoItem"); 473 detailID = GetVideoMetadata(path, name); 474 if( !detailID ) 475 strcpy(name, orig_name); 476 } 477 else if( is_playlist(name) ) 478 { 479 if( insert_playlist(path, name) == 0 ) 480 return 1; 481 } 482 if( !detailID && (types & TYPE_AUDIO) && is_audio(name) ) 483 { 484 strcpy(base, MUSIC_DIR_ID); 485 strcpy(class, "item.audioItem.musicTrack"); 486 detailID = GetAudioMetadata(path, name); 487 } 488 free(orig_name); 489 if( !detailID ) 490 { 491 DPRINTF(E_WARN, L_SCANNER, "Unsuccessful getting details for %s!\n", path); 492 return -1; 493 } 494 495 sprintf(objectID, "%s%s$%X", BROWSEDIR_ID, parentID, object); 496 497 sql_exec(db, "INSERT into OBJECTS" 498 " (OBJECT_ID, PARENT_ID, CLASS, DETAIL_ID, NAME) " 499 "VALUES" 500 " ('%s', '%s%s', '%s', %lld, '%q')", 501 objectID, BROWSEDIR_ID, parentID, class, detailID, name); 502 503 if( *parentID ) 504 { 505 int typedir_objectID = 0; 506 typedir_parentID = strdup(parentID); 507 baseid = strrchr(typedir_parentID, '$'); 508 if( baseid ) 509 { 510 typedir_objectID = strtol(baseid+1, NULL, 16); 511 *baseid = '\0'; 512 } 513 insert_directory(name, path, base, typedir_parentID, typedir_objectID); 514 free(typedir_parentID); 515 } 516 sql_exec(db, "INSERT into OBJECTS" 517 " (OBJECT_ID, PARENT_ID, REF_ID, CLASS, DETAIL_ID, NAME) " 518 "VALUES" 519 " ('%s%s$%X', '%s%s', '%s', '%s', %lld, '%q')", 520 base, parentID, object, base, parentID, objectID, class, detailID, name); 521 522 insert_containers(name, path, objectID, class, detailID); 523 return 0; 524} 525 526int 527CreateDatabase(void) 528{ 529 int ret, i; 530 const char *containers[] = { "0","-1", "root", 531 MUSIC_ID, "0", _("Music"), 532 MUSIC_ALL_ID, MUSIC_ID, _("All Music"), 533 MUSIC_GENRE_ID, MUSIC_ID, _("Genre"), 534 MUSIC_ARTIST_ID, MUSIC_ID, _("Artist"), 535 MUSIC_ALBUM_ID, MUSIC_ID, _("Album"), 536 MUSIC_DIR_ID, MUSIC_ID, _("Folders"), 537 MUSIC_PLIST_ID, MUSIC_ID, _("Playlists"), 538 539 VIDEO_ID, "0", _("Video"), 540 VIDEO_ALL_ID, VIDEO_ID, _("All Video"), 541 VIDEO_DIR_ID, VIDEO_ID, _("Folders"), 542 543 IMAGE_ID, "0", _("Pictures"), 544 IMAGE_ALL_ID, IMAGE_ID, _("All Pictures"), 545 IMAGE_DATE_ID, IMAGE_ID, _("Date Taken"), 546 IMAGE_CAMERA_ID, IMAGE_ID, _("Camera"), 547 IMAGE_DIR_ID, IMAGE_ID, _("Folders"), 548 549 BROWSEDIR_ID, "0", _("Browse Folders"), 550 0 }; 551 552 ret = sql_exec(db, create_objectTable_sqlite); 553 if( ret != SQLITE_OK ) 554 goto sql_failed; 555 ret = sql_exec(db, create_detailTable_sqlite); 556 if( ret != SQLITE_OK ) 557 goto sql_failed; 558 ret = sql_exec(db, create_albumArtTable_sqlite); 559 if( ret != SQLITE_OK ) 560 goto sql_failed; 561 ret = sql_exec(db, create_captionTable_sqlite); 562 if( ret != SQLITE_OK ) 563 goto sql_failed; 564 ret = sql_exec(db, create_bookmarkTable_sqlite); 565 if( ret != SQLITE_OK ) 566 goto sql_failed; 567 ret = sql_exec(db, create_playlistTable_sqlite); 568 if( ret != SQLITE_OK ) 569 goto sql_failed; 570 ret = sql_exec(db, create_settingsTable_sqlite); 571 if( ret != SQLITE_OK ) 572 goto sql_failed; 573 ret = sql_exec(db, "INSERT into SETTINGS values ('UPDATE_ID', '0')"); 574 if( ret != SQLITE_OK ) 575 goto sql_failed; 576 for( i=0; containers[i]; i=i+3 ) 577 { 578 ret = sql_exec(db, "INSERT into OBJECTS (OBJECT_ID, PARENT_ID, DETAIL_ID, CLASS, NAME)" 579 " values " 580 "('%s', '%s', %lld, 'container.storageFolder', '%q')", 581 containers[i], containers[i+1], GetFolderMetadata(containers[i+2], NULL, NULL, NULL, 0), containers[i+2]); 582 if( ret != SQLITE_OK ) 583 goto sql_failed; 584 } 585 for( i=0; magic_containers[i].objectid_match; i++ ) 586 { 587 struct magic_container_s *magic = &magic_containers[i]; 588 if (!magic->name) 589 continue; 590 if( sql_get_int_field(db, "SELECT 1 from OBJECTS where OBJECT_ID = '%s'", magic->objectid_match) == 0 ) 591 { 592 char *parent = strdup(magic->objectid_match); 593 if (strrchr(parent, '$')) 594 *strrchr(parent, '$') = '\0'; 595 ret = sql_exec(db, "INSERT into OBJECTS (OBJECT_ID, PARENT_ID, DETAIL_ID, CLASS, NAME)" 596 " values " 597 "('%s', '%s', %lld, 'container.storageFolder', '%q')", 598 magic->objectid_match, parent, GetFolderMetadata(magic->name, NULL, NULL, NULL, 0), magic->name); 599 free(parent); 600 if( ret != SQLITE_OK ) 601 goto sql_failed; 602 } 603 } 604 sql_exec(db, "create INDEX IDX_OBJECTS_OBJECT_ID ON OBJECTS(OBJECT_ID);"); 605 sql_exec(db, "create INDEX IDX_OBJECTS_PARENT_ID ON OBJECTS(PARENT_ID);"); 606 sql_exec(db, "create INDEX IDX_OBJECTS_DETAIL_ID ON OBJECTS(DETAIL_ID);"); 607 sql_exec(db, "create INDEX IDX_OBJECTS_CLASS ON OBJECTS(CLASS);"); 608 sql_exec(db, "create INDEX IDX_DETAILS_PATH ON DETAILS(PATH);"); 609 sql_exec(db, "create INDEX IDX_DETAILS_ID ON DETAILS(ID);"); 610 sql_exec(db, "create INDEX IDX_ALBUM_ART ON ALBUM_ART(ID);"); 611 sql_exec(db, "create INDEX IDX_SCANNER_OPT ON OBJECTS(PARENT_ID, NAME, OBJECT_ID);"); 612 613sql_failed: 614 if( ret != SQLITE_OK ) 615 DPRINTF(E_ERROR, L_DB_SQL, "Error creating SQLite3 database!\n"); 616 return (ret != SQLITE_OK); 617} 618 619static inline int 620filter_hidden(scan_filter *d) 621{ 622 return (d->d_name[0] != '.'); 623} 624 625static int 626filter_type(scan_filter *d) 627{ 628#if HAVE_STRUCT_DIRENT_D_TYPE 629 return ( (d->d_type == DT_DIR) || 630 (d->d_type == DT_LNK) || 631 (d->d_type == DT_UNKNOWN) 632 ); 633#else 634 return 1; 635#endif 636} 637 638static int 639filter_a(scan_filter *d) 640{ 641 return ( filter_hidden(d) && 642 (filter_type(d) || 643 (is_reg(d) && 644 (is_audio(d->d_name) || 645 is_playlist(d->d_name)))) 646 ); 647} 648 649static int 650filter_av(scan_filter *d) 651{ 652 return ( filter_hidden(d) && 653 (filter_type(d) || 654 (is_reg(d) && 655 (is_audio(d->d_name) || 656 is_video(d->d_name) || 657 is_playlist(d->d_name)))) 658 ); 659} 660 661static int 662filter_ap(scan_filter *d) 663{ 664 return ( filter_hidden(d) && 665 (filter_type(d) || 666 (is_reg(d) && 667 (is_audio(d->d_name) || 668 is_image(d->d_name) || 669 is_playlist(d->d_name)))) 670 ); 671} 672 673static int 674filter_v(scan_filter *d) 675{ 676 return ( filter_hidden(d) && 677 (filter_type(d) || 678 (is_reg(d) && 679 is_video(d->d_name))) 680 ); 681} 682 683static int 684filter_vp(scan_filter *d) 685{ 686 return ( filter_hidden(d) && 687 (filter_type(d) || 688 (is_reg(d) && 689 (is_video(d->d_name) || 690 is_image(d->d_name)))) 691 ); 692} 693 694static int 695filter_p(scan_filter *d) 696{ 697 return ( filter_hidden(d) && 698 (filter_type(d) || 699 (is_reg(d) && 700 is_image(d->d_name))) 701 ); 702} 703 704static int 705filter_avp(scan_filter *d) 706{ 707 return ( filter_hidden(d) && 708 (filter_type(d) || 709 (is_reg(d) && 710 (is_audio(d->d_name) || 711 is_image(d->d_name) || 712 is_video(d->d_name) || 713 is_playlist(d->d_name)))) 714 ); 715} 716 717static int 718path_is_dir(const char *path) 719{ 720 struct stat stat_buf; 721 722 if (!stat(path, &stat_buf)) 723 return S_ISDIR(stat_buf.st_mode); 724 else 725 return 0; 726} 727 728static int 729is_sys_dir(const char *dirname) 730{ 731 char *MS_System_folder[] = {"SYSTEM VOLUME INFORMATION", "RECYCLER", "RECYCLED", "$RECYCLE.BIN", NULL}; 732 char *Linux_System_folder[] = {"lost+found", NULL}; 733 int i; 734 735 for (i = 0; MS_System_folder[i] != NULL; ++i) { 736 if (!strcasecmp(dirname, MS_System_folder[i])) 737 return 1; 738 } 739 740 for (i = 0; Linux_System_folder[i] != NULL; ++i) { 741 if (!strcasecmp(dirname, Linux_System_folder[i])) 742 return 1; 743 } 744 745 return 0; 746} 747 748static void 749ScanDirectory(const char *dir, const char *parent, media_types dir_types) 750{ 751 struct dirent **namelist; 752 int i, n, startID = 0; 753 char *full_path; 754 char *name = NULL; 755 static long long unsigned int fileno = 0; 756 enum file_types type; 757 758 DPRINTF(parent?E_INFO:E_WARN, L_SCANNER, _("Scanning %s\n"), dir); 759 switch( dir_types ) 760 { 761 case ALL_MEDIA: 762 n = scandir(dir, &namelist, filter_avp, alphasort); 763 break; 764 case TYPE_AUDIO: 765 n = scandir(dir, &namelist, filter_a, alphasort); 766 break; 767 case TYPE_AUDIO|TYPE_VIDEO: 768 n = scandir(dir, &namelist, filter_av, alphasort); 769 break; 770 case TYPE_AUDIO|TYPE_IMAGES: 771 n = scandir(dir, &namelist, filter_ap, alphasort); 772 break; 773 case TYPE_VIDEO: 774 n = scandir(dir, &namelist, filter_v, alphasort); 775 break; 776 case TYPE_VIDEO|TYPE_IMAGES: 777 n = scandir(dir, &namelist, filter_vp, alphasort); 778 break; 779 case TYPE_IMAGES: 780 n = scandir(dir, &namelist, filter_p, alphasort); 781 break; 782 default: 783 n = -1; 784 errno = EINVAL; 785 break; 786 } 787 if( n < 0 ) 788 { 789 DPRINTF(E_WARN, L_SCANNER, "Error scanning %s [%s]\n", 790 dir, strerror(errno)); 791 return; 792 } 793 794 full_path = malloc(PATH_MAX); 795 if (!full_path) 796 { 797 DPRINTF(E_ERROR, L_SCANNER, "Memory allocation failed scanning %s\n", dir); 798 return; 799 } 800 801 if( !parent ) 802 { 803 startID = get_next_available_id("OBJECTS", BROWSEDIR_ID); 804 } 805 806 for (i=0; i < n; i++) 807 { 808#if !USE_FORK 809 if( quitting ) 810 break; 811#endif 812 type = TYPE_UNKNOWN; 813 snprintf(full_path, PATH_MAX, "%s/%s", dir, namelist[i]->d_name); 814 name = escape_tag(namelist[i]->d_name, 1); 815 816 if (strstr(full_path,"/Download2/InComplete") || strstr(full_path,"/Download2/Seeds") || strstr(full_path,"/Download2/config") || strstr(full_path,"/Download2/action")) 817 continue; 818 if ((strncmp(name,"asusware",8) == 0))//eric added for have no need to scan asusware folder 819 continue; 820 if ((strncmp(name,".minidlna",9) == 0))//sungmin added for have no need to scan minidlna folder 821 continue; 822 if (path_is_dir(full_path) && is_sys_dir(name)) 823 continue; 824 825 if( is_dir(namelist[i]) == 1 ) 826 { 827 type = TYPE_DIR; 828 } 829 else if( is_reg(namelist[i]) == 1 ) 830 { 831 type = TYPE_FILE; 832 } 833 else 834 { 835 type = resolve_unknown_type(full_path, dir_types); 836 } 837 if( (type == TYPE_DIR) && (access(full_path, R_OK|X_OK) == 0) ) 838 { 839 char *parent_id; 840 insert_directory(name, full_path, BROWSEDIR_ID, THISORNUL(parent), i+startID); 841 xasprintf(&parent_id, "%s$%X", THISORNUL(parent), i+startID); 842 ScanDirectory(full_path, parent_id, dir_types); 843 free(parent_id); 844 } 845 else if( type == TYPE_FILE && (access(full_path, R_OK) == 0) ) 846 { 847 if( insert_file(name, full_path, THISORNUL(parent), i+startID, dir_types) == 0 ) 848 fileno++; 849 } 850 free(name); 851 free(namelist[i]); 852 } 853 free(namelist); 854 free(full_path); 855 if( !parent ) 856 { 857 DPRINTF(E_WARN, L_SCANNER, _("Scanning %s finished (%llu files)!\n"), dir, fileno); 858 } 859} 860 861extern void create_scantag(void); 862extern void remove_scantag(void); 863 864static void 865_notify_start(void) 866{ 867#ifdef READYNAS 868 FILE *flag = fopen("/ramfs/.upnp-av_scan", "w"); 869 if( flag ) 870 fclose(flag); 871#else 872 create_scantag(); 873#endif 874} 875 876static void 877_notify_stop(void) 878{ 879#ifdef READYNAS 880 if( access("/ramfs/.rescan_done", F_OK) == 0 ) 881 system("/bin/sh /ramfs/.rescan_done"); 882 unlink("/ramfs/.upnp-av_scan"); 883#else 884 remove_scantag(); 885#endif 886} 887 888/* rescan functions added by shrimpkin@sourceforge.net */ 889static int 890cb_orphans(void *args, int argc, char **argv, char **azColName) 891{ 892 struct stat file; 893 char *path = argv[0], *mime = argv[1]; 894 895 /* If we can't stat path, remove it */ 896 if (stat(path, &file) != 0) 897 { 898 DPRINTF(E_DEBUG, L_SCANNER, "Removing %s [%s]!\n", path, (mime) ? "file" : "dir"); 899 if (mime) 900 { 901 inotify_remove_file(path); 902 } 903 else 904 { 905 inotify_remove_directory(0, path); 906 } 907 } 908 return 0; 909} 910 911void 912start_rescan() 913{ 914 struct media_dir_s *media_path; 915 char path[MAXPATHLEN], buf[MAXPATHLEN], *esc_name = NULL, *zErrMsg; 916 char *sql_files = "SELECT path, mime FROM details WHERE path NOT NULL AND mime IS NOT NULL;", *sql_dir = "SELECT path, mime FROM details WHERE path NOT NULL AND mime IS NULL;"; 917 int ret; 918 919 DPRINTF(E_INFO, L_SCANNER, "Starting rescan\n"); 920 921 /* Find and remove any dead directory links */ 922 ret = sqlite3_exec(db, sql_dir, cb_orphans, NULL, &zErrMsg); 923 if (ret != SQLITE_OK) 924 { 925 DPRINTF(E_MAXDEBUG, L_SCANNER, "SQL error: %s\nBAD SQL: %s\n", zErrMsg, sql_dir); 926 sqlite3_free(zErrMsg); 927 } 928 929 /* Find and remove any dead file links */ 930 ret = sqlite3_exec(db, sql_files, cb_orphans, NULL, &zErrMsg); 931 if (ret != SQLITE_OK) 932 { 933 DPRINTF(E_MAXDEBUG, L_SCANNER, "SQL error: %s\nBAD SQL: %s\n", zErrMsg, sql_files); 934 sqlite3_free(zErrMsg); 935 } 936 937 /* Rescan media_paths for new and/or modified files */ 938 for (media_path = media_dirs; media_path != NULL; media_path = media_path->next) 939 { 940 strncpyt(path, media_path->path, sizeof(path)); 941 strncpyt(buf, media_path->path, sizeof(buf)); 942 esc_name = escape_tag(basename(buf), 1); 943 inotify_insert_directory(0, esc_name, path); 944 free(esc_name); 945 } 946 DPRINTF(E_INFO, L_SCANNER, "Rescan completed\n"); 947} 948/* end rescan functions */ 949 950void 951start_scanner() 952{ 953 struct media_dir_s *media_path; 954 char path[MAXPATHLEN]; 955 956 if (setpriority(PRIO_PROCESS, 0, 15) == -1) 957 DPRINTF(E_WARN, L_INOTIFY, "Failed to reduce scanner thread priority\n"); 958 959 setlocale(LC_COLLATE, ""); 960 961 av_register_all(); 962 av_log_set_level(AV_LOG_PANIC); 963 if (rescan_db) 964 { 965 start_rescan(); 966 return; 967 } 968 _notify_start(); 969 for( media_path = media_dirs; media_path != NULL; media_path = media_path->next ) 970 { 971 int64_t id; 972 char *bname, *parent = NULL; 973 char buf[8]; 974 strncpyt(path, media_path->path, sizeof(path)); 975 bname = basename(path); 976 /* If there are multiple media locations, add a level to the ContentDirectory */ 977 if( !GETFLAG(MERGE_MEDIA_DIRS_MASK) && media_dirs->next ) 978 { 979 int startID = get_next_available_id("OBJECTS", BROWSEDIR_ID); 980 id = insert_directory(bname, path, BROWSEDIR_ID, "", startID); 981 sprintf(buf, "$%X", startID); 982 parent = buf; 983 } 984 else 985 id = GetFolderMetadata(bname, media_path->path, NULL, NULL, 0); 986 /* Use TIMESTAMP to store the media type */ 987 sql_exec(db, "UPDATE DETAILS set TIMESTAMP = %d where ID = %lld", media_path->types, (long long)id); 988 ScanDirectory(media_path->path, parent, media_path->types); 989 sql_exec(db, "INSERT into SETTINGS values (%Q, %Q)", "media_dir", media_path->path); 990 } 991 _notify_stop(); 992 /* Create this index after scanning, so it doesn't slow down the scanning process. 993 * This index is very useful for large libraries used with an XBox360 (or any 994 * client that uses UPnPSearch on large containers). */ 995 sql_exec(db, "create INDEX IDX_SEARCH_OPT ON OBJECTS(OBJECT_ID, CLASS, DETAIL_ID);"); 996 997 if( GETFLAG(NO_PLAYLIST_MASK) ) 998 { 999 DPRINTF(E_WARN, L_SCANNER, "Playlist creation disabled\n"); 1000 } 1001 else 1002 { 1003 fill_playlists(); 1004 } 1005 1006 DPRINTF(E_DEBUG, L_SCANNER, "Initial file scan completed\n"); 1007 //JM: Set up a db version number, so we know if we need to rebuild due to a new structure. 1008 sql_exec(db, "pragma user_version = %d;", DB_VERSION); 1009} 1010