Compare commits

..

69 Commits

Author SHA1 Message Date
Max Kellermann
c67372f8af release v0.21.25 2020-07-06 21:41:53 +02:00
Max Kellermann
00789de7d4 db/upnp/Object: root nodes are allowed to omit parent_id and name
This fixes compatibility with Plex DLNA.

Closes https://github.com/MusicPlayerDaemon/MPD/issues/851
2020-07-06 21:36:30 +02:00
Max Kellermann
5ece9685c2 PluginUnavailable: backport class PluginUnconfigured from master
Stop bothering people about the Tidal/Qobuz plugins.
2020-07-06 21:08:22 +02:00
Max Kellermann
e7c5a42821 Log: add Log() and LogFormat() overloads with std::exception_ptr
Make LogError()/FormatError() wrappers for those.  Now we can log
exceptions with a lower level.
2020-07-06 21:08:04 +02:00
Max Kellermann
36e6079c57 Log: make LogLevel the first parameter
Prepare for templated functions.
2020-07-06 21:07:26 +02:00
Max Kellermann
e5f23678ca Log: use GetFullMessage() to print exceptions
Print all nested exceptions on a single line to avoid confusion.
2020-07-06 21:07:16 +02:00
Max Kellermann
749ad7cd83 PluginUnavailable: inherit the base class constructor 2020-07-06 20:40:25 +02:00
Max Kellermann
0b59f4eaee doc/plugins.rst: merge redundant nfs:// documentation 2020-07-06 20:37:58 +02:00
Max Kellermann
402663de74 doc/plugins.rst: more markup 2020-07-06 20:34:04 +02:00
Max Kellermann
eaa66c7ee3 doc/plugins.rst: add smb:// with password example
Closes https://github.com/MusicPlayerDaemon/MPD/issues/864
2020-07-06 20:32:41 +02:00
Max Kellermann
996714d6ff doc/plugins.rst: more markup 2020-07-06 20:32:11 +02:00
Max Kellermann
fe48e5596f command/storage: automatically scan new mounts
Closes https://github.com/MusicPlayerDaemon/MPD/issues/841
2020-07-06 20:23:41 +02:00
Max Kellermann
d7744d2b8e command/storage: check if storage is already mounted
Mounting one storage URI twice on different mount points can lead to
conflicts with the database cache file, and it doesn't make a lot of
sense.

But most importantly, our udisks storage plugin will unmount the disk
from the kernel VFS, and if two exist, they will compete with each
others.  We could (and should) fix this in the udisks storage plugin,
but for now, this workaround is good enough (and useful).
2020-07-06 18:02:47 +02:00
Max Kellermann
33ee35ab92 command/storage: check if mount point is busy
When mounting something over a directory that is already a mount
point, CompositeStorage::Mount() silently overwrites the previously
mounted storage, disposing it.  After that, SimpleDatabase::Mount()
will fail and handle_mount() will roll back the
CompositeStorage::Mount() command, effectively unmounting what was
there before (and also leaking memory).

Closes https://github.com/MusicPlayerDaemon/MPD/issues/918
2020-07-06 17:49:38 +02:00
Max Kellermann
5b291ff768 db/update/Walk: pass concatenated .mpdignore URI to storage.MapUTF8()
Fixes the "Unrecognized URI" error with the udisks storage plugin,
which is caused by the kludge in UdisksStorage::MapUTF8().
2020-07-06 17:19:38 +02:00
Max Kellermann
39d6816a6d neighbor/upnp: roll back changes if DoOpen() fails 2020-07-06 16:23:58 +02:00
Max Kellermann
6517b2d2ac neighbor/upnp: remove D-Bus filter and match in Close()
Fixes use-after-free crash bug during MPD shutdown.
2020-07-06 16:15:18 +02:00
Max Kellermann
bfdf13dca3 decoder/Plugin: allow scan_{file,stream}() to throw
Bug #915 is about an I/O exception thrown where none was allowed,
leading to crash via std::terminate().  However, instead of catching
and logging the error inside the decoder plugin, it should be able to
propagate the I/O error to the MPD core, so MPD can avoid trying other
decoder plugins.

Closes https://github.com/MusicPlayerDaemon/MPD/issues/915
2020-07-06 14:13:34 +02:00
Max Kellermann
daefc61aa4 output/osx: postpone start until the end of Play()
Wait until there is data in the ring buffer.
2020-07-02 15:26:38 +02:00
Max Kellermann
6fed6e50e4 output/osx: merge some duplicate code 2020-07-02 15:25:51 +02:00
Max Kellermann
bc9e074822 output/osx: postpone start until the first Play() call
Wait until there is some data; don't let our render callback be
invoked without any data.
2020-07-02 15:21:54 +02:00
Max Kellermann
8047102542 output/osx: don't restart AudioUnit at the end of Cancel()
We shouldn't restart the AudioUnit while the ring buffer is empty, or
else our render callback may emit noise.

