Update to 6762
[qball-mpd.git] / src / directory.c
blobadfd1f2387bbf29635570655b43fd71e9bfbc097
1 /* the Music Player Daemon (MPD)
2 * Copyright (C) 2003-2007 by Warren Dukes (warren.dukes@gmail.com)
3 * This project's homepage is: 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.
14 * You should have received a copy of the GNU General Public License
15 * along with this program; if not, write to the Free Software
16 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
19 #include "directory.h"
21 #include "command.h"
22 #include "conf.h"
23 #include "dbUtils.h"
24 #include "interface.h"
25 #include "list.h"
26 #include "listen.h"
27 #include "log.h"
28 #include "ls.h"
29 #include "mpd_types.h"
30 #include "path.h"
31 #include "player.h"
32 #include "playlist.h"
33 #include "sig_handlers.h"
34 #include "stats.h"
35 #include "tagTracker.h"
36 #include "utils.h"
37 #include "volume.h"
39 #include <sys/wait.h>
40 #include <dirent.h>
41 #include <errno.h>
42 #include <assert.h>
43 #include <libgen.h>
45 #define DIRECTORY_DIR "directory: "
46 #define DIRECTORY_MTIME "mtime: "
47 #define DIRECTORY_BEGIN "begin: "
48 #define DIRECTORY_END "end: "
49 #define DIRECTORY_INFO_BEGIN "info_begin"
50 #define DIRECTORY_INFO_END "info_end"
51 #define DIRECTORY_MPD_VERSION "mpd_version: "
52 #define DIRECTORY_FS_CHARSET "fs_charset: "
54 #define DIRECTORY_UPDATE_EXIT_NOUPDATE 0
55 #define DIRECTORY_UPDATE_EXIT_UPDATE 1
56 #define DIRECTORY_UPDATE_EXIT_ERROR 2
58 #define DIRECTORY_RETURN_NOUPDATE 0
59 #define DIRECTORY_RETURN_UPDATE 1
60 #define DIRECTORY_RETURN_ERROR -1
62 static Directory *mp3rootDirectory;
64 static time_t directory_dbModTime;
66 static volatile int directory_updatePid;
68 static volatile int directory_reReadDB;
70 static volatile mpd_uint16 directory_updateJobId;
72 static DirectoryList *newDirectoryList();
74 static int addToDirectory(Directory * directory, char *shortname, char *name);
76 static void freeDirectoryList(DirectoryList * list);
78 static void freeDirectory(Directory * directory);
80 static int exploreDirectory(Directory * directory);
82 static int updateDirectory(Directory * directory);
84 static void deleteEmptyDirectoriesInDirectory(Directory * directory);
86 static void removeSongFromDirectory(Directory * directory, char *shortname);
88 static int addSubDirectoryToDirectory(Directory * directory, char *shortname,
89 char *name, struct stat *st);
91 static Directory *getDirectoryDetails(char *name, char **shortname);
93 static Directory *getDirectory(char *name);
95 static Song *getSongDetails(char *file, char **shortnameRet,
96 Directory ** directoryRet);
98 static int updatePath(char *utf8path);
100 static void sortDirectory(Directory * directory);
102 static int inodeFoundInParent(Directory * parent, ino_t inode, dev_t device);
104 static int statDirectory(Directory * dir);
106 static char *getDbFile(void)
108 ConfigParam *param = parseConfigFilePath(CONF_DB_FILE, 1);
110 assert(param);
111 assert(param->value);
113 return param->value;
116 static void clearUpdatePid(void)
118 directory_updatePid = 0;
121 int isUpdatingDB(void)
123 if (directory_updatePid > 0 || directory_reReadDB) {
124 return directory_updateJobId;
126 return 0;
129 void directory_sigChldHandler(int pid, int status)
131 if (directory_updatePid == pid) {
132 if (WIFSIGNALED(status) && WTERMSIG(status) != SIGTERM) {
133 ERROR("update process died from a "
134 "non-TERM signal: %i\n", WTERMSIG(status));
135 } else if (!WIFSIGNALED(status)) {
136 switch (WEXITSTATUS(status)) {
137 case DIRECTORY_UPDATE_EXIT_UPDATE:
138 directory_reReadDB = 1;
139 DEBUG("directory_sigChldHandler: "
140 "updated db\n");
141 case DIRECTORY_UPDATE_EXIT_NOUPDATE:
142 DEBUG("directory_sigChldHandler: "
143 "update exited succesffully\n");
144 break;
145 default:
146 ERROR("error updating db\n");
149 clearUpdatePid();
153 void readDirectoryDBIfUpdateIsFinished(void)
155 if (directory_reReadDB && 0 == directory_updatePid) {
156 DEBUG("readDirectoryDB since update finished successfully\n");
157 readDirectoryDB();
158 playlistVersionChange();
159 directory_reReadDB = 0;
163 int updateInit(int fd, List * pathList)
165 if (directory_updatePid > 0) {
166 commandError(fd, ACK_ERROR_UPDATE_ALREADY, "already updating");
167 return -1;
170 /* need to block CHLD signal, cause it can exit before we
171 even get a chance to assign directory_updatePID */
172 blockSignals();
173 directory_updatePid = fork();
174 if (directory_updatePid == 0) {
175 /* child */
176 int dbUpdated = 0;
177 clearPlayerPid();
179 unblockSignals();
181 finishSigHandlers();
182 closeAllListenSockets();
183 freeAllInterfaces();
184 finishPlaylist();
185 finishVolume();
187 if (pathList) {
188 ListNode *node = pathList->firstNode;
190 while (node) {
191 switch (updatePath(node->key)) {
192 case 1:
193 dbUpdated = 1;
194 break;
195 case 0:
196 break;
197 default:
198 exit(DIRECTORY_UPDATE_EXIT_ERROR);
200 node = node->nextNode;
202 } else {
203 if ((dbUpdated = updateDirectory(mp3rootDirectory)) < 0) {
204 exit(DIRECTORY_UPDATE_EXIT_ERROR);
208 if (!dbUpdated)
209 exit(DIRECTORY_UPDATE_EXIT_NOUPDATE);
211 /* ignore signals since we don't want them to corrupt the db */
212 ignoreSignals();
213 if (writeDirectoryDB() < 0) {
214 exit(DIRECTORY_UPDATE_EXIT_ERROR);
216 exit(DIRECTORY_UPDATE_EXIT_UPDATE);
217 } else if (directory_updatePid < 0) {
218 unblockSignals();
219 ERROR("updateInit: Problems forking()'ing\n");
220 commandError(fd, ACK_ERROR_SYSTEM,
221 "problems trying to update");
222 directory_updatePid = 0;
223 return -1;
225 unblockSignals();
227 directory_updateJobId++;
228 if (directory_updateJobId > 1 << 15)
229 directory_updateJobId = 1;
230 DEBUG("updateInit: fork()'d update child for update job id %i\n",
231 (int)directory_updateJobId);
232 fdprintf(fd, "updating_db: %i\n", (int)directory_updateJobId);
234 return 0;
237 static DirectoryStat *newDirectoryStat(struct stat *st)
239 DirectoryStat *ret = xmalloc(sizeof(DirectoryStat));
240 ret->inode = st->st_ino;
241 ret->device = st->st_dev;
242 return ret;
245 static void freeDirectoryStatFromDirectory(Directory * dir)
247 if (dir->stat)
248 free(dir->stat);
249 dir->stat = NULL;
252 static DirectoryList *newDirectoryList(void)
254 return makeList((ListFreeDataFunc *) freeDirectory, 1);
257 static Directory *newDirectory(char *dirname, Directory * parent)
259 Directory *directory;
261 directory = xmalloc(sizeof(Directory));
263 if (dirname && strlen(dirname))
264 directory->path = xstrdup(dirname);
265 else
266 directory->path = NULL;
267 directory->subDirectories = newDirectoryList();
268 directory->songs = newSongList();
269 directory->stat = NULL;
270 directory->parent = parent;
272 return directory;
275 static void freeDirectory(Directory * directory)
277 freeDirectoryList(directory->subDirectories);
278 freeSongList(directory->songs);
279 if (directory->path)
280 free(directory->path);
281 freeDirectoryStatFromDirectory(directory);
282 free(directory);
283 /* this resets last dir returned */
284 /*getDirectoryPath(NULL); */
287 static void freeDirectoryList(DirectoryList * directoryList)
289 freeList(directoryList);
292 static void removeSongFromDirectory(Directory * directory, char *shortname)
294 void *song;
296 if (findInList(directory->songs, shortname, &song)) {
297 LOG("removing: %s\n", getSongUrl((Song *) song));
298 deleteFromList(directory->songs, shortname);
302 static void deleteEmptyDirectoriesInDirectory(Directory * directory)
304 ListNode *node = directory->subDirectories->firstNode;
305 ListNode *nextNode;
306 Directory *subDir;
308 while (node) {
309 subDir = (Directory *) node->data;
310 deleteEmptyDirectoriesInDirectory(subDir);
311 nextNode = node->nextNode;
312 if (subDir->subDirectories->numberOfNodes == 0 &&
313 subDir->songs->numberOfNodes == 0) {
314 deleteNodeFromList(directory->subDirectories, node);
316 node = nextNode;
320 /* return values:
321 -1 -> error
322 0 -> no error, but nothing updated
323 1 -> no error, and stuff updated
325 static int updateInDirectory(Directory * directory, char *shortname, char *name)
327 void *song;
328 void *subDir;
329 struct stat st;
331 if (myStat(name, &st))
332 return -1;
334 if (S_ISREG(st.st_mode) && hasMusicSuffix(name, 0)) {
335 if (0 == findInList(directory->songs, shortname, &song)) {
336 addToDirectory(directory, shortname, name);
337 return DIRECTORY_RETURN_UPDATE;
338 } else if (st.st_mtime != ((Song *) song)->mtime) {
339 LOG("updating %s\n", name);
340 if (updateSongInfo((Song *) song) < 0) {
341 removeSongFromDirectory(directory, shortname);
343 return 1;
345 } else if (S_ISDIR(st.st_mode)) {
346 if (findInList
347 (directory->subDirectories, shortname, (void **)&subDir)) {
348 freeDirectoryStatFromDirectory(subDir);
349 ((Directory *) subDir)->stat = newDirectoryStat(&st);
350 return updateDirectory((Directory *) subDir);
351 } else {
352 return addSubDirectoryToDirectory(directory, shortname,
353 name, &st);
357 return 0;
360 /* return values:
361 -1 -> error
362 0 -> no error, but nothing removed
363 1 -> no error, and stuff removed
365 static int removeDeletedFromDirectory(Directory * directory, DIR * dir)
367 char cwd[2];
368 struct dirent *ent;
369 char *dirname = getDirectoryPath(directory);
370 List *entList = makeList(free, 1);
371 void *name;
372 char *s;
373 char *utf8;
374 ListNode *node;
375 ListNode *tmpNode;
376 int ret = 0;
378 cwd[0] = '.';
379 cwd[1] = '\0';
380 if (dirname == NULL)
381 dirname = cwd;
383 while ((ent = readdir(dir))) {
384 if (ent->d_name[0] == '.')
385 continue; /* hide hidden stuff */
386 if (strchr(ent->d_name, '\n'))
387 continue;
389 utf8 = fsCharsetToUtf8(ent->d_name);
391 if (!utf8)
392 continue;
394 if (directory->path) {
395 s = xmalloc(strlen(getDirectoryPath(directory))
396 + strlen(utf8) + 2);
397 sprintf(s, "%s/%s", getDirectoryPath(directory), utf8);
398 } else
399 s = xstrdup(utf8);
400 insertInList(entList, utf8, s);
403 node = directory->subDirectories->firstNode;
404 while (node) {
405 tmpNode = node->nextNode;
406 if (findInList(entList, node->key, &name)) {
407 if (!isDir((char *)name)) {
408 LOG("removing directory: %s\n", (char *)name);
409 deleteFromList(directory->subDirectories,
410 node->key);
411 ret = 1;
413 } else {
414 LOG("removing directory: %s/%s\n",
415 getDirectoryPath(directory), node->key);
416 deleteFromList(directory->subDirectories, node->key);
417 ret = 1;
419 node = tmpNode;
422 node = directory->songs->firstNode;
423 while (node) {
424 tmpNode = node->nextNode;
425 if (findInList(entList, node->key, (void **)&name)) {
426 if (!isMusic(name, NULL, 0)) {
427 removeSongFromDirectory(directory, node->key);
428 ret = 1;
430 } else {
431 removeSongFromDirectory(directory, node->key);
432 ret = 1;
434 node = tmpNode;
437 freeList(entList);
439 return ret;
442 static Directory *addDirectoryPathToDB(char *utf8path, char **shortname)
444 char *parent;
445 Directory *parentDirectory;
446 void *directory;
448 parent = xstrdup(parentPath(utf8path));
450 if (strlen(parent) == 0)
451 parentDirectory = (void *)mp3rootDirectory;
452 else
453 parentDirectory = addDirectoryPathToDB(parent, shortname);
455 if (!parentDirectory) {
456 free(parent);
457 return NULL;
460 *shortname = utf8path + strlen(parent);
461 while (*(*shortname) && *(*shortname) == '/')
462 (*shortname)++;
464 if (!findInList
465 (parentDirectory->subDirectories, *shortname, &directory)) {
466 struct stat st;
467 if (myStat(utf8path, &st) < 0 ||
468 inodeFoundInParent(parentDirectory, st.st_ino, st.st_dev)) {
469 free(parent);
470 return NULL;
471 } else {
472 directory = newDirectory(utf8path, parentDirectory);
473 insertInList(parentDirectory->subDirectories,
474 *shortname, directory);
478 /* if we're adding directory paths, make sure to delete filenames
479 with potentially the same name */
480 removeSongFromDirectory(parentDirectory, *shortname);
482 free(parent);
484 return (Directory *) directory;
487 static Directory *addParentPathToDB(char *utf8path, char **shortname)
489 char *parent;
490 Directory *parentDirectory;
492 parent = xstrdup(parentPath(utf8path));
494 if (strlen(parent) == 0)
495 parentDirectory = (void *)mp3rootDirectory;
496 else
497 parentDirectory = addDirectoryPathToDB(parent, shortname);
499 if (!parentDirectory) {
500 free(parent);
501 return NULL;
504 *shortname = utf8path + strlen(parent);
505 while (*(*shortname) && *(*shortname) == '/')
506 (*shortname)++;
508 free(parent);
510 return (Directory *) parentDirectory;
513 /* return values:
514 -1 -> error
515 0 -> no error, but nothing updated
516 1 -> no error, and stuff updated
518 static int updatePath(char *utf8path)
520 Directory *directory;
521 Directory *parentDirectory;
522 Song *song;
523 char *shortname;
524 char *path = sanitizePathDup(utf8path);
525 time_t mtime;
526 int ret = 0;
528 if (NULL == path)
529 return -1;
531 /* if path is in the DB try to update it, or else delete it */
532 if ((directory = getDirectoryDetails(path, &shortname))) {
533 parentDirectory = directory->parent;
535 /* if this update directory is successfull, we are done */
536 if ((ret = updateDirectory(directory)) >= 0) {
537 free(path);
538 sortDirectory(directory);
539 return ret;
541 /* we don't want to delete the root directory */
542 else if (directory == mp3rootDirectory) {
543 free(path);
544 return 0;
546 /* if updateDirectory fails, means we should delete it */
547 else {
548 LOG("removing directory: %s\n", path);
549 deleteFromList(parentDirectory->subDirectories,
550 shortname);
551 ret = 1;
552 /* don't return, path maybe a song now */
554 } else if ((song = getSongDetails(path, &shortname, &parentDirectory))) {
555 if (!parentDirectory->stat
556 && statDirectory(parentDirectory) < 0) {
557 free(path);
558 return 0;
560 /* if this song update is successfull, we are done */
561 else if (0 == inodeFoundInParent(parentDirectory->parent,
562 parentDirectory->stat->inode,
563 parentDirectory->stat->device)
564 && song && isMusic(getSongUrl(song), &mtime, 0)) {
565 free(path);
566 if (song->mtime == mtime)
567 return 0;
568 else if (updateSongInfo(song) == 0)
569 return 1;
570 else {
571 removeSongFromDirectory(parentDirectory,
572 shortname);
573 return 1;
576 /* if updateDirectory fails, means we should delete it */
577 else {
578 removeSongFromDirectory(parentDirectory, shortname);
579 ret = 1;
580 /* don't return, path maybe a directory now */
584 /* path not found in the db, see if it actually exists on the fs.
585 * Also, if by chance a directory was replaced by a file of the same
586 * name or vice versa, we need to add it to the db
588 if (isDir(path) || isMusic(path, NULL, 0)) {
589 parentDirectory = addParentPathToDB(path, &shortname);
590 if (!parentDirectory || (!parentDirectory->stat &&
591 statDirectory(parentDirectory) < 0)) {
592 } else if (0 == inodeFoundInParent(parentDirectory->parent,
593 parentDirectory->stat->inode,
594 parentDirectory->stat->
595 device)
596 && addToDirectory(parentDirectory, shortname, path)
597 > 0) {
598 ret = 1;
602 free(path);
604 return ret;
607 /* return values:
608 -1 -> error
609 0 -> no error, but nothing updated
610 1 -> no error, and stuff updated
612 static int updateDirectory(Directory * directory)
614 DIR *dir;
615 char cwd[2];
616 struct dirent *ent;
617 char *s;
618 char *utf8;
619 char *dirname = getDirectoryPath(directory);
620 int ret = 0;
623 if (!directory->stat && statDirectory(directory) < 0) {
624 return -1;
625 } else if (inodeFoundInParent(directory->parent,
626 directory->stat->inode,
627 directory->stat->device)) {
628 return -1;
632 cwd[0] = '.';
633 cwd[1] = '\0';
634 if (dirname == NULL)
635 dirname = cwd;
637 if ((dir = opendir(rmp2amp(utf8ToFsCharset(dirname)))) == NULL)
638 return -1;
640 if (removeDeletedFromDirectory(directory, dir) > 0)
641 ret = 1;
643 rewinddir(dir);
645 while ((ent = readdir(dir))) {
646 if (ent->d_name[0] == '.')
647 continue; /* hide hidden stuff */
648 if (strchr(ent->d_name, '\n'))
649 continue;
651 utf8 = fsCharsetToUtf8(ent->d_name);
653 if (!utf8)
654 continue;
656 utf8 = xstrdup(utf8);
658 if (directory->path) {
659 s = xmalloc(strlen(getDirectoryPath(directory)) +
660 strlen(utf8) + 2);
661 sprintf(s, "%s/%s", getDirectoryPath(directory), utf8);
662 } else
663 s = xstrdup(utf8);
664 if (updateInDirectory(directory, utf8, s) > 0)
665 ret = 1;
666 free(utf8);
667 free(s);
670 closedir(dir);
672 return ret;
675 /* return values:
676 -1 -> error
677 0 -> no error, but nothing found
678 1 -> no error, and stuff found
680 static int exploreDirectory(Directory * directory)
682 DIR *dir;
683 char cwd[2];
684 struct dirent *ent;
685 char *s;
686 char *utf8;
687 char *dirname = getDirectoryPath(directory);
688 int ret = 0;
690 cwd[0] = '.';
691 cwd[1] = '\0';
692 if (dirname == NULL)
693 dirname = cwd;
695 DEBUG("explore: attempting to opendir: %s\n", dirname);
696 if ((dir = opendir(rmp2amp(utf8ToFsCharset(dirname)))) == NULL)
697 return -1;
699 DEBUG("explore: %s\n", dirname);
700 while ((ent = readdir(dir))) {
701 if (ent->d_name[0] == '.')
702 continue; /* hide hidden stuff */
703 if (strchr(ent->d_name, '\n'))
704 continue;
706 utf8 = fsCharsetToUtf8(ent->d_name);
708 if (!utf8)
709 continue;
711 utf8 = xstrdup(utf8);
713 DEBUG("explore: found: %s (%s)\n", ent->d_name, utf8);
715 if (directory->path) {
716 s = xmalloc(strlen(getDirectoryPath(directory)) +
717 strlen(utf8) + 2);
718 sprintf(s, "%s/%s", getDirectoryPath(directory), utf8);
719 } else
720 s = xstrdup(utf8);
721 if (addToDirectory(directory, utf8, s) > 0)
722 ret = 1;
723 free(utf8);
724 free(s);
727 closedir(dir);
729 return ret;
732 static int statDirectory(Directory * dir)
734 struct stat st;
736 if (myStat(getDirectoryPath(dir) ? getDirectoryPath(dir) : "", &st) < 0)
738 return -1;
741 dir->stat = newDirectoryStat(&st);
743 return 0;
746 static int inodeFoundInParent(Directory * parent, ino_t inode, dev_t device)
748 while (parent) {
749 if (!parent->stat) {
750 if (statDirectory(parent) < 0)
751 return -1;
753 if (parent->stat->inode == inode &&
754 parent->stat->device == device) {
755 DEBUG("recursive directory found\n");
756 return 1;
758 parent = parent->parent;
761 return 0;
764 static int addSubDirectoryToDirectory(Directory * directory, char *shortname,
765 char *name, struct stat *st)
767 Directory *subDirectory;
769 if (inodeFoundInParent(directory, st->st_ino, st->st_dev))
770 return 0;
772 subDirectory = newDirectory(name, directory);
773 subDirectory->stat = newDirectoryStat(st);
775 if (exploreDirectory(subDirectory) < 1) {
776 freeDirectory(subDirectory);
777 return 0;
780 insertInList(directory->subDirectories, shortname, subDirectory);
782 return 1;
785 static int addToDirectory(Directory * directory, char *shortname, char *name)
787 struct stat st;
789 if (myStat(name, &st)) {
790 DEBUG("failed to stat %s: %s\n", name, strerror(errno));
791 return -1;
794 if (S_ISREG(st.st_mode) && hasMusicSuffix(name, 0)) {
795 Song *song;
796 song = addSongToList(directory->songs, shortname, name,
797 SONG_TYPE_FILE, directory);
798 if (!song)
799 return -1;
800 LOG("added %s\n", name);
801 return 1;
802 } else if (S_ISDIR(st.st_mode)) {
803 return addSubDirectoryToDirectory(directory, shortname, name,
804 &st);
807 DEBUG("addToDirectory: %s is not a directory or music\n", name);
809 return -1;
812 void closeMp3Directory(void)
814 freeDirectory(mp3rootDirectory);
817 static Directory *findSubDirectory(Directory * directory, char *name)
819 void *subDirectory;
820 char *dup = xstrdup(name);
821 char *key;
823 key = strtok(dup, "/");
824 if (!key) {
825 free(dup);
826 return NULL;
829 if (findInList(directory->subDirectories, key, &subDirectory)) {
830 free(dup);
831 return (Directory *) subDirectory;
834 free(dup);
835 return NULL;
838 int isRootDirectory(char *name)
840 if (name == NULL || name[0] == '\0' || strcmp(name, "/") == 0) {
841 return 1;
843 return 0;
846 static Directory *getSubDirectory(Directory * directory, char *name,
847 char **shortname)
849 Directory *subDirectory;
850 int len;
852 if (isRootDirectory(name)) {
853 return directory;
856 if ((subDirectory = findSubDirectory(directory, name)) == NULL)
857 return NULL;
859 *shortname = name;
861 len = 0;
862 while (name[len] != '/' && name[len] != '\0')
863 len++;
864 while (name[len] == '/')
865 len++;
867 return getSubDirectory(subDirectory, &(name[len]), shortname);
870 static Directory *getDirectoryDetails(char *name, char **shortname)
872 *shortname = NULL;
874 return getSubDirectory(mp3rootDirectory, name, shortname);
877 static Directory *getDirectory(char *name)
879 char *shortname;
881 return getSubDirectory(mp3rootDirectory, name, &shortname);
884 static int printDirectoryList(int fd, DirectoryList * directoryList)
886 ListNode *node = directoryList->firstNode;
887 Directory *directory;
889 while (node != NULL) {
890 directory = (Directory *) node->data;
891 fdprintf(fd, "%s%s\n", DIRECTORY_DIR,
892 getDirectoryPath(directory));
893 node = node->nextNode;
896 return 0;
899 int printDirectoryInfo(int fd, char *name)
901 Directory *directory;
903 if ((directory = getDirectory(name)) == NULL) {
904 commandError(fd, ACK_ERROR_NO_EXIST, "directory not found");
905 return -1;
908 printDirectoryList(fd, directory->subDirectories);
909 printSongInfoFromList(fd, directory->songs);
911 return 0;
914 static void writeDirectoryInfo(FILE * fp, Directory * directory)
916 ListNode *node = (directory->subDirectories)->firstNode;
917 Directory *subDirectory;
918 int retv;
920 if (directory->path) {
921 retv = fprintf(fp, "%s%s\n", DIRECTORY_BEGIN,
922 getDirectoryPath(directory));
923 if (retv < 0) {
924 ERROR("Failed to write data to database file: %s\n",strerror(errno));
925 return;
929 while (node != NULL) {
930 subDirectory = (Directory *) node->data;
931 retv = fprintf(fp, "%s%s\n", DIRECTORY_DIR, node->key);
932 if (retv < 0) {
933 ERROR("Failed to write data to database file: %s\n",strerror(errno));
934 return;
936 writeDirectoryInfo(fp, subDirectory);
937 node = node->nextNode;
940 writeSongInfoFromList(fp, directory->songs);
942 if (directory->path) {
943 retv = fprintf(fp, "%s%s\n", DIRECTORY_END,
944 getDirectoryPath(directory));
945 if (retv < 0) {
946 ERROR("Failed to write data to database file: %s\n",strerror(errno));
947 return;
952 static void readDirectoryInfo(FILE * fp, Directory * directory)
954 char buffer[MAXPATHLEN * 2];
955 int bufferSize = MAXPATHLEN * 2;
956 char *key;
957 Directory *subDirectory;
958 int strcmpRet;
959 char *name;
960 ListNode *nextDirNode = directory->subDirectories->firstNode;
961 ListNode *nodeTemp;
963 while (myFgets(buffer, bufferSize, fp)
964 && 0 != strncmp(DIRECTORY_END, buffer, strlen(DIRECTORY_END))) {
965 if (0 == strncmp(DIRECTORY_DIR, buffer, strlen(DIRECTORY_DIR))) {
966 key = xstrdup(&(buffer[strlen(DIRECTORY_DIR)]));
967 if (!myFgets(buffer, bufferSize, fp))
968 FATAL("Error reading db, fgets\n");
969 /* for compatibility with db's prior to 0.11 */
970 if (0 == strncmp(DIRECTORY_MTIME, buffer,
971 strlen(DIRECTORY_MTIME))) {
972 if (!myFgets(buffer, bufferSize, fp))
973 FATAL("Error reading db, fgets\n");
975 if (strncmp
976 (DIRECTORY_BEGIN, buffer,
977 strlen(DIRECTORY_BEGIN))) {
978 FATAL("Error reading db at line: %s\n", buffer);
980 name = xstrdup(&(buffer[strlen(DIRECTORY_BEGIN)]));
982 while (nextDirNode && (strcmpRet =
983 strcmp(key,
984 nextDirNode->key)) > 0) {
985 nodeTemp = nextDirNode->nextNode;
986 deleteNodeFromList(directory->subDirectories,
987 nextDirNode);
988 nextDirNode = nodeTemp;
991 if (NULL == nextDirNode) {
992 subDirectory = newDirectory(name, directory);
993 insertInList(directory->subDirectories,
994 key, (void *)subDirectory);
995 } else if (strcmpRet == 0) {
996 subDirectory = (Directory *) nextDirNode->data;
997 nextDirNode = nextDirNode->nextNode;
998 } else {
999 subDirectory = newDirectory(name, directory);
1000 insertInListBeforeNode(directory->
1001 subDirectories,
1002 nextDirNode, -1, key,
1003 (void *)subDirectory);
1006 free(name);
1007 free(key);
1008 readDirectoryInfo(fp, subDirectory);
1009 } else if (0 == strncmp(SONG_BEGIN, buffer, strlen(SONG_BEGIN))) {
1010 readSongInfoIntoList(fp, directory->songs, directory);
1011 } else {
1012 FATAL("Unknown line in db: %s\n", buffer);
1016 while (nextDirNode) {
1017 nodeTemp = nextDirNode->nextNode;
1018 deleteNodeFromList(directory->subDirectories, nextDirNode);
1019 nextDirNode = nodeTemp;
1023 static void sortDirectory(Directory * directory)
1025 ListNode *node = directory->subDirectories->firstNode;
1026 Directory *subDir;
1028 sortList(directory->subDirectories);
1029 sortList(directory->songs);
1031 while (node != NULL) {
1032 subDir = (Directory *) node->data;
1033 sortDirectory(subDir);
1034 node = node->nextNode;
1038 int checkDirectoryDB(void)
1040 struct stat st;
1041 char *dbFile;
1042 char *dirPath;
1043 char *dbPath;
1045 dbFile = getDbFile();
1047 /* Check if the file exists */
1048 if (access(dbFile, F_OK)) {
1049 /* If the file doesn't exist, we can't check if we can write
1050 * it, so we are going to try to get the directory path, and
1051 * see if we can write a file in that */
1052 dbPath = xstrdup(dbFile);
1053 dirPath = dirname(dbPath);
1055 /* Check that the parent part of the path is a directory */
1056 if (stat(dirPath, &st) < 0) {
1057 ERROR("Couldn't stat parent directory of db file "
1058 "\"%s\": %s\n", dbFile, strerror(errno));
1059 free(dbPath);
1060 return -1;
1063 if (!S_ISDIR(st.st_mode)) {
1064 ERROR("Couldn't create db file \"%s\" because the "
1065 "parent path is not a directory\n", dbFile);
1066 free(dbPath);
1067 return -1;
1070 /* Check if we can write to the directory */
1071 if (access(dirPath, R_OK | W_OK)) {
1072 ERROR("Can't create db file in \"%s\": %s\n", dirPath,
1073 strerror(errno));
1074 free(dbPath);
1075 return -1;
1079 free(dbPath);
1080 return 0;
1083 /* Path exists, now check if it's a regular file */
1084 if (stat(dbFile, &st) < 0) {
1085 ERROR("Couldn't stat db file \"%s\": %s\n", dbFile,
1086 strerror(errno));
1087 return -1;
1090 if (!S_ISREG(st.st_mode)) {
1091 ERROR("db file \"%s\" is not a regular file\n", dbFile);
1092 return -1;
1095 /* And check that we can write to it */
1096 if (access(dbFile, R_OK | W_OK)) {
1097 ERROR("Can't open db file \"%s\" for reading/writing: %s\n",
1098 dbFile, strerror(errno));
1099 return -1;
1102 return 0;
1105 int writeDirectoryDB(void)
1107 FILE *fp;
1108 char *dbFile = getDbFile();
1109 struct stat st;
1111 DEBUG("removing empty directories from DB\n");
1112 deleteEmptyDirectoriesInDirectory(mp3rootDirectory);
1114 DEBUG("sorting DB\n");
1116 sortDirectory(mp3rootDirectory);
1118 DEBUG("writing DB\n");
1120 while (!(fp = fopen(dbFile, "w")) && errno == EINTR);
1121 if (!fp) {
1122 ERROR("unable to write to db file \"%s\": %s\n",
1123 dbFile, strerror(errno));
1124 return -1;
1127 /* block signals when writing the db so we don't get a corrupted db */
1128 fprintf(fp, "%s\n", DIRECTORY_INFO_BEGIN);
1129 fprintf(fp, "%s%s\n", DIRECTORY_MPD_VERSION, VERSION);
1130 fprintf(fp, "%s%s\n", DIRECTORY_FS_CHARSET, getFsCharset());
1131 fprintf(fp, "%s\n", DIRECTORY_INFO_END);
1133 writeDirectoryInfo(fp, mp3rootDirectory);
1135 while (fclose(fp) && errno == EINTR);
1137 if (stat(dbFile, &st) == 0)
1138 directory_dbModTime = st.st_mtime;
1140 return 0;
1143 int readDirectoryDB(void)
1145 FILE *fp = NULL;
1146 char *dbFile = getDbFile();
1147 struct stat st;
1149 if (!mp3rootDirectory)
1150 mp3rootDirectory = newDirectory(NULL, NULL);
1151 while (!(fp = fopen(dbFile, "r")) && errno == EINTR) ;
1152 if (fp == NULL) {
1153 ERROR("unable to open db file \"%s\": %s\n",
1154 dbFile, strerror(errno));
1155 return -1;
1158 /* get initial info */
1160 char buffer[100];
1161 int bufferSize = 100;
1162 int foundFsCharset = 0;
1163 int foundVersion = 0;
1165 if (!myFgets(buffer, bufferSize, fp))
1166 FATAL("Error reading db, fgets\n");
1167 if (0 == strcmp(DIRECTORY_INFO_BEGIN, buffer)) {
1168 while (myFgets(buffer, bufferSize, fp) &&
1169 0 != strcmp(DIRECTORY_INFO_END, buffer)) {
1170 if (0 == strncmp(DIRECTORY_MPD_VERSION, buffer,
1171 strlen(DIRECTORY_MPD_VERSION)))
1173 if (foundVersion)
1174 FATAL("already found version in db\n");
1175 foundVersion = 1;
1176 } else if (0 ==
1177 strncmp(DIRECTORY_FS_CHARSET, buffer,
1178 strlen
1179 (DIRECTORY_FS_CHARSET))) {
1180 char *fsCharset;
1181 char *tempCharset;
1183 if (foundFsCharset)
1184 FATAL("already found fs charset in db\n");
1186 foundFsCharset = 1;
1188 fsCharset = &(buffer[strlen(DIRECTORY_FS_CHARSET)]);
1189 if ((tempCharset = getConfigParamValue(CONF_FS_CHARSET))
1190 && strcmp(fsCharset, tempCharset)) {
1191 WARNING("Using \"%s\" for the "
1192 "filesystem charset "
1193 "instead of \"%s\"\n",
1194 fsCharset, tempCharset);
1195 WARNING("maybe you need to "
1196 "recreate the db?\n");
1197 setFsCharset(fsCharset);
1199 } else {
1200 FATAL("directory: unknown line in db info: %s\n",
1201 buffer);
1204 } else {
1205 ERROR("db info not found in db file\n");
1206 ERROR("you should recreate the db using --create-db\n");
1207 fseek(fp, 0, SEEK_SET);
1208 return -1;
1212 DEBUG("reading DB\n");
1214 readDirectoryInfo(fp, mp3rootDirectory);
1215 while (fclose(fp) && errno == EINTR) ;
1217 stats.numberOfSongs = countSongsIn(STDERR_FILENO, NULL);
1218 stats.dbPlayTime = sumSongTimesIn(STDERR_FILENO, NULL);
1220 if (stat(dbFile, &st) == 0)
1221 directory_dbModTime = st.st_mtime;
1223 return 0;
1226 void updateMp3Directory(void)
1228 switch (updateDirectory(mp3rootDirectory)) {
1229 case 0:
1230 /* nothing updated */
1231 return;
1232 case 1:
1233 if (writeDirectoryDB() < 0)
1234 exit(EXIT_FAILURE);
1235 break;
1236 default:
1237 /* something was updated and db should be written */
1238 FATAL("problems updating music db\n");
1241 return;
1244 static int traverseAllInSubDirectory(int fd, Directory * directory,
1245 int (*forEachSong) (int, Song *,
1246 void *),
1247 int (*forEachDir) (int, Directory *,
1248 void *), void *data)
1250 ListNode *node = directory->songs->firstNode;
1251 Song *song;
1252 Directory *dir;
1253 int errFlag = 0;
1255 if (forEachDir) {
1256 errFlag = forEachDir(fd, directory, data);
1257 if (errFlag)
1258 return errFlag;
1261 if (forEachSong) {
1262 while (node != NULL && !errFlag) {
1263 song = (Song *) node->data;
1264 errFlag = forEachSong(fd, song, data);
1265 node = node->nextNode;
1267 if (errFlag)
1268 return errFlag;
1271 node = directory->subDirectories->firstNode;
1273 while (node != NULL && !errFlag) {
1274 dir = (Directory *) node->data;
1275 errFlag = traverseAllInSubDirectory(fd, dir, forEachSong,
1276 forEachDir, data);
1277 node = node->nextNode;
1280 return errFlag;
1283 int traverseAllIn(int fd, char *name,
1284 int (*forEachSong) (int, Song *, void *),
1285 int (*forEachDir) (int, Directory *, void *), void *data)
1287 Directory *directory;
1289 if ((directory = getDirectory(name)) == NULL) {
1290 Song *song;
1291 if ((song = getSongFromDB(name)) && forEachSong) {
1292 return forEachSong(fd, song, data);
1294 commandError(fd, ACK_ERROR_NO_EXIST,
1295 "directory or file not found");
1296 return -1;
1299 return traverseAllInSubDirectory(fd, directory, forEachSong, forEachDir,
1300 data);
1303 static void freeAllDirectoryStats(Directory * directory)
1305 ListNode *node = directory->subDirectories->firstNode;
1307 while (node != NULL) {
1308 freeAllDirectoryStats((Directory *) node->data);
1309 node = node->nextNode;
1312 freeDirectoryStatFromDirectory(directory);
1315 void initMp3Directory(void)
1317 mp3rootDirectory = newDirectory(NULL, NULL);
1318 exploreDirectory(mp3rootDirectory);
1319 freeAllDirectoryStats(mp3rootDirectory);
1320 stats.numberOfSongs = countSongsIn(STDERR_FILENO, NULL);
1321 stats.dbPlayTime = sumSongTimesIn(STDERR_FILENO, NULL);
1324 static Song *getSongDetails(char *file, char **shortnameRet,
1325 Directory ** directoryRet)
1327 void *song = NULL;
1328 Directory *directory;
1329 char *dir = NULL;
1330 char *dup = xstrdup(file);
1331 char *shortname = dup;
1332 char *c = strtok(dup, "/");
1334 DEBUG("get song: %s\n", file);
1336 while (c) {
1337 shortname = c;
1338 c = strtok(NULL, "/");
1341 if (shortname != dup) {
1342 for (c = dup; c < shortname - 1; c++) {
1343 if (*c == '\0')
1344 *c = '/';
1346 dir = dup;
1349 if (!(directory = getDirectory(dir))) {
1350 free(dup);
1351 return NULL;
1354 if (!findInList(directory->songs, shortname, &song)) {
1355 free(dup);
1356 return NULL;
1359 free(dup);
1360 if (shortnameRet)
1361 *shortnameRet = shortname;
1362 if (directoryRet)
1363 *directoryRet = directory;
1364 return (Song *) song;
1367 Song *getSongFromDB(char *file)
1369 return getSongDetails(file, NULL, NULL);
1372 time_t getDbModTime(void)
1374 return directory_dbModTime;