Merge branch 'v0.21.x'

This commit is contained in:
Max Kellermann 2020-07-06 20:45:29 +02:00
commit c3cfb5fe16
27 changed files with 239 additions and 99 deletions

9
NEWS
View File

@ -43,15 +43,24 @@ ver 0.22 (not yet released)
ver 0.21.25 (not yet released) ver 0.21.25 (not yet released)
* protocol: * protocol:
- fix crash when using "rangeid" while playing - fix crash when using "rangeid" while playing
* database
- simple: automatically scan new mounts
* storage
- fix disappearing mounts after mounting twice
- udisks: fix reading ".mpdignore"
* input * input
- file: detect premature end of file - file: detect premature end of file
- smbclient: don't send credentials to MPD clients - smbclient: don't send credentials to MPD clients
* decoder * decoder
- opus: apply pre-skip and end trimming - opus: apply pre-skip and end trimming
- opus: fix memory leak - opus: fix memory leak
- opus: fix crash bug
- vorbis: fix crash bug
* output * output
- osx: improve sample rate selection - osx: improve sample rate selection
- osx: fix noise while stopping - osx: fix noise while stopping
* neighbor
- upnp: fix crash during shutdown
* Windows/Android: * Windows/Android:
- fix Boost detection after breaking change in Meson 0.54 - fix Boost detection after breaking change in Meson 0.54

View File

@ -60,25 +60,25 @@ The default plugin which gives :program:`MPD` access to local files. It is used
curl 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 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 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`: See :ref:`input_nfs` for more information.
.. 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.
udisks udisks
------ ------
@ -186,7 +186,10 @@ curl
Opens remote files or streams over HTTP using libcurl. 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:: .. list-table::
:widths: 20 80 :widths: 20 80
@ -206,7 +209,9 @@ Note that unless overridden by the below settings (e.g. by setting them to a bla
ffmpeg 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 file
---- ----
@ -218,30 +223,51 @@ mms
Plays streams with the MMS protocol using `libmms <https://launchpad.net/libmms>`_. Plays streams with the MMS protocol using `libmms <https://launchpad.net/libmms>`_.
.. _input_nfs:
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 .. code-block:: none
mpc add nfs://servername/path/filename.ogg 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 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 .. code-block:: none
mpc add smb://servername/sharename/filename.ogg mpc add smb://servername/sharename/filename.ogg
mpc add smb://username:password@servername/sharename/filename.ogg
qobuz 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 .. code-block:: none
@ -267,7 +293,9 @@ Play songs from the commercial streaming service Qobuz. It plays URLs in the for
tidal 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:: .. warning::

View File

@ -29,9 +29,7 @@
*/ */
class PluginUnavailable : public std::runtime_error { class PluginUnavailable : public std::runtime_error {
public: public:
template<typename M> using std::runtime_error::runtime_error;
explicit PluginUnavailable(M &&msg) noexcept
:std::runtime_error(std::forward<M>(msg)) {}
}; };
/** /**
@ -42,9 +40,7 @@ public:
*/ */
class PluginUnconfigured : public PluginUnavailable { class PluginUnconfigured : public PluginUnavailable {
public: public:
template<typename M> using PluginUnavailable::PluginUnavailable;
explicit PluginUnconfigured(M &&msg) noexcept
:PluginUnavailable(std::forward<M>(msg)) {}
}; };
#endif #endif

View File

