1/* MiniDLNA media server 2 * Copyright (C) 2008-2010 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 "config.h" 19 20#include <stdio.h> 21#include <string.h> 22#include <stdlib.h> 23#include <errno.h> 24#include <unistd.h> 25#include <dirent.h> 26#include <libgen.h> 27#include <errno.h> 28#include <sys/types.h> 29#include <sys/stat.h> 30#include <sys/time.h> 31#ifdef HAVE_INOTIFY 32#include <sys/resource.h> 33#include <poll.h> 34#ifdef HAVE_SYS_INOTIFY_H 35#include <sys/inotify.h> 36#else 37#include "linux/inotify.h" 38#include "linux/inotify-syscalls.h" 39#endif 40#endif 41#include "libav.h" 42 43#include "upnpglobalvars.h" 44#include "inotify.h" 45#include "utils.h" 46#include "sql.h" 47#include "scanner.h" 48#include "metadata.h" 49#include "albumart.h" 50#include "playlist.h" 51#include "log.h" 52 53static time_t next_pl_fill = 0; 54 55#ifdef HAVE_INOTIFY 56#define EVENT_SIZE ( sizeof (struct inotify_event) ) 57#define BUF_LEN ( 1024 * ( EVENT_SIZE + 16 ) ) 58#define DESIRED_WATCH_LIMIT 65536 59 60#define PATH_BUF_SIZE PATH_MAX 61 62struct watch 63{ 64 int wd; /* watch descriptor */ 65 char *path; /* watched path */ 66 struct watch *next; 67}; 68 69static struct watch *watches; 70static struct watch *lastwatch = NULL; 71 72char *get_path_from_wd(int wd) 73{ 74 struct watch *w = watches; 75 76 while( w != NULL ) 77 { 78 if( w->wd == wd ) 79 return w->path; 80 w = w->next; 81 } 82 83 return NULL; 84} 85 86int 87add_watch(int fd, const char * path) 88{ 89 struct watch *nw; 90 int wd; 91 92 wd = inotify_add_watch(fd, path, IN_CREATE|IN_CLOSE_WRITE|IN_DELETE|IN_MOVE); 93 if( wd < 0 ) 94 { 95 DPRINTF(E_ERROR, L_INOTIFY, "inotify_add_watch(%s) [%s]\n", path, strerror(errno)); 96 return -1; 97 } 98 99 nw = malloc(sizeof(struct watch)); 100 if( nw == NULL ) 101 { 102 DPRINTF(E_ERROR, L_INOTIFY, "malloc() error\n"); 103 return -1; 104 } 105 nw->wd = wd; 106 nw->next = NULL; 107 nw->path = strdup(path); 108 109 if( watches == NULL ) 110 { 111 watches = nw; 112 } 113 114 if( lastwatch != NULL ) 115 { 116 lastwatch->next = nw; 117 } 118 lastwatch = nw; 119 120 return wd; 121} 122 123int 124remove_watch(int fd, const char * path) 125{ 126 struct watch *w; 127 128 for( w = watches; w; w = w->next ) 129 { 130 if( strcmp(path, w->path) == 0 ) 131 return(inotify_rm_watch(fd, w->wd)); 132 } 133 134 return 1; 135} 136 137unsigned int 138next_highest(unsigned int num) 139{ 140 num |= num >> 1; 141 num |= num >> 2; 142 num |= num >> 4; 143 num |= num >> 8; 144 num |= num >> 16; 145 return(++num); 146} 147 148int 149inotify_create_watches(int fd) 150{ 151 FILE * max_watches; 152 unsigned int num_watches = 0, watch_limit; 153 char **result; 154 int i, rows = 0; 155 struct media_dir_s * media_path; 156 157 for( media_path = media_dirs; media_path != NULL; media_path = media_path->next ) 158 { 159 DPRINTF(E_DEBUG, L_INOTIFY, "Add watch to %s\n", media_path->path); 160 add_watch(fd, media_path->path); 161 num_watches++; 162 } 163 sql_get_table(db, "SELECT PATH from DETAILS where MIME is NULL and PATH is not NULL", &result, &rows, NULL); 164 for( i=1; i <= rows; i++ ) 165 { 166 DPRINTF(E_DEBUG, L_INOTIFY, "Add watch to %s\n", result[i]); 167 add_watch(fd, result[i]); 168 num_watches++; 169 } 170 sqlite3_free_table(result); 171 172 max_watches = fopen("/proc/sys/fs/inotify/max_user_watches", "r"); 173 if( max_watches ) 174 { 175 if( fscanf(max_watches, "%10u", &watch_limit) < 1 ) 176 watch_limit = 8192; 177 fclose(max_watches); 178 if( (watch_limit < DESIRED_WATCH_LIMIT) || (watch_limit < (num_watches*4/3)) ) 179 { 180 max_watches = fopen("/proc/sys/fs/inotify/max_user_watches", "w"); 181 if( max_watches ) 182 { 183 if( DESIRED_WATCH_LIMIT >= (num_watches*3/4) ) 184 { 185 fprintf(max_watches, "%u", DESIRED_WATCH_LIMIT); 186 } 187 else if( next_highest(num_watches) >= (num_watches*3/4) ) 188 { 189 fprintf(max_watches, "%u", next_highest(num_watches)); 190 } 191 else 192 { 193 fprintf(max_watches, "%u", next_highest(next_highest(num_watches))); 194 } 195 fclose(max_watches); 196 } 197 else 198 { 199 DPRINTF(E_WARN, L_INOTIFY, "WARNING: Inotify max_user_watches [%u] is low or close to the number of used watches [%u] " 200 "and I do not have permission to increase this limit. Please do so manually by " 201 "writing a higher value into /proc/sys/fs/inotify/max_user_watches.\n", watch_limit, num_watches); 202 } 203 } 204 } 205 else 206 { 207 DPRINTF(E_WARN, L_INOTIFY, "WARNING: Could not read inotify max_user_watches! " 208 "Hopefully it is enough to cover %u current directories plus any new ones added.\n", num_watches); 209 } 210 211 return rows; 212} 213 214int 215inotify_remove_watches(int fd) 216{ 217 struct watch *w = watches; 218 struct watch *last_w; 219 int rm_watches = 0; 220 221 while( w ) 222 { 223 last_w = w; 224 inotify_rm_watch(fd, w->wd); 225 free(w->path); 226 rm_watches++; 227 w = w->next; 228 free(last_w); 229 } 230 231 return rm_watches; 232} 233 234int add_dir_watch(int fd, char * path, char * filename) 235{ 236 DIR *ds; 237 struct dirent *e; 238 char *dir; 239 char buf[PATH_MAX]; 240 int wd; 241 int i = 0; 242 243 if( filename ) 244 { 245 snprintf(buf, sizeof(buf), "%s/%s", path, filename); 246 dir = buf; 247 } 248 else 249 dir = path; 250 251 wd = add_watch(fd, dir); 252 if( wd == -1 ) 253 { 254 DPRINTF(E_ERROR, L_INOTIFY, "add_watch() [%s]\n", strerror(errno)); 255 } 256 else 257 { 258 DPRINTF(E_INFO, L_INOTIFY, "Added watch to %s [%d]\n", dir, wd); 259 } 260 261 ds = opendir(dir); 262 if( ds != NULL ) 263 { 264 while( (e = readdir(ds)) ) 265 { 266 if( strcmp(e->d_name, ".") == 0 || 267 strcmp(e->d_name, "..") == 0 ) 268 continue; 269 if( (e->d_type == DT_DIR) || 270 (e->d_type == DT_UNKNOWN && resolve_unknown_type(dir, NO_MEDIA) == TYPE_DIR) ) 271 i += add_dir_watch(fd, dir, e->d_name); 272 } 273 } 274 else 275 { 276 DPRINTF(E_ERROR, L_INOTIFY, "Opendir error! [%s]\n", strerror(errno)); 277 } 278 closedir(ds); 279 i++; 280 281 return(i); 282} 283#endif 284 285int 286inotify_insert_file(char * name, const char * path) 287{ 288 int len; 289 char * last_dir; 290 char * path_buf; 291 char * base_name; 292 char * base_copy; 293 char * parent_buf = NULL; 294 char * id = NULL; 295 int depth = 1; 296 int ts; 297 media_types types = ALL_MEDIA; 298 struct media_dir_s * media_path = media_dirs; 299 struct stat st; 300 301 /* Is it cover art for another file? */ 302 if( is_image(path) ) 303 update_if_album_art(path); 304 else if( is_caption(path) ) 305 check_for_captions(path, 0); 306 307 /* Check if we're supposed to be scanning for this file type in this directory */ 308 while( media_path ) 309 { 310 if( strncmp(path, media_path->path, strlen(media_path->path)) == 0 ) 311 { 312 types = media_path->types; 313 break; 314 } 315 media_path = media_path->next; 316 } 317 switch( types ) 318 { 319 case ALL_MEDIA: 320 if( !is_image(path) && 321 !is_audio(path) && 322 !is_video(path) && 323 !is_playlist(path) ) 324 return -1; 325 break; 326 case TYPE_AUDIO: 327 if( !is_audio(path) && 328 !is_playlist(path) ) 329 return -1; 330 break; 331 case TYPE_AUDIO|TYPE_VIDEO: 332 if( !is_audio(path) && 333 !is_video(path) && 334 !is_playlist(path) ) 335 return -1; 336 break; 337 case TYPE_AUDIO|TYPE_IMAGES: 338 if( !is_image(path) && 339 !is_audio(path) && 340 !is_playlist(path) ) 341 return -1; 342 break; 343 case TYPE_VIDEO: 344 if( !is_video(path) ) 345 return -1; 346 break; 347 case TYPE_VIDEO|TYPE_IMAGES: 348 if( !is_image(path) && 349 !is_video(path) ) 350 return -1; 351 break; 352 case TYPE_IMAGES: 353 if( !is_image(path) ) 354 return -1; 355 break; 356 default: 357 return -1; 358 break; 359 } 360 361 /* If it's already in the database and hasn't been modified, skip it. */ 362 if( stat(path, &st) != 0 ) 363 return -1; 364 365 ts = sql_get_int_field(db, "SELECT TIMESTAMP from DETAILS where PATH = '%q'", path); 366 if( !ts && is_playlist(path) && (sql_get_int_field(db, "SELECT ID from PLAYLISTS where PATH = '%q'", path) > 0) ) 367 { 368 DPRINTF(E_DEBUG, L_INOTIFY, "Re-reading modified playlist (%s).\n", path); 369 inotify_remove_file(path); 370 next_pl_fill = 1; 371 } 372 else if( !ts ) 373 { 374 DPRINTF(E_DEBUG, L_INOTIFY, "Adding: %s\n", path); 375 } 376 else if( ts != st.st_mtime ) 377 { 378 DPRINTF(E_DEBUG, L_INOTIFY, "%s is %s than the last db entry.\n", path, (ts < st.st_mtime) ? "older" : "newer"); 379 inotify_remove_file(path); 380 } 381 else 382 { 383 if( ts == st.st_mtime ) 384 DPRINTF(E_DEBUG, L_INOTIFY, "%s already exists\n", path); 385 return 0; 386 } 387 388 /* Find the parentID. If it's not found, create all necessary parents. */ 389 len = strlen(path)+1; 390 if( !(path_buf = malloc(len)) || 391 !(last_dir = malloc(len)) || 392 !(base_name = malloc(len)) ) 393 return -1; 394 base_copy = base_name; 395 while( depth ) 396 { 397 depth = 0; 398 strcpy(path_buf, path); 399 parent_buf = dirname(path_buf); 400 401 do 402 { 403 //DEBUG DPRINTF(E_DEBUG, L_INOTIFY, "Checking %s\n", parent_buf); 404 id = sql_get_text_field(db, "SELECT OBJECT_ID from OBJECTS o left join DETAILS d on (d.ID = o.DETAIL_ID)" 405 " where d.PATH = '%q' and REF_ID is NULL", parent_buf); 406 if( id ) 407 { 408 if( !depth ) 409 break; 410 DPRINTF(E_DEBUG, L_INOTIFY, "Found first known parentID: %s [%s]\n", id, parent_buf); 411 /* Insert newly-found directory */ 412 strcpy(base_name, last_dir); 413 base_copy = basename(base_name); 414 insert_directory(base_copy, last_dir, BROWSEDIR_ID, id+2, get_next_available_id("OBJECTS", id)); 415 sqlite3_free(id); 416 break; 417 } 418 depth++; 419 strcpy(last_dir, parent_buf); 420 parent_buf = dirname(parent_buf); 421 } 422 while( strcmp(parent_buf, "/") != 0 ); 423 424 if( strcmp(parent_buf, "/") == 0 ) 425 { 426 id = sqlite3_mprintf("%s", BROWSEDIR_ID); 427 depth = 0; 428 break; 429 } 430 strcpy(path_buf, path); 431 } 432 free(last_dir); 433 free(path_buf); 434 free(base_name); 435 436 if( !depth ) 437 { 438 //DEBUG DPRINTF(E_DEBUG, L_INOTIFY, "Inserting %s\n", name); 439 insert_file(name, path, id+2, get_next_available_id("OBJECTS", id), types); 440 sqlite3_free(id); 441 if( (is_audio(path) || is_playlist(path)) && next_pl_fill != 1 ) 442 { 443 next_pl_fill = uptime() + 120; // Schedule a playlist scan for 2 minutes from now. 444 //DEBUG DPRINTF(E_WARN, L_INOTIFY, "Playlist scan scheduled for %s", ctime(&next_pl_fill)); 445 } 446 } 447 return depth; 448} 449 450int 451inotify_insert_directory(int fd, char *name, const char * path) 452{ 453 DIR * ds; 454 struct dirent * e; 455 char *id, *parent_buf, *esc_name; 456 char path_buf[PATH_MAX]; 457 enum file_types type = TYPE_UNKNOWN; 458 media_types dir_types = ALL_MEDIA; 459 struct media_dir_s* media_path; 460 struct stat st; 461 462 if( access(path, R_OK|X_OK) != 0 ) 463 { 464 DPRINTF(E_WARN, L_INOTIFY, "Could not access %s [%s]\n", path, strerror(errno)); 465 return -1; 466 } 467 if( sql_get_int_field(db, "SELECT ID from DETAILS where PATH = '%q'", path) > 0 ) 468 { 469 fd = 0; 470 DPRINTF(E_DEBUG, L_INOTIFY, "%s already exists\n", path); 471 } 472 else 473 { 474 parent_buf = strdup(path); 475 id = sql_get_text_field(db, "SELECT OBJECT_ID from OBJECTS o left join DETAILS d on (d.ID = o.DETAIL_ID)" 476 " where d.PATH = '%q' and REF_ID is NULL", dirname(parent_buf)); 477 if( !id ) 478 id = sqlite3_mprintf("%s", BROWSEDIR_ID); 479 insert_directory(name, path, BROWSEDIR_ID, id+2, get_next_available_id("OBJECTS", id)); 480 sqlite3_free(id); 481 free(parent_buf); 482 } 483 484 if( fd > 0 ) 485 { 486#ifdef HAVE_INOTIFY 487 int wd = add_watch(fd, path); 488 if( wd == -1 ) 489 { 490 DPRINTF(E_ERROR, L_INOTIFY, "add_watch() failed\n"); 491 } 492else 493 { 494 DPRINTF(E_INFO, L_INOTIFY, "Added watch to %s [%d]\n", path, wd); 495 } 496#endif 497 } 498 499 media_path = media_dirs; 500 while( media_path ) 501 { 502 if( strncmp(path, media_path->path, strlen(media_path->path)) == 0 ) 503 { 504 dir_types = media_path->types; 505 break; 506 } 507 media_path = media_path->next; 508 } 509 510 ds = opendir(path); 511 if( !ds ) 512 { 513 DPRINTF(E_ERROR, L_INOTIFY, "opendir failed! [%s]\n", strerror(errno)); 514 return -1; 515 } 516 while( (e = readdir(ds)) ) 517 { 518 if( e->d_name[0] == '.' ) 519 continue; 520 esc_name = escape_tag(e->d_name, 1); 521 snprintf(path_buf, sizeof(path_buf), "%s/%s", path, e->d_name); 522 switch( e->d_type ) 523 { 524 case DT_DIR: 525 case DT_REG: 526 case DT_LNK: 527 case DT_UNKNOWN: 528 type = resolve_unknown_type(path_buf, dir_types); 529 default: 530 break; 531 } 532 if( type == TYPE_DIR ) 533 { 534 inotify_insert_directory(fd, esc_name, path_buf); 535 } 536 else if( type == TYPE_FILE ) 537 { 538 if( (stat(path_buf, &st) == 0) && (st.st_blocks<<9 >= st.st_size) ) 539 { 540 inotify_insert_file(esc_name, path_buf); 541 } 542 } 543 free(esc_name); 544 } 545 closedir(ds); 546 547 return 0; 548} 549 550int 551inotify_remove_file(const char * path) 552{ 553 char sql[128]; 554 char art_cache[PATH_MAX]; 555 char *id; 556 char *ptr; 557 char **result; 558 int64_t detailID; 559 int rows, playlist; 560 561 if( is_caption(path) ) 562 { 563 return sql_exec(db, "DELETE from CAPTIONS where PATH = '%q'", path); 564 } 565 /* Invalidate the scanner cache so we don't insert files into non-existent containers */ 566 valid_cache = 0; 567 playlist = is_playlist(path); 568 id = sql_get_text_field(db, "SELECT ID from %s where PATH = '%q'", playlist?"PLAYLISTS":"DETAILS", path); 569 if( !id ) 570 return 1; 571 detailID = strtoll(id, NULL, 10); 572 sqlite3_free(id); 573 if( playlist ) 574 { 575 sql_exec(db, "DELETE from PLAYLISTS where ID = %lld", detailID); 576 sql_exec(db, "DELETE from DETAILS where ID =" 577 " (SELECT DETAIL_ID from OBJECTS where OBJECT_ID = '%s$%llX')", 578 MUSIC_PLIST_ID, detailID); 579 sql_exec(db, "DELETE from OBJECTS where OBJECT_ID = '%s$%llX' or PARENT_ID = '%s$%llX'", 580 MUSIC_PLIST_ID, detailID, MUSIC_PLIST_ID, detailID); 581 } 582 else 583 { 584 /* Delete the parent containers if we are about to empty them. */ 585 snprintf(sql, sizeof(sql), "SELECT PARENT_ID from OBJECTS where DETAIL_ID = %lld" 586 " and PARENT_ID not like '64$%%'", 587 (long long int)detailID); 588 if( (sql_get_table(db, sql, &result, &rows, NULL) == SQLITE_OK) ) 589 { 590 int i, children; 591 for( i = 1; i <= rows; i++ ) 592 { 593 /* If it's a playlist item, adjust the item count of the playlist */ 594 if( strncmp(result[i], MUSIC_PLIST_ID, strlen(MUSIC_PLIST_ID)) == 0 ) 595 { 596 sql_exec(db, "UPDATE PLAYLISTS set FOUND = (FOUND-1) where ID = %d", 597 atoi(strrchr(result[i], '$') + 1)); 598 } 599 600 children = sql_get_int_field(db, "SELECT count(*) from OBJECTS where PARENT_ID = '%s'", result[i]); 601 if( children < 0 ) 602 continue; 603 if( children < 2 ) 604 { 605 sql_exec(db, "DELETE from OBJECTS where OBJECT_ID = '%s'", result[i]); 606 607 ptr = strrchr(result[i], '$'); 608 if( ptr ) 609 *ptr = '\0'; 610 if( sql_get_int_field(db, "SELECT count(*) from OBJECTS where PARENT_ID = '%s'", result[i]) == 0 ) 611 { 612 sql_exec(db, "DELETE from OBJECTS where OBJECT_ID = '%s'", result[i]); 613 } 614 } 615 } 616 sqlite3_free_table(result); 617 } 618 /* Now delete the actual objects */ 619 sql_exec(db, "DELETE from DETAILS where ID = %lld", detailID); 620 sql_exec(db, "DELETE from OBJECTS where DETAIL_ID = %lld", detailID); 621 } 622 snprintf(art_cache, sizeof(art_cache), "%s/art_cache%s", db_path, path); 623 remove(art_cache); 624 625 return 0; 626} 627 628int 629inotify_remove_directory(int fd, const char * path) 630{ 631 char * sql; 632 char **result; 633 int64_t detailID = 0; 634 int rows, i, ret = 1; 635 636 /* Invalidate the scanner cache so we don't insert files into non-existent containers */ 637 valid_cache = 0; 638 if( fd > 0 ) 639 { 640#ifdef HAVE_INOTIFY 641 remove_watch(fd, path); 642#endif 643 } 644 sql = sqlite3_mprintf("SELECT ID from DETAILS where (PATH > '%q/' and PATH <= '%q/%c')" 645 " or PATH = '%q'", path, path, 0xFF, path); 646 if( (sql_get_table(db, sql, &result, &rows, NULL) == SQLITE_OK) ) 647 { 648 if( rows ) 649 { 650 for( i=1; i <= rows; i++ ) 651 { 652 detailID = strtoll(result[i], NULL, 10); 653 sql_exec(db, "DELETE from DETAILS where ID = %lld", detailID); 654 sql_exec(db, "DELETE from OBJECTS where DETAIL_ID = %lld", detailID); 655 } 656 ret = 0; 657 } 658 sqlite3_free_table(result); 659 } 660 sqlite3_free(sql); 661 /* Clean up any album art entries in the deleted directory */ 662 sql_exec(db, "DELETE from ALBUM_ART where (PATH > '%q/' and PATH <= '%q/%c')", path, path, 0xFF); 663 664 return ret; 665} 666 667#ifdef HAVE_INOTIFY 668void * 669start_inotify() 670{ 671 struct pollfd pollfds[1]; 672 int timeout = 1000; 673 char buffer[BUF_LEN]; 674 char path_buf[PATH_MAX]; 675 int length, i = 0; 676 char * esc_name = NULL; 677 struct stat st; 678 679 pollfds[0].fd = inotify_init(); 680 pollfds[0].events = POLLIN; 681 682 if ( pollfds[0].fd < 0 ) 683 DPRINTF(E_ERROR, L_INOTIFY, "inotify_init() failed!\n"); 684 685 while( scanning ) 686 { 687 if( quitting ) 688 goto quitting; 689 sleep(1); 690 } 691 inotify_create_watches(pollfds[0].fd); 692 if (setpriority(PRIO_PROCESS, 0, 19) == -1) 693 DPRINTF(E_WARN, L_INOTIFY, "Failed to reduce inotify thread priority\n"); 694 sqlite3_release_memory(1<<31); 695 av_register_all(); 696 697 while( !quitting ) 698 { 699 length = poll(pollfds, 1, timeout); 700 if( !length ) 701 { 702 if( next_pl_fill && (uptime() >= next_pl_fill) ) 703 { 704 fill_playlists(); 705 next_pl_fill = 0; 706 } 707 continue; 708 } 709 else if( length < 0 ) 710 { 711 if( (errno == EINTR) || (errno == EAGAIN) ) 712 continue; 713 else 714 DPRINTF(E_ERROR, L_INOTIFY, "read failed!\n"); 715 } 716 else 717 { 718 length = read(pollfds[0].fd, buffer, BUF_LEN); 719 buffer[BUF_LEN-1] = '\0'; 720 } 721 722 i = 0; 723 while( i < length ) 724 { 725 struct inotify_event * event = (struct inotify_event *) &buffer[i]; 726 if( event->len ) 727 { 728 if( *(event->name) == '.' ) 729 { 730 i += EVENT_SIZE + event->len; 731 continue; 732 } 733 esc_name = modifyString(strdup(event->name), "&", "&amp;", 0); 734 snprintf(path_buf, sizeof(path_buf), "%s/%s", get_path_from_wd(event->wd), event->name); 735 if ( event->mask & IN_ISDIR && (event->mask & (IN_CREATE|IN_MOVED_TO)) ) 736 { 737 DPRINTF(E_DEBUG, L_INOTIFY, "The directory %s was %s.\n", 738 path_buf, (event->mask & IN_MOVED_TO ? "moved here" : "created")); 739 inotify_insert_directory(pollfds[0].fd, esc_name, path_buf); 740 } 741 else if ( (event->mask & (IN_CLOSE_WRITE|IN_MOVED_TO|IN_CREATE)) && 742 (lstat(path_buf, &st) == 0) ) 743 { 744 if( (event->mask & (IN_MOVED_TO|IN_CREATE)) && (S_ISLNK(st.st_mode) || st.st_nlink > 1) ) 745 { 746 DPRINTF(E_DEBUG, L_INOTIFY, "The %s link %s was %s.\n", 747 (S_ISLNK(st.st_mode) ? "symbolic" : "hard"), 748 path_buf, (event->mask & IN_MOVED_TO ? "moved here" : "created")); 749 if( stat(path_buf, &st) == 0 && S_ISDIR(st.st_mode) ) 750 inotify_insert_directory(pollfds[0].fd, esc_name, path_buf); 751 else 752 inotify_insert_file(esc_name, path_buf); 753 } 754 else if( event->mask & (IN_CLOSE_WRITE|IN_MOVED_TO) && st.st_size > 0 ) 755 { 756 if( (event->mask & IN_MOVED_TO) || 757 (sql_get_int_field(db, "SELECT TIMESTAMP from DETAILS where PATH = '%q'", path_buf) != st.st_mtime) ) 758 { 759 DPRINTF(E_DEBUG, L_INOTIFY, "The file %s was %s.\n", 760 path_buf, (event->mask & IN_MOVED_TO ? "moved here" : "changed")); 761 inotify_insert_file(esc_name, path_buf); 762 } 763 } 764 } 765 else if ( event->mask & (IN_DELETE|IN_MOVED_FROM) ) 766 { 767 DPRINTF(E_DEBUG, L_INOTIFY, "The %s %s was %s.\n", 768 (event->mask & IN_ISDIR ? "directory" : "file"), 769 path_buf, (event->mask & IN_MOVED_FROM ? "moved away" : "deleted")); 770 if ( event->mask & IN_ISDIR ) 771 inotify_remove_directory(pollfds[0].fd, path_buf); 772 else 773 inotify_remove_file(path_buf); 774 } 775 free(esc_name); 776 } 777 i += EVENT_SIZE + event->len; 778 } 779 } 780 inotify_remove_watches(pollfds[0].fd); 781quitting: 782 close(pollfds[0].fd); 783 784 return 0; 785} 786#endif 787