Initial revision 6759
[qball-mpd.git] / src / .svn / text-base / directory.c.svn-base
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
4  *
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.
9  *
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
17  */
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;
125         }
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");
147                         }
148                 }
149                 clearUpdatePid();
150         }
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;
160         }
163 int updateInit(int fd, List * pathList)
165         if (directory_updatePid > 0) {
166                 commandError(fd, ACK_ERROR_UPDATE_ALREADY, "already updating");
167                 return -1;
168         }
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);
199                                 }
200                                 node = node->nextNode;
201                         }
202                 } else {
203                         if ((dbUpdated = updateDirectory(mp3rootDirectory)) < 0) {
204                                 exit(DIRECTORY_UPDATE_EXIT_ERROR);
205                         }
206                 }
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);
215                 }
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;
224         }
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);
299         }
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);
315                 }
316                 node = nextNode;
317         }
320 /* return values:
321    -1 -> error
322     0 -> no error, but nothing updated
323     1 -> no error, and stuff updated
324  */
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);
342                         }
343                         return 1;
344                 }
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);
354                 }
355         }
357         return 0;
360 /* return values:
361    -1 -> error
362     0 -> no error, but nothing removed
363     1 -> no error, and stuff removed
364  */
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);
401         }
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;
412                         }
413                 } else {
414                         LOG("removing directory: %s/%s\n",
415                             getDirectoryPath(directory), node->key);
416                         deleteFromList(directory->subDirectories, node->key);
417                         ret = 1;
418                 }
419                 node = tmpNode;
420         }
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;
429                         }
430                 } else {
431                         removeSongFromDirectory(directory, node->key);
432                         ret = 1;
433                 }
434                 node = tmpNode;
435         }
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;
458         }
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);
475                 }
476         }
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;
502         }
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
517  */
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;
540                 }
541                 /* we don't want to delete the root directory */
542                 else if (directory == mp3rootDirectory) {
543                         free(path);
544                         return 0;
545                 }
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 */
553                 }
554         } else if ((song = getSongDetails(path, &shortname, &parentDirectory))) {
555                 if (!parentDirectory->stat
556                     && statDirectory(parentDirectory) < 0) {
557                         free(path);
558                         return 0;
559                 }
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;
574                         }
575                 }
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 */
581                 }
582         }
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
587          */
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;
599                 }
600         }
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
611  */
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;
622         {
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;
629                 }
630         }
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);
668         }
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
679  */
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);
725         }
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)
737         {
738                 return -1;
739         }
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;
752                 }
753                 if (parent->stat->inode == inode &&
754                     parent->stat->device == device) {
755                         DEBUG("recursive directory found\n");
756                         return 1;
757                 }
758                 parent = parent->parent;
759         }
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;
778         }
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;
792         }
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);
805         }
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;
827         }
829         if (findInList(directory->subDirectories, key, &subDirectory)) {
830                 free(dup);
831                 return (Directory *) subDirectory;
832         }
834         free(dup);
835         return NULL;
838 int isRootDirectory(char *name)
840         if (name == NULL || name[0] == '\0' || strcmp(name, "/") == 0) {
841                 return 1;
842         }
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;
854         }
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;
894         }
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;
906         }
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;
926                 }
927         }
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;
935                 }
936                 writeDirectoryInfo(fp, subDirectory);
937                 node = node->nextNode;
938         }
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;
948                 }
949         }
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");
974                         }
975                         if (strncmp
976                             (DIRECTORY_BEGIN, buffer,
977                              strlen(DIRECTORY_BEGIN))) {
978                                 FATAL("Error reading db at line: %s\n", buffer);
979                         }
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;
989                         }
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);
1004                         }
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);
1013                 }
1014         }
1016         while (nextDirNode) {
1017                 nodeTemp = nextDirNode->nextNode;
1018                 deleteNodeFromList(directory->subDirectories, nextDirNode);
1019                 nextDirNode = nodeTemp;
1020         }
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;
1035         }
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;
1061                 }
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;
1068                 }
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;
1077                 }
1079                 free(dbPath);
1080                 return 0;
1081         }
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;
1088         }
1090         if (!S_ISREG(st.st_mode)) {
1091                 ERROR("db file \"%s\" is not a regular file\n", dbFile);
1092                 return -1;
1093         }
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;
1100         }
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;
1125         }
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;
1156         }
1158         /* get initial info */
1159         {
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)))
1172                                 {
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);
1198                                         }
1199                                 } else {
1200                                         FATAL("directory: unknown line in db info: %s\n",
1201                                              buffer);
1202                                 }
1203                         }
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;
1209                 }
1210         }
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");
1239         }
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;
1259         }
1261         if (forEachSong) {
1262                 while (node != NULL && !errFlag) {
1263                         song = (Song *) node->data;
1264                         errFlag = forEachSong(fd, song, data);
1265                         node = node->nextNode;
1266                 }
1267                 if (errFlag)
1268                         return errFlag;
1269         }
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;
1278         }
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);
1293                 }
1294                 commandError(fd, ACK_ERROR_NO_EXIST,
1295                              "directory or file not found");
1296                 return -1;
1297         }
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;
1310         }
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, "/");
1339         }
1341         if (shortname != dup) {
1342                 for (c = dup; c < shortname - 1; c++) {
1343                         if (*c == '\0')
1344                                 *c = '/';
1345                 }
1346                 dir = dup;
1347         }
1349         if (!(directory = getDirectory(dir))) {
1350                 free(dup);
1351                 return NULL;
1352         }
1354         if (!findInList(directory->songs, shortname, &song)) {
1355                 free(dup);
1356                 return NULL;
1357         }
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;