@ -69,17 +69,22 @@ Song::UpdateFile(Storage &storage)
TagBuilder tag_builder; TagBuilder tag_builder;
auto new_audio_format = AudioFormat::Undefined(); auto new_audio_format = AudioFormat::Undefined();
const auto path_fs = storage.MapFS(relative_uri.c_str()); try {
if (path_fs.IsNull()) { const auto path_fs = storage.MapFS(relative_uri.c_str());
const auto absolute_uri = if (path_fs.IsNull()) {
storage.MapUTF8(relative_uri.c_str()); const auto absolute_uri =
if (!tag_stream_scan(absolute_uri.c_str(), tag_builder, storage.MapUTF8(relative_uri.c_str());
&new_audio_format)) if (!tag_stream_scan(absolute_uri.c_str(), tag_builder,
return false;
} else {
if (!ScanFileTagsWithGeneric(path_fs, tag_builder,
&new_audio_format)) &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; mtime = info.mtime;
@ -139,8 +144,14 @@ DetachedSong::LoadFile(Path path)
return false; return false;
TagBuilder tag_builder; 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; return false;
}
mtime = fi.GetModificationTime(); mtime = fi.GetModificationTime();
tag_builder.Commit(tag); tag_builder.Commit(tag);
@ -157,8 +168,14 @@ DetachedSong::Update()
return LoadFile(path_fs); return LoadFile(path_fs);
} else if (IsRemote()) { } else if (IsRemote()) {
TagBuilder tag_builder; 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; return false;
}
mtime = std::chrono::system_clock::time_point::min(); mtime = std::chrono::system_clock::time_point::min();
tag_builder.Commit(tag); tag_builder.Commit(tag);

View File

@ -43,7 +43,7 @@ CheckDecoderPlugin(const DecoderPlugin &plugin,
} }
bool bool
tag_stream_scan(InputStream &is, TagHandler &handler) noexcept tag_stream_scan(InputStream &is, TagHandler &handler)
{ {
assert(is.IsReady()); assert(is.IsReady());
@ -81,7 +81,7 @@ tag_stream_scan(const char *uri, TagHandler &handler)
bool bool
tag_stream_scan(InputStream &is, TagBuilder &builder, tag_stream_scan(InputStream &is, TagBuilder &builder,
AudioFormat *audio_format) noexcept AudioFormat *audio_format)
{ {
assert(is.IsReady()); assert(is.IsReady());

View File

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

View File

@ -195,6 +195,16 @@ handle_mount(Client &client, Request args, Response &r)
return CommandResult::ERROR; 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 &event_loop = instance.io_thread.GetEventLoop();
auto storage = CreateStorageURI(event_loop, remote_uri); auto storage = CreateStorageURI(event_loop, remote_uri);
if (storage == nullptr) { if (storage == nullptr) {
@ -207,8 +217,10 @@ handle_mount(Client &client, Request args, Response &r)
#ifdef ENABLE_DATABASE #ifdef ENABLE_DATABASE
if (auto *db = dynamic_cast<SimpleDatabase *>(instance.GetDatabase())) { if (auto *db = dynamic_cast<SimpleDatabase *>(instance.GetDatabase())) {
bool need_update;
try { try {
db->Mount(local_uri, remote_uri); need_update = !db->Mount(local_uri, remote_uri);
} catch (...) { } catch (...) {
composite.Unmount(local_uri); composite.Unmount(local_uri);
throw; throw;
@ -217,6 +229,12 @@ handle_mount(Client &client, Request args, Response &r)
// TODO: call Instance::OnDatabaseModified()? // TODO: call Instance::OnDatabaseModified()?
// TODO: trigger database update? // TODO: trigger database update?
instance.EmitIdle(IDLE_DATABASE); instance.EmitIdle(IDLE_DATABASE);
if (need_update) {
UpdateService *update = client.GetInstance().update;
if (update != nullptr)
update->Enqueue(local_uri, false);
}
} }
#endif #endif

View File

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

View File

@ -103,9 +103,11 @@ public:
/** /**
* Throws #std::runtime_error on error. * Throws #std::runtime_error on error.
*
* @return false if the mounted database needs to be updated
*/ */
gcc_nonnull_all 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 gcc_nonnull_all
bool Unmount(const char *uri) noexcept; bool Unmount(const char *uri) noexcept;

View File

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

View File

@ -67,18 +67,22 @@ struct DecoderPlugin {
void (*file_decode)(DecoderClient &client, Path path_fs) = nullptr; void (*file_decode)(DecoderClient &client, Path path_fs) = nullptr;
/** /**
* 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 = nullptr; bool (*scan_file)(Path path_fs, TagHandler &handler) = nullptr;
/** /**
* 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 = nullptr; bool (*scan_stream)(InputStream &is, TagHandler &handler) = nullptr;
/** /**
* @brief Return a "virtual" filename for subtracks in * @brief Return a "virtual" filename for subtracks in
@ -99,14 +103,14 @@ struct DecoderPlugin {
void (*_file_decode)(DecoderClient &client, void (*_file_decode)(DecoderClient &client,
Path path_fs), Path path_fs),
bool (*_scan_file)(Path path_fs, bool (*_scan_file)(Path path_fs,
TagHandler &handler) noexcept) noexcept TagHandler &handler)) noexcept
:name(_name), :name(_name),
file_decode(_file_decode), scan_file(_scan_file) {} file_decode(_file_decode), scan_file(_scan_file) {}
constexpr DecoderPlugin(const char *_name, constexpr DecoderPlugin(const char *_name,
void (*_stream_decode)(DecoderClient &client, void (*_stream_decode)(DecoderClient &client,
InputStream &is), InputStream &is),
bool (*_scan_stream)(InputStream &is, TagHandler &handler) noexcept) noexcept bool (*_scan_stream)(InputStream &is, TagHandler &handler)) noexcept
:name(_name), :name(_name),
stream_decode(_stream_decode), stream_decode(_stream_decode),
scan_stream(_scan_stream) {} scan_stream(_scan_stream) {}
@ -114,11 +118,11 @@ struct DecoderPlugin {
constexpr DecoderPlugin(const char *_name, constexpr DecoderPlugin(const char *_name,
void (*_stream_decode)(DecoderClient &client, void (*_stream_decode)(DecoderClient &client,
InputStream &is), InputStream &is),
bool (*_scan_stream)(InputStream &is, TagHandler &handler) noexcept, bool (*_scan_stream)(InputStream &is, TagHandler &handler),
void (*_file_decode)(DecoderClient &client, void (*_file_decode)(DecoderClient &client,
Path path_fs), Path path_fs),
bool (*_scan_file)(Path path_fs, bool (*_scan_file)(Path path_fs,
TagHandler &handler) noexcept) noexcept TagHandler &handler)) noexcept
:name(_name), :name(_name),
stream_decode(_stream_decode), stream_decode(_stream_decode),
file_decode(_file_decode), file_decode(_file_decode),
@ -191,7 +195,7 @@ struct DecoderPlugin {
* Read the tag of a file. * Read the tag of a file.
*/ */
template<typename P> template<typename P>
bool ScanFile(P path_fs, TagHandler &handler) const noexcept { bool ScanFile(P path_fs, TagHandler &handler) const {
return scan_file != nullptr return scan_file != nullptr
? scan_file(path_fs, handler) ? scan_file(path_fs, handler)
: false; : false;
@ -200,7 +204,7 @@ struct DecoderPlugin {
/** /**
* Read the tag of a stream. * 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 return scan_stream != nullptr
? scan_stream(is, handler) ? scan_stream(is, handler)
: false; : false;

View File

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

View File

@ -448,7 +448,7 @@ dsdiff_stream_decode(DecoderClient &client, InputStream &is)
} }
static bool static bool
dsdiff_scan_stream(InputStream &is, TagHandler &handler) noexcept dsdiff_scan_stream(InputStream &is, TagHandler &handler)
{ {
DsdiffMetaData metadata; DsdiffMetaData metadata;
DsdiffChunkHeader chunk_header; DsdiffChunkHeader chunk_header;

View File

@ -325,7 +325,7 @@ dsf_stream_decode(DecoderClient &client, InputStream &is)
} }
static bool static bool
dsf_scan_stream(InputStream &is, TagHandler &handler) noexcept dsf_scan_stream(InputStream &is, TagHandler &handler)
{ {
/* check DSF metadata */ /* check DSF metadata */
DsfMetaData metadata; DsfMetaData metadata;

View File

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

View File

@ -591,8 +591,7 @@ ffmpeg_decode(DecoderClient &client, InputStream &input)
} }
static bool static bool
FfmpegScanStream(AVFormatContext &format_context, FfmpegScanStream(AVFormatContext &format_context, TagHandler &handler)
TagHandler &handler) noexcept
{ {
const int find_result = const int find_result =
avformat_find_stream_info(&format_context, nullptr); avformat_find_stream_info(&format_context, nullptr);
@ -625,16 +624,14 @@ FfmpegScanStream(AVFormatContext &format_context,
} }
static bool static bool
ffmpeg_scan_stream(InputStream &is, TagHandler &handler) noexcept ffmpeg_scan_stream(InputStream &is, TagHandler &handler)
try { {
AvioStream stream(nullptr, is); AvioStream stream(nullptr, is);
if (!stream.Open()) if (!stream.Open())
return false; return false;
auto f = FfmpegOpenInput(stream.io, is.GetURI(), nullptr); auto f = FfmpegOpenInput(stream.io, is.GetURI(), nullptr);
return FfmpegScanStream(*f, handler); return FfmpegScanStream(*f, handler);
} catch (...) {
return false;
} }
/** /**

View File

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

View File

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

View File

@ -273,7 +273,7 @@ mpcdec_get_file_duration(InputStream &is)
} }
static bool 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); const auto duration = mpcdec_get_file_duration(is);
if (duration.IsNegative()) if (duration.IsNegative())

View File

@ -423,8 +423,8 @@ VisitOpusDuration(InputStream &is, OggSyncState &sync, OggStreamState &stream,
} }
} }
bool static bool
mpd_opus_scan_stream(InputStream &is, TagHandler &handler) noexcept mpd_opus_scan_stream(InputStream &is, TagHandler &handler)
{ {
InputStreamReader reader(is); InputStreamReader reader(is);
OggSyncState oy(reader); OggSyncState oy(reader);

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -100,6 +100,15 @@ public:
gcc_pure gcc_nonnull_all gcc_pure gcc_nonnull_all
Storage *GetMount(std::string_view uri) noexcept; Storage *GetMount(std::string_view 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 * Call the given function for each mounted storage, including
* the root storage. Passes mount point URI and the a const * the root storage. Passes mount point URI and the a const
@ -112,6 +121,15 @@ public:
VisitMounts(uri, root, t); 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); void Mount(const char *uri, std::unique_ptr<Storage> storage);
bool Unmount(const char *uri); 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 * Follow the given URI path, and find the outermost directory
* which is a #Storage mount point. If there are no mounts, * 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()); 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 &event_loop = instance.io_thread.GetEventLoop();
auto storage = CreateStorageURI(event_loop, url.c_str()); auto storage = CreateStorageURI(event_loop, url.c_str());
if (storage == nullptr) { if (storage == nullptr) {
@ -124,8 +135,7 @@ storage_state_restore(const char *line, TextFile &file, Instance &instance)
} }
} }
((CompositeStorage*)instance.storage)->Mount(uri.c_str(), composite_storage.Mount(uri.c_str(), std::move(storage));
std::move(storage));
return true; return true;
} }