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"
24 #include "interface.h"
29 #include "mpd_types.h"
33 #include "sig_handlers.h"
35 #include "tagTracker.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);
111 assert(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
;
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: "
141 case DIRECTORY_UPDATE_EXIT_NOUPDATE
:
142 DEBUG("directory_sigChldHandler: "
143 "update exited succesffully\n");
146 ERROR("error updating db\n");
153 void readDirectoryDBIfUpdateIsFinished(void)
155 if (directory_reReadDB
&& 0 == directory_updatePid
) {
156 DEBUG("readDirectoryDB since update finished successfully\n");
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");
170 /* need to block CHLD signal, cause it can exit before we
171 even get a chance to assign directory_updatePID */
173 directory_updatePid
= fork();
174 if (directory_updatePid
== 0) {
182 closeAllListenSockets();
188 ListNode
*node
= pathList
->firstNode
;
191 switch (updatePath(node
->key
)) {
198 exit(DIRECTORY_UPDATE_EXIT_ERROR
);
200 node
= node
->nextNode
;
203 if ((dbUpdated
= updateDirectory(mp3rootDirectory
)) < 0) {
204 exit(DIRECTORY_UPDATE_EXIT_ERROR
);
209 exit(DIRECTORY_UPDATE_EXIT_NOUPDATE
);
211 /* ignore signals since we don't want them to corrupt the db */
213 if (writeDirectoryDB() < 0) {
214 exit(DIRECTORY_UPDATE_EXIT_ERROR
);
216 exit(DIRECTORY_UPDATE_EXIT_UPDATE
);
217 } else if (directory_updatePid
< 0) {
219 ERROR("updateInit: Problems forking()'ing\n");
220 commandError(fd
, ACK_ERROR_SYSTEM
,
221 "problems trying to update");
222 directory_updatePid
= 0;
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
);
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
;
245 static void freeDirectoryStatFromDirectory(Directory
* dir
)
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
);
266 directory
->path
= NULL
;
267 directory
->subDirectories
= newDirectoryList();
268 directory
->songs
= newSongList();
269 directory
->stat
= NULL
;
270 directory
->parent
= parent
;
275 static void freeDirectory(Directory
* directory
)
277 freeDirectoryList(directory
->subDirectories
);
278 freeSongList(directory
->songs
);
280 free(directory
->path
);
281 freeDirectoryStatFromDirectory(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
)
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
;
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
);
322 0 -> no error, but nothing updated
323 1 -> no error, and stuff updated
325 static int updateInDirectory(Directory
* directory
, char *shortname
, char *name
)
331 if (myStat(name
, &st
))
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
);
345 } else if (S_ISDIR(st
.st_mode
)) {
347 (directory
->subDirectories
, shortname
, (void **)&subDir
)) {
348 freeDirectoryStatFromDirectory(subDir
);
349 ((Directory
*) subDir
)->stat
= newDirectoryStat(&st
);
350 return updateDirectory((Directory
*) subDir
);
352 return addSubDirectoryToDirectory(directory
, shortname
,
362 0 -> no error, but nothing removed
363 1 -> no error, and stuff removed
365 static int removeDeletedFromDirectory(Directory
* directory
, DIR * dir
)
369 char *dirname
= getDirectoryPath(directory
);
370 List
*entList
= makeList(free
, 1);
383 while ((ent
= readdir(dir
))) {
384 if (ent
->d_name
[0] == '.')
385 continue; /* hide hidden stuff */
386 if (strchr(ent
->d_name
, '\n'))
389 utf8
= fsCharsetToUtf8(ent
->d_name
);
394 if (directory
->path
) {
395 s
= xmalloc(strlen(getDirectoryPath(directory
))
397 sprintf(s
, "%s/%s", getDirectoryPath(directory
), utf8
);
400 insertInList(entList
, utf8
, s
);
403 node
= directory
->subDirectories
->firstNode
;
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
,
414 LOG("removing directory: %s/%s\n",
415 getDirectoryPath(directory
), node
->key
);
416 deleteFromList(directory
->subDirectories
, node
->key
);
422 node
= directory
->songs
->firstNode
;
424 tmpNode
= node
->nextNode
;
425 if (findInList(entList
, node
->key
, (void **)&name
)) {
426 if (!isMusic(name
, NULL
, 0)) {
427 removeSongFromDirectory(directory
, node
->key
);
431 removeSongFromDirectory(directory
, node
->key
);
442 static Directory
*addDirectoryPathToDB(char *utf8path
, char **shortname
)
445 Directory
*parentDirectory
;
448 parent
= xstrdup(parentPath(utf8path
));
450 if (strlen(parent
) == 0)
451 parentDirectory
= (void *)mp3rootDirectory
;
453 parentDirectory
= addDirectoryPathToDB(parent
, shortname
);
455 if (!parentDirectory
) {
460 *shortname
= utf8path
+ strlen(parent
);
461 while (*(*shortname
) && *(*shortname
) == '/')
465 (parentDirectory
->subDirectories
, *shortname
, &directory
)) {
467 if (myStat(utf8path
, &st
) < 0 ||
468 inodeFoundInParent(parentDirectory
, st
.st_ino
, st
.st_dev
)) {
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
);
484 return (Directory
*) directory
;
487 static Directory
*addParentPathToDB(char *utf8path
, char **shortname
)
490 Directory
*parentDirectory
;
492 parent
= xstrdup(parentPath(utf8path
));
494 if (strlen(parent
) == 0)
495 parentDirectory
= (void *)mp3rootDirectory
;
497 parentDirectory
= addDirectoryPathToDB(parent
, shortname
);
499 if (!parentDirectory
) {
504 *shortname
= utf8path
+ strlen(parent
);
505 while (*(*shortname
) && *(*shortname
) == '/')
510 return (Directory
*) parentDirectory
;
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
;
524 char *path
= sanitizePathDup(utf8path
);
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) {
538 sortDirectory(directory
);
541 /* we don't want to delete the root directory */
542 else if (directory
== mp3rootDirectory
) {
546 /* if updateDirectory fails, means we should delete it */
548 LOG("removing directory: %s\n", path
);
549 deleteFromList(parentDirectory
->subDirectories
,
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) {
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)) {
566 if (song
->mtime
== mtime
)
568 else if (updateSongInfo(song
) == 0)
571 removeSongFromDirectory(parentDirectory
,
576 /* if updateDirectory fails, means we should delete it */
578 removeSongFromDirectory(parentDirectory
, shortname
);
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
->
596 && addToDirectory(parentDirectory
, shortname
, path
)
609 0 -> no error, but nothing updated
610 1 -> no error, and stuff updated
612 static int updateDirectory(Directory
* directory
)
619 char *dirname
= getDirectoryPath(directory
);
623 if (!directory
->stat
&& statDirectory(directory
) < 0) {
625 } else if (inodeFoundInParent(directory
->parent
,
626 directory
->stat
->inode
,
627 directory
->stat
->device
)) {
637 if ((dir
= opendir(rmp2amp(utf8ToFsCharset(dirname
)))) == NULL
)
640 if (removeDeletedFromDirectory(directory
, dir
) > 0)
645 while ((ent
= readdir(dir
))) {
646 if (ent
->d_name
[0] == '.')
647 continue; /* hide hidden stuff */
648 if (strchr(ent
->d_name
, '\n'))
651 utf8
= fsCharsetToUtf8(ent
->d_name
);
656 utf8
= xstrdup(utf8
);
658 if (directory
->path
) {
659 s
= xmalloc(strlen(getDirectoryPath(directory
)) +
661 sprintf(s
, "%s/%s", getDirectoryPath(directory
), utf8
);
664 if (updateInDirectory(directory
, utf8
, s
) > 0)
677 0 -> no error, but nothing found
678 1 -> no error, and stuff found
680 static int exploreDirectory(Directory
* directory
)
687 char *dirname
= getDirectoryPath(directory
);
695 DEBUG("explore: attempting to opendir: %s\n", dirname
);
696 if ((dir
= opendir(rmp2amp(utf8ToFsCharset(dirname
)))) == NULL
)
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'))
706 utf8
= fsCharsetToUtf8(ent
->d_name
);
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
)) +
718 sprintf(s
, "%s/%s", getDirectoryPath(directory
), utf8
);
721 if (addToDirectory(directory
, utf8
, s
) > 0)
732 static int statDirectory(Directory
* dir
)
736 if (myStat(getDirectoryPath(dir
) ? getDirectoryPath(dir
) : "", &st
) < 0)
741 dir
->stat
= newDirectoryStat(&st
);
746 static int inodeFoundInParent(Directory
* parent
, ino_t inode
, dev_t device
)
750 if (statDirectory(parent
) < 0)
753 if (parent
->stat
->inode
== inode
&&
754 parent
->stat
->device
== device
) {
755 DEBUG("recursive directory found\n");
758 parent
= parent
->parent
;
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
))
772 subDirectory
= newDirectory(name
, directory
);
773 subDirectory
->stat
= newDirectoryStat(st
);
775 if (exploreDirectory(subDirectory
) < 1) {
776 freeDirectory(subDirectory
);
780 insertInList(directory
->subDirectories
, shortname
, subDirectory
);
785 static int addToDirectory(Directory
* directory
, char *shortname
, char *name
)
789 if (myStat(name
, &st
)) {
790 DEBUG("failed to stat %s: %s\n", name
, strerror(errno
));
794 if (S_ISREG(st
.st_mode
) && hasMusicSuffix(name
, 0)) {
796 song
= addSongToList(directory
->songs
, shortname
, name
,
797 SONG_TYPE_FILE
, directory
);
800 LOG("added %s\n", name
);
802 } else if (S_ISDIR(st
.st_mode
)) {
803 return addSubDirectoryToDirectory(directory
, shortname
, name
,
807 DEBUG("addToDirectory: %s is not a directory or music\n", name
);
812 void closeMp3Directory(void)
814 freeDirectory(mp3rootDirectory
);
817 static Directory
*findSubDirectory(Directory
* directory
, char *name
)
820 char *dup
= xstrdup(name
);
823 key
= strtok(dup
, "/");
829 if (findInList(directory
->subDirectories
, key
, &subDirectory
)) {
831 return (Directory
*) subDirectory
;
838 int isRootDirectory(char *name
)
840 if (name
== NULL
|| name
[0] == '\0' || strcmp(name
, "/") == 0) {
846 static Directory
*getSubDirectory(Directory
* directory
, char *name
,
849 Directory
*subDirectory
;
852 if (isRootDirectory(name
)) {
856 if ((subDirectory
= findSubDirectory(directory
, name
)) == NULL
)
862 while (name
[len
] != '/' && name
[len
] != '\0')
864 while (name
[len
] == '/')
867 return getSubDirectory(subDirectory
, &(name
[len
]), shortname
);
870 static Directory
*getDirectoryDetails(char *name
, char **shortname
)
874 return getSubDirectory(mp3rootDirectory
, name
, shortname
);
877 static Directory
*getDirectory(char *name
)
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
;
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");
908 printDirectoryList(fd
, directory
->subDirectories
);
909 printSongInfoFromList(fd
, directory
->songs
);
914 static void writeDirectoryInfo(FILE * fp
, Directory
* directory
)
916 ListNode
*node
= (directory
->subDirectories
)->firstNode
;
917 Directory
*subDirectory
;
920 if (directory
->path
) {
921 retv
= fprintf(fp
, "%s%s\n", DIRECTORY_BEGIN
,
922 getDirectoryPath(directory
));
924 ERROR("Failed to write data to database file: %s\n",strerror(errno
));
929 while (node
!= NULL
) {
930 subDirectory
= (Directory
*) node
->data
;
931 retv
= fprintf(fp
, "%s%s\n", DIRECTORY_DIR
, node
->key
);
933 ERROR("Failed to write data to database file: %s\n",strerror(errno
));
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
));
946 ERROR("Failed to write data to database file: %s\n",strerror(errno
));
952 static void readDirectoryInfo(FILE * fp
, Directory
* directory
)
954 char buffer
[MAXPATHLEN
* 2];
955 int bufferSize
= MAXPATHLEN
* 2;
957 Directory
*subDirectory
;
960 ListNode
*nextDirNode
= directory
->subDirectories
->firstNode
;
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");
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
=
984 nextDirNode
->key
)) > 0) {
985 nodeTemp
= nextDirNode
->nextNode
;
986 deleteNodeFromList(directory
->subDirectories
,
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
;
999 subDirectory
= newDirectory(name
, directory
);
1000 insertInListBeforeNode(directory
->
1002 nextDirNode
, -1, key
,
1003 (void *)subDirectory
);
1008 readDirectoryInfo(fp
, subDirectory
);
1009 } else if (0 == strncmp(SONG_BEGIN
, buffer
, strlen(SONG_BEGIN
))) {
1010 readSongInfoIntoList(fp
, directory
->songs
, directory
);
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
;
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)
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
));
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
);
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
,
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
,
1090 if (!S_ISREG(st
.st_mode
)) {
1091 ERROR("db file \"%s\" is not a regular file\n", dbFile
);
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
));
1105 int writeDirectoryDB(void)
1108 char *dbFile
= getDbFile();
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
);
1122 ERROR("unable to write to db file \"%s\": %s\n",
1123 dbFile
, strerror(errno
));
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
;
1143 int readDirectoryDB(void)
1146 char *dbFile
= getDbFile();
1149 if (!mp3rootDirectory
)
1150 mp3rootDirectory
= newDirectory(NULL
, NULL
);
1151 while (!(fp
= fopen(dbFile
, "r")) && errno
== EINTR
) ;
1153 ERROR("unable to open db file \"%s\": %s\n",
1154 dbFile
, strerror(errno
));
1158 /* get initial info */
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
)))
1174 FATAL("already found version in db\n");
1177 strncmp(DIRECTORY_FS_CHARSET
, buffer
,
1179 (DIRECTORY_FS_CHARSET
))) {
1184 FATAL("already found fs charset in db\n");
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
);
1200 FATAL("directory: unknown line in db info: %s\n",
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
);
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
;
1226 void updateMp3Directory(void)
1228 switch (updateDirectory(mp3rootDirectory
)) {
1230 /* nothing updated */
1233 if (writeDirectoryDB() < 0)
1237 /* something was updated and db should be written */
1238 FATAL("problems updating music db\n");
1244 static int traverseAllInSubDirectory(int fd
, Directory
* directory
,
1245 int (*forEachSong
) (int, Song
*,
1247 int (*forEachDir
) (int, Directory
*,
1248 void *), void *data
)
1250 ListNode
*node
= directory
->songs
->firstNode
;
1256 errFlag
= forEachDir(fd
, directory
, data
);
1262 while (node
!= NULL
&& !errFlag
) {
1263 song
= (Song
*) node
->data
;
1264 errFlag
= forEachSong(fd
, song
, data
);
1265 node
= node
->nextNode
;
1271 node
= directory
->subDirectories
->firstNode
;
1273 while (node
!= NULL
&& !errFlag
) {
1274 dir
= (Directory
*) node
->data
;
1275 errFlag
= traverseAllInSubDirectory(fd
, dir
, forEachSong
,
1277 node
= node
->nextNode
;
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
) {
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");
1299 return traverseAllInSubDirectory(fd
, directory
, forEachSong
, forEachDir
,
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
)
1328 Directory
*directory
;
1330 char *dup
= xstrdup(file
);
1331 char *shortname
= dup
;
1332 char *c
= strtok(dup
, "/");
1334 DEBUG("get song: %s\n", file
);
1338 c
= strtok(NULL
, "/");
1341 if (shortname
!= dup
) {
1342 for (c
= dup
; c
< shortname
- 1; c
++) {
1349 if (!(directory
= getDirectory(dir
))) {
1354 if (!findInList(directory
->songs
, shortname
, &song
)) {
1361 *shortnameRet
= shortname
;
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
;