diff options
Diffstat (limited to 'trunk/src/directory.c')
-rw-r--r-- | trunk/src/directory.c | 1362 |
1 files changed, 1362 insertions, 0 deletions
diff --git a/trunk/src/directory.c b/trunk/src/directory.c new file mode 100644 index 000000000..560c04b7b --- /dev/null +++ b/trunk/src/directory.c @@ -0,0 +1,1362 @@ +/* the Music Player Daemon (MPD) + * Copyright (C) 2003-2007 by Warren Dukes (warren.dukes@gmail.com) + * This project's homepage is: http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include "directory.h" + +#include "command.h" +#include "conf.h" +#include "dbUtils.h" +#include "interface.h" +#include "list.h" +#include "listen.h" +#include "log.h" +#include "ls.h" +#include "mpd_types.h" +#include "path.h" +#include "player.h" +#include "playlist.h" +#include "sig_handlers.h" +#include "stats.h" +#include "tagTracker.h" +#include "utils.h" +#include "volume.h" + +#include <sys/wait.h> +#include <dirent.h> +#include <errno.h> +#include <assert.h> +#include <libgen.h> + +#define DIRECTORY_DIR "directory: " +#define DIRECTORY_MTIME "mtime: " +#define DIRECTORY_BEGIN "begin: " +#define DIRECTORY_END "end: " +#define DIRECTORY_INFO_BEGIN "info_begin" +#define DIRECTORY_INFO_END "info_end" +#define DIRECTORY_MPD_VERSION "mpd_version: " +#define DIRECTORY_FS_CHARSET "fs_charset: " + +#define DIRECTORY_UPDATE_EXIT_NOUPDATE 0 +#define DIRECTORY_UPDATE_EXIT_UPDATE 1 +#define DIRECTORY_UPDATE_EXIT_ERROR 2 + +#define DIRECTORY_RETURN_NOUPDATE 0 +#define DIRECTORY_RETURN_UPDATE 1 +#define DIRECTORY_RETURN_ERROR -1 + +static Directory *mp3rootDirectory; + +static time_t directory_dbModTime; + +static volatile int directory_updatePid; + +static volatile int directory_reReadDB; + +static volatile mpd_uint16 directory_updateJobId; + +static DirectoryList *newDirectoryList(); + +static int addToDirectory(Directory * directory, char *shortname, char *name); + +static void freeDirectoryList(DirectoryList * list); + +static void freeDirectory(Directory * directory); + +static int exploreDirectory(Directory * directory); + +static int updateDirectory(Directory * directory); + +static void deleteEmptyDirectoriesInDirectory(Directory * directory); + +static void removeSongFromDirectory(Directory * directory, char *shortname); + +static int addSubDirectoryToDirectory(Directory * directory, char *shortname, + char *name, struct stat *st); + +static Directory *getDirectoryDetails(char *name, char **shortname); + +static Directory *getDirectory(char *name); + +static Song *getSongDetails(char *file, char **shortnameRet, + Directory ** directoryRet); + +static int updatePath(char *utf8path); + +static void sortDirectory(Directory * directory); + +static int inodeFoundInParent(Directory * parent, ino_t inode, dev_t device); + +static int statDirectory(Directory * dir); + +static char *getDbFile(void) +{ + ConfigParam *param = parseConfigFilePath(CONF_DB_FILE, 1); + + assert(param); + assert(param->value); + + return param->value; +} + +static void clearUpdatePid(void) +{ + directory_updatePid = 0; +} + +int isUpdatingDB(void) +{ + if (directory_updatePid > 0 || directory_reReadDB) { + return directory_updateJobId; + } + return 0; +} + +void directory_sigChldHandler(int pid, int status) +{ + if (directory_updatePid == pid) { + if (WIFSIGNALED(status) && WTERMSIG(status) != SIGTERM) { + ERROR("update process died from a " + "non-TERM signal: %i\n", WTERMSIG(status)); + } else if (!WIFSIGNALED(status)) { + switch (WEXITSTATUS(status)) { + case DIRECTORY_UPDATE_EXIT_UPDATE: + directory_reReadDB = 1; + DEBUG("directory_sigChldHandler: " + "updated db\n"); + case DIRECTORY_UPDATE_EXIT_NOUPDATE: + DEBUG("directory_sigChldHandler: " + "update exited succesffully\n"); + break; + default: + ERROR("error updating db\n"); + } + } + clearUpdatePid(); + } +} + +void readDirectoryDBIfUpdateIsFinished(void) +{ + if (directory_reReadDB && 0 == directory_updatePid) { + DEBUG("readDirectoryDB since update finished successfully\n"); + readDirectoryDB(); + playlistVersionChange(); + directory_reReadDB = 0; + } +} + +int updateInit(int fd, List * pathList) +{ + if (directory_updatePid > 0) { + commandError(fd, ACK_ERROR_UPDATE_ALREADY, "already updating"); + return -1; + } + + /* need to block CHLD signal, cause it can exit before we + even get a chance to assign directory_updatePID */ + blockSignals(); + directory_updatePid = fork(); + if (directory_updatePid == 0) { + /* child */ + int dbUpdated = 0; + clearPlayerPid(); + + unblockSignals(); + + finishSigHandlers(); + closeAllListenSockets(); + freeAllInterfaces(); + finishPlaylist(); + finishVolume(); + + if (pathList) { + ListNode *node = pathList->firstNode; + + while (node) { + switch (updatePath(node->key)) { + case 1: + dbUpdated = 1; + break; + case 0: + break; + default: + exit(DIRECTORY_UPDATE_EXIT_ERROR); + } + node = node->nextNode; + } + } else { + if ((dbUpdated = updateDirectory(mp3rootDirectory)) < 0) { + exit(DIRECTORY_UPDATE_EXIT_ERROR); + } + } + + if (!dbUpdated) + exit(DIRECTORY_UPDATE_EXIT_NOUPDATE); + + /* ignore signals since we don't want them to corrupt the db */ + ignoreSignals(); + if (writeDirectoryDB() < 0) { + exit(DIRECTORY_UPDATE_EXIT_ERROR); + } + exit(DIRECTORY_UPDATE_EXIT_UPDATE); + } else if (directory_updatePid < 0) { + unblockSignals(); + ERROR("updateInit: Problems forking()'ing\n"); + commandError(fd, ACK_ERROR_SYSTEM, + "problems trying to update"); + directory_updatePid = 0; + return -1; + } + unblockSignals(); + + directory_updateJobId++; + if (directory_updateJobId > 1 << 15) + directory_updateJobId = 1; + DEBUG("updateInit: fork()'d update child for update job id %i\n", + (int)directory_updateJobId); + fdprintf(fd, "updating_db: %i\n", (int)directory_updateJobId); + + return 0; +} + +static DirectoryStat *newDirectoryStat(struct stat *st) +{ + DirectoryStat *ret = xmalloc(sizeof(DirectoryStat)); + ret->inode = st->st_ino; + ret->device = st->st_dev; + return ret; +} + +static void freeDirectoryStatFromDirectory(Directory * dir) +{ + if (dir->stat) + free(dir->stat); + dir->stat = NULL; +} + +static DirectoryList *newDirectoryList(void) +{ + return makeList((ListFreeDataFunc *) freeDirectory, 1); +} + +static Directory *newDirectory(char *dirname, Directory * parent) +{ + Directory *directory; + + directory = xmalloc(sizeof(Directory)); + + if (dirname && strlen(dirname)) + directory->path = xstrdup(dirname); + else + directory->path = NULL; + directory->subDirectories = newDirectoryList(); + directory->songs = newSongList(); + directory->stat = NULL; + directory->parent = parent; + + return directory; +} + +static void freeDirectory(Directory * directory) +{ + freeDirectoryList(directory->subDirectories); + freeSongList(directory->songs); + if (directory->path) + free(directory->path); + freeDirectoryStatFromDirectory(directory); + free(directory); + /* this resets last dir returned */ + /*getDirectoryPath(NULL); */ +} + +static void freeDirectoryList(DirectoryList * directoryList) +{ + freeList(directoryList); +} + +static void removeSongFromDirectory(Directory * directory, char *shortname) +{ + void *song; + + if (findInList(directory->songs, shortname, &song)) { + LOG("removing: %s\n", getSongUrl((Song *) song)); + deleteFromList(directory->songs, shortname); + } +} + +static void deleteEmptyDirectoriesInDirectory(Directory * directory) +{ + ListNode *node = directory->subDirectories->firstNode; + ListNode *nextNode; + Directory *subDir; + + while (node) { + subDir = (Directory *) node->data; + deleteEmptyDirectoriesInDirectory(subDir); + nextNode = node->nextNode; + if (subDir->subDirectories->numberOfNodes == 0 && + subDir->songs->numberOfNodes == 0) { + deleteNodeFromList(directory->subDirectories, node); + } + node = nextNode; + } +} + +/* return values: + -1 -> error + 0 -> no error, but nothing updated + 1 -> no error, and stuff updated + */ +static int updateInDirectory(Directory * directory, char *shortname, char *name) +{ + void *song; + void *subDir; + struct stat st; + + if (myStat(name, &st)) + return -1; + + if (S_ISREG(st.st_mode) && hasMusicSuffix(name, 0)) { + if (0 == findInList(directory->songs, shortname, &song)) { + addToDirectory(directory, shortname, name); + return DIRECTORY_RETURN_UPDATE; + } else if (st.st_mtime != ((Song *) song)->mtime) { + LOG("updating %s\n", name); + if (updateSongInfo((Song *) song) < 0) { + removeSongFromDirectory(directory, shortname); + } + return 1; + } + } else if (S_ISDIR(st.st_mode)) { + if (findInList + (directory->subDirectories, shortname, (void **)&subDir)) { + freeDirectoryStatFromDirectory(subDir); + ((Directory *) subDir)->stat = newDirectoryStat(&st); + return updateDirectory((Directory *) subDir); + } else { + return addSubDirectoryToDirectory(directory, shortname, + name, &st); + } + } + + return 0; +} + +/* return values: + -1 -> error + 0 -> no error, but nothing removed + 1 -> no error, and stuff removed + */ +static int removeDeletedFromDirectory(Directory * directory, DIR * dir) +{ + char cwd[2]; + struct dirent *ent; + char *dirname = getDirectoryPath(directory); + List *entList = makeList(free, 1); + void *name; + char *s; + char *utf8; + ListNode *node; + ListNode *tmpNode; + int ret = 0; + + cwd[0] = '.'; + cwd[1] = '\0'; + if (dirname == NULL) + dirname = cwd; + + while ((ent = readdir(dir))) { + if (ent->d_name[0] == '.') + continue; /* hide hidden stuff */ + if (strchr(ent->d_name, '\n')) + continue; + + utf8 = fsCharsetToUtf8(ent->d_name); + + if (!utf8) + continue; + + if (directory->path) { + s = xmalloc(strlen(getDirectoryPath(directory)) + + strlen(utf8) + 2); + sprintf(s, "%s/%s", getDirectoryPath(directory), utf8); + } else + s = xstrdup(utf8); + insertInList(entList, utf8, s); + } + + node = directory->subDirectories->firstNode; + while (node) { + tmpNode = node->nextNode; + if (findInList(entList, node->key, &name)) { + if (!isDir((char *)name)) { + LOG("removing directory: %s\n", (char *)name); + deleteFromList(directory->subDirectories, + node->key); + ret = 1; + } + } else { + LOG("removing directory: %s/%s\n", + getDirectoryPath(directory), node->key); + deleteFromList(directory->subDirectories, node->key); + ret = 1; + } + node = tmpNode; + } + + node = directory->songs->firstNode; + while (node) { + tmpNode = node->nextNode; + if (findInList(entList, node->key, (void **)&name)) { + if (!isMusic(name, NULL, 0)) { + removeSongFromDirectory(directory, node->key); + ret = 1; + } + } else { + removeSongFromDirectory(directory, node->key); + ret = 1; + } + node = tmpNode; + } + + freeList(entList); + + return ret; +} + +static Directory *addDirectoryPathToDB(char *utf8path, char **shortname) +{ + char *parent; + Directory *parentDirectory; + void *directory; + + parent = xstrdup(parentPath(utf8path)); + + if (strlen(parent) == 0) + parentDirectory = (void *)mp3rootDirectory; + else + parentDirectory = addDirectoryPathToDB(parent, shortname); + + if (!parentDirectory) { + free(parent); + return NULL; + } + + *shortname = utf8path + strlen(parent); + while (*(*shortname) && *(*shortname) == '/') + (*shortname)++; + + if (!findInList + (parentDirectory->subDirectories, *shortname, &directory)) { + struct stat st; + if (myStat(utf8path, &st) < 0 || + inodeFoundInParent(parentDirectory, st.st_ino, st.st_dev)) { + free(parent); + return NULL; + } else { + directory = newDirectory(utf8path, parentDirectory); + insertInList(parentDirectory->subDirectories, + *shortname, directory); + } + } + + /* if we're adding directory paths, make sure to delete filenames + with potentially the same name */ + removeSongFromDirectory(parentDirectory, *shortname); + + free(parent); + + return (Directory *) directory; +} + +static Directory *addParentPathToDB(char *utf8path, char **shortname) +{ + char *parent; + Directory *parentDirectory; + + parent = xstrdup(parentPath(utf8path)); + + if (strlen(parent) == 0) + parentDirectory = (void *)mp3rootDirectory; + else + parentDirectory = addDirectoryPathToDB(parent, shortname); + + if (!parentDirectory) { + free(parent); + return NULL; + } + + *shortname = utf8path + strlen(parent); + while (*(*shortname) && *(*shortname) == '/') + (*shortname)++; + + free(parent); + + return (Directory *) parentDirectory; +} + +/* return values: + -1 -> error + 0 -> no error, but nothing updated + 1 -> no error, and stuff updated + */ +static int updatePath(char *utf8path) +{ + Directory *directory; + Directory *parentDirectory; + Song *song; + char *shortname; + char *path = sanitizePathDup(utf8path); + time_t mtime; + int ret = 0; + + if (NULL == path) + return -1; + + /* if path is in the DB try to update it, or else delete it */ + if ((directory = getDirectoryDetails(path, &shortname))) { + parentDirectory = directory->parent; + + /* if this update directory is successfull, we are done */ + if ((ret = updateDirectory(directory)) >= 0) { + free(path); + sortDirectory(directory); + return ret; + } + /* we don't want to delete the root directory */ + else if (directory == mp3rootDirectory) { + free(path); + return 0; + } + /* if updateDirectory fails, means we should delete it */ + else { + LOG("removing directory: %s\n", path); + deleteFromList(parentDirectory->subDirectories, + shortname); + ret = 1; + /* don't return, path maybe a song now */ + } + } else if ((song = getSongDetails(path, &shortname, &parentDirectory))) { + if (!parentDirectory->stat + && statDirectory(parentDirectory) < 0) { + free(path); + return 0; + } + /* if this song update is successfull, we are done */ + else if (0 == inodeFoundInParent(parentDirectory->parent, + parentDirectory->stat->inode, + parentDirectory->stat->device) + && song && isMusic(getSongUrl(song), &mtime, 0)) { + free(path); + if (song->mtime == mtime) + return 0; + else if (updateSongInfo(song) == 0) + return 1; + else { + removeSongFromDirectory(parentDirectory, + shortname); + return 1; + } + } + /* if updateDirectory fails, means we should delete it */ + else { + removeSongFromDirectory(parentDirectory, shortname); + ret = 1; + /* don't return, path maybe a directory now */ + } + } + + /* path not found in the db, see if it actually exists on the fs. + * Also, if by chance a directory was replaced by a file of the same + * name or vice versa, we need to add it to the db + */ + if (isDir(path) || isMusic(path, NULL, 0)) { + parentDirectory = addParentPathToDB(path, &shortname); + if (!parentDirectory || (!parentDirectory->stat && + statDirectory(parentDirectory) < 0)) { + } else if (0 == inodeFoundInParent(parentDirectory->parent, + parentDirectory->stat->inode, + parentDirectory->stat-> + device) + && addToDirectory(parentDirectory, shortname, path) + > 0) { + ret = 1; + } + } + + free(path); + + return ret; +} + +/* return values: + -1 -> error + 0 -> no error, but nothing updated + 1 -> no error, and stuff updated + */ +static int updateDirectory(Directory * directory) +{ + DIR *dir; + char cwd[2]; + struct dirent *ent; + char *s; + char *utf8; + char *dirname = getDirectoryPath(directory); + int ret = 0; + + { + if (!directory->stat && statDirectory(directory) < 0) { + return -1; + } else if (inodeFoundInParent(directory->parent, + directory->stat->inode, + directory->stat->device)) { + return -1; + } + } + + cwd[0] = '.'; + cwd[1] = '\0'; + if (dirname == NULL) + dirname = cwd; + + if ((dir = opendir(rmp2amp(utf8ToFsCharset(dirname)))) == NULL) + return -1; + + if (removeDeletedFromDirectory(directory, dir) > 0) + ret = 1; + + rewinddir(dir); + + while ((ent = readdir(dir))) { + if (ent->d_name[0] == '.') + continue; /* hide hidden stuff */ + if (strchr(ent->d_name, '\n')) + continue; + + utf8 = fsCharsetToUtf8(ent->d_name); + + if (!utf8) + continue; + + utf8 = xstrdup(utf8); + + if (directory->path) { + s = xmalloc(strlen(getDirectoryPath(directory)) + + strlen(utf8) + 2); + sprintf(s, "%s/%s", getDirectoryPath(directory), utf8); + } else + s = xstrdup(utf8); + if (updateInDirectory(directory, utf8, s) > 0) + ret = 1; + free(utf8); + free(s); + } + + closedir(dir); + + return ret; +} + +/* return values: + -1 -> error + 0 -> no error, but nothing found + 1 -> no error, and stuff found + */ +static int exploreDirectory(Directory * directory) +{ + DIR *dir; + char cwd[2]; + struct dirent *ent; + char *s; + char *utf8; + char *dirname = getDirectoryPath(directory); + int ret = 0; + + cwd[0] = '.'; + cwd[1] = '\0'; + if (dirname == NULL) + dirname = cwd; + + DEBUG("explore: attempting to opendir: %s\n", dirname); + if ((dir = opendir(rmp2amp(utf8ToFsCharset(dirname)))) == NULL) + return -1; + + DEBUG("explore: %s\n", dirname); + while ((ent = readdir(dir))) { + if (ent->d_name[0] == '.') + continue; /* hide hidden stuff */ + if (strchr(ent->d_name, '\n')) + continue; + + utf8 = fsCharsetToUtf8(ent->d_name); + + if (!utf8) + continue; + + utf8 = xstrdup(utf8); + + DEBUG("explore: found: %s (%s)\n", ent->d_name, utf8); + + if (directory->path) { + s = xmalloc(strlen(getDirectoryPath(directory)) + + strlen(utf8) + 2); + sprintf(s, "%s/%s", getDirectoryPath(directory), utf8); + } else + s = xstrdup(utf8); + if (addToDirectory(directory, utf8, s) > 0) + ret = 1; + free(utf8); + free(s); + } + + closedir(dir); + + return ret; +} + +static int statDirectory(Directory * dir) +{ + struct stat st; + + if (myStat(getDirectoryPath(dir) ? getDirectoryPath(dir) : "", &st) < 0) + { + return -1; + } + + dir->stat = newDirectoryStat(&st); + + return 0; +} + +static int inodeFoundInParent(Directory * parent, ino_t inode, dev_t device) +{ + while (parent) { + if (!parent->stat) { + if (statDirectory(parent) < 0) + return -1; + } + if (parent->stat->inode == inode && + parent->stat->device == device) { + DEBUG("recursive directory found\n"); + return 1; + } + parent = parent->parent; + } + + return 0; +} + +static int addSubDirectoryToDirectory(Directory * directory, char *shortname, + char *name, struct stat *st) +{ + Directory *subDirectory; + + if (inodeFoundInParent(directory, st->st_ino, st->st_dev)) + return 0; + + subDirectory = newDirectory(name, directory); + subDirectory->stat = newDirectoryStat(st); + + if (exploreDirectory(subDirectory) < 1) { + freeDirectory(subDirectory); + return 0; + } + + insertInList(directory->subDirectories, shortname, subDirectory); + + return 1; +} + +static int addToDirectory(Directory * directory, char *shortname, char *name) +{ + struct stat st; + + if (myStat(name, &st)) { + DEBUG("failed to stat %s: %s\n", name, strerror(errno)); + return -1; + } + + if (S_ISREG(st.st_mode) && hasMusicSuffix(name, 0)) { + Song *song; + song = addSongToList(directory->songs, shortname, name, + SONG_TYPE_FILE, directory); + if (!song) + return -1; + LOG("added %s\n", name); + return 1; + } else if (S_ISDIR(st.st_mode)) { + return addSubDirectoryToDirectory(directory, shortname, name, + &st); + } + + DEBUG("addToDirectory: %s is not a directory or music\n", name); + + return -1; +} + +void closeMp3Directory(void) +{ + freeDirectory(mp3rootDirectory); +} + +static Directory *findSubDirectory(Directory * directory, char *name) +{ + void *subDirectory; + char *dup = xstrdup(name); + char *key; + + key = strtok(dup, "/"); + if (!key) { + free(dup); + return NULL; + } + + if (findInList(directory->subDirectories, key, &subDirectory)) { + free(dup); + return (Directory *) subDirectory; + } + + free(dup); + return NULL; +} + +int isRootDirectory(char *name) +{ + if (name == NULL || name[0] == '\0' || strcmp(name, "/") == 0) { + return 1; + } + return 0; +} + +static Directory *getSubDirectory(Directory * directory, char *name, + char **shortname) +{ + Directory *subDirectory; + int len; + + if (isRootDirectory(name)) { + return directory; + } + + if ((subDirectory = findSubDirectory(directory, name)) == NULL) + return NULL; + + *shortname = name; + + len = 0; + while (name[len] != '/' && name[len] != '\0') + len++; + while (name[len] == '/') + len++; + + return getSubDirectory(subDirectory, &(name[len]), shortname); +} + +static Directory *getDirectoryDetails(char *name, char **shortname) +{ + *shortname = NULL; + + return getSubDirectory(mp3rootDirectory, name, shortname); +} + +static Directory *getDirectory(char *name) +{ + char *shortname; + + return getSubDirectory(mp3rootDirectory, name, &shortname); +} + +static int printDirectoryList(int fd, DirectoryList * directoryList) +{ + ListNode *node = directoryList->firstNode; + Directory *directory; + + while (node != NULL) { + directory = (Directory *) node->data; + fdprintf(fd, "%s%s\n", DIRECTORY_DIR, + getDirectoryPath(directory)); + node = node->nextNode; + } + + return 0; +} + +int printDirectoryInfo(int fd, char *name) +{ + Directory *directory; + + if ((directory = getDirectory(name)) == NULL) { + commandError(fd, ACK_ERROR_NO_EXIST, "directory not found"); + return -1; + } + + printDirectoryList(fd, directory->subDirectories); + printSongInfoFromList(fd, directory->songs); + + return 0; +} + +static void writeDirectoryInfo(FILE * fp, Directory * directory) +{ + ListNode *node = (directory->subDirectories)->firstNode; + Directory *subDirectory; + + if (directory->path) { + fprintf(fp, "%s%s\n", DIRECTORY_BEGIN, + getDirectoryPath(directory)); + } + + while (node != NULL) { + subDirectory = (Directory *) node->data; + fprintf(fp, "%s%s\n", DIRECTORY_DIR, node->key); + writeDirectoryInfo(fp, subDirectory); + node = node->nextNode; + } + + writeSongInfoFromList(fp, directory->songs); + + if (directory->path) { + fprintf(fp, "%s%s\n", DIRECTORY_END, + getDirectoryPath(directory)); + } +} + +static void readDirectoryInfo(FILE * fp, Directory * directory) +{ + char buffer[MAXPATHLEN * 2]; + int bufferSize = MAXPATHLEN * 2; + char *key; + Directory *subDirectory; + int strcmpRet; + char *name; + ListNode *nextDirNode = directory->subDirectories->firstNode; + ListNode *nodeTemp; + + while (myFgets(buffer, bufferSize, fp) + && 0 != strncmp(DIRECTORY_END, buffer, strlen(DIRECTORY_END))) { + if (0 == strncmp(DIRECTORY_DIR, buffer, strlen(DIRECTORY_DIR))) { + key = xstrdup(&(buffer[strlen(DIRECTORY_DIR)])); + if (!myFgets(buffer, bufferSize, fp)) + FATAL("Error reading db, fgets\n"); + /* for compatibility with db's prior to 0.11 */ + if (0 == strncmp(DIRECTORY_MTIME, buffer, + strlen(DIRECTORY_MTIME))) { + if (!myFgets(buffer, bufferSize, fp)) + FATAL("Error reading db, fgets\n"); + } + if (strncmp + (DIRECTORY_BEGIN, buffer, + strlen(DIRECTORY_BEGIN))) { + FATAL("Error reading db at line: %s\n", buffer); + } + name = xstrdup(&(buffer[strlen(DIRECTORY_BEGIN)])); + + while (nextDirNode && (strcmpRet = + strcmp(key, + nextDirNode->key)) > 0) { + nodeTemp = nextDirNode->nextNode; + deleteNodeFromList(directory->subDirectories, + nextDirNode); + nextDirNode = nodeTemp; + } + + if (NULL == nextDirNode) { + subDirectory = newDirectory(name, directory); + insertInList(directory->subDirectories, + key, (void *)subDirectory); + } else if (strcmpRet == 0) { + subDirectory = (Directory *) nextDirNode->data; + nextDirNode = nextDirNode->nextNode; + } else { + subDirectory = newDirectory(name, directory); + insertInListBeforeNode(directory-> + subDirectories, + nextDirNode, -1, key, + (void *)subDirectory); + } + + free(name); + free(key); + readDirectoryInfo(fp, subDirectory); + } else if (0 == strncmp(SONG_BEGIN, buffer, strlen(SONG_BEGIN))) { + readSongInfoIntoList(fp, directory->songs, directory); + } else { + FATAL("Unknown line in db: %s\n", buffer); + } + } + + while (nextDirNode) { + nodeTemp = nextDirNode->nextNode; + deleteNodeFromList(directory->subDirectories, nextDirNode); + nextDirNode = nodeTemp; + } +} + +static void sortDirectory(Directory * directory) +{ + ListNode *node = directory->subDirectories->firstNode; + Directory *subDir; + + sortList(directory->subDirectories); + sortList(directory->songs); + + while (node != NULL) { + subDir = (Directory *) node->data; + sortDirectory(subDir); + node = node->nextNode; + } +} + +int checkDirectoryDB(void) +{ + struct stat st; + char *dbFile; + char *dirPath; + char *dbPath; + + dbFile = getDbFile(); + + /* Check if the file exists */ + if (access(dbFile, F_OK)) { + /* If the file doesn't exist, we can't check if we can write + * it, so we are going to try to get the directory path, and + * see if we can write a file in that */ + dbPath = xstrdup(dbFile); + dirPath = dirname(dbPath); + + /* Check that the parent part of the path is a directory */ + if (stat(dirPath, &st) < 0) { + ERROR("Couldn't stat parent directory of db file " + "\"%s\": %s\n", dbFile, strerror(errno)); + free(dbPath); + return -1; + } + + if (!S_ISDIR(st.st_mode)) { + ERROR("Couldn't create db file \"%s\" because the " + "parent path is not a directory\n", dbFile); + free(dbPath); + return -1; + } + + /* Check if we can write to the directory */ + if (access(dirPath, R_OK | W_OK)) { + ERROR("Can't create db file in \"%s\": %s\n", dirPath, + strerror(errno)); + free(dbPath); + return -1; + + } + + free(dbPath); + return 0; + } + + /* Path exists, now check if it's a regular file */ + if (stat(dbFile, &st) < 0) { + ERROR("Couldn't stat db file \"%s\": %s\n", dbFile, + strerror(errno)); + return -1; + } + + if (!S_ISREG(st.st_mode)) { + ERROR("db file \"%s\" is not a regular file\n", dbFile); + return -1; + } + + /* And check that we can write to it */ + if (access(dbFile, R_OK | W_OK)) { + ERROR("Can't open db file \"%s\" for reading/writing: %s\n", + dbFile, strerror(errno)); + return -1; + } + + return 0; +} + +int writeDirectoryDB(void) +{ + FILE *fp; + char *dbFile = getDbFile(); + struct stat st; + + DEBUG("removing empty directories from DB\n"); + deleteEmptyDirectoriesInDirectory(mp3rootDirectory); + + DEBUG("sorting DB\n"); + + sortDirectory(mp3rootDirectory); + + DEBUG("writing DB\n"); + + while (!(fp = fopen(dbFile, "w")) && errno == EINTR); + if (!fp) { + ERROR("unable to write to db file \"%s\": %s\n", + dbFile, strerror(errno)); + return -1; + } + + /* block signals when writing the db so we don't get a corrupted db */ + fprintf(fp, "%s\n", DIRECTORY_INFO_BEGIN); + fprintf(fp, "%s%s\n", DIRECTORY_MPD_VERSION, VERSION); + fprintf(fp, "%s%s\n", DIRECTORY_FS_CHARSET, getFsCharset()); + fprintf(fp, "%s\n", DIRECTORY_INFO_END); + + writeDirectoryInfo(fp, mp3rootDirectory); + + while (fclose(fp) && errno == EINTR); + + if (stat(dbFile, &st) == 0) + directory_dbModTime = st.st_mtime; + + return 0; +} + +int readDirectoryDB(void) +{ + FILE *fp = NULL; + char *dbFile = getDbFile(); + struct stat st; + + if (!mp3rootDirectory) + mp3rootDirectory = newDirectory(NULL, NULL); + while (!(fp = fopen(dbFile, "r")) && errno == EINTR) ; + if (fp == NULL) { + ERROR("unable to open db file \"%s\": %s\n", + dbFile, strerror(errno)); + return -1; + } + + /* get initial info */ + { + char buffer[100]; + int bufferSize = 100; + int foundFsCharset = 0; + int foundVersion = 0; + + if (!myFgets(buffer, bufferSize, fp)) + FATAL("Error reading db, fgets\n"); + if (0 == strcmp(DIRECTORY_INFO_BEGIN, buffer)) { + while (myFgets(buffer, bufferSize, fp) && + 0 != strcmp(DIRECTORY_INFO_END, buffer)) { + if (0 == strncmp(DIRECTORY_MPD_VERSION, buffer, + strlen(DIRECTORY_MPD_VERSION))) + { + if (foundVersion) + FATAL("already found version in db\n"); + foundVersion = 1; + } else if (0 == + strncmp(DIRECTORY_FS_CHARSET, buffer, + strlen + (DIRECTORY_FS_CHARSET))) { + char *fsCharset; + char *tempCharset; + + if (foundFsCharset) + FATAL("already found fs charset in db\n"); + + foundFsCharset = 1; + + fsCharset = &(buffer[strlen(DIRECTORY_FS_CHARSET)]); + if ((tempCharset = getConfigParamValue(CONF_FS_CHARSET)) + && strcmp(fsCharset, tempCharset)) { + WARNING("Using \"%s\" for the " + "filesystem charset " + "instead of \"%s\"\n", + fsCharset, tempCharset); + WARNING("maybe you need to " + "recreate the db?\n"); + setFsCharset(fsCharset); + } + } else { + FATAL("directory: unknown line in db info: %s\n", + buffer); + } + } + } else { + ERROR("db info not found in db file\n"); + ERROR("you should recreate the db using --create-db\n"); + fseek(fp, 0, SEEK_SET); + return -1; + } + } + + DEBUG("reading DB\n"); + + readDirectoryInfo(fp, mp3rootDirectory); + while (fclose(fp) && errno == EINTR) ; + + stats.numberOfSongs = countSongsIn(STDERR_FILENO, NULL); + stats.dbPlayTime = sumSongTimesIn(STDERR_FILENO, NULL); + + if (stat(dbFile, &st) == 0) + directory_dbModTime = st.st_mtime; + + return 0; +} + +void updateMp3Directory(void) +{ + switch (updateDirectory(mp3rootDirectory)) { + case 0: + /* nothing updated */ + return; + case 1: + if (writeDirectoryDB() < 0) + exit(EXIT_FAILURE); + break; + default: + /* something was updated and db should be written */ + FATAL("problems updating music db\n"); + } + + return; +} + +static int traverseAllInSubDirectory(int fd, Directory * directory, + int (*forEachSong) (int, Song *, + void *), + int (*forEachDir) (int, Directory *, + void *), void *data) +{ + ListNode *node = directory->songs->firstNode; + Song *song; + Directory *dir; + int errFlag = 0; + + if (forEachDir) { + errFlag = forEachDir(fd, directory, data); + if (errFlag) + return errFlag; + } + + if (forEachSong) { + while (node != NULL && !errFlag) { + song = (Song *) node->data; + errFlag = forEachSong(fd, song, data); + node = node->nextNode; + } + if (errFlag) + return errFlag; + } + + node = directory->subDirectories->firstNode; + + while (node != NULL && !errFlag) { + dir = (Directory *) node->data; + errFlag = traverseAllInSubDirectory(fd, dir, forEachSong, + forEachDir, data); + node = node->nextNode; + } + + return errFlag; +} + +int traverseAllIn(int fd, char *name, + int (*forEachSong) (int, Song *, void *), + int (*forEachDir) (int, Directory *, void *), void *data) +{ + Directory *directory; + + if ((directory = getDirectory(name)) == NULL) { + Song *song; + if ((song = getSongFromDB(name)) && forEachSong) { + return forEachSong(fd, song, data); + } + commandError(fd, ACK_ERROR_NO_EXIST, + "directory or file not found"); + return -1; + } + + return traverseAllInSubDirectory(fd, directory, forEachSong, forEachDir, + data); +} + +static void freeAllDirectoryStats(Directory * directory) +{ + ListNode *node = directory->subDirectories->firstNode; + + while (node != NULL) { + freeAllDirectoryStats((Directory *) node->data); + node = node->nextNode; + } + + freeDirectoryStatFromDirectory(directory); +} + +void initMp3Directory(void) +{ + mp3rootDirectory = newDirectory(NULL, NULL); + exploreDirectory(mp3rootDirectory); + freeAllDirectoryStats(mp3rootDirectory); + stats.numberOfSongs = countSongsIn(STDERR_FILENO, NULL); + stats.dbPlayTime = sumSongTimesIn(STDERR_FILENO, NULL); +} + +static Song *getSongDetails(char *file, char **shortnameRet, + Directory ** directoryRet) +{ + void *song = NULL; + Directory *directory; + char *dir = NULL; + char *dup = xstrdup(file); + char *shortname = dup; + char *c = strtok(dup, "/"); + + DEBUG("get song: %s\n", file); + + while (c) { + shortname = c; + c = strtok(NULL, "/"); + } + + if (shortname != dup) { + for (c = dup; c < shortname - 1; c++) { + if (*c == '\0') + *c = '/'; + } + dir = dup; + } + + if (!(directory = getDirectory(dir))) { + free(dup); + return NULL; + } + + if (!findInList(directory->songs, shortname, &song)) { + free(dup); + return NULL; + } + + free(dup); + if (shortnameRet) + *shortnameRet = shortname; + if (directoryRet) + *directoryRet = directory; + return (Song *) song; +} + +Song *getSongFromDB(char *file) +{ + return getSongDetails(file, NULL, NULL); +} + +time_t getDbModTime(void) +{ + return directory_dbModTime; +} |