Compare commits
92 Commits
Author | SHA1 | Date | |
---|---|---|---|
![]() |
cabcbb059d | ||
![]() |
5e21b2db3c | ||
![]() |
3a0d6d96c1 | ||
![]() |
f39d2d33c0 | ||
![]() |
ead3dc6a92 | ||
![]() |
7d814cc899 | ||
![]() |
f5b4606c09 | ||
![]() |
d6dbf64efb | ||
![]() |
8d18b4c24b | ||
![]() |
fe8621906d | ||
![]() |
b4fcbdb235 | ||
![]() |
f4b5a28596 | ||
![]() |
6cbd77fc57 | ||
![]() |
1bc78e9f2c | ||
![]() |
cb6282e0a7 | ||
![]() |
f6941f9a44 | ||
![]() |
d2eb4df8fc | ||
![]() |
df33a898d7 | ||
![]() |
325c7b8e8b | ||
![]() |
380656d8c9 | ||
![]() |
9111bc2c21 | ||
![]() |
37b54179d8 | ||
![]() |
511826763a | ||
![]() |
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 |
.travis.ymlNEWS
android
doc
meson.buildpython/build
src
CommandLine.cxxLocateUri.cxxLocateUri.hxxPlaylistFile.cxxSongLoader.cxx
command
config
db
decoder
event
BufferedSocket.cxxBufferedSocket.hxxFullyBufferedSocket.hxxServerSocket.cxxServerSocket.hxxSocketMonitor.cxx
filter
haiku
input
lib
neighbor
plugins
net
IPv4Address.hxxIPv6Address.hxxSocketDescriptor.cxxSocketDescriptor.hxxSocketUtil.cxxSocketUtil.hxxStaticSocketAddress.cxxStaticSocketAddress.hxxToString.cxx
output
plugins
playlist
song
system
tag
thread
zeroconf
systemd
test
win32/res
@@ -9,7 +9,7 @@ matrix:
|
||||
sources:
|
||||
- ubuntu-toolchain-r-test
|
||||
- sourceline: 'ppa:mhier/libboost-latest'
|
||||
- sourceline: 'ppa:saiarcot895/chromium-dev' # for ninja-build
|
||||
- sourceline: 'ppa:mstipicevic/ninja-build-1-7-2'
|
||||
- sourceline: 'ppa:deadsnakes/ppa' # for Python 3.7 (required by Meson)
|
||||
packages:
|
||||
- g++-6
|
||||
@@ -34,7 +34,7 @@ matrix:
|
||||
sources:
|
||||
- ubuntu-toolchain-r-test
|
||||
- sourceline: 'ppa:mhier/libboost-latest'
|
||||
- sourceline: 'ppa:saiarcot895/chromium-dev' # for ninja-build
|
||||
- sourceline: 'ppa:mstipicevic/ninja-build-1-7-2'
|
||||
- sourceline: 'ppa:deadsnakes/ppa' # for Python 3.7 (required by Meson)
|
||||
packages:
|
||||
- g++-8
|
||||
|
46
NEWS
46
NEWS
@@ -1,3 +1,49 @@
|
||||
ver 0.21.8 (2019/04/23)
|
||||
* input
|
||||
- smbclient: download to buffer instead of throttling transfer
|
||||
* output
|
||||
- httpd: add missing mutex lock
|
||||
- httpd: fix use-after-free bug
|
||||
* playlist
|
||||
- soundcloud: fix "Unsupported URI scheme" (0.21.6 regression)
|
||||
* fix Bonjour bug
|
||||
* fix build failure with GCC 9
|
||||
* fix build failure with -Ddatabase=false
|
||||
* systemd: add user socket unit
|
||||
* doc: "list file" is deprecated
|
||||
|
||||
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="30"
|
||||
android:versionName="0.21.8">
|
||||
|
||||
<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.8'
|
||||
# The full version, including alpha/beta/rc tags.
|
||||
release = version
|
||||
|
||||
|
@@ -96,10 +96,8 @@ When the whole patch series is finished, convert stgit patches to git commits:
|
||||
Submitting Patches
|
||||
==================
|
||||
|
||||
Send your patches to the mailing list:
|
||||
Email: `mpd-devel <mpd-devel@musicpd.org>`_
|
||||
|
||||
:program:`git pull` requests are preferred.
|
||||
Submit pull requests on GitHub:
|
||||
https://github.com/MusicPlayerDaemon/MPD/pulls
|
||||
|
||||
Development Tools
|
||||
=================
|
||||
|
@@ -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
|
||||
@@ -409,9 +414,10 @@ Querying :program:`MPD`'s status
|
||||
- ``songid``: playlist songid of the current song stopped on or playing
|
||||
- ``nextsong`` [#since_0_15]_: playlist song number of the next song to be played
|
||||
- ``nextsongid`` [#since_0_15]_: playlist songid of the next song to be played
|
||||
- ``time``: total time elapsed (of current playing/paused song)
|
||||
- ``time``: total time elapsed (of current playing/paused song) in seconds
|
||||
(deprecated, use ``elapsed`` instead)
|
||||
- ``elapsed`` [#since_0_16]_: Total time elapsed within the current song, but with higher resolution.
|
||||
- ``elapsed`` [#since_0_16]_: Total time elapsed within the
|
||||
current song in seconds, but with higher resolution.
|
||||
- ``duration`` [#since_0_20]_: Duration of the current song in seconds.
|
||||
- ``bitrate``: instantaneous bitrate in kbps
|
||||
- ``xfade``: ``crossfade`` in seconds
|
||||
@@ -432,7 +438,7 @@ Querying :program:`MPD`'s status
|
||||
- ``albums``: number of albums
|
||||
- ``songs``: number of songs
|
||||
- ``uptime``: daemon uptime in seconds
|
||||
- ``db_playtime``: sum of all song times in the db
|
||||
- ``db_playtime``: sum of all song times in the database in seconds
|
||||
- ``db_update``: last db update in UNIX time
|
||||
- ``playtime``: time length of music played
|
||||
|
||||
@@ -594,7 +600,7 @@ Whenever possible, ids should be used.
|
||||
Deletes the song ``SONGID`` from the
|
||||
playlist
|
||||
|
||||
:command:`move {FROM} [{START:END} | {TO}]`
|
||||
:command:`move [{FROM} | {START:END}] {TO}`
|
||||
Moves the song at ``FROM`` or range of songs
|
||||
at ``START:END`` [#since_0_15]_ to ``TO``
|
||||
in the playlist.
|
||||
@@ -714,7 +720,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}`
|
||||
@@ -853,8 +861,7 @@ The music database
|
||||
:command:`list {TYPE} {FILTER} [group {GROUPTYPE}]`
|
||||
Lists unique tags values of the specified type.
|
||||
``TYPE`` can be any tag supported by
|
||||
:program:`MPD` or
|
||||
*file*.
|
||||
:program:`MPD`.
|
||||
|
||||
Additional arguments may specify a :ref:`filter <filter_syntax>`.
|
||||
The *group* keyword may be used
|
||||
@@ -865,6 +872,10 @@ The music database
|
||||
|
||||
list album group albumartist
|
||||
|
||||
``list file`` was implemented in an early :program:`MPD` version,
|
||||
but does not appear to make a lot of sense. It still works (to
|
||||
avoid breaking compatibility), but is deprecated.
|
||||
|
||||
.. _command_listall:
|
||||
|
||||
:command:`listall [URI]`
|
||||
@@ -924,7 +935,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).
|
||||
|
||||
@@ -1046,7 +1057,8 @@ Stickers
|
||||
"Stickers" [#since_0_15]_ are pieces of
|
||||
information attached to existing
|
||||
:program:`MPD` objects (e.g. song files,
|
||||
directories, albums). Clients can create arbitrary name/value
|
||||
directories, albums; but currently, they are only implemented for
|
||||
song). Clients can create arbitrary name/value
|
||||
pairs. :program:`MPD` itself does not assume
|
||||
any special meaning in them.
|
||||
|
||||
@@ -1215,7 +1227,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::
|
||||
|
||||
|
29
meson.build
29
meson.build
@@ -1,8 +1,8 @@
|
||||
project(
|
||||
'mpd',
|
||||
['c', 'cpp'],
|
||||
version: '0.21.5',
|
||||
meson_version: '>= 0.47.2',
|
||||
version: '0.21.8',
|
||||
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 = [
|
||||
@@ -367,8 +367,10 @@ basic_dep = declare_dependency(
|
||||
|
||||
if enable_database
|
||||
subdir('src/storage')
|
||||
subdir('src/db')
|
||||
else
|
||||
storage_glue_dep = dependency('', required: false)
|
||||
endif
|
||||
subdir('src/db')
|
||||
|
||||
if neighbor_glue_dep.found()
|
||||
sources += 'src/command/NeighborCommands.cxx'
|
||||
@@ -390,6 +392,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 +402,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 +454,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,12 +387,12 @@ libnfs = AutotoolsProject(
|
||||
|
||||
'--disable-utils', '--disable-examples',
|
||||
],
|
||||
base='libnfs-libnfs-3.0.0',
|
||||
base='libnfs-libnfs-4.0.0',
|
||||
autoreconf=True,
|
||||
)
|
||||
|
||||
boost = BoostProject(
|
||||
'http://downloads.sourceforge.net/project/boost/boost/1.69.0/boost_1_69_0.tar.bz2',
|
||||
'8f32d4617390d1c2d16f26a27ab60d97807b35440d45891fa340fc2648b04406',
|
||||
'http://downloads.sourceforge.net/project/boost/boost/1.70.0/boost_1_70_0.tar.bz2',
|
||||
'430ae8354789de4fd19ee52f3b1f739e1fba576f0aded0897c3c2bc00fb38778',
|
||||
'include/boost/version.hpp',
|
||||
)
|
||||
|
@@ -108,17 +108,17 @@ static constexpr Domain cmdline_domain("cmdline");
|
||||
gcc_noreturn
|
||||
static void version(void)
|
||||
{
|
||||
printf("Music Player Daemon " VERSION " (%s)\n"
|
||||
printf("Music Player Daemon " VERSION " (%s)"
|
||||
"\n"
|
||||
"Copyright 2003-2007 Warren Dukes <warren.dukes@gmail.com>\n"
|
||||
"Copyright 2008-2018 Max Kellermann <max.kellermann@gmail.com>\n"
|
||||
"This is free software; see the source for copying conditions. There is NO\n"
|
||||
"warranty; not even MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n"
|
||||
"warranty; not even MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n",
|
||||
GIT_VERSION);
|
||||
|
||||
#ifdef ENABLE_DATABASE
|
||||
"\n"
|
||||
"Database plugins:\n",
|
||||
GIT_VERSION);
|
||||
printf("\n"
|
||||
"Database plugins:\n");
|
||||
|
||||
for (auto i = database_plugins; *i != nullptr; ++i)
|
||||
printf(" %s", (*i)->name);
|
||||
@@ -129,18 +129,18 @@ static void version(void)
|
||||
for (auto i = storage_plugins; *i != nullptr; ++i)
|
||||
printf(" %s", (*i)->name);
|
||||
|
||||
printf("\n"
|
||||
printf("\n");
|
||||
#endif
|
||||
|
||||
#ifdef ENABLE_NEIGHBOR_PLUGINS
|
||||
"\n"
|
||||
printf("\n"
|
||||
"Neighbor plugins:\n");
|
||||
for (auto i = neighbor_plugins; *i != nullptr; ++i)
|
||||
printf(" %s", (*i)->name);
|
||||
|
||||
printf("\n"
|
||||
#endif
|
||||
|
||||
printf("\n"
|
||||
"\n"
|
||||
"Decoders plugins:\n");
|
||||
|
||||
|
@@ -55,14 +55,26 @@ LocateFileUri(const char *uri, const Client *client
|
||||
}
|
||||
|
||||
static LocatedUri
|
||||
LocateAbsoluteUri(const char *uri
|
||||
LocateAbsoluteUri(UriPluginKind kind, const char *uri
|
||||
#ifdef ENABLE_DATABASE
|
||||
, const Storage *storage
|
||||
#endif
|
||||
)
|
||||
{
|
||||
if (!uri_supported_scheme(uri))
|
||||
throw std::runtime_error("Unsupported URI scheme");
|
||||
switch (kind) {
|
||||
case UriPluginKind::INPUT:
|
||||
case UriPluginKind::STORAGE: // TODO: separate check for storage plugins
|
||||
if (!uri_supported_scheme(uri))
|
||||
throw std::runtime_error("Unsupported URI scheme");
|
||||
break;
|
||||
|
||||
case UriPluginKind::PLAYLIST:
|
||||
/* for now, no validation for playlist URIs; this is
|
||||
more complicated because there are three ways to
|
||||
identify which plugin to use: URI scheme, filename
|
||||
suffix and MIME type */
|
||||
break;
|
||||
}
|
||||
|
||||
#ifdef ENABLE_DATABASE
|
||||
if (storage != nullptr) {
|
||||
@@ -76,7 +88,8 @@ LocateAbsoluteUri(const char *uri
|
||||
}
|
||||
|
||||
LocatedUri
|
||||
LocateUri(const char *uri, const Client *client
|
||||
LocateUri(UriPluginKind kind,
|
||||
const char *uri, const Client *client
|
||||
#ifdef ENABLE_DATABASE
|
||||
, const Storage *storage
|
||||
#endif
|
||||
@@ -100,7 +113,7 @@ LocateUri(const char *uri, const Client *client
|
||||
#endif
|
||||
);
|
||||
else if (uri_has_scheme(uri))
|
||||
return LocateAbsoluteUri(uri
|
||||
return LocateAbsoluteUri(kind, uri
|
||||
#ifdef ENABLE_DATABASE
|
||||
, storage
|
||||
#endif
|
||||
|
@@ -41,6 +41,12 @@ class Client;
|
||||
class Storage;
|
||||
#endif
|
||||
|
||||
enum class UriPluginKind {
|
||||
INPUT,
|
||||
STORAGE,
|
||||
PLAYLIST,
|
||||
};
|
||||
|
||||
struct LocatedUri {
|
||||
enum class Type {
|
||||
/**
|
||||
@@ -84,7 +90,8 @@ struct LocatedUri {
|
||||
* that feature is disabled if this parameter is nullptr
|
||||
*/
|
||||
LocatedUri
|
||||
LocateUri(const char *uri, const Client *client
|
||||
LocateUri(UriPluginKind kind,
|
||||
const char *uri, const Client *client
|
||||
#ifdef ENABLE_DATABASE
|
||||
, const Storage *storage
|
||||
#endif
|
||||
|
@@ -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;
|
||||
|
@@ -94,7 +94,8 @@ SongLoader::LoadSong(const char *uri_utf8) const
|
||||
assert(uri_utf8 != nullptr);
|
||||
#endif
|
||||
|
||||
const auto located_uri = LocateUri(uri_utf8, client
|
||||
const auto located_uri = LocateUri(UriPluginKind::INPUT,
|
||||
uri_utf8, client
|
||||
#ifdef ENABLE_DATABASE
|
||||
, storage
|
||||
#endif
|
||||
|
@@ -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,
|
||||
|
@@ -218,7 +218,7 @@ handle_read_comments(Client &client, Request args, Response &r)
|
||||
|
||||
const char *const uri = args.front();
|
||||
|
||||
const auto located_uri = LocateUri(uri, &client
|
||||
const auto located_uri = LocateUri(UriPluginKind::INPUT, uri, &client
|
||||
#ifdef ENABLE_DATABASE
|
||||
, nullptr
|
||||
#endif
|
||||
@@ -331,7 +331,7 @@ handle_album_art(Client &client, Request args, Response &r)
|
||||
const char *uri = args.front();
|
||||
size_t offset = args.ParseUnsigned(1);
|
||||
|
||||
const auto located_uri = LocateUri(uri, &client
|
||||
const auto located_uri = LocateUri(UriPluginKind::INPUT, uri, &client
|
||||
#ifdef ENABLE_DATABASE
|
||||
, nullptr
|
||||
#endif
|
||||
|
@@ -99,7 +99,7 @@ handle_listfiles(Client &client, Request args, Response &r)
|
||||
/* default is root directory */
|
||||
const auto uri = args.GetOptional(0, "");
|
||||
|
||||
const auto located_uri = LocateUri(uri, &client
|
||||
const auto located_uri = LocateUri(UriPluginKind::STORAGE, uri, &client
|
||||
#ifdef ENABLE_DATABASE
|
||||
, nullptr
|
||||
#endif
|
||||
@@ -219,7 +219,7 @@ handle_lsinfo(Client &client, Request args, Response &r)
|
||||
compatibility, work around this here */
|
||||
uri = "";
|
||||
|
||||
const auto located_uri = LocateUri(uri, &client
|
||||
const auto located_uri = LocateUri(UriPluginKind::INPUT, uri, &client
|
||||
#ifdef ENABLE_DATABASE
|
||||
, nullptr
|
||||
#endif
|
||||
|
@@ -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,43 @@ 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(UriPluginKind::PLAYLIST, 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(UriPluginKind::PLAYLIST, args.front(),
|
||||
&client
|
||||
#ifdef ENABLE_DATABASE
|
||||
, nullptr
|
||||
#endif
|
||||
);
|
||||
|
||||
if (playlist_file_print(r, client.GetPartition(), SongLoader(client),
|
||||
name, false))
|
||||
@@ -93,7 +117,12 @@ 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(UriPluginKind::PLAYLIST, args.front(),
|
||||
&client
|
||||
#ifdef ENABLE_DATABASE
|
||||
, nullptr
|
||||
#endif
|
||||
);
|
||||
|
||||
if (playlist_file_print(r, client.GetPartition(), SongLoader(client),
|
||||
name, true))
|
||||
|
@@ -83,7 +83,8 @@ handle_add(Client &client, Request args, Response &r)
|
||||
here */
|
||||
uri = "";
|
||||
|
||||
const auto located_uri = LocateUri(uri, &client
|
||||
const auto located_uri = LocateUri(UriPluginKind::INPUT, uri,
|
||||
&client
|
||||
#ifdef ENABLE_DATABASE
|
||||
, nullptr
|
||||
#endif
|
||||
|
@@ -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.
|
||||
*
|
||||
|
@@ -9,6 +9,11 @@ db_api_dep = declare_dependency(
|
||||
link_with: db_api,
|
||||
)
|
||||
|
||||
if not enable_database
|
||||
db_glue_dep = db_api_dep
|
||||
subdir_done()
|
||||
endif
|
||||
|
||||
subdir('plugins')
|
||||
|
||||
db_glue_sources = [
|
||||
|
@@ -568,7 +568,8 @@ ProxyDatabase::OnSocketReady(gcc_unused unsigned flags) noexcept
|
||||
if (!is_idle) {
|
||||
// TODO: can this happen?
|
||||
IdleMonitor::Schedule();
|
||||
return false;
|
||||
SocketMonitor::Cancel();
|
||||
return true;
|
||||
}
|
||||
|
||||
unsigned idle = (unsigned)mpd_recv_idle(connection, false);
|
||||
@@ -586,7 +587,8 @@ ProxyDatabase::OnSocketReady(gcc_unused unsigned flags) noexcept
|
||||
idle_received |= idle;
|
||||
is_idle = false;
|
||||
IdleMonitor::Schedule();
|
||||
return false;
|
||||
SocketMonitor::Cancel();
|
||||
return true;
|
||||
}
|
||||
|
||||
void
|
||||
|
@@ -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)
|
||||
|
@@ -110,15 +110,9 @@ BufferedSocket::OnSocketReady(unsigned flags) noexcept
|
||||
if (flags & READ) {
|
||||
assert(!input.IsFull());
|
||||
|
||||
if (!ReadToBuffer())
|
||||
if (!ReadToBuffer() || !ResumeInput())
|
||||
return false;
|
||||
|
||||
if (!ResumeInput())
|
||||
/* we must return "true" here or
|
||||
SocketMonitor::Dispatch() will call
|
||||
Cancel() on a freed object */
|
||||
return true;
|
||||
|
||||
if (!input.IsFull())
|
||||
ScheduleRead();
|
||||
}
|
||||
|
@@ -46,6 +46,11 @@ public:
|
||||
using SocketMonitor::Close;
|
||||
|
||||
private:
|
||||
/**
|
||||
* @return the number of bytes read from the socket, 0 if the
|
||||
* socket isn't ready for reading, -1 on error (the socket has
|
||||
* been closed and probably destructed)
|
||||
*/
|
||||
ssize_t DirectRead(void *data, size_t length) noexcept;
|
||||
|
||||
/**
|
||||
|
@@ -45,6 +45,11 @@ public:
|
||||
}
|
||||
|
||||
private:
|
||||
/**
|
||||
* @return the number of bytes written to the socket, 0 if the
|
||||
* socket isn't ready for writing, -1 on error (the socket has
|
||||
* been closed and probably destructed)
|
||||
*/
|
||||
ssize_t DirectWrite(const void *data, size_t length) noexcept;
|
||||
|
||||
protected:
|
||||
|
@@ -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
|
||||
|
@@ -33,8 +33,8 @@ SocketMonitor::Dispatch(unsigned flags) noexcept
|
||||
{
|
||||
flags &= GetScheduledFlags();
|
||||
|
||||
if (flags != 0 && !OnSocketReady(flags) && IsDefined())
|
||||
Cancel();
|
||||
if (flags != 0)
|
||||
OnSocketReady(flags);
|
||||
}
|
||||
|
||||
SocketMonitor::~SocketMonitor() noexcept
|
||||
|
@@ -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);
|
||||
|
@@ -22,14 +22,13 @@
|
||||
#include "lib/smbclient/Mutex.hxx"
|
||||
#include "../InputStream.hxx"
|
||||
#include "../InputPlugin.hxx"
|
||||
#include "../MaybeBufferedInputStream.hxx"
|
||||
#include "PluginUnavailable.hxx"
|
||||
#include "system/Error.hxx"
|
||||
#include "util/ASCII.hxx"
|
||||
|
||||
#include <libsmbclient.h>
|
||||
|
||||
#include <stdexcept>
|
||||
|
||||
class SmbclientInputStream final : public InputStream {
|
||||
SMBCCTX *ctx;
|
||||
int fd;
|
||||
@@ -72,9 +71,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?
|
||||
@@ -115,8 +113,9 @@ input_smbclient_open(const char *uri,
|
||||
throw MakeErrno(e, "smbc_fstat() failed");
|
||||
}
|
||||
|
||||
return std::make_unique<SmbclientInputStream>(uri, mutex,
|
||||
ctx, fd, st);
|
||||
return std::make_unique<MaybeBufferedInputStream>
|
||||
(std::make_unique<SmbclientInputStream>(uri, mutex,
|
||||
ctx, fd, st));
|
||||
}
|
||||
|
||||
size_t
|
||||
|
@@ -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')
|
||||
|
@@ -150,8 +150,6 @@ ReadServers(NeighborExplorer::List &list, int fd)
|
||||
smbc_dirent *e;
|
||||
while ((e = smbc_readdir(fd)) != nullptr)
|
||||
ReadEntry(list, *e);
|
||||
|
||||
smbc_closedir(fd);
|
||||
}
|
||||
|
||||
static void
|
||||
|
@@ -168,7 +168,7 @@ public:
|
||||
}
|
||||
|
||||
constexpr operator SocketAddress() const noexcept {
|
||||
return SocketAddress((const struct sockaddr *)&address,
|
||||
return SocketAddress((const struct sockaddr *)(const void *)&address,
|
||||
sizeof(address));
|
||||
}
|
||||
|
||||
|
@@ -135,7 +135,7 @@ public:
|
||||
}
|
||||
|
||||
constexpr operator SocketAddress() const noexcept {
|
||||
return SocketAddress((const struct sockaddr *)&address,
|
||||
return SocketAddress((const struct sockaddr *)(const void *)&address,
|
||||
sizeof(address));
|
||||
}
|
||||
|
||||
|
@@ -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,
|
||||
|
@@ -154,7 +154,7 @@ HttpdClient::SendResponse() noexcept
|
||||
FormatWarning(httpd_output_domain,
|
||||
"failed to write to client: %s",
|
||||
(const char *)msg);
|
||||
Close();
|
||||
LockClose();
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -428,6 +428,7 @@ void
|
||||
HttpdClient::OnSocketError(std::exception_ptr ep) noexcept
|
||||
{
|
||||
LogError(ep);
|
||||
LockClose();
|
||||
}
|
||||
|
||||
void
|
||||
|
@@ -142,6 +142,8 @@ public:
|
||||
|
||||
/**
|
||||
* Frees the client and removes it from the server's client list.
|
||||
*
|
||||
* Caller must lock the mutex.
|
||||
*/
|
||||
void Close() noexcept;
|
||||
|
||||
|
@@ -208,10 +208,15 @@ public:
|
||||
return HasClients();
|
||||
}
|
||||
|
||||
/**
|
||||
* Caller must lock the mutex.
|
||||
*/
|
||||
void AddClient(UniqueSocketDescriptor fd) noexcept;
|
||||
|
||||
/**
|
||||
* Removes a client from the httpd_output.clients linked list.
|
||||
*
|
||||
* Caller must lock the mutex.
|
||||
*/
|
||||
void RemoveClient(HttpdClient &client) noexcept;
|
||||
|
||||
@@ -239,10 +244,14 @@ public:
|
||||
|
||||
/**
|
||||
* Broadcasts data from the encoder to all clients.
|
||||
*
|
||||
* Mutext must not be locked.
|
||||
*/
|
||||
void BroadcastFromEncoder();
|
||||
|
||||
/**
|
||||
* Mutext must not be locked.
|
||||
*
|
||||
* Throws #std::runtime_error on error.
|
||||
*/
|
||||
void EncodeAndPlay(const void *chunk, size_t size);
|
||||
@@ -251,6 +260,9 @@ public:
|
||||
|
||||
size_t Play(const void *chunk, size_t size) override;
|
||||
|
||||
/**
|
||||
* Mutext must not be locked.
|
||||
*/
|
||||
void CancelAllClients() noexcept;
|
||||
|
||||
void Cancel() noexcept override;
|
||||
|
@@ -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))
|
||||
|
||||
|
@@ -50,7 +50,7 @@ protected:
|
||||
/* virtual methods from class SocketMonitor */
|
||||
bool OnSocketReady(gcc_unused unsigned flags) noexcept override {
|
||||
DNSServiceProcessResult(service_ref);
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
|
@@ -1,5 +1,5 @@
|
||||
[Socket]
|
||||
ListenStream=/run/mpd/socket
|
||||
ListenStream=%t/mpd/socket
|
||||
ListenStream=6600
|
||||
Backlog=5
|
||||
KeepAlive=true
|
||||
|
@@ -3,6 +3,12 @@ if systemd_user_unit_dir == ''
|
||||
systemd_user_unit_dir = join_paths(get_option('prefix'), 'lib', 'systemd', 'user')
|
||||
endif
|
||||
|
||||
# copy the system socket unit to the "user" directory
|
||||
install_data(
|
||||
join_paths('..', 'system', 'mpd.socket'),
|
||||
install_dir: systemd_user_unit_dir,
|
||||
)
|
||||
|
||||
configure_file(
|
||||
input: 'mpd.service.in',
|
||||
output: 'mpd.service',
|
||||
|
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