diff options
author | Eric Wong <normalperson@yhbt.net> | 2008-10-11 23:39:50 -0700 |
---|---|---|
committer | Eric Wong <normalperson@yhbt.net> | 2008-10-11 23:39:50 -0700 |
commit | 0df62a2c3cb7af88347d40a17cc336b5d1740f62 (patch) | |
tree | 25040833144c3e24f6b4702d9b745cd068b5371a | |
parent | 3456e2de5bf90207d8149a842bb12c3f9bdd218f (diff) | |
parent | 6e2b0ca9edaed200f250ef487701ad161aa4a168 (diff) | |
download | mpd-0df62a2c3cb7af88347d40a17cc336b5d1740f62.tar.gz mpd-0df62a2c3cb7af88347d40a17cc336b5d1740f62.tar.xz mpd-0df62a2c3cb7af88347d40a17cc336b5d1740f62.zip |
Merge branch 'mk/directory'
* mk/directory: (59 commits)
directory: don't use identical struct and variable names
update: replaced update_return with global "modified" flag
update: make the variable "progress" static
update: don't print debug message when song was not modified
update: fix memory leak in directory_update_init()
update: make the job id unsigned
update: job ID must be positive
update: check progress!=IDLE in reap_update_task()
update: fixed stack corruption due to pthread_join() call
updated: always call removeDeletedFromDirectory()
update: eliminated addSubDirectoryToDirectory()
update: make the "song" variable more local
update: do the recursive directory check only once
update: copy stat to new directory
update: avoid duplicate stat() calls
update: rewrote updatePath() using updateInDirectory()
update: don't export updateDirectory()
update: pass const pointer to addSubDirectoryToDirectory()
update: never pass root path to updatePath()
update: merged addDirectoryPathToDB() into addParentPathToDB()
...
Conflicts:
src/song.c
-rw-r--r-- | src/Makefile.am | 9 | ||||
-rw-r--r-- | src/command.c | 16 | ||||
-rw-r--r-- | src/database.c | 314 | ||||
-rw-r--r-- | src/database.h | 48 | ||||
-rw-r--r-- | src/dbUtils.c | 63 | ||||
-rw-r--r-- | src/directory.c | 1041 | ||||
-rw-r--r-- | src/directory.h | 83 | ||||
-rw-r--r-- | src/directory_print.c | 47 | ||||
-rw-r--r-- | src/directory_print.h | 26 | ||||
-rw-r--r-- | src/directory_save.c | 120 | ||||
-rw-r--r-- | src/directory_save.h | 30 | ||||
-rw-r--r-- | src/dirvec.c | 70 | ||||
-rw-r--r-- | src/dirvec.h | 69 | ||||
-rw-r--r-- | src/inputPlugin.c | 1 | ||||
-rw-r--r-- | src/inputPlugin.h | 2 | ||||
-rw-r--r-- | src/locate.c | 16 | ||||
-rw-r--r-- | src/locate.h | 6 | ||||
-rw-r--r-- | src/log.h | 2 | ||||
-rw-r--r-- | src/main.c | 18 | ||||
-rw-r--r-- | src/playlist.c | 61 | ||||
-rw-r--r-- | src/playlist.h | 4 | ||||
-rw-r--r-- | src/sig_handlers.c | 5 | ||||
-rw-r--r-- | src/song.c | 139 | ||||
-rw-r--r-- | src/song.h | 45 | ||||
-rw-r--r-- | src/songvec.c | 23 | ||||
-rw-r--r-- | src/songvec.h | 11 | ||||
-rw-r--r-- | src/stats.c | 8 | ||||
-rw-r--r-- | src/storedPlaylist.c | 12 | ||||
-rw-r--r-- | src/storedPlaylist.h | 2 | ||||
-rw-r--r-- | src/update.c | 444 | ||||
-rw-r--r-- | src/update.h | 34 |
31 files changed, 1471 insertions, 1298 deletions
diff --git a/src/Makefile.am b/src/Makefile.am index bfefd5eec..6a7497286 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -39,9 +39,13 @@ mpd_headers = \ command.h \ condition.h \ conf.h \ + database.h \ dbUtils.h \ decode.h \ directory.h \ + directory_print.h \ + directory_save.h \ + update.h \ dirvec.h \ gcc.h \ inputPlugin.h \ @@ -110,9 +114,14 @@ mpd_SOURCES = \ command.c \ condition.c \ conf.c \ + database.c \ dbUtils.c \ decode.c \ directory.c \ + directory_print.c \ + directory_save.c \ + dirvec.c \ + update.c \ inputPlugin.c \ inputStream.c \ inputStream_file.c \ diff --git a/src/command.c b/src/command.c index f60f4e69e..518bf5f0d 100644 --- a/src/command.c +++ b/src/command.c @@ -19,7 +19,10 @@ #include "command.h" #include "playlist.h" #include "ls.h" +#include "database.h" #include "directory.h" +#include "directory_print.h" +#include "update.h" #include "volume.h" #include "stats.h" #include "myfprintf.h" @@ -556,15 +559,18 @@ static int handleLsInfo(int fd, mpd_unused int *permission, int argc, char *argv[]) { const char *path = ""; + const struct directory *dir; if (argc == 2) path = argv[1]; - if (printDirectoryInfo(fd, path) < 0) { + if (!(dir = db_get_directory(path))) { commandError(fd, ACK_ERROR_NO_EXIST, "directory not found"); return -1; } + directory_print(fd, dir); + if (isRootDirectory(path)) return lsPlaylists(fd, path); @@ -781,14 +787,12 @@ static int handlePlaylistMove(int fd, mpd_unused int *permission, static int print_update_result(int fd, int ret) { - if (ret >= 0) { + if (ret > 0) { fdprintf(fd, "updating_db: %i\n", ret); return 0; } - if (ret == -2) - commandError(fd, ACK_ERROR_ARG, "invalid path"); - else - commandError(fd, ACK_ERROR_UPDATE_ALREADY, "already updating"); + assert(!ret); + commandError(fd, ACK_ERROR_UPDATE_ALREADY, "already updating"); return -1; } diff --git a/src/database.c b/src/database.c new file mode 100644 index 000000000..dde57ce6a --- /dev/null +++ b/src/database.c @@ -0,0 +1,314 @@ +/* the Music Player Daemon (MPD) + * Copyright (C) 2003-2007 by Warren Dukes (warren.dukes@gmail.com) + * Copyright (C) 2008 Max Kellermann <max@duempel.org> + * 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 "database.h" +#include "directory.h" +#include "directory_save.h" +#include "song.h" +#include "conf.h" +#include "log.h" +#include "ls.h" +#include "path.h" +#include "stats.h" +#include "utils.h" +#include "dbUtils.h" +#include "update.h" +#include "os_compat.h" +#include "myfprintf.h" + +static struct directory *music_root; + +static time_t directory_dbModTime; + +void db_init(void) +{ + music_root = directory_new("", NULL); + + if (!directory_update_init(NULL)) + FATAL("directory update failed\n"); + + do { + my_usleep(100000); + reap_update_task(); + } while (isUpdatingDB()); + + stats.numberOfSongs = countSongsIn(NULL); + stats.dbPlayTime = sumSongTimesIn(NULL); +} + +void db_finish(void) +{ + directory_free(music_root); +} + +struct directory * db_get_root(void) +{ + assert(music_root != NULL); + + return music_root; +} + +struct directory * db_get_directory(const char *name) +{ + if (name == NULL) + return music_root; + + return directory_get_subdir(music_root, name); +} + +struct mpd_song *db_get_song(const char *file) +{ + struct mpd_song *song = NULL; + struct directory *dir; + char *dirpath = NULL; + char *duplicated = xstrdup(file); + char *shortname = strrchr(duplicated, '/'); + + DEBUG("get song: %s\n", file); + + if (!shortname) { + shortname = duplicated; + } else { + *shortname = '\0'; + ++shortname; + dirpath = duplicated; + } + + if (!(dir = db_get_directory(dirpath))) + goto out; + if (!(song = songvec_find(&dir->songs, shortname))) + goto out; + assert(song->parent == dir); + +out: + free(duplicated); + return song; +} + +int db_walk(const char *name, + int (*forEachSong) (struct mpd_song *, void *), + int (*forEachDir) (struct directory *, void *), void *data) +{ + struct directory *dir; + + if ((dir = db_get_directory(name)) == NULL) { + struct mpd_song *song; + if ((song = db_get_song(name)) && forEachSong) { + return forEachSong(song, data); + } + return -1; + } + + return directory_walk(dir, forEachSong, forEachDir, data); +} + +static char *db_get_file(void) +{ + ConfigParam *param = parseConfigFilePath(CONF_DB_FILE, 1); + + assert(param); + assert(param->value); + + return param->value; +} + +int db_check(void) +{ + struct stat st; + char *dbFile = db_get_file(); + + /* 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 */ + char dirPath[MPD_PATH_MAX]; + parent_path(dirPath, dbFile); + if (*dirPath == '\0') + strcpy(dirPath, "/"); + + /* 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)); + 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); + 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)); + return -1; + } + + 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 db_save(void) +{ + int fd; + char *dbFile = db_get_file(); + struct stat st; + + DEBUG("removing empty directories from DB\n"); + directory_prune_empty(music_root); + + DEBUG("sorting DB\n"); + + directory_sort(music_root); + + DEBUG("writing DB\n"); + + fd = open(dbFile, O_WRONLY|O_TRUNC|O_CREAT, 0666); + if (fd < 0) { + ERROR("unable to write to db file \"%s\": %s\n", + dbFile, strerror(errno)); + return -1; + } + + /* + * TODO: block signals when writing the db so we don't get a corrupted + * db (or unexpected failures). fdprintf() needs better error handling + */ + fdprintf(fd, + DIRECTORY_INFO_BEGIN "\n" + DIRECTORY_MPD_VERSION VERSION "\n" + DIRECTORY_FS_CHARSET "%s\n" + DIRECTORY_INFO_END "\n", getFsCharset()); + + if (directory_save(fd, music_root) < 0) { + ERROR("Failed to write to database file: %s\n", + strerror(errno)); + xclose(fd); + return -1; + } + xclose(fd); + + if (stat(dbFile, &st) == 0) + directory_dbModTime = st.st_mtime; + + return 0; +} + +int db_load(void) +{ + FILE *fp = NULL; + char *dbFile = db_get_file(); + struct stat st; + char buffer[100]; + int foundFsCharset = 0; + int foundVersion = 0; + + if (!music_root) + music_root = directory_new("", 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 */ + if (!myFgets(buffer, sizeof(buffer), fp)) + FATAL("Error reading db, fgets\n"); + + if (0 != strcmp(DIRECTORY_INFO_BEGIN, buffer)) { + ERROR("db info not found in db file\n"); + ERROR("you should recreate the db using --create-db\n"); + while (fclose(fp) && errno == EINTR) ; + return -1; + } + + while (myFgets(buffer, sizeof(buffer), fp) && + 0 != strcmp(DIRECTORY_INFO_END, buffer)) { + if (!prefixcmp(buffer, DIRECTORY_MPD_VERSION)) { + if (foundVersion) + FATAL("already found version in db\n"); + foundVersion = 1; + } else if (!prefixcmp(buffer, 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); + } + + DEBUG("reading DB\n"); + + directory_load(fp, music_root); + while (fclose(fp) && errno == EINTR) ; + + stats.numberOfSongs = countSongsIn(NULL); + stats.dbPlayTime = sumSongTimesIn(NULL); + + if (stat(dbFile, &st) == 0) + directory_dbModTime = st.st_mtime; + + return 0; +} + +time_t db_get_mtime(void) +{ + return directory_dbModTime; +} diff --git a/src/database.h b/src/database.h new file mode 100644 index 000000000..0eb7d535d --- /dev/null +++ b/src/database.h @@ -0,0 +1,48 @@ +/* the Music Player Daemon (MPD) + * Copyright (C) 2003-2007 by Warren Dukes (warren.dukes@gmail.com) + * Copyright (C) 2008 Max Kellermann <max@duempel.org> + * 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 + */ + +#ifndef DATABASE_H +#define DATABASE_H + +#include "os_compat.h" +#include "directory.h" + +void db_init(void); + +void db_finish(void); + +struct directory * db_get_root(void); + +struct directory * db_get_directory(const char *name); + +struct mpd_song * db_get_song(const char *file); + +int db_walk(const char *name, + int (*forEachSong) (struct mpd_song *, void *), + int (*forEachDir) (struct directory *, void *), void *data); + +int db_check(void); + +int db_save(void); + +int db_load(void); + +time_t db_get_mtime(void); + +#endif /* DATABASE_H */ diff --git a/src/dbUtils.c b/src/dbUtils.c index bd990e96d..1fadb232e 100644 --- a/src/dbUtils.c +++ b/src/dbUtils.c @@ -18,7 +18,7 @@ #include "dbUtils.h" -#include "directory.h" +#include "database.h" #include "myfprintf.h" #include "utils.h" #include "playlist.h" @@ -45,22 +45,20 @@ typedef struct _SearchStats { unsigned long playTime; } SearchStats; -static int countSongsInDirectory(Directory * directory, - void *data) +static int countSongsInDirectory(struct directory *dir, void *data) { int *count = (int *)data; - *count += directory->songs.nr; + *count += dir->songs.nr; return 0; } -static int printDirectoryInDirectory(Directory * directory, void *data) +static int printDirectoryInDirectory(struct directory *dir, void *data) { int fd = (int)(size_t)data; - if (directory->path) { - fdprintf(fd, "directory: %s\n", getDirectoryPath(directory)); - } + if (!isRootDirectory(dir->path)) + fdprintf(fd, "directory: %s\n", directory_get_path(dir)); return 0; } @@ -69,7 +67,7 @@ struct search_data { LocateTagItemArray array; }; -static int searchInDirectory(Song * song, void *_data) +static int searchInDirectory(struct mpd_song * song, void *_data) { struct search_data *data = _data; int fd = data->fd; @@ -99,7 +97,7 @@ int searchForSongsIn(int fd, const char *name, int numItems, data.array.numItems = numItems; data.array.items = items; - ret = traverseAllIn(name, searchInDirectory, NULL, &data); + ret = db_walk(name, searchInDirectory, NULL, &data); for (i = 0; i < numItems; i++) { free(items[i].needle); @@ -111,7 +109,7 @@ int searchForSongsIn(int fd, const char *name, int numItems, return ret; } -static int findInDirectory(Song * song, void *_data) +static int findInDirectory(struct mpd_song * song, void *_data) { struct search_data *data = _data; int fd = data->fd; @@ -131,7 +129,7 @@ int findSongsIn(int fd, const char *name, int numItems, LocateTagItem * items) data.array.numItems = numItems; data.array.items = items; - return traverseAllIn(name, findInDirectory, NULL, &data); + return db_walk(name, findInDirectory, NULL, &data); } static void printSearchStats(int fd, SearchStats *stats) @@ -140,7 +138,7 @@ static void printSearchStats(int fd, SearchStats *stats) fdprintf(fd, "playtime: %li\n", stats->playTime); } -static int searchStatsInDirectory(Song * song, void *data) +static int searchStatsInDirectory(struct mpd_song * song, void *data) { SearchStats *stats = data; @@ -165,7 +163,7 @@ int searchStatsForSongsIn(int fd, const char *name, int numItems, stats.numberOfSongs = 0; stats.playTime = 0; - ret = traverseAllIn(name, searchStatsInDirectory, NULL, &stats); + ret = db_walk(name, searchStatsInDirectory, NULL, &stats); if (ret == 0) printSearchStats(fd, &stats); @@ -174,11 +172,12 @@ int searchStatsForSongsIn(int fd, const char *name, int numItems, int printAllIn(int fd, const char *name) { - return traverseAllIn(name, song_print_url_x, + return db_walk(name, song_print_url_x, printDirectoryInDirectory, (void*)(size_t)fd); } -static int directoryAddSongToPlaylist(Song * song, mpd_unused void *data) +static int +directoryAddSongToPlaylist(struct mpd_song * song, mpd_unused void *data) { return addSongToPlaylist(song, NULL); } @@ -187,7 +186,7 @@ struct add_data { const char *path; }; -static int directoryAddSongToStoredPlaylist(Song *song, void *_data) +static int directoryAddSongToStoredPlaylist(struct mpd_song *song, void *_data) { struct add_data *data = _data; @@ -198,7 +197,7 @@ static int directoryAddSongToStoredPlaylist(Song *song, void *_data) int addAllIn(const char *name) { - return traverseAllIn(name, directoryAddSongToPlaylist, NULL, NULL); + return db_walk(name, directoryAddSongToPlaylist, NULL, NULL); } int addAllInToStoredPlaylist(const char *name, const char *utf8file) @@ -206,11 +205,11 @@ int addAllInToStoredPlaylist(const char *name, const char *utf8file) struct add_data data; data.path = utf8file; - return traverseAllIn(name, directoryAddSongToStoredPlaylist, NULL, + return db_walk(name, directoryAddSongToStoredPlaylist, NULL, &data); } -static int sumSongTime(Song * song, void *data) +static int sumSongTime(struct mpd_song * song, void *data) { unsigned long *sum_time = (unsigned long *)data; @@ -222,7 +221,7 @@ static int sumSongTime(Song * song, void *data) int printInfoForAllIn(int fd, const char *name) { - return traverseAllIn(name, song_print_info_x, + return db_walk(name, song_print_info_x, printDirectoryInDirectory, (void*)(size_t)fd); } @@ -231,7 +230,7 @@ int countSongsIn(const char *name) int count = 0; void *ptr = (void *)&count; - traverseAllIn(name, NULL, countSongsInDirectory, ptr); + db_walk(name, NULL, countSongsInDirectory, ptr); return count; } @@ -241,7 +240,7 @@ unsigned long sumSongTimesIn(const char *name) unsigned long dbPlayTime = 0; void *ptr = (void *)&dbPlayTime; - traverseAllIn(name, sumSongTime, NULL, ptr); + db_walk(name, sumSongTime, NULL, ptr); return dbPlayTime; } @@ -264,7 +263,7 @@ static void freeListCommandItem(ListCommandItem * item) } static void visitTag(int fd, struct strset *set, - Song * song, enum tag_type tagType) + struct mpd_song * song, enum tag_type tagType) { int i; struct mpd_tag *tag = song->tag; @@ -292,7 +291,7 @@ struct list_tags_data { struct strset *set; }; -static int listUniqueTagsInDirectory(Song * song, void *_data) +static int listUniqueTagsInDirectory(struct mpd_song * song, void *_data) { struct list_tags_data *data = _data; ListCommandItem *item = data->item; @@ -318,7 +317,7 @@ int listAllUniqueTags(int fd, int type, int numConditionals, data.set = strset_new(); } - ret = traverseAllIn(NULL, listUniqueTagsInDirectory, NULL, &data); + ret = db_walk(NULL, listUniqueTagsInDirectory, NULL, &data); if (type >= 0 && type <= TAG_NUM_OF_ITEM_TYPES) { const char *value; @@ -336,20 +335,20 @@ int listAllUniqueTags(int fd, int type, int numConditionals, return ret; } -static int sumSavedFilenameMemoryInDirectory(Directory * dir, void *data) +static int sumSavedFilenameMemoryInDirectory(struct directory *dir, void *data) { int *sum = data; - if (!dir->path) + if (!isRootDirectory(dir->path)) return 0; - *sum += (strlen(getDirectoryPath(dir)) + 1 - sizeof(Directory *)) * - dir->songs.nr; + *sum += (strlen(directory_get_path(dir)) + 1 + - sizeof(struct directory *)) * dir->songs.nr; return 0; } -static int sumSavedFilenameMemoryInSong(Song * song, void *data) +static int sumSavedFilenameMemoryInSong(struct mpd_song * song, void *data) { int *sum = data; @@ -362,7 +361,7 @@ void printSavedMemoryFromFilenames(void) { int sum = 0; - traverseAllIn(NULL, sumSavedFilenameMemoryInSong, + db_walk(NULL, sumSavedFilenameMemoryInSong, sumSavedFilenameMemoryInDirectory, (void *)&sum); DEBUG("saved memory from filenames: %i\n", sum); diff --git a/src/directory.c b/src/directory.c index 6dbf7a638..b90b477fd 100644 --- a/src/directory.c +++ b/src/directory.c @@ -18,664 +18,68 @@ #include "directory.h" -#include "conf.h" -#include "log.h" -#include "ls.h" -#include "path.h" -#include "playlist.h" -#include "stats.h" #include "utils.h" #include "ack.h" #include "myfprintf.h" -#include "dbUtils.h" -#include "main_notify.h" #include "dirvec.h" -#define DIRECTORY_DIR "directory: " -#define DIRECTORY_MTIME "mtime: " /* DEPRECATED, noop-read-only */ -#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: " - -enum update_return { - UPDATE_RETURN_ERROR = -1, - UPDATE_RETURN_NOUPDATE = 0, - UPDATE_RETURN_UPDATED = 1 -}; - -enum update_progress { - UPDATE_PROGRESS_IDLE = 0, - UPDATE_PROGRESS_RUNNING = 1, - UPDATE_PROGRESS_DONE = 2 -} progress; - -/* make this dynamic?, or maybe this is big enough... */ -static char *update_paths[32]; -static size_t update_paths_nr; - -static Directory *music_root; - -static time_t directory_dbModTime; - -static pthread_t update_thr; - -static const int update_task_id_max = 1 << 15; - -static int update_task_id; - -static Song *delete; - -static struct condition delete_cond; - -static int addToDirectory(Directory * directory, const char *name); - -static void freeDirectory(Directory * directory); - -static enum update_return exploreDirectory(Directory * directory); - -static enum update_return updateDirectory(Directory * directory); - -static void deleteEmptyDirectoriesInDirectory(Directory * directory); - -static void delete_song(Directory *dir, Song *del); - -static enum update_return addSubDirectoryToDirectory(Directory * directory, - const char *name, struct stat *st); - -static Directory *getDirectory(const char *name); - -static enum update_return updatePath(const 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; -} - -int isUpdatingDB(void) +struct directory * directory_new(const char *dirname, struct directory * parent) { - return (progress != UPDATE_PROGRESS_IDLE) ? update_task_id : 0; -} + struct directory *dir; -static void * update_task(void *_path) -{ - enum update_return ret = UPDATE_RETURN_NOUPDATE; + assert(dirname != NULL); + assert((*dirname == 0) == (parent == NULL)); - if (_path) { - ret = updatePath((char *)_path); - free(_path); - } else { - ret = updateDirectory(music_root); - } + dir = xcalloc(1, sizeof(struct directory)); + dir->path = xstrdup(dirname); + dir->parent = parent; - if (ret == UPDATE_RETURN_UPDATED && writeDirectoryDB() < 0) - ret = UPDATE_RETURN_ERROR; - progress = UPDATE_PROGRESS_DONE; - wakeup_main_task(); - return (void *)ret; + return dir; } -static void spawn_update_task(char *path) +void directory_free(struct directory *dir) { - pthread_attr_t attr; - - assert(pthread_equal(pthread_self(), main_task)); - - progress = UPDATE_PROGRESS_RUNNING; - pthread_attr_init(&attr); - if (pthread_create(&update_thr, &attr, update_task, path)) - FATAL("Failed to spawn update task: %s\n", strerror(errno)); - if (++update_task_id > update_task_id_max) - update_task_id = 1; - DEBUG("spawned thread for update job id %i\n", update_task_id); -} - -void reap_update_task(void) -{ - enum update_return ret; - - assert(pthread_equal(pthread_self(), main_task)); - - cond_enter(&delete_cond); - if (delete) { - char tmp[MPD_PATH_MAX]; - LOG("removing: %s\n", get_song_url(tmp, delete)); - deleteASongFromPlaylist(delete); - delete = NULL; - cond_signal(&delete_cond); - } - cond_leave(&delete_cond); - - if (progress != UPDATE_PROGRESS_DONE) - return; - if (pthread_join(update_thr, (void **)&ret)) - FATAL("error joining update thread: %s\n", strerror(errno)); - if (ret == UPDATE_RETURN_UPDATED) - playlistVersionChange(); - - if (update_paths_nr) { - char *path = update_paths[0]; - memmove(&update_paths[0], &update_paths[1], - --update_paths_nr * sizeof(char *)); - spawn_update_task(path); - } else { - progress = UPDATE_PROGRESS_IDLE; - } -} - -int directory_update_init(char *path) -{ - assert(pthread_equal(pthread_self(), main_task)); - - if (progress != UPDATE_PROGRESS_IDLE) { - int next_task_id; - - if (!path) - return -1; - if (update_paths_nr == ARRAY_SIZE(update_paths)) - return -1; - assert(update_paths_nr < ARRAY_SIZE(update_paths)); - update_paths[update_paths_nr++] = path; - next_task_id = update_task_id + update_paths_nr; - - return next_task_id > update_task_id_max ? 1 : next_task_id; - } - spawn_update_task(path); - return update_task_id; -} - -static void directory_set_stat(Directory * dir, const struct stat *st) -{ - dir->inode = st->st_ino; - dir->device = st->st_dev; - dir->stat = 1; -} - -static Directory *newDirectory(const char *dirname, Directory * parent) -{ - Directory *directory; - - directory = xcalloc(1, sizeof(Directory)); - - if (dirname && strlen(dirname)) - directory->path = xstrdup(dirname); - directory->parent = parent; - - return directory; -} - -static void freeDirectory(Directory * directory) -{ - dirvec_destroy(&directory->children); - songvec_destroy(&directory->songs); - if (directory->path) - free(directory->path); - free(directory); + dirvec_destroy(&dir->children); + songvec_destroy(&dir->songs); + free(dir->path); + free(dir); /* this resets last dir returned */ - /*getDirectoryPath(NULL); */ -} - -static void delete_song(Directory *dir, Song *del) -{ - /* first, prevent traversers in main task from getting this */ - songvec_delete(&dir->songs, del); - - /* now take it out of the playlist (in the main_task) */ - cond_enter(&delete_cond); - assert(!delete); - delete = del; - wakeup_main_task(); - do { cond_wait(&delete_cond); } while (delete); - cond_leave(&delete_cond); - - /* finally, all possible references gone, free it */ - freeJustSong(del); + /*directory_get_path(NULL); */ } -static void deleteEmptyDirectoriesInDirectory(Directory * directory) +void directory_prune_empty(struct directory *dir) { int i; - struct dirvec *dv = &directory->children; + struct dirvec *dv = &dir->children; for (i = dv->nr; --i >= 0; ) { - deleteEmptyDirectoriesInDirectory(dv->base[i]); - if (!dv->base[i]->children.nr && !dv->base[i]->songs.nr) + directory_prune_empty(dv->base[i]); + if (directory_is_empty(dv->base[i])) dirvec_delete(dv, dv->base[i]); } if (!dv->nr) dirvec_destroy(dv); } -static enum update_return -updateInDirectory(Directory * directory, const char *name) +struct directory * +directory_get_subdir(struct directory *dir, const char *name) { - Song *song; - struct stat st; - - if (myStat(name, &st)) - return UPDATE_RETURN_ERROR; - - if (S_ISREG(st.st_mode) && hasMusicSuffix(name, 0)) { - const char *shortname = mpd_basename(name); - - if (!(song = songvec_find(&directory->songs, shortname))) { - addToDirectory(directory, name); - return UPDATE_RETURN_UPDATED; - } else if (st.st_mtime != song->mtime) { - LOG("updating %s\n", name); - if (updateSongInfo(song) < 0) - delete_song(directory, song); - return UPDATE_RETURN_UPDATED; - } - } else if (S_ISDIR(st.st_mode)) { - Directory *subdir = dirvec_find(&directory->children, name); - if (subdir) { - assert(directory == subdir->parent); - directory_set_stat(subdir, &st); - return updateDirectory(subdir); - } else { - return addSubDirectoryToDirectory(directory, name, &st); - } - } - - return UPDATE_RETURN_NOUPDATE; -} - -/* we don't look at hidden files nor files with newlines in them */ -static int skip_path(const char *path) -{ - return (path[0] == '.' || strchr(path, '\n')) ? 1 : 0; -} - -struct delete_data { - char *tmp; - Directory *dir; - enum update_return ret; -}; - -/* passed to songvec_for_each */ -static int delete_song_if_removed(Song *song, void *_data) -{ - struct delete_data *data = _data; - - data->tmp = get_song_url(data->tmp, song); - assert(data->tmp); - - if (!isFile(data->tmp, NULL)) { - delete_song(data->dir, song); - data->ret = UPDATE_RETURN_UPDATED; - } - return 0; -} - -static enum update_return -removeDeletedFromDirectory(char *path_max_tmp, Directory * directory) -{ - enum update_return ret = UPDATE_RETURN_NOUPDATE; - int i; - struct dirvec *dv = &directory->children; - struct delete_data data; - - for (i = dv->nr; --i >= 0; ) { - if (isDir(dv->base[i]->path)) - continue; - LOG("removing directory: %s\n", dv->base[i]->path); - dirvec_delete(dv, dv->base[i]); - ret = UPDATE_RETURN_UPDATED; - } - - data.dir = directory; - data.tmp = path_max_tmp; - data.ret = ret; - songvec_for_each(&directory->songs, delete_song_if_removed, &data); - - return data.ret; -} - -static Directory *addDirectoryPathToDB(const char *utf8path) -{ - char path_max_tmp[MPD_PATH_MAX]; - char *parent; - Directory *parentDirectory; - Directory *directory; - Song *conflicting; - - parent = parent_path(path_max_tmp, utf8path); - - if (strlen(parent) == 0) - parentDirectory = music_root; - else - parentDirectory = addDirectoryPathToDB(parent); - - if (!parentDirectory) - return NULL; - - if ((directory = dirvec_find(&parentDirectory->children, utf8path))) { - assert(parentDirectory == directory->parent); - } else { - struct stat st; - if (myStat(utf8path, &st) < 0 || - inodeFoundInParent(parentDirectory, st.st_ino, st.st_dev)) - return NULL; - else { - directory = newDirectory(utf8path, parentDirectory); - dirvec_add(&parentDirectory->children, directory); - } - } - - /* if we're adding directory paths, make sure to delete filenames - with potentially the same name */ - conflicting = songvec_find(&parentDirectory->songs, - mpd_basename(directory->path)); - if (conflicting) - delete_song(parentDirectory, conflicting); - - return directory; -} - -static Directory *addParentPathToDB(const char *utf8path) -{ - char *parent; - char path_max_tmp[MPD_PATH_MAX]; - Directory *parentDirectory; - - parent = parent_path(path_max_tmp, utf8path); - - if (strlen(parent) == 0) - parentDirectory = music_root; - else - parentDirectory = addDirectoryPathToDB(parent); - - if (!parentDirectory) - return NULL; - - return (Directory *) parentDirectory; -} - -static enum update_return updatePath(const char *utf8path) -{ - Directory *directory; - Directory *parentDirectory; - Song *song; - time_t mtime; - enum update_return ret = UPDATE_RETURN_NOUPDATE; - char path_max_tmp[MPD_PATH_MAX]; - - assert(utf8path); - - /* if path is in the DB try to update it, or else delete it */ - if ((directory = getDirectory(utf8path))) { - parentDirectory = directory->parent; - - /* if this update directory is successfull, we are done */ - if ((ret = updateDirectory(directory)) >= 0) { - sortDirectory(directory); - return ret; - } - /* we don't want to delete the root directory */ - else if (directory == music_root) { - return UPDATE_RETURN_NOUPDATE; - } - /* if updateDirectory fails, means we should delete it */ - else { - LOG("removing directory: %s\n", utf8path); - dirvec_delete(&parentDirectory->children, directory); - ret = UPDATE_RETURN_UPDATED; - /* don't return, path maybe a song now */ - } - } else if ((song = getSongFromDB(utf8path))) { - parentDirectory = song->parentDir; - if (!parentDirectory->stat - && statDirectory(parentDirectory) < 0) { - return UPDATE_RETURN_NOUPDATE; - } - /* if this song update is successful, we are done */ - else if (!inodeFoundInParent(parentDirectory->parent, - parentDirectory->inode, - parentDirectory->device) && - isMusic(get_song_url(path_max_tmp, song), &mtime, 0)) { - if (song->mtime == mtime) - return UPDATE_RETURN_NOUPDATE; - else if (updateSongInfo(song) == 0) - return UPDATE_RETURN_UPDATED; - else { - delete_song(parentDirectory, song); - return UPDATE_RETURN_UPDATED; - } - } - /* if updateDirectory fails, means we should delete it */ - else { - delete_song(parentDirectory, song); - ret = UPDATE_RETURN_UPDATED; - /* 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(utf8path) || isMusic(utf8path, NULL, 0)) { - parentDirectory = addParentPathToDB(utf8path); - if (!parentDirectory || (!parentDirectory->stat && - statDirectory(parentDirectory) < 0)) { - } else if (0 == inodeFoundInParent(parentDirectory->parent, - parentDirectory->inode, - parentDirectory->device) - && addToDirectory(parentDirectory, utf8path) - > 0) { - ret = UPDATE_RETURN_UPDATED; - } - } - - return ret; -} - -static const char *opendir_path(char *path_max_tmp, const char *dirname) -{ - if (*dirname != '\0') - return rmp2amp_r(path_max_tmp, - utf8_to_fs_charset(path_max_tmp, dirname)); - return musicDir; -} - -static enum update_return updateDirectory(Directory * directory) -{ - DIR *dir; - const char *dirname = getDirectoryPath(directory); - struct dirent *ent; - char path_max_tmp[MPD_PATH_MAX]; - enum update_return ret = UPDATE_RETURN_NOUPDATE; - - if (!directory->stat && statDirectory(directory) < 0) - return UPDATE_RETURN_ERROR; - else if (inodeFoundInParent(directory->parent, - directory->inode, - directory->device)) - return UPDATE_RETURN_ERROR; - - dir = opendir(opendir_path(path_max_tmp, dirname)); - if (!dir) - return UPDATE_RETURN_ERROR; - - if (removeDeletedFromDirectory(path_max_tmp, directory) > 0) - ret = UPDATE_RETURN_UPDATED; - - while ((ent = readdir(dir))) { - char *utf8; - if (skip_path(ent->d_name)) - continue; - - utf8 = fs_charset_to_utf8(path_max_tmp, ent->d_name); - if (!utf8) - continue; - - if (directory->path) - utf8 = pfx_dir(path_max_tmp, utf8, strlen(utf8), - dirname, strlen(dirname)); - if (updateInDirectory(directory, path_max_tmp) > 0) - ret = UPDATE_RETURN_UPDATED; - } - - closedir(dir); - - return ret; -} - -static enum update_return exploreDirectory(Directory * directory) -{ - DIR *dir; - const char *dirname = getDirectoryPath(directory); - struct dirent *ent; - char path_max_tmp[MPD_PATH_MAX]; - enum update_return ret = UPDATE_RETURN_NOUPDATE; - - DEBUG("explore: attempting to opendir: %s\n", dirname); - - dir = opendir(opendir_path(path_max_tmp, dirname)); - if (!dir) - return UPDATE_RETURN_ERROR; - - DEBUG("explore: %s\n", dirname); - - while ((ent = readdir(dir))) { - char *utf8; - if (skip_path(ent->d_name)) - continue; - - utf8 = fs_charset_to_utf8(path_max_tmp, ent->d_name); - if (!utf8) - continue; - - DEBUG("explore: found: %s (%s)\n", ent->d_name, utf8); - - if (directory->path) - utf8 = pfx_dir(path_max_tmp, utf8, strlen(utf8), - dirname, strlen(dirname)); - if (addToDirectory(directory, path_max_tmp) > 0) - ret = UPDATE_RETURN_UPDATED; - } - - closedir(dir); - - return ret; -} - -static int statDirectory(Directory * dir) -{ - struct stat st; - - if (myStat(getDirectoryPath(dir), &st) < 0) - return -1; - - directory_set_stat(dir, &st); - - return 0; -} - -static int inodeFoundInParent(Directory * parent, ino_t inode, dev_t device) -{ - while (parent) { - if (!parent->stat && statDirectory(parent) < 0) - return -1; - if (parent->inode == inode && parent->device == device) { - DEBUG("recursive directory found\n"); - return 1; - } - parent = parent->parent; - } - - return 0; -} - -static enum update_return addSubDirectoryToDirectory(Directory * directory, - const char *name, struct stat *st) -{ - Directory *subDirectory; - - if (inodeFoundInParent(directory, st->st_ino, st->st_dev)) - return UPDATE_RETURN_NOUPDATE; - - subDirectory = newDirectory(name, directory); - directory_set_stat(subDirectory, st); - - if (exploreDirectory(subDirectory) < 1) { - freeDirectory(subDirectory); - return UPDATE_RETURN_NOUPDATE; - } - - dirvec_add(&directory->children, subDirectory); - - return UPDATE_RETURN_UPDATED; -} - -static int addToDirectory(Directory * directory, const 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) && isMusic(name, NULL, 0)) { - Song *song; - const char *shortname = mpd_basename(name); - - if (!(song = newSong(shortname, directory))) - return -1; - songvec_add(&directory->songs, song); - LOG("added %s\n", name); - return 1; - } else if (S_ISDIR(st.st_mode)) { - return addSubDirectoryToDirectory(directory, name, &st); - } - - DEBUG("addToDirectory: %s is not a directory or music\n", name); - - return -1; -} - -void directory_finish(void) -{ - freeDirectory(music_root); -} - -int isRootDirectory(const char *name) -{ - return (!name || name[0] == '\0' || !strcmp(name, "/")); -} - -static Directory *getSubDirectory(Directory * directory, const char *name) -{ - Directory *cur = directory; - Directory *found = NULL; + struct directory *cur = dir; + struct directory *found = NULL; char *duplicated; char *locate; + assert(name != NULL); + if (isRootDirectory(name)) - return directory; + return dir; duplicated = xstrdup(name); locate = strchr(duplicated, '/'); while (1) { if (locate) *locate = '\0'; - if (!(found = dirvec_find(&cur->children, duplicated))) + if (!(found = directory_get_child(cur, duplicated))) break; assert(cur == found->parent); cur = found; @@ -690,407 +94,40 @@ static Directory *getSubDirectory(Directory * directory, const char *name) return found; } -static Directory *getDirectory(const char *name) -{ - return getSubDirectory(music_root, name); -} - -static int printDirectoryList(int fd, struct dirvec *dv) -{ - size_t i; - - for (i = 0; i < dv->nr; ++i) { - if (fdprintf(fd, DIRECTORY_DIR "%s\n", - getDirectoryPath(dv->base[i])) < 0) - return -1; - } - - return 0; -} - -int printDirectoryInfo(int fd, const char *name) -{ - Directory *directory; - - if ((directory = getDirectory(name)) == NULL) - return -1; - - printDirectoryList(fd, &directory->children); - songvec_for_each(&directory->songs, - song_print_info_x, (void *)(size_t)fd); - - return 0; -} - -static int directory_song_write(Song *song, void *data) -{ - int fd = (int)(size_t)data; - - if (fdprintf(fd, SONG_KEY "%s\n", song->url) < 0) - return -1; - if (song_print_info(song, fd) < 0) - return -1; - if (fdprintf(fd, SONG_MTIME "%li\n", (long)song->mtime) < 0) - return -1; - - return 0; -} - -/* TODO error checking */ -static int writeDirectoryInfo(int fd, Directory * directory) -{ - struct dirvec *children = &directory->children; - size_t i; - - if (directory->path && - fdprintf(fd, DIRECTORY_BEGIN "%s\n", - getDirectoryPath(directory)) < 0) - return -1; - - for (i = 0; i < children->nr; ++i) { - Directory *cur = children->base[i]; - const char *base = mpd_basename(cur->path); - - if (fdprintf(fd, DIRECTORY_DIR "%s\n", base) < 0) - return -1; - if (writeDirectoryInfo(fd, cur) < 0) - return -1; - } - - if (fdprintf(fd, SONG_BEGIN "\n") < 0) - return -1; - - if (songvec_for_each(&directory->songs, - directory_song_write, (void *)(size_t)fd) < 0) - return -1; - - if (fdprintf(fd, SONG_END "\n") < 0) - return -1; - - if (directory->path && - fdprintf(fd, DIRECTORY_END "%s\n", - getDirectoryPath(directory)) < 0) - return -1; - return 0; -} - -static void readDirectoryInfo(FILE * fp, Directory * directory) -{ - char buffer[MPD_PATH_MAX * 2]; - int bufferSize = MPD_PATH_MAX * 2; - char key[MPD_PATH_MAX * 2]; - char *name; - - while (myFgets(buffer, bufferSize, fp) - && prefixcmp(buffer, DIRECTORY_END)) { - if (!prefixcmp(buffer, DIRECTORY_DIR)) { - Directory *subdir; - - strcpy(key, &(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 (!prefixcmp(buffer, DIRECTORY_MTIME)) { - if (!myFgets(buffer, bufferSize, fp)) - FATAL("Error reading db, fgets\n"); - } - if (prefixcmp(buffer, DIRECTORY_BEGIN)) - FATAL("Error reading db at line: %s\n", buffer); - name = &(buffer[strlen(DIRECTORY_BEGIN)]); - if ((subdir = getDirectory(name))) { - assert(subdir->parent == directory); - } else { - subdir = newDirectory(name, directory); - dirvec_add(&directory->children, subdir); - } - readDirectoryInfo(fp, subdir); - } else if (!prefixcmp(buffer, SONG_BEGIN)) { - readSongInfoIntoList(fp, directory); - } else { - FATAL("Unknown line in db: %s\n", buffer); - } - } -} - -static void sortDirectory(Directory * directory) +void directory_sort(struct directory *dir) { int i; - struct dirvec *dv = &directory->children; + struct dirvec *dv = &dir->children; dirvec_sort(dv); - songvec_sort(&directory->songs); + songvec_sort(&dir->songs); for (i = dv->nr; --i >= 0; ) - sortDirectory(dv->base[i]); -} - -int checkDirectoryDB(void) -{ - struct stat st; - char *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 */ - char dirPath[MPD_PATH_MAX]; - parent_path(dirPath, dbFile); - if (*dirPath == '\0') - strcpy(dirPath, "/"); - - /* 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)); - 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); - 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)); - return -1; - } - - 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) -{ - int fd; - char *dbFile = getDbFile(); - struct stat st; - - DEBUG("removing empty directories from DB\n"); - deleteEmptyDirectoriesInDirectory(music_root); - - DEBUG("sorting DB\n"); - - sortDirectory(music_root); - - DEBUG("writing DB\n"); - - fd = open(dbFile, O_WRONLY|O_TRUNC|O_CREAT, 0666); - if (fd < 0) { - ERROR("unable to write to db file \"%s\": %s\n", - dbFile, strerror(errno)); - return -1; - } - - /* - * TODO: block signals when writing the db so we don't get a corrupted - * db (or unexpected failures). fdprintf() needs better error handling - */ - fdprintf(fd, - DIRECTORY_INFO_BEGIN "\n" - DIRECTORY_MPD_VERSION VERSION "\n" - DIRECTORY_FS_CHARSET "%s\n" - DIRECTORY_INFO_END "\n", getFsCharset()); - - if (writeDirectoryInfo(fd, music_root) < 0) { - ERROR("Failed to write to database file: %s\n", - strerror(errno)); - xclose(fd); - return -1; - } - xclose(fd); - - 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 (!music_root) - music_root = 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 (!prefixcmp(buffer, DIRECTORY_MPD_VERSION)) - { - if (foundVersion) - FATAL("already found version in db\n"); - foundVersion = 1; - } else if (!prefixcmp(buffer, - 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"); - while (fclose(fp) && errno == EINTR) ; - return -1; - } - } - - DEBUG("reading DB\n"); - - readDirectoryInfo(fp, music_root); - while (fclose(fp) && errno == EINTR) ; - - stats.numberOfSongs = countSongsIn(NULL); - stats.dbPlayTime = sumSongTimesIn(NULL); - - if (stat(dbFile, &st) == 0) - directory_dbModTime = st.st_mtime; - - return 0; + directory_sort(dv->base[i]); } -static int traverseAllInSubDirectory(Directory * directory, - int (*forEachSong) (Song *, void *), - int (*forEachDir) (Directory *, void *), - void *data) +int +directory_walk(struct directory *dir, + int (*forEachSong) (struct mpd_song *, void *), + int (*forEachDir) (struct directory *, void *), + void *data) { - struct dirvec *dv = &directory->children; + struct dirvec *dv = &dir->children; int err = 0; size_t j; - if (forEachDir && (err = forEachDir(directory, data)) < 0) + if (forEachDir && (err = forEachDir(dir, data)) < 0) return err; if (forEachSong) { - err = songvec_for_each(&directory->songs, forEachSong, data); + err = songvec_for_each(&dir->songs, forEachSong, data); if (err < 0) return err; } for (j = 0; err >= 0 && j < dv->nr; ++j) - err = traverseAllInSubDirectory(dv->base[j], forEachSong, + err = directory_walk(dv->base[j], forEachSong, forEachDir, data); return err; } - -int traverseAllIn(const char *name, - int (*forEachSong) (Song *, void *), - int (*forEachDir) (Directory *, void *), void *data) -{ - Directory *directory; - - if ((directory = getDirectory(name)) == NULL) { - Song *song; - if ((song = getSongFromDB(name)) && forEachSong) { - return forEachSong(song, data); - } - return -1; - } - - return traverseAllInSubDirectory(directory, forEachSong, forEachDir, - data); -} - -void directory_init(void) -{ - music_root = newDirectory(NULL, NULL); - exploreDirectory(music_root); - stats.numberOfSongs = countSongsIn(NULL); - stats.dbPlayTime = sumSongTimesIn(NULL); -} - -Song *getSongFromDB(const char *file) -{ - Song *song = NULL; - Directory *directory; - char *dir = NULL; - char *duplicated = xstrdup(file); - char *shortname = strrchr(duplicated, '/'); - - DEBUG("get song: %s\n", file); - - if (!shortname) { - shortname = duplicated; - } else { - *shortname = '\0'; - ++shortname; - dir = duplicated; - } - - if (!(directory = getDirectory(dir))) - goto out; - if (!(song = songvec_find(&directory->songs, shortname))) - goto out; - assert(song->parentDir == directory); - -out: - free(duplicated); - return song; -} - -time_t getDbModTime(void) -{ - return directory_dbModTime; -} diff --git a/src/directory.h b/src/directory.h index 20b784166..949df0c0e 100644 --- a/src/directory.h +++ b/src/directory.h @@ -20,56 +20,83 @@ #define DIRECTORY_H #include "song.h" +#include "dirvec.h" #include "songvec.h" -struct dirvec { - struct _Directory **base; - size_t nr; -}; +#define DIRECTORY_DIR "directory: " +#define DIRECTORY_MTIME "mtime: " /* DEPRECATED, noop-read-only */ +#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: " -typedef struct _Directory { +struct directory { char *path; struct dirvec children; struct songvec songs; - struct _Directory *parent; + struct directory *parent; ino_t inode; dev_t device; unsigned stat; /* not needed if ino_t == dev_t == 0 is impossible */ -} Directory; +}; -void reap_update_task(void); +static inline int isRootDirectory(const char *name) +{ + /* TODO: verify and remove !name check */ + return (!name || *name == '\0' || !strcmp(name, "/")); +} -int isUpdatingDB(void); +struct directory * directory_new(const char *dirname, struct directory *parent); -/* - * returns the non-negative update job ID on success, - * returns -1 if busy - * @path will be freed by this function and should not be reused - */ -int directory_update_init(char *path); +void directory_free(struct directory *dir); + +static inline int directory_is_empty(struct directory *dir) +{ + return dir->children.nr == 0 && dir->songs.nr == 0; +} + +static inline const char * directory_get_path(struct directory *dir) +{ + return dir->path; +} -void directory_init(void); +static inline struct directory * +directory_get_child(const struct directory *dir, const char *name) +{ + return dirvec_find(&dir->children, name); +} -void directory_finish(void); +static inline struct directory * +directory_new_child(struct directory *dir, const char *name) +{ + struct directory *subdir = directory_new(name, dir); + dirvec_add(&dir->children, subdir); + return subdir; +} -int isRootDirectory(const char *name); +void directory_prune_empty(struct directory *dir); -int printDirectoryInfo(int fd, const char *dirname); +struct directory * +directory_get_subdir(struct directory *dir, const char *name); -int checkDirectoryDB(void); +int directory_print(int fd, const struct directory *dir); -int writeDirectoryDB(void); +struct mpd_song *db_get_song(const char *file); -int readDirectoryDB(void); +int directory_save(int fd, struct directory *dir); -Song *getSongFromDB(const char *file); +void directory_load(FILE *fp, struct directory *dir); -time_t getDbModTime(void); +void directory_sort(struct directory *dir); -int traverseAllIn(const char *name, - int (*forEachSong) (Song *, void *), - int (*forEachDir) (Directory *, void *), void *data); +int db_walk(const char *name, + int (*forEachSong) (struct mpd_song *, void *), + int (*forEachDir) (struct directory *, void *), void *data); -#define getDirectoryPath(dir) ((dir && dir->path) ? dir->path : "") +int directory_walk(struct directory *dir, + int (*forEachSong) (struct mpd_song *, void *), + int (*forEachDir) (struct directory *, void *), void *data); #endif diff --git a/src/directory_print.c b/src/directory_print.c new file mode 100644 index 000000000..1c30f1608 --- /dev/null +++ b/src/directory_print.c @@ -0,0 +1,47 @@ +/* the Music Player Daemon (MPD) + * Copyright (C) 2003-2007 by Warren Dukes (warren.dukes@gmail.com) + * Copyright (C) 2008 Max Kellermann <max@duempel.org> + * 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_print.h" +#include "directory.h" +#include "dirvec.h" +#include "songvec.h" +#include "myfprintf.h" + +static int dirvec_print(int fd, const struct dirvec *dv) +{ + size_t i; + + for (i = 0; i < dv->nr; ++i) { + if (fdprintf(fd, DIRECTORY_DIR "%s\n", + directory_get_path(dv->base[i])) < 0) + return -1; + } + + return 0; +} + +int directory_print(int fd, const struct directory *dir) +{ + if (dirvec_print(fd, &dir->children) < 0) + return -1; + if (songvec_for_each(&dir->songs, song_print_info_x, + (void *)(size_t)fd) < 0) + return -1; + return 0; +} diff --git a/src/directory_print.h b/src/directory_print.h new file mode 100644 index 000000000..a55c3672d --- /dev/null +++ b/src/directory_print.h @@ -0,0 +1,26 @@ +/* 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 + */ + +#ifndef DIRECTORY_PRINT_H +#define DIRECTORY_PRINT_H + +struct directory; + +int directory_print(int fd, const struct directory *dir); + +#endif diff --git a/src/directory_save.c b/src/directory_save.c new file mode 100644 index 000000000..c39ece58a --- /dev/null +++ b/src/directory_save.c @@ -0,0 +1,120 @@ +/* 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_save.h" +#include "directory.h" +#include "song.h" +#include "log.h" +#include "path.h" +#include "utils.h" +#include "dirvec.h" +#include "myfprintf.h" + +static int directory_song_write(struct mpd_song *song, void *data) +{ + int fd = (int)(size_t)data; + + if (fdprintf(fd, SONG_KEY "%s\n", song->url) < 0) + return -1; + if (song_print_info(song, fd) < 0) + return -1; + if (fdprintf(fd, SONG_MTIME "%li\n", (long)song->mtime) < 0) + return -1; + + return 0; +} + +/* TODO error checking */ +int directory_save(int fd, struct directory *dir) +{ + struct dirvec *children = &dir->children; + size_t i; + + if (!isRootDirectory(dir->path) && + fdprintf(fd, DIRECTORY_BEGIN "%s\n", + directory_get_path(dir)) < 0) + return -1; + + for (i = 0; i < children->nr; ++i) { + struct directory *cur = children->base[i]; + const char *base = mpd_basename(cur->path); + + if (fdprintf(fd, DIRECTORY_DIR "%s\n", base) < 0) + return -1; + if (directory_save(fd, cur) < 0) + return -1; + } + + if (fdprintf(fd, SONG_BEGIN "\n") < 0) + return -1; + + if (songvec_for_each(&dir->songs, + directory_song_write, (void *)(size_t)fd) < 0) + return -1; + + if (fdprintf(fd, SONG_END "\n") < 0) + return -1; + + if (!isRootDirectory(dir->path) && + fdprintf(fd, DIRECTORY_END "%s\n", + directory_get_path(dir)) < 0) + return -1; + return 0; +} + +void directory_load(FILE * fp, struct directory *dir) +{ + char buffer[MPD_PATH_MAX * 2]; + int bufferSize = MPD_PATH_MAX * 2; + char key[MPD_PATH_MAX * 2]; + char *name; + + while (myFgets(buffer, bufferSize, fp) + && prefixcmp(buffer, DIRECTORY_END)) { + if (!prefixcmp(buffer, DIRECTORY_DIR)) { + struct directory *subdir; + + strcpy(key, &(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 (!prefixcmp(buffer, DIRECTORY_MTIME)) { + if (!myFgets(buffer, bufferSize, fp)) + FATAL("Error reading db, fgets\n"); + } + if (prefixcmp(buffer, DIRECTORY_BEGIN)) + FATAL("Error reading db at line: %s\n", buffer); + name = &(buffer[strlen(DIRECTORY_BEGIN)]); + if (prefixcmp(name, dir->path) != 0) + FATAL("Wrong path in database: '%s' in '%s'\n", + name, dir->path); + + if ((subdir = directory_get_child(dir, name))) { + assert(subdir->parent == dir); + } else { + subdir = directory_new(name, dir); + dirvec_add(&dir->children, subdir); + } + directory_load(fp, subdir); + } else if (!prefixcmp(buffer, SONG_BEGIN)) { + readSongInfoIntoList(fp, dir); + } else { + FATAL("Unknown line in db: %s\n", buffer); + } + } +} diff --git a/src/directory_save.h b/src/directory_save.h new file mode 100644 index 000000000..64364d82d --- /dev/null +++ b/src/directory_save.h @@ -0,0 +1,30 @@ +/* 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 + */ + +#ifndef DIRECTORY_SAVE_H +#define DIRECTORY_SAVE_H + +#include "os_compat.h" + +struct directory; + +int directory_save(int fd, struct directory *dir); + +void directory_load(FILE *, struct directory *dir); + +#endif /* DIRECTORY_SAVE_H */ diff --git a/src/dirvec.c b/src/dirvec.c new file mode 100644 index 000000000..fdfbb3434 --- /dev/null +++ b/src/dirvec.c @@ -0,0 +1,70 @@ +#include "dirvec.h" +#include "directory.h" +#include "os_compat.h" +#include "utils.h" + +static size_t dv_size(struct dirvec *dv) +{ + return dv->nr * sizeof(struct directory *); +} + +/* Only used for sorting/searching a dirvec, not general purpose compares */ +static int dirvec_cmp(const void *d1, const void *d2) +{ + const struct directory *a = ((const struct directory * const *)d1)[0]; + const struct directory *b = ((const struct directory * const *)d2)[0]; + return strcmp(a->path, b->path); +} + +void dirvec_sort(struct dirvec *dv) +{ + qsort(dv->base, dv->nr, sizeof(struct directory *), dirvec_cmp); +} + +struct directory *dirvec_find(const struct dirvec *dv, const char *path) +{ + int i; + + for (i = dv->nr; --i >= 0; ) + if (!strcmp(dv->base[i]->path, path)) + return dv->base[i]; + return NULL; +} + +int dirvec_delete(struct dirvec *dv, struct directory *del) +{ + int i; + + for (i = dv->nr; --i >= 0; ) { + if (dv->base[i] != del) + continue; + /* we _don't_ call directory_free() here */ + if (!--dv->nr) { + free(dv->base); + dv->base = NULL; + } else { + memmove(&dv->base[i], &dv->base[i + 1], + (dv->nr - i + 1) * sizeof(struct directory *)); + dv->base = xrealloc(dv->base, dv_size(dv)); + } + return i; + } + + return -1; /* not found */ +} + +void dirvec_add(struct dirvec *dv, struct directory *add) +{ + ++dv->nr; + dv->base = xrealloc(dv->base, dv_size(dv)); + dv->base[dv->nr - 1] = add; +} + +void dirvec_destroy(struct dirvec *dv) +{ + if (dv->base) { + free(dv->base); + dv->base = NULL; + } + dv->nr = 0; +} diff --git a/src/dirvec.h b/src/dirvec.h index 8b2f634e2..02496cd2b 100644 --- a/src/dirvec.h +++ b/src/dirvec.h @@ -1,73 +1,26 @@ #ifndef DIRVEC_H #define DIRVEC_H -#include "directory.h" #include "os_compat.h" -#include "utils.h" -static size_t dv_size(struct dirvec *dv) -{ - return dv->nr * sizeof(Directory *); -} +struct dirvec { + struct directory **base; + size_t nr; +}; -/* Only used for sorting/searching a dirvec, not general purpose compares */ -static int dirvec_cmp(const void *d1, const void *d2) -{ - const Directory *a = ((const Directory * const *)d1)[0]; - const Directory *b = ((const Directory * const *)d2)[0]; - return strcmp(a->path, b->path); -} +void dirvec_sort(struct dirvec *dv); -static void dirvec_sort(struct dirvec *dv) -{ - qsort(dv->base, dv->nr, sizeof(Directory *), dirvec_cmp); -} +struct directory *dirvec_find(const struct dirvec *dv, const char *path); -static Directory *dirvec_find(struct dirvec *dv, const char *path) -{ - int i; +int dirvec_delete(struct dirvec *dv, struct directory *del); - for (i = dv->nr; --i >= 0; ) - if (!strcmp(dv->base[i]->path, path)) - return dv->base[i]; - return NULL; -} +void dirvec_add(struct dirvec *dv, struct directory *add); -static int dirvec_delete(struct dirvec *dv, Directory *del) +static inline void dirvec_clear(struct dirvec *dv) { - int i; - - for (i = dv->nr; --i >= 0; ) { - if (dv->base[i] != del) - continue; - /* we _don't_ call freeDirectory() here */ - if (!--dv->nr) { - free(dv->base); - dv->base = NULL; - } else { - memmove(&dv->base[i], &dv->base[i + 1], - (dv->nr - i + 1) * sizeof(Directory *)); - dv->base = xrealloc(dv->base, dv_size(dv)); - } - return i; - } - - return -1; /* not found */ + dv->nr = 0; } -static void dirvec_add(struct dirvec *dv, Directory *add) -{ - ++dv->nr; - dv->base = xrealloc(dv->base, dv_size(dv)); - dv->base[dv->nr - 1] = add; -} +void dirvec_destroy(struct dirvec *dv); -static void dirvec_destroy(struct dirvec *dv) -{ - if (dv->base) { - free(dv->base); - dv->base = NULL; - } - dv->nr = 0; -} #endif /* DIRVEC_H */ diff --git a/src/inputPlugin.c b/src/inputPlugin.c index 1ab118ceb..2bb05c018 100644 --- a/src/inputPlugin.c +++ b/src/inputPlugin.c @@ -17,6 +17,7 @@ */ #include "inputPlugin.h" +#include "list.h" static List *inputPlugin_list; diff --git a/src/inputPlugin.h b/src/inputPlugin.h index 0fd39ea9f..4338ce471 100644 --- a/src/inputPlugin.h +++ b/src/inputPlugin.h @@ -23,6 +23,8 @@ #include "outputBuffer.h" #include "metadata_pipe.h" #include "decode.h" +#include "tag.h" +#include "tag_id3.h" /* valid values for streamTypes in the InputPlugin struct: */ #define INPUT_PLUGIN_STREAM_FILE 0x01 diff --git a/src/locate.c b/src/locate.c index 6101dd95b..96fa4ee58 100644 --- a/src/locate.c +++ b/src/locate.c @@ -121,7 +121,8 @@ void freeLocateTagItem(LocateTagItem * item) free(item); } -static int strstrSearchTag(Song * song, enum tag_type type, char *str) +static int strstrSearchTag(struct mpd_song * song, + enum tag_type type, char *str) { int i; char *duplicate; @@ -131,7 +132,7 @@ static int strstrSearchTag(Song * song, enum tag_type type, char *str) if (type == LOCATE_TAG_FILE_TYPE || type == LOCATE_TAG_ANY_TYPE) { char path_max_tmp[MPD_PATH_MAX]; - string_toupper(get_song_url(path_max_tmp, song)); + string_toupper(song_get_url(song, path_max_tmp)); if (strstr(path_max_tmp, str)) ret = 1; if (ret == 1 || type == LOCATE_TAG_FILE_TYPE) @@ -166,7 +167,8 @@ static int strstrSearchTag(Song * song, enum tag_type type, char *str) return ret; } -int strstrSearchTags(Song * song, int numItems, LocateTagItem * items) +int +strstrSearchTags(struct mpd_song * song, int numItems, LocateTagItem * items) { int i; @@ -180,14 +182,15 @@ int strstrSearchTags(Song * song, int numItems, LocateTagItem * items) return 1; } -static int tagItemFoundAndMatches(Song * song, enum tag_type type, char *str) +static int +tagItemFoundAndMatches(struct mpd_song * song, enum tag_type type, char *str) { int i; int8_t visitedTypes[TAG_NUM_OF_ITEM_TYPES] = { 0 }; if (type == LOCATE_TAG_FILE_TYPE || type == LOCATE_TAG_ANY_TYPE) { char path_max_tmp[MPD_PATH_MAX]; - if (0 == strcmp(str, get_song_url(path_max_tmp, song))) + if (0 == strcmp(str, song_get_url(song, path_max_tmp))) return 1; if (type == LOCATE_TAG_FILE_TYPE) return 0; @@ -220,7 +223,8 @@ static int tagItemFoundAndMatches(Song * song, enum tag_type type, char *str) } -int tagItemsFoundAndMatches(Song * song, int numItems, LocateTagItem * items) +int tagItemsFoundAndMatches(struct mpd_song * song, + int numItems, LocateTagItem * items) { int i; diff --git a/src/locate.h b/src/locate.h index 7a817828a..0ccc497ff 100644 --- a/src/locate.h +++ b/src/locate.h @@ -44,8 +44,10 @@ void freeLocateTagItemArray(int count, LocateTagItem * array); void freeLocateTagItem(LocateTagItem * item); -int strstrSearchTags(Song * song, int numItems, LocateTagItem * items); +int strstrSearchTags(struct mpd_song * song, + int numItems, LocateTagItem * items); -int tagItemsFoundAndMatches(Song * song, int numItems, LocateTagItem * items); +int tagItemsFoundAndMatches(struct mpd_song * song, + int numItems, LocateTagItem * items); #endif @@ -19,9 +19,7 @@ #ifndef LOG_H #define LOG_H -#include "../config.h" #include "gcc.h" -#include "os_compat.h" #define LOG_LEVEL_LOW 0 #define LOG_LEVEL_SECURE 1 diff --git a/src/main.c b/src/main.c index c0acc260b..f1c288dd5 100644 --- a/src/main.c +++ b/src/main.c @@ -19,7 +19,8 @@ #include "client.h" #include "command.h" #include "playlist.h" -#include "directory.h" +#include "database.h" +#include "update.h" #include "listen.h" #include "conf.h" #include "path.h" @@ -268,17 +269,17 @@ static void changeToUser(void) static void openDB(Options * options, char *argv0) { - if (options->createDB > 0 || readDirectoryDB() < 0) { + if (options->createDB > 0 || db_load() < 0) { if (options->createDB < 0) { FATAL("can't open db file and using " "\"--no-create-db\" command line option\n" "try running \"%s --create-db\"\n", argv0); } flushWarningLog(); - if (checkDirectoryDB() < 0) + if (db_check() < 0) exit(EXIT_FAILURE); - directory_init(); - if (writeDirectoryDB() < 0) + db_init(); + if (db_save() < 0) exit(EXIT_FAILURE); if (options->createDB) exit(EXIT_SUCCESS); @@ -408,6 +409,8 @@ int main(int argc, char *argv[]) initPlaylist(); initInputPlugins(); + init_main_notify(); + openDB(&options, argv[0]); initCommands(); @@ -423,7 +426,6 @@ int main(int argc, char *argv[]) daemonize(&options); - init_main_notify(); init_output_buffer(); setup_log_output(options.stdOutput); @@ -450,8 +452,8 @@ int main(int argc, char *argv[]) finishPlaylist(); start = clock(); - directory_finish(); - DEBUG("directory_finish took %f seconds\n", + db_finish(); + DEBUG("db_finish took %f seconds\n", ((float)(clock()-start))/CLOCKS_PER_SEC); finishNormalization(); diff --git a/src/playlist.c b/src/playlist.c index 696636993..8a7b5cf7f 100644 --- a/src/playlist.c +++ b/src/playlist.c @@ -22,7 +22,7 @@ #include "ls.h" #include "tag.h" #include "conf.h" -#include "directory.h" +#include "database.h" #include "log.h" #include "path.h" #include "utils.h" @@ -41,7 +41,7 @@ enum _playlist_state { static enum _playlist_state playlist_state; struct _playlist { - Song **songs; + struct mpd_song **songs; /* holds version a song was modified on */ uint32_t *songMod; int *order; @@ -157,7 +157,8 @@ void initPlaylist(void) playlist_saveAbsolutePaths = DEFAULT_PLAYLIST_SAVE_ABSOLUTE_PATHS; - playlist.songs = xcalloc(playlist_max_length, sizeof(Song *)); + playlist.songs = xcalloc(playlist_max_length, + sizeof(struct mpd_song *)); playlist.songMod = xmalloc(sizeof(uint32_t) * playlist_max_length); playlist.order = xmalloc(sizeof(int) * playlist_max_length); playlist.idToPosition = xmalloc(sizeof(int) * playlist_max_length * @@ -188,7 +189,7 @@ void finishPlaylist(void) for (i = playlist.length; --i >= 0; ) { if (!song_is_file(playlist.songs[i])) - freeJustSong(playlist.songs[i]); + song_free(playlist.songs[i]); } playlist.length = 0; @@ -213,7 +214,7 @@ void clearPlaylist(void) for (i = playlist.length; --i >= 0 ; ) { if (!song_is_file(playlist.songs[i])) - freeJustSong(playlist.songs[i]); + song_free(playlist.songs[i]); playlist.idToPosition[playlist.positionToId[i]] = -1; playlist.songs[i] = NULL; } @@ -235,7 +236,7 @@ void showPlaylist(int fd) for (i = 0; i < playlist.length; i++) fdprintf(fd, "%i:%s\n", i, - get_song_url(path_max_tmp, playlist.songs[i])); + song_get_url(playlist.songs[i], path_max_tmp)); } void savePlaylistState(int fd) @@ -440,7 +441,7 @@ enum playlist_result playlistId(int fd, int id) static void swapSongs(int song1, int song2) { - Song *sTemp; + struct mpd_song *sTemp; int iTemp; assert(song1 < playlist.length); @@ -463,7 +464,7 @@ static void swapSongs(int song1, int song2) playlist.positionToId[song2] = iTemp; } -static Song *song_at(int order_num) +static struct mpd_song *song_at(int order_num) { if (order_num >= 0 && order_num < playlist.length) { assert(playlist.songs[playlist.order[order_num]]); @@ -536,13 +537,13 @@ void playlist_queue_next(void) char *playlist_queued_url(char utf8url[MPD_PATH_MAX]) { - Song *song; + struct mpd_song *song; assert(pthread_equal(pthread_self(), dc.thread)); pthread_mutex_lock(&queue_lock); song = song_at(playlist.queued); - return song ? get_song_url(utf8url, song) : NULL; + return song ? song_get_url(song, utf8url) : NULL; } static void queue_song_locked(int order_num) @@ -570,13 +571,13 @@ static int clear_queue(void) enum playlist_result addToPlaylist(const char *url, int *added_id) { - Song *song; + struct mpd_song *song; DEBUG("add to playlist: %s\n", url); - if ((song = getSongFromDB(url))) { + if ((song = db_get_song(url))) { } else if (!(isValidRemoteUtf8Url(url) && - (song = newSong(url, NULL)))) { + (song = song_remote_new(url)))) { return PLAYLIST_RESULT_NO_SUCH_SONG; } @@ -585,26 +586,26 @@ enum playlist_result addToPlaylist(const char *url, int *added_id) int addToStoredPlaylist(const char *url, const char *utf8file) { - Song *song; + struct mpd_song *song; DEBUG("add to stored playlist: %s\n", url); - if ((song = getSongFromDB(url))) + if ((song = db_get_song(url))) return appendSongToStoredPlaylistByPath(utf8file, song); if (!isValidRemoteUtf8Url(url)) return ACK_ERROR_NO_EXIST; - if ((song = newSong(url, NULL))) { + if ((song = song_remote_new(url))) { int ret = appendSongToStoredPlaylistByPath(utf8file, song); - freeJustSong(song); + song_free(song); return ret; } return ACK_ERROR_NO_EXIST; } -enum playlist_result addSongToPlaylist(Song * song, int *added_id) +enum playlist_result addSongToPlaylist(struct mpd_song * song, int *added_id) { int id; @@ -740,7 +741,7 @@ enum playlist_result deleteFromPlaylist(int song) } if (!song_is_file(playlist.songs[song])) - freeJustSong(playlist.songs[song]); + song_free(playlist.songs[song]); playlist.idToPosition[playlist.positionToId[song]] = -1; @@ -798,7 +799,7 @@ enum playlist_result deleteFromPlaylistById(int id) return deleteFromPlaylist(playlist.idToPosition[id]); } -void deleteASongFromPlaylist(const Song * song) +void deleteASongFromPlaylist(const struct mpd_song * song) { int i; @@ -841,7 +842,7 @@ static void play_order_num(int order_num, float seek_time) assert(song_at(order_num)); DEBUG("playlist: play %i:\"%s\"\n", order_num, - get_song_url(path, song_at(order_num))); + song_get_url(song_at(order_num), path)); dc_trigger_action(DC_ACTION_STOP, 0); queue_song_locked(order_num); @@ -917,7 +918,7 @@ enum playlist_result playPlaylistById(int id, int stopOnError) /* This is used when we stream data out to shout while playing static files */ struct mpd_tag *playlist_current_tag(void) { - Song *song = song_at(playlist.current); + struct mpd_song *song = song_at(playlist.current); /* Non-file song tags can get swept out from under us */ return (song && song_is_file(song)) ? song->tag : NULL; @@ -926,7 +927,7 @@ struct mpd_tag *playlist_current_tag(void) /* This receives dynamic metadata updates from streams */ static void sync_metadata(void) { - Song *song; + struct mpd_song *song; struct mpd_tag *tag; if (!(tag = metadata_pipe_current())) @@ -998,7 +999,7 @@ void setPlaylistRepeatStatus(int status) enum playlist_result moveSongInPlaylist(int from, int to) { int i; - Song *tmpSong; + struct mpd_song *tmpSong; int tmpId; int currentSong; int queued_is_current = (playlist.queued == playlist.current); @@ -1145,11 +1146,7 @@ static void randomizeOrder(int start, int end) void setPlaylistRandomStatus(int status) { - int statusWas = playlist.random; - - playlist.random = status; - - if (status != statusWas) { + if (status != playlist.random) { if (playlist.random) randomizeOrder(0, playlist.length - 1); else @@ -1262,7 +1259,7 @@ enum playlist_result savePlaylist(const char *utf8file) for (i = 0; i < playlist.length; i++) { char tmp[MPD_PATH_MAX]; - get_song_url(path_max_tmp, playlist.songs[i]); + song_get_url(playlist.songs[i], path_max_tmp); utf8_to_fs_charset(tmp, path_max_tmp); if (playlist_saveAbsolutePaths && @@ -1329,7 +1326,7 @@ enum playlist_result seekSongInPlaylist(int song, float seek_time) */ } - DEBUG("playlist: seek %i:\"%s\"\n", i, get_song_url(path, song_at(i))); + DEBUG("playlist: seek %i:\"%s\"\n", i, song_get_url(song_at(i), path)); play_order_num(i, seek_time); return PLAYLIST_RESULT_SUCCESS; } @@ -1361,7 +1358,7 @@ int PlaylistInfo(int fd, const char *utf8file, int detail) int wrote = 0; if (detail) { - Song *song = getSongFromDB(temp); + struct mpd_song *song = db_get_song(temp); if (song) { song_print_info(song, fd); wrote = 1; diff --git a/src/playlist.h b/src/playlist.h index 2be2711e5..f4df8ce41 100644 --- a/src/playlist.h +++ b/src/playlist.h @@ -57,7 +57,7 @@ enum playlist_result addToPlaylist(const char *file, int *added_id); int addToStoredPlaylist(const char *file, const char *utf8file); -enum playlist_result addSongToPlaylist(Song * song, int *added_id); +enum playlist_result addSongToPlaylist(struct mpd_song * song, int *added_id); void showPlaylist(int fd); @@ -93,7 +93,7 @@ enum playlist_result savePlaylist(const char *utf8file); enum playlist_result deletePlaylist(const char *utf8file); -void deleteASongFromPlaylist(const Song * song); +void deleteASongFromPlaylist(const struct mpd_song * song); enum playlist_result moveSongInPlaylist(int from, int to); diff --git a/src/sig_handlers.c b/src/sig_handlers.c index e4ac21f22..5a4ebe57d 100644 --- a/src/sig_handlers.c +++ b/src/sig_handlers.c @@ -19,7 +19,8 @@ #include "sig_handlers.h" #include "playlist.h" -#include "directory.h" +#include "database.h" +#include "update.h" #include "command.h" #include "signal_check.h" #include "log.h" @@ -35,7 +36,7 @@ int handlePendingSignals(void) DEBUG("got SIGHUP, rereading DB\n"); signal_clear(SIGHUP); if (!isUpdatingDB()) { - readDirectoryDB(); + db_load(); playlistVersionChange(); } if (cycle_log_files() < 0) diff --git a/src/song.c b/src/song.c index ddc3bc4bd..aa6641020 100644 --- a/src/song.c +++ b/src/song.c @@ -29,74 +29,69 @@ #include "os_compat.h" -static Song * song_alloc(const char *url, Directory *parent) +static struct mpd_song * song_alloc(const char *url, struct directory *parent) { size_t urllen; - Song *song; + struct mpd_song *song; assert(url); urllen = strlen(url); assert(urllen); - song = xmalloc(sizeof(Song) + urllen); + song = xmalloc(sizeof(*song) - sizeof(song->url) + urllen + 1); song->tag = NULL; memcpy(song->url, url, urllen + 1); - song->parentDir = parent; + song->parent = parent; return song; } -Song *newSong(const char *url, Directory * parentDir) +struct mpd_song * song_remote_new(const char *url) { - Song *song; - assert(*url); + return song_alloc(url, NULL); +} - if (strchr(url, '\n')) { - DEBUG("newSong: '%s' is not a valid uri\n", url); - return NULL; - } +struct mpd_song * song_file_new(const char *path, struct directory *parent) +{ + assert(parent != NULL); - song = song_alloc(url, parentDir); + return song_alloc(path, parent); +} - if (song_is_file(song)) { - InputPlugin *plugin; - unsigned int next = 0; - char path_max_tmp[MPD_PATH_MAX]; - char abs_path[MPD_PATH_MAX]; +struct mpd_song * song_file_load(const char *path, struct directory *parent) +{ + struct mpd_song *song; - utf8_to_fs_charset(abs_path, get_song_url(path_max_tmp, song)); - rmp2amp_r(abs_path, abs_path); + if (strchr(path, '\n')) { + DEBUG("song_file_load: '%s' is not a valid uri\n", path); + return NULL; + } - while (!song->tag && (plugin = isMusic(abs_path, - &(song->mtime), - next++))) { - song->tag = plugin->tagDupFunc(abs_path); - } - if (!song->tag || song->tag->time < 0) { - freeJustSong(song); - song = NULL; - } + song = song_file_new(path, parent); + if (!song_file_update(song)) { + song_free(song); + return NULL; } return song; } -void freeJustSong(Song * song) +void song_free(struct mpd_song * song) { if (song->tag) tag_free(song->tag); free(song); } -ssize_t song_print_url(Song *song, int fd) +ssize_t song_print_url(struct mpd_song *song, int fd) { - if (song->parentDir && song->parentDir->path) + if (song->parent && song->parent->path) return fdprintf(fd, "%s%s/%s\n", SONG_FILE, - getDirectoryPath(song->parentDir), song->url); + directory_get_path(song->parent), song->url); return fdprintf(fd, "%s%s\n", SONG_FILE, song->url); } -ssize_t song_print_info(Song *song, int fd) +ssize_t song_print_info(struct mpd_song *song, int fd) { ssize_t ret = song_print_url(song, fd); @@ -108,19 +103,19 @@ ssize_t song_print_info(Song *song, int fd) return ret; } -int song_print_info_x(Song * song, void *data) +int song_print_info_x(struct mpd_song * song, void *data) { return song_print_info(song, (int)(size_t)data); } -int song_print_url_x(Song * song, void *data) +int song_print_url_x(struct mpd_song * song, void *data) { return song_print_url(song, (int)(size_t)data); } -static void insertSongIntoList(struct songvec *sv, Song *newsong) +static void insertSongIntoList(struct songvec *sv, struct mpd_song *newsong) { - Song *existing = songvec_find(sv, newsong->url); + struct mpd_song *existing = songvec_find(sv, newsong->url); if (!existing) { songvec_add(sv, newsong); @@ -141,10 +136,10 @@ static void insertSongIntoList(struct songvec *sv, Song *newsong) if (old_tag) tag_free(old_tag); } - /* prevent tag_free in freeJustSong */ + /* prevent tag_free in song_free */ newsong->tag = NULL; } - freeJustSong(newsong); + song_free(newsong); } } @@ -162,19 +157,19 @@ static int matchesAnMpdTagItemKey(char *buffer, int *itemType) return 0; } -void readSongInfoIntoList(FILE * fp, Directory * parentDir) +void readSongInfoIntoList(FILE * fp, struct directory * parent) { char buffer[MPD_PATH_MAX + 1024]; int bufferSize = MPD_PATH_MAX + 1024; - Song *song = NULL; - struct songvec *sv = &parentDir->songs; + struct mpd_song *song = NULL; + struct songvec *sv = &parent->songs; int itemType; while (myFgets(buffer, bufferSize, fp) && 0 != strcmp(SONG_END, buffer)) { if (!prefixcmp(buffer, SONG_KEY)) { if (song) insertSongIntoList(sv, song); - song = song_alloc(buffer + strlen(SONG_KEY), parentDir); + song = song_file_new(buffer + strlen(SONG_KEY), parent); } else if (*buffer == 0) { /* ignore empty lines (starting with '\0') */ } else if (song == NULL) { @@ -209,49 +204,47 @@ void readSongInfoIntoList(FILE * fp, Directory * parentDir) insertSongIntoList(sv, song); } -int updateSongInfo(Song * song) +int song_file_update(struct mpd_song * song) { - if (song_is_file(song)) { - InputPlugin *plugin; - unsigned int next = 0; - char path_max_tmp[MPD_PATH_MAX]; - char abs_path[MPD_PATH_MAX]; - struct mpd_tag *old_tag = song->tag; - struct mpd_tag *new_tag = NULL; - - utf8_to_fs_charset(abs_path, get_song_url(path_max_tmp, song)); - rmp2amp_r(abs_path, abs_path); - - while ((plugin = isMusic(abs_path, &song->mtime, next++))) { - if ((new_tag = plugin->tagDupFunc(abs_path))) - break; - } - if (new_tag && tag_equal(new_tag, old_tag)) { - tag_free(new_tag); - } else { - song->tag = new_tag; - if (old_tag) - tag_free(old_tag); - } - if (!song->tag || song->tag->time < 0) - return -1; + InputPlugin *plugin; + unsigned int next = 0; + char path_max_tmp[MPD_PATH_MAX]; + char abs_path[MPD_PATH_MAX]; + struct mpd_tag *old_tag = song->tag; + struct mpd_tag *new_tag = NULL; + + assert(song_is_file(song)); + + utf8_to_fs_charset(abs_path, song_get_url(song, path_max_tmp)); + rmp2amp_r(abs_path, abs_path); + + while ((plugin = isMusic(abs_path, &song->mtime, next++))) { + if ((new_tag = plugin->tagDupFunc(abs_path))) + break; + } + if (new_tag && tag_equal(new_tag, old_tag)) { + tag_free(new_tag); + } else { + song->tag = new_tag; + if (old_tag) + tag_free(old_tag); } - return 0; + return (song->tag && song->tag->time >= 0); } -char *get_song_url(char *path_max_tmp, Song *song) +char *song_get_url(struct mpd_song *song, char *path_max_tmp) { if (!song) return NULL; assert(*song->url); - if (!song->parentDir || !song->parentDir->path) + if (!song->parent || !song->parent->path) strcpy(path_max_tmp, song->url); else pfx_dir(path_max_tmp, song->url, strlen(song->url), - getDirectoryPath(song->parentDir), - strlen(getDirectoryPath(song->parentDir))); + directory_get_path(song->parent), + strlen(directory_get_path(song->parent))); return path_max_tmp; } diff --git a/src/song.h b/src/song.h index 9aa9efa94..338bcf6c1 100644 --- a/src/song.h +++ b/src/song.h @@ -22,8 +22,6 @@ #include "../config.h" #include "os_compat.h" #include "tag.h" -#include "list.h" -#include "gcc.h" #define SONG_KEY "key: " #define SONG_MTIME "mtime: " @@ -33,42 +31,53 @@ #define SONG_FILE "file: " #define SONG_TIME "Time: " -typedef struct _Song { +struct mpd_song { struct mpd_tag *tag; - struct _Directory *parentDir; + struct directory *parent; time_t mtime; - char url[1]; -} mpd_packed Song; + char url[sizeof(size_t)]; +}; -Song *newSong(const char *url, struct _Directory *parentDir); +void song_free(struct mpd_song *); -void freeJustSong(Song *); +/** allocate a new song with a remote URL */ +struct mpd_song * song_remote_new(const char *url); -ssize_t song_print_info(Song * song, int fd); +/** allocate a new song with a local file name */ +struct mpd_song * song_file_new(const char *path, struct directory *parent); + +/** + * allocate a new song structure with a local file name and attempt to + * load its metadata. If all decoder plugin fail to read its meta + * data, NULL is returned. + */ +struct mpd_song * song_file_load(const char *path, struct directory *parent); + +ssize_t song_print_info(struct mpd_song * song, int fd); /* like song_print_info, but casts data into an fd first */ -int song_print_info_x(Song * song, void *data); +int song_print_info_x(struct mpd_song * song, void *data); -void readSongInfoIntoList(FILE * fp, struct _Directory *parent); +void readSongInfoIntoList(FILE * fp, struct directory *parent); -int updateSongInfo(Song * song); +int song_file_update(struct mpd_song * song); -ssize_t song_print_url(Song * song, int fd); +ssize_t song_print_url(struct mpd_song * song, int fd); /* like song_print_url_x, but casts data into an fd first */ -int song_print_url_x(Song * song, void *data); +int song_print_url_x(struct mpd_song * song, void *data); /* - * get_song_url - Returns a path of a song in UTF8-encoded form + * song_get_url - Returns a path of a song in UTF8-encoded form * path_max_tmp is the argument that the URL is written to, this * buffer is assumed to be MPD_PATH_MAX or greater (including * terminating '\0'). */ -char *get_song_url(char *path_max_tmp, Song * song); +char *song_get_url(struct mpd_song * song, char *path_max_tmp); -static inline int song_is_file(const Song *song) +static inline int song_is_file(const struct mpd_song *song) { - return !!song->parentDir; + return !!song->parent; } #endif diff --git a/src/songvec.c b/src/songvec.c index cf0991029..ce04a4373 100644 --- a/src/songvec.c +++ b/src/songvec.c @@ -7,27 +7,27 @@ static pthread_mutex_t nr_lock = PTHREAD_MUTEX_INITIALIZER; /* Only used for sorting/searchin a songvec, not general purpose compares */ static int songvec_cmp(const void *s1, const void *s2) { - const Song *a = ((const Song * const *)s1)[0]; - const Song *b = ((const Song * const *)s2)[0]; + const struct mpd_song *a = ((const struct mpd_song * const *)s1)[0]; + const struct mpd_song *b = ((const struct mpd_song * const *)s2)[0]; return strcmp(a->url, b->url); } static size_t sv_size(struct songvec *sv) { - return sv->nr * sizeof(Song *); + return sv->nr * sizeof(struct mpd_song *); } void songvec_sort(struct songvec *sv) { pthread_mutex_lock(&nr_lock); - qsort(sv->base, sv->nr, sizeof(Song *), songvec_cmp); + qsort(sv->base, sv->nr, sizeof(struct mpd_song *), songvec_cmp); pthread_mutex_unlock(&nr_lock); } -Song *songvec_find(struct songvec *sv, const char *url) +struct mpd_song *songvec_find(const struct songvec *sv, const char *url) { int i; - Song *ret = NULL; + struct mpd_song *ret = NULL; pthread_mutex_lock(&nr_lock); for (i = sv->nr; --i >= 0; ) { @@ -40,7 +40,7 @@ Song *songvec_find(struct songvec *sv, const char *url) return ret; } -int songvec_delete(struct songvec *sv, const Song *del) +int songvec_delete(struct songvec *sv, const struct mpd_song *del) { int i; @@ -54,7 +54,7 @@ int songvec_delete(struct songvec *sv, const Song *del) sv->base = NULL; } else { memmove(&sv->base[i], &sv->base[i + 1], - (sv->nr - i + 1) * sizeof(Song *)); + (sv->nr - i + 1) * sizeof(struct mpd_song *)); sv->base = xrealloc(sv->base, sv_size(sv)); } break; @@ -64,7 +64,7 @@ int songvec_delete(struct songvec *sv, const Song *del) return i; } -void songvec_add(struct songvec *sv, Song *add) +void songvec_add(struct songvec *sv, struct mpd_song *add) { pthread_mutex_lock(&nr_lock); ++sv->nr; @@ -84,13 +84,14 @@ void songvec_destroy(struct songvec *sv) pthread_mutex_unlock(&nr_lock); } -int songvec_for_each(struct songvec *sv, int (*fn)(Song *, void *), void *arg) +int songvec_for_each(const struct songvec *sv, + int (*fn)(struct mpd_song *, void *), void *arg) { size_t i; pthread_mutex_lock(&nr_lock); for (i = 0; i < sv->nr; ++i) { - Song *song = sv->base[i]; + struct mpd_song *song = sv->base[i]; assert(song); assert(*song->url); diff --git a/src/songvec.h b/src/songvec.h index dbe6be508..633cf8d66 100644 --- a/src/songvec.h +++ b/src/songvec.h @@ -5,20 +5,21 @@ #include "os_compat.h" struct songvec { - Song **base; + struct mpd_song **base; size_t nr; }; void songvec_sort(struct songvec *sv); -Song *songvec_find(struct songvec *sv, const char *url); +struct mpd_song *songvec_find(const struct songvec *sv, const char *url); -int songvec_delete(struct songvec *sv, const Song *del); +int songvec_delete(struct songvec *sv, const struct mpd_song *del); -void songvec_add(struct songvec *sv, Song *add); +void songvec_add(struct songvec *sv, struct mpd_song *add); void songvec_destroy(struct songvec *sv); -int songvec_for_each(struct songvec *sv, int (*fn)(Song *, void *), void *arg); +int songvec_for_each(const struct songvec *sv, + int (*fn)(struct mpd_song *, void *), void *arg); #endif /* SONGVEC_H */ diff --git a/src/stats.c b/src/stats.c index 4555907db..abe03444a 100644 --- a/src/stats.c +++ b/src/stats.c @@ -19,7 +19,7 @@ #include "stats.h" -#include "directory.h" +#include "database.h" #include "myfprintf.h" #include "outputBuffer.h" #include "tag.h" @@ -40,7 +40,7 @@ struct visit_data { struct strset *set; }; -static int visit_tag_items(Song *song, void *_data) +static int visit_tag_items(struct mpd_song *song, void *_data) { const struct visit_data *data = _data; unsigned i; @@ -65,7 +65,7 @@ static unsigned int getNumberOfTagItems(int type) data.type = type; data.set = strset_new(); - traverseAllIn(NULL, visit_tag_items, NULL, &data); + db_walk(NULL, visit_tag_items, NULL, &data); ret = strset_size(data.set); strset_free(data.set); @@ -88,6 +88,6 @@ int printStats(int fd) time(NULL) - stats.daemonStart, (long)(ob_get_total_time() + 0.5), stats.dbPlayTime, - getDbModTime()); + db_get_mtime()); return 0; } diff --git a/src/storedPlaylist.c b/src/storedPlaylist.c index 265301392..2e6828483 100644 --- a/src/storedPlaylist.c +++ b/src/storedPlaylist.c @@ -20,7 +20,7 @@ #include "path.h" #include "utils.h" #include "ls.h" -#include "directory.h" +#include "database.h" #include "os_compat.h" static ListNode *nodeOfStoredPlaylist(List *list, int idx) @@ -110,7 +110,7 @@ List *loadStoredPlaylist(const char *utf8path) while (myFgets(buffer, sizeof(buffer), file)) { char *s = buffer; - Song *song; + struct mpd_song *song; if (*s == PLAYLIST_COMMENT) continue; @@ -118,8 +118,8 @@ List *loadStoredPlaylist(const char *utf8path) !strncmp(s, musicDir, musicDir_len)) memmove(s, s + musicDir_len + 1, strlen(s + musicDir_len + 1) + 1); - if ((song = getSongFromDB(s))) { - get_song_url(path_max_tmp, song); + if ((song = db_get_song(s))) { + song_get_url(song, path_max_tmp); insertInListWithoutKey(list, xstrdup(path_max_tmp)); } else if (isValidRemoteUtf8Url(s)) insertInListWithoutKey(list, xstrdup(s)); @@ -263,7 +263,7 @@ removeOneSongFromStoredPlaylistByPath(const char *utf8path, int pos) } enum playlist_result -appendSongToStoredPlaylistByPath(const char *utf8path, Song *song) +appendSongToStoredPlaylistByPath(const char *utf8path, struct mpd_song *song) { FILE *file; char *s; @@ -295,7 +295,7 @@ appendSongToStoredPlaylistByPath(const char *utf8path, Song *song) return PLAYLIST_RESULT_TOO_LARGE; } - s = utf8_to_fs_charset(path_max_tmp2, get_song_url(path_max_tmp, song)); + s = utf8_to_fs_charset(path_max_tmp2, song_get_url(song, path_max_tmp)); if (playlist_saveAbsolutePaths && song_is_file(song)) s = rmp2amp_r(path_max_tmp, s); diff --git a/src/storedPlaylist.h b/src/storedPlaylist.h index 964669d35..4721bcc98 100644 --- a/src/storedPlaylist.h +++ b/src/storedPlaylist.h @@ -35,7 +35,7 @@ enum playlist_result removeOneSongFromStoredPlaylistByPath(const char *utf8path, int pos); enum playlist_result -appendSongToStoredPlaylistByPath(const char *utf8path, Song *song); +appendSongToStoredPlaylistByPath(const char *utf8path, struct mpd_song *song); enum playlist_result renameStoredPlaylist(const char *utf8from, const char *utf8to); diff --git a/src/update.c b/src/update.c new file mode 100644 index 000000000..ca3640b73 --- /dev/null +++ b/src/update.c @@ -0,0 +1,444 @@ +/* the Music Player Daemon (MPD) + * Copyright (C) 2003-2007 by Warren Dukes (warren.dukes@gmail.com) + * Copyright (C) 2008 Max Kellermann <max@duempel.org> + * 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 "update.h" +#include "database.h" +#include "log.h" +#include "ls.h" +#include "path.h" +#include "playlist.h" +#include "utils.h" +#include "main_notify.h" +#include "condition.h" +#include "update.h" + +static enum update_progress { + UPDATE_PROGRESS_IDLE = 0, + UPDATE_PROGRESS_RUNNING = 1, + UPDATE_PROGRESS_DONE = 2 +} progress; + +static int modified; + +/* make this dynamic?, or maybe this is big enough... */ +static char *update_paths[32]; +static size_t update_paths_nr; + +static pthread_t update_thr; + +static const unsigned update_task_id_max = 1 << 15; + +static unsigned update_task_id; + +static struct mpd_song *delete; + +static struct condition delete_cond; + +unsigned isUpdatingDB(void) +{ + return (progress != UPDATE_PROGRESS_IDLE) ? update_task_id : 0; +} + +static void directory_set_stat(struct directory *dir, const struct stat *st) +{ + dir->inode = st->st_ino; + dir->device = st->st_dev; + dir->stat = 1; +} + +static void delete_song(struct directory *dir, struct mpd_song *del) +{ + /* first, prevent traversers in main task from getting this */ + songvec_delete(&dir->songs, del); + + /* now take it out of the playlist (in the main_task) */ + cond_enter(&delete_cond); + assert(!delete); + delete = del; + wakeup_main_task(); + do { cond_wait(&delete_cond); } while (delete); + cond_leave(&delete_cond); + + /* finally, all possible references gone, free it */ + song_free(del); +} + +static int delete_each_song(struct mpd_song *song, mpd_unused void *data) +{ + struct directory *dir = data; + assert(song->parent == dir); + delete_song(dir, song); + return 0; +} + +/** + * Recursively remove all sub directories and songs from a directory, + * leaving an empty directory. + */ +static void clear_directory(struct directory *dir) +{ + int i; + + for (i = dir->children.nr; --i >= 0;) + clear_directory(dir->children.base[i]); + dirvec_clear(&dir->children); + + songvec_for_each(&dir->songs, delete_each_song, dir); +} + +/** + * Recursively free a directory and all its contents. + */ +static void delete_directory(struct directory *dir) +{ + assert(dir->parent != NULL); + + clear_directory(dir); + dirvec_delete(&dir->parent->children, dir); + directory_free(dir); +} + +struct delete_data { + char *tmp; + struct directory *dir; +}; + +/* passed to songvec_for_each */ +static int delete_song_if_removed(struct mpd_song *song, void *_data) +{ + struct delete_data *data = _data; + + data->tmp = song_get_url(song, data->tmp); + assert(data->tmp); + + if (!isFile(data->tmp, NULL)) { + delete_song(data->dir, song); + modified = 1; + } + return 0; +} + +static void delete_path(const char *path) +{ + struct directory *dir = db_get_directory(path); + struct mpd_song *song = db_get_song(path); + + if (dir) { + delete_directory(dir); + modified = 1; + } + if (song) { + delete_song(song->parent, song); + modified = 1; + } +} + +static void +removeDeletedFromDirectory(char *path_max_tmp, struct directory *dir) +{ + int i; + struct dirvec *dv = &dir->children; + struct delete_data data; + + for (i = dv->nr; --i >= 0; ) { + if (isDir(dv->base[i]->path)) + continue; + LOG("removing directory: %s\n", dv->base[i]->path); + dirvec_delete(dv, dv->base[i]); + modified = 1; + } + + data.dir = dir; + data.tmp = path_max_tmp; + songvec_for_each(&dir->songs, delete_song_if_removed, &data); +} + +static const char *opendir_path(char *path_max_tmp, const char *dirname) +{ + if (*dirname != '\0') + return rmp2amp_r(path_max_tmp, + utf8_to_fs_charset(path_max_tmp, dirname)); + return musicDir; +} + +static int statDirectory(struct directory *dir) +{ + struct stat st; + + if (myStat(directory_get_path(dir), &st) < 0) + return -1; + + directory_set_stat(dir, &st); + + return 0; +} + +static int +inodeFoundInParent(struct directory *parent, ino_t inode, dev_t device) +{ + while (parent) { + if (!parent->stat && statDirectory(parent) < 0) + return -1; + if (parent->inode == inode && parent->device == device) { + DEBUG("recursive directory found\n"); + return 1; + } + parent = parent->parent; + } + + return 0; +} + +static int updateDirectory(struct directory *dir, const struct stat *st); + +static void +updateInDirectory(struct directory *dir, + const char *name, const struct stat *st) +{ + if (S_ISREG(st->st_mode) && hasMusicSuffix(name, 0)) { + const char *shortname = mpd_basename(name); + struct mpd_song *song; + + if (!(song = songvec_find(&dir->songs, shortname))) { + if (!(song = song_file_load(shortname, dir))) + return; + songvec_add(&dir->songs, song); + modified = 1; + LOG("added %s\n", name); + } else if (st->st_mtime != song->mtime) { + LOG("updating %s\n", name); + if (!song_file_update(song)) + delete_song(dir, song); + modified = 1; + } + } else if (S_ISDIR(st->st_mode)) { + struct directory *subdir; + + if (inodeFoundInParent(dir, st->st_ino, st->st_dev)) + return; + + if (!(subdir = directory_get_child(dir, name))) + subdir = directory_new_child(dir, name); + + assert(dir == subdir->parent); + + if (!updateDirectory(subdir, st)) + delete_directory(subdir); + } else { + DEBUG("update: %s is not a directory or music\n", name); + } +} + +/* we don't look at hidden files nor files with newlines in them */ +static int skip_path(const char *path) +{ + return (path[0] == '.' || strchr(path, '\n')) ? 1 : 0; +} + +static int updateDirectory(struct directory *dir, const struct stat *st) +{ + DIR *fs_dir; + const char *dirname = directory_get_path(dir); + struct dirent *ent; + char path_max_tmp[MPD_PATH_MAX]; + + assert(S_ISDIR(st->st_mode)); + + directory_set_stat(dir, st); + + if (!(fs_dir = opendir(opendir_path(path_max_tmp, dirname)))) + return 0; + + removeDeletedFromDirectory(path_max_tmp, dir); + + while ((ent = readdir(fs_dir))) { + char *utf8; + struct stat st2; + + if (skip_path(ent->d_name)) + continue; + + utf8 = fs_charset_to_utf8(path_max_tmp, ent->d_name); + if (!utf8) + continue; + + if (!isRootDirectory(dir->path)) + utf8 = pfx_dir(path_max_tmp, utf8, strlen(utf8), + dirname, strlen(dirname)); + + if (myStat(path_max_tmp, &st2) == 0) + updateInDirectory(dir, path_max_tmp, &st2); + else + delete_path(path_max_tmp); + } + + closedir(fs_dir); + + return 1; +} + +static struct directory * +directory_make_child_checked(struct directory *parent, const char *path) +{ + struct directory *dir; + struct stat st; + struct mpd_song *conflicting; + + if ((dir = directory_get_child(parent, path))) + return dir; + + if (myStat(path, &st) < 0 || + inodeFoundInParent(parent, st.st_ino, st.st_dev)) + return NULL; + + /* if we're adding directory paths, make sure to delete filenames + with potentially the same name */ + if ((conflicting = songvec_find(&parent->songs, mpd_basename(path)))) + delete_song(parent, conflicting); + + dir = directory_new_child(parent, path); + directory_set_stat(dir, &st); + return dir; +} + +static struct directory * +addParentPathToDB(const char *utf8path) +{ + struct directory *dir = db_get_root(); + char *duplicated = xstrdup(utf8path); + char *slash = duplicated; + + while ((slash = strchr(slash, '/'))) { + *slash = 0; + + dir = directory_make_child_checked(dir, duplicated); + if (!dir || !slash) + break; + + *slash++ = '/'; + } + + free(duplicated); + return dir; +} + +static void updatePath(const char *utf8path) +{ + struct stat st; + + if (myStat(utf8path, &st) < 0) + delete_path(utf8path); + else + updateInDirectory(addParentPathToDB(utf8path), utf8path, &st); +} + +static void * update_task(void *_path) +{ + if (_path != NULL && !isRootDirectory(_path)) { + updatePath((char *)_path); + free(_path); + } else { + struct directory *dir = db_get_root(); + struct stat st; + + if (myStat(directory_get_path(dir), &st) == 0) + updateDirectory(dir, &st); + } + + if (modified) + db_save(); + progress = UPDATE_PROGRESS_DONE; + wakeup_main_task(); + return NULL; +} + +static void spawn_update_task(char *path) +{ + pthread_attr_t attr; + + assert(pthread_equal(pthread_self(), main_task)); + + progress = UPDATE_PROGRESS_RUNNING; + modified = 0; + pthread_attr_init(&attr); + if (pthread_create(&update_thr, &attr, update_task, path)) + FATAL("Failed to spawn update task: %s\n", strerror(errno)); + if (++update_task_id > update_task_id_max) + update_task_id = 1; + DEBUG("spawned thread for update job id %i\n", update_task_id); +} + +unsigned directory_update_init(char *path) +{ + assert(pthread_equal(pthread_self(), main_task)); + + if (progress != UPDATE_PROGRESS_IDLE) { + unsigned next_task_id; + + if (!path) + return 0; + if (update_paths_nr == ARRAY_SIZE(update_paths)) { + free(path); + return 0; + } + + assert(update_paths_nr < ARRAY_SIZE(update_paths)); + update_paths[update_paths_nr++] = path; + next_task_id = update_task_id + update_paths_nr; + + return next_task_id > update_task_id_max ? 1 : next_task_id; + } + spawn_update_task(path); + return update_task_id; +} + +void reap_update_task(void) +{ + assert(pthread_equal(pthread_self(), main_task)); + + if (progress == UPDATE_PROGRESS_IDLE) + return; + + cond_enter(&delete_cond); + if (delete) { + char tmp[MPD_PATH_MAX]; + LOG("removing: %s\n", song_get_url(delete, tmp)); + deleteASongFromPlaylist(delete); + delete = NULL; + cond_signal(&delete_cond); + } + cond_leave(&delete_cond); + + if (progress != UPDATE_PROGRESS_DONE) + return; + if (pthread_join(update_thr, NULL)) + FATAL("error joining update thread: %s\n", strerror(errno)); + + if (modified) + playlistVersionChange(); + + if (update_paths_nr) { + char *path = update_paths[0]; + memmove(&update_paths[0], &update_paths[1], + --update_paths_nr * sizeof(char *)); + spawn_update_task(path); + } else { + progress = UPDATE_PROGRESS_IDLE; + } +} diff --git a/src/update.h b/src/update.h new file mode 100644 index 000000000..0842c0df0 --- /dev/null +++ b/src/update.h @@ -0,0 +1,34 @@ +/* the Music Player Daemon (MPD) + * Copyright (C) 2003-2007 by Warren Dukes (warren.dukes@gmail.com) + * Copyright (C) 2008 Max Kellermann <max@duempel.org> + * 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 + */ + +#ifndef UPDATE_H +#define UPDATE_H + +unsigned isUpdatingDB(void); + +/* + * returns the positive update job ID on success, + * returns 0 if busy + * @path will be freed by this function and should not be reused + */ +unsigned directory_update_init(char *path); + +void reap_update_task(void); + +#endif |