Compare commits
69 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ef10354d06 | ||
|
|
158458db5f | ||
|
|
e183ab5cf8 | ||
|
|
fef839e2a9 | ||
|
|
9776e43bbe | ||
|
|
5201147ab1 | ||
|
|
fb7daa0d05 | ||
|
|
2e9f3d8b9f | ||
|
|
976731ab6c | ||
|
|
0d8942e64a | ||
|
|
37a0f04712 | ||
|
|
cde9348009 | ||
|
|
095e6e6ad4 | ||
|
|
9d0bf5e95c | ||
|
|
8b327f1d9b | ||
|
|
aef0507abb | ||
|
|
6bab3bcfea | ||
|
|
a854595886 | ||
|
|
8fc3c5c612 | ||
|
|
4f408bd952 | ||
|
|
7de8fd04a4 | ||
|
|
8158bd218c | ||
|
|
aa1d867b72 | ||
|
|
34c8242133 | ||
|
|
e22bdee808 | ||
|
|
7f87de783f | ||
|
|
c66389a453 | ||
|
|
b63c1a2144 | ||
|
|
808dd7cc54 | ||
|
|
62a129c18f | ||
|
|
c18cd941aa | ||
|
|
6d12c22653 | ||
|
|
b76d78e6ae | ||
|
|
0a6e484b1a | ||
|
|
0bb71f1f20 | ||
|
|
1aa7cdd602 | ||
|
|
a4b8a0d801 | ||
|
|
3bf521d5ca | ||
|
|
0acb55cde5 | ||
|
|
6b89fd6100 | ||
|
|
52ce39dc3e | ||
|
|
7a3e15d8e5 | ||
|
|
cf66a60c60 | ||
|
|
9b26d451e4 | ||
|
|
137ffba1b4 | ||
|
|
5c5dc1b7c0 | ||
|
|
9e9418294a | ||
|
|
b850eb74b7 | ||
|
|
67d73a2aee | ||
|
|
fde9a470dd | ||
|
|
8d1f30e55b | ||
|
|
ddd2b60489 | ||
|
|
8777737861 | ||
|
|
cb71f6dd04 | ||
|
|
1881b0e975 | ||
|
|
98b29f6d1c | ||
|
|
59fdfd25cb | ||
|
|
0d98677212 | ||
|
|
38f0c16904 | ||
|
|
4fbf6b6c95 | ||
|
|
1f8ff48168 | ||
|
|
20b6e0d684 | ||
|
|
713c1f2ba9 | ||
|
|
a149bc4c5d | ||
|
|
b3a458338a | ||
|
|
44422b2b2f | ||
|
|
f10afd38b5 | ||
|
|
4c50a5e0b3 | ||
|
|
f255a485b7 |
32
NEWS
32
NEWS
@@ -1,3 +1,35 @@
|
||||
ver 0.21.7 (2019/04/03)
|
||||
* input
|
||||
- qobuz/tidal: scan tags when loading a playlist
|
||||
* require Meson 0.49.0 for native libgcrypt-config support
|
||||
* fix build failure with -Dlocal_socket=false
|
||||
* Haiku
|
||||
- fix build
|
||||
- add version info
|
||||
|
||||
ver 0.21.6 (2019/03/17)
|
||||
* protocol
|
||||
- allow loading playlists specified as absolute filesystem paths
|
||||
- fix negated filter expressions with multiple tag values
|
||||
- fix "list" with filter expression
|
||||
- omit empty playlist names in "listplaylists"
|
||||
* input
|
||||
- cdio_paranoia: fix build failure due to missing #include
|
||||
* decoder
|
||||
- opus: fix replay gain when there are no other tags
|
||||
- opus: fix seeking to beginning of song
|
||||
- vorbis: fix Tremor conflict resulting in crash
|
||||
* output
|
||||
- pulse: work around error with unusual channel count
|
||||
- osx: fix build failure
|
||||
* playlist
|
||||
- flac: fix use-after-free bug
|
||||
* support abstract sockets on Linux
|
||||
* Windows
|
||||
- remove the unused libwinpthread-1.dll dependency
|
||||
* Android
|
||||
- enable SLES power saving mode
|
||||
|
||||
ver 0.21.5 (2019/02/22)
|
||||
* protocol
|
||||
- fix deadlock in "albumart" command
|
||||
|
||||
@@ -2,8 +2,8 @@
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
package="org.musicpd"
|
||||
android:installLocation="auto"
|
||||
android:versionCode="27"
|
||||
android:versionName="0.21.5">
|
||||
android:versionCode="29"
|
||||
android:versionName="0.21.7">
|
||||
|
||||
<uses-sdk android:minSdkVersion="21" android:targetSdkVersion="26"/>
|
||||
|
||||
|
||||
@@ -38,7 +38,7 @@ author = 'Max Kellermann'
|
||||
# built documents.
|
||||
#
|
||||
# The short X.Y version.
|
||||
version = '0.21.5'
|
||||
version = '0.21.7'
|
||||
# The full version, including alpha/beta/rc tags.
|
||||
release = version
|
||||
|
||||
|
||||
@@ -690,6 +690,8 @@ Valid quality values for libsoxr:
|
||||
* "low"
|
||||
* "quick"
|
||||
|
||||
.. _output_plugins:
|
||||
|
||||
Output plugins
|
||||
--------------
|
||||
|
||||
@@ -800,6 +802,15 @@ The fifo plugin writes raw PCM data to a FIFO (First In, First Out) file. The da
|
||||
* - **path P**
|
||||
- This specifies the path of the FIFO to write to. Must be an absolute path. If the path does not exist, it will be created when MPD is started, and removed when MPD is stopped. The FIFO will be created with the same user and group as MPD is running as. Default permissions can be modified by using the builtin shell command umask. If a FIFO already exists at the specified path it will be reused, and will not be removed when MPD is stopped. You can use the "mkfifo" command to create this, and then you may modify the permissions to your liking.
|
||||
|
||||
haiku
|
||||
~~~~~
|
||||
|
||||
Use the SoundPlayer API on the Haiku operating system.
|
||||
|
||||
This plugin is unmaintained and contains known bugs. It will be
|
||||
removed soon, unless there is a new maintainer.
|
||||
|
||||
|
||||
jack
|
||||
~~~~
|
||||
The jack plugin connects to a `JACK server <http://jackaudio.org/>`_.
|
||||
@@ -838,7 +849,7 @@ It is highly recommended to configure a fixed format, because a stream cannot sw
|
||||
* - **port P**
|
||||
- Binds the HTTP server to the specified port.
|
||||
* - **bind_to_address ADDR**
|
||||
- Binds the HTTP server to the specified address (IPv4, IPv6 or UNIX socket). Multiple addresses in parallel are not supported.
|
||||
- Binds the HTTP server to the specified address (IPv4, IPv6 or local socket). Multiple addresses in parallel are not supported.
|
||||
* - **encoder NAME**
|
||||
- Chooses an encoder plugin. A list of encoder plugins can be found in the encoder plugin reference :ref:`encoder_plugins`.
|
||||
* - **max_clients MC**
|
||||
@@ -1037,8 +1048,41 @@ The "Solaris" plugin runs only on SUN Solaris, and plays via /dev/audio.
|
||||
* - **device PATH**
|
||||
- Sets the path of the audio device, defaults to /dev/audio.
|
||||
|
||||
|
||||
.. _filter_plugins:
|
||||
|
||||
Filter plugins
|
||||
--------------
|
||||
|
||||
normalize
|
||||
~~~~~~~~~
|
||||
|
||||
Normalize the volume during playback (at the expensve of quality).
|
||||
|
||||
|
||||
null
|
||||
~~~~
|
||||
|
||||
A no-op filter. Audio data is returned as-is.
|
||||
|
||||
|
||||
route
|
||||
~~~~~
|
||||
|
||||
Reroute channels.
|
||||
|
||||
.. list-table::
|
||||
:widths: 20 80
|
||||
:header-rows: 1
|
||||
|
||||
* - Setting
|
||||
- Description
|
||||
* - **routes "0>0, 1>1, ..."**
|
||||
- Specifies the channel mapping.
|
||||
|
||||
|
||||
.. _playlist_plugins:
|
||||
|
||||
|
||||
Playlist plugins
|
||||
----------------
|
||||
|
||||
|
||||
@@ -144,15 +144,20 @@ syntax::
|
||||
``EXPRESSION`` is a string enclosed in parantheses which can be one
|
||||
of:
|
||||
|
||||
- ``(TAG == 'VALUE')``: match a tag value.
|
||||
``(TAG != 'VALUE')``: mismatch a tag value.
|
||||
The special tag "*any*" checks all
|
||||
tag values.
|
||||
*albumartist* looks for
|
||||
- ``(TAG == 'VALUE')``: match a tag value; if there are multiple
|
||||
values of the given type, at least one must match.
|
||||
``(TAG != 'VALUE')``: mismatch a tag value; if there are multiple
|
||||
values of the given type, none of them must match.
|
||||
The special tag ``any`` checks all
|
||||
tag types.
|
||||
``AlbumArtist`` looks for
|
||||
``VALUE`` in ``AlbumArtist``
|
||||
and falls back to ``Artist`` tags if
|
||||
``AlbumArtist`` does not exist.
|
||||
``VALUE`` is what to find.
|
||||
An empty value string means: match only if the given tag type does
|
||||
not exist at all; this implies that negation with an empty value
|
||||
checks for the existence of the given tag type.
|
||||
|
||||
- ``(TAG contains 'VALUE')`` checks if the given value is a substring
|
||||
of the tag value.
|
||||
@@ -178,7 +183,7 @@ of:
|
||||
|
||||
- ``(AudioFormat =~ 'SAMPLERATE:BITS:CHANNELS')``:
|
||||
matches the audio format with the given mask (i.e. one
|
||||
or more attributes may be "*").
|
||||
or more attributes may be ``*``).
|
||||
|
||||
- ``(!EXPRESSION)``: negate an expression. Note that each expression
|
||||
must be enclosed in parantheses, e.g. :code:`(!(artist == 'VALUE'))`
|
||||
@@ -207,11 +212,11 @@ backslash.
|
||||
|
||||
Example expression which matches an artist named ``foo'bar"``::
|
||||
|
||||
(artist "foo\'bar\"")
|
||||
(Artist == "foo\'bar\"")
|
||||
|
||||
At the protocol level, the command must look like this::
|
||||
|
||||
find "(artist \"foo\\'bar\\\"\")"
|
||||
find "(Artist == \"foo\\'bar\\\"\")"
|
||||
|
||||
The double quotes enclosing the artist name must be escaped because
|
||||
they are inside a double-quoted ``find`` parameter. The single quote
|
||||
@@ -714,7 +719,9 @@ and without the `.m3u` suffix).
|
||||
Some of the commands described in this section can be used to
|
||||
run playlist plugins instead of the hard-coded simple
|
||||
`m3u` parser. They can access playlists in
|
||||
the music directory (relative path including the suffix) or
|
||||
the music directory (relative path including the suffix),
|
||||
playlists in arbitrary location (absolute path including the suffix;
|
||||
allowed only for clients that are connected via local socket), or
|
||||
remote playlists (absolute URI with a supported scheme).
|
||||
|
||||
:command:`listplaylist {NAME}`
|
||||
@@ -924,7 +931,7 @@ The music database
|
||||
This command may be used to list metadata of remote
|
||||
files (e.g. URI beginning with "http://" or "smb://").
|
||||
|
||||
Clients that are connected via UNIX domain socket may
|
||||
Clients that are connected via local socket may
|
||||
use this command to read the tags of an arbitrary local
|
||||
file (URI is an absolute path).
|
||||
|
||||
@@ -1215,7 +1222,7 @@ Reflection
|
||||
:command:`config`
|
||||
Dumps configuration values that may be interesting for
|
||||
the client. This command is only permitted to "local"
|
||||
clients (connected via UNIX domain socket).
|
||||
clients (connected via local socket).
|
||||
|
||||
The following response attributes are available:
|
||||
|
||||
|
||||
29
doc/user.rst
29
doc/user.rst
@@ -54,7 +54,7 @@ Download the source tarball from the `MPD home page <https://musicpd.org>`_ and
|
||||
In any case, you need:
|
||||
|
||||
* a C++14 compiler (e.g. gcc 6.0 or clang 3.9)
|
||||
* `Meson 0.47.2 <http://mesonbuild.com/>`__ and `Ninja
|
||||
* `Meson 0.49.0 <http://mesonbuild.com/>`__ and `Ninja
|
||||
<https://ninja-build.org/>`__
|
||||
* Boost 1.58
|
||||
* pkg-config
|
||||
@@ -365,10 +365,14 @@ More information can be found in the :ref:`decoder_plugins` reference.
|
||||
Configuring encoder plugins
|
||||
---------------------------
|
||||
|
||||
Encoders are used by some of the output plugins (such as shout). The encoder settings are included in the audio_output section.
|
||||
Encoders are used by some of the output plugins (such as shout). The
|
||||
encoder settings are included in the ``audio_output`` section, see :ref:`config_audio_output`.
|
||||
|
||||
More information can be found in the :ref:`encoder_plugins` reference.
|
||||
|
||||
|
||||
.. _config_audio_output:
|
||||
|
||||
Configuring audio outputs
|
||||
-------------------------
|
||||
|
||||
@@ -421,6 +425,15 @@ The following table lists the audio_output options valid for all plugins:
|
||||
implement an external mixer :ref:`external_mixer`) or no mixer
|
||||
(:samp:`none`). By default, the hardware mixer is used for
|
||||
devices which support it, and none for the others.
|
||||
* - **filters "name,...**"
|
||||
- The specified configured filters are instantiated in the given
|
||||
order. Each filter name refers to a ``filter`` block, see
|
||||
:ref:`config_filter`.
|
||||
|
||||
More information can be found in the :ref:`output_plugins` reference.
|
||||
|
||||
|
||||
.. _config_filter:
|
||||
|
||||
Configuring filters
|
||||
-------------------
|
||||
@@ -436,6 +449,9 @@ To configure a filter, add a :code:`filter` block to :file:`mpd.conf`:
|
||||
name "software volume"
|
||||
}
|
||||
|
||||
Configured filters may then be added to the ``filters`` setting of an
|
||||
``audio_output`` section, see :ref:`config_audio_output`.
|
||||
|
||||
The following table lists the filter options valid for all plugins:
|
||||
|
||||
.. list-table::
|
||||
@@ -449,6 +465,9 @@ The following table lists the filter options valid for all plugins:
|
||||
* - **name**
|
||||
- The name of the filter
|
||||
|
||||
More information can be found in the :ref:`filter_plugins` reference.
|
||||
|
||||
|
||||
Configuring playlist plugins
|
||||
----------------------------
|
||||
|
||||
@@ -531,6 +550,12 @@ choice::
|
||||
|
||||
bind_to_address "/var/run/mpd/socket"
|
||||
|
||||
On Linux, local sockets can be bound to a name without a socket inode
|
||||
on the filesystem; MPD implements this by prepending ``@`` to the
|
||||
address::
|
||||
|
||||
bind_to_address "@mpd"
|
||||
|
||||
If no port is specified, the default port is 6600. This default can
|
||||
be changed with the port setting::
|
||||
|
||||
|
||||
25
meson.build
25
meson.build
@@ -1,8 +1,8 @@
|
||||
project(
|
||||
'mpd',
|
||||
['c', 'cpp'],
|
||||
version: '0.21.5',
|
||||
meson_version: '>= 0.47.2',
|
||||
version: '0.21.7',
|
||||
meson_version: '>= 0.49.0',
|
||||
default_options: [
|
||||
'c_std=c99',
|
||||
'cpp_std=c++14'
|
||||
@@ -20,7 +20,7 @@ conf.set_quoted('PACKAGE', meson.project_name())
|
||||
conf.set_quoted('PACKAGE_NAME', meson.project_name())
|
||||
conf.set_quoted('PACKAGE_VERSION', meson.project_version())
|
||||
conf.set_quoted('VERSION', meson.project_version())
|
||||
conf.set_quoted('PROTOCOL_VERSION', '0.21.4')
|
||||
conf.set_quoted('PROTOCOL_VERSION', '0.21.6')
|
||||
conf.set_quoted('SYSTEM_CONFIG_FILE_LOCATION', join_paths(get_option('prefix'), get_option('sysconfdir'), 'mpd.conf'))
|
||||
|
||||
common_cppflags = [
|
||||
@@ -390,6 +390,7 @@ more_deps = []
|
||||
if is_android
|
||||
subdir('src/java')
|
||||
target_type = 'shared_library'
|
||||
target_name = 'mpd'
|
||||
link_args += [
|
||||
'-Wl,--no-undefined,-shared,-Bsymbolic',
|
||||
'-llog',
|
||||
@@ -399,12 +400,20 @@ if is_android
|
||||
declare_dependency(sources: [classes_jar]),
|
||||
java_dep,
|
||||
]
|
||||
elif is_haiku
|
||||
target_type = 'executable'
|
||||
target_name = 'mpd.nores'
|
||||
link_args += [
|
||||
'-lnetwork',
|
||||
'-lbe',
|
||||
]
|
||||
else
|
||||
target_type = 'executable'
|
||||
target_name = 'mpd'
|
||||
endif
|
||||
|
||||
mpd = build_target(
|
||||
'mpd',
|
||||
target_name,
|
||||
sources,
|
||||
target_type: target_type,
|
||||
include_directories: inc,
|
||||
@@ -443,6 +452,14 @@ endif
|
||||
|
||||
if is_haiku
|
||||
subdir('src/haiku')
|
||||
custom_target(
|
||||
'mpd',
|
||||
output: 'mpd',
|
||||
input: [mpd, rsrc],
|
||||
command: [addres, '@OUTPUT@', '@INPUT0@', '@INPUT1@'],
|
||||
install: true,
|
||||
install_dir: get_option('bindir'),
|
||||
)
|
||||
endif
|
||||
|
||||
configure_file(output: 'config.h', configuration: conf)
|
||||
|
||||
@@ -112,8 +112,8 @@ liblame = AutotoolsProject(
|
||||
)
|
||||
|
||||
ffmpeg = FfmpegProject(
|
||||
'http://ffmpeg.org/releases/ffmpeg-4.1.1.tar.xz',
|
||||
'373749824dfd334d84e55dff406729edfd1606575ee44dd485d97d45ea4d2d86',
|
||||
'http://ffmpeg.org/releases/ffmpeg-4.1.3.tar.xz',
|
||||
'0c3020452880581a8face91595b239198078645e7d7184273b8bcc7758beb63d',
|
||||
'lib/libavcodec.a',
|
||||
[
|
||||
'--disable-shared', '--enable-static',
|
||||
@@ -341,8 +341,8 @@ ffmpeg = FfmpegProject(
|
||||
)
|
||||
|
||||
curl = AutotoolsProject(
|
||||
'http://curl.haxx.se/download/curl-7.64.0.tar.xz',
|
||||
'2f2f13fa34d44aa29cb444077ad7dc4dc6d189584ad552e0aaeb06e608af6001',
|
||||
'http://curl.haxx.se/download/curl-7.64.1.tar.xz',
|
||||
'9252332a7f871ce37bfa7f78bdd0a0e3924d8187cc27cb57c76c9474a7168fb3',
|
||||
'lib/libcurl.a',
|
||||
[
|
||||
'--disable-shared', '--enable-static',
|
||||
@@ -375,8 +375,8 @@ libexpat = AutotoolsProject(
|
||||
)
|
||||
|
||||
libnfs = AutotoolsProject(
|
||||
'https://github.com/sahlberg/libnfs/archive/libnfs-3.0.0.tar.gz',
|
||||
'445d92c5fc55e4a5b115e358e60486cf8f87ee50e0103d46a02e7fb4618566a5',
|
||||
'https://github.com/sahlberg/libnfs/archive/libnfs-4.0.0.tar.gz',
|
||||
'6ee77e9fe220e2d3e3b1f53cfea04fb319828cc7dbb97dd9df09e46e901d797d',
|
||||
'lib/libnfs.a',
|
||||
[
|
||||
'--disable-shared', '--enable-static',
|
||||
@@ -387,7 +387,7 @@ libnfs = AutotoolsProject(
|
||||
|
||||
'--disable-utils', '--disable-examples',
|
||||
],
|
||||
base='libnfs-libnfs-3.0.0',
|
||||
base='libnfs-libnfs-4.0.0',
|
||||
autoreconf=True,
|
||||
)
|
||||
|
||||
|
||||
@@ -134,7 +134,9 @@ LoadPlaylistFileInfo(PlaylistInfo &info,
|
||||
const auto *const name_fs_end =
|
||||
FindStringSuffix(name_fs_str,
|
||||
PATH_LITERAL(PLAYLIST_FILE_SUFFIX));
|
||||
if (name_fs_end == nullptr)
|
||||
if (name_fs_end == nullptr ||
|
||||
/* no empty playlist names (raw file name = ".m3u") */
|
||||
name_fs_end == name_fs_str)
|
||||
return false;
|
||||
|
||||
FileInfo fi;
|
||||
|
||||
@@ -268,7 +268,10 @@ handle_list(Client &client, Request args, Response &r)
|
||||
std::unique_ptr<SongFilter> filter;
|
||||
TagType group = TAG_NUM_OF_ITEM_TYPES;
|
||||
|
||||
if (args.size == 1) {
|
||||
if (args.size == 1 &&
|
||||
/* parantheses are the syntax for filter expressions: no
|
||||
compatibility mode */
|
||||
args.front()[0] != '(') {
|
||||
/* for compatibility with < 0.12.0 */
|
||||
if (tagType != TAG_ALBUM) {
|
||||
r.FormatError(ACK_ERROR_ARG,
|
||||
|
||||
@@ -20,6 +20,7 @@
|
||||
#include "config.h"
|
||||
#include "PlaylistCommands.hxx"
|
||||
#include "Request.hxx"
|
||||
#include "Instance.hxx"
|
||||
#include "db/DatabasePlaylist.hxx"
|
||||
#include "CommandError.hxx"
|
||||
#include "PlaylistSave.hxx"
|
||||
@@ -27,6 +28,7 @@
|
||||
#include "PlaylistError.hxx"
|
||||
#include "db/PlaylistVector.hxx"
|
||||
#include "SongLoader.hxx"
|
||||
#include "song/DetachedSong.hxx"
|
||||
#include "BulkEdit.hxx"
|
||||
#include "playlist/PlaylistQueue.hxx"
|
||||
#include "playlist/Print.hxx"
|
||||
@@ -38,6 +40,7 @@
|
||||
#include "util/UriUtil.hxx"
|
||||
#include "util/ConstBuffer.hxx"
|
||||
#include "util/ChronoUtil.hxx"
|
||||
#include "LocateUri.hxx"
|
||||
|
||||
bool
|
||||
playlist_commands_available() noexcept
|
||||
@@ -66,22 +69,41 @@ handle_save(Client &client, Request args, gcc_unused Response &r)
|
||||
CommandResult
|
||||
handle_load(Client &client, Request args, gcc_unused Response &r)
|
||||
{
|
||||
const auto uri = LocateUri(args.front(), &client
|
||||
#ifdef ENABLE_DATABASE
|
||||
, nullptr
|
||||
#endif
|
||||
);
|
||||
RangeArg range = args.ParseOptional(1, RangeArg::All());
|
||||
|
||||
const ScopeBulkEdit bulk_edit(client.GetPartition());
|
||||
|
||||
auto &playlist = client.GetPlaylist();
|
||||
const unsigned old_size = playlist.GetLength();
|
||||
|
||||
const SongLoader loader(client);
|
||||
playlist_open_into_queue(args.front(),
|
||||
playlist_open_into_queue(uri,
|
||||
range.start, range.end,
|
||||
client.GetPlaylist(),
|
||||
playlist,
|
||||
client.GetPlayerControl(), loader);
|
||||
|
||||
/* invoke the RemoteTagScanner on all newly added songs */
|
||||
auto &instance = client.GetInstance();
|
||||
const unsigned new_size = playlist.GetLength();
|
||||
for (unsigned i = old_size; i < new_size; ++i)
|
||||
instance.LookupRemoteTag(playlist.queue.Get(i).GetURI());
|
||||
|
||||
return CommandResult::OK;
|
||||
}
|
||||
|
||||
CommandResult
|
||||
handle_listplaylist(Client &client, Request args, Response &r)
|
||||
{
|
||||
const char *const name = args.front();
|
||||
const auto name = LocateUri(args.front(), &client
|
||||
#ifdef ENABLE_DATABASE
|
||||
, nullptr
|
||||
#endif
|
||||
);
|
||||
|
||||
if (playlist_file_print(r, client.GetPartition(), SongLoader(client),
|
||||
name, false))
|
||||
@@ -93,7 +115,11 @@ handle_listplaylist(Client &client, Request args, Response &r)
|
||||
CommandResult
|
||||
handle_listplaylistinfo(Client &client, Request args, Response &r)
|
||||
{
|
||||
const char *const name = args.front();
|
||||
const auto name = LocateUri(args.front(), &client
|
||||
#ifdef ENABLE_DATABASE
|
||||
, nullptr
|
||||
#endif
|
||||
);
|
||||
|
||||
if (playlist_file_print(r, client.GetPartition(), SongLoader(client),
|
||||
name, true))
|
||||
|
||||
@@ -29,6 +29,8 @@ ServerSocketAddGeneric(ServerSocket &server_socket, const char *address, unsigne
|
||||
server_socket.AddPort(port);
|
||||
} else if (address[0] == '/' || address[0] == '~') {
|
||||
server_socket.AddPath(ParsePath(address));
|
||||
} else if (address[0] == '@') {
|
||||
server_socket.AddAbstract(address);
|
||||
} else {
|
||||
server_socket.AddHost(address, port);
|
||||
}
|
||||
|
||||
@@ -23,14 +23,14 @@
|
||||
class ServerSocket;
|
||||
|
||||
/**
|
||||
* Sets the address or unix socket of a ServerSocket instance
|
||||
* Sets the address or local socket of a ServerSocket instance
|
||||
* There are three possible ways
|
||||
* 1) Set address to a valid ip address and specify port.
|
||||
* server_socket will listen on this address/port tuple.
|
||||
* 2) Set address to null and specify port.
|
||||
* server_socket will listen on ANY address on that port.
|
||||
* 3) Set address to a path of a unix socket. port is ignored.
|
||||
* server_socket will listen on this unix socket.
|
||||
* 3) Set address to a path of a local socket. port is ignored.
|
||||
* server_socket will listen on this local socket.
|
||||
*
|
||||
* Throws #std::runtime_error on error.
|
||||
*
|
||||
|
||||
@@ -39,8 +39,8 @@ InitHybridDsdDecoder(const ConfigBlock &block)
|
||||
without a DSD DAC, the PCM (=ALAC) part of the file is
|
||||
better */
|
||||
if (block.GetBlockParam("enabled") == nullptr) {
|
||||
LogInfo(hybrid_dsd_domain,
|
||||
"The Hybrid DSD decoder is disabled because it was not explicitly enabled");
|
||||
LogDebug(hybrid_dsd_domain,
|
||||
"The Hybrid DSD decoder is disabled because it was not explicitly enabled");
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
@@ -208,10 +208,12 @@ MPDOpusDecoder::HandleTags(const ogg_packet &packet)
|
||||
TagBuilder tag_builder;
|
||||
AddTagHandler h(tag_builder);
|
||||
|
||||
if (ScanOpusTags(packet.packet, packet.bytes, &rgi, h) &&
|
||||
!tag_builder.empty()) {
|
||||
client.SubmitReplayGain(&rgi);
|
||||
if (!ScanOpusTags(packet.packet, packet.bytes, &rgi, h))
|
||||
return;
|
||||
|
||||
client.SubmitReplayGain(&rgi);
|
||||
|
||||
if (!tag_builder.empty()) {
|
||||
Tag tag = tag_builder.Commit();
|
||||
auto cmd = client.SubmitTag(input_stream, std::move(tag));
|
||||
if (cmd != DecoderCommand::NONE)
|
||||
|
||||
@@ -392,7 +392,29 @@ ServerSocket::AddPath(AllocatedPath &&path)
|
||||
#else /* !HAVE_UN */
|
||||
(void)path;
|
||||
|
||||
throw std::runtime_error("UNIX domain socket support is disabled");
|
||||
throw std::runtime_error("Local socket support is disabled");
|
||||
#endif /* !HAVE_UN */
|
||||
}
|
||||
|
||||
|
||||
void
|
||||
ServerSocket::AddAbstract(const char *name)
|
||||
{
|
||||
#if !defined(__linux__)
|
||||
(void)name;
|
||||
|
||||
throw std::runtime_error("Abstract sockets are only available on Linux");
|
||||
#elif !defined(HAVE_UN)
|
||||
(void)name;
|
||||
|
||||
throw std::runtime_error("Local socket support is disabled");
|
||||
#else
|
||||
assert(name != nullptr);
|
||||
assert(*name == '@');
|
||||
|
||||
AllocatedSocketAddress address;
|
||||
address.SetLocal(name);
|
||||
|
||||
AddAddress(std::move(address));
|
||||
#endif
|
||||
}
|
||||
|
||||
@@ -90,7 +90,7 @@ public:
|
||||
void AddHost(const char *hostname, unsigned port);
|
||||
|
||||
/**
|
||||
* Add a listener on a Unix domain socket.
|
||||
* Add a listener on a local socket.
|
||||
*
|
||||
* Throws #std::runtime_error on error.
|
||||
*
|
||||
@@ -99,6 +99,16 @@ public:
|
||||
*/
|
||||
void AddPath(AllocatedPath &&path);
|
||||
|
||||
/**
|
||||
* Add a listener on an abstract local socket (Linux specific).
|
||||
*
|
||||
* Throws on error.
|
||||
*
|
||||
* @param name the abstract socket name, starting with a '@'
|
||||
* instead of a null byte
|
||||
*/
|
||||
void AddAbstract(const char *name);
|
||||
|
||||
/**
|
||||
* Add a socket descriptor that is accepting connections. After this
|
||||
* has been called, don't call server_socket_open(), because the
|
||||
|
||||
@@ -65,7 +65,7 @@ public:
|
||||
|
||||
/**
|
||||
* Flush pending data and return it. This should be called
|
||||
* repepatedly until it returns nullptr.
|
||||
* repeatedly until it returns nullptr.
|
||||
*/
|
||||
virtual ConstBuffer<void> Flush();
|
||||
};
|
||||
|
||||
@@ -56,6 +56,7 @@ public:
|
||||
}
|
||||
|
||||
ConstBuffer<void> FilterPCM(ConstBuffer<void> src) override;
|
||||
ConstBuffer<void> Flush() override;
|
||||
};
|
||||
|
||||
class PreparedAutoConvertFilter final : public PreparedFilter {
|
||||
@@ -104,6 +105,18 @@ AutoConvertFilter::FilterPCM(ConstBuffer<void> src)
|
||||
return filter->FilterPCM(src);
|
||||
}
|
||||
|
||||
ConstBuffer<void>
|
||||
AutoConvertFilter::Flush()
|
||||
{
|
||||
if (convert != nullptr) {
|
||||
auto result = convert->Flush();
|
||||
if (!result.IsNull())
|
||||
return filter->FilterPCM(result);
|
||||
}
|
||||
|
||||
return filter->Flush();
|
||||
}
|
||||
|
||||
std::unique_ptr<PreparedFilter>
|
||||
autoconvert_filter_new(std::unique_ptr<PreparedFilter> filter) noexcept
|
||||
{
|
||||
|
||||
3
src/haiku/add_resources.sh
Executable file
3
src/haiku/add_resources.sh
Executable file
@@ -0,0 +1,3 @@
|
||||
#!/bin/sh
|
||||
|
||||
cp "$2" "$1" && xres -o "$1" -- "$3" && mimeset -f "$1" || (rm -f "$1"; exit 1)
|
||||
@@ -1,18 +1,26 @@
|
||||
rc = meson.find_program('rc')
|
||||
xres = meson.find_program('xres')
|
||||
haiku_conf = configuration_data()
|
||||
haiku_conf.set('VERSION', meson.project_version())
|
||||
|
||||
splitted_version = meson.project_version().split('~')[0].split('.')
|
||||
haiku_conf.set('VERSION_MAJOR', splitted_version[0])
|
||||
haiku_conf.set('VERSION_MINOR', splitted_version.get(1, '0'))
|
||||
haiku_conf.set('VERSION_REVISION', splitted_version.get(2, '0'))
|
||||
haiku_conf.set('VERSION_EXTRA', splitted_version.get(3, '0'))
|
||||
|
||||
mpd_rdef = configure_file(
|
||||
input: 'mpd.rdef.in',
|
||||
output: 'mpd.rdef',
|
||||
configuration: haiku_conf,
|
||||
)
|
||||
|
||||
rc = find_program('rc')
|
||||
xres = find_program('xres')
|
||||
|
||||
rsrc = custom_target(
|
||||
'mpd.rsrc',
|
||||
output: 'mpd.rsrc',
|
||||
input: 'mpd.rdef',
|
||||
input: mpd_rdef,
|
||||
command: [rc, '-o', '@OUTPUT@', '@INPUT@'],
|
||||
)
|
||||
|
||||
custom_target(
|
||||
'mpd.rsrc',
|
||||
output: 'mpd',
|
||||
input: [mpd, rsrc],
|
||||
command: [xres, '-o', '@OUTPUT@', '--', '@INPUT@'],
|
||||
install: true,
|
||||
install_dir: get_option('bindir'),
|
||||
)
|
||||
addres = files('add_resources.sh')
|
||||
|
||||
@@ -2,7 +2,15 @@ resource app_signature "application/x-vnd.MusicPD";
|
||||
|
||||
resource app_flags B_BACKGROUND_APP;
|
||||
|
||||
// TODO: resource app_version {};
|
||||
resource app_version {
|
||||
major = @VERSION_MAJOR@,
|
||||
middle = @VERSION_MINOR@,
|
||||
minor = @VERSION_REVISION@,
|
||||
variety = B_APPV_ALPHA,
|
||||
internal = @VERSION_EXTRA@,
|
||||
short_info = "Music Player Daemon @VERSION@",
|
||||
long_info = "Music Player Daemon @VERSION@ ©The Music Player Daemon Project"
|
||||
};
|
||||
|
||||
resource vector_icon {
|
||||
$"6E6369661F050102031604BEE29BBEC5403EC540BEE29B4A10004A10000001C6"
|
||||
@@ -306,9 +306,8 @@ input_curl_init(EventLoop &event_loop, const ConfigBlock &block)
|
||||
{
|
||||
try {
|
||||
curl_init = new CurlInit(event_loop);
|
||||
} catch (const std::runtime_error &e) {
|
||||
LogError(e);
|
||||
throw PluginUnavailable(e.what());
|
||||
} catch (...) {
|
||||
std::throw_with_nested(PluginUnavailable("CURL initialization failed"));
|
||||
}
|
||||
|
||||
const auto version_info = curl_version_info(CURLVERSION_FIRST);
|
||||
|
||||
@@ -28,8 +28,6 @@
|
||||
|
||||
#include <libsmbclient.h>
|
||||
|
||||
#include <stdexcept>
|
||||
|
||||
class SmbclientInputStream final : public InputStream {
|
||||
SMBCCTX *ctx;
|
||||
int fd;
|
||||
@@ -72,9 +70,8 @@ input_smbclient_init(EventLoop &, const ConfigBlock &)
|
||||
{
|
||||
try {
|
||||
SmbclientInit();
|
||||
} catch (const std::runtime_error &e) {
|
||||
// TODO: use std::throw_with_nested()?
|
||||
throw PluginUnavailable(e.what());
|
||||
} catch (...) {
|
||||
std::throw_with_nested(PluginUnavailable("libsmbclient initialization failed"));
|
||||
}
|
||||
|
||||
// TODO: create one global SMBCCTX here?
|
||||
|
||||
@@ -43,6 +43,8 @@
|
||||
#include <stdexcept>
|
||||
#include <utility>
|
||||
|
||||
#include <cstdio>
|
||||
|
||||
class CdromDrive {
|
||||
cdrom_drive_t *drv = nullptr;
|
||||
|
||||
|
||||
@@ -1,4 +1,17 @@
|
||||
# Since version 0.49.0 Meson has native libgcrypt dependency support, which has
|
||||
# the advantage over find_library() as it uses libgcrypt-config to query the
|
||||
# required linker flags.
|
||||
# However, we still need to use find_library() first, to prevent Meson
|
||||
# falsly assuming a target libgcrypt is available in case there is no
|
||||
# libgcrypt-config entry in the cross file and libgcrypt is installed on the
|
||||
# host. In this case, Meson will falsly use the native libgcrypt-config and
|
||||
# will falsly assume it has found the gcrypt library for the target.
|
||||
#
|
||||
# See: https://github.com/MusicPlayerDaemon/MPD/pull/495
|
||||
gcrypt_dep = c_compiler.find_library('gcrypt', required: get_option('qobuz'))
|
||||
if gcrypt_dep.found()
|
||||
gcrypt_dep = dependency('libgcrypt')
|
||||
endif
|
||||
if not gcrypt_dep.found()
|
||||
subdir_done()
|
||||
endif
|
||||
|
||||
@@ -20,6 +20,7 @@
|
||||
#include "OggVisitor.hxx"
|
||||
|
||||
#include <stdexcept>
|
||||
#include <utility>
|
||||
|
||||
void
|
||||
OggVisitor::EndStream()
|
||||
@@ -51,7 +52,13 @@ OggVisitor::ReadNextPage()
|
||||
inline void
|
||||
OggVisitor::HandlePacket(const ogg_packet &packet)
|
||||
{
|
||||
const bool _post_seek = std::exchange(post_seek, false);
|
||||
|
||||
if (packet.b_o_s) {
|
||||
if (_post_seek)
|
||||
/* ignore the BOS packet after seeking */
|
||||
return;
|
||||
|
||||
EndStream();
|
||||
has_stream = true;
|
||||
OnOggBeginning(packet);
|
||||
@@ -97,4 +104,6 @@ OggVisitor::PostSeek()
|
||||
|
||||
/* find the next Ogg page and feed it into the stream */
|
||||
sync.ExpectPageSeekIn(stream);
|
||||
|
||||
post_seek = true;
|
||||
}
|
||||
|
||||
@@ -39,6 +39,14 @@ class OggVisitor {
|
||||
|
||||
bool has_stream = false;
|
||||
|
||||
/**
|
||||
* This is true after seeking; its one-time effect is to
|
||||
* ignore the BOS packet, just in case we have been seeking to
|
||||
* the beginning of the file, because that would disrupt
|
||||
* playback.
|
||||
*/
|
||||
bool post_seek = false;
|
||||
|
||||
public:
|
||||
explicit OggVisitor(Reader &reader)
|
||||
:sync(reader), stream(0) {}
|
||||
|
||||
@@ -1,7 +1,20 @@
|
||||
libflac_dep = dependency('flac', version: '>= 1.2', required: get_option('flac'))
|
||||
libopus_dep = dependency('opus', required: get_option('opus'))
|
||||
libvorbis_dep = dependency('vorbis', required: get_option('vorbis'))
|
||||
libvorbisidec_dep = dependency('vorbisidec', required: get_option('tremor'))
|
||||
|
||||
if get_option('tremor').enabled()
|
||||
# no libvorbis if Tremor was explicitly enabled
|
||||
libvorbis_dep = dependency('', required: false)
|
||||
else
|
||||
libvorbis_dep = dependency('vorbis', required: get_option('vorbis'))
|
||||
endif
|
||||
|
||||
if libvorbis_dep.found()
|
||||
# no Tremor if libvorbis is used
|
||||
libvorbisidec_dep = dependency('', required: false)
|
||||
else
|
||||
# attempt to auto-detect Tremor only if libvorbis was disabled or not found
|
||||
libvorbisidec_dep = dependency('vorbisidec', required: get_option('tremor'))
|
||||
endif
|
||||
|
||||
if get_option('vorbis').enabled() and get_option('tremor').enabled()
|
||||
error('Cannot build both, the Vorbis decoder AND the Tremor (Vorbis fixed-point) decoder')
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (C) 2012-2017 Max Kellermann <max.kellermann@gmail.com>
|
||||
* Copyright 2012-2019 Max Kellermann <max.kellermann@gmail.com>
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions
|
||||
@@ -315,6 +315,13 @@ SocketDescriptor::SetTcpDeferAccept(const int &seconds) noexcept
|
||||
return SetOption(IPPROTO_TCP, TCP_DEFER_ACCEPT, &seconds, sizeof(seconds));
|
||||
}
|
||||
|
||||
bool
|
||||
SocketDescriptor::SetTcpUserTimeout(const unsigned &milliseconds) noexcept
|
||||
{
|
||||
return SetOption(IPPROTO_TCP, TCP_USER_TIMEOUT,
|
||||
&milliseconds, sizeof(milliseconds));
|
||||
}
|
||||
|
||||
bool
|
||||
SocketDescriptor::SetV6Only(bool value) noexcept
|
||||
{
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (C) 2012-2017 Max Kellermann <max.kellermann@gmail.com>
|
||||
* Copyright 2012-2019 Max Kellermann <max.kellermann@gmail.com>
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions
|
||||
@@ -188,6 +188,12 @@ public:
|
||||
bool SetCork(bool value=true) noexcept;
|
||||
|
||||
bool SetTcpDeferAccept(const int &seconds) noexcept;
|
||||
|
||||
/**
|
||||
* Setter for TCP_USER_TIMEOUT.
|
||||
*/
|
||||
bool SetTcpUserTimeout(const unsigned &milliseconds) noexcept;
|
||||
|
||||
bool SetV6Only(bool value) noexcept;
|
||||
|
||||
/**
|
||||
|
||||
@@ -35,7 +35,7 @@ socket_bind_listen(int domain, int type, int protocol,
|
||||
throw MakeSocketError("Failed to create socket");
|
||||
|
||||
#ifdef HAVE_UN
|
||||
if (domain == AF_UNIX) {
|
||||
if (domain == AF_LOCAL) {
|
||||
/* Prevent access until right permissions are set */
|
||||
fchmod(fd.Get(), 0);
|
||||
}
|
||||
|
||||
@@ -32,7 +32,7 @@ class SocketAddress;
|
||||
/**
|
||||
* Creates a socket listening on the specified address. This is a
|
||||
* shortcut for socket(), bind() and listen().
|
||||
* When a unix socket is created (domain == AF_UNIX), its
|
||||
* When a local socket is created (domain == AF_LOCAL), its
|
||||
* permissions will be stripped down to prevent unauthorized
|
||||
* access. The caller is responsible to apply proper permissions
|
||||
* at a later point.
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (C) 2012-2017 Max Kellermann <max.kellermann@gmail.com>
|
||||
* Copyright 2012-2019 Max Kellermann <max.kellermann@gmail.com>
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions
|
||||
@@ -29,6 +29,7 @@
|
||||
|
||||
#include "config.h"
|
||||
#include "StaticSocketAddress.hxx"
|
||||
#include "util/StringView.hxx"
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
@@ -50,6 +51,16 @@ StaticSocketAddress::operator=(SocketAddress other) noexcept
|
||||
return *this;
|
||||
}
|
||||
|
||||
#ifdef HAVE_UN
|
||||
|
||||
StringView
|
||||
StaticSocketAddress::GetLocalRaw() const noexcept
|
||||
{
|
||||
return SocketAddress(*this).GetLocalRaw();
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
#ifdef HAVE_TCP
|
||||
|
||||
bool
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (C) 2012-2017 Max Kellermann <max.kellermann@gmail.com>
|
||||
* Copyright 2012-2019 Max Kellermann <max.kellermann@gmail.com>
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions
|
||||
@@ -66,14 +66,6 @@ public:
|
||||
return reinterpret_cast<const struct sockaddr *>(&address);
|
||||
}
|
||||
|
||||
struct sockaddr *GetAddress() noexcept {
|
||||
return reinterpret_cast<struct sockaddr *>(&address);
|
||||
}
|
||||
|
||||
const struct sockaddr *GetAddress() const noexcept {
|
||||
return reinterpret_cast<const struct sockaddr *>(&address);
|
||||
}
|
||||
|
||||
constexpr size_type GetCapacity() const noexcept {
|
||||
return sizeof(address);
|
||||
}
|
||||
@@ -109,6 +101,14 @@ public:
|
||||
address.ss_family = AF_UNSPEC;
|
||||
}
|
||||
|
||||
#ifdef HAVE_UN
|
||||
/**
|
||||
* @see SocketAddress::GetLocalRaw()
|
||||
*/
|
||||
gcc_pure
|
||||
StringView GetLocalRaw() const noexcept;
|
||||
#endif
|
||||
|
||||
#ifdef HAVE_TCP
|
||||
/**
|
||||
* Extract the port number. Returns 0 if not applicable.
|
||||
|
||||
@@ -106,7 +106,7 @@ ToString(SocketAddress address) noexcept
|
||||
{
|
||||
#ifdef HAVE_UN
|
||||
if (address.GetFamily() == AF_LOCAL)
|
||||
/* return path of UNIX domain sockets */
|
||||
/* return path of local socket */
|
||||
return LocalAddressToString(*(const sockaddr_un *)address.GetAddress(),
|
||||
address.GetSize());
|
||||
#endif
|
||||
|
||||
@@ -140,9 +140,6 @@ HaikuOutput::Close() noexcept
|
||||
|
||||
HaikuOutput::~HaikuOutput()
|
||||
{
|
||||
delete_sem(new_buffer);
|
||||
delete_sem(buffer_done);
|
||||
|
||||
finalize_application();
|
||||
}
|
||||
|
||||
|
||||
@@ -581,8 +581,8 @@ PulseOutput::SetupStream(const pa_sample_spec &ss)
|
||||
|
||||
/* WAVE-EX is been adopted as the speaker map for most media files */
|
||||
pa_channel_map chan_map;
|
||||
pa_channel_map_init_auto(&chan_map, ss.channels,
|
||||
PA_CHANNEL_MAP_WAVEEX);
|
||||
pa_channel_map_init_extend(&chan_map, ss.channels,
|
||||
PA_CHANNEL_MAP_WAVEEX);
|
||||
stream = pa_stream_new(context, name, &ss, &chan_map);
|
||||
if (stream == nullptr)
|
||||
throw MakePulseError(context,
|
||||
|
||||
@@ -76,7 +76,10 @@ if is_darwin
|
||||
audiounit_dep = declare_dependency(
|
||||
link_args: [
|
||||
'-framework', 'AudioUnit', '-framework', 'CoreAudio', '-framework', 'CoreServices',
|
||||
]
|
||||
],
|
||||
dependencies: [
|
||||
boost_dep,
|
||||
],
|
||||
)
|
||||
else
|
||||
audiounit_dep = dependency('', required: false)
|
||||
|
||||
@@ -229,6 +229,14 @@ SlesOutput::Open(AudioFormat &audio_format)
|
||||
SL_ANDROID_KEY_STREAM_TYPE,
|
||||
&stream_type,
|
||||
sizeof(stream_type));
|
||||
|
||||
/* MPD doesn't care much about latency, so let's
|
||||
configure power saving mode */
|
||||
SLuint32 performance_mode = SL_ANDROID_PERFORMANCE_POWER_SAVING;
|
||||
(*android_config)->SetConfiguration(android_config,
|
||||
SL_ANDROID_KEY_PERFORMANCE_MODE,
|
||||
&performance_mode,
|
||||
sizeof(performance_mode));
|
||||
}
|
||||
|
||||
result = play_object.Realize(false);
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*/
|
||||
|
||||
#include "LocateUri.hxx"
|
||||
#include "PlaylistAny.hxx"
|
||||
#include "PlaylistStream.hxx"
|
||||
#include "PlaylistMapper.hxx"
|
||||
@@ -25,17 +26,26 @@
|
||||
#include "config.h"
|
||||
|
||||
std::unique_ptr<SongEnumerator>
|
||||
playlist_open_any(const char *uri,
|
||||
playlist_open_any(const LocatedUri &located_uri,
|
||||
#ifdef ENABLE_DATABASE
|
||||
const Storage *storage,
|
||||
#endif
|
||||
Mutex &mutex)
|
||||
{
|
||||
return uri_has_scheme(uri)
|
||||
? playlist_open_remote(uri, mutex)
|
||||
: playlist_mapper_open(uri,
|
||||
switch (located_uri.type) {
|
||||
case LocatedUri::Type::ABSOLUTE:
|
||||
return playlist_open_remote(located_uri.canonical_uri, mutex);
|
||||
|
||||
case LocatedUri::Type::PATH:
|
||||
return playlist_open_path(located_uri.path, mutex);
|
||||
|
||||
case LocatedUri::Type::RELATIVE:
|
||||
return playlist_mapper_open(located_uri.canonical_uri,
|
||||
#ifdef ENABLE_DATABASE
|
||||
storage,
|
||||
#endif
|
||||
mutex);
|
||||
}
|
||||
|
||||
gcc_unreachable();
|
||||
}
|
||||
|
||||
@@ -34,7 +34,7 @@ class Storage;
|
||||
* music or playlist directory.
|
||||
*/
|
||||
std::unique_ptr<SongEnumerator>
|
||||
playlist_open_any(const char *uri,
|
||||
playlist_open_any(const LocatedUri &located_uri,
|
||||
#ifdef ENABLE_DATABASE
|
||||
const Storage *storage,
|
||||
#endif
|
||||
|
||||
@@ -18,6 +18,7 @@
|
||||
*/
|
||||
|
||||
#include "config.h"
|
||||
#include "LocateUri.hxx"
|
||||
#include "PlaylistQueue.hxx"
|
||||
#include "PlaylistAny.hxx"
|
||||
#include "PlaylistSong.hxx"
|
||||
@@ -63,7 +64,7 @@ playlist_load_into_queue(const char *uri, SongEnumerator &e,
|
||||
}
|
||||
|
||||
void
|
||||
playlist_open_into_queue(const char *uri,
|
||||
playlist_open_into_queue(const LocatedUri &uri,
|
||||
unsigned start_index, unsigned end_index,
|
||||
playlist &dest, PlayerControl &pc,
|
||||
const SongLoader &loader)
|
||||
@@ -78,7 +79,7 @@ playlist_open_into_queue(const char *uri,
|
||||
if (playlist == nullptr)
|
||||
throw PlaylistError::NoSuchList();
|
||||
|
||||
playlist_load_into_queue(uri, *playlist,
|
||||
playlist_load_into_queue(uri.canonical_uri, *playlist,
|
||||
start_index, end_index,
|
||||
dest, pc, loader);
|
||||
}
|
||||
|
||||
@@ -49,7 +49,7 @@ playlist_load_into_queue(const char *uri, SongEnumerator &e,
|
||||
* play queue.
|
||||
*/
|
||||
void
|
||||
playlist_open_into_queue(const char *uri,
|
||||
playlist_open_into_queue(const LocatedUri &uri,
|
||||
unsigned start_index, unsigned end_index,
|
||||
playlist &dest, PlayerControl &pc,
|
||||
const SongLoader &loader);
|
||||
|
||||
@@ -18,6 +18,7 @@
|
||||
*/
|
||||
|
||||
#include "config.h"
|
||||
#include "LocateUri.hxx"
|
||||
#include "Print.hxx"
|
||||
#include "PlaylistAny.hxx"
|
||||
#include "PlaylistSong.hxx"
|
||||
@@ -55,7 +56,7 @@ playlist_provider_print(Response &r,
|
||||
bool
|
||||
playlist_file_print(Response &r, Partition &partition,
|
||||
const SongLoader &loader,
|
||||
const char *uri, bool detail)
|
||||
const LocatedUri &uri, bool detail)
|
||||
{
|
||||
Mutex mutex;
|
||||
|
||||
@@ -71,6 +72,6 @@ playlist_file_print(Response &r, Partition &partition,
|
||||
if (playlist == nullptr)
|
||||
return false;
|
||||
|
||||
playlist_provider_print(r, loader, uri, *playlist, detail);
|
||||
playlist_provider_print(r, loader, uri.canonical_uri, *playlist, detail);
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -34,6 +34,6 @@ struct Partition;
|
||||
bool
|
||||
playlist_file_print(Response &r, Partition &partition,
|
||||
const SongLoader &loader,
|
||||
const char *uri, bool detail);
|
||||
const LocatedUri &uri, bool detail);
|
||||
|
||||
#endif
|
||||
|
||||
@@ -34,7 +34,7 @@
|
||||
#include <FLAC/metadata.h>
|
||||
|
||||
class FlacPlaylist final : public SongEnumerator {
|
||||
const char *const uri;
|
||||
const std::string uri;
|
||||
|
||||
FLAC__StreamMetadata *const cuesheet;
|
||||
const unsigned sample_rate;
|
||||
|
||||
@@ -22,13 +22,10 @@
|
||||
|
||||
#include <assert.h>
|
||||
|
||||
inline bool
|
||||
bool
|
||||
StringFilter::MatchWithoutNegation(const char *s) const noexcept
|
||||
{
|
||||
#if !CLANG_CHECK_VERSION(3,6)
|
||||
/* disabled on clang due to -Wtautological-pointer-compare */
|
||||
assert(s != nullptr);
|
||||
#endif
|
||||
|
||||
#ifdef HAVE_PCRE
|
||||
if (regex)
|
||||
|
||||
@@ -105,7 +105,9 @@ public:
|
||||
gcc_pure
|
||||
bool Match(const char *s) const noexcept;
|
||||
|
||||
private:
|
||||
/**
|
||||
* Like Match(), but ignore the "negated" flag.
|
||||
*/
|
||||
gcc_pure
|
||||
bool MatchWithoutNegation(const char *s) const noexcept;
|
||||
};
|
||||
|
||||
@@ -35,43 +35,41 @@ TagSongFilter::ToExpression() const noexcept
|
||||
}
|
||||
|
||||
bool
|
||||
TagSongFilter::MatchNN(const TagItem &item) const noexcept
|
||||
TagSongFilter::Match(const Tag &tag) const noexcept
|
||||
{
|
||||
return (type == TAG_NUM_OF_ITEM_TYPES || item.type == type) &&
|
||||
filter.Match(item.value);
|
||||
}
|
||||
|
||||
bool
|
||||
TagSongFilter::MatchNN(const Tag &tag) const noexcept
|
||||
{
|
||||
bool visited_types[TAG_NUM_OF_ITEM_TYPES];
|
||||
std::fill_n(visited_types, size_t(TAG_NUM_OF_ITEM_TYPES), false);
|
||||
bool visited_types[TAG_NUM_OF_ITEM_TYPES]{};
|
||||
|
||||
for (const auto &i : tag) {
|
||||
visited_types[i.type] = true;
|
||||
|
||||
if (MatchNN(i))
|
||||
return true;
|
||||
if ((type == TAG_NUM_OF_ITEM_TYPES || i.type == type) &&
|
||||
filter.MatchWithoutNegation(i.value))
|
||||
return !filter.IsNegated();
|
||||
}
|
||||
|
||||
if (type < TAG_NUM_OF_ITEM_TYPES && !visited_types[type]) {
|
||||
/* if the specified tag is not present, try the
|
||||
fallback tags */
|
||||
|
||||
bool result = false;
|
||||
if (ApplyTagFallback(type,
|
||||
[&](TagType tag2) {
|
||||
if (!visited_types[tag2])
|
||||
return false;
|
||||
if (ApplyTagFallback(type, [&](TagType tag2) {
|
||||
if (!visited_types[tag2])
|
||||
/* we already know that this tag type
|
||||
isn't present, so let's bail out
|
||||
without checking again */
|
||||
return false;
|
||||
|
||||
for (const auto &item : tag) {
|
||||
if (item.type == tag2 &&
|
||||
filter.Match(item.value)) {
|
||||
result = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
for (const auto &item : tag) {
|
||||
if (item.type == tag2 &&
|
||||
filter.MatchWithoutNegation(item.value)) {
|
||||
result = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}))
|
||||
return result;
|
||||
return true;
|
||||
}))
|
||||
return result != filter.IsNegated();
|
||||
|
||||
/* If the search critieron was not visited during the
|
||||
sweep through the song's tag, it means this field
|
||||
@@ -80,14 +78,14 @@ TagSongFilter::MatchNN(const Tag &tag) const noexcept
|
||||
then it's a match as well and we should return
|
||||
true. */
|
||||
if (filter.empty())
|
||||
return true;
|
||||
return !filter.IsNegated();
|
||||
}
|
||||
|
||||
return false;
|
||||
return filter.IsNegated();
|
||||
}
|
||||
|
||||
bool
|
||||
TagSongFilter::Match(const LightSong &song) const noexcept
|
||||
{
|
||||
return MatchNN(song.tag);
|
||||
return Match(song.tag);
|
||||
}
|
||||
|
||||
@@ -68,8 +68,7 @@ public:
|
||||
bool Match(const LightSong &song) const noexcept override;
|
||||
|
||||
private:
|
||||
bool MatchNN(const Tag &tag) const noexcept;
|
||||
bool MatchNN(const TagItem &tag) const noexcept;
|
||||
bool Match(const Tag &tag) const noexcept;
|
||||
};
|
||||
|
||||
#endif
|
||||
|
||||
@@ -79,6 +79,11 @@ public:
|
||||
return FileDescriptor::CreatePipe(r, w);
|
||||
}
|
||||
|
||||
static bool CreatePipeNonBlock(UniqueFileDescriptor &r,
|
||||
UniqueFileDescriptor &w) noexcept {
|
||||
return FileDescriptor::CreatePipeNonBlock(r, w);
|
||||
}
|
||||
|
||||
static bool CreatePipe(FileDescriptor &r, FileDescriptor &w) noexcept;
|
||||
#endif
|
||||
|
||||
|
||||
@@ -22,6 +22,11 @@
|
||||
|
||||
#include <utility>
|
||||
|
||||
/**
|
||||
* Invoke the given function for all fallback tags of the given
|
||||
* #TagType, until the function returns true (or until there are no
|
||||
* more fallback tags).
|
||||
*/
|
||||
template<typename F>
|
||||
bool
|
||||
ApplyTagFallback(TagType type, F &&f) noexcept
|
||||
@@ -43,6 +48,11 @@ ApplyTagFallback(TagType type, F &&f) noexcept
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Invoke the given function for the given #TagType and all of its
|
||||
* fallback tags, until the function returns true (or until there are
|
||||
* no more fallback tags).
|
||||
*/
|
||||
template<typename F>
|
||||
bool
|
||||
ApplyTagWithFallback(TagType type, F &&f) noexcept
|
||||
|
||||
@@ -1,4 +1,11 @@
|
||||
threads_dep = dependency('threads')
|
||||
if is_windows
|
||||
# avoid the unused libwinpthread-1.dll dependency on Windows; MPD
|
||||
# doesn't use the pthread API on Windows, but this is what Meson
|
||||
# unhelpfully detects for us
|
||||
threads_dep = []
|
||||
else
|
||||
threads_dep = dependency('threads')
|
||||
endif
|
||||
|
||||
conf.set('HAVE_PTHREAD_SETNAME_NP', compiler.has_function('pthread_setname_np', dependencies: threads_dep))
|
||||
|
||||
|
||||
45
test/MakeTag.hxx
Normal file
45
test/MakeTag.hxx
Normal file
@@ -0,0 +1,45 @@
|
||||
/*
|
||||
* Copyright 2003-2019 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 "tag/Builder.hxx"
|
||||
#include "tag/Tag.hxx"
|
||||
#include "util/Compiler.h"
|
||||
|
||||
inline void
|
||||
BuildTag(gcc_unused TagBuilder &tag) noexcept
|
||||
{
|
||||
}
|
||||
|
||||
template<typename... Args>
|
||||
inline void
|
||||
BuildTag(TagBuilder &tag, TagType type, const char *value,
|
||||
Args&&... args) noexcept
|
||||
{
|
||||
tag.AddItem(type, value);
|
||||
BuildTag(tag, std::forward<Args>(args)...);
|
||||
}
|
||||
|
||||
template<typename... Args>
|
||||
inline Tag
|
||||
MakeTag(Args&&... args) noexcept
|
||||
{
|
||||
TagBuilder tag;
|
||||
BuildTag(tag, std::forward<Args>(args)...);
|
||||
return tag.Commit();
|
||||
}
|
||||
@@ -165,8 +165,8 @@ public:
|
||||
return GetCommand();
|
||||
}
|
||||
|
||||
void SubmitReplayGain(const ReplayGainInfo *) {}
|
||||
void SubmitMixRamp(MixRampInfo &&) {}
|
||||
void SubmitReplayGain(const ReplayGainInfo *) override {}
|
||||
void SubmitMixRamp(MixRampInfo &&) override {}
|
||||
};
|
||||
|
||||
void
|
||||
|
||||
163
test/TestTagSongFilter.cxx
Normal file
163
test/TestTagSongFilter.cxx
Normal file
@@ -0,0 +1,163 @@
|
||||
/*
|
||||
* Copyright 2003-2019 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 "MakeTag.hxx"
|
||||
#include "song/TagSongFilter.hxx"
|
||||
#include "song/LightSong.hxx"
|
||||
#include "tag/Type.h"
|
||||
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
static bool
|
||||
InvokeFilter(const TagSongFilter &f, const Tag &tag) noexcept
|
||||
{
|
||||
return f.Match(LightSong("dummy", tag));
|
||||
}
|
||||
|
||||
TEST(TagSongFilter, Basic)
|
||||
{
|
||||
const TagSongFilter f(TAG_TITLE,
|
||||
StringFilter("needle", false, false, false));
|
||||
|
||||
EXPECT_TRUE(InvokeFilter(f, MakeTag(TAG_TITLE, "needle")));
|
||||
EXPECT_TRUE(InvokeFilter(f, MakeTag(TAG_TITLE, "foo", TAG_TITLE, "needle")));
|
||||
EXPECT_TRUE(InvokeFilter(f, MakeTag(TAG_TITLE, "needle", TAG_TITLE, "foo")));
|
||||
EXPECT_TRUE(InvokeFilter(f, MakeTag(TAG_ARTIST, "foo", TAG_TITLE, "needle", TAG_ALBUM, "bar")));
|
||||
|
||||
EXPECT_FALSE(InvokeFilter(f, MakeTag()));
|
||||
EXPECT_FALSE(InvokeFilter(f, MakeTag(TAG_TITLE, "foo")));
|
||||
EXPECT_FALSE(InvokeFilter(f, MakeTag(TAG_TITLE, "foo", TAG_TITLE, "bar")));
|
||||
EXPECT_FALSE(InvokeFilter(f, MakeTag(TAG_TITLE, "foo", TAG_ARTIST, "needle")));
|
||||
EXPECT_FALSE(InvokeFilter(f, MakeTag(TAG_TITLE, "FOOneedleBAR")));
|
||||
}
|
||||
|
||||
/**
|
||||
* Test with empty string. This matches tags where the given tag type
|
||||
* does not exist.
|
||||
*/
|
||||
TEST(TagSongFilter, Empty)
|
||||
{
|
||||
const TagSongFilter f(TAG_TITLE,
|
||||
StringFilter("", false, false, false));
|
||||
|
||||
EXPECT_TRUE(InvokeFilter(f, MakeTag()));
|
||||
|
||||
EXPECT_FALSE(InvokeFilter(f, MakeTag(TAG_TITLE, "foo")));
|
||||
EXPECT_FALSE(InvokeFilter(f, MakeTag(TAG_TITLE, "foo", TAG_TITLE, "bar")));
|
||||
}
|
||||
|
||||
TEST(TagSongFilter, Substring)
|
||||
{
|
||||
const TagSongFilter f(TAG_TITLE,
|
||||
StringFilter("needle", false, true, false));
|
||||
|
||||
EXPECT_TRUE(InvokeFilter(f, MakeTag(TAG_TITLE, "needle")));
|
||||
EXPECT_TRUE(InvokeFilter(f, MakeTag(TAG_TITLE, "needleBAR")));
|
||||
EXPECT_TRUE(InvokeFilter(f, MakeTag(TAG_TITLE, "FOOneedle")));
|
||||
EXPECT_TRUE(InvokeFilter(f, MakeTag(TAG_TITLE, "FOOneedleBAR")));
|
||||
|
||||
EXPECT_FALSE(InvokeFilter(f, MakeTag()));
|
||||
EXPECT_FALSE(InvokeFilter(f, MakeTag(TAG_TITLE, "eedle")));
|
||||
}
|
||||
|
||||
TEST(TagSongFilter, Negated)
|
||||
{
|
||||
const TagSongFilter f(TAG_TITLE,
|
||||
StringFilter("needle", false, false, true));
|
||||
|
||||
EXPECT_TRUE(InvokeFilter(f, MakeTag()));
|
||||
EXPECT_FALSE(InvokeFilter(f, MakeTag(TAG_TITLE, "needle")));
|
||||
EXPECT_TRUE(InvokeFilter(f, MakeTag(TAG_TITLE, "foo")));
|
||||
}
|
||||
|
||||
/**
|
||||
* Combine the "Empty" and "Negated" tests.
|
||||
*/
|
||||
TEST(TagSongFilter, EmptyNegated)
|
||||
{
|
||||
const TagSongFilter f(TAG_TITLE,
|
||||
StringFilter("", false, false, true));
|
||||
|
||||
EXPECT_FALSE(InvokeFilter(f, MakeTag()));
|
||||
EXPECT_TRUE(InvokeFilter(f, MakeTag(TAG_TITLE, "foo")));
|
||||
}
|
||||
|
||||
/**
|
||||
* Negation with multiple tag values.
|
||||
*/
|
||||
TEST(TagSongFilter, MultiNegated)
|
||||
{
|
||||
const TagSongFilter f(TAG_TITLE,
|
||||
StringFilter("needle", false, false, true));
|
||||
|
||||
EXPECT_TRUE(InvokeFilter(f, MakeTag(TAG_TITLE, "foo", TAG_TITLE, "bar")));
|
||||
EXPECT_FALSE(InvokeFilter(f, MakeTag(TAG_TITLE, "needle", TAG_TITLE, "bar")));
|
||||
EXPECT_FALSE(InvokeFilter(f, MakeTag(TAG_TITLE, "foo", TAG_TITLE, "needle")));
|
||||
}
|
||||
|
||||
/**
|
||||
* Check whether fallback tags work, e.g. AlbumArtist falls back to
|
||||
* just Artist if there is no AlbumArtist.
|
||||
*/
|
||||
TEST(TagSongFilter, Fallback)
|
||||
{
|
||||
const TagSongFilter f(TAG_ALBUM_ARTIST,
|
||||
StringFilter("needle", false, false, false));
|
||||
|
||||
EXPECT_TRUE(InvokeFilter(f, MakeTag(TAG_ALBUM_ARTIST, "needle")));
|
||||
EXPECT_TRUE(InvokeFilter(f, MakeTag(TAG_ARTIST, "needle")));
|
||||
|
||||
EXPECT_FALSE(InvokeFilter(f, MakeTag()));
|
||||
EXPECT_FALSE(InvokeFilter(f, MakeTag(TAG_ALBUM_ARTIST, "foo")));
|
||||
EXPECT_FALSE(InvokeFilter(f, MakeTag(TAG_ARTIST, "foo")));
|
||||
|
||||
/* no fallback, thus the Artist tag isn't used and this must
|
||||
be a mismatch */
|
||||
EXPECT_FALSE(InvokeFilter(f, MakeTag(TAG_ARTIST, "needle", TAG_ALBUM_ARTIST, "foo")));
|
||||
}
|
||||
|
||||
/**
|
||||
* Combine the "Empty" and "Fallback" tests.
|
||||
*/
|
||||
TEST(TagSongFilter, EmptyFallback)
|
||||
{
|
||||
const TagSongFilter f(TAG_ALBUM_ARTIST,
|
||||
StringFilter("", false, false, false));
|
||||
|
||||
EXPECT_TRUE(InvokeFilter(f, MakeTag()));
|
||||
|
||||
EXPECT_FALSE(InvokeFilter(f, MakeTag(TAG_ALBUM_ARTIST, "foo")));
|
||||
EXPECT_FALSE(InvokeFilter(f, MakeTag(TAG_ARTIST, "foo")));
|
||||
}
|
||||
|
||||
/**
|
||||
* Combine the "Negated" and "Fallback" tests.
|
||||
*/
|
||||
TEST(TagSongFilter, NegatedFallback)
|
||||
{
|
||||
const TagSongFilter f(TAG_ALBUM_ARTIST,
|
||||
StringFilter("needle", false, false, true));
|
||||
|
||||
EXPECT_TRUE(InvokeFilter(f, MakeTag()));
|
||||
EXPECT_TRUE(InvokeFilter(f, MakeTag(TAG_ALBUM_ARTIST, "foo")));
|
||||
EXPECT_FALSE(InvokeFilter(f, MakeTag(TAG_ALBUM_ARTIST, "needle")));
|
||||
EXPECT_TRUE(InvokeFilter(f, MakeTag(TAG_ARTIST, "foo")));
|
||||
EXPECT_FALSE(InvokeFilter(f, MakeTag(TAG_ARTIST, "needle")));
|
||||
EXPECT_TRUE(InvokeFilter(f, MakeTag(TAG_ARTIST, "needle", TAG_ALBUM_ARTIST, "foo")));
|
||||
}
|
||||
@@ -20,16 +20,6 @@ gtest_dep = declare_dependency(
|
||||
|
||||
subdir('net')
|
||||
|
||||
executable(
|
||||
'ParseSongFilter',
|
||||
'ParseSongFilter.cxx',
|
||||
include_directories: inc,
|
||||
dependencies: [
|
||||
song_dep,
|
||||
pcm_dep,
|
||||
],
|
||||
)
|
||||
|
||||
executable(
|
||||
'read_conf',
|
||||
'read_conf.cxx',
|
||||
@@ -211,6 +201,33 @@ if zlib_dep.found()
|
||||
)
|
||||
endif
|
||||
|
||||
#
|
||||
# Filter
|
||||
#
|
||||
|
||||
executable(
|
||||
'ParseSongFilter',
|
||||
'ParseSongFilter.cxx',
|
||||
include_directories: inc,
|
||||
dependencies: [
|
||||
song_dep,
|
||||
pcm_dep,
|
||||
],
|
||||
)
|
||||
|
||||
test(
|
||||
'TestSongFilter',
|
||||
executable(
|
||||
'TestSongFilter',
|
||||
'TestTagSongFilter.cxx',
|
||||
include_directories: inc,
|
||||
dependencies: [
|
||||
song_dep,
|
||||
gtest_dep,
|
||||
],
|
||||
)
|
||||
)
|
||||
|
||||
#
|
||||
# Neighbor
|
||||
#
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
* Unit tests for playlist_check_translate_song().
|
||||
*/
|
||||
|
||||
#include "MakeTag.hxx"
|
||||
#include "playlist/PlaylistSong.hxx"
|
||||
#include "song/DetachedSong.hxx"
|
||||
#include "SongLoader.hxx"
|
||||
@@ -38,28 +39,6 @@ uri_supported_scheme(const char *uri) noexcept
|
||||
static constexpr auto music_directory = PATH_LITERAL("/music");
|
||||
static Storage *storage;
|
||||
|
||||
static void
|
||||
BuildTag(gcc_unused TagBuilder &tag)
|
||||
{
|
||||
}
|
||||
|
||||
template<typename... Args>
|
||||
static void
|
||||
BuildTag(TagBuilder &tag, TagType type, const char *value, Args&&... args)
|
||||
{
|
||||
tag.AddItem(type, value);
|
||||
BuildTag(tag, std::forward<Args>(args)...);
|
||||
}
|
||||
|
||||
template<typename... Args>
|
||||
static Tag
|
||||
MakeTag(Args&&... args)
|
||||
{
|
||||
TagBuilder tag;
|
||||
BuildTag(tag, std::forward<Args>(args)...);
|
||||
return tag.Commit();
|
||||
}
|
||||
|
||||
static Tag
|
||||
MakeTag1a()
|
||||
{
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
windows_conf = configuration_data()
|
||||
windows_conf.set('VERSION', meson.project_version())
|
||||
|
||||
splitted_version = meson.project_version().split('.')
|
||||
splitted_version = meson.project_version().split('~')[0].split('.')
|
||||
windows_conf.set('VERSION_MAJOR', splitted_version[0])
|
||||
windows_conf.set('VERSION_MINOR', splitted_version.get(1, '0'))
|
||||
windows_conf.set('VERSION_REVISION', splitted_version.get(2, '0'))
|
||||
|
||||
Reference in New Issue
Block a user