Closes https://github.com/MusicPlayerDaemon/MPD/issues/771
2020-07-02 15:20:43 +02:00
Max Kellermann
fe5b81e180 output/osx: check started in Close() and Cancel() 2020-07-02 15:19:40 +02:00
Max Kellermann
f032925c2d output/osx: add started flag
This will keep track of AudioOutputUnitStart() and
AudioOutputUnitStop().  This will provide some separation between "not
(yet) (re)started" and "paused".
2020-07-02 15:18:37 +02:00
Max Kellermann
8125a5dddb output/osx: don't uninitialize AudioUnit if restart fails
This shall be done by Close(), which will be called automatically
after an error.
2020-07-02 15:10:03 +02:00
Max Kellermann
154170e475 output/osx: clear pause flag only after successful AudioOutputUnitStart() 2020-07-02 15:08:59 +02:00
Max Kellermann
fb83936feb apple/AudioUnit: add AudioUnitSetPropertyT() 2020-07-02 14:59:40 +02:00
Max Kellermann
db8bf52f7d apple/AudioObject: add AudioObjectGetStringProperty() 2020-07-02 13:50:05 +02:00
Max Kellermann
756f0b8027 apple: build static library
Move build rules from src/output/plugins/meson.build
2020-07-02 13:49:54 +02:00
Max Kellermann
b1fba8d3d7 apple/AudioObject: add missing inline 2020-07-02 13:49:52 +02:00
Max Kellermann
e606044271 apple/AudioUnit: library wrapping AudioUnit*() functions 2020-07-01 23:02:22 +02:00
Max Kellermann
bcbb3371ff apple/AudioUnit: rename to AudioObject.hxx 2020-07-01 22:49:03 +02:00
Max Kellermann
de632882d1 output/osx: move code to FindAudioDeviceByName() 2020-07-01 22:48:12 +02:00
Max Kellermann
745e492d15 output/osx: use [[maybe_unused]] 2020-07-01 22:41:00 +02:00
Max Kellermann
c5dc615efe output/osx: use IsDigitASCII() 2020-07-01 22:39:54 +02:00
Max Kellermann
beeb02025e output/osx: use range-based for 2020-07-01 22:06:36 +02:00
Max Kellermann
cdf7062597 apple/AudioUnit: wrapper functions for AudioObject properties 2020-07-01 22:05:11 +02:00
Max Kellermann
346084da1e apple/Throw: new helper library replacing osx_os_status_to_cstring() 2020-07-01 22:05:11 +02:00
Max Kellermann
bbceb5eb91 output/osx: silently ignore some errors in osx_output_set_device() 2020-07-01 22:05:11 +02:00
Max Kellermann
90d85319c2 apple/ErrorRef: new library wrapping CFErrorRef 2020-07-01 22:05:10 +02:00
Max Kellermann
3d03683e7d output: use StringIsEqual() 2020-07-01 22:04:26 +02:00
Max Kellermann
d8a74802d1 apple/StringRef: new library wrapping CFStringRef 2020-07-01 22:01:53 +02:00
Max Kellermann
191919d1b1 output/osx: remove trailing newline from exception messages 2020-07-01 22:01:51 +02:00
Max Kellermann
df38e7565b util/HugeAllocator: import std::swap() 2020-07-01 21:56:58 +02:00
Max Kellermann
cb49a03fd7 util/HugeAllocator: add noexcept 2020-07-01 21:56:54 +02:00
Max Kellermann
faee5bbb78 decoder/opus: implement End Trimming (RFC7845 4.4)
Closes https://github.com/MusicPlayerDaemon/MPD/issues/867
2020-07-01 21:26:34 +02:00
Max Kellermann
7befab7e83 decoder/opus: keep track of the granulepos
Will be needed for End Trimming (RFC7845 4.4,
https://github.com/MusicPlayerDaemon/MPD/issues/867).
2020-07-01 21:21:06 +02:00
Max Kellermann
4244e61214 decoder/opus: simplify indentation in HandleAudio() 2020-07-01 21:19:52 +02:00
Max Kellermann
46eab05045 decoder/opus: allocate buffer only in the first chained song
Fixes memory leak.  That's what we get for
2020-07-01 21:07:49 +02:00
Max Kellermann
5ca137c73c decoder/opus: add API docs 2020-07-01 20:55:18 +02:00
Max Kellermann
760238fe16 decoder/opus: apply pre-skip (RFC7845 4.2)
Fixes the first part of
https://github.com/MusicPlayerDaemon/MPD/issues/867
2020-07-01 20:44:53 +02:00
Max Kellermann
a99b4abae8 decoder/OpusHead: return pre-skip 2020-07-01 17:51:07 +02:00
Max Kellermann
472881cb95 util/ByteOrder: remove redundant inline keywords from constexpr functions 2020-07-01 17:50:34 +02:00
Max Kellermann
c4efc37ad8 system/ByteOrder: move to util/ 2020-07-01 17:49:57 +02:00
Max Kellermann
691b6a236e output/osx: improve sample rate selection
The formula in osx_output_score_sample_rate() to detect multiples of
the source sample rate was broken: when given a 44.1 kHz input file,
it preferred 16 kHz over 48 kHz, because its `frac_portion(16)=0.75`
is smaller than `frac_portion(48)=0.91`.

That formula, introduced by commit 40a1ebee29, looks completely
wrong.  It doesn't do what the code comment pretends it does.

Instead of using that `frac_portion` to calculate a score, this patch
adds to the score only if `frac_portion` is nearly `0` or `1`.  This
means that the factor is nearly integer.

Closes https://github.com/MusicPlayerDaemon/MPD/issues/904
2020-07-01 17:38:08 +02:00
Max Kellermann
5c7243d3ad output/osx: make several fields const 2020-07-01 17:35:39 +02:00
Max Kellermann
44cfdff39a output/osx: make variables more local 2020-07-01 17:35:33 +02:00
Max Kellermann
5eedda691a output/osx: make more AudioObjectPropertyAddress instances static constexpr 2020-07-01 17:35:19 +02:00
Max Kellermann
a30d5e1b6a output/osx: make AudioObjectPropertyAddress variables static constexpr 2020-07-01 17:34:12 +02:00
Max Kellermann
8ef09a0a71 output/osx: don't use C99 designated initializers
Fixes `-Wpedantic`.
2020-07-01 17:34:06 +02:00
Max Kellermann
e8044663b3 output/{alsa,osx}: use ConstBuffer::empty() 2020-07-01 17:32:37 +02:00
Max Kellermann
8444c33514 output/osx: don't use variable-length arrays 2020-07-01 17:31:46 +02:00
Max Kellermann
2b7328b434 output/osx: fix coding style 2020-07-01 17:11:02 +02:00
Max Kellermann
ca705e1e37 python/build/meson.py: set BOOST_ROOT for Meson 0.54
Commit
08224dafcb
changed Meson to require BOOST_ROOT for cross builds.
2020-07-01 16:55:28 +02:00
Max Kellermann
d9f9b3df10 input/file: detect premature end of file
A bug report (https://github.com/MusicPlayerDaemon/MPD/issues/912)
suggests that on Linux, reading on `cifs` files may rarely return 0 (=
end of file) before the end of the file has really been reached.  But
that's just a theory which I need to validate, so this runtime check
shall catch this condition before the assertion in
DecoderBridge::Read() crashes MPD.  Let's see.

Closes https://github.com/MusicPlayerDaemon/MPD/issues/912
2020-07-01 15:14:27 +02:00
Max Kellermann
a43ee97746 util/UriUtil: strip credentials from smb:// URIs
Closes https://github.com/MusicPlayerDaemon/MPD/issues/910
2020-06-22 22:48:56 +02:00
Max Kellermann
43c32372e7 util/UriUtil: make schemes array static 2020-06-22 22:48:07 +02:00
Max Kellermann
5716cde1fb queue/PlaylistEdit: fix crash in SetSongIdRange() while playing
An assertion failure in UpdateQueuedSong() could trigger because the
`prev` parameter is always `nullptr`, but `queued` may be set.  And in
fact, calling UpdateQueuedSong() is only necessary when the queued
song was edited, to re-queue it with the new range.

Closes https://github.com/MusicPlayerDaemon/MPD/issues/901
2020-06-11 07:07:02 +02:00
Max Kellermann
b7a99b4a4b increment version number to 0.21.25 2020-06-11 06:29:08 +02:00
89 changed files with 1489 additions and 721 deletions

25
NEWS
View File

@@ -1,3 +1,28 @@
ver 0.21.25 (2020/07/06)
* protocol:
- fix crash when using "rangeid" while playing
* database
- simple: automatically scan new mounts
- upnp: fix compatibility with Plex DLNA
* storage
- fix disappearing mounts after mounting twice
- udisks: fix reading ".mpdignore"
* input
- file: detect premature end of file
- smbclient: don't send credentials to MPD clients
* decoder
- opus: apply pre-skip and end trimming
- opus: fix memory leak
- opus: fix crash bug
- vorbis: fix crash bug
* output
- osx: improve sample rate selection
- osx: fix noise while stopping
* neighbor
- upnp: fix crash during shutdown
* Windows/Android:
- fix Boost detection after breaking change in Meson 0.54
ver 0.21.24 (2020/06/10)
* protocol
- "tagtypes" requires no permissions

View File

@@ -2,8 +2,8 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="org.musicpd"
android:installLocation="auto"
android:versionCode="47"
android:versionName="0.21.24">
android:versionCode="48"
android:versionName="0.21.25">
<uses-sdk android:minSdkVersion="21" android:targetSdkVersion="28"/>

View File

@@ -38,7 +38,7 @@ author = 'Max Kellermann'
# built documents.
#
# The short X.Y version.
version = '0.21.24'
version = '0.21.25'
# The full version, including alpha/beta/rc tags.
release = version

View File

@@ -60,25 +60,25 @@ The default plugin which gives :program:`MPD` access to local files. It is used
curl
----
A WebDAV client using libcurl. It is used when :code:`music_directory` contains a http:// or https:// URI, for example :samp:`https://the.server/dav/`.
A WebDAV client using libcurl. It is used when :code:`music_directory`
contains a ``http://`` or ``https://`` URI, for example
:samp:`https://the.server/dav/`.
smbclient
---------
Load music files from a SMB/CIFS server. It is used when :code:`music_directory` contains a smb:// URI, for example :samp:`smb://myfileserver/Music`.
Load music files from a SMB/CIFS server. It is used when
:code:`music_directory` contains a ``smb://`` URI, for example
:samp:`smb://myfileserver/Music`.
nfs
---
Load music files from a NFS server. It is used when :code:`music_directory` contains a nfs:// URI according to RFC2224, for example :samp:`nfs://servername/path`.
Load music files from a NFS server. It is used when
:code:`music_directory` contains a ``nfs://`` URI according to
RFC2224, for example :samp:`nfs://servername/path`.
This plugin uses libnfs, which supports only NFS version 3. Since :program:`MPD` is not allowed to bind to "privileged ports", the NFS server needs to enable the "insecure" setting; example :file:`/etc/exports`:
.. code-block:: none
/srv/mp3 192.168.1.55(ro,insecure)
Don't fear: "insecure" does not mean that your NFS server is insecure. A few decades ago, people thought the concept of "privileged ports" would make network services "secure", which was a fallacy. The absence of this obsolete "security" measure means little.
See :ref:`input_nfs` for more information.
udisks
------
@@ -162,7 +162,10 @@ curl
Opens remote files or streams over HTTP using libcurl.
Note that unless overridden by the below settings (e.g. by setting them to a blank value), general curl configuration from environment variables such as http_proxy or specified in :file:`~/.curlrc` will be in effect.
Note that unless overridden by the below settings (e.g. by setting
them to a blank value), general curl configuration from environment
variables such as ``http_proxy`` or specified in :file:`~/.curlrc`
will be in effect.
.. list-table::
:widths: 20 80
@@ -182,7 +185,9 @@ Note that unless overridden by the below settings (e.g. by setting them to a bla
ffmpeg
------
Access to various network protocols implemented by the FFmpeg library: gopher://, rtp://, rtsp://, rtmp://, rtmpt://, rtmps://
Access to various network protocols implemented by the FFmpeg library:
``gopher://``, ``rtp://``, ``rtsp://``, ``rtmp://``, ``rtmpt://``,
``rtmps://``
file
----
@@ -194,30 +199,51 @@ mms
Plays streams with the MMS protocol using `libmms <https://launchpad.net/libmms>`_.
.. _input_nfs:
nfs
---
Allows :program:`MPD` to access files on NFSv3 servers without actually mounting them (i.e. in userspace, without help from the kernel's VFS layer). All URIs with the nfs:// scheme are used according to RFC2224. Example:
Allows :program:`MPD` to access files on NFS servers without actually
mounting them (i.e. with :program:`libnfs` in userspace, without help
from the kernel's VFS layer). All URIs with the ``nfs://`` scheme are
used according to RFC2224. Example:
.. code-block:: none
mpc add nfs://servername/path/filename.ogg
Note that this usually requires enabling the "insecure" flag in the server's /etc/exports file, because :program:`MPD` cannot bind to so-called "privileged" ports. Don't fear: this will not make your file server insecure; the flag was named in a time long ago when privileged ports were thought to be meaningful for security. By today's standards, NFSv3 is not secure at all, and if you believe it is, you're already doomed.
This plugin uses :program:`libnfs`, which supports only NFS version 3.
Since :program:`MPD` is not allowed to bind to so-called "privileged
ports", the NFS server needs to enable the ``insecure`` setting;
example :file:`/etc/exports`:
.. code-block:: none
/srv/mp3 192.168.1.55(ro,insecure)
Don't fear: this will not make your file server insecure; the flag was
named a time long ago when privileged ports were thought to be
meaningful for security. By today's standards, NFSv3 is not secure at
all, and if you believe it is, you're already doomed.
smbclient
---------
Allows :program:`MPD` to access files on SMB/CIFS servers (e.g. Samba or Microsoft Windows). All URIs with the smb:// scheme are used. Example:
Allows :program:`MPD` to access files on SMB/CIFS servers (e.g. Samba
or Microsoft Windows). All URIs with the ``smb://`` scheme are
used. Example:
.. code-block:: none
mpc add smb://servername/sharename/filename.ogg
mpc add smb://username:password@servername/sharename/filename.ogg
qobuz
-----
Play songs from the commercial streaming service Qobuz. It plays URLs in the form qobuz://track/ID, e.g.:
Play songs from the commercial streaming service Qobuz. It plays URLs
in the form ``qobuz://track/ID``, e.g.:
.. code-block:: none
@@ -243,7 +269,9 @@ Play songs from the commercial streaming service Qobuz. It plays URLs in the for
tidal
-----
Play songs from the commercial streaming service `Tidal <http://tidal.com/>`_. It plays URLs in the form tidal://track/ID, e.g.:
Play songs from the commercial streaming service `Tidal
<http://tidal.com/>`_. It plays URLs in the form ``tidal://track/ID``,
e.g.:
.. warning::

View File

@@ -1,7 +1,7 @@
project(
'mpd',
['c', 'cpp'],
version: '0.21.24',
version: '0.21.25',
meson_version: '>= 0.49.0',
default_options: [
'c_std=c99',
@@ -319,6 +319,8 @@ subdir('src/thread')
subdir('src/net')
subdir('src/event')
subdir('src/apple')
subdir('src/lib/dbus')
subdir('src/lib/icu')
subdir('src/lib/smbclient')

View File

@@ -91,7 +91,12 @@ def configure(toolchain, src, build, args=()):
'--cross-file', cross_file,
] + args
subprocess.check_call(configure, env=toolchain.env)
env = toolchain.env.copy()
# Meson 0.54 requires the BOOST_ROOT environment variable
env['BOOST_ROOT'] = toolchain.install_prefix
subprocess.check_call(configure, env=env)
class MesonProject(Project):
def __init__(self, url, md5, installed, configure_args=[],

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2003-2018 The Music Player Daemon Project
* Copyright 2003-2019 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
@@ -19,8 +19,7 @@
#include "LogV.hxx"
#include "util/Domain.hxx"
#include <exception>
#include "util/Exception.hxx"
#include <stdio.h>
#include <string.h>
@@ -29,20 +28,20 @@
static constexpr Domain exception_domain("exception");
void
LogFormatV(const Domain &domain, LogLevel level,
LogFormatV(LogLevel level, const Domain &domain,
const char *fmt, va_list ap) noexcept
{
char msg[1024];
vsnprintf(msg, sizeof(msg), fmt, ap);
Log(domain, level, msg);
Log(level, domain, msg);
}
void
LogFormat(const Domain &domain, LogLevel level, const char *fmt, ...) noexcept
LogFormat(LogLevel level, const Domain &domain, const char *fmt, ...) noexcept
{
va_list ap;
va_start(ap, fmt);
LogFormatV(domain, level, fmt, ap);
LogFormatV(level, domain, fmt, ap);
va_end(ap);
}
@@ -51,7 +50,7 @@ FormatDebug(const Domain &domain, const char *fmt, ...) noexcept
{
va_list ap;
va_start(ap, fmt);
LogFormatV(domain, LogLevel::DEBUG, fmt, ap);
LogFormatV(LogLevel::DEBUG, domain, fmt, ap);
va_end(ap);
}
@@ -60,7 +59,7 @@ FormatInfo(const Domain &domain, const char *fmt, ...) noexcept
{
va_list ap;
va_start(ap, fmt);
LogFormatV(domain, LogLevel::INFO, fmt, ap);
LogFormatV(LogLevel::INFO, domain, fmt, ap);
va_end(ap);
}
@@ -69,7 +68,7 @@ FormatDefault(const Domain &domain, const char *fmt, ...) noexcept
{
va_list ap;
va_start(ap, fmt);
LogFormatV(domain, LogLevel::DEFAULT, fmt, ap);
LogFormatV(LogLevel::DEFAULT, domain, fmt, ap);
va_end(ap);
}
@@ -78,7 +77,7 @@ FormatWarning(const Domain &domain, const char *fmt, ...) noexcept
{
va_list ap;
va_start(ap, fmt);
LogFormatV(domain, LogLevel::WARNING, fmt, ap);
LogFormatV(LogLevel::WARNING, domain, fmt, ap);
va_end(ap);
}
@@ -87,42 +86,24 @@ FormatError(const Domain &domain, const char *fmt, ...) noexcept
{
va_list ap;
va_start(ap, fmt);
LogFormatV(domain, LogLevel::ERROR, fmt, ap);
LogFormatV(LogLevel::ERROR, domain, fmt, ap);
va_end(ap);
}
void
LogError(const std::exception &e) noexcept
Log(LogLevel level, const std::exception &e) noexcept
{
Log(exception_domain, LogLevel::ERROR, e.what());
try {
std::rethrow_if_nested(e);
} catch (const std::exception &nested) {
LogError(nested, "nested");
} catch (...) {
Log(exception_domain, LogLevel::ERROR,
"Unrecognized nested exception");
}
Log(level, exception_domain, GetFullMessage(e).c_str());
}
void
LogError(const std::exception &e, const char *msg) noexcept
Log(LogLevel level, const std::exception &e, const char *msg) noexcept
{
FormatError(exception_domain, "%s: %s", msg, e.what());
try {
std::rethrow_if_nested(e);
} catch (const std::exception &nested) {
LogError(nested);
} catch (...) {
Log(exception_domain, LogLevel::ERROR,
"Unrecognized nested exception");
}
LogFormat(level, exception_domain, "%s: %s", msg, GetFullMessage(e).c_str());
}
void
FormatError(const std::exception &e, const char *fmt, ...) noexcept
LogFormat(LogLevel level, const std::exception &e, const char *fmt, ...) noexcept
{
char msg[1024];
va_list ap;
@@ -130,37 +111,24 @@ FormatError(const std::exception &e, const char *fmt, ...) noexcept
vsnprintf(msg, sizeof(msg), fmt, ap);
va_end(ap);
LogError(e, msg);
Log(level, e, msg);
}
void
LogError(const std::exception_ptr &ep) noexcept
Log(LogLevel level, const std::exception_ptr &ep) noexcept
{
try {
std::rethrow_exception(ep);
} catch (const std::exception &e) {
LogError(e);
} catch (...) {
Log(exception_domain, LogLevel::ERROR,
"Unrecognized exception");
}
Log(level, exception_domain, GetFullMessage(ep).c_str());
}
void
LogError(const std::exception_ptr &ep, const char *msg) noexcept
Log(LogLevel level, const std::exception_ptr &ep, const char *msg) noexcept
{
try {
std::rethrow_exception(ep);
} catch (const std::exception &e) {
LogError(e, msg);
} catch (...) {
FormatError(exception_domain,
"%s: Unrecognized exception", msg);
}
LogFormat(level, exception_domain, "%s: %s", msg,
GetFullMessage(ep).c_str());
}
void
FormatError(const std::exception_ptr &ep, const char *fmt, ...) noexcept
LogFormat(LogLevel level, const std::exception_ptr &ep, const char *fmt, ...) noexcept
{
char msg[1024];
va_list ap;
@@ -168,13 +136,13 @@ FormatError(const std::exception_ptr &ep, const char *fmt, ...) noexcept
vsnprintf(msg, sizeof(msg), fmt, ap);
va_end(ap);
LogError(ep, msg);
Log(level, ep, msg);
}
void
LogErrno(const Domain &domain, int e, const char *msg) noexcept
{
LogFormat(domain, LogLevel::ERROR, "%s: %s", msg, strerror(e));
LogFormat(LogLevel::ERROR, domain, "%s: %s", msg, strerror(e));
}
void

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2003-2018 The Music Player Daemon Project
* Copyright 2003-2019 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
@@ -28,16 +28,38 @@
class Domain;
void
Log(const Domain &domain, LogLevel level, const char *msg) noexcept;
Log(LogLevel level, const Domain &domain, const char *msg) noexcept;
gcc_printf(3,4)
void
LogFormat(const Domain &domain, LogLevel level, const char *fmt, ...) noexcept;
LogFormat(LogLevel level, const Domain &domain, const char *fmt, ...) noexcept;
void
Log(LogLevel level, const std::exception &e) noexcept;
void
Log(LogLevel level, const std::exception &e, const char *msg) noexcept;
gcc_printf(3,4)
void
LogFormat(LogLevel level, const std::exception &e,
const char *fmt, ...) noexcept;
void
Log(LogLevel level, const std::exception_ptr &ep) noexcept;
void
Log(LogLevel level, const std::exception_ptr &ep, const char *msg) noexcept;
gcc_printf(3,4)
void
LogFormat(LogLevel level, const std::exception_ptr &ep,
const char *fmt, ...) noexcept;
static inline void
LogDebug(const Domain &domain, const char *msg) noexcept
{
Log(domain, LogLevel::DEBUG, msg);
Log(LogLevel::DEBUG, domain, msg);
}
gcc_printf(2,3)
@@ -47,7 +69,7 @@ FormatDebug(const Domain &domain, const char *fmt, ...) noexcept;
static inline void
LogInfo(const Domain &domain, const char *msg) noexcept
{
Log(domain, LogLevel::INFO, msg);
Log(LogLevel::INFO, domain, msg);
}
gcc_printf(2,3)
@@ -57,7 +79,7 @@ FormatInfo(const Domain &domain, const char *fmt, ...) noexcept;
static inline void
LogDefault(const Domain &domain, const char *msg) noexcept
{
Log(domain, LogLevel::DEFAULT, msg);
Log(LogLevel::DEFAULT, domain, msg);
}
gcc_printf(2,3)
@@ -67,7 +89,7 @@ FormatDefault(const Domain &domain, const char *fmt, ...) noexcept;
static inline void
LogWarning(const Domain &domain, const char *msg) noexcept
{
Log(domain, LogLevel::WARNING, msg);
Log(LogLevel::WARNING, domain, msg);
}
gcc_printf(2,3)
@@ -77,28 +99,47 @@ FormatWarning(const Domain &domain, const char *fmt, ...) noexcept;
static inline void
LogError(const Domain &domain, const char *msg) noexcept
{
Log(domain, LogLevel::ERROR, msg);
Log(LogLevel::ERROR, domain, msg);
}
void
LogError(const std::exception &e) noexcept;
inline void
LogError(const std::exception &e) noexcept
{
Log(LogLevel::ERROR, e);
}
void
LogError(const std::exception &e, const char *msg) noexcept;
inline void
LogError(const std::exception &e, const char *msg) noexcept
{
Log(LogLevel::ERROR, e, msg);
}
gcc_printf(2,3)
void
FormatError(const std::exception &e, const char *fmt, ...) noexcept;
template<typename... Args>
inline void
FormatError(const std::exception &e, const char *fmt, Args&&... args) noexcept
{
LogFormat(LogLevel::ERROR, e, fmt, std::forward<Args>(args)...);
}
void
LogError(const std::exception_ptr &ep) noexcept;
inline void
LogError(const std::exception_ptr &ep) noexcept
{
Log(LogLevel::ERROR, ep);
}
void
LogError(const std::exception_ptr &ep, const char *msg) noexcept;
inline void
LogError(const std::exception_ptr &ep, const char *msg) noexcept
{
Log(LogLevel::ERROR, ep, msg);
}
gcc_printf(2,3)
void
FormatError(const std::exception_ptr &ep, const char *fmt, ...) noexcept;
template<typename... Args>
inline void
FormatError(const std::exception_ptr &ep,
const char *fmt, Args&&... args) noexcept
{
LogFormat(LogLevel::ERROR, ep, fmt, std::forward<Args>(args)...);
}
gcc_printf(2,3)
void

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2003-2018 The Music Player Daemon Project
* Copyright 2003-2019 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
@@ -176,7 +176,7 @@ FileLog(const Domain &domain, const char *message) noexcept
#endif /* !ANDROID */
void
Log(const Domain &domain, LogLevel level, const char *msg) noexcept
Log(LogLevel level, const Domain &domain, const char *msg) noexcept
{
#ifdef ANDROID
__android_log_print(ToAndroidLogLevel(level), "MPD",

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2003-2018 The Music Player Daemon Project
* Copyright 2003-2019 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2003-2018 The Music Player Daemon Project
* Copyright 2003-2019 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
@@ -25,7 +25,7 @@
#include <stdarg.h>
void
LogFormatV(const Domain &domain, LogLevel level,
LogFormatV(LogLevel level, const Domain &domain,
const char *fmt, va_list ap) noexcept;
#endif /* LOG_H */

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2003-2018 The Music Player Daemon Project
* Copyright 2003-2020 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
@@ -27,10 +27,20 @@
* that this plugin is unavailable. It will be disabled, and MPD can
* continue initialization.
*/
class PluginUnavailable final : public std::runtime_error {
class PluginUnavailable : public std::runtime_error {
public:
explicit PluginUnavailable(const char *msg)
:std::runtime_error(msg) {}
using std::runtime_error::runtime_error;
};
/**
* Like #PluginUnavailable, but denotes that the plugin is not
* available because it was not explicitly enabled in the
* configuration. The message may describe the necessary steps to
* enable it.
*/
class PluginUnconfigured : public PluginUnavailable {
public:
using PluginUnavailable::PluginUnavailable;
};
#endif

View File

@@ -79,17 +79,22 @@ Song::UpdateFile(Storage &storage) noexcept
TagBuilder tag_builder;
auto new_audio_format = AudioFormat::Undefined();
const auto path_fs = storage.MapFS(relative_uri.c_str());
if (path_fs.IsNull()) {
const auto absolute_uri =
storage.MapUTF8(relative_uri.c_str());
if (!tag_stream_scan(absolute_uri.c_str(), tag_builder,
&new_audio_format))
return false;
} else {
if (!ScanFileTagsWithGeneric(path_fs, tag_builder,
try {
const auto path_fs = storage.MapFS(relative_uri.c_str());
if (path_fs.IsNull()) {
const auto absolute_uri =
storage.MapUTF8(relative_uri.c_str());
if (!tag_stream_scan(absolute_uri.c_str(), tag_builder,
&new_audio_format))
return false;
return false;
} else {
if (!ScanFileTagsWithGeneric(path_fs, tag_builder,
&new_audio_format))
return false;
}
} catch (...) {
// TODO: log or propagate I/O errors?
return false;
}
mtime = info.mtime;
@@ -153,8 +158,14 @@ DetachedSong::LoadFile(Path path) noexcept
return false;
TagBuilder tag_builder;
if (!ScanFileTagsWithGeneric(path, tag_builder))
try {
if (!ScanFileTagsWithGeneric(path, tag_builder))
return false;
} catch (...) {
// TODO: log or propagate I/O errors?
return false;
}
mtime = fi.GetModificationTime();
tag_builder.Commit(tag);
@@ -173,8 +184,14 @@ DetachedSong::Update() noexcept
return LoadFile(path_fs);
} else if (IsRemote()) {
TagBuilder tag_builder;
if (!tag_stream_scan(uri.c_str(), tag_builder))
try {
if (!tag_stream_scan(uri.c_str(), tag_builder))
return false;
} catch (...) {
// TODO: log or propagate I/O errors?
return false;
}
mtime = std::chrono::system_clock::time_point::min();
tag_builder.Commit(tag);

View File

@@ -47,11 +47,11 @@ public:
handler(_handler),
is(nullptr) {}
bool ScanFile(const DecoderPlugin &plugin) noexcept {
bool ScanFile(const DecoderPlugin &plugin) {
return plugin.ScanFile(path_fs, handler);
}
bool ScanStream(const DecoderPlugin &plugin) noexcept {
bool ScanStream(const DecoderPlugin &plugin) {
if (plugin.scan_stream == nullptr)
return false;
@@ -73,14 +73,14 @@ public:
return plugin.ScanStream(*is, handler);
}
bool Scan(const DecoderPlugin &plugin) noexcept {
bool Scan(const DecoderPlugin &plugin) {
return plugin.SupportsSuffix(suffix) &&
(ScanFile(plugin) || ScanStream(plugin));
}
};
bool
ScanFileTagsNoGeneric(Path path_fs, TagHandler &handler) noexcept
ScanFileTagsNoGeneric(Path path_fs, TagHandler &handler)
{
assert(!path_fs.IsNull());
@@ -100,7 +100,7 @@ ScanFileTagsNoGeneric(Path path_fs, TagHandler &handler) noexcept
bool
ScanFileTagsWithGeneric(Path path, TagBuilder &builder,
AudioFormat *audio_format) noexcept
AudioFormat *audio_format)
{
FullTagHandler h(builder, audio_format);

View File

@@ -30,22 +30,26 @@ class TagBuilder;
* but does not fall back to generic scanners (APE and ID3) if no tags
* were found (but the file was recognized).
*
* Throws on I/O error.
*
* @return true if the file was recognized (even if no metadata was
* found)
*/
bool
ScanFileTagsNoGeneric(Path path, TagHandler &handler) noexcept;
ScanFileTagsNoGeneric(Path path, TagHandler &handler);
/**
* Scan the tags of a song file. Invokes matching decoder plugins,
* and falls back to generic scanners (APE and ID3) if no tags were
* found (but the file was recognized).
*
* Throws on I/O error.
*
* @return true if the file was recognized (even if no metadata was
* found)
*/
bool
ScanFileTagsWithGeneric(Path path, TagBuilder &builder,
AudioFormat *audio_format=nullptr) noexcept;
AudioFormat *audio_format=nullptr);
#endif

View File

@@ -45,7 +45,7 @@ CheckDecoderPlugin(const DecoderPlugin &plugin,
}
bool
tag_stream_scan(InputStream &is, TagHandler &handler) noexcept
tag_stream_scan(InputStream &is, TagHandler &handler)
{
assert(is.IsReady());
@@ -73,19 +73,17 @@ tag_stream_scan(InputStream &is, TagHandler &handler) noexcept
}
bool
tag_stream_scan(const char *uri, TagHandler &handler) noexcept
try {
tag_stream_scan(const char *uri, TagHandler &handler)
{
Mutex mutex;
auto is = InputStream::OpenReady(uri, mutex);
return tag_stream_scan(*is, handler);
} catch (const std::exception &e) {
return false;
}
bool
tag_stream_scan(InputStream &is, TagBuilder &builder,
AudioFormat *audio_format) noexcept
AudioFormat *audio_format)
{
assert(is.IsReady());
@@ -102,12 +100,10 @@ tag_stream_scan(InputStream &is, TagBuilder &builder,
bool
tag_stream_scan(const char *uri, TagBuilder &builder,
AudioFormat *audio_format) noexcept
try {
AudioFormat *audio_format)
{
Mutex mutex;
auto is = InputStream::OpenReady(uri, mutex);
return tag_stream_scan(*is, builder, audio_format);
} catch (const std::exception &e) {
return false;
}

View File

@@ -29,29 +29,39 @@ class TagBuilder;
* Scan the tags of an #InputStream. Invokes matching decoder
* plugins, but does not invoke the special "APE" and "ID3" scanners.
*
* Throws on I/O error.
*
* @return true if the file was recognized (even if no metadata was
* found)
*/
bool
tag_stream_scan(InputStream &is, TagHandler &handler) noexcept;
tag_stream_scan(InputStream &is, TagHandler &handler);
/**
* Throws on I/O error.
*/
bool
tag_stream_scan(const char *uri, TagHandler &handler) noexcept;
tag_stream_scan(const char *uri, TagHandler &handler);
/**
* Scan the tags of an #InputStream. Invokes matching decoder
* plugins, and falls back to generic scanners (APE and ID3) if no
* tags were found (but the file was recognized).
*
* Throws on I/O error.
*
* @return true if the file was recognized (even if no metadata was
* found)
*/
bool
tag_stream_scan(InputStream &is, TagBuilder &builder,
AudioFormat *audio_format=nullptr) noexcept;
AudioFormat *audio_format=nullptr);
/**
* Throws on I/O error.
*/
bool
tag_stream_scan(const char *uri, TagBuilder &builder,
AudioFormat *audio_format=nullptr) noexcept;
AudioFormat *audio_format=nullptr);
#endif

40
src/apple/AudioObject.cxx Normal file
View File

@@ -0,0 +1,40 @@
/*
* Copyright 2020 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
* are met:
*
* - Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
*
* - Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the
* distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
* FOUNDATION OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
* STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
* OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#include "AudioObject.hxx"
#include "StringRef.hxx"
Apple::StringRef
AudioObjectGetStringProperty(AudioObjectID inObjectID,
const AudioObjectPropertyAddress &inAddress)
{
auto s = AudioObjectGetPropertyDataT<CFStringRef>(inObjectID,
inAddress);
return Apple::StringRef(s);
}

105
src/apple/AudioObject.hxx Normal file
View File

@@ -0,0 +1,105 @@
/*
* Copyright 2020 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
* are met:
*
* - Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
*
* - Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the
* distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
* FOUNDATION OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
* STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
* OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#ifndef APPLE_AUDIO_OBJECT_HXX
#define APPLE_AUDIO_OBJECT_HXX
#include "Throw.hxx"
#include "util/AllocatedArray.hxx"
#include <CoreAudio/AudioHardware.h>
#include <cstddef>
namespace Apple {
class StringRef;
}
inline std::size_t
AudioObjectGetPropertyDataSize(AudioObjectID inObjectID,
const AudioObjectPropertyAddress &inAddress)
{
UInt32 size;
OSStatus status = AudioObjectGetPropertyDataSize(inObjectID,
&inAddress,
0, nullptr, &size);
if (status != noErr)
Apple::ThrowOSStatus(status);
return size;
}
template<typename T>
T
AudioObjectGetPropertyDataT(AudioObjectID inObjectID,
const AudioObjectPropertyAddress &inAddress)
{
OSStatus status;
UInt32 size = sizeof(T);
T value;
status = AudioObjectGetPropertyData(inObjectID, &inAddress,
0, nullptr,
&size, &value);
if (status != noErr)
Apple::ThrowOSStatus(status);
return value;
}
Apple::StringRef
AudioObjectGetStringProperty(AudioObjectID inObjectID,
const AudioObjectPropertyAddress &inAddress);
template<typename T>
AllocatedArray<T>
AudioObjectGetPropertyDataArray(AudioObjectID inObjectID,
const AudioObjectPropertyAddress &inAddress)
{
OSStatus status;
UInt32 size;
status = AudioObjectGetPropertyDataSize(inObjectID,
&inAddress,
0, nullptr, &size);
if (status != noErr)
Apple::ThrowOSStatus(status);
AllocatedArray<T> result(size / sizeof(T));
status = AudioObjectGetPropertyData(inObjectID, &inAddress,
0, nullptr,
&size, result.begin());
if (status != noErr)
Apple::ThrowOSStatus(status);
return result;
}
#endif

111
src/apple/AudioUnit.hxx Normal file
View File

@@ -0,0 +1,111 @@
/*
* Copyright 2020 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
* are met:
*
* - Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
*
* - Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the
* distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
* FOUNDATION OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
* STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
* OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#ifndef APPLE_AUDIO_UNIT_HXX
#define APPLE_AUDIO_UNIT_HXX
#include "Throw.hxx"
#include <AudioUnit/AudioUnit.h>
template<typename T>
T
AudioUnitGetPropertyT(AudioUnit inUnit, AudioUnitPropertyID inID,
AudioUnitScope inScope,
AudioUnitElement inElement)
{
UInt32 size = sizeof(T);
T value;
OSStatus status = AudioUnitGetProperty(inUnit, inID, inScope,
inElement,
&value, &size);
if (status != noErr)
Apple::ThrowOSStatus(status);
return value;
}
template<typename T>
void
AudioUnitSetPropertyT(AudioUnit inUnit, AudioUnitPropertyID inID,
AudioUnitScope inScope,
AudioUnitElement inElement,
const T &value)
{
OSStatus status = AudioUnitSetProperty(inUnit, inID, inScope,
inElement,
&value, sizeof(value));
if (status != noErr)
Apple::ThrowOSStatus(status);
}
inline void
AudioUnitSetCurrentDevice(AudioUnit inUnit, const AudioDeviceID &value)
{
AudioUnitSetPropertyT(inUnit, kAudioOutputUnitProperty_CurrentDevice,
kAudioUnitScope_Global, 0,
value);
}
inline void
AudioUnitSetInputStreamFormat(AudioUnit inUnit,
const AudioStreamBasicDescription &value)
{
AudioUnitSetPropertyT(inUnit, kAudioUnitProperty_StreamFormat,
kAudioUnitScope_Input, 0,
value);
}
inline void
AudioUnitSetInputRenderCallback(AudioUnit inUnit,
const AURenderCallbackStruct &value)
{
AudioUnitSetPropertyT(inUnit, kAudioUnitProperty_SetRenderCallback,
kAudioUnitScope_Input, 0,
value);
}
inline UInt32
AudioUnitGetBufferFrameSize(AudioUnit inUnit)
{
return AudioUnitGetPropertyT<UInt32>(inUnit,
kAudioDevicePropertyBufferFrameSize,
kAudioUnitScope_Global, 0);
}
inline void
AudioUnitSetBufferFrameSize(AudioUnit inUnit, const UInt32 &value)
{
AudioUnitSetPropertyT(inUnit, kAudioDevicePropertyBufferFrameSize,
kAudioUnitScope_Global, 0,
value);
}
#endif

75
src/apple/ErrorRef.hxx Normal file
View File

@@ -0,0 +1,75 @@
/*
* Copyright 2020 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
* are met:
*
* - Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
*
* - Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the
* distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
* FOUNDATION OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
* STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
* OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#ifndef APPLE_ERROR_REF_HXX
#define APPLE_ERROR_REF_HXX
#include <CoreFoundation/CFError.h>
#include <utility>
namespace Apple {
class ErrorRef {
CFErrorRef ref = nullptr;
public:
explicit ErrorRef(CFErrorRef _ref) noexcept
:ref(_ref) {}
ErrorRef(CFAllocatorRef allocator, CFErrorDomain domain,
CFIndex code, CFDictionaryRef userInfo) noexcept
:ref(CFErrorCreate(allocator, domain, code, userInfo)) {}
ErrorRef(ErrorRef &&src) noexcept
:ref(std::exchange(src.ref, nullptr)) {}
~ErrorRef() noexcept {
if (ref)
CFRelease(ref);
}
ErrorRef &operator=(ErrorRef &&src) noexcept {
using std::swap;
swap(ref, src.ref);
return *this;
}
operator bool() const noexcept {
return ref != nullptr;
}
CFStringRef CopyDescription() const noexcept {
return CFErrorCopyDescription(ref);
}
};
} // namespace Apple
#endif

73
src/apple/StringRef.hxx Normal file
View File

@@ -0,0 +1,73 @@
/*
* Copyright 2020 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
* are met:
*
* - Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
*
* - Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the
* distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
* FOUNDATION OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
* STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
* OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#ifndef APPLE_STRING_REF_HXX
#define APPLE_STRING_REF_HXX
#include <CoreFoundation/CFString.h>
#include <utility>
namespace Apple {
class StringRef {
CFStringRef ref = nullptr;
public:
explicit StringRef(CFStringRef _ref) noexcept
:ref(_ref) {}
StringRef(StringRef &&src) noexcept
:ref(std::exchange(src.ref, nullptr)) {}
~StringRef() noexcept {
if (ref)
CFRelease(ref);
}
StringRef &operator=(StringRef &&src) noexcept {
using std::swap;
swap(ref, src.ref);
return *this;
}
operator bool() const noexcept {
return ref != nullptr;
}
bool GetCString(char *buffer, std::size_t size,
CFStringEncoding encoding=kCFStringEncodingUTF8) const noexcept
{
return CFStringGetCString(ref, buffer, size, encoding);
}
};
} // namespace Apple
#endif

67
src/apple/Throw.cxx Normal file
View File

@@ -0,0 +1,67 @@
/*
* Copyright 2020 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
* are met:
*
* - Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
*
* - Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the
* distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
* FOUNDATION OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
* STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
* OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#include "Throw.hxx"
#include "ErrorRef.hxx"
#include "StringRef.hxx"
#include <stdexcept>
namespace Apple {
void
ThrowOSStatus(OSStatus status)
{
const Apple::ErrorRef cferr(nullptr, kCFErrorDomainOSStatus,
status, nullptr);
const Apple::StringRef cfstr(cferr.CopyDescription());
char msg[1024];
if (!cfstr.GetCString(msg, sizeof(msg)))
throw std::runtime_error("Unknown OSStatus");
throw std::runtime_error(msg);
}
void
ThrowOSStatus(OSStatus status, const char *_msg)
{
const Apple::ErrorRef cferr(nullptr, kCFErrorDomainOSStatus,
status, nullptr);
const Apple::StringRef cfstr(cferr.CopyDescription());
char msg[1024];
strcpy(msg, _msg);
size_t length = strlen(msg);
cfstr.GetCString(msg + length, sizeof(msg) - length);
throw std::runtime_error(msg);
}
} // namespace Apple

45
src/apple/Throw.hxx Normal file
View File

@@ -0,0 +1,45 @@
/*
* Copyright 2020 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
* are met:
*
* - Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
*
* - Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the
* distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
* FOUNDATION OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
* STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
* OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#ifndef APPLE_THROW_HXX
#define APPLE_THROW_HXX
#include <CoreFoundation/CFBase.h>
namespace Apple {
void
ThrowOSStatus(OSStatus status);
void
ThrowOSStatus(OSStatus status, const char *msg);
} // namespace Apple
#endif

28
src/apple/meson.build Normal file
View File

@@ -0,0 +1,28 @@
if not is_darwin
apple_dep = dependency('', required: false)
subdir_done()
endif
audiounit_dep = declare_dependency(
link_args: ['-framework', 'AudioUnit', '-framework', 'CoreAudio', '-framework', 'CoreServices'],
dependencies: [
boost_dep,
],
)
apple = static_library(
'apple',
'AudioObject.cxx',
'Throw.cxx',
include_directories: inc,
dependencies: [
audiounit_dep,
],
)
apple_dep = declare_dependency(
link_with: apple,
dependencies: [
audiounit_dep,
],
)

View File

@@ -198,6 +198,16 @@ handle_mount(Client &client, Request args, Response &r)
return CommandResult::ERROR;
}
if (composite.IsMountPoint(local_uri)) {
r.Error(ACK_ERROR_ARG, "Mount point busy");
return CommandResult::ERROR;
}
if (composite.IsMounted(remote_uri)) {
r.Error(ACK_ERROR_ARG, "This storage is already mounted");
return CommandResult::ERROR;
}
auto &event_loop = instance.io_thread.GetEventLoop();
auto storage = CreateStorageURI(event_loop, remote_uri);
if (storage == nullptr) {
@@ -210,8 +220,10 @@ handle_mount(Client &client, Request args, Response &r)
#ifdef ENABLE_DATABASE
if (auto *db = dynamic_cast<SimpleDatabase *>(instance.GetDatabase())) {
bool need_update;
try {
db->Mount(local_uri, remote_uri);
need_update = !db->Mount(local_uri, remote_uri);
} catch (...) {
composite.Unmount(local_uri);
throw;
@@ -220,6 +232,12 @@ handle_mount(Client &client, Request args, Response &r)
// TODO: call Instance::OnDatabaseModified()?
// TODO: trigger database update?
instance.EmitIdle(IDLE_DATABASE);
if (need_update) {
UpdateService *update = client.GetInstance().update;
if (update != nullptr)
update->Enqueue(local_uri, false);
}
}
#endif

View File

@@ -428,7 +428,7 @@ IsUnsafeChar(char ch)
return !IsSafeChar(ch);
}
void
bool
SimpleDatabase::Mount(const char *local_uri, const char *storage_uri)
{
if (cache_path.IsNull())
@@ -447,9 +447,11 @@ SimpleDatabase::Mount(const char *local_uri, const char *storage_uri)
compress);
db->Open();
// TODO: update the new database instance?
bool exists = db->FileExists();
Mount(local_uri, std::move(db));
return exists;
}
inline DatabasePtr

View File

@@ -103,9 +103,11 @@ public:
/**
* Throws #std::runtime_error on error.
*
* @return false if the mounted database needs to be updated
*/
gcc_nonnull_all
void Mount(const char *local_uri, const char *storage_uri);
bool Mount(const char *local_uri, const char *storage_uri);
gcc_nonnull_all
bool Unmount(const char *uri) noexcept;

View File

@@ -89,9 +89,18 @@ public:
tag.Clear();
}
gcc_pure
bool IsRoot() const noexcept {
return type == Type::CONTAINER && id == "0";
}
gcc_pure
bool Check() const noexcept {
return !id.empty() && !parent_id.empty() && !name.empty() &&
return !id.empty() &&
/* root nodes don't need a parent id and a
name */
(IsRoot() || (!parent_id.empty() &&
!name.empty())) &&
(type != UPnPDirObject::Type::ITEM ||
item_class != UPnPDirObject::ItemClass::UNKNOWN);
}

View File

@@ -341,8 +341,8 @@ UpdateWalk::UpdateDirectory(Directory &directory,
try {
Mutex mutex;
auto is = InputStream::OpenReady(PathTraitsUTF8::Build(storage.MapUTF8(directory.GetPath()).c_str(),
".mpdignore").c_str(),
auto is = InputStream::OpenReady(storage.MapUTF8(PathTraitsUTF8::Build(directory.GetPath(),
".mpdignore").c_str()).c_str(),
mutex);
child_exclude_list.Load(std::move(is));
} catch (...) {

View File

@@ -67,18 +67,22 @@ struct DecoderPlugin {
void (*file_decode)(DecoderClient &client, Path path_fs);
/**
* Scan metadata of a file.
* Scan metadata of a file.
*
* Throws on I/O error.
*
* @return false if the operation has failed
* @return false if the file was not recognized
*/
bool (*scan_file)(Path path_fs, TagHandler &handler) noexcept;
bool (*scan_file)(Path path_fs, TagHandler &handler);
/**
* Scan metadata of a file.
* Scan metadata of a stream.
*
* Throws on I/O error.
*
* @return false if the operation has failed
* @return false if the stream was not recognized
*/
bool (*scan_stream)(InputStream &is, TagHandler &handler) noexcept;
bool (*scan_stream)(InputStream &is, TagHandler &handler);
/**
* @brief Return a "virtual" filename for subtracks in
@@ -135,7 +139,7 @@ struct DecoderPlugin {
* Read the tag of a file.
*/
template<typename P>
bool ScanFile(P path_fs, TagHandler &handler) const noexcept {
bool ScanFile(P path_fs, TagHandler &handler) const {
return scan_file != nullptr
? scan_file(path_fs, handler)
: false;
@@ -144,7 +148,7 @@ struct DecoderPlugin {
/**
* Read the tag of a stream.
*/
bool ScanStream(InputStream &is, TagHandler &handler) const noexcept {
bool ScanStream(InputStream &is, TagHandler &handler) const {
return scan_stream != nullptr
? scan_stream(is, handler)
: false;

View File

@@ -241,7 +241,7 @@ audiofile_stream_decode(DecoderClient &client, InputStream &is)
}
static bool
audiofile_scan_stream(InputStream &is, TagHandler &handler) noexcept
audiofile_scan_stream(InputStream &is, TagHandler &handler)
{
if (!is.IsSeekable() || !is.KnownSize())
return false;

View File

@@ -20,7 +20,7 @@
#ifndef MPD_DECODER_DSDLIB_HXX
#define MPD_DECODER_DSDLIB_HXX
#include "system/ByteOrder.hxx"
#include "util/ByteOrder.hxx"
#include "input/Offset.hxx"
#include "util/Compiler.h"

View File

@@ -32,7 +32,7 @@
#include "input/InputStream.hxx"
#include "CheckAudioFormat.hxx"
#include "util/bit_reverse.h"
#include "system/ByteOrder.hxx"
#include "util/ByteOrder.hxx"
#include "tag/Handler.hxx"
#include "DsdLib.hxx"
#include "Log.hxx"
@@ -449,7 +449,7 @@ dsdiff_stream_decode(DecoderClient &client, InputStream &is)
}
static bool
dsdiff_scan_stream(InputStream &is, TagHandler &handler) noexcept
dsdiff_scan_stream(InputStream &is, TagHandler &handler)
{
DsdiffMetaData metadata;
DsdiffChunkHeader chunk_header;

View File

@@ -33,7 +33,7 @@
#include "input/InputStream.hxx"
#include "CheckAudioFormat.hxx"
#include "util/bit_reverse.h"
#include "system/ByteOrder.hxx"
#include "util/ByteOrder.hxx"
#include "DsdLib.hxx"
#include "tag/Handler.hxx"
#include "Log.hxx"
@@ -326,7 +326,7 @@ dsf_stream_decode(DecoderClient &client, InputStream &is)
}
static bool
dsf_scan_stream(InputStream &is, TagHandler &handler) noexcept
dsf_scan_stream(InputStream &is, TagHandler &handler)
{
/* check DSF metadata */
DsfMetaData metadata;

View File

@@ -414,7 +414,7 @@ faad_stream_decode(DecoderClient &client, InputStream &is)
}
static bool
faad_scan_stream(InputStream &is, TagHandler &handler) noexcept
faad_scan_stream(InputStream &is, TagHandler &handler)
{
auto result = faad_get_file_time(is);
if (!result.first)

View File

@@ -646,8 +646,7 @@ ffmpeg_decode(DecoderClient &client, InputStream &input)
}
static bool
FfmpegScanStream(AVFormatContext &format_context,
TagHandler &handler) noexcept
FfmpegScanStream(AVFormatContext &format_context, TagHandler &handler)
{
const int find_result =
avformat_find_stream_info(&format_context, nullptr);
@@ -680,7 +679,7 @@ FfmpegScanStream(AVFormatContext &format_context,
}
static bool
ffmpeg_scan_stream(InputStream &is, TagHandler &handler) noexcept
ffmpeg_scan_stream(InputStream &is, TagHandler &handler)
{
AvioStream stream(nullptr, is);
if (!stream.Open())

View File

@@ -69,7 +69,7 @@ flac_write_cb(const FLAC__StreamDecoder *dec, const FLAC__Frame *frame,
}
static bool
flac_scan_file(Path path_fs, TagHandler &handler) noexcept
flac_scan_file(Path path_fs, TagHandler &handler)
{
FlacMetadataChain chain;
if (!chain.Read(NarrowPath(path_fs))) {
@@ -84,7 +84,7 @@ flac_scan_file(Path path_fs, TagHandler &handler) noexcept
}
static bool
flac_scan_stream(InputStream &is, TagHandler &handler) noexcept
flac_scan_stream(InputStream &is, TagHandler &handler)
{
FlacMetadataChain chain;
if (!chain.Read(is)) {
@@ -313,7 +313,7 @@ oggflac_init(gcc_unused const ConfigBlock &block)
}
static bool
oggflac_scan_file(Path path_fs, TagHandler &handler) noexcept
oggflac_scan_file(Path path_fs, TagHandler &handler)
{
FlacMetadataChain chain;
if (!chain.ReadOgg(NarrowPath(path_fs))) {
@@ -328,7 +328,7 @@ oggflac_scan_file(Path path_fs, TagHandler &handler) noexcept
}
static bool
oggflac_scan_stream(InputStream &is, TagHandler &handler) noexcept
oggflac_scan_stream(InputStream &is, TagHandler &handler)
{
FlacMetadataChain chain;
if (!chain.ReadOgg(is)) {

View File

@@ -70,8 +70,8 @@ fluidsynth_mpd_log_function(int level,
char *message,
void *)
{
Log(fluidsynth_domain,
fluidsynth_level_to_mpd(fluid_log_level(level)),
Log(fluidsynth_level_to_mpd(fluid_log_level(level)),
fluidsynth_domain,
message);
}

View File

@@ -20,7 +20,7 @@
#include "HybridDsdDecoderPlugin.hxx"
#include "../DecoderAPI.hxx"
#include "input/InputStream.hxx"
#include "system/ByteOrder.hxx"
#include "util/ByteOrder.hxx"
#include "util/Domain.hxx"
#include "util/WritableBuffer.hxx"
#include "util/StaticFifoBuffer.hxx"

View File

@@ -1051,7 +1051,7 @@ MadDecoder::RunScan(TagHandler &handler) noexcept
}
static bool
mad_decoder_scan_stream(InputStream &is, TagHandler &handler) noexcept
mad_decoder_scan_stream(InputStream &is, TagHandler &handler)
{
MadDecoder data(nullptr, is);
return data.RunScan(handler);

View File

@@ -275,7 +275,7 @@ mpcdec_get_file_duration(InputStream &is)
}
static bool
mpcdec_scan_stream(InputStream &is, TagHandler &handler) noexcept
mpcdec_scan_stream(InputStream &is, TagHandler &handler)
{
const auto duration = mpcdec_get_file_duration(is);
if (duration.IsNegative())

View File

@@ -75,6 +75,19 @@ class MPDOpusDecoder final : public OggDecoder {
OpusDecoder *opus_decoder = nullptr;
opus_int16 *output_buffer = nullptr;
/**
* The pre-skip value from the Opus header. Initialized by
* OnOggBeginning().
*/
unsigned pre_skip;
/**
* The number of decoded samples which shall be skipped. At
* the beginning of the file, this gets set to #pre_skip (by
* OnOggBeginning()), and may also be set while seeking.
*/
unsigned skip;
/**
* If non-zero, then a previous Opus stream has been found
* already with this number of channels. If opus_decoder is
@@ -85,6 +98,13 @@ class MPDOpusDecoder final : public OggDecoder {
size_t frame_size;
/**
* The granulepos of the next sample to be submitted to
* DecoderClient::SubmitData(). Negative if unkown.
* Initialized by OnOggBeginning().
*/
ogg_int64_t granulepos;
public:
explicit MPDOpusDecoder(DecoderReader &reader)
:OggDecoder(reader) {}
@@ -101,6 +121,13 @@ public:
bool Seek(uint64_t where_frame);
private:
void AddGranulepos(ogg_int64_t n) noexcept {
assert(n >= 0);
if (granulepos >= 0)
granulepos += n;
}
void HandleTags(const ogg_packet &packet);
void HandleAudio(const ogg_packet &packet);
@@ -137,10 +164,13 @@ MPDOpusDecoder::OnOggBeginning(const ogg_packet &packet)
throw std::runtime_error("BOS packet must be OpusHead");
unsigned channels;
if (!ScanOpusHeader(packet.packet, packet.bytes, channels) ||
if (!ScanOpusHeader(packet.packet, packet.bytes, channels, pre_skip) ||
!audio_valid_channel_count(channels))
throw std::runtime_error("Malformed BOS packet");
granulepos = 0;
skip = pre_skip;
assert(opus_decoder == nullptr);
assert(IsInitialized() == (output_buffer != nullptr));
@@ -177,8 +207,12 @@ MPDOpusDecoder::OnOggBeginning(const ogg_packet &packet)
client.Ready(audio_format, eos_granulepos > 0, duration);
frame_size = audio_format.GetFrameSize();
output_buffer = new opus_int16[opus_output_buffer_frames
* audio_format.channels];
if (output_buffer == nullptr)
/* note: if we ever support changing the channel count
in chained streams, we need to reallocate this
buffer instead of keeping it */
output_buffer = new opus_int16[opus_output_buffer_frames
* audio_format.channels];
auto cmd = client.GetCommand();
if (cmd != DecoderCommand::NONE)
@@ -231,22 +265,58 @@ MPDOpusDecoder::HandleAudio(const ogg_packet &packet)
packet.bytes,
output_buffer, opus_output_buffer_frames,
0);
if (nframes < 0)
throw FormatRuntimeError("libopus error: %s",
opus_strerror(nframes));
if (nframes > 0) {
const size_t nbytes = nframes * frame_size;
auto cmd = client.SubmitData(input_stream,
output_buffer, nbytes,
0);
if (cmd != DecoderCommand::NONE)
throw cmd;
if (packet.granulepos > 0)
client.SubmitTimestamp(FloatDuration(packet.granulepos)
/ opus_sample_rate);
if (gcc_unlikely(nframes <= 0)) {
if (nframes < 0)
throw FormatRuntimeError("libopus error: %s",
opus_strerror(nframes));
else
return;
}
/* apply the "skip" value */
if (skip >= (unsigned)nframes) {
skip -= nframes;
AddGranulepos(nframes);
return;
}
const opus_int16 *data = output_buffer;
data += skip * previous_channels;
nframes -= skip;
AddGranulepos(skip);
skip = 0;
if (packet.e_o_s && packet.granulepos > 0 && granulepos >= 0) {
/* End Trimming (RFC7845 4.4): "The page with the 'end
of stream' flag set MAY have a granule position
that indicates the page contains less audio data
than would normally be returned by decoding up
through the final packet. This is used to end the
stream somewhere other than an even frame
boundary. [...] The remaining samples are
discarded. */
ogg_int64_t remaining = packet.granulepos - granulepos;
if (remaining <= 0)
return;
if (remaining < nframes)
nframes = remaining;
}
/* submit decoded samples to the DecoderClient */
const size_t nbytes = nframes * frame_size;
auto cmd = client.SubmitData(input_stream,
data, nbytes,
0);
if (cmd != DecoderCommand::NONE)
throw cmd;
if (packet.granulepos > 0) {
granulepos = packet.granulepos;
client.SubmitTimestamp(FloatDuration(granulepos - pre_skip)
/ opus_sample_rate);
} else
AddGranulepos(nframes);
}
bool
@@ -258,8 +328,20 @@ MPDOpusDecoder::Seek(uint64_t where_frame)
const ogg_int64_t where_granulepos(where_frame);
/* we don't know the exact granulepos after seeking, so let's
set it to -1 - it will be set after the next packet which
declares its granulepos */
granulepos = -1;
try {
SeekGranulePos(where_granulepos);
/* since all frame numbers are offset by the file's
pre-skip value, we need to apply it here as well;
we could just seek to "where_frame+pre_skip" as
well, but I think by decoding those samples and
discard them, we're safer */
skip = pre_skip;
return true;
} catch (...) {
return false;
@@ -302,13 +384,14 @@ mpd_opus_stream_decode(DecoderClient &client,
static bool
ReadAndParseOpusHead(OggSyncState &sync, OggStreamState &stream,
unsigned &channels)
unsigned &channels, unsigned &pre_skip)
{
ogg_packet packet;
return OggReadPacket(sync, stream, packet) && packet.b_o_s &&
IsOpusHead(packet) &&
ScanOpusHeader(packet.packet, packet.bytes, channels) &&
ScanOpusHeader(packet.packet, packet.bytes, channels,
pre_skip) &&
audio_valid_channel_count(channels);
}
@@ -327,11 +410,12 @@ ReadAndVisitOpusTags(OggSyncState &sync, OggStreamState &stream,
static void
VisitOpusDuration(InputStream &is, OggSyncState &sync, OggStreamState &stream,
TagHandler &handler)
ogg_int64_t pre_skip, TagHandler &handler)
{
ogg_packet packet;
if (OggSeekFindEOS(sync, stream, packet, is)) {
if (OggSeekFindEOS(sync, stream, packet, is) &&
packet.granulepos >= pre_skip) {
const auto duration =
SongTime::FromScale<uint64_t>(packet.granulepos,
opus_sample_rate);
@@ -340,7 +424,7 @@ VisitOpusDuration(InputStream &is, OggSyncState &sync, OggStreamState &stream,
}
static bool
mpd_opus_scan_stream(InputStream &is, TagHandler &handler) noexcept
mpd_opus_scan_stream(InputStream &is, TagHandler &handler)
{
InputStreamReader reader(is);
OggSyncState oy(reader);
@@ -351,15 +435,15 @@ mpd_opus_scan_stream(InputStream &is, TagHandler &handler) noexcept
OggStreamState os(first_page);
unsigned channels;
if (!ReadAndParseOpusHead(oy, os, channels) ||
unsigned channels, pre_skip;
if (!ReadAndParseOpusHead(oy, os, channels, pre_skip) ||
!ReadAndVisitOpusTags(oy, os, handler))
return false;
handler.OnAudioFormat(AudioFormat(opus_sample_rate,
SampleFormat::S16, channels));
VisitOpusDuration(is, oy, os, handler);
VisitOpusDuration(is, oy, os, pre_skip, handler);
return true;
}

View File

@@ -18,6 +18,7 @@
*/
#include "OpusHead.hxx"
#include "util/ByteOrder.hxx"
#include <stdint.h>
@@ -31,12 +32,14 @@ struct OpusHead {
};
bool
ScanOpusHeader(const void *data, size_t size, unsigned &channels_r)
ScanOpusHeader(const void *data, size_t size, unsigned &channels_r,
unsigned &pre_skip_r)
{
const OpusHead *h = (const OpusHead *)data;
if (size < 19 || (h->version & 0xf0) != 0)
return false;
channels_r = h->channels;
pre_skip_r = FromLE16(h->pre_skip);
return true;
}

View File

@@ -23,6 +23,7 @@
#include <stddef.h>
bool
ScanOpusHeader(const void *data, size_t size, unsigned &channels_r);
ScanOpusHeader(const void *data, size_t size, unsigned &channels_r,
unsigned &pre_skip_r);
#endif

View File

@@ -22,7 +22,7 @@
#include "CheckAudioFormat.hxx"
#include "pcm/PcmPack.hxx"
#include "input/InputStream.hxx"
#include "system/ByteOrder.hxx"
#include "util/ByteOrder.hxx"
#include "util/Domain.hxx"
#include "util/ByteReverse.hxx"
#include "util/StaticFifoBuffer.hxx"

View File

@@ -35,7 +35,7 @@
#include "util/Domain.hxx"
#include "util/AllocatedString.hxx"
#include "util/CharUtil.hxx"
#include "system/ByteOrder.hxx"
#include "util/ByteOrder.hxx"
#include "Log.hxx"
#ifdef HAVE_SIDPLAYFP

View File

@@ -267,7 +267,7 @@ static constexpr struct {
};
static bool
sndfile_scan_stream(InputStream &is, TagHandler &handler) noexcept
sndfile_scan_stream(InputStream &is, TagHandler &handler)
{
SF_INFO info;

View File

@@ -370,7 +370,7 @@ VisitVorbisDuration(InputStream &is,
}
static bool
vorbis_scan_stream(InputStream &is, TagHandler &handler) noexcept
vorbis_scan_stream(InputStream &is, TagHandler &handler)
{
/* initialize libogg */

View File

@@ -614,7 +614,7 @@ wavpack_scan_file(Path path_fs, TagHandler &handler) noexcept
}
static bool
wavpack_scan_stream(InputStream &is, TagHandler &handler) noexcept
wavpack_scan_stream(InputStream &is, TagHandler &handler)
{
WavpackInput isp(nullptr, is);

View File

@@ -22,7 +22,7 @@
#include "AudioFormat.hxx"
#include "config/Domain.hxx"
#include "util/Alloc.hxx"
#include "system/ByteOrder.hxx"
#include "util/ByteOrder.hxx"
#include "util/StringUtil.hxx"
#include <opus.h>

View File

@@ -19,7 +19,7 @@
#include "WaveEncoderPlugin.hxx"
#include "../EncoderAPI.hxx"
#include "system/ByteOrder.hxx"
#include "util/ByteOrder.hxx"
#include "util/DynamicFifoBuffer.hxx"
#include <assert.h>

View File

@@ -58,6 +58,11 @@ input_stream_global_init(const ConfigData &config, EventLoop &event_loop)
if (plugin->init != nullptr)
plugin->init(event_loop, *block);
input_plugins_enabled[i] = true;
} catch (const PluginUnconfigured &e) {
LogFormat(LogLevel::INFO, e,
"Input plugin '%s' is not configured",
plugin->name);
continue;
} catch (const PluginUnavailable &e) {
FormatError(e,
"Input plugin '%s' is unavailable",

View File

@@ -30,7 +30,7 @@
#include "util/StringCompare.hxx"
#include "util/RuntimeError.hxx"
#include "util/Domain.hxx"
#include "system/ByteOrder.hxx"
#include "util/ByteOrder.hxx"
#include "fs/AllocatedPath.hxx"
#include "Log.hxx"
#include "config/Block.hxx"

View File

@@ -26,6 +26,8 @@
#include "system/FileDescriptor.hxx"
#include "util/RuntimeError.hxx"
#include <cinttypes> // for PRIu64 (PRIoffset)
#include <sys/stat.h>
#include <fcntl.h>
@@ -94,6 +96,11 @@ FileInputStream::Read(void *ptr, size_t read_size)
nbytes = reader.Read(ptr, read_size);
}
if (nbytes == 0 && !IsEOF())
throw FormatRuntimeError("Unexpected end of file %s"
" at %" PRIoffset " of %" PRIoffset,
GetURI(), GetOffset(), GetSize());
offset += nbytes;
return nbytes;
}

View File

@@ -133,11 +133,11 @@ InitQobuzInput(EventLoop &event_loop, const ConfigBlock &block)
const char *app_id = block.GetBlockValue("app_id");
if (app_id == nullptr)
throw PluginUnavailable("No Qobuz app_id configured");
throw PluginUnconfigured("No Qobuz app_id configured");
const char *app_secret = block.GetBlockValue("app_secret");
if (app_secret == nullptr)
throw PluginUnavailable("No Qobuz app_secret configured");
throw PluginUnconfigured("No Qobuz app_secret configured");
const char *device_manufacturer_id = block.GetBlockValue("device_manufacturer_id",
"df691fdc-fa36-11e7-9718-635337d7df8f");
@@ -145,11 +145,11 @@ InitQobuzInput(EventLoop &event_loop, const ConfigBlock &block)
const char *username = block.GetBlockValue("username");
const char *email = block.GetBlockValue("email");
if (username == nullptr && email == nullptr)
throw PluginUnavailable("No Qobuz username configured");
throw PluginUnconfigured("No Qobuz username configured");
const char *password = block.GetBlockValue("password");
if (password == nullptr)
throw PluginUnavailable("No Qobuz password configured");
throw PluginUnconfigured("No Qobuz password configured");
const char *format_id = block.GetBlockValue("format_id", "5");

View File

@@ -170,15 +170,15 @@ InitTidalInput(EventLoop &event_loop, const ConfigBlock &block)
const char *token = block.GetBlockValue("token");
if (token == nullptr)
throw PluginUnavailable("No Tidal application token configured");
throw PluginUnconfigured("No Tidal application token configured");
const char *username = block.GetBlockValue("username");
if (username == nullptr)
throw PluginUnavailable("No Tidal username configured");
throw PluginUnconfigured("No Tidal username configured");
const char *password = block.GetBlockValue("password");
if (password == nullptr)
throw PluginUnavailable("No Tidal password configured");
throw PluginUnconfigured("No Tidal password configured");
FormatWarning(tidal_domain, "The Tidal input plugin is deprecated because Tidal has changed the protocol and doesn't share documentation");

View File

@@ -19,7 +19,7 @@
#include "HwSetup.hxx"
#include "Format.hxx"
#include "system/ByteOrder.hxx"
#include "util/ByteOrder.hxx"
#include "util/Domain.hxx"
#include "util/RuntimeError.hxx"
#include "AudioFormat.hxx"

View File

@@ -62,6 +62,6 @@ FfmpegLogCallback(gcc_unused void *ptr, int level, const char *fmt, va_list vl)
ffmpeg_domain.GetName(),
cls->item_name(ptr));
const Domain d(domain);
LogFormatV(d, FfmpegImportLogLevel(level), fmt, vl);
LogFormatV(FfmpegImportLogLevel(level), d, fmt, vl);
}
}

View File

@@ -47,6 +47,11 @@ ToNeighborInfo(const UDisks2::Object &o) noexcept
return {o.GetUri(), o.path};
}
static constexpr char udisks_neighbor_match[] =
"type='signal',sender='" UDISKS2_INTERFACE "',"
"interface='" DBUS_OM_INTERFACE "',"
"path='" UDISKS2_PATH "'";
class UdisksNeighborExplorer final
: public NeighborExplorer {
@@ -108,26 +113,37 @@ UdisksNeighborExplorer::DoOpen()
auto &connection = GetConnection();
/* this ugly try/catch cascade is only here because this
method has no RAII for this method - TODO: improve this */
try {
Error error;
dbus_bus_add_match(connection,
"type='signal',sender='" UDISKS2_INTERFACE "',"
"interface='" DBUS_OM_INTERFACE "',"
"path='" UDISKS2_PATH "'",
error);
dbus_bus_add_match(connection, udisks_neighbor_match, error);
error.CheckThrow("DBus AddMatch error");
dbus_connection_add_filter(connection,
HandleMessage, this,
nullptr);
try {
dbus_connection_add_filter(connection,
HandleMessage, this,
nullptr);
auto msg = Message::NewMethodCall(UDISKS2_INTERFACE,
UDISKS2_PATH,
DBUS_OM_INTERFACE,
"GetManagedObjects");
list_request.Send(connection, *msg.Get(),
std::bind(&UdisksNeighborExplorer::OnListNotify,
this, std::placeholders::_1));
try {
auto msg = Message::NewMethodCall(UDISKS2_INTERFACE,
UDISKS2_PATH,
DBUS_OM_INTERFACE,
"GetManagedObjects");
list_request.Send(connection, *msg.Get(),
std::bind(&UdisksNeighborExplorer::OnListNotify,
this, std::placeholders::_1));
} catch (...) {
dbus_connection_remove_filter(connection,
HandleMessage,
this);
throw;
}
} catch (...) {
dbus_bus_remove_match(connection,
udisks_neighbor_match, nullptr);
throw;
}
} catch (...) {
dbus_glue.Destruct();
throw;
@@ -147,8 +163,10 @@ UdisksNeighborExplorer::DoClose() noexcept
list_request.Cancel();
}
// TODO: remove_match
// TODO: remove_filter
auto &connection = GetConnection();
dbus_connection_remove_filter(connection, HandleMessage, this);
dbus_bus_remove_match(connection, udisks_neighbor_match, nullptr);
dbus_glue.Destruct();
}

View File

@@ -31,7 +31,7 @@
#define IPV4_ADDRESS_HXX
#include "SocketAddress.hxx"
#include "system/ByteOrder.hxx"
#include "util/ByteOrder.hxx"
#include <stdint.h>

View File

@@ -31,7 +31,7 @@
#define IPV6_ADDRESS_HXX
#include "SocketAddress.hxx"
#include "system/ByteOrder.hxx"
#include "util/ByteOrder.hxx"
#include "util/Compiler.h"
#include <stdint.h>

View File

@@ -39,6 +39,7 @@
#include "config/Option.hxx"
#include "config/Block.hxx"
#include "util/RuntimeError.hxx"
#include "util/StringAPI.hxx"
#include "util/StringFormat.hxx"
#include "Log.hxx"
@@ -214,7 +215,7 @@ FilteredAudioOutput::Setup(EventLoop &event_loop,
const char *replay_gain_handler =
block.GetBlockValue("replay_gain_handler", "software");
if (strcmp(replay_gain_handler, "none") != 0) {
if (!StringIsEqual(replay_gain_handler, "none")) {
prepared_replay_gain_filter =
NewReplayGainFilter(replay_gain_config);
assert(prepared_replay_gain_filter != nullptr);
@@ -240,14 +241,14 @@ FilteredAudioOutput::Setup(EventLoop &event_loop,
/* use the hardware mixer for replay gain? */
if (strcmp(replay_gain_handler, "mixer") == 0) {
if (StringIsEqual(replay_gain_handler, "mixer")) {
if (mixer != nullptr)
replay_gain_filter_set_mixer(*prepared_replay_gain_filter,
mixer, 100);
else
FormatError(output_domain,
"No such mixer for output '%s'", name);
} else if (strcmp(replay_gain_handler, "software") != 0 &&
} else if (!StringIsEqual(replay_gain_handler, "software") &&
prepared_replay_gain_filter != nullptr) {
throw std::runtime_error("Invalid \"replay_gain_handler\" value");
}

View File

@@ -28,6 +28,7 @@
#include "config/Data.hxx"
#include "config/Option.hxx"
#include "util/RuntimeError.hxx"
#include "util/StringAPI.hxx"
#include <stdexcept>
@@ -147,7 +148,7 @@ AudioOutputControl *
MultipleOutputs::FindByName(const char *name) noexcept
{
for (auto *i : outputs)
if (strcmp(i->GetName(), name) == 0)
if (StringIsEqual(i->GetName(), name))
return i;
return nullptr;

View File

@@ -38,8 +38,7 @@
#include "plugins/sles/SlesOutputPlugin.hxx"
#include "plugins/SolarisOutputPlugin.hxx"
#include "plugins/WinmmOutputPlugin.hxx"
#include <string.h>
#include "util/StringAPI.hxx"
const AudioOutputPlugin *const audio_output_plugins[] = {
#ifdef HAVE_SHOUT
@@ -101,7 +100,7 @@ const AudioOutputPlugin *
AudioOutputPlugin_get(const char *name)
{
audio_output_plugins_for_each(plugin)
if (strcmp(plugin->name, name) == 0)
if (StringIsEqual(plugin->name, name))
return plugin;
return nullptr;

View File

@@ -939,7 +939,7 @@ AlsaOutput::Play(const void *chunk, size_t size)
assert(size % in_frame_size == 0);
const auto e = pcm_export->Export({chunk, size});
if (e.size == 0)
if (e.empty())
/* the DoP (DSD over PCM) filter converts two frames
at a time and ignores the last odd frame; if there
was only one frame (e.g. the last frame in the

View File

@@ -25,12 +25,11 @@
#include "util/SplitString.hxx"
#include "util/RuntimeError.hxx"
#include "util/Domain.hxx"
#include "util/StringAPI.hxx"
#include "Log.hxx"
#include <ao/ao.h>
#include <string.h>
/* An ao_sample_format, with all fields set to zero: */
static ao_sample_format OUR_AO_FORMAT_INITIALIZER;
@@ -105,7 +104,7 @@ AoOutput::AoOutput(const ConfigBlock &block)
write_size(block.GetPositiveValue("write_size", 1024U))
{
const char *value = block.GetBlockValue("driver", "default");
if (0 == strcmp(value, "default"))
if (StringIsEqual(value, "default"))
driver = ao_default_driver_id();
else
driver = ao_driver_id(value);

File diff suppressed because it is too large Load Diff

View File

@@ -25,7 +25,7 @@
#include "util/ConstBuffer.hxx"
#include "util/Domain.hxx"
#include "util/Macros.hxx"
#include "system/ByteOrder.hxx"
#include "util/ByteOrder.hxx"
#include "Log.hxx"
#include <stdexcept>

View File

@@ -35,7 +35,6 @@
#include <assert.h>
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
static constexpr unsigned DEFAULT_CONN_TIMEOUT = 2;
@@ -123,15 +122,15 @@ ShoutOutput::ShoutOutput(const ConfigBlock &block)
unsigned protocol;
const char *value = block.GetBlockValue("protocol");
if (value != nullptr) {
if (0 == strcmp(value, "shoutcast") &&
if (StringIsEqual(value, "shoutcast") &&
!StringIsEqual(mime_type, "audio/mpeg"))
throw FormatRuntimeError("you cannot stream \"%s\" to shoutcast, use mp3",
mime_type);
else if (0 == strcmp(value, "shoutcast"))
else if (StringIsEqual(value, "shoutcast"))
protocol = SHOUT_PROTOCOL_ICY;
else if (0 == strcmp(value, "icecast1"))
else if (StringIsEqual(value, "icecast1"))
protocol = SHOUT_PROTOCOL_XAUDIOCAST;
else if (0 == strcmp(value, "icecast2"))
else if (StringIsEqual(value, "icecast2"))
protocol = SHOUT_PROTOCOL_HTTP;
else
throw FormatRuntimeError("shout protocol \"%s\" is not \"shoutcast\" or "
@@ -145,15 +144,15 @@ ShoutOutput::ShoutOutput(const ConfigBlock &block)
unsigned tls;
value = block.GetBlockValue("tls");
if (value != nullptr) {
if (0 == strcmp(value, "disabled"))
if (StringIsEqual(value, "disabled"))
tls = SHOUT_TLS_DISABLED;
else if(0 == strcmp(value, "auto"))
else if (StringIsEqual(value, "auto"))
tls = SHOUT_TLS_AUTO;
else if(0 == strcmp(value, "auto_no_plain"))
else if (StringIsEqual(value, "auto_no_plain"))
tls = SHOUT_TLS_AUTO_NO_PLAIN;
else if(0 == strcmp(value, "rfc2818"))
else if (StringIsEqual(value, "rfc2818"))
tls = SHOUT_TLS_RFC2818;
else if(0 == strcmp(value, "rfc2817"))
else if (StringIsEqual(value, "rfc2817"))
tls = SHOUT_TLS_RFC2817;
else
throw FormatRuntimeError("invalid shout TLS option \"%s\"", value);

View File

@@ -72,17 +72,9 @@ if enable_oss
endif
if is_darwin
output_plugins_sources += 'OSXOutputPlugin.cxx'
audiounit_dep = declare_dependency(
link_args: [
'-framework', 'AudioUnit', '-framework', 'CoreAudio', '-framework', 'CoreServices',
],
dependencies: [
boost_dep,
],
)
else
audiounit_dep = dependency('', required: false)
output_plugins_sources += [
'OSXOutputPlugin.cxx',
]
endif
conf.set('HAVE_OSX', is_darwin)
@@ -145,7 +137,7 @@ output_plugins = static_library(
include_directories: inc,
dependencies: [
alsa_dep,
audiounit_dep,
apple_dep,
libao_dep,
libjack_dep,
pulse_dep,

View File

@@ -27,7 +27,7 @@
#include "thread/Cond.hxx"
#include "util/Macros.hxx"
#include "util/Domain.hxx"
#include "system/ByteOrder.hxx"
#include "util/ByteOrder.hxx"
#include "mixer/MixerList.hxx"
#include "Log.hxx"

View File

@@ -18,7 +18,7 @@
*/
#include "PcmPack.hxx"
#include "system/ByteOrder.hxx"
#include "util/ByteOrder.hxx"
static void
pack_sample(uint8_t *dest, const int32_t *src0) noexcept

View File

@@ -431,6 +431,8 @@ playlist::SetSongIdRange(PlayerControl &pc, unsigned id,
if (position < 0)
throw PlaylistError::NoSuchSong();
bool was_queued = false;
if (playing) {
if (position == current)
throw PlaylistError(PlaylistResult::DENIED,
@@ -442,6 +444,10 @@ playlist::SetSongIdRange(PlayerControl &pc, unsigned id,
already; cancel that */
pc.LockCancel();
queued = -1;
/* schedule a call to UpdateQueuedSong() to
re-queue the song with its new range */
was_queued = true;
}
}
@@ -464,7 +470,8 @@ playlist::SetSongIdRange(PlayerControl &pc, unsigned id,
song.SetEndTime(end);
/* announce the change to all interested subsystems */
UpdateQueuedSong(pc, nullptr);
if (was_queued)
UpdateQueuedSong(pc, nullptr);
queue.ModifyAtPosition(position);
OnModified();
}

View File

@@ -206,6 +206,7 @@ CompositeStorage::Mount(const char *uri, std::unique_ptr<Storage> storage)
const std::lock_guard<Mutex> protect(mutex);
Directory &directory = root.Make(uri);
assert(!directory.storage);
directory.storage = std::move(storage);
}

View File

@@ -100,6 +100,15 @@ public:
gcc_pure gcc_nonnull_all
Storage *GetMount(const char *uri) noexcept;
/**
* Is the given URI a mount point, i.e. is something already
* mounted on this path?
*/
gcc_pure gcc_nonnull_all
bool IsMountPoint(const char *uri) noexcept {
return GetMount(uri) != nullptr;
}
/**
* Call the given function for each mounted storage, including
* the root storage. Passes mount point URI and the a const
@@ -112,6 +121,15 @@ public:
VisitMounts(uri, root, t);
}
/**
* Is a storage with the given URI already mounted?
*/
gcc_pure gcc_nonnull_all
bool IsMounted(const char *storage_uri) const noexcept {
const std::lock_guard<Mutex> protect(mutex);
return IsMounted(root, storage_uri);
}
void Mount(const char *uri, std::unique_ptr<Storage> storage);
bool Unmount(const char *uri);
@@ -146,6 +164,22 @@ private:
}
}
gcc_pure gcc_nonnull_all
static bool IsMounted(const Directory &directory,
const char *storage_uri) noexcept {
if (directory.storage) {
const auto uri = directory.storage->MapUTF8("");
if (uri == storage_uri)
return true;
}
for (const auto &i : directory.children)
if (IsMounted(i.second, storage_uri))
return true;
return false;
}
/**
* Follow the given URI path, and find the outermost directory
* which is a #Storage mount point. If there are no mounts,

View File

@@ -106,6 +106,17 @@ storage_state_restore(const char *line, TextFile &file, Instance &instance)
FormatDebug(storage_domain, "Restoring mount %s => %s", uri.c_str(), url.c_str());
auto &composite_storage = *(CompositeStorage *)instance.storage;
if (composite_storage.IsMountPoint(uri.c_str())) {
LogError(storage_domain, "Mount point busy");
return true;
}
if (composite_storage.IsMounted(url.c_str())) {
LogError(storage_domain, "This storage is already mounted");
return true;
}
auto &event_loop = instance.io_thread.GetEventLoop();
auto storage = CreateStorageURI(event_loop, url.c_str());
if (storage == nullptr) {
@@ -124,8 +135,7 @@ storage_state_restore(const char *line, TextFile &file, Instance &instance)
}
}
((CompositeStorage*)instance.storage)->Mount(uri.c_str(),
std::move(storage));
composite_storage.Mount(uri.c_str(), std::move(storage));
return true;
}

View File

@@ -54,7 +54,7 @@ FormatFatalError(const char *fmt, ...)
{
va_list ap;
va_start(ap, fmt);
LogFormatV(fatal_error_domain, LogLevel::ERROR, fmt, ap);
LogFormatV(LogLevel::ERROR, fatal_error_domain, fmt, ap);
va_end(ap);
Abort();

View File

@@ -19,7 +19,7 @@
#include "Aiff.hxx"
#include "input/InputStream.hxx"
#include "system/ByteOrder.hxx"
#include "util/ByteOrder.hxx"
#include <limits>
#include <stdexcept>

View File

@@ -18,7 +18,7 @@
*/
#include "ApeLoader.hxx"
#include "system/ByteOrder.hxx"
#include "util/ByteOrder.hxx"
#include "input/InputStream.hxx"
#include "util/StringView.hxx"

View File

@@ -19,7 +19,7 @@
#include "Riff.hxx"
#include "input/InputStream.hxx"
#include "system/ByteOrder.hxx"
#include "util/ByteOrder.hxx"
#include <limits>
#include <stdexcept>

View File

@@ -30,7 +30,7 @@
#ifndef BYTE_ORDER_HXX
#define BYTE_ORDER_HXX
#include "util/Compiler.h"
#include "Compiler.h"
#include <stdint.h>
@@ -73,39 +73,39 @@
# endif
#endif
static inline constexpr bool
constexpr bool
IsLittleEndian()
{
return IS_LITTLE_ENDIAN;
}
static inline constexpr bool
constexpr bool
IsBigEndian()
{
return IS_BIG_ENDIAN;
}
static inline constexpr uint16_t
constexpr uint16_t
GenericByteSwap16(uint16_t value)
{
return (value >> 8) | (value << 8);
}
static inline constexpr uint32_t
constexpr uint32_t
GenericByteSwap32(uint32_t value)
{
return (value >> 24) | ((value >> 8) & 0x0000ff00) |
((value << 8) & 0x00ff0000) | (value << 24);
}
static inline constexpr uint64_t
constexpr uint64_t
GenericByteSwap64(uint64_t value)
{
return uint64_t(GenericByteSwap32(uint32_t(value >> 32)))
| (uint64_t(GenericByteSwap32(value)) << 32);
}
static inline constexpr uint16_t
constexpr uint16_t
ByteSwap16(uint16_t value)
{
#if CLANG_OR_GCC_VERSION(4,8)
@@ -115,7 +115,7 @@ ByteSwap16(uint16_t value)
#endif
}
static inline constexpr uint32_t
constexpr uint32_t
ByteSwap32(uint32_t value)
{
#if CLANG_OR_GCC_VERSION(4,3)
@@ -125,7 +125,7 @@ ByteSwap32(uint32_t value)
#endif
}
static inline constexpr uint64_t
constexpr uint64_t
ByteSwap64(uint64_t value)
{
#if CLANG_OR_GCC_VERSION(4,3)
@@ -138,7 +138,7 @@ ByteSwap64(uint64_t value)
/**
* Converts a 16bit value from big endian to the system's byte order
*/
static inline constexpr uint16_t
constexpr uint16_t
FromBE16(uint16_t value)
{
return IsBigEndian() ? value : ByteSwap16(value);
@@ -147,7 +147,7 @@ FromBE16(uint16_t value)
/**
* Converts a 32bit value from big endian to the system's byte order
*/
static inline constexpr uint32_t
constexpr uint32_t
FromBE32(uint32_t value)
{
return IsBigEndian() ? value : ByteSwap32(value);
@@ -156,7 +156,7 @@ FromBE32(uint32_t value)
/**
* Converts a 64bit value from big endian to the system's byte order
*/
static inline constexpr uint64_t
constexpr uint64_t
FromBE64(uint64_t value)
{
return IsBigEndian() ? value : ByteSwap64(value);
@@ -165,7 +165,7 @@ FromBE64(uint64_t value)
/**
* Converts a 16bit value from little endian to the system's byte order
*/
static inline constexpr uint16_t
constexpr uint16_t
FromLE16(uint16_t value)
{
return IsLittleEndian() ? value : ByteSwap16(value);
@@ -174,7 +174,7 @@ FromLE16(uint16_t value)
/**
* Converts a 32bit value from little endian to the system's byte order
*/
static inline constexpr uint32_t
constexpr uint32_t
FromLE32(uint32_t value)
{
return IsLittleEndian() ? value : ByteSwap32(value);
@@ -183,7 +183,7 @@ FromLE32(uint32_t value)
/**
* Converts a 64bit value from little endian to the system's byte order
*/
static inline constexpr uint64_t
constexpr uint64_t
FromLE64(uint64_t value)
{
return IsLittleEndian() ? value : ByteSwap64(value);
@@ -192,7 +192,7 @@ FromLE64(uint64_t value)
/**
* Converts a 16bit value from the system's byte order to big endian
*/
static inline constexpr uint16_t
constexpr uint16_t
ToBE16(uint16_t value)
{
return IsBigEndian() ? value : ByteSwap16(value);
@@ -201,7 +201,7 @@ ToBE16(uint16_t value)
/**
* Converts a 32bit value from the system's byte order to big endian
*/
static inline constexpr uint32_t
constexpr uint32_t
ToBE32(uint32_t value)
{
return IsBigEndian() ? value : ByteSwap32(value);
@@ -210,7 +210,7 @@ ToBE32(uint32_t value)
/**
* Converts a 64bit value from the system's byte order to big endian
*/
static inline constexpr uint64_t
constexpr uint64_t
ToBE64(uint64_t value)
{
return IsBigEndian() ? value : ByteSwap64(value);
@@ -219,7 +219,7 @@ ToBE64(uint64_t value)
/**
* Converts a 16bit value from the system's byte order to little endian
*/
static inline constexpr uint16_t
constexpr uint16_t
ToLE16(uint16_t value)
{
return IsLittleEndian() ? value : ByteSwap16(value);
@@ -228,7 +228,7 @@ ToLE16(uint16_t value)
/**
* Converts a 32bit value from the system's byte order to little endian
*/
static inline constexpr uint32_t
constexpr uint32_t
ToLE32(uint32_t value)
{
return IsLittleEndian() ? value : ByteSwap32(value);
@@ -237,7 +237,7 @@ ToLE32(uint32_t value)
/**
* Converts a 64bit value from the system's byte order to little endian
*/
static inline constexpr uint64_t
constexpr uint64_t
ToLE64(uint64_t value)
{
return IsLittleEndian() ? value : ByteSwap64(value);

View File

@@ -18,7 +18,7 @@
*/
#include "ByteReverse.hxx"
#include "system/ByteOrder.hxx"
#include "util/ByteOrder.hxx"
#include "Compiler.h"
#include <assert.h>

View File

@@ -1,5 +1,5 @@
/*
* Copyright (C) 2013-2017 Max Kellermann <max.kellermann@gmail.com>
* Copyright 2013-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

View File

@@ -1,5 +1,5 @@
/*
* Copyright (C) 2013-2017 Max Kellermann <max.kellermann@gmail.com>
* Copyright 2013-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
@@ -153,18 +153,19 @@ public:
explicit HugeArray(size_type _size)
:buffer(Buffer::FromVoidFloor(HugeAllocate(sizeof(value_type) * _size))) {}
constexpr HugeArray(HugeArray &&other)
constexpr HugeArray(HugeArray &&other) noexcept
:buffer(std::exchange(other.buffer, nullptr)) {}
~HugeArray() {
~HugeArray() noexcept {
if (buffer != nullptr) {
auto v = buffer.ToVoid();
HugeFree(v.data, v.size);
}
}
HugeArray &operator=(HugeArray &&other) {
std::swap(buffer, other.buffer);
HugeArray &operator=(HugeArray &&other) noexcept {
using std::swap;
swap(buffer, other.buffer);
return *this;
}
@@ -178,64 +179,64 @@ public:
HugeDiscard(v.data, v.size);
}
constexpr bool operator==(std::nullptr_t) const {
constexpr bool operator==(std::nullptr_t) const noexcept {
return buffer == nullptr;
}
constexpr bool operator!=(std::nullptr_t) const {
constexpr bool operator!=(std::nullptr_t) const noexcept {
return buffer != nullptr;
}
/**
* Returns the number of allocated elements.
*/
constexpr size_type size() const {
constexpr size_type size() const noexcept {
return buffer.size;
}
reference front() {
reference front() noexcept {
return buffer.front();
}
const_reference front() const {
const_reference front() const noexcept {
return buffer.front();
}
reference back() {
reference back() noexcept {
return buffer.back();
}
const_reference back() const {
const_reference back() const noexcept {
return buffer.back();
}
/**
* Returns one element. No bounds checking.
*/
reference operator[](size_type i) {
reference operator[](size_type i) noexcept {
return buffer[i];
}
/**
* Returns one constant element. No bounds checking.
*/
const_reference operator[](size_type i) const {
const_reference operator[](size_type i) const noexcept {
return buffer[i];
}
iterator begin() {
iterator begin() noexcept {
return buffer.begin();
}
constexpr const_iterator begin() const {
constexpr const_iterator begin() const noexcept {
return buffer.cbegin();
}
iterator end() {
iterator end() noexcept {
return buffer.end();
}
constexpr const_iterator end() const {
constexpr const_iterator end() const noexcept {
return buffer.cend();
}
};

View File

@@ -167,7 +167,12 @@ gcc_pure
static const char *
SkipUriScheme(const char *uri) noexcept
{
const char *const schemes[] = { "http://", "https://", "ftp://" };
static const char *const schemes[] = {
"http://", "https://",
"ftp://",
"smb://",
};
for (auto scheme : schemes) {
auto result = StringAfterPrefixCaseASCII(uri, scheme);
if (result != nullptr)

View File

@@ -20,7 +20,7 @@
#include "config.h"
#include "pcm/PcmExport.hxx"
#include "pcm/Traits.hxx"
#include "system/ByteOrder.hxx"
#include "util/ByteOrder.hxx"
#include "util/ConstBuffer.hxx"
#include <gtest/gtest.h>

View File

@@ -19,7 +19,7 @@
#include "test_pcm_util.hxx"
#include "pcm/PcmPack.hxx"
#include "system/ByteOrder.hxx"
#include "util/ByteOrder.hxx"
#include <gtest/gtest.h>

View File

@@ -25,7 +25,7 @@
#include <stdio.h>
void
Log(const Domain &domain, gcc_unused LogLevel level, const char *msg) noexcept
Log(LogLevel, const Domain &domain, const char *msg) noexcept
{
fprintf(stderr, "[%s] %s\n", domain.GetName(), msg);
}