diff options
Diffstat (limited to 'src/directory.c')
-rw-r--r-- | src/directory.c | 841 |
1 files changed, 841 insertions, 0 deletions
diff --git a/src/directory.c b/src/directory.c new file mode 100644 index 000000000..c933a9f0c --- /dev/null +++ b/src/directory.c @@ -0,0 +1,841 @@ +/* the Music Player Daemon (MPD) + * (c)2003-2004 by Warren Dukes (shank@mercury.chem.pitt.edu) + * 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 "ls.h" +#include "command.h" +#include "tables.h" +#include "utils.h" +#include "path.h" +#include "log.h" +#include "playlist.h" +#include "conf.h" +#include "stats.h" + +#include <string.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <dirent.h> +#include <unistd.h> +#include <stdio.h> +#include <errno.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_SEARCH_ALBUM "album" +#define DIRECTORY_SEARCH_ARTIST "artist" +#define DIRECTORY_SEARCH_TITLE "title" +#define DIRECTORY_SEARCH_FILENAME "filename" + +typedef List DirectoryList; + +typedef struct _Directory { + char * utf8name; + DirectoryList * subDirectories; + struct _Directory * parentDirectory; + SongList * songs; + time_t mtime; /* modification time */ +} Directory; + +Directory * mp3rootDirectory; + +char directorydb[MAXPATHLEN+1]; + +DirectoryList * newDirectoryList(); + +int addToDirectory(Directory * directory, char * shortname, char * name); + +void freeDirectoryList(DirectoryList * list); + +void freeDirectory(Directory * directory); + +int exploreDirectory(Directory * directory); + +int updateDirectory(Directory * directory); + +int addSubDirectoryToDirectory(Directory * directory, char * shortname, char * name); + +Directory * newDirectory(Directory * parentDirectory, char * dirname, time_t mtime) { + Directory * directory; + + directory = malloc(sizeof(Directory)); + + if(dirname!=NULL) directory->utf8name = strdup(dirname); + else directory->utf8name = NULL; + directory->parentDirectory = parentDirectory; + directory->subDirectories = newDirectoryList(); + directory->songs = newSongList(); + if(mtime<0) directory->mtime = isDir(dirname); + else directory->mtime = mtime; + + return directory; +} + +void freeDirectory(Directory * directory) { + freeDirectoryList(directory->subDirectories); + removeSongsFromTables(directory->songs); + deleteSongsFromPlaylist(directory->songs); + freeSongList(directory->songs); + if(directory->utf8name) free(directory->utf8name); + free(directory); +} + +DirectoryList * newDirectoryList() { + return makeList((ListFreeDataFunc *)freeDirectory); +} + +void freeDirectoryList(DirectoryList * directoryList) { + freeList(directoryList); +} + +void removeSongFromDirectory(Directory * directory, char * shortname) { + void * song; + + if(findInList(directory->songs,shortname,&song)) { + removeASongFromTables((Song *)song); + deleteASongFromPlaylist((Song *)song); + deleteFromList(directory->songs,shortname); + } +} + +int updateInDirectory(Directory * directory, char * shortname, char * name) { + time_t mtime; + void * song; + void * subDir; + + if((mtime = isMusic(name))) { + if(0==findInList(directory->songs,shortname,&song)) { + LOG("adding %s\n",name); + addToDirectory(directory,shortname,name); + } + else if(mtime>((Song *)song)->mtime) { + LOG("updating %s\n",name); + updateSongInfo((Song *)song); + } + } + else if((mtime = isDir(name))) { + if(findInList(directory->subDirectories,shortname,(void **)&subDir)) { + updateDirectory((Directory *)subDir); + } + else addSubDirectoryToDirectory(directory,shortname,name); + } + + return 0; +} + +int removeDeletedFromDirectory(Directory * directory) { + DIR * dir; + char cwd[2]; + struct dirent * ent; + char * dirname = directory->utf8name; + List * entList = makeList(free); + void * name; + char * s; + char * utf8; + ListNode * node; + ListNode * tmpNode; + + cwd[0] = '.'; + cwd[1] = '\0'; + if(dirname==NULL) dirname=cwd; + + if((dir = opendir(rmp2amp(utf8ToFsCharset(dirname))))==NULL) return -1; + + while((ent = readdir(dir))) { + if(ent->d_name[0]=='.') continue; /* hide hidden stuff */ + + utf8 = strdup(fsCharsetToUtf8(ent->d_name)); + + if(directory->utf8name) { + s = malloc(strlen(directory->utf8name)+strlen(utf8)+2); + sprintf(s,"%s/%s",directory->utf8name,utf8); + } + else s= strdup(utf8); + insertInList(entList,fsCharsetToUtf8(ent->d_name),s); + free(utf8); + } + + closedir(dir); + + node = directory->subDirectories->firstNode; + while(node) { + tmpNode = node->nextNode; + if(findInList(entList,node->key,&name)) { + if(!isDir((char *)name)) { + deleteFromList(directory->subDirectories,node->key); + } + } + else { + deleteFromList(directory->subDirectories,node->key); + } + node = tmpNode; + } + + node = directory->songs->firstNode; + while(node) { + tmpNode = node->nextNode; + if(findInList(entList,node->key,(void **)&name)) { + if(!isMusic(name)) { + removeSongFromDirectory(directory,node->key); + } + } + else { + removeSongFromDirectory(directory,node->key); + } + node = tmpNode; + } + + freeList(entList); + + return 0; +} + +int updateDirectory(Directory * directory) { + DIR * dir; + char cwd[2]; + struct dirent * ent; + char * s; + char * utf8; + char * dirname = directory->utf8name; + + cwd[0] = '.'; + cwd[1] = '\0'; + if(dirname==NULL) dirname=cwd; + + removeDeletedFromDirectory(directory); + + if((dir = opendir(rmp2amp(utf8ToFsCharset(dirname))))==NULL) return -1; + + while((ent = readdir(dir))) { + if(ent->d_name[0]=='.') continue; /* hide hidden stuff */ + + utf8 = strdup(fsCharsetToUtf8(ent->d_name)); + + if(directory->utf8name) { + s = malloc(strlen(directory->utf8name)+strlen(utf8)+2); + sprintf(s,"%s/%s",directory->utf8name,utf8); + } + else s = strdup(utf8); + updateInDirectory(directory,utf8,s); + free(utf8); + free(s); + } + + closedir(dir); + + if(directory->utf8name) directory->mtime = isDir(directory->utf8name); + + return 0; +} + +int exploreDirectory(Directory * directory) { + DIR * dir; + char cwd[2]; + struct dirent * ent; + char * s; + char * utf8; + char * dirname = directory->utf8name; + + 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; + + LOG("explore: %s\n",dirname); + while((ent = readdir(dir))) { + if(ent->d_name[0]=='.') continue; /* hide hidden stuff */ + + utf8 = strdup(fsCharsetToUtf8(ent->d_name)); + + DEBUG("explore: found: %s (%s)\n",ent->d_name,utf8); + + if(directory->utf8name) { + s = malloc(strlen(directory->utf8name)+strlen(utf8)+2); + sprintf(s,"%s/%s",directory->utf8name,utf8); + } + else s = strdup(utf8); + addToDirectory(directory,utf8,s); + free(utf8); + free(s); + } + + closedir(dir); + + return 0; +} + +int addSubDirectoryToDirectory(Directory * directory, char * shortname, + char * name) +{ + Directory * subDirectory = newDirectory(directory,name,-1); + + insertInList(directory->subDirectories,shortname,subDirectory); + exploreDirectory(subDirectory); + + return 0; +} + +int addToDirectory(Directory * directory, char * shortname, char * name) { + if(isDir(name)) { + return addSubDirectoryToDirectory(directory,shortname,name); + } + else if(isMusic(name)) { + Song * song; + song = addSongToList(directory->songs,shortname,name); + if(!song) return -1; + addSongToTables(song); + return 0; + } + + DEBUG("addToDirectory: %s is not a directory or music\n",name); + + return -1; +} + +void closeMp3Directory() { + freeDirectory(mp3rootDirectory); +} + +Directory * findSubDirectory(Directory * directory,char * name) { + void * subDirectory; + char * dup = strdup(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; +} + +Directory * getSubDirectory(Directory * directory,char * name) { + Directory * subDirectory; + int len; + + if(name==NULL || name[0]=='\0' || strcmp(name,"/")==0) { + return directory; + } + + if((subDirectory = findSubDirectory(directory,name))==NULL) return NULL; + + len = 0; + while(name[len]!='/' && name[len]!='\0') len++; + while(name[len]=='/') len++; + + return getSubDirectory(subDirectory,&(name[len])); +} + +Directory * getDirectory(char * name) { + return getSubDirectory(mp3rootDirectory,name); +} + +int printDirectoryList(FILE * fp, DirectoryList * directoryList) { + ListNode * node = directoryList->firstNode; + Directory * directory; + + while(node!=NULL) { + directory = (Directory *)node->data; + myfprintf(fp,"%s%s\n",DIRECTORY_DIR,directory->utf8name); + node = node->nextNode; + } + + return 0; +} + +int printDirectoryInfo(FILE * fp, char * name) { + Directory * directory; + + if((directory = getDirectory(name))==NULL) { + myfprintf(fp,"%s: directory not found\n",COMMAND_RESPOND_ERROR); + return -1; + } + + printDirectoryList(fp,directory->subDirectories); + printSongInfoFromList(fp,directory->songs); + + return 0; +} + +void writeDirectoryInfo(FILE * fp, Directory * directory) { + ListNode * node = (directory->subDirectories)->firstNode; + Directory * subDirectory; + + if(directory->utf8name) { + myfprintf(fp,"%s%s\n",DIRECTORY_BEGIN,directory->utf8name); + } + + while(node!=NULL) { + subDirectory = (Directory *)node->data; + myfprintf(fp,"%s%s\n",DIRECTORY_DIR,node->key); + myfprintf(fp,"%s%li\n",DIRECTORY_MTIME,(long)subDirectory->mtime); + writeDirectoryInfo(fp,subDirectory); + node = node->nextNode; + } + + writeSongInfoFromList(fp,directory->songs); + + if(directory->utf8name) { + myfprintf(fp,"%s%s\n",DIRECTORY_END,directory->utf8name); + } +} + +void readDirectoryInfo(FILE * fp,Directory * directory) { + char buffer[MAXPATHLEN*2]; + int bufferSize = MAXPATHLEN*2; + char * key; + Directory * subDirectory; + char * name; + time_t mtime; + + while(myFgets(buffer,bufferSize,fp) && 0!=strncmp(DIRECTORY_END,buffer,strlen(DIRECTORY_END))) { + if(0==strncmp(DIRECTORY_DIR,buffer,strlen(DIRECTORY_DIR))) { + key = strdup(&(buffer[strlen(DIRECTORY_DIR)])); + if(myFgets(buffer,bufferSize,fp)<0) { + ERROR("Error reading db\n"); + exit(-1); + } + if(strncmp(DIRECTORY_MTIME,buffer,strlen(DIRECTORY_MTIME))) { + ERROR("Error reading db\n"); + ERROR("%s\n",buffer); + exit(-1); + } + mtime = atoi(&(buffer[strlen(DIRECTORY_BEGIN)])); + if(myFgets(buffer,bufferSize,fp)<0) { + ERROR("Error reading db\n"); + exit(-1); + } + if(strncmp(DIRECTORY_BEGIN,buffer,strlen(DIRECTORY_BEGIN))) { + ERROR("Error reading db\n"); + exit(-1); + } + name = strdup(&(buffer[strlen(DIRECTORY_BEGIN)])); + subDirectory = newDirectory(directory,name,mtime); + insertInList(directory->subDirectories,key,(void *)subDirectory); + free(key); + free(name); + readDirectoryInfo(fp,subDirectory); + } + else if(0==strncmp(SONG_BEGIN,buffer,strlen(SONG_BEGIN))) { + readSongInfoIntoList(fp,directory->songs); + } + else { + ERROR("Unknown line in db: %s\n",buffer); + exit(-1); + } + } +} + +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 writeDirectoryDB() { + FILE * fp; + + sortDirectory(mp3rootDirectory); + stats.numberOfSongs = countSongsIn(stderr,NULL); + + while(!(fp=fopen(directorydb,"w")) && errno==EINTR); + if(!fp) return -1; + + myfprintf(fp,"%s\n",DIRECTORY_INFO_BEGIN); + myfprintf(fp,"%s%s\n",DIRECTORY_MPD_VERSION,VERSION); + myfprintf(fp,"%s%s\n",DIRECTORY_FS_CHARSET,getFsCharset()); + myfprintf(fp,"%s\n",DIRECTORY_INFO_END); + + writeDirectoryInfo(fp,mp3rootDirectory); + while(fclose(fp) && errno==EINTR); + + return 0; +} + +int readDirectoryDB() { + FILE * fp; + + mp3rootDirectory = newDirectory(NULL,NULL,0); + while(!(fp=fopen(directorydb,"r")) && errno==EINTR); + if(!fp) return -1; + + /* get initial info */ + { + char buffer[100]; + int bufferSize = 100; + int foundFsCharset = 0; + int foundVersion = 0; + + if(myFgets(buffer,bufferSize,fp)<0) { + ERROR("Error reading db\n"); + exit(-1); + } + 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) { + ERROR("already found " + "version in db\n"); + exit(-1); + } + foundVersion = 1; + } + else if(0==strncmp(DIRECTORY_FS_CHARSET,buffer, + strlen(DIRECTORY_FS_CHARSET))) + { + char * fsCharset; + + if(foundFsCharset) { + ERROR("already found " + "fs charset in db\n"); + exit(-1); + } + + foundFsCharset = 1; + + fsCharset = &(buffer[strlen( + DIRECTORY_FS_CHARSET)]); + if(getConf()[CONF_FS_CHARSET] && + strcmp(fsCharset, + getFsCharset())) + { + ERROR("Using \"%s\" for the " + "filesystem charset " + "instead of \"%s\"\n", + fsCharset, + getFsCharset()); + ERROR("maybe you need to " + "recreate the db?\n"); + setFsCharset(fsCharset); + } + } + else { + ERROR("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); + } + } + + readDirectoryInfo(fp,mp3rootDirectory); + while(fclose(fp) && errno==EINTR); + + stats.numberOfSongs = countSongsIn(stderr,NULL); + + return 0; +} + +int updateMp3Directory(FILE * fp) { + if(updateDirectory(mp3rootDirectory)<0) { + ERROR("problems updating music db\n"); + myfprintf(fp,"%s problems updating music db\n",COMMAND_RESPOND_ERROR); + return -1; + } + + if(writeDirectoryDB()<0) { + ERROR("problems writing music db file, \"%s\"\n",directorydb); + myfprintf(fp,"%s problems writing music db\n",COMMAND_RESPOND_ERROR); + return -1; + } + + return 0; +} + +int traverseAllInSubDirectory(FILE * fp, Directory * directory, + int (*forEachSong)(FILE *, Song *, void *), + int (*forEachDir)(FILE *, Directory *, void *), + void * data) +{ + ListNode * node = directory->songs->firstNode; + Song * song; + Directory * dir; + int errFlag = 0; + + if(forEachDir) { + errFlag = forEachDir(fp,directory,data); + if(errFlag) return errFlag; + } + + if(forEachSong) { + while(node!=NULL && !errFlag) { + song = (Song *)node->data; + errFlag = forEachSong(fp,song,data); + node = node->nextNode; + } + if(errFlag) return errFlag; + } + + node = directory->subDirectories->firstNode; + + while(node!=NULL && !errFlag) { + dir = (Directory *)node->data; + errFlag = traverseAllInSubDirectory(fp,dir,forEachSong, + forEachDir,data); + node = node->nextNode; + } + + return errFlag; +} + +int traverseAllIn(FILE * fp, char * name, + int (*forEachSong)(FILE *, Song *, void *), + int (*forEachDir)(FILE *, Directory *, void *), + void * data) { + Directory * directory; + + if((directory = getDirectory(name))==NULL) { + Song * song; + if((song = getSong(name)) && forEachSong) { + return forEachSong(fp, song, data); + } + myfprintf(fp,"%s: directory or file not found\n",COMMAND_RESPOND_ERROR); + return -1; + } + + return traverseAllInSubDirectory(fp,directory,forEachSong,forEachDir, + data); +} + +int countSongsInDirectory(FILE * fp, Directory * directory, void * data) { + int * count = (int *)data; + + *count+=directory->songs->numberOfNodes; + + return 0; +} + +int printDirectoryInDirectory(FILE * fp, Directory * directory, void * data) { + if(directory->utf8name) { + myfprintf(fp,"directory: %s\n",directory->utf8name); + } + return 0; +} + +int printSongInDirectory(FILE * fp, Song * song, void * data) { + myfprintf(fp,"file: %s\n",song->utf8file); + return 0; +} + +int searchForAlbumInDirectory(FILE * fp, Song * song, void * string) { + if(song->tag && song->tag->album) { + char * dup = strDupToUpper(song->tag->album); + if(strstr(dup,(char *)string)) printSongInfo(fp,song); + free(dup); + } + return 0; +} + +int searchForArtistInDirectory(FILE * fp, Song * song, void * string) { + if(song->tag && song->tag->artist) { + char * dup = strDupToUpper(song->tag->artist); + if(strstr(dup,(char *)string)) printSongInfo(fp,song); + free(dup); + } + return 0; +} + +int searchForTitleInDirectory(FILE * fp, Song * song, void * string) { + if(song->tag && song->tag->title) { + char * dup = strDupToUpper(song->tag->title); + if(strstr(dup,(char *)string)) printSongInfo(fp,song); + free(dup); + } + return 0; +} + +int searchForFilenameInDirectory(FILE * fp, Song * song, void * string) { + char * dup = strDupToUpper(song->utf8file); + if(strstr(dup,(char *)string)) printSongInfo(fp,song); + free(dup); + return 0; +} + +int searchForSongsIn(FILE * fp, char * name, char * item, char * string) { + char * dup = strDupToUpper(string); + int ret = -1; + + if(strcmp(item,DIRECTORY_SEARCH_ALBUM)==0) { + ret = traverseAllIn(fp,name,searchForAlbumInDirectory,NULL, + (void *)dup); + } + else if(strcmp(item,DIRECTORY_SEARCH_ARTIST)==0) { + ret = traverseAllIn(fp,name,searchForArtistInDirectory,NULL, + (void *)dup); + } + else if(strcmp(item,DIRECTORY_SEARCH_TITLE)==0) { + ret = traverseAllIn(fp,name,searchForTitleInDirectory,NULL, + (void *)dup); + } + else if(strcmp(item,DIRECTORY_SEARCH_FILENAME)==0) { + ret = traverseAllIn(fp,name,searchForFilenameInDirectory,NULL, + (void *)dup); + } + else myfprintf(fp,"%s unknown table\n",COMMAND_RESPOND_ERROR); + + free(dup); + + return ret; +} + +int findAlbumInDirectory(FILE * fp, Song * song, void * string) { + if(song->tag && song->tag->album && + strcmp((char *)string,song->tag->album)==0) + { + printSongInfo(fp,song); + } + + return 0; +} + +int findArtistInDirectory(FILE * fp, Song * song, void * string) { + if(song->tag && song->tag->artist && + strcmp((char *)string,song->tag->artist)==0) + { + printSongInfo(fp,song); + } + + return 0; +} + +int findSongsIn(FILE * fp, char * name, char * item, char * string) { + if(strcmp(item,DIRECTORY_SEARCH_ALBUM)==0) { + return traverseAllIn(fp,name,findAlbumInDirectory,NULL, + (void *)string); + } + else if(strcmp(item,DIRECTORY_SEARCH_ARTIST)==0) { + return traverseAllIn(fp,name,findArtistInDirectory,NULL, + (void *)string); + } + + myfprintf(fp,"%s unknown table\n",COMMAND_RESPOND_ERROR); + return -1; +} + +int printAllIn(FILE * fp, char * name) { + return traverseAllIn(fp,name,printSongInDirectory, + printDirectoryInDirectory,NULL); +} + +int directoryAddSongToPlaylist(FILE * fp, Song * song, void * data) { + return addSongToPlaylist(fp,song); +} + +int addAllIn(FILE * fp, char * name) { + return traverseAllIn(fp,name,directoryAddSongToPlaylist,NULL,NULL); +} + +int directoryPrintSongInfo(FILE * fp, Song * song, void * data) { + return printSongInfo(fp,song); +} + +int printInfoForAllIn(FILE * fp, char * name) { + return traverseAllIn(fp,name,directoryPrintSongInfo,NULL,NULL); +} + +int countSongsIn(FILE * fp, char * name) { + int count = 0; + void * ptr = (void *)&count; + + traverseAllIn(fp,name,NULL,countSongsInDirectory,ptr); + + return count; +} + +void initMp3Directory() { + mp3rootDirectory = newDirectory(NULL,NULL,0); + exploreDirectory(mp3rootDirectory); +} + +Song * getSong(char * file) { + void * song; + Directory * directory; + char * dir = NULL; + char * dup = strdup(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 = getSubDirectory(mp3rootDirectory,dir))) { + free(dup); + return NULL; + } + + if(!findInList(directory->songs,shortname,&song)) { + free(dup); + return NULL; + } + + free(dup); + return (Song *)song; +} + +time_t getDbModTime() { + time_t mtime = 0; + struct stat st; + + if(stat(directorydb,&st)==0) mtime = st.st_mtime; + + return mtime; +} |