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;