Commit 41bd98a5 authored by Alexey Morsov's avatar Alexey Morsov

Merge commit 'v0.17.0-git200911' into alt

parents cb802aa8 3ea10738
......@@ -39,6 +39,7 @@ tags
*~
.#*
.stgit*
doc/doxygen.conf
doc/protocol.html
doc/protocol
doc/user
......@@ -60,3 +61,4 @@ test/run_normalize
test/tmp
test/run_inotify
test/test_queue_priority
test/run_ntp_server
......@@ -84,11 +84,6 @@ For Ogg Vorbis support. You will need libogg and libvorbis.
FLAC - http://flac.sourceforge.net/
For FLAC support. You will need version 1.1.0 or higher of libflac.
OggFLAC - http://www.xiph.org/ogg/vorbis/ and http://flac.sourceforge.net/
For OggFLAC support. You will need liboggflac, which can be built from the
FLAC sources if libogg is already installed. Versions of flac 1.1.3 and
greater will automatically detect and use OggFLAC if it's available.
Audio File - http://www.68k.org/~michael/audiofile/
For WAVE, AIFF, and AU support. You will need libaudiofile.
......
ACLOCAL_AMFLAGS = -I m4
AUTOMAKE_OPTIONS = foreign 1.10 dist-bzip2 subdir-objects
AM_CPPFLAGS = -I$(srcdir)/src $(GLIB_CFLAGS)
AM_CPPFLAGS += -I$(srcdir)/src $(GLIB_CFLAGS)
AM_CPPFLAGS += -DSYSTEM_CONFIG_FILE_LOCATION='"$(sysconfdir)/mpd.conf"'
......@@ -11,6 +11,7 @@ noinst_LIBRARIES =
src_mpd_CFLAGS = $(AM_CFLAGS) $(MPD_CFLAGS)
src_mpd_CPPFLAGS = $(AM_CPPFLAGS) \
$(AVAHI_CFLAGS) \
$(LIBWRAP_CFLAGS) \
$(SQLITE_CFLAGS) \
$(ARCHIVE_CFLAGS) \
......@@ -21,6 +22,7 @@ src_mpd_CPPFLAGS = $(AM_CPPFLAGS) \
$(FILTER_CFLAGS) \
$(OUTPUT_CFLAGS)
src_mpd_LDADD = $(MPD_LIBS) \
$(AVAHI_LIBS) \
$(LIBWRAP_LDFLAGS) \
$(SQLITE_LIBS) \
$(ARCHIVE_LIBS) \
......@@ -43,7 +45,6 @@ mpd_headers = \
src/audio_parser.h \
src/output_internal.h \
src/output_api.h \
src/output_plugin.h \
src/output_list.h \
src/output_all.h \
src/output_thread.h \
......@@ -76,7 +77,6 @@ mpd_headers = \
src/decoder_internal.h \
src/directory.h \
src/directory_save.h \
src/directory_print.h \
src/database.h \
src/encoder_plugin.h \
src/encoder_list.h \
......@@ -143,9 +143,6 @@ mpd_headers = \
src/open.h \
src/output/httpd_client.h \
src/output/httpd_internal.h \
src/output/pulse_output_plugin.h \
src/output/roar_output_plugin.h \
src/output/winmm_output_plugin.h \
src/page.h \
src/pcm_buffer.h \
src/pcm_utils.h \
......@@ -164,6 +161,7 @@ mpd_headers = \
src/player_thread.h \
src/player_control.h \
src/playlist.h \
src/playlist_error.h \
src/playlist_internal.h \
src/playlist_print.h \
src/playlist_save.h \
......@@ -248,6 +246,7 @@ src_mpd_SOURCES = \
$(OUTPUT_API_SRC) $(OUTPUT_SRC) \
$(MIXER_API_SRC) $(MIXER_SRC) \
$(FILTER_SRC) \
src/glib_socket.h \
src/notify.c \
src/audio.c \
src/audio_check.c \
......@@ -266,8 +265,14 @@ src_mpd_SOURCES = \
src/decoder_print.c \
src/directory.c \
src/directory_save.c \
src/directory_print.c \
src/database.c \
src/db_error.h \
src/db_save.c src/db_save.h \
src/db_print.c src/db_print.h \
src/db_plugin.h \
src/db_visitor.h \
src/db_selection.h \
src/db/simple_db_plugin.c src/db/simple_db_plugin.h \
src/dirvec.c \
src/exclude.c \
src/fd_util.c \
......@@ -294,10 +299,13 @@ src_mpd_SOURCES = \
src/client_message.c \
src/client_subscribe.h \
src/client_subscribe.c \
src/tcp_socket.c src/tcp_socket.h \
src/udp_server.c src/udp_server.h \
src/server_socket.c \
src/listen.c \
src/log.c \
src/ls.c \
src/io_thread.c src/io_thread.h \
src/main.c \
src/main_win32.c \
src/event_pipe.c \
......@@ -346,6 +354,7 @@ src_mpd_SOURCES = \
src/song_print.c \
src/song_save.c \
src/songvec.c \
src/resolver.c src/resolver.h \
src/socket_util.c \
src/state_file.c \
src/stats.c \
......@@ -527,10 +536,6 @@ if HAVE_FLAC
DECODER_SRC += src/decoder/flac_decoder_plugin.c
endif
if HAVE_OGGFLAC
DECODER_SRC += src/decoder/oggflac_decoder_plugin.c
endif
if HAVE_AUDIOFILE
DECODER_SRC += src/decoder/audiofile_decoder_plugin.c
endif
......@@ -635,12 +640,14 @@ endif
INPUT_CFLAGS = \
$(CURL_CFLAGS) \
$(SOUP_CFLAGS) \
$(CDIO_PARANOIA_CFLAGS) \
$(FFMPEG_CFLAGS) \
$(MMS_CFLAGS)
INPUT_LIBS = \
$(CURL_LIBS) \
$(SOUP_LIBS) \
$(CDIO_PARANOIA_LIBS) \
$(FFMPEG_LIBS) \
$(MMS_LIBS)
......@@ -649,6 +656,7 @@ INPUT_SRC = \
src/input_init.c \
src/input_registry.c \
src/input_stream.c \
src/input_internal.c src/input_internal.h \
src/input/rewind_input_plugin.c \
src/input/file_input_plugin.c
......@@ -657,6 +665,12 @@ INPUT_SRC += src/input/curl_input_plugin.c \
src/icy_metadata.c
endif
if ENABLE_SOUP
INPUT_SRC += \
src/input/soup_input_plugin.c \
src/input/soup_input_plugin.h
endif
if ENABLE_CDIO_PARANOIA
INPUT_SRC += src/input/cdio_paranoia_input_plugin.c
endif
......@@ -680,6 +694,7 @@ OUTPUT_CFLAGS = \
$(FFADO_CFLAGS) \
$(JACK_CFLAGS) \
$(OPENAL_CFLAGS) \
$(OPENSSL_CFLAGS) \
$(PULSE_CFLAGS) \
$(SHOUT_CFLAGS)
......@@ -702,10 +717,12 @@ OUTPUT_API_SRC = \
src/output_state.c \
src/output_print.c \
src/output_command.c \
src/output_plugin.c src/output_plugin.h \
src/output_finish.c \
src/output_init.c
OUTPUT_SRC = \
src/output/null_plugin.c
src/output/null_output_plugin.c
MIXER_API_SRC = \
src/mixer_control.c \
......@@ -717,12 +734,14 @@ MIXER_SRC = \
src/mixer/software_mixer_plugin.c
if HAVE_ALSA
OUTPUT_SRC += src/output/alsa_plugin.c
OUTPUT_SRC += \
src/output/alsa_output_plugin.c src/output/alsa_output_plugin.h
MIXER_SRC += src/mixer/alsa_mixer_plugin.c
endif
if HAVE_ROAR
OUTPUT_SRC += src/output/roar_plugin.c
OUTPUT_SRC += \
src/output/roar_output_plugin.c src/output/roar_output_plugin.h
MIXER_SRC += src/mixer/roar_mixer_plugin.c
endif
......@@ -731,7 +750,7 @@ OUTPUT_SRC += src/output/ffado_output_plugin.c
endif
if HAVE_AO
OUTPUT_SRC += src/output/ao_plugin.c
OUTPUT_SRC += src/output/ao_output_plugin.c
endif
if HAVE_FIFO
......@@ -747,34 +766,39 @@ OUTPUT_SRC += src/output/jack_output_plugin.c
endif
if HAVE_MVP
OUTPUT_SRC += src/output/mvp_plugin.c
OUTPUT_SRC += src/output/mvp_output_plugin.c
endif
if HAVE_OSS
OUTPUT_SRC += src/output/oss_plugin.c
OUTPUT_SRC += src/output/oss_output_plugin.c
MIXER_SRC += src/mixer/oss_mixer_plugin.c
endif
if HAVE_OPENAL
OUTPUT_SRC += src/output/openal_plugin.c
OUTPUT_SRC += src/output/openal_output_plugin.c
endif
if HAVE_OSX
OUTPUT_SRC += src/output/osx_plugin.c
OUTPUT_SRC += src/output/osx_output_plugin.c
endif
if ENABLE_RAOP_OUTPUT
OUTPUT_SRC += src/output/raop_output_plugin.c
OUTPUT_SRC += \
src/ntp_server.c src/ntp_server.h \
src/rtsp_client.c src/rtsp_client.h \
src/output/raop_output_plugin.c
MIXER_SRC += src/mixer/raop_mixer_plugin.c
OUTPUT_LIBS += $(OPENSSL_LIBS)
endif
if HAVE_PULSE
OUTPUT_SRC += src/output/pulse_output_plugin.c
OUTPUT_SRC += \
src/output/pulse_output_plugin.c src/output/pulse_output_plugin.h
MIXER_SRC += src/mixer/pulse_mixer_plugin.c
endif
if HAVE_SHOUT
OUTPUT_SRC += src/output/shout_plugin.c
OUTPUT_SRC += src/output/shout_output_plugin.c
endif
if ENABLE_RECORDER_OUTPUT
......@@ -793,7 +817,8 @@ OUTPUT_SRC += src/output/solaris_output_plugin.c
endif
if ENABLE_WINMM_OUTPUT
OUTPUT_SRC += src/output/winmm_output_plugin.c
OUTPUT_SRC += \
src/output/winmm_output_plugin.c src/output/winmm_output_plugin.h
MIXER_SRC += src/mixer/winmm_mixer_plugin.c
endif
......@@ -857,8 +882,9 @@ SPARSE_CPPFLAGS = $(DEFAULT_INCLUDES) \
-I$(shell $(CC) -print-file-name=include-fixed)
SPARSE_CPPFLAGS += -D__SCHAR_MAX__=127 -D__SHRT_MAX__=32767 \
-D__INT_MAX__=2147483647 -D__LONG_MAX__=2147483647
SPARSE_SRC = $(addprefix $(top_srcdir)/,$(filter %.c,$(src_mpd_SOURCES)))
sparse-check:
$(SPARSE) -I. $(src_mpd_CFLAGS) $(src_mpd_CPPFLAGS) $(SPARSE_FLAGS) $(SPARSE_CPPFLAGS) $(filter-out %.cxx,$(src_mpd_SOURCES))
$(SPARSE) -I. $(src_mpd_CFLAGS) $(src_mpd_CPPFLAGS) $(SPARSE_FLAGS) $(SPARSE_CPPFLAGS) $(SPARSE_SRC)
.PHONY: sparse-check
......@@ -881,6 +907,7 @@ noinst_PROGRAMS = \
test/dump_playlist \
test/run_decoder \
test/read_tags \
test/run_ntp_server \
test/run_filter \
test/run_output \
test/run_convert \
......@@ -908,6 +935,7 @@ test_run_input_LDADD = $(MPD_LIBS) \
$(GLIB_LIBS)
test_run_input_SOURCES = test/run_input.c \
test/stdbin.h \
src/io_thread.c src/io_thread.h \
src/conf.c src/tokenizer.c src/utils.c src/string_util.c\
src/tag.c src/tag_pool.c src/tag_save.c \
src/fd_util.c \
......@@ -926,6 +954,7 @@ test_dump_playlist_LDADD = $(MPD_LIBS) \
$(INPUT_LIBS) \
$(GLIB_LIBS)
test_dump_playlist_SOURCES = test/dump_playlist.c \
src/io_thread.c src/io_thread.h \
src/conf.c src/tokenizer.c src/utils.c src/string_util.c\
src/uri.c \
src/song.c src/tag.c src/tag_pool.c src/tag_save.c \
......@@ -956,6 +985,7 @@ test_run_decoder_LDADD = $(MPD_LIBS) \
$(GLIB_LIBS)
test_run_decoder_SOURCES = test/run_decoder.c \
test/stdbin.h \
src/io_thread.c src/io_thread.h \
src/conf.c src/tokenizer.c src/utils.c src/string_util.c src/log.c \
src/tag.c src/tag_pool.c \
src/replay_gain_info.c \
......@@ -979,6 +1009,7 @@ test_read_tags_LDADD = $(MPD_LIBS) \
$(INPUT_LIBS) $(DECODER_LIBS) \
$(GLIB_LIBS)
test_read_tags_SOURCES = test/read_tags.c \
src/io_thread.c src/io_thread.h \
src/conf.c src/tokenizer.c src/utils.c src/string_util.c src/log.c \
src/tag.c src/tag_pool.c \
src/replay_gain_info.c \
......@@ -991,6 +1022,15 @@ test_read_tags_SOURCES = test/read_tags.c \
$(TAG_SRC) \
$(DECODER_SRC)
test_run_ntp_server_CPPFLAGS = $(AM_CPPFLAGS)
test_run_ntp_server_LDADD = $(MPD_LIBS) \
$(GLIB_LIBS)
test_run_ntp_server_SOURCES = test/run_ntp_server.c \
test/signals.c test/signals.h \
src/io_thread.c src/io_thread.h \
src/udp_server.c src/udp_server.h \
src/ntp_server.c src/ntp_server.h
test_run_filter_CPPFLAGS = $(AM_CPPFLAGS)
test_run_filter_LDADD = $(MPD_LIBS) \
$(SAMPLERATE_LIBS) \
......@@ -1094,6 +1134,9 @@ test_run_output_LDADD = $(MPD_LIBS) \
test_run_output_SOURCES = test/run_output.c \
test/stdbin.h \
src/conf.c src/tokenizer.c src/utils.c src/string_util.c src/log.c \
src/io_thread.c src/io_thread.h \
src/udp_server.c src/udp_server.h \
src/tcp_socket.c src/tcp_socket.h \
src/audio_check.c \
src/audio_format.c \
src/audio_parser.c \
......@@ -1102,7 +1145,9 @@ test_run_output_SOURCES = test/run_output.c \
src/fifo_buffer.c \
src/page.c \
src/socket_util.c \
src/output_init.c src/output_list.c \
src/resolver.c \
src/output_init.c src/output_finish.c src/output_list.c \
src/output_plugin.c \
$(ENCODER_SRC) \
src/mixer_api.c \
src/mixer_control.c \
......
......@@ -5,11 +5,14 @@ ver 0.17 (2011/??/??)
* input:
- cdio_paranoia: new input plugin to play audio CDs
- curl: enable CURLOPT_NETRC
- curl: non-blocking I/O
- soup: new input plugin based on libsoup
- ffmpeg: support libavformat 0.7
* decoder:
- mpg123: implement seeking
- ffmpeg: drop support for pre-0.5 ffmpeg
- ffmpeg: support libavformat 0.7
- oggflac: delete this obsolete plugin
* output:
- osx: allow user to specify other audio devices
- raop: new output plugin
......@@ -19,10 +22,30 @@ ver 0.17 (2011/??/??)
* cue: show CUE track numbers
ver 0.16.4 (2011/??/??)
ver 0.16.5 (2010/??/??)
* configure.ac
- disable assertions in the non-debugging build
- show solaris plugin result correctly
- add option --enable-solaris-output
* pcm_format: fix 32-to-24 bit conversion (the "silence" bug)
* input:
- rewind: reduce heap usage
* decoder:
- ffmpeg: higher precision timestamps
- ffmpeg: don't require key frame for seeking
* WIN32: close sockets properly
ver 0.16.4 (2011/09/01)
* don't abort configure when avahi is not found
* auto-detect libmad without pkg-config
* fix memory leaks
* don't resume playback when seeking to another song while paused
* apply follow_inside_symlinks to absolute symlinks
* fix playback discontinuation after seeking
* input:
- curl: limit the receive buffer size
- curl: implement a hard-coded timeout of 10 seconds
* decoder:
- ffmpeg: workaround for semantic API change in recent ffmpeg versions
- flac: validate the sample rate when scanning the tag
......@@ -31,6 +54,10 @@ ver 0.16.4 (2011/??/??)
- vorbis: don't send end-of-stream on flush
* output:
- alsa: fix SIGFPE when alsa announces a period size of 0
- httpd: don't warn on client disconnect
- osx: don't drain the buffer when closing
- pulse: fix deadlock when resuming the stream
- pulse: fix deadlock when the stream was suspended
ver 0.16.3 (2011/06/04)
......
......@@ -57,7 +57,7 @@
Some example code:
</para>
<programlisting lang="C">static inline bool
<programlisting lang="C">static inline int
foo(const char *abc, int xyz)
{
if (abc == NULL) {
......
......@@ -534,7 +534,7 @@ WARN_LOGFILE =
# directories like "/usr/src/myproject". Separate the files or directories
# with spaces.
INPUT = @top_srcdir@/src/
INPUT = @abs_top_srcdir@/src/
# This tag can be used to specify the character encoding of the source files
# that doxygen parses. Internally doxygen uses the UTF-8 encoding, which is
......
......@@ -703,6 +703,34 @@ cd mpd-version</programlisting>
</informaltable>
</section>
<section>
<title><varname>soup</varname></title>
<para>
Opens remote files or streams over HTTP.
</para>
<informaltable>
<tgroup cols="2">
<thead>
<row>
<entry>Setting</entry>
<entry>Description</entry>
</row>
</thead>
<tbody>
<row>
<entry>
<varname>proxy</varname>
</entry>
<entry>
Sets the address of the HTTP proxy server.
</entry>
</row>
</tbody>
</tgroup>
</informaltable>
</section>
</section>
<section>
......
......@@ -63,3 +63,18 @@ AC_DEFUN([MPD_AUTO_PKG], [
MPD_AUTO_RESULT([$1], [$4], [$5])
])
dnl Check with pkg-config first, fall back to AC_CHECK_LIB.
dnl
dnl Parameters: varname1, varname2, pkgname, libname, symname, libs, cflags, description, errmsg
AC_DEFUN([MPD_AUTO_PKG_LIB], [
if eval "test x`echo '$'enable_$1` != xno"; then
PKG_CHECK_MODULES([$2], [$3],
[eval "found_$1=yes"],
AC_CHECK_LIB($4, $5,
[eval "found_$1=yes $2_LIBS='$6' $2_CFLAGS='$7'"],
[eval "found_$1=no"]))
fi
MPD_AUTO_RESULT([$1], [$8], [$9])
])
......@@ -3,7 +3,7 @@ PWD=`pwd`
## If we're not in the scripts directory
## assume the base directory.
if test "`basename $PWD`" == "scripts"; then
if test "`basename $PWD`" = "scripts"; then
cd ../
else
MYOLDPWD=`pwd`
......@@ -18,7 +18,7 @@ fi
make
make dist
if test "`basename $PWD`" == "scripts"; then
if test "`basename $PWD`" = "scripts"; then
cd contrib/
else
cd $MYOLDPWD
......
......@@ -24,6 +24,7 @@
#include "config.h"
#include "archive/bz2_archive_plugin.h"
#include "archive_api.h"
#include "input_internal.h"
#include "input_plugin.h"
#include "refcount.h"
......@@ -113,7 +114,11 @@ bz2_open(const char *pathname, GError **error_r)
refcount_init(&context->ref);
//open archive
context->istream = input_stream_open(pathname, error_r);
static GStaticMutex mutex = G_STATIC_MUTEX_INIT;
context->istream = input_stream_open(pathname,
g_static_mutex_get_mutex(&mutex),
NULL,
error_r);
if (context->istream == NULL) {
g_free(context);
return NULL;
......@@ -168,12 +173,15 @@ bz2_close(struct archive_file *file)
/* single archive handling */
static struct input_stream *
bz2_open_stream(struct archive_file *file, const char *path, GError **error_r)
bz2_open_stream(struct archive_file *file, const char *path,
GMutex *mutex, GCond *cond,
GError **error_r)
{
struct bz2_archive_file *context = (struct bz2_archive_file *) file;
struct bz2_input_stream *bis = g_new(struct bz2_input_stream, 1);
input_stream_init(&bis->base, &bz2_inputplugin, path);
input_stream_init(&bis->base, &bz2_inputplugin, path,
mutex, cond);
bis->archive = context;
......
......@@ -24,6 +24,7 @@
#include "config.h"
#include "archive/iso9660_archive_plugin.h"
#include "archive_api.h"
#include "input_internal.h"
#include "input_plugin.h"
#include "refcount.h"
......@@ -172,15 +173,17 @@ struct iso9660_input_stream {
};
static struct input_stream *
iso9660_archive_open_stream(struct archive_file *file,
const char *pathname, GError **error_r)
iso9660_archive_open_stream(struct archive_file *file, const char *pathname,
GMutex *mutex, GCond *cond,
GError **error_r)
{
struct iso9660_archive_file *context =
(struct iso9660_archive_file *)file;
struct iso9660_input_stream *iis;
iis = g_new(struct iso9660_input_stream, 1);
input_stream_init(&iis->base, &iso9660_input_plugin, pathname);
input_stream_init(&iis->base, &iso9660_input_plugin, pathname,
mutex, cond);
iis->archive = context;
iis->statbuf = iso9660_ifs_stat_translate(context->iso, pathname);
......
......@@ -25,6 +25,7 @@
#include "archive/zzip_archive_plugin.h"
#include "archive_api.h"
#include "archive_api.h"
#include "input_internal.h"
#include "input_plugin.h"
#include "refcount.h"
......@@ -134,14 +135,17 @@ struct zzip_input_stream {
static struct input_stream *
zzip_archive_open_stream(struct archive_file *file,
const char *pathname, GError **error_r)
const char *pathname,
GMutex *mutex, GCond *cond,
GError **error_r)
{
struct zzip_archive *context = (struct zzip_archive *) file;
struct zzip_input_stream *zis;
ZZIP_STAT z_stat;
zis = g_new(struct zzip_input_stream, 1);
input_stream_init(&zis->base, &zzip_input_plugin, pathname);
input_stream_init(&zis->base, &zzip_input_plugin, pathname,
mutex, cond);
zis->archive = context;
zis->file = zzip_file_open(context->dir, pathname, 0);
......
......@@ -81,12 +81,14 @@ archive_file_scan_next(struct archive_file *file)
}
struct input_stream *
archive_file_open_stream(struct archive_file *file,
const char *path, GError **error_r)
archive_file_open_stream(struct archive_file *file, const char *path,
GMutex *mutex, GCond *cond,
GError **error_r)
{
assert(file != NULL);
assert(file->plugin != NULL);
assert(file->plugin->open_stream != NULL);
return file->plugin->open_stream(file, path, error_r);
return file->plugin->open_stream(file, path, mutex, cond,
error_r);
}
......@@ -73,6 +73,7 @@ struct archive_plugin {
*/
struct input_stream *(*open_stream)(struct archive_file *af,
const char *path,
GMutex *mutex, GCond *cond,
GError **error_r);
/**
......@@ -101,7 +102,8 @@ char *
archive_file_scan_next(struct archive_file *file);
struct input_stream *
archive_file_open_stream(struct archive_file *file,
const char *path, GError **error_r);
archive_file_open_stream(struct archive_file *file, const char *path,
GMutex *mutex, GCond *cond,
GError **error_r);
#endif
......@@ -19,9 +19,11 @@
#include "config.h"
#include "client_internal.h"
#include "fd_util.h"
#include "fifo_buffer.h"
#include "socket_util.h"
#include "resolver.h"
#include "permission.h"
#include "glib_socket.h"
#include <assert.h>
#include <sys/types.h>
......@@ -69,7 +71,7 @@ client_new(struct player_control *player_control,
progname, hostaddr);
g_free(hostaddr);
close(fd);
close_socket(fd);
return;
}
......@@ -79,18 +81,14 @@ client_new(struct player_control *player_control,
if (client_list_is_full()) {
g_warning("Max Connections Reached!");
close(fd);
close_socket(fd);
return;
}
client = g_new0(struct client, 1);
client->player_control = player_control;
#ifndef G_OS_WIN32
client->channel = g_io_channel_unix_new(fd);
#else
client->channel = g_io_channel_win32_new_socket(fd);
#endif
client->channel = g_io_channel_new_socket(fd);
/* GLib is responsible for closing the file descriptor */
g_io_channel_set_close_on_unref(client->channel, true);
/* NULL encoding means the stream is binary safe; the MPD
......
......@@ -143,7 +143,7 @@ config_new_param(const char *value, int line)
return ret;
}
static void
void
config_param_free(struct config_param *param)
{
g_free(param->value);
......@@ -223,20 +223,13 @@ void config_global_check(void)
}
}
bool
void
config_add_block_param(struct config_param * param, const char *name,
const char *value, int line, GError **error_r)
const char *value, int line)
{
struct block_param *bp;
bp = config_get_block_param(param, name);
if (bp != NULL) {
g_set_error(error_r, config_quark(), 0,
"\"%s\" first defined on line %i, and "
"redefined on line %i\n", name,
bp->line, line);
return false;
}
assert(config_get_block_param(param, name) == NULL);
param->num_block_params++;
......@@ -250,7 +243,46 @@ config_add_block_param(struct config_param * param, const char *name,
bp->value = g_strdup(value);
bp->line = line;
bp->used = false;
}
static bool
config_read_name_value(struct config_param *param, char *input, unsigned line,
GError **error_r)
{
const char *name = tokenizer_next_word(&input, error_r);
if (name == NULL) {
assert(*input != 0);
return false;
}
const char *value = tokenizer_next_string(&input, error_r);
if (value == NULL) {
if (*input == 0) {
assert(error_r == NULL || *error_r == NULL);
g_set_error(error_r, config_quark(), 0,
"Value missing");
} else {
assert(error_r == NULL || *error_r != NULL);
}
return false;
}
if (*input != 0 && *input != CONF_COMMENT) {
g_set_error(error_r, config_quark(), 0,
"Unknown tokens after value");
return false;
}
const struct block_param *bp = config_get_block_param(param, name);
if (bp != NULL) {
g_set_error(error_r, config_quark(), 0,
"\"%s\" is duplicate, first defined on line %i",
name, bp->line);
return false;
}
config_add_block_param(param, name, value, line);
return true;
}
......@@ -259,11 +291,9 @@ config_read_block(FILE *fp, int *count, char *string, GError **error_r)
{
struct config_param *ret = config_new_param(NULL, *count);
GError *error = NULL;
bool success;
while (true) {
char *line;
const char *name, *value;
line = fgets(string, MAX_STRING_SIZE, fp);
if (line == NULL) {
......@@ -296,42 +326,13 @@ config_read_block(FILE *fp, int *count, char *string, GError **error_r)
/* parse name and value */
name = tokenizer_next_word(&line, &error);
if (name == NULL) {
if (!config_read_name_value(ret, line, *count, &error)) {
assert(*line != 0);
config_param_free(ret);
g_propagate_prefixed_error(error_r, error,
"line %i: ", *count);
return NULL;
}
value = tokenizer_next_string(&line, &error);
if (value == NULL) {
config_param_free(ret);
if (*line == 0)
g_set_error(error_r, config_quark(), 0,
"line %i: Value missing", *count);
else
g_propagate_prefixed_error(error_r, error,
"line %i: ",
*count);
return NULL;
}
if (*line != 0 && *line != CONF_COMMENT) {
config_param_free(ret);
g_set_error(error_r, config_quark(), 0,
"line %i: Unknown tokens after value",
*count);
return NULL;
}
success = config_add_block_param(ret, name, value, *count,
error_r);
if (!success) {
config_param_free(ret);
return false;
}
}
}
......@@ -462,7 +463,7 @@ config_read_file(const char *file, GError **error_r)
return true;
}
struct config_param *
const struct config_param *
config_get_next_param(const char *name, const struct config_param * last)
{
struct config_entry *entry;
......@@ -502,22 +503,23 @@ config_get_string(const char *name, const char *default_value)
return param->value;
}
const char *
config_get_path(const char *name)
char *
config_dup_path(const char *name, GError **error_r)
{
struct config_param *param = config_get_param(name);
char *path;
assert(error_r != NULL);
assert(*error_r == NULL);
const struct config_param *param = config_get_param(name);
if (param == NULL)
return NULL;
path = parsePath(param->value);
if (path == NULL)
MPD_ERROR("error parsing \"%s\" at line %i\n",
name, param->line);
char *path = parsePath(param->value, error_r);
if (G_UNLIKELY(path == NULL))
g_prefix_error(error_r,
"Invalid path in \"%s\" at line %i: ",
name, param->line);
g_free(param->value);
return param->value = path;
return path;
}
unsigned
......@@ -558,7 +560,7 @@ config_get_positive(const char *name, unsigned default_value)
return (unsigned)value;
}
struct block_param *
const struct block_param *
config_get_block_param(const struct config_param * param, const char *name)
{
if (param == NULL)
......@@ -596,7 +598,7 @@ const char *
config_get_block_string(const struct config_param *param, const char *name,
const char *default_value)
{
struct block_param *bp = config_get_block_param(param, name);
const struct block_param *bp = config_get_block_param(param, name);
if (bp == NULL)
return default_value;
......@@ -604,11 +606,31 @@ config_get_block_string(const struct config_param *param, const char *name,
return bp->value;
}
char *
config_dup_block_path(const struct config_param *param, const char *name,
GError **error_r)
{
assert(error_r != NULL);
assert(*error_r == NULL);
const struct block_param *bp = config_get_block_param(param, name);
if (bp == NULL)
return NULL;
char *path = parsePath(bp->value, error_r);
if (G_UNLIKELY(path == NULL))
g_prefix_error(error_r,
"Invalid path in \"%s\" at line %i: ",
name, bp->line);
return path;
}
unsigned
config_get_block_unsigned(const struct config_param *param, const char *name,
unsigned default_value)
{
struct block_param *bp = config_get_block_param(param, name);
const struct block_param *bp = config_get_block_param(param, name);
long value;
char *endptr;
......@@ -629,7 +651,7 @@ bool
config_get_block_bool(const struct config_param *param, const char *name,
bool default_value)
{
struct block_param *bp = config_get_block_param(param, name);
const struct block_param *bp = config_get_block_param(param, name);
bool success, value;
if (bp == NULL)
......
......@@ -111,6 +111,7 @@ struct config_param {
* A GQuark for GError instances, resulting from malformed
* configuration.
*/
G_GNUC_CONST
static inline GQuark
config_quark(void)
{
......@@ -132,11 +133,11 @@ config_read_file(const char *file, GError **error_r);
/* don't free the returned value
set _last_ to NULL to get first entry */
G_GNUC_PURE
struct config_param *
const struct config_param *
config_get_next_param(const char *name, const struct config_param *last);
G_GNUC_PURE
static inline struct config_param *
static inline const struct config_param *
config_get_param(const char *name)
{
return config_get_next_param(name, NULL);
......@@ -155,17 +156,15 @@ config_get_string(const char *name, const char *default_value);
/**
* Returns an optional configuration variable which contains an
* absolute path. If there is a tilde prefix, it is expanded. Aborts
* MPD if the path is not a valid absolute path.
* absolute path. If there is a tilde prefix, it is expanded.
* Returns NULL if the value is not present. If the path could not be
* parsed, returns NULL and sets the error.
*
* The return value must be freed with g_free().
*/
/* We lie here really. This function is not pure as it has side
effects -- it parse the value and creates new string freeing
previous one. However, because this works the very same way each
time (ie. from the outside it appears as if function had no side
effects) we should be in the clear declaring it pure. */
G_GNUC_PURE
const char *
config_get_path(const char *name);
G_GNUC_MALLOC
char *
config_dup_path(const char *name, GError **error_r);
G_GNUC_PURE
unsigned
......@@ -176,7 +175,7 @@ unsigned
config_get_positive(const char *name, unsigned default_value);
G_GNUC_PURE
struct block_param *
const struct block_param *
config_get_block_param(const struct config_param *param, const char *name);
G_GNUC_PURE
......@@ -187,6 +186,7 @@ const char *
config_get_block_string(const struct config_param *param, const char *name,
const char *default_value);
G_GNUC_MALLOC
static inline char *
config_dup_block_string(const struct config_param *param, const char *name,
const char *default_value)
......@@ -194,6 +194,15 @@ config_dup_block_string(const struct config_param *param, const char *name,
return g_strdup(config_get_block_string(param, name, default_value));
}
/**
* Same as config_dup_path(), but looks up the setting in the
* specified block.
*/
G_GNUC_MALLOC
char *
config_dup_block_path(const struct config_param *param, const char *name,
GError **error_r);
G_GNUC_PURE
unsigned
config_get_block_unsigned(const struct config_param *param, const char *name,
......@@ -204,11 +213,15 @@ bool
config_get_block_bool(const struct config_param *param, const char *name,
bool default_value);
G_GNUC_MALLOC
struct config_param *
config_new_param(const char *value, int line);
bool
void
config_param_free(struct config_param *param);
void
config_add_block_param(struct config_param * param, const char *name,
const char *value, int line, GError **error_r);
const char *value, int line);
#endif
......@@ -20,52 +20,61 @@
#ifndef MPD_DATABASE_H
#define MPD_DATABASE_H
#include "gcc.h"
#include <glib.h>
#include <sys/time.h>
#include <stdbool.h>
struct config_param;
struct directory;
struct db_selection;
struct db_visitor;
/**
* Initialize the database library.
*
* @param path the absolute path of the database file
*/
void
db_init(const char *path);
bool
db_init(const struct config_param *path, GError **error_r);
void
db_finish(void);
/**
* Clear the database.
*/
void
db_clear(void);
/**
* Returns the root directory object. Returns NULL if there is no
* configured music directory.
*/
struct directory *
db_get_root(void);
gcc_nonnull(1)
struct directory *
db_get_directory(const char *name);
gcc_nonnull(1)
struct song *
db_get_song(const char *file);
int db_walk(const char *name,
int (*forEachSong)(struct song *, void *),
int (*forEachDir)(struct directory *, void *), void *data);
gcc_nonnull(1,2)
bool
db_visit(const struct db_selection *selection,
const struct db_visitor *visitor, void *ctx,
GError **error_r);
gcc_nonnull(1,2)
bool
db_walk(const char *uri,
const struct db_visitor *visitor, void *ctx,
GError **error_r);
bool
db_check(void);
db_check(GError **error_r);
bool
db_save(void);
db_save(GError **error_r);
bool
db_load(GError **error);
......
/*
* Copyright (C) 2003-2011 The Music Player Daemon Project
* 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.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
#include "config.h"
#include "simple_db_plugin.h"
#include "db_internal.h"
#include "db_error.h"
#include "db_selection.h"
#include "db_visitor.h"
#include "db_save.h"
#include "conf.h"
#include "glib_compat.h"
#include "directory.h"
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <errno.h>
struct simple_db {
struct db base;
char *path;
struct directory *root;
time_t mtime;
};
G_GNUC_CONST
static inline GQuark
simple_db_quark(void)
{
return g_quark_from_static_string("simple_db");
}
G_GNUC_PURE
static const struct directory *
simple_db_lookup_directory(const struct simple_db *db, const char *uri)
{
assert(db != NULL);
assert(db->root != NULL);
assert(uri != NULL);
return directory_lookup_directory(db->root, uri);
}
static struct db *
simple_db_init(const struct config_param *param, GError **error_r)
{
struct simple_db *db = g_malloc(sizeof(*db));
db_base_init(&db->base, &simple_db_plugin);
GError *error = NULL;
db->path = config_dup_block_path(param, "path", error_r);
if (db->path == NULL) {
g_free(db);
if (error != NULL)
g_propagate_error(error_r, error);
else
g_set_error(error_r, simple_db_quark(), 0,
"No \"path\" parameter specified");
return NULL;
}
return &db->base;
}
static void
simple_db_finish(struct db *_db)
{
struct simple_db *db = (struct simple_db *)_db;
g_free(db->path);
g_free(db);
}
static bool
simple_db_check(struct simple_db *db, GError **error_r)
{
assert(db != NULL);
assert(db->path != NULL);
/* Check if the file exists */
if (access(db->path, 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 = g_path_get_dirname(db->path);
/* Check that the parent part of the path is a directory */
struct stat st;
if (stat(dirPath, &st) < 0) {
g_free(dirPath);
g_set_error(error_r, simple_db_quark(), errno,
"Couldn't stat parent directory of db file "
"\"%s\": %s",
db->path, g_strerror(errno));
return false;
}
if (!S_ISDIR(st.st_mode)) {
g_free(dirPath);
g_set_error(error_r, simple_db_quark(), 0,
"Couldn't create db file \"%s\" because the "
"parent path is not a directory",
db->path);
return false;
}
/* Check if we can write to the directory */
if (access(dirPath, X_OK | W_OK)) {
g_set_error(error_r, simple_db_quark(), errno,
"Can't create db file in \"%s\": %s",
dirPath, g_strerror(errno));
g_free(dirPath);
return false;
}
g_free(dirPath);
return true;
}
/* Path exists, now check if it's a regular file */
struct stat st;
if (stat(db->path, &st) < 0) {
g_set_error(error_r, simple_db_quark(), errno,
"Couldn't stat db file \"%s\": %s",
db->path, g_strerror(errno));
return false;
}
if (!S_ISREG(st.st_mode)) {
g_set_error(error_r, simple_db_quark(), 0,
"db file \"%s\" is not a regular file",
db->path);
return false;
}
/* And check that we can write to it */
if (access(db->path, R_OK | W_OK)) {
g_set_error(error_r, simple_db_quark(), errno,
"Can't open db file \"%s\" for reading/writing: %s",
db->path, g_strerror(errno));
return false;
}
return true;
}
static bool
simple_db_load(struct simple_db *db, GError **error_r)
{
assert(db != NULL);
assert(db->path != NULL);
assert(db->root != NULL);
FILE *fp = fopen(db->path, "r");
if (fp == NULL) {
g_set_error(error_r, simple_db_quark(), errno,
"Failed to open database file \"%s\": %s",
db->path, g_strerror(errno));
return false;
}
if (!db_load_internal(fp, db->root, error_r)) {
fclose(fp);
return false;
}
fclose(fp);
struct stat st;
if (stat(db->path, &st) == 0)
db->mtime = st.st_mtime;
return true;
}
static bool
simple_db_open(struct db *_db, G_GNUC_UNUSED GError **error_r)
{
struct simple_db *db = (struct simple_db *)_db;
db->root = directory_new("", NULL);
db->mtime = 0;
GError *error = NULL;
if (!simple_db_load(db, &error)) {
directory_free(db->root);
g_warning("Failed to load database: %s", error->message);
g_error_free(error);
if (!simple_db_check(db, error_r))
return false;
db->root = directory_new("", NULL);
}
return true;
}
static void
simple_db_close(struct db *_db)
{
struct simple_db *db = (struct simple_db *)_db;
assert(db->root != NULL);
directory_free(db->root);
}
static struct song *
simple_db_get_song(struct db *_db, const char *uri, GError **error_r)
{
struct simple_db *db = (struct simple_db *)_db;
assert(db->root != NULL);
struct song *song = directory_lookup_song(db->root, uri);
if (song == NULL)
g_set_error(error_r, db_quark(), DB_NOT_FOUND,
"No such song: %s", uri);
return song;
}
static bool
simple_db_visit(struct db *_db, const struct db_selection *selection,
const struct db_visitor *visitor, void *ctx,
GError **error_r)
{
const struct simple_db *db = (const struct simple_db *)_db;
const struct directory *directory =
simple_db_lookup_directory(db, selection->uri);
if (directory == NULL) {
struct song *song;
if (visitor->song != NULL &&
(song = simple_db_get_song(_db, selection->uri, NULL)) != NULL)
return visitor->song(song, ctx, error_r);
g_set_error(error_r, db_quark(), DB_NOT_FOUND,
"No such directory");
return false;
}
if (selection->recursive && visitor->directory != NULL &&
!visitor->directory(directory, ctx, error_r))
return false;
return directory_walk(directory, selection->recursive,
visitor, ctx, error_r);
}
const struct db_plugin simple_db_plugin = {
.name = "simple",
.init = simple_db_init,
.finish = simple_db_finish,
.open = simple_db_open,
.close = simple_db_close,
.get_song = simple_db_get_song,
.visit = simple_db_visit,
};
struct directory *
simple_db_get_root(struct db *_db)
{
struct simple_db *db = (struct simple_db *)_db;
assert(db != NULL);
assert(db->root != NULL);
return db->root;
}
bool
simple_db_save(struct db *_db, GError **error_r)
{
struct simple_db *db = (struct simple_db *)_db;
struct directory *music_root = db->root;
g_debug("removing empty directories from DB");
directory_prune_empty(music_root);
g_debug("sorting DB");
directory_sort(music_root);
g_debug("writing DB");
FILE *fp = fopen(db->path, "w");
if (!fp) {
g_set_error(error_r, simple_db_quark(), errno,
"unable to write to db file \"%s\": %s",
db->path, g_strerror(errno));
return false;
}
db_save_internal(fp, music_root);
if (ferror(fp)) {
g_set_error(error_r, simple_db_quark(), errno,
"Failed to write to database file: %s",
g_strerror(errno));
fclose(fp);
return false;
}
fclose(fp);
struct stat st;
if (stat(db->path, &st) == 0)
db->mtime = st.st_mtime;
return true;
}
time_t
simple_db_get_mtime(const struct db *_db)
{
const struct simple_db *db = (const struct simple_db *)_db;
assert(db != NULL);
assert(db->root != NULL);
return db->mtime;
}
/*
* Copyright (C) 2003-2011 The Music Player Daemon Project
* 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.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
#ifndef MPD_SIMPLE_DB_PLUGIN_H
#define MPD_SIMPLE_DB_PLUGIN_H
#include <glib.h>
#include <stdbool.h>
#include <time.h>
extern const struct db_plugin simple_db_plugin;
struct db;
G_GNUC_PURE
struct directory *
simple_db_get_root(struct db *db);
bool
simple_db_save(struct db *db, GError **error_r);
G_GNUC_PURE
time_t
simple_db_get_mtime(const struct db *db);
#endif
......@@ -20,317 +20,105 @@
#include "config.h"
#include "dbUtils.h"
#include "locate.h"
#include "directory.h"
#include "database.h"
#include "client.h"
#include "db_visitor.h"
#include "playlist.h"
#include "song.h"
#include "song_print.h"
#include "tag.h"
#include "strset.h"
#include "stored_playlist.h"
#include "client_internal.h"
#include <glib.h>
#include <stdlib.h>
typedef struct _ListCommandItem {
int8_t tagType;
const struct locate_item_list *criteria;
} ListCommandItem;
typedef struct _SearchStats {
const struct locate_item_list *criteria;
int numberOfSongs;
unsigned long playTime;
} SearchStats;
static int
printDirectoryInDirectory(struct directory *directory, void *data)
{
struct client *client = data;
if (!directory_is_root(directory))
client_printf(client, "directory: %s\n", directory_get_path(directory));
return 0;
}
static int
printSongInDirectory(struct song *song, G_GNUC_UNUSED void *data)
{
struct client *client = data;
song_print_uri(client, song);
return 0;
}
struct search_data {
struct client *client;
const struct locate_item_list *criteria;
};
static int
searchInDirectory(struct song *song, void *_data)
{
struct search_data *data = _data;
if (locate_song_search(song, data->criteria))
song_print_info(data->client, song);
return 0;
}
int
searchForSongsIn(struct client *client, const char *name,
const struct locate_item_list *criteria)
{
int ret;
struct locate_item_list *new_list
= locate_item_list_casefold(criteria);
struct search_data data;
data.client = client;
data.criteria = new_list;
ret = db_walk(name, searchInDirectory, NULL, &data);
locate_item_list_free(new_list);
return ret;
}
static int
findInDirectory(struct song *song, void *_data)
static bool
add_to_queue_song(struct song *song, void *ctx, GError **error_r)
{
struct search_data *data = _data;
if (locate_song_match(song, data->criteria))
song_print_info(data->client, song);
struct player_control *pc = ctx;
return 0;
}
int
findSongsIn(struct client *client, const char *name,
const struct locate_item_list *criteria)
{
struct search_data data;
data.client = client;
data.criteria = criteria;
return db_walk(name, findInDirectory, NULL, &data);
}
static void printSearchStats(struct client *client, SearchStats *stats)
{
client_printf(client, "songs: %i\n", stats->numberOfSongs);
client_printf(client, "playtime: %li\n", stats->playTime);
}
static int
searchStatsInDirectory(struct song *song, void *data)
{
SearchStats *stats = data;
if (locate_song_match(song, stats->criteria)) {
stats->numberOfSongs++;
stats->playTime += song_get_duration(song);
enum playlist_result result =
playlist_append_song(&g_playlist, pc, song, NULL);
if (result != PLAYLIST_RESULT_SUCCESS) {
g_set_error(error_r, playlist_quark(), result,
"Playlist error");
return false;
}
return 0;
}
int
searchStatsForSongsIn(struct client *client, const char *name,
const struct locate_item_list *criteria)
{
SearchStats stats;
int ret;
stats.criteria = criteria;
stats.numberOfSongs = 0;
stats.playTime = 0;
ret = db_walk(name, searchStatsInDirectory, NULL, &stats);
if (ret == 0)
printSearchStats(client, &stats);
return ret;
return true;
}
int printAllIn(struct client *client, const char *name)
{
return db_walk(name, printSongInDirectory,
printDirectoryInDirectory, client);
}
static const struct db_visitor add_to_queue_visitor = {
.song = add_to_queue_song,
};
static int
directoryAddSongToPlaylist(struct song *song, void *data)
bool
addAllIn(struct player_control *pc, const char *uri, GError **error_r)
{
struct player_control *pc = data;
return playlist_append_song(&g_playlist, pc, song, NULL);
return db_walk(uri, &add_to_queue_visitor, pc, error_r);
}
struct add_data {
const char *path;
};
static int
directoryAddSongToStoredPlaylist(struct song *song, void *_data)
static bool
add_to_spl_song(struct song *song, void *ctx, GError **error_r)
{
struct add_data *data = _data;
struct add_data *data = ctx;
if (spl_append_song(data->path, song) != 0)
return -1;
return 0;
}
if (!spl_append_song(data->path, song, error_r))
return false;
int
addAllIn(struct player_control *pc, const char *name)
{
return db_walk(name, directoryAddSongToPlaylist, NULL, pc);
return true;
}
int addAllInToStoredPlaylist(const char *name, const char *utf8file)
static const struct db_visitor add_to_spl_visitor = {
.song = add_to_spl_song,
};
bool
addAllInToStoredPlaylist(const char *uri_utf8, const char *path_utf8,
GError **error_r)
{
struct add_data data = {
.path = utf8file,
.path = path_utf8,
};
return db_walk(name, directoryAddSongToStoredPlaylist, NULL, &data);
}
static int
findAddInDirectory(struct song *song, void *_data)
{
struct search_data *data = _data;
if (locate_song_match(song, data->criteria))
return playlist_append_song(&g_playlist,
data->client->player_control,
song, NULL);
return 0;
}
int findAddIn(struct client *client, const char *name,
const struct locate_item_list *criteria)
{
struct search_data data;
data.client = client;
data.criteria = criteria;
return db_walk(name, findAddInDirectory, NULL, &data);
}
static int
directoryPrintSongInfo(struct song *song, void *data)
{
struct client *client = data;
song_print_info(client, song);
return 0;
}
int printInfoForAllIn(struct client *client, const char *name)
{
return db_walk(name, directoryPrintSongInfo,
printDirectoryInDirectory, client);
}
static ListCommandItem *
newListCommandItem(int tagType, const struct locate_item_list *criteria)
{
ListCommandItem *item = g_new(ListCommandItem, 1);
item->tagType = tagType;
item->criteria = criteria;
return item;
return db_walk(uri_utf8, &add_to_spl_visitor, &data, error_r);
}
static void freeListCommandItem(ListCommandItem * item)
{
g_free(item);
}
struct find_add_data {
struct player_control *pc;
const struct locate_item_list *criteria;
};
static void
visitTag(struct client *client, struct strset *set,
struct song *song, enum tag_type tagType)
static bool
find_add_song(struct song *song, void *ctx, GError **error_r)
{
struct tag *tag = song->tag;
bool found = false;
struct find_add_data *data = ctx;
if (tagType == LOCATE_TAG_FILE_TYPE) {
song_print_uri(client, song);
return;
}
if (!tag)
return;
if (!locate_song_match(song, data->criteria))
return true;
for (unsigned i = 0; i < tag->num_items; i++) {
if (tag->items[i]->type == tagType) {
strset_add(set, tag->items[i]->value);
found = true;
}
enum playlist_result result =
playlist_append_song(&g_playlist, data->pc,
song, NULL);
if (result != PLAYLIST_RESULT_SUCCESS) {
g_set_error(error_r, playlist_quark(), result,
"Playlist error");
return false;
}
if (!found)
strset_add(set, "");
return true;
}
struct list_tags_data {
struct client *client;
ListCommandItem *item;
struct strset *set;
static const struct db_visitor find_add_visitor = {
.song = find_add_song,
};
static int
listUniqueTagsInDirectory(struct song *song, void *_data)
bool
findAddIn(struct player_control *pc, const char *name,
const struct locate_item_list *criteria, GError **error_r)
{
struct list_tags_data *data = _data;
ListCommandItem *item = data->item;
if (locate_song_match(song, item->criteria))
visitTag(data->client, data->set, song, item->tagType);
return 0;
}
int listAllUniqueTags(struct client *client, int type,
const struct locate_item_list *criteria)
{
int ret;
ListCommandItem *item = newListCommandItem(type, criteria);
struct list_tags_data data = {
.client = client,
.item = item,
};
if (type >= 0 && type <= TAG_NUM_OF_ITEM_TYPES) {
data.set = strset_new();
}
ret = db_walk(NULL, listUniqueTagsInDirectory, NULL, &data);
if (type >= 0 && type <= TAG_NUM_OF_ITEM_TYPES) {
const char *value;
strset_rewind(data.set);
while ((value = strset_next(data.set)) != NULL)
client_printf(client, "%s: %s\n",
tag_item_names[type],
value);
strset_free(data.set);
}
freeListCommandItem(item);
struct find_add_data data;
data.pc = pc;
data.criteria = criteria;
return ret;
return db_walk(name, &find_add_visitor, &data, error_r);
}
......@@ -20,39 +20,26 @@
#ifndef MPD_DB_UTILS_H
#define MPD_DB_UTILS_H
struct client;
struct locate_item_list;
struct player_control;
int printAllIn(struct client *client, const char *name);
int
addAllIn(struct player_control *pc, const char *name);
int addAllInToStoredPlaylist(const char *name, const char *utf8file);
#include "gcc.h"
int printInfoForAllIn(struct client *client, const char *name);
#include <glib.h>
#include <stdbool.h>
int
searchForSongsIn(struct client *client, const char *name,
const struct locate_item_list *criteria);
int
findSongsIn(struct client *client, const char *name,
const struct locate_item_list *criteria);
int
findAddIn(struct client *client, const char *name,
const struct locate_item_list *criteria);
struct locate_item_list;
struct player_control;
int
searchStatsForSongsIn(struct client *client, const char *name,
const struct locate_item_list *criteria);
gcc_nonnull(1,2)
bool
addAllIn(struct player_control *pc, const char *uri, GError **error_r);
unsigned long sumSongTimesIn(const char *name);
gcc_nonnull(1,2)
bool
addAllInToStoredPlaylist(const char *uri_utf8, const char *path_utf8,
GError **error_r);
int
listAllUniqueTags(struct client *client, int type,
const struct locate_item_list *criteria);
gcc_nonnull(1,2,3)
bool
findAddIn(struct player_control *pc, const char *name,
const struct locate_item_list *criteria, GError **error_r);
#endif
/*
* Copyright (C) 2003-2011 The Music Player Daemon Project
* 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.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
#ifndef MPD_DB_ERROR_H
#define MPD_DB_ERROR_H
#include <glib.h>
enum db_error {
/**
* The database is disabled, i.e. none is configured in this
* MPD instance.
*/
DB_DISABLED,
DB_NOT_FOUND,
};
/**
* Quark for GError.domain; the code is an enum #db_error.
*/
G_GNUC_CONST
static inline GQuark
db_quark(void)
{
return g_quark_from_static_string("db");
}
#endif
/*
* Copyright (C) 2003-2011 The Music Player Daemon Project
* 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.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
#ifndef MPD_DB_INTERNAL_H
#define MPD_DB_INTERNAL_H
#include "db_plugin.h"
#include <assert.h>
static inline void
db_base_init(struct db *db, const struct db_plugin *plugin)
{
assert(plugin != NULL);
db->plugin = plugin;
}
#endif
/*
* Copyright (C) 2003-2011 The Music Player Daemon Project
* 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.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
/** \file
*
* This header declares the db_plugin class. It describes a
* plugin API for databases of song metadata.
*/
#ifndef MPD_DB_PLUGIN_H
#define MPD_DB_PLUGIN_H
#include <glib.h>
#include <assert.h>
#include <stdbool.h>
struct config_param;
struct db_selection;
struct db_visitor;
struct db {
const struct db_plugin *plugin;
};
struct db_plugin {
const char *name;
/**
* Allocates and configures a database.
*/
struct db *(*init)(const struct config_param *param, GError **error_r);
/**
* Free instance data.
*/
void (*finish)(struct db *db);
/**
* Open the database. Read it into memory if applicable.
*/
bool (*open)(struct db *db, GError **error_r);
/**
* Close the database, free allocated memory.
*/
void (*close)(struct db *db);
/**
* Look up a song (including tag data) in the database.
*
* @param the URI of the song within the music directory
* (UTF-8)
*/
struct song *(*get_song)(struct db *db, const char *uri,
GError **error_r);
/**
* Visit the selected entities.
*/
bool (*visit)(struct db *db, const struct db_selection *selection,
const struct db_visitor *visitor, void *ctx,
GError **error_r);
};
G_GNUC_MALLOC
static inline struct db *
db_plugin_new(const struct db_plugin *plugin, const struct config_param *param,
GError **error_r)
{
assert(plugin != NULL);
assert(plugin->init != NULL);
assert(plugin->finish != NULL);
assert(plugin->get_song != NULL);
assert(plugin->visit != NULL);
assert(error_r == NULL || *error_r == NULL);
struct db *db = plugin->init(param, error_r);
assert(db == NULL || db->plugin == plugin);
assert(db != NULL || error_r == NULL || *error_r != NULL);
return db;
}
static inline void
db_plugin_free(struct db *db)
{
assert(db != NULL);
assert(db->plugin != NULL);
assert(db->plugin->finish != NULL);
db->plugin->finish(db);
}
static inline bool
db_plugin_open(struct db *db, GError **error_r)
{
assert(db != NULL);
assert(db->plugin != NULL);
return db->plugin->open != NULL
? db->plugin->open(db, error_r)
: true;
}
static inline void
db_plugin_close(struct db *db)
{
assert(db != NULL);
assert(db->plugin != NULL);
if (db->plugin->close != NULL)
db->plugin->close(db);
}
static inline struct song *
db_plugin_get_song(struct db *db, const char *uri, GError **error_r)
{
assert(db != NULL);
assert(db->plugin != NULL);
assert(db->plugin->get_song != NULL);
assert(uri != NULL);
return db->plugin->get_song(db, uri, error_r);
}
static inline bool
db_plugin_visit(struct db *db, const struct db_selection *selection,
const struct db_visitor *visitor, void *ctx,
GError **error_r)
{
assert(db != NULL);
assert(db->plugin != NULL);
assert(selection != NULL);
assert(visitor != NULL);
assert(error_r == NULL || *error_r == NULL);
return db->plugin->visit(db, selection, visitor, ctx, error_r);
}
#endif
/*
* Copyright (C) 2003-2011 The Music Player Daemon Project
* 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.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
#include "config.h"
#include "db_print.h"
#include "db_selection.h"
#include "db_visitor.h"
#include "locate.h"
#include "directory.h"
#include "database.h"
#include "client.h"
#include "song.h"
#include "song_print.h"
#include "tag.h"
#include "strset.h"
#include <glib.h>
typedef struct _ListCommandItem {
int8_t tagType;
const struct locate_item_list *criteria;
} ListCommandItem;
typedef struct _SearchStats {
const struct locate_item_list *criteria;
int numberOfSongs;
unsigned long playTime;
} SearchStats;
static bool
print_visitor_directory(const struct directory *directory, void *data,
G_GNUC_UNUSED GError **error_r)
{
struct client *client = data;
if (!directory_is_root(directory))
client_printf(client, "directory: %s\n", directory_get_path(directory));
return true;
}
static bool
print_visitor_song(struct song *song, void *data,
G_GNUC_UNUSED GError **error_r)
{
struct client *client = data;
song_print_uri(client, song);
return true;
}
static bool
print_visitor_song_info(struct song *song, void *data,
G_GNUC_UNUSED GError **error_r)
{
struct client *client = data;
song_print_info(client, song);
return true;
}
static bool
print_visitor_playlist(const struct playlist_metadata *playlist, void *ctx,
G_GNUC_UNUSED GError **error_r)
{
struct client *client = ctx;
client_printf(client, "playlist: %s\n", playlist->name);
return true;
}
static bool
print_visitor_playlist_info(const struct playlist_metadata *playlist,
void *ctx, G_GNUC_UNUSED GError **error_r)
{
struct client *client = ctx;
client_printf(client, "playlist: %s\n", playlist->name);
#ifndef G_OS_WIN32
struct tm tm;
#endif
char timestamp[32];
time_t t = playlist->mtime;
strftime(timestamp, sizeof(timestamp),
#ifdef G_OS_WIN32
"%Y-%m-%dT%H:%M:%SZ",
gmtime(&t)
#else
"%FT%TZ",
gmtime_r(&t, &tm)
#endif
);
client_printf(client, "Last-Modified: %s\n", timestamp);
return true;
}
static const struct db_visitor print_visitor = {
.directory = print_visitor_directory,
.song = print_visitor_song,
.playlist = print_visitor_playlist,
};
static const struct db_visitor print_info_visitor = {
.directory = print_visitor_directory,
.song = print_visitor_song_info,
.playlist = print_visitor_playlist_info,
};
bool
db_selection_print(struct client *client, const struct db_selection *selection,
bool full, GError **error_r)
{
return db_visit(selection, full ? &print_info_visitor : &print_visitor,
client, error_r);
}
struct search_data {
struct client *client;
const struct locate_item_list *criteria;
};
static bool
search_visitor_song(struct song *song, void *_data,
G_GNUC_UNUSED GError **error_r)
{
struct search_data *data = _data;
if (locate_song_search(song, data->criteria))
song_print_info(data->client, song);
return true;
}
static const struct db_visitor search_visitor = {
.song = search_visitor_song,
};
bool
searchForSongsIn(struct client *client, const char *name,
const struct locate_item_list *criteria,
GError **error_r)
{
struct locate_item_list *new_list
= locate_item_list_casefold(criteria);
struct search_data data;
data.client = client;
data.criteria = new_list;
bool success = db_walk(name, &search_visitor, &data, error_r);
locate_item_list_free(new_list);
return success;
}
static bool
find_visitor_song(struct song *song, void *_data,
G_GNUC_UNUSED GError **error_r)
{
struct search_data *data = _data;
if (locate_song_match(song, data->criteria))
song_print_info(data->client, song);
return true;
}
static const struct db_visitor find_visitor = {
.song = find_visitor_song,
};
bool
findSongsIn(struct client *client, const char *name,
const struct locate_item_list *criteria,
GError **error_r)
{
struct search_data data;
data.client = client;
data.criteria = criteria;
return db_walk(name, &find_visitor, &data, error_r);
}
static void printSearchStats(struct client *client, SearchStats *stats)
{
client_printf(client, "songs: %i\n", stats->numberOfSongs);
client_printf(client, "playtime: %li\n", stats->playTime);
}
static bool
stats_visitor_song(struct song *song, void *data,
G_GNUC_UNUSED GError **error_r)
{
SearchStats *stats = data;
if (locate_song_match(song, stats->criteria)) {
stats->numberOfSongs++;
stats->playTime += song_get_duration(song);
}
return true;
}
static const struct db_visitor stats_visitor = {
.song = stats_visitor_song,
};
bool
searchStatsForSongsIn(struct client *client, const char *name,
const struct locate_item_list *criteria,
GError **error_r)
{
SearchStats stats;
stats.criteria = criteria;
stats.numberOfSongs = 0;
stats.playTime = 0;
if (!db_walk(name, &stats_visitor, &stats, error_r))
return false;
printSearchStats(client, &stats);
return true;
}
bool
printAllIn(struct client *client, const char *uri_utf8, GError **error_r)
{
struct db_selection selection;
db_selection_init(&selection, uri_utf8, true);
return db_selection_print(client, &selection, false, error_r);
}
bool
printInfoForAllIn(struct client *client, const char *uri_utf8,
GError **error_r)
{
struct db_selection selection;
db_selection_init(&selection, uri_utf8, true);
return db_selection_print(client, &selection, true, error_r);
}
static ListCommandItem *
newListCommandItem(int tagType, const struct locate_item_list *criteria)
{
ListCommandItem *item = g_new(ListCommandItem, 1);
item->tagType = tagType;
item->criteria = criteria;
return item;
}
static void freeListCommandItem(ListCommandItem * item)
{
g_free(item);
}
static void
visitTag(struct client *client, struct strset *set,
struct song *song, enum tag_type tagType)
{
struct tag *tag = song->tag;
bool found = false;
if (tagType == LOCATE_TAG_FILE_TYPE) {
song_print_uri(client, song);
return;
}
if (!tag)
return;
for (unsigned i = 0; i < tag->num_items; i++) {
if (tag->items[i]->type == tagType) {
strset_add(set, tag->items[i]->value);
found = true;
}
}
if (!found)
strset_add(set, "");
}
struct list_tags_data {
struct client *client;
ListCommandItem *item;
struct strset *set;
};
static bool
unique_tags_visitor_song(struct song *song, void *_data,
G_GNUC_UNUSED GError **error_r)
{
struct list_tags_data *data = _data;
ListCommandItem *item = data->item;
if (locate_song_match(song, item->criteria))
visitTag(data->client, data->set, song, item->tagType);
return true;
}
static const struct db_visitor unique_tags_visitor = {
.song = unique_tags_visitor_song,
};
bool
listAllUniqueTags(struct client *client, int type,
const struct locate_item_list *criteria,
GError **error_r)
{
ListCommandItem *item = newListCommandItem(type, criteria);
struct list_tags_data data = {
.client = client,
.item = item,
};
if (type >= 0 && type <= TAG_NUM_OF_ITEM_TYPES) {
data.set = strset_new();
}
if (!db_walk("", &unique_tags_visitor, &data, error_r)) {
freeListCommandItem(item);
return false;
}
if (type >= 0 && type <= TAG_NUM_OF_ITEM_TYPES) {
const char *value;
strset_rewind(data.set);
while ((value = strset_next(data.set)) != NULL)
client_printf(client, "%s: %s\n",
tag_item_names[type],
value);
strset_free(data.set);
}
freeListCommandItem(item);
return true;
}
/*
* Copyright (C) 2003-2011 The Music Player Daemon Project
* 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.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
#ifndef MPD_DB_PRINT_H
#define MPD_DB_PRINT_H
#include "gcc.h"
#include <glib.h>
#include <stdbool.h>
struct client;
struct locate_item_list;
struct db_selection;
struct db_visitor;
gcc_nonnull(1,2)
bool
db_selection_print(struct client *client, const struct db_selection *selection,
bool full, GError **error_r);
gcc_nonnull(1,2)
bool
printAllIn(struct client *client, const char *uri_utf8, GError **error_r);
gcc_nonnull(1,2)
bool
printInfoForAllIn(struct client *client, const char *uri_utf8,
GError **error_r);
gcc_nonnull(1,2,3)
bool
searchForSongsIn(struct client *client, const char *name,
const struct locate_item_list *criteria,
GError **error_r);
gcc_nonnull(1,2,3)
bool
findSongsIn(struct client *client, const char *name,
const struct locate_item_list *criteria,
GError **error_r);
gcc_nonnull(1,2,3)
bool
searchStatsForSongsIn(struct client *client, const char *name,
const struct locate_item_list *criteria,
GError **error_r);
gcc_nonnull(1,3)
bool
listAllUniqueTags(struct client *client, int type,
const struct locate_item_list *criteria,
GError **error_r);
#endif
/*
* Copyright (C) 2003-2011 The Music Player Daemon Project
* 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.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
#include "config.h"
#include "db_save.h"
#include "directory.h"
#include "directory_save.h"
#include "song.h"
#include "path.h"
#include "text_file.h"
#include "tag.h"
#include "tag_internal.h"
#include <glib.h>
#include <assert.h>
#include <stdlib.h>
#undef G_LOG_DOMAIN
#define G_LOG_DOMAIN "database"
#define DIRECTORY_INFO_BEGIN "info_begin"
#define DIRECTORY_INFO_END "info_end"
#define DB_FORMAT_PREFIX "format: "
#define DIRECTORY_MPD_VERSION "mpd_version: "
#define DIRECTORY_FS_CHARSET "fs_charset: "
#define DB_TAG_PREFIX "tag: "
enum {
DB_FORMAT = 1,
};
G_GNUC_CONST
static GQuark
db_quark(void)
{
return g_quark_from_static_string("database");
}
void
db_save_internal(FILE *fp, const struct directory *music_root)
{
assert(music_root != NULL);
fprintf(fp, "%s\n", DIRECTORY_INFO_BEGIN);
fprintf(fp, DB_FORMAT_PREFIX "%u\n", DB_FORMAT);
fprintf(fp, "%s%s\n", DIRECTORY_MPD_VERSION, VERSION);
fprintf(fp, "%s%s\n", DIRECTORY_FS_CHARSET, path_get_fs_charset());
for (unsigned i = 0; i < TAG_NUM_OF_ITEM_TYPES; ++i)
if (!ignore_tag_items[i])
fprintf(fp, DB_TAG_PREFIX "%s\n", tag_item_names[i]);
fprintf(fp, "%s\n", DIRECTORY_INFO_END);
directory_save(fp, music_root);
}
bool
db_load_internal(FILE *fp, struct directory *music_root, GError **error)
{
GString *buffer = g_string_sized_new(1024);
char *line;
int format = 0;
bool found_charset = false, found_version = false;
bool success;
bool tags[TAG_NUM_OF_ITEM_TYPES];
assert(music_root != NULL);
/* get initial info */
line = read_text_line(fp, buffer);
if (line == NULL || strcmp(DIRECTORY_INFO_BEGIN, line) != 0) {
g_set_error(error, db_quark(), 0, "Database corrupted");
g_string_free(buffer, true);
return false;
}
memset(tags, false, sizeof(tags));
while ((line = read_text_line(fp, buffer)) != NULL &&
strcmp(line, DIRECTORY_INFO_END) != 0) {
if (g_str_has_prefix(line, DB_FORMAT_PREFIX)) {
format = atoi(line + sizeof(DB_FORMAT_PREFIX) - 1);
} else if (g_str_has_prefix(line, DIRECTORY_MPD_VERSION)) {
if (found_version) {
g_set_error(error, db_quark(), 0,
"Duplicate version line");
g_string_free(buffer, true);
return false;
}
found_version = true;
} else if (g_str_has_prefix(line, DIRECTORY_FS_CHARSET)) {
const char *new_charset, *old_charset;
if (found_charset) {
g_set_error(error, db_quark(), 0,
"Duplicate charset line");
g_string_free(buffer, true);
return false;
}
found_charset = true;
new_charset = line + sizeof(DIRECTORY_FS_CHARSET) - 1;
old_charset = path_get_fs_charset();
if (old_charset != NULL
&& strcmp(new_charset, old_charset)) {
g_set_error(error, db_quark(), 0,
"Existing database has charset "
"\"%s\" instead of \"%s\"; "
"discarding database file",
new_charset, old_charset);
g_string_free(buffer, true);
return false;
}
} else if (g_str_has_prefix(line, DB_TAG_PREFIX)) {
const char *name = line + sizeof(DB_TAG_PREFIX) - 1;
enum tag_type tag = tag_name_parse(name);
if (tag == TAG_NUM_OF_ITEM_TYPES) {
g_set_error(error, db_quark(), 0,
"Unrecognized tag '%s', "
"discarding database file",
name);
return false;
}
tags[tag] = true;
} else {
g_set_error(error, db_quark(), 0,
"Malformed line: %s", line);
g_string_free(buffer, true);
return false;
}
}
if (format != DB_FORMAT) {
g_set_error(error, db_quark(), 0,
"Database format mismatch, "
"discarding database file");
return false;
}
for (unsigned i = 0; i < TAG_NUM_OF_ITEM_TYPES; ++i) {
if (!ignore_tag_items[i] && !tags[i]) {
g_set_error(error, db_quark(), 0,
"Tag list mismatch, "
"discarding database file");
return false;
}
}
g_debug("reading DB");
success = directory_load(fp, music_root, buffer, error);
g_string_free(buffer, true);
return success;
}
/*
* Copyright (C) 2003-2011 The Music Player Daemon Project
* 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.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
#ifndef MPD_DB_SAVE_H
#define MPD_DB_SAVE_H
#include <glib.h>
#include <stdbool.h>
#include <stdio.h>
struct directory;
void
db_save_internal(FILE *file, const struct directory *root);
bool
db_load_internal(FILE *file, struct directory *root, GError **error);
#endif
/*
* Copyright (C) 2003-2011 The Music Player Daemon Project
* 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.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
#ifndef MPD_DB_SELECTION_H
#define MPD_DB_SELECTION_H
#include "gcc.h"
#include <assert.h>
struct directory;
struct song;
struct db_selection {
/**
* The base URI of the search (UTF-8). Must not begin or end
* with a slash. NULL or an empty string searches the whole
* database.
*/
const char *uri;
/**
* Recursively search all sub directories?
*/
bool recursive;
};
gcc_nonnull(1,2)
static inline void
db_selection_init(struct db_selection *selection,
const char *uri, bool recursive)
{
assert(selection != NULL);
assert(uri != NULL);
selection->uri = uri;
selection->recursive = recursive;
}
#endif
/*
* Copyright (C) 2003-2011 The Music Player Daemon Project
* 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.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
#ifndef MPD_DB_VISITOR_H
#define MPD_DB_VISITOR_H
struct directory;
struct song;
struct playlist_metadata;
struct db_visitor {
/**
* Visit a directory. Optional method.
*
* @return true to continue the operation, false on error (set error_r)
*/
bool (*directory)(const struct directory *directory, void *ctx,
GError **error_r);
/**
* Visit a song. Optional method.
*
* @return true to continue the operation, false on error (set error_r)
*/
bool (*song)(struct song *song, void *ctx, GError **error_r);
/**
* Visit a playlist. Optional method.
*
* @return true to continue the operation, false on error (set error_r)
*/
bool (*playlist)(const struct playlist_metadata *playlist, void *ctx,
GError **error_r);
};
#endif
......@@ -25,6 +25,7 @@
#include <af_vfs.h>
#include <assert.h>
#include <glib.h>
#include <stdio.h>
#undef G_LOG_DOMAIN
#define G_LOG_DOMAIN "audiofile"
......@@ -53,7 +54,7 @@ audiofile_file_read(AFvirtualfile *vfile, void *data, size_t length)
GError *error = NULL;
size_t nbytes;
nbytes = input_stream_read(is, data, length, &error);
nbytes = input_stream_lock_read(is, data, length, &error);
if (nbytes == 0 && error != NULL) {
g_warning("%s", error->message);
g_error_free(error);
......@@ -90,7 +91,7 @@ audiofile_file_seek(AFvirtualfile *vfile, long offset, int is_relative)
{
struct input_stream *is = (struct input_stream *) vfile->closure;
int whence = (is_relative ? SEEK_CUR : SEEK_SET);
if (input_stream_seek(is, offset, whence, NULL)) {
if (input_stream_lock_seek(is, offset, whence, NULL)) {
return is->offset;
} else {
return -1;
......
......@@ -205,7 +205,7 @@ faad_song_duration(struct decoder_buffer *buffer, struct input_stream *is)
/* obtain the duration from the ADTS header */
float song_length = adts_song_duration(buffer);
input_stream_seek(is, tagsize, SEEK_SET, NULL);
input_stream_lock_seek(is, tagsize, SEEK_SET, NULL);
data = decoder_buffer_read(buffer, &length);
if (data != NULL)
......@@ -406,7 +406,7 @@ faad_stream_decode(struct decoder *mpd_decoder, struct input_stream *is)
faacDecSetConfiguration(decoder, config);
while (!decoder_buffer_is_full(buffer) &&
!input_stream_eof(is) &&
!input_stream_lock_eof(is) &&
decoder_get_command(mpd_decoder) == DECODE_COMMAND_NONE) {
adts_find_frame(buffer);
decoder_buffer_fill(buffer);
......
......@@ -105,7 +105,7 @@ mpd_ffmpeg_stream_seek(void *opaque, int64_t pos, int whence)
if (whence == AVSEEK_SIZE)
return stream->input->size;
if (!input_stream_seek(stream->input, pos, whence, NULL))
if (!input_stream_lock_seek(stream->input, pos, whence, NULL))
return -1;
return stream->input->offset;
......@@ -211,6 +211,24 @@ align16(void *p, size_t *length_p)
return (char *)p + add;
}
G_GNUC_CONST
static double
time_from_ffmpeg(int64_t t, const AVRational time_base)
{
assert(t != (int64_t)AV_NOPTS_VALUE);
return (double)av_rescale_q(t, time_base, (AVRational){1, 1024})
/ (double)1024;
}
G_GNUC_CONST
static int64_t
time_to_ffmpeg(double t, const AVRational time_base)
{
return av_rescale_q((int64_t)(t * 1024), (AVRational){1, 1024},
time_base);
}
static enum decoder_command
ffmpeg_send_packet(struct decoder *decoder, struct input_stream *is,
const AVPacket *packet,
......@@ -219,8 +237,7 @@ ffmpeg_send_packet(struct decoder *decoder, struct input_stream *is,
{
if (packet->pts != (int64_t)AV_NOPTS_VALUE)
decoder_timestamp(decoder,
av_rescale_q(packet->pts, *time_base,
(AVRational){1, 1}));
time_from_ffmpeg(packet->pts, *time_base));
#if LIBAVCODEC_VERSION_INT >= AV_VERSION_INT(52,25,0)
AVPacket packet2 = *packet;
......@@ -303,7 +320,8 @@ ffmpeg_probe(struct decoder *decoder, struct input_stream *is)
unsigned char *buffer = g_malloc(BUFFER_SIZE);
size_t nbytes = decoder_read(decoder, is, buffer, BUFFER_SIZE);
if (nbytes <= PADDING || !input_stream_seek(is, 0, SEEK_SET, NULL)) {
if (nbytes <= PADDING ||
!input_stream_lock_seek(is, 0, SEEK_SET, NULL)) {
g_free(buffer);
return NULL;
}
......@@ -367,8 +385,9 @@ ffmpeg_decode(struct decoder *decoder, struct input_stream *input)
return;
}
AVCodecContext *codec_context =
format_context->streams[audio_stream]->codec;
AVStream *av_stream = format_context->streams[audio_stream];
AVCodecContext *codec_context = av_stream->codec;
if (codec_context->codec_name[0] != 0)
g_debug("codec '%s'", codec_context->codec_name);
......@@ -419,7 +438,7 @@ ffmpeg_decode(struct decoder *decoder, struct input_stream *input)
if (packet.stream_index == audio_stream)
cmd = ffmpeg_send_packet(decoder, input,
&packet, codec_context,
&format_context->streams[audio_stream]->time_base);
&av_stream->time_base);
else
cmd = decoder_get_command(decoder);
......@@ -427,12 +446,16 @@ ffmpeg_decode(struct decoder *decoder, struct input_stream *input)
if (cmd == DECODE_COMMAND_SEEK) {
int64_t where =
decoder_seek_where(decoder) * AV_TIME_BASE;
time_to_ffmpeg(decoder_seek_where(decoder),
av_stream->time_base);
if (av_seek_frame(format_context, -1, where, 0) < 0)
if (av_seek_frame(format_context, audio_stream, where,
AV_TIME_BASE) < 0)
decoder_seek_error(decoder);
else
else {
avcodec_flush_buffers(codec_context);
decoder_command_finished(decoder);
}
}
} while (cmd != DECODE_COMMAND_STOP);
......
......@@ -50,7 +50,7 @@ flac_read_cb(G_GNUC_UNUSED const FLAC__StreamDecoder *fd,
if (r == 0) {
if (decoder_get_command(data->decoder) != DECODE_COMMAND_NONE ||
input_stream_eof(data->input_stream))
input_stream_lock_eof(data->input_stream))
return FLAC__STREAM_DECODER_READ_STATUS_END_OF_STREAM;
else
return FLAC__STREAM_DECODER_READ_STATUS_ABORT;
......@@ -68,7 +68,8 @@ flac_seek_cb(G_GNUC_UNUSED const FLAC__StreamDecoder *fd,
if (!data->input_stream->seekable)
return FLAC__STREAM_DECODER_SEEK_STATUS_UNSUPPORTED;
if (!input_stream_seek(data->input_stream, offset, SEEK_SET, NULL))
if (!input_stream_lock_seek(data->input_stream, offset, SEEK_SET,
NULL))
return FLAC__STREAM_DECODER_SEEK_STATUS_ERROR;
return FLAC__STREAM_DECODER_SEEK_STATUS_OK;
......@@ -109,7 +110,7 @@ flac_eof_cb(G_GNUC_UNUSED const FLAC__StreamDecoder *fd, void *fdata)
return (decoder_get_command(data->decoder) != DECODE_COMMAND_NONE &&
decoder_get_command(data->decoder) != DECODE_COMMAND_SEEK) ||
input_stream_eof(data->input_stream);
input_stream_lock_eof(data->input_stream);
}
static void
......@@ -449,7 +450,7 @@ oggflac_decode(struct decoder *decoder, struct input_stream *input_stream)
/* rewind the stream, because ogg_stream_type_detect() has
moved it */
input_stream_seek(input_stream, 0, SEEK_SET, NULL);
input_stream_lock_seek(input_stream, 0, SEEK_SET, NULL);
flac_decode_internal(decoder, input_stream, true);
}
......
......@@ -102,7 +102,7 @@ fluidsynth_file_decode(struct decoder *decoder, const char *path_fs)
fluid_player_t *player;
char *path_dup;
int ret;
Timer *timer;
struct timer *timer;
enum decoder_command cmd;
soundfont_path =
......
......@@ -168,7 +168,7 @@ mp3_data_init(struct mp3_data *data, struct decoder *decoder,
static bool mp3_seek(struct mp3_data *data, long offset)
{
if (!input_stream_seek(data->input_stream, offset, SEEK_SET, NULL))
if (!input_stream_lock_seek(data->input_stream, offset, SEEK_SET, NULL))
return false;
mad_stream_buffer(&data->stream, data->input_buffer, 0);
......
......@@ -62,7 +62,7 @@ static GByteArray *mod_loadfile(struct decoder *decoder, struct input_stream *is
while (true) {
ret = decoder_read(decoder, is, data, MODPLUG_READ_BLOCK);
if (ret == 0) {
if (input_stream_eof(is))
if (input_stream_lock_eof(is))
/* end of file */
break;
......
......@@ -102,7 +102,8 @@ mp4_seek(void *user_data, uint64_t position)
{
struct mp4ff_input_stream *mis = user_data;
return input_stream_seek(mis->input_stream, position, SEEK_SET, NULL)
return input_stream_lock_seek(mis->input_stream, position, SEEK_SET,
NULL)
? 0 : -1;
}
......
......@@ -61,7 +61,7 @@ mpc_seek_cb(cb_first_arg, mpc_int32_t offset)
{
struct mpc_decoder_data *data = (struct mpc_decoder_data *) cb_data;
return input_stream_seek(data->is, offset, SEEK_SET, NULL);
return input_stream_lock_seek(data->is, offset, SEEK_SET, NULL);
}
static mpc_int32_t
......@@ -153,7 +153,6 @@ mpcdec_decode(struct decoder *mpd_decoder, struct input_stream *is)
mpc_uint32_t ret;
int32_t chunk[G_N_ELEMENTS(sample_buffer)];
long bit_rate = 0;
mpc_uint32_t vbr_update_acc;
mpc_uint32_t vbr_update_bits;
enum decoder_command cmd = DECODE_COMMAND_NONE;
......@@ -243,10 +242,11 @@ mpcdec_decode(struct decoder *mpd_decoder, struct input_stream *is)
decoder_seek_error(mpd_decoder);
}
vbr_update_acc = 0;
vbr_update_bits = 0;
#ifdef MPC_IS_OLD_API
mpc_uint32_t vbr_update_acc = 0;
ret = mpc_decoder_decode(&decoder, sample_buffer,
&vbr_update_acc, &vbr_update_bits);
if (ret == 0 || ret == (mpc_uint32_t)-1)
......
......@@ -52,7 +52,7 @@ pcm_stream_decode(struct decoder *decoder, struct input_stream *is)
size_t nbytes = decoder_read(decoder, is,
buffer, sizeof(buffer));
if (nbytes == 0 && input_stream_eof(is))
if (nbytes == 0 && input_stream_lock_eof(is))
break;
cmd = nbytes > 0
......@@ -62,7 +62,8 @@ pcm_stream_decode(struct decoder *decoder, struct input_stream *is)
if (cmd == DECODE_COMMAND_SEEK) {
goffset offset = (goffset)(time_to_size *
decoder_seek_where(decoder));
if (input_stream_seek(is, offset, SEEK_SET, &error)) {
if (input_stream_lock_seek(is, offset, SEEK_SET,
&error)) {
decoder_command_finished(decoder);
} else {
g_warning("seeking failed: %s", error->message);
......
......@@ -40,7 +40,7 @@ sndfile_vio_seek(sf_count_t offset, int whence, void *user_data)
struct input_stream *is = user_data;
bool success;
success = input_stream_seek(is, offset, whence, NULL);
success = input_stream_lock_seek(is, offset, whence, NULL);
if (!success)
return -1;
......@@ -54,7 +54,7 @@ sndfile_vio_read(void *ptr, sf_count_t count, void *user_data)
GError *error = NULL;
size_t nbytes;
nbytes = input_stream_read(is, ptr, count, &error);
nbytes = input_stream_lock_read(is, ptr, count, &error);
if (nbytes == 0 && error != NULL) {
g_warning("%s", error->message);
g_error_free(error);
......
......@@ -80,7 +80,7 @@ static int ogg_seek_cb(void *data, ogg_int64_t offset, int whence)
return vis->seekable &&
(!vis->decoder || decoder_get_command(vis->decoder) != DECODE_COMMAND_STOP) &&
input_stream_seek(vis->input_stream, offset, whence, NULL)
input_stream_lock_seek(vis->input_stream, offset, whence, NULL)
? 0 : -1;
}
......@@ -290,7 +290,7 @@ vorbis_stream_decode(struct decoder *decoder,
/* rewind the stream, because ogg_stream_type_detect() has
moved it */
input_stream_seek(input_stream, 0, SEEK_SET, NULL);
input_stream_lock_seek(input_stream, 0, SEEK_SET, NULL);
if (!vorbis_is_open(&vis, &vf, decoder, input_stream))
return;
......
......@@ -390,13 +390,15 @@ wavpack_input_get_pos(void *id)
static int
wavpack_input_set_pos_abs(void *id, uint32_t pos)
{
return input_stream_seek(wpin(id)->is, pos, SEEK_SET, NULL) ? 0 : -1;
return input_stream_lock_seek(wpin(id)->is, pos, SEEK_SET, NULL)
? 0 : -1;
}
static int
wavpack_input_set_pos_rel(void *id, int32_t delta, int mode)
{
return input_stream_seek(wpin(id)->is, delta, mode, NULL) ? 0 : -1;
return input_stream_lock_seek(wpin(id)->is, delta, mode, NULL)
? 0 : -1;
}
static int
......@@ -447,6 +449,7 @@ wavpack_input_init(struct wavpack_input *isp, struct decoder *decoder,
static struct input_stream *
wavpack_open_wvc(struct decoder *decoder, const char *uri,
GMutex *mutex, GCond *cond,
struct wavpack_input *wpi)
{
struct input_stream *is_wvc;
......@@ -462,7 +465,7 @@ wavpack_open_wvc(struct decoder *decoder, const char *uri,
return false;
wvc_url = g_strconcat(uri, "c", NULL);
is_wvc = input_stream_open(wvc_url, NULL);
is_wvc = input_stream_open(wvc_url, mutex, cond, NULL);
g_free(wvc_url);
if (is_wvc == NULL)
......@@ -499,7 +502,8 @@ wavpack_streamdecode(struct decoder * decoder, struct input_stream *is)
struct wavpack_input isp, isp_wvc;
bool can_seek = is->seekable;
is_wvc = wavpack_open_wvc(decoder, is->uri, &isp_wvc);
is_wvc = wavpack_open_wvc(decoder, is->uri, is->mutex, is->cond,
&isp_wvc);
if (is_wvc != NULL) {
open_flags |= OPEN_WVC;
can_seek &= is_wvc->seekable;
......
......@@ -142,50 +142,73 @@ void decoder_seek_error(struct decoder * decoder)
decoder_command_finished(decoder);
}
/**
* Should be read operation be cancelled? That is the case when the
* player thread has sent a command such as "STOP".
*/
G_GNUC_PURE
static inline bool
decoder_check_cancel_read(const struct decoder *decoder)
{
if (decoder == NULL)
return false;
const struct decoder_control *dc = decoder->dc;
if (dc->command == DECODE_COMMAND_NONE)
return false;
/* ignore the SEEK command during initialization, the plugin
should handle that after it has initialized successfully */
if (dc->command == DECODE_COMMAND_SEEK &&
(dc->state == DECODE_STATE_START || decoder->seeking))
return false;
return true;
}
size_t decoder_read(struct decoder *decoder,
struct input_stream *is,
void *buffer, size_t length)
{
const struct decoder_control *dc =
decoder != NULL ? decoder->dc : NULL;
/* XXX don't allow decoder==NULL */
GError *error = NULL;
size_t nbytes;
assert(decoder == NULL ||
dc->state == DECODE_STATE_START ||
dc->state == DECODE_STATE_DECODE);
decoder->dc->state == DECODE_STATE_START ||
decoder->dc->state == DECODE_STATE_DECODE);
assert(is != NULL);
assert(buffer != NULL);
if (length == 0)
return 0;
input_stream_lock(is);
while (true) {
/* XXX don't allow decoder==NULL */
if (decoder != NULL &&
/* ignore the SEEK command during initialization,
the plugin should handle that after it has
initialized successfully */
(dc->command != DECODE_COMMAND_SEEK ||
(dc->state != DECODE_STATE_START && !decoder->seeking)) &&
dc->command != DECODE_COMMAND_NONE)
if (decoder_check_cancel_read(decoder)) {
input_stream_unlock(is);
return 0;
}
nbytes = input_stream_read(is, buffer, length, &error);
if (input_stream_available(is))
break;
if (G_UNLIKELY(nbytes == 0 && error != NULL)) {
g_warning("%s", error->message);
g_error_free(error);
return 0;
}
g_cond_wait(is->cond, is->mutex);
}
if (nbytes > 0 || input_stream_eof(is))
return nbytes;
nbytes = input_stream_read(is, buffer, length, &error);
assert(nbytes == 0 || error == NULL);
assert(nbytes > 0 || error != NULL || input_stream_eof(is));
/* sleep for a fraction of a second! */
/* XXX don't sleep, wait for an event instead */
g_usleep(10000);
if (G_UNLIKELY(nbytes == 0 && error != NULL)) {
g_warning("%s", error->message);
g_error_free(error);
}
input_stream_unlock(is);
return nbytes;
}
void
......@@ -202,8 +225,7 @@ decoder_timestamp(struct decoder *decoder, double t)
* (decoder.chunk) if there is one.
*/
static enum decoder_command
do_send_tag(struct decoder *decoder, struct input_stream *is,
const struct tag *tag)
do_send_tag(struct decoder *decoder, const struct tag *tag)
{
struct music_chunk *chunk;
......@@ -216,7 +238,7 @@ do_send_tag(struct decoder *decoder, struct input_stream *is,
assert(decoder->chunk == NULL);
chunk = decoder_get_chunk(decoder, is);
chunk = decoder_get_chunk(decoder);
if (chunk == NULL) {
assert(decoder->dc->command != DECODE_COMMAND_NONE);
return decoder->dc->command;
......@@ -232,7 +254,7 @@ update_stream_tag(struct decoder *decoder, struct input_stream *is)
struct tag *tag;
tag = is != NULL
? input_stream_tag(is)
? input_stream_lock_tag(is)
: NULL;
if (tag == NULL) {
tag = decoder->song_tag;
......@@ -283,11 +305,11 @@ decoder_data(struct decoder *decoder,
tag = tag_merge(decoder->decoder_tag,
decoder->stream_tag);
cmd = do_send_tag(decoder, is, tag);
cmd = do_send_tag(decoder, tag);
tag_free(tag);
} else
/* send only the stream tag */
cmd = do_send_tag(decoder, is, decoder->stream_tag);
cmd = do_send_tag(decoder, decoder->stream_tag);
if (cmd != DECODE_COMMAND_NONE)
return cmd;
......@@ -313,7 +335,7 @@ decoder_data(struct decoder *decoder,
size_t nbytes;
bool full;
chunk = decoder_get_chunk(decoder, is);
chunk = decoder_get_chunk(decoder);
if (chunk == NULL) {
assert(dc->command != DECODE_COMMAND_NONE);
return dc->command;
......@@ -392,11 +414,11 @@ decoder_tag(G_GNUC_UNUSED struct decoder *decoder, struct input_stream *is,
struct tag *merged;
merged = tag_merge(decoder->stream_tag, decoder->decoder_tag);
cmd = do_send_tag(decoder, is, merged);
cmd = do_send_tag(decoder, merged);
tag_free(merged);
} else
/* send only the decoder tag */
cmd = do_send_tag(decoder, is, tag);
cmd = do_send_tag(decoder, tag);
return cmd;
}
......
......@@ -28,41 +28,17 @@
#include <assert.h>
/**
* This is a wrapper for input_stream_buffer(). It assumes that the
* decoder is currently locked, and temporarily unlocks it while
* calling input_stream_buffer(). We shouldn't hold the lock during a
* potentially blocking operation.
*/
static bool
decoder_input_buffer(struct decoder_control *dc, struct input_stream *is)
{
GError *error = NULL;
int ret;
decoder_unlock(dc);
ret = input_stream_buffer(is, &error);
if (ret < 0) {
g_warning("%s", error->message);
g_error_free(error);
}
decoder_lock(dc);
return ret > 0;
}
/**
* All chunks are full of decoded data; wait for the player to free
* one.
*/
static enum decoder_command
need_chunks(struct decoder_control *dc, struct input_stream *is, bool do_wait)
need_chunks(struct decoder_control *dc, bool do_wait)
{
if (dc->command == DECODE_COMMAND_STOP ||
dc->command == DECODE_COMMAND_SEEK)
return dc->command;
if ((is == NULL || !decoder_input_buffer(dc, is)) && do_wait) {
if (do_wait) {
decoder_wait(dc);
g_cond_signal(dc->client_cond);
......@@ -73,7 +49,7 @@ need_chunks(struct decoder_control *dc, struct input_stream *is, bool do_wait)
}
struct music_chunk *
decoder_get_chunk(struct decoder *decoder, struct input_stream *is)
decoder_get_chunk(struct decoder *decoder)
{
struct decoder_control *dc = decoder->dc;
enum decoder_command cmd;
......@@ -96,7 +72,7 @@ decoder_get_chunk(struct decoder *decoder, struct input_stream *is)
}
decoder_lock(dc);
cmd = need_chunks(dc, is, true);
cmd = need_chunks(dc, true);
decoder_unlock(dc);
} while (cmd == DECODE_COMMAND_NONE);
......
......@@ -70,7 +70,7 @@ struct decoder {
* @return the chunk, or NULL if we have received a decoder command
*/
struct music_chunk *
decoder_get_chunk(struct decoder *decoder, struct input_stream *is);
decoder_get_chunk(struct decoder *decoder);
/**
* Flushes the current chunk.
......
......@@ -58,7 +58,7 @@ const struct decoder_plugin *const decoder_plugins[] = {
#ifdef ENABLE_VORBIS_DECODER
&vorbis_decoder_plugin,
#endif
#if defined(HAVE_FLAC) || defined(HAVE_OGGFLAC)
#if defined(HAVE_FLAC)
&oggflac_decoder_plugin,
#endif
#ifdef HAVE_FLAC
......
......@@ -41,18 +41,6 @@
#undef G_LOG_DOMAIN
#define G_LOG_DOMAIN "decoder_thread"
static enum decoder_command
decoder_lock_get_command(struct decoder_control *dc)
{
enum decoder_command command;
decoder_lock(dc);
command = dc->command;
decoder_unlock(dc);
return command;
}
/**
* Marks the current decoder command as "finished" and notifies the
* player thread.
......@@ -86,7 +74,7 @@ decoder_input_stream_open(struct decoder_control *dc, const char *uri)
GError *error = NULL;
struct input_stream *is;
is = input_stream_open(uri, &error);
is = input_stream_open(uri, dc->mutex, dc->cond, &error);
if (is == NULL) {
if (error != NULL) {
g_warning("%s", error->message);
......@@ -99,19 +87,27 @@ decoder_input_stream_open(struct decoder_control *dc, const char *uri)
/* wait for the input stream to become ready; its metadata
will be available then */
decoder_lock(dc);
input_stream_update(is);
while (!is->ready &&
decoder_lock_get_command(dc) != DECODE_COMMAND_STOP) {
int ret;
dc->command != DECODE_COMMAND_STOP) {
decoder_wait(dc);
ret = input_stream_buffer(is, &error);
if (ret < 0) {
input_stream_close(is);
g_warning("%s", error->message);
g_error_free(error);
return NULL;
}
input_stream_update(is);
}
if (!input_stream_check(is, &error)) {
decoder_unlock(dc);
g_warning("%s", error->message);
g_error_free(error);
return NULL;
}
decoder_unlock(dc);
return is;
}
......@@ -132,11 +128,11 @@ decoder_stream_decode(const struct decoder_plugin *plugin,
if (decoder->dc->command == DECODE_COMMAND_STOP)
return true;
decoder_unlock(decoder->dc);
/* rewind the stream, so each plugin gets a fresh start */
input_stream_seek(input_stream, 0, SEEK_SET, NULL);
decoder_unlock(decoder->dc);
decoder_plugin_stream_decode(plugin, decoder, input_stream);
decoder_lock(decoder->dc);
......
......@@ -21,6 +21,7 @@
#include "directory.h"
#include "song.h"
#include "path.h"
#include "db_visitor.h"
#include <glib.h>
......@@ -167,28 +168,42 @@ directory_sort(struct directory *directory)
directory_sort(dv->base[i]);
}
int
directory_walk(struct directory *directory,
int (*forEachSong)(struct song *, void *),
int (*forEachDir)(struct directory *, void *),
void *data)
bool
directory_walk(const struct directory *directory, bool recursive,
const struct db_visitor *visitor, void *ctx,
GError **error_r)
{
struct dirvec *dv = &directory->children;
int err = 0;
size_t j;
if (forEachDir && (err = forEachDir(directory, data)) < 0)
return err;
assert(directory != NULL);
assert(visitor != NULL);
assert(error_r == NULL || *error_r == NULL);
if (visitor->song != NULL) {
const struct songvec *sv = &directory->songs;
for (size_t i = 0; i < sv->nr; ++i)
if (!visitor->song(sv->base[i], ctx, error_r))
return false;
}
if (forEachSong) {
err = songvec_for_each(&directory->songs, forEachSong, data);
if (err < 0)
return err;
if (visitor->playlist != NULL) {
const struct playlist_vector *pv = &directory->playlists;
for (const struct playlist_metadata *i = pv->head;
i != NULL; i = i->next)
if (!visitor->playlist(i, ctx, error_r))
return false;
}
for (j = 0; err >= 0 && j < dv->nr; ++j)
err = directory_walk(dv->base[j], forEachSong,
forEachDir, data);
const struct dirvec *dv = &directory->children;
for (size_t i = 0; i < dv->nr; ++i) {
struct directory *child = dv->base[i];
if (visitor->directory != NULL &&
!visitor->directory(child, ctx, error_r))
return false;
if (recursive &&
!directory_walk(child, recursive, visitor, ctx, error_r))
return false;
}
return err;
return true;
}
......@@ -25,6 +25,7 @@
#include "songvec.h"
#include "playlist_vector.h"
#include <glib.h>
#include <stdbool.h>
#include <sys/types.h>
......@@ -33,6 +34,8 @@
#define DEVICE_INARCHIVE (dev_t)(-1)
#define DEVICE_CONTAINER (dev_t)(-2)
struct db_visitor;
struct directory {
struct dirvec children;
struct songvec songs;
......@@ -127,10 +130,9 @@ directory_lookup_song(struct directory *directory, const char *uri);
void
directory_sort(struct directory *directory);
int
directory_walk(struct directory *directory,
int (*forEachSong)(struct song *, void *),
int (*forEachDir)(struct directory *, void *),
void *data);
bool
directory_walk(const struct directory *directory, bool recursive,
const struct db_visitor *visitor, void *ctx,
GError **error_r);
#endif
/*
* Copyright (C) 2003-2011 The Music Player Daemon Project
* 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.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
#include "config.h"
#include "directory_print.h"
#include "directory.h"
#include "client.h"
#include "song_print.h"
#include "mapper.h"
#include "decoder_list.h"
#include "path.h"
#include "uri.h"
#include "input_stream.h"
#include <sys/types.h>
#include <dirent.h>
static void
dirvec_print(struct client *client, const struct dirvec *dv)
{
size_t i;
for (i = 0; i < dv->nr; ++i)
client_printf(client, DIRECTORY_DIR "%s\n",
directory_get_path(dv->base[i]));
}
static void
print_playlist_in_directory(struct client *client,
const struct directory *directory,
const char *name_utf8)
{
if (directory_is_root(directory))
client_printf(client, "playlist: %s\n", name_utf8);
else
client_printf(client, "playlist: %s/%s\n",
directory_get_path(directory), name_utf8);
}
/**
* Print a list of playlists in the specified directory.
*/
static void
directory_print_playlists(struct client *client,
const struct directory *directory)
{
for (const struct playlist_metadata *pm = directory->playlists.head;
pm != NULL; pm = pm->next)
print_playlist_in_directory(client, directory, pm->name);
}
void
directory_print(struct client *client, const struct directory *directory)
{
dirvec_print(client, &directory->children);
songvec_print(client, &directory->songs);
directory_print_playlists(client, directory);
}
......@@ -42,9 +42,8 @@ directory_quark(void)
}
void
directory_save(FILE *fp, struct directory *directory)
directory_save(FILE *fp, const struct directory *directory)
{
struct dirvec *children = &directory->children;
size_t i;
if (!directory_is_root(directory)) {
......@@ -55,8 +54,9 @@ directory_save(FILE *fp, struct directory *directory)
directory_get_path(directory));
}
const struct dirvec *children = &directory->children;
for (i = 0; i < children->nr; ++i) {
struct directory *cur = children->base[i];
const struct directory *cur = children->base[i];
char *base = g_path_get_basename(cur->path);
fprintf(fp, DIRECTORY_DIR "%s\n", base);
......
......@@ -28,7 +28,7 @@
struct directory;
void
directory_save(FILE *fp, struct directory *directory);
directory_save(FILE *fp, const struct directory *directory);
bool
directory_load(FILE *fp, struct directory *directory,
......
......@@ -21,6 +21,7 @@
#include "event_pipe.h"
#include "fd_util.h"
#include "mpd_error.h"
#include "glib_socket.h"
#include <stdbool.h>
#include <assert.h>
......@@ -94,11 +95,7 @@ void event_pipe_init(void)
if (ret < 0)
MPD_ERROR("Couldn't open pipe: %s", strerror(errno));
#ifndef G_OS_WIN32
channel = g_io_channel_unix_new(event_pipe[0]);
#else
channel = g_io_channel_win32_new_fd(event_pipe[0]);
#endif
channel = g_io_channel_new_socket(event_pipe[0]);
g_io_channel_set_encoding(channel, NULL, NULL);
g_io_channel_set_buffered(channel, false);
......
......@@ -327,3 +327,13 @@ inotify_init_cloexec(void)
}
#endif
int
close_socket(int fd)
{
#ifdef WIN32
return closesocket(fd);
#else
return close(fd);
#endif
}
......@@ -36,6 +36,8 @@
#ifndef FD_UTIL_H
#define FD_UTIL_H
#include "check.h"
#include <stdbool.h>
#include <stddef.h>
......@@ -127,6 +129,8 @@ recvmsg_cloexec(int sockfd, struct msghdr *msg, int flags);
#endif
#ifdef HAVE_INOTIFY_INIT
/**
* Wrapper for inotify_init(), which sets the CLOEXEC flag (atomically
* if supported by the OS).
......@@ -135,3 +139,11 @@ int
inotify_init_cloexec(void);
#endif
/**
* Portable wrapper for close(); use closesocket() on WIN32/WinSock.
*/
int
close_socket(int fd);
#endif
......@@ -20,35 +20,44 @@
#ifndef MPD_GCC_H
#define MPD_GCC_H
#define GCC_CHECK_VERSION(major, minor) \
(defined(__GNUC__) && \
(__GNUC__ > (major) || \
(__GNUC__ == (major) && __GNUC_MINOR__ >= (minor))))
/* this allows us to take advantage of special gcc features while still
* allowing other compilers to compile:
*
* example taken from: http://rlove.org/log/2005102601
*/
#if defined(__GNUC__) && (__GNUC__ >= 3)
# define mpd_must_check __attribute__ ((warn_unused_result))
# define mpd_packed __attribute__ ((packed))
#if GCC_CHECK_VERSION(3,0)
# define gcc_must_check __attribute__ ((warn_unused_result))
# define gcc_packed __attribute__ ((packed))
/* these are very useful for type checking */
# define mpd_printf __attribute__ ((format(printf,1,2)))
# define mpd_fprintf __attribute__ ((format(printf,2,3)))
# define mpd_fprintf_ __attribute__ ((format(printf,3,4)))
# define mpd_fprintf__ __attribute__ ((format(printf,4,5)))
# define mpd_scanf __attribute__ ((format(scanf,1,2)))
# define mpd_used __attribute__ ((used))
# define gcc_printf __attribute__ ((format(printf,1,2)))
# define gcc_fprintf __attribute__ ((format(printf,2,3)))
# define gcc_fprintf_ __attribute__ ((format(printf,3,4)))
# define gcc_fprintf__ __attribute__ ((format(printf,4,5)))
# define gcc_scanf __attribute__ ((format(scanf,1,2)))
# define gcc_used __attribute__ ((used))
/* # define inline inline __attribute__ ((always_inline)) */
# define mpd_noinline __attribute__ ((noinline))
# define gcc_noinline __attribute__ ((noinline))
# define gcc_nonnull(...) __attribute__((nonnull(__VA_ARGS__)))
# define gcc_nonnull_all __attribute__((nonnull))
#else
# define mpd_must_check
# define mpd_packed
# define mpd_printf
# define mpd_fprintf
# define mpd_fprintf_
# define mpd_fprintf__
# define mpd_scanf
# define mpd_used
# define gcc_must_check
# define gcc_packed
# define gcc_printf
# define gcc_fprintf
# define gcc_fprintf_
# define gcc_fprintf__
# define gcc_scanf
# define gcc_used
/* # define inline */
# define mpd_noinline
# define gcc_noinline
# define gcc_nonnull(...)
# define gcc_nonnull_all
#endif
#endif /* MPD_GCC_H */
......@@ -32,6 +32,12 @@
#define g_queue_clear(q) do { g_queue_free(q); q = g_queue_new(); } while (0)
static inline GSource *
g_timeout_source_new_seconds(guint interval)
{
return g_timeout_source_new(interval * 1000);
}
static inline guint
g_timeout_add_seconds(guint interval, GSourceFunc function, gpointer data)
{
......@@ -43,6 +49,12 @@ g_timeout_add_seconds(guint interval, GSourceFunc function, gpointer data)
#if !GLIB_CHECK_VERSION(2,16,0)
static inline void
g_prefix_error(G_GNUC_UNUSED GError **error_r,
G_GNUC_UNUSED const gchar *format, ...)
{
}
static inline void
g_propagate_prefixed_error(GError **dest_r, GError *src,
G_GNUC_UNUSED const gchar *format, ...)
{
......@@ -74,4 +86,15 @@ g_uri_parse_scheme(const char *uri)
#endif
#if !GLIB_CHECK_VERSION(2,18,0)
static inline void
g_set_error_literal(GError **err, GQuark domain, gint code,
const gchar *message)
{
g_set_error(err, domain, code, "%s", message);
}
#endif
#endif
/*
* Copyright (C) 2003-2011 The Music Player Daemon Project
* 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.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
#ifndef MPD_GLIB_SOCKET_H
#define MPD_GLIB_SOCKET_H
#include <glib.h>
/**
* Portable wrapper for g_io_channel_unix_new() or
* g_io_channel_win32_new_socket().
*/
G_GNUC_MALLOC
static inline GIOChannel *
g_io_channel_new_socket(int fd)
{
#ifdef G_OS_WIN32
return g_io_channel_win32_new_socket(fd);
#else
return g_io_channel_unix_new(fd);
#endif
}
#endif
......@@ -34,7 +34,9 @@
* plugin and gzip fetches file from disk
*/
static struct input_stream *
input_archive_open(const char *pathname, GError **error_r)
input_archive_open(const char *pathname,
GMutex *mutex, GCond *cond,
GError **error_r)
{
const struct archive_plugin *arplug;
struct archive_file *file;
......@@ -65,7 +67,8 @@ input_archive_open(const char *pathname, GError **error_r)
return NULL;
//setup fileops
is = archive_file_open_stream(file, filename, error_r);
is = archive_file_open_stream(file, filename, mutex, cond,
error_r);
archive_file_close(file);
g_free(pname);
......
......@@ -23,6 +23,7 @@
#include "config.h"
#include "input/cdio_paranoia_input_plugin.h"
#include "input_internal.h"
#include "input_plugin.h"
#include "refcount.h"
#include "pcm_buffer.h"
......@@ -148,7 +149,9 @@ cdio_detect_device(void)
}
static struct input_stream *
input_cdio_open(const char *uri, GError **error_r)
input_cdio_open(const char *uri,
GMutex *mutex, GCond *cond,
GError **error_r)
{
struct input_cdio_paranoia *i;
......@@ -157,7 +160,8 @@ input_cdio_open(const char *uri, GError **error_r)
return NULL;
i = g_new(struct input_cdio_paranoia, 1);
input_stream_init(&i->base, &input_plugin_cdio_paranoia, uri);
input_stream_init(&i->base, &input_plugin_cdio_paranoia, uri,
mutex, cond);
/* initialize everything (should be already) */
i->drv = NULL;
......
......@@ -24,12 +24,4 @@ struct input_stream;
extern const struct input_plugin input_plugin_curl;
/**
* This is a workaround for an input_stream API deficiency; after
* exchanging the input_stream pointer in input_rewind_open(), this
* function is called to reinitialize CURL's data pointers.
*/
void
input_curl_reinit(struct input_stream *is);
#endif
......@@ -19,6 +19,7 @@
#include "config.h"
#include "input/despotify_input_plugin.h"
#include "input_internal.h"
#include "input_plugin.h"
#include "tag.h"
#include "despotify_utils.h"
......@@ -96,7 +97,9 @@ static void callback(G_GNUC_UNUSED struct despotify_session* ds,
static struct input_stream *
input_despotify_open(const char *url, G_GNUC_UNUSED GError **error_r)
input_despotify_open(const char *url,
GMutex *mutex, GCond *cond,
G_GNUC_UNUSED GError **error_r)
{
struct input_despotify *ctx;
struct despotify_session *session;
......@@ -130,7 +133,8 @@ input_despotify_open(const char *url, G_GNUC_UNUSED GError **error_r)
return NULL;
}
input_stream_init(&ctx->base, &input_plugin_despotify, url);
input_stream_init(&ctx->base, &input_plugin_despotify, url,
mutex, cond);
ctx->session = session;
ctx->track = track;
ctx->tag = mpd_despotify_tag_from_track(track);
......
......@@ -19,6 +19,7 @@
#include "config.h"
#include "input/ffmpeg_input_plugin.h"
#include "input_internal.h"
#include "input_plugin.h"
#include <libavformat/avio.h>
......@@ -73,7 +74,9 @@ input_ffmpeg_init(G_GNUC_UNUSED const struct config_param *param,
}
static struct input_stream *
input_ffmpeg_open(const char *uri, GError **error_r)
input_ffmpeg_open(const char *uri,
GMutex *mutex, GCond *cond,
GError **error_r)
{
struct input_ffmpeg *i;
......@@ -86,7 +89,8 @@ input_ffmpeg_open(const char *uri, GError **error_r)
return NULL;
i = g_new(struct input_ffmpeg, 1);
input_stream_init(&i->base, &input_plugin_ffmpeg, uri);
input_stream_init(&i->base, &input_plugin_ffmpeg, uri,
mutex, cond);
#if LIBAVFORMAT_VERSION_INT >= AV_VERSION_INT(53,1,0)
int ret = avio_open(&i->h, uri, AVIO_FLAG_READ);
......
......@@ -19,6 +19,7 @@
#include "config.h" /* must be first for large file support */
#include "input/file_input_plugin.h"
#include "input_internal.h"
#include "input_plugin.h"
#include "fd_util.h"
#include "open.h"
......@@ -45,14 +46,16 @@ file_quark(void)
}
static struct input_stream *
input_file_open(const char *filename, GError **error_r)
input_file_open(const char *filename,
GMutex *mutex, GCond *cond,
GError **error_r)
{
int fd, ret;
struct stat st;
struct file_input_stream *fis;
if (!g_path_is_absolute(filename))
return false;
return NULL;
fd = open_cloexec(filename, O_RDONLY|O_BINARY, 0);
if (fd < 0) {
......@@ -60,7 +63,7 @@ input_file_open(const char *filename, GError **error_r)
g_set_error(error_r, file_quark(), errno,
"Failed to open \"%s\": %s",
filename, g_strerror(errno));
return false;
return NULL;
}
ret = fstat(fd, &st);
......@@ -69,14 +72,14 @@ input_file_open(const char *filename, GError **error_r)
"Failed to stat \"%s\": %s",
filename, g_strerror(errno));
close(fd);
return false;
return NULL;
}
if (!S_ISREG(st.st_mode)) {
g_set_error(error_r, file_quark(), 0,
"Not a regular file: %s", filename);
close(fd);
return false;
return NULL;
}
#ifdef POSIX_FADV_SEQUENTIAL
......@@ -84,7 +87,8 @@ input_file_open(const char *filename, GError **error_r)
#endif
fis = g_new(struct file_input_stream, 1);
input_stream_init(&fis->base, &input_plugin_file, filename);
input_stream_init(&fis->base, &input_plugin_file, filename,
mutex, cond);
fis->base.size = st.st_size;
fis->base.seekable = true;
......
......@@ -19,6 +19,7 @@
#include "config.h"
#include "input/mms_input_plugin.h"
#include "input_internal.h"
#include "input_plugin.h"
#include <glib.h>
......@@ -45,7 +46,9 @@ mms_quark(void)
}
static struct input_stream *
input_mms_open(const char *url, GError **error_r)
input_mms_open(const char *url,
GMutex *mutex, GCond *cond,
GError **error_r)
{
struct input_mms *m;
......@@ -56,7 +59,8 @@ input_mms_open(const char *url, GError **error_r)
return NULL;
m = g_new(struct input_mms, 1);
input_stream_init(&m->base, &input_plugin_mms, url);
input_stream_init(&m->base, &input_plugin_mms, url,
mutex, cond);
m->mms = mmsx_connect(NULL, NULL, url, 128 * 1024);
if (m->mms == NULL) {
......
......@@ -19,7 +19,7 @@
#include "config.h"
#include "input/rewind_input_plugin.h"
#include "input/curl_input_plugin.h"
#include "input_internal.h"
#include "input_plugin.h"
#include "tag.h"
......@@ -83,12 +83,14 @@ copy_attributes(struct input_rewind *r)
assert(dest != src);
assert(src->mime == NULL || dest->mime != src->mime);
bool dest_ready = dest->ready;
dest->ready = src->ready;
dest->seekable = src->seekable;
dest->size = src->size;
dest->offset = src->offset;
if (src->mime != NULL) {
if (!dest_ready && src->ready) {
g_free(dest->mime);
dest->mime = g_strdup(src->mime);
}
......@@ -105,6 +107,23 @@ input_rewind_close(struct input_stream *is)
g_free(r);
}
static bool
input_rewind_check(struct input_stream *is, GError **error_r)
{
struct input_rewind *r = (struct input_rewind *)is;
return input_stream_check(r->input, error_r);
}
static void
input_rewind_update(struct input_stream *is)
{
struct input_rewind *r = (struct input_rewind *)is;
if (!reading_from_buffer(r))
copy_attributes(r);
}
static struct tag *
input_rewind_tag(struct input_stream *is)
{
......@@ -113,16 +132,12 @@ input_rewind_tag(struct input_stream *is)
return input_stream_tag(r->input);
}
static int
input_rewind_buffer(struct input_stream *is, GError **error_r)
static bool
input_rewind_available(struct input_stream *is)
{
struct input_rewind *r = (struct input_rewind *)is;
int ret = input_stream_buffer(r->input, error_r);
if (ret < 0 || !reading_from_buffer(r))
copy_attributes(r);
return ret;
return input_stream_available(r->input);
}
static size_t
......@@ -210,8 +225,10 @@ input_rewind_seek(struct input_stream *is, goffset offset, int whence,
static const struct input_plugin rewind_input_plugin = {
.close = input_rewind_close,
.check = input_rewind_check,
.update = input_rewind_update,
.tag = input_rewind_tag,
.buffer = input_rewind_buffer,
.available = input_rewind_available,
.read = input_rewind_read,
.eof = input_rewind_eof,
.seek = input_rewind_seek,
......@@ -230,7 +247,8 @@ input_rewind_open(struct input_stream *is)
return is;
c = g_new(struct input_rewind, 1);
input_stream_init(&c->base, &rewind_input_plugin, is->uri);
input_stream_init(&c->base, &rewind_input_plugin, is->uri,
is->mutex, is->cond);
c->tail = 0;
c->input = is;
......
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment