configure.ac: Move OggVorbis Encoder to Encoder Plugins.
[mpd-mk.git] / src / update_walk.c
blob192e8830e3b830472520d7cbd227e499f446e6aa
1 /*
2 * Copyright (C) 2003-2010 The Music Player Daemon Project
3 * http://www.musicpd.org
5 * This program is free software; you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation; either version 2 of the License, or
8 * (at your option) any later version.
10 * This program 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.
15 * You should have received a copy of the GNU General Public License along
16 * with this program; if not, write to the Free Software Foundation, Inc.,
17 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
20 #include "config.h" /* must be first for large file support */
21 #include "update_internal.h"
22 #include "database.h"
23 #include "exclude.h"
24 #include "directory.h"
25 #include "song.h"
26 #include "uri.h"
27 #include "mapper.h"
28 #include "path.h"
29 #include "decoder_list.h"
30 #include "decoder_plugin.h"
31 #include "conf.h"
33 #ifdef ENABLE_ARCHIVE
34 #include "archive_list.h"
35 #include "archive_plugin.h"
36 #endif
38 #include <glib.h>
40 #include <assert.h>
41 #include <sys/types.h>
42 #include <sys/stat.h>
43 #include <unistd.h>
44 #include <dirent.h>
45 #include <string.h>
46 #include <stdlib.h>
47 #include <errno.h>
49 static bool walk_discard;
50 static bool modified;
52 #ifndef WIN32
54 enum {
55 DEFAULT_FOLLOW_INSIDE_SYMLINKS = true,
56 DEFAULT_FOLLOW_OUTSIDE_SYMLINKS = true,
59 static bool follow_inside_symlinks;
60 static bool follow_outside_symlinks;
62 #endif
64 void
65 update_walk_global_init(void)
67 #ifndef WIN32
68 follow_inside_symlinks =
69 config_get_bool(CONF_FOLLOW_INSIDE_SYMLINKS,
70 DEFAULT_FOLLOW_INSIDE_SYMLINKS);
72 follow_outside_symlinks =
73 config_get_bool(CONF_FOLLOW_OUTSIDE_SYMLINKS,
74 DEFAULT_FOLLOW_OUTSIDE_SYMLINKS);
75 #endif
78 void
79 update_walk_global_finish(void)
83 static void
84 directory_set_stat(struct directory *dir, const struct stat *st)
86 dir->inode = st->st_ino;
87 dir->device = st->st_dev;
88 dir->stat = 1;
91 static void
92 delete_song(struct directory *dir, struct song *del)
94 /* first, prevent traversers in main task from getting this */
95 songvec_delete(&dir->songs, del);
97 /* now take it out of the playlist (in the main_task) */
98 update_remove_song(del);
100 /* finally, all possible references gone, free it */
101 song_free(del);
104 static int
105 delete_each_song(struct song *song, G_GNUC_UNUSED void *data)
107 struct directory *directory = data;
108 assert(song->parent == directory);
109 delete_song(directory, song);
110 return 0;
113 static void
114 delete_directory(struct directory *directory);
117 * Recursively remove all sub directories and songs from a directory,
118 * leaving an empty directory.
120 static void
121 clear_directory(struct directory *directory)
123 int i;
125 for (i = directory->children.nr; --i >= 0;)
126 delete_directory(directory->children.base[i]);
128 assert(directory->children.nr == 0);
130 songvec_for_each(&directory->songs, delete_each_song, directory);
134 * Recursively free a directory and all its contents.
136 static void
137 delete_directory(struct directory *directory)
139 assert(directory->parent != NULL);
141 clear_directory(directory);
143 dirvec_delete(&directory->parent->children, directory);
144 directory_free(directory);
147 static void
148 delete_name_in(struct directory *parent, const char *name)
150 struct directory *directory = directory_get_child(parent, name);
151 struct song *song = songvec_find(&parent->songs, name);
153 if (directory != NULL) {
154 delete_directory(directory);
155 modified = true;
158 if (song != NULL) {
159 delete_song(parent, song);
160 modified = true;
164 /* passed to songvec_for_each */
165 static int
166 delete_song_if_excluded(struct song *song, void *_data)
168 GSList *exclude_list = _data;
169 char *name_fs;
171 assert(song->parent != NULL);
173 name_fs = utf8_to_fs_charset(song->uri);
174 if (exclude_list_check(exclude_list, name_fs)) {
175 delete_song(song->parent, song);
176 modified = true;
179 g_free(name_fs);
180 return 0;
183 static void
184 remove_excluded_from_directory(struct directory *directory,
185 GSList *exclude_list)
187 int i;
188 struct dirvec *dv = &directory->children;
190 for (i = dv->nr; --i >= 0; ) {
191 struct directory *child = dv->base[i];
192 char *name_fs = utf8_to_fs_charset(directory_get_name(child));
194 if (exclude_list_check(exclude_list, name_fs)) {
195 delete_directory(child);
196 modified = true;
199 g_free(name_fs);
202 songvec_for_each(&directory->songs,
203 delete_song_if_excluded, exclude_list);
206 /* passed to songvec_for_each */
207 static int
208 delete_song_if_removed(struct song *song, void *_data)
210 struct directory *dir = _data;
211 char *path;
212 struct stat st;
214 if ((path = map_song_fs(song)) == NULL ||
215 stat(path, &st) < 0 || !S_ISREG(st.st_mode)) {
216 delete_song(dir, song);
217 modified = true;
220 g_free(path);
221 return 0;
224 static bool
225 directory_exists(const struct directory *directory)
227 char *path_fs;
228 GFileTest test;
229 bool exists;
231 path_fs = map_directory_fs(directory);
232 if (path_fs == NULL)
233 /* invalid path: cannot exist */
234 return false;
236 test = directory->device == DEVICE_INARCHIVE ||
237 directory->device == DEVICE_CONTAINER
238 ? G_FILE_TEST_IS_REGULAR
239 : G_FILE_TEST_IS_DIR;
241 exists = g_file_test(path_fs, test);
242 g_free(path_fs);
244 return exists;
247 static void
248 removeDeletedFromDirectory(struct directory *directory)
250 int i;
251 struct dirvec *dv = &directory->children;
253 for (i = dv->nr; --i >= 0; ) {
254 if (directory_exists(dv->base[i]))
255 continue;
257 g_debug("removing directory: %s", dv->base[i]->path);
258 delete_directory(dv->base[i]);
259 modified = true;
262 songvec_for_each(&directory->songs, delete_song_if_removed, directory);
265 static int
266 stat_directory(const struct directory *directory, struct stat *st)
268 char *path_fs;
269 int ret;
271 path_fs = map_directory_fs(directory);
272 if (path_fs == NULL)
273 return -1;
274 ret = stat(path_fs, st);
275 g_free(path_fs);
276 return ret;
279 static int
280 stat_directory_child(const struct directory *parent, const char *name,
281 struct stat *st)
283 char *path_fs;
284 int ret;
286 path_fs = map_directory_child_fs(parent, name);
287 if (path_fs == NULL)
288 return -1;
290 ret = stat(path_fs, st);
291 g_free(path_fs);
292 return ret;
295 static int
296 statDirectory(struct directory *dir)
298 struct stat st;
300 if (stat_directory(dir, &st) < 0)
301 return -1;
303 directory_set_stat(dir, &st);
305 return 0;
308 static int
309 inodeFoundInParent(struct directory *parent, ino_t inode, dev_t device)
311 while (parent) {
312 if (!parent->stat && statDirectory(parent) < 0)
313 return -1;
314 if (parent->inode == inode && parent->device == device) {
315 g_debug("recursive directory found");
316 return 1;
318 parent = parent->parent;
321 return 0;
324 static struct directory *
325 make_subdir(struct directory *parent, const char *name)
327 struct directory *directory;
329 directory = directory_get_child(parent, name);
330 if (directory == NULL) {
331 char *path;
333 if (directory_is_root(parent))
334 path = NULL;
335 else
336 name = path = g_strconcat(directory_get_path(parent),
337 "/", name, NULL);
339 directory = directory_new_child(parent, name);
340 g_free(path);
343 return directory;
346 #ifdef ENABLE_ARCHIVE
347 static void
348 update_archive_tree(struct directory *directory, char *name)
350 struct directory *subdir;
351 struct song *song;
352 char *tmp;
354 tmp = strchr(name, '/');
355 if (tmp) {
356 *tmp = 0;
357 //add dir is not there already
358 if ((subdir = dirvec_find(&directory->children, name)) == NULL) {
359 //create new directory
360 subdir = make_subdir(directory, name);
361 subdir->device = DEVICE_INARCHIVE;
363 //create directories first
364 update_archive_tree(subdir, tmp+1);
365 } else {
366 if (strlen(name) == 0) {
367 g_warning("archive returned directory only");
368 return;
370 //add file
371 song = songvec_find(&directory->songs, name);
372 if (song == NULL) {
373 song = song_file_load(name, directory);
374 if (song != NULL) {
375 songvec_add(&directory->songs, song);
376 modified = true;
377 g_message("added %s/%s",
378 directory_get_path(directory), name);
385 * Updates the file listing from an archive file.
387 * @param parent the parent directory the archive file resides in
388 * @param name the UTF-8 encoded base name of the archive file
389 * @param st stat() information on the archive file
390 * @param plugin the archive plugin which fits this archive type
392 static void
393 update_archive_file(struct directory *parent, const char *name,
394 const struct stat *st,
395 const struct archive_plugin *plugin)
397 GError *error = NULL;
398 char *path_fs;
399 struct archive_file *file;
400 struct directory *directory;
401 char *filepath;
403 directory = dirvec_find(&parent->children, name);
404 if (directory != NULL && directory->mtime == st->st_mtime &&
405 !walk_discard)
406 /* MPD has already scanned the archive, and it hasn't
407 changed since - don't consider updating it */
408 return;
410 path_fs = map_directory_child_fs(parent, name);
412 /* open archive */
413 file = archive_file_open(plugin, path_fs, &error);
414 if (file == NULL) {
415 g_free(path_fs);
416 g_warning("%s", error->message);
417 g_error_free(error);
418 return;
421 g_debug("archive %s opened", path_fs);
422 g_free(path_fs);
424 if (directory == NULL) {
425 g_debug("creating archive directory: %s", name);
426 directory = make_subdir(parent, name);
427 /* mark this directory as archive (we use device for
428 this) */
429 directory->device = DEVICE_INARCHIVE;
432 directory->mtime = st->st_mtime;
434 archive_file_scan_reset(file);
436 while ((filepath = archive_file_scan_next(file)) != NULL) {
437 /* split name into directory and file */
438 g_debug("adding archive file: %s", filepath);
439 update_archive_tree(directory, filepath);
442 archive_file_close(file);
444 #endif
446 static bool
447 update_container_file( struct directory* directory,
448 const char* name,
449 const struct stat* st,
450 const struct decoder_plugin* plugin)
452 char* vtrack = NULL;
453 unsigned int tnum = 0;
454 char* pathname = map_directory_child_fs(directory, name);
455 struct directory* contdir = dirvec_find(&directory->children, name);
457 // directory exists already
458 if (contdir != NULL)
460 // modification time not eq. file mod. time
461 if (contdir->mtime != st->st_mtime || walk_discard)
463 g_message("removing container file: %s", pathname);
465 delete_directory(contdir);
466 contdir = NULL;
468 modified = true;
470 else {
471 g_free(pathname);
472 return true;
476 contdir = make_subdir(directory, name);
477 contdir->mtime = st->st_mtime;
478 contdir->device = DEVICE_CONTAINER;
480 while ((vtrack = plugin->container_scan(pathname, ++tnum)) != NULL)
482 struct song* song = song_file_new(vtrack, contdir);
483 char *child_path_fs;
485 // shouldn't be necessary but it's there..
486 song->mtime = st->st_mtime;
488 child_path_fs = map_directory_child_fs(contdir, vtrack);
490 song->tag = plugin->tag_dup(child_path_fs);
491 g_free(child_path_fs);
493 songvec_add(&contdir->songs, song);
495 modified = true;
497 g_message("added %s/%s",
498 directory_get_path(directory), vtrack);
499 g_free(vtrack);
502 g_free(pathname);
504 if (tnum == 1)
506 delete_directory(contdir);
507 return false;
509 else
510 return true;
513 static void
514 update_regular_file(struct directory *directory,
515 const char *name, const struct stat *st)
517 const char *suffix = uri_get_suffix(name);
518 const struct decoder_plugin* plugin;
519 #ifdef ENABLE_ARCHIVE
520 const struct archive_plugin *archive;
521 #endif
522 if (suffix == NULL)
523 return;
525 if ((plugin = decoder_plugin_from_suffix(suffix, false)) != NULL)
527 struct song* song = songvec_find(&directory->songs, name);
529 if (!(song != NULL && st->st_mtime == song->mtime &&
530 !walk_discard) &&
531 plugin->container_scan != NULL)
533 if (update_container_file(directory, name, st, plugin))
535 if (song != NULL)
536 delete_song(directory, song);
538 return;
542 if (song == NULL) {
543 song = song_file_load(name, directory);
544 if (song == NULL) {
545 g_debug("ignoring unrecognized file %s/%s",
546 directory_get_path(directory), name);
547 return;
550 songvec_add(&directory->songs, song);
551 modified = true;
552 g_message("added %s/%s",
553 directory_get_path(directory), name);
554 } else if (st->st_mtime != song->mtime || walk_discard) {
555 g_message("updating %s/%s",
556 directory_get_path(directory), name);
557 if (!song_file_update(song)) {
558 g_debug("deleting unrecognized file %s/%s",
559 directory_get_path(directory), name);
560 delete_song(directory, song);
563 modified = true;
565 #ifdef ENABLE_ARCHIVE
566 } else if ((archive = archive_plugin_from_suffix(suffix))) {
567 update_archive_file(directory, name, st, archive);
568 #endif
572 static bool
573 updateDirectory(struct directory *directory, const struct stat *st);
575 static void
576 updateInDirectory(struct directory *directory,
577 const char *name, const struct stat *st)
579 assert(strchr(name, '/') == NULL);
581 if (S_ISREG(st->st_mode)) {
582 update_regular_file(directory, name, st);
583 } else if (S_ISDIR(st->st_mode)) {
584 struct directory *subdir;
585 bool ret;
587 if (inodeFoundInParent(directory, st->st_ino, st->st_dev))
588 return;
590 subdir = make_subdir(directory, name);
591 assert(directory == subdir->parent);
593 ret = updateDirectory(subdir, st);
594 if (!ret)
595 delete_directory(subdir);
596 } else {
597 g_debug("update: %s is not a directory, archive or music", name);
601 /* we don't look at "." / ".." nor files with newlines in their name */
602 static bool skip_path(const char *path)
604 return (path[0] == '.' && path[1] == 0) ||
605 (path[0] == '.' && path[1] == '.' && path[2] == 0) ||
606 strchr(path, '\n') != NULL;
609 static bool
610 skip_symlink(const struct directory *directory, const char *utf8_name)
612 #ifndef WIN32
613 char buffer[MPD_PATH_MAX];
614 char *path_fs;
615 const char *p;
616 ssize_t ret;
618 path_fs = map_directory_child_fs(directory, utf8_name);
619 if (path_fs == NULL)
620 return true;
622 ret = readlink(path_fs, buffer, sizeof(buffer));
623 g_free(path_fs);
624 if (ret < 0)
625 /* don't skip if this is not a symlink */
626 return errno != EINVAL;
628 if (!follow_inside_symlinks && !follow_outside_symlinks) {
629 /* ignore all symlinks */
630 return true;
631 } else if (follow_inside_symlinks && follow_outside_symlinks) {
632 /* consider all symlinks */
633 return false;
636 if (buffer[0] == '/')
637 return !follow_outside_symlinks;
639 p = buffer;
640 while (*p == '.') {
641 if (p[1] == '.' && G_IS_DIR_SEPARATOR(p[2])) {
642 /* "../" moves to parent directory */
643 directory = directory->parent;
644 if (directory == NULL) {
645 /* we have moved outside the music
646 directory - skip this symlink
647 if such symlinks are not allowed */
648 return !follow_outside_symlinks;
650 p += 3;
651 } else if (G_IS_DIR_SEPARATOR(p[1]))
652 /* eliminate "./" */
653 p += 2;
654 else
655 break;
658 /* we are still in the music directory, so this symlink points
659 to a song which is already in the database - skip according
660 to the follow_inside_symlinks param*/
661 return !follow_inside_symlinks;
662 #else
663 /* no symlink checking on WIN32 */
665 (void)directory;
666 (void)utf8_name;
668 return false;
669 #endif
672 static bool
673 updateDirectory(struct directory *directory, const struct stat *st)
675 DIR *dir;
676 struct dirent *ent;
677 char *path_fs, *exclude_path_fs;
678 GSList *exclude_list;
680 assert(S_ISDIR(st->st_mode));
682 directory_set_stat(directory, st);
684 path_fs = map_directory_fs(directory);
685 if (path_fs == NULL)
686 return false;
688 dir = opendir(path_fs);
689 if (!dir) {
690 g_warning("Failed to open directory %s: %s",
691 path_fs, g_strerror(errno));
692 g_free(path_fs);
693 return false;
696 exclude_path_fs = g_build_filename(path_fs, ".mpdignore", NULL);
697 exclude_list = exclude_list_load(exclude_path_fs);
698 g_free(exclude_path_fs);
700 g_free(path_fs);
702 if (exclude_list != NULL)
703 remove_excluded_from_directory(directory, exclude_list);
705 removeDeletedFromDirectory(directory);
707 while ((ent = readdir(dir))) {
708 char *utf8;
709 struct stat st2;
711 if (skip_path(ent->d_name) ||
712 exclude_list_check(exclude_list, ent->d_name))
713 continue;
715 utf8 = fs_charset_to_utf8(ent->d_name);
716 if (utf8 == NULL)
717 continue;
719 if (skip_symlink(directory, utf8)) {
720 delete_name_in(directory, utf8);
721 g_free(utf8);
722 continue;
725 if (stat_directory_child(directory, utf8, &st2) == 0)
726 updateInDirectory(directory, utf8, &st2);
727 else
728 delete_name_in(directory, utf8);
730 g_free(utf8);
733 exclude_list_free(exclude_list);
735 closedir(dir);
737 directory->mtime = st->st_mtime;
739 return true;
742 static struct directory *
743 directory_make_child_checked(struct directory *parent, const char *path)
745 struct directory *directory;
746 char *base;
747 struct stat st;
748 struct song *conflicting;
750 directory = directory_get_child(parent, path);
751 if (directory != NULL)
752 return directory;
754 base = g_path_get_basename(path);
756 if (stat_directory_child(parent, base, &st) < 0 ||
757 inodeFoundInParent(parent, st.st_ino, st.st_dev)) {
758 g_free(base);
759 return NULL;
762 /* if we're adding directory paths, make sure to delete filenames
763 with potentially the same name */
764 conflicting = songvec_find(&parent->songs, base);
765 if (conflicting)
766 delete_song(parent, conflicting);
768 g_free(base);
770 directory = directory_new_child(parent, path);
771 directory_set_stat(directory, &st);
772 return directory;
775 static struct directory *
776 addParentPathToDB(const char *utf8path)
778 struct directory *directory = db_get_root();
779 char *duplicated = g_strdup(utf8path);
780 char *slash = duplicated;
782 while ((slash = strchr(slash, '/')) != NULL) {
783 *slash = 0;
785 directory = directory_make_child_checked(directory,
786 duplicated);
787 if (directory == NULL || slash == NULL)
788 break;
790 *slash++ = '/';
793 g_free(duplicated);
794 return directory;
797 static void
798 updatePath(const char *path)
800 struct directory *parent;
801 char *name;
802 struct stat st;
804 parent = addParentPathToDB(path);
805 if (parent == NULL)
806 return;
808 name = g_path_get_basename(path);
810 if (stat_directory_child(parent, name, &st) == 0)
811 updateInDirectory(parent, name, &st);
812 else
813 delete_name_in(parent, name);
815 g_free(name);
818 bool
819 update_walk(const char *path, bool discard)
821 walk_discard = discard;
822 modified = false;
824 if (path != NULL && !isRootDirectory(path)) {
825 updatePath(path);
826 } else {
827 struct directory *directory = db_get_root();
828 struct stat st;
830 if (stat_directory(directory, &st) == 0)
831 updateDirectory(directory, &st);
834 return modified;