Compare commits
34 Commits
Author | SHA1 | Date | |
---|---|---|---|
![]() |
938728820b | ||
![]() |
80531ef8d8 | ||
![]() |
a91fba6a3d | ||
![]() |
f8be403c34 | ||
![]() |
28a5cdf319 | ||
![]() |
6b1d264b35 | ||
![]() |
a6c10e9a1c | ||
![]() |
19a46064e9 | ||
![]() |
b57eeaa720 | ||
![]() |
ad059d5804 | ||
![]() |
6e1940e930 | ||
![]() |
103194e32d | ||
![]() |
481c330c17 | ||
![]() |
7ef489e057 | ||
![]() |
d9e5d5ff5b | ||
![]() |
ca02fb7782 | ||
![]() |
d4d06da2f8 | ||
![]() |
efde78db77 | ||
![]() |
f1b8bcd6b2 | ||
![]() |
c2bc3704e1 | ||
![]() |
def120aca4 | ||
![]() |
6d2b09ac2b | ||
![]() |
78b43a9930 | ||
![]() |
da5ff779c6 | ||
![]() |
e7da5b104d | ||
![]() |
4be76f3c8f | ||
![]() |
c58c53293c | ||
![]() |
8695a2806a | ||
![]() |
a59f1b21a6 | ||
![]() |
9e2d09dabc | ||
![]() |
2719f62feb | ||
![]() |
234cedd6c6 | ||
![]() |
5b946e9d95 | ||
![]() |
b46ca50dcc |
21
NEWS
21
NEWS
@@ -1,3 +1,24 @@
|
|||||||
|
ver 0.22.6 (2021/02/16)
|
||||||
|
* fix missing tags on songs in queue
|
||||||
|
|
||||||
|
ver 0.22.5 (2021/02/15)
|
||||||
|
* protocol
|
||||||
|
- error for malformed ranges instead of ignoring silently
|
||||||
|
- better error message for open-ended range with "move"
|
||||||
|
* database
|
||||||
|
- simple: fix missing CUE sheet metadata in "addid" command
|
||||||
|
* tags
|
||||||
|
- id: translate TPE3 to Conductor, not Performer
|
||||||
|
* archive
|
||||||
|
- iso9660: another fix for unaligned reads
|
||||||
|
* output
|
||||||
|
- httpd: error handling on Windows improved
|
||||||
|
- pulse: fix deadlock with "always_on"
|
||||||
|
* Windows:
|
||||||
|
- enable https:// support (via Schannel)
|
||||||
|
* Android
|
||||||
|
- work around "Permission denied" on mpd.conf
|
||||||
|
|
||||||
ver 0.22.4 (2021/01/21)
|
ver 0.22.4 (2021/01/21)
|
||||||
* protocol
|
* protocol
|
||||||
- add command "binarylimit" to allow larger chunk sizes
|
- add command "binarylimit" to allow larger chunk sizes
|
||||||
|
@@ -2,10 +2,10 @@
|
|||||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
package="org.musicpd"
|
package="org.musicpd"
|
||||||
android:installLocation="auto"
|
android:installLocation="auto"
|
||||||
android:versionCode="51"
|
android:versionCode="54"
|
||||||
android:versionName="0.22.1">
|
android:versionName="0.22.6">
|
||||||
|
|
||||||
<uses-sdk android:minSdkVersion="21" android:targetSdkVersion="28"/>
|
<uses-sdk android:minSdkVersion="21" android:targetSdkVersion="29"/>
|
||||||
|
|
||||||
<uses-feature android:name="android.software.leanback"
|
<uses-feature android:name="android.software.leanback"
|
||||||
android:required="false" />
|
android:required="false" />
|
||||||
@@ -19,6 +19,7 @@
|
|||||||
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
|
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
|
||||||
|
|
||||||
<application android:allowBackup="true"
|
<application android:allowBackup="true"
|
||||||
|
android:requestLegacyExternalStorage="true"
|
||||||
android:icon="@drawable/icon"
|
android:icon="@drawable/icon"
|
||||||
android:banner="@drawable/icon"
|
android:banner="@drawable/icon"
|
||||||
android:label="@string/app_name">
|
android:label="@string/app_name">
|
||||||
|
@@ -5,8 +5,8 @@ android_ndk = get_option('android_ndk')
|
|||||||
android_sdk = get_option('android_sdk')
|
android_sdk = get_option('android_sdk')
|
||||||
android_abi = get_option('android_abi')
|
android_abi = get_option('android_abi')
|
||||||
|
|
||||||
android_sdk_build_tools_version = '27.0.0'
|
android_sdk_build_tools_version = '29.0.3'
|
||||||
android_sdk_platform = 'android-23'
|
android_sdk_platform = 'android-29'
|
||||||
|
|
||||||
android_build_tools_dir = join_paths(android_sdk, 'build-tools', android_sdk_build_tools_version)
|
android_build_tools_dir = join_paths(android_sdk, 'build-tools', android_sdk_build_tools_version)
|
||||||
android_sdk_platform_dir = join_paths(android_sdk, 'platforms', android_sdk_platform)
|
android_sdk_platform_dir = join_paths(android_sdk, 'platforms', android_sdk_platform)
|
||||||
|
@@ -38,7 +38,7 @@ author = 'Max Kellermann'
|
|||||||
# built documents.
|
# built documents.
|
||||||
#
|
#
|
||||||
# The short X.Y version.
|
# The short X.Y version.
|
||||||
version = '0.22.4'
|
version = '0.22.6'
|
||||||
# The full version, including alpha/beta/rc tags.
|
# The full version, including alpha/beta/rc tags.
|
||||||
release = version
|
release = version
|
||||||
|
|
||||||
|
@@ -68,11 +68,11 @@ There are two active branches in the git repository:
|
|||||||
|
|
||||||
- the "unstable" branch called ``master`` where new features are
|
- the "unstable" branch called ``master`` where new features are
|
||||||
merged. This will become the next major release eventually.
|
merged. This will become the next major release eventually.
|
||||||
- the "stable" branch (currently called ``v0.21.x``) where only bug
|
- the "stable" branch (currently called ``v0.22.x``) where only bug
|
||||||
fixes are merged.
|
fixes are merged.
|
||||||
|
|
||||||
Once :program:`MPD` 0.22 is released, a new branch called ``v0.22.x``
|
Once :program:`MPD` 0.23 is released, a new branch called ``v0.23.x``
|
||||||
will be created for 0.22 bug-fix releases; after that, ``v0.21.x``
|
will be created for 0.23 bug-fix releases; after that, ``v0.22.x``
|
||||||
will eventually cease to be maintained.
|
will eventually cease to be maintained.
|
||||||
|
|
||||||
After bug fixes have been added to the "stable" branch, it will be
|
After bug fixes have been added to the "stable" branch, it will be
|
||||||
|
@@ -677,6 +677,11 @@ Whenever possible, ids should be used.
|
|||||||
(directories add recursively). ``URI``
|
(directories add recursively). ``URI``
|
||||||
can also be a single file.
|
can also be a single file.
|
||||||
|
|
||||||
|
Clients that are connected via local socket may add arbitrary
|
||||||
|
local files (URI is an absolute path). Exmaple::
|
||||||
|
|
||||||
|
add "/home/foo/Music/bar.ogg"
|
||||||
|
|
||||||
.. _command_addid:
|
.. _command_addid:
|
||||||
|
|
||||||
:command:`addid {URI} [POSITION]`
|
:command:`addid {URI} [POSITION]`
|
||||||
|
@@ -1,7 +1,7 @@
|
|||||||
project(
|
project(
|
||||||
'mpd',
|
'mpd',
|
||||||
['c', 'cpp'],
|
['c', 'cpp'],
|
||||||
version: '0.22.4',
|
version: '0.22.6',
|
||||||
meson_version: '>= 0.49.0',
|
meson_version: '>= 0.49.0',
|
||||||
default_options: [
|
default_options: [
|
||||||
'c_std=c11',
|
'c_std=c11',
|
||||||
|
@@ -407,6 +407,9 @@ curl = AutotoolsProject(
|
|||||||
'--disable-progress-meter',
|
'--disable-progress-meter',
|
||||||
'--disable-alt-svc',
|
'--disable-alt-svc',
|
||||||
'--without-gnutls', '--without-nss', '--without-libssh2',
|
'--without-gnutls', '--without-nss', '--without-libssh2',
|
||||||
|
|
||||||
|
# native Windows SSL/TLS support, option ignored on non-Windows builds
|
||||||
|
'--with-schannel',
|
||||||
],
|
],
|
||||||
|
|
||||||
patches='src/lib/curl/patches',
|
patches='src/lib/curl/patches',
|
||||||
|
@@ -221,8 +221,8 @@ public:
|
|||||||
if (new_offset > size)
|
if (new_offset > size)
|
||||||
throw std::runtime_error("Invalid seek offset");
|
throw std::runtime_error("Invalid seek offset");
|
||||||
|
|
||||||
|
offset = new_offset;
|
||||||
skip = new_offset % ISO_BLOCKSIZE;
|
skip = new_offset % ISO_BLOCKSIZE;
|
||||||
offset = new_offset - skip;
|
|
||||||
buffer.Clear();
|
buffer.Clear();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@@ -260,13 +260,13 @@ Iso9660InputStream::Read(std::unique_lock<Mutex> &,
|
|||||||
if (r.empty()) {
|
if (r.empty()) {
|
||||||
/* the buffer is empty - read more data from the ISO file */
|
/* the buffer is empty - read more data from the ISO file */
|
||||||
|
|
||||||
assert(offset % ISO_BLOCKSIZE == 0);
|
assert((offset - skip) % ISO_BLOCKSIZE == 0);
|
||||||
|
|
||||||
const ScopeUnlock unlock(mutex);
|
const ScopeUnlock unlock(mutex);
|
||||||
|
|
||||||
const lsn_t read_lsn = lsn + offset / ISO_BLOCKSIZE;
|
const lsn_t read_lsn = lsn + offset / ISO_BLOCKSIZE;
|
||||||
|
|
||||||
if (read_size >= ISO_BLOCKSIZE) {
|
if (read_size >= ISO_BLOCKSIZE && skip == 0) {
|
||||||
/* big read - read right into the caller's buffer */
|
/* big read - read right into the caller's buffer */
|
||||||
|
|
||||||
auto nbytes = iso->SeekRead(ptr, read_lsn,
|
auto nbytes = iso->SeekRead(ptr, read_lsn,
|
||||||
|
@@ -326,6 +326,11 @@ CommandResult
|
|||||||
handle_move(Client &client, Request args, [[maybe_unused]] Response &r)
|
handle_move(Client &client, Request args, [[maybe_unused]] Response &r)
|
||||||
{
|
{
|
||||||
RangeArg range = args.ParseRange(0);
|
RangeArg range = args.ParseRange(0);
|
||||||
|
if (range.IsOpenEnded()) {
|
||||||
|
r.Error(ACK_ERROR_ARG, "Open-ended range not supported");
|
||||||
|
return CommandResult::ERROR;
|
||||||
|
}
|
||||||
|
|
||||||
int to = args.ParseInt(1);
|
int to = args.ParseInt(1);
|
||||||
client.GetPartition().MoveRange(range.start, range.end, to);
|
client.GetPartition().MoveRange(range.start, range.end, to);
|
||||||
return CommandResult::OK;
|
return CommandResult::OK;
|
||||||
|
@@ -29,6 +29,12 @@
|
|||||||
* a #LightSong, e.g. a merged #Tag.
|
* a #LightSong, e.g. a merged #Tag.
|
||||||
*/
|
*/
|
||||||
class ExportedSong : public LightSong {
|
class ExportedSong : public LightSong {
|
||||||
|
/**
|
||||||
|
* A reference target for LightSong::tag, but it is only used
|
||||||
|
* if this instance "owns" the #Tag. For instances referring
|
||||||
|
* to a foreign #Tag instance (e.g. a Song::tag), this field
|
||||||
|
* is not used (and empty).
|
||||||
|
*/
|
||||||
Tag tag_buffer;
|
Tag tag_buffer;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
@@ -37,6 +43,25 @@ public:
|
|||||||
ExportedSong(const char *_uri, Tag &&_tag) noexcept
|
ExportedSong(const char *_uri, Tag &&_tag) noexcept
|
||||||
:LightSong(_uri, tag_buffer),
|
:LightSong(_uri, tag_buffer),
|
||||||
tag_buffer(std::move(_tag)) {}
|
tag_buffer(std::move(_tag)) {}
|
||||||
|
|
||||||
|
/* this custom move constructor is necessary so LightSong::tag
|
||||||
|
points to this instance's #Tag field instead of leaving a
|
||||||
|
dangling reference to the source object's #Tag field */
|
||||||
|
ExportedSong(ExportedSong &&src) noexcept
|
||||||
|
:LightSong(src,
|
||||||
|
/* refer to tag_buffer only if the
|
||||||
|
moved-from instance also owned the Tag
|
||||||
|
which its LightSong::tag field refers
|
||||||
|
to */
|
||||||
|
OwnsTag() ? tag_buffer : src.tag),
|
||||||
|
tag_buffer(std::move(src.tag_buffer)) {}
|
||||||
|
|
||||||
|
ExportedSong &operator=(ExportedSong &&) = delete;
|
||||||
|
|
||||||
|
private:
|
||||||
|
bool OwnsTag() const noexcept {
|
||||||
|
return &tag == &tag_buffer;
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
@@ -36,7 +36,7 @@ BufferedSocket::DirectRead(void *data, size_t length) noexcept
|
|||||||
}
|
}
|
||||||
|
|
||||||
const auto code = GetSocketError();
|
const auto code = GetSocketError();
|
||||||
if (IsSocketErrorAgain(code))
|
if (IsSocketErrorReceiveWouldBlock(code))
|
||||||
return 0;
|
return 0;
|
||||||
|
|
||||||
if (IsSocketErrorClosed(code))
|
if (IsSocketErrorClosed(code))
|
||||||
|
@@ -31,7 +31,7 @@ FullyBufferedSocket::DirectWrite(const void *data, size_t length) noexcept
|
|||||||
const auto nbytes = GetSocket().Write((const char *)data, length);
|
const auto nbytes = GetSocket().Write((const char *)data, length);
|
||||||
if (gcc_unlikely(nbytes < 0)) {
|
if (gcc_unlikely(nbytes < 0)) {
|
||||||
const auto code = GetSocketError();
|
const auto code = GetSocketError();
|
||||||
if (IsSocketErrorAgain(code))
|
if (IsSocketErrorSendWouldBlock(code))
|
||||||
return 0;
|
return 0;
|
||||||
|
|
||||||
IdleMonitor::Cancel();
|
IdleMonitor::Cancel();
|
||||||
|
@@ -1,20 +1,30 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright 2003-2021 The Music Player Daemon Project
|
* Copyright 2015-2021 Max Kellermann <max.kellermann@gmail.com>
|
||||||
* http://www.musicpd.org
|
|
||||||
*
|
*
|
||||||
* This program is free software; you can redistribute it and/or modify
|
* Redistribution and use in source and binary forms, with or without
|
||||||
* it under the terms of the GNU General Public License as published by
|
* modification, are permitted provided that the following conditions
|
||||||
* the Free Software Foundation; either version 2 of the License, or
|
* are met:
|
||||||
* (at your option) any later version.
|
|
||||||
*
|
*
|
||||||
* This program is distributed in the hope that it will be useful,
|
* - Redistributions of source code must retain the above copyright
|
||||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
* notice, this list of conditions and the following disclaimer.
|
||||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
* GNU General Public License for more details.
|
|
||||||
*
|
*
|
||||||
* You should have received a copy of the GNU General Public License along
|
* - Redistributions in binary form must reproduce the above copyright
|
||||||
* with this program; if not, write to the Free Software Foundation, Inc.,
|
* notice, this list of conditions and the following disclaimer in the
|
||||||
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
* 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 "SocketError.hxx"
|
#include "SocketError.hxx"
|
||||||
|
@@ -1,24 +1,34 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright 2003-2021 The Music Player Daemon Project
|
* Copyright 2015-2021 Max Kellermann <max.kellermann@gmail.com>
|
||||||
* http://www.musicpd.org
|
|
||||||
*
|
*
|
||||||
* This program is free software; you can redistribute it and/or modify
|
* Redistribution and use in source and binary forms, with or without
|
||||||
* it under the terms of the GNU General Public License as published by
|
* modification, are permitted provided that the following conditions
|
||||||
* the Free Software Foundation; either version 2 of the License, or
|
* are met:
|
||||||
* (at your option) any later version.
|
|
||||||
*
|
*
|
||||||
* This program is distributed in the hope that it will be useful,
|
* - Redistributions of source code must retain the above copyright
|
||||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
* notice, this list of conditions and the following disclaimer.
|
||||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
* GNU General Public License for more details.
|
|
||||||
*
|
*
|
||||||
* You should have received a copy of the GNU General Public License along
|
* - Redistributions in binary form must reproduce the above copyright
|
||||||
* with this program; if not, write to the Free Software Foundation, Inc.,
|
* notice, this list of conditions and the following disclaimer in the
|
||||||
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
* 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 MPD_SOCKET_ERROR_HXX
|
#ifndef SOCKET_ERROR_HXX
|
||||||
#define MPD_SOCKET_ERROR_HXX
|
#define SOCKET_ERROR_HXX
|
||||||
|
|
||||||
#include "util/Compiler.h"
|
#include "util/Compiler.h"
|
||||||
#include "system/Error.hxx"
|
#include "system/Error.hxx"
|
||||||
@@ -42,14 +52,79 @@ GetSocketError() noexcept
|
|||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
gcc_const
|
constexpr bool
|
||||||
static inline bool
|
IsSocketErrorInProgress(socket_error_t code) noexcept
|
||||||
IsSocketErrorAgain(socket_error_t code) noexcept
|
|
||||||
{
|
{
|
||||||
#ifdef _WIN32
|
#ifdef _WIN32
|
||||||
return code == WSAEINPROGRESS;
|
return code == WSAEINPROGRESS;
|
||||||
#else
|
#else
|
||||||
return code == EAGAIN;
|
return code == EINPROGRESS;
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
constexpr bool
|
||||||
|
IsSocketErrorWouldBlock(socket_error_t code) noexcept
|
||||||
|
{
|
||||||
|
#ifdef _WIN32
|
||||||
|
return code == WSAEWOULDBLOCK;
|
||||||
|
#else
|
||||||
|
return code == EWOULDBLOCK;
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
constexpr bool
|
||||||
|
IsSocketErrorConnectWouldBlock(socket_error_t code) noexcept
|
||||||
|
{
|
||||||
|
#if defined(_WIN32) || defined(__linux__)
|
||||||
|
/* on Windows, WSAEINPROGRESS is for blocking sockets and
|
||||||
|
WSAEWOULDBLOCK for non-blocking sockets */
|
||||||
|
/* on Linux, EAGAIN==EWOULDBLOCK is for local sockets and
|
||||||
|
EINPROGRESS is for all other sockets */
|
||||||
|
return IsSocketErrorInProgress(code) || IsSocketErrorWouldBlock(code);
|
||||||
|
#else
|
||||||
|
/* on all other operating systems, there's just EINPROGRESS */
|
||||||
|
return IsSocketErrorInProgress(code);
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
constexpr bool
|
||||||
|
IsSocketErrorSendWouldBlock(socket_error_t code) noexcept
|
||||||
|
{
|
||||||
|
#ifdef _WIN32
|
||||||
|
/* on Windows, WSAEINPROGRESS is for blocking sockets and
|
||||||
|
WSAEWOULDBLOCK for non-blocking sockets */
|
||||||
|
return IsSocketErrorInProgress(code) || IsSocketErrorWouldBlock(code);
|
||||||
|
#else
|
||||||
|
/* on all other operating systems, there's just EAGAIN==EWOULDBLOCK */
|
||||||
|
return IsSocketErrorWouldBlock(code);
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
constexpr bool
|
||||||
|
IsSocketErrorReceiveWouldBlock(socket_error_t code) noexcept
|
||||||
|
{
|
||||||
|
#ifdef _WIN32
|
||||||
|
/* on Windows, WSAEINPROGRESS is for blocking sockets and
|
||||||
|
WSAEWOULDBLOCK for non-blocking sockets */
|
||||||
|
return IsSocketErrorInProgress(code) || IsSocketErrorWouldBlock(code);
|
||||||
|
#else
|
||||||
|
/* on all other operating systems, there's just
|
||||||
|
EAGAIN==EWOULDBLOCK */
|
||||||
|
return IsSocketErrorWouldBlock(code);
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
constexpr bool
|
||||||
|
IsSocketErrorAcceptWouldBlock(socket_error_t code) noexcept
|
||||||
|
{
|
||||||
|
#ifdef _WIN32
|
||||||
|
/* on Windows, WSAEINPROGRESS is for blocking sockets and
|
||||||
|
WSAEWOULDBLOCK for non-blocking sockets */
|
||||||
|
return IsSocketErrorInProgress(code) || IsSocketErrorWouldBlock(code);
|
||||||
|
#else
|
||||||
|
/* on all other operating systems, there's just
|
||||||
|
EAGAIN==EWOULDBLOCK */
|
||||||
|
return IsSocketErrorWouldBlock(code);
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -181,6 +181,14 @@ class AudioOutputControl {
|
|||||||
*/
|
*/
|
||||||
bool open = false;
|
bool open = false;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Is the device currently playing, i.e. is its buffer
|
||||||
|
* (likely) non-empty? If not, then it will never be drained.
|
||||||
|
*
|
||||||
|
* This field is only valid while the output is open.
|
||||||
|
*/
|
||||||
|
bool playing;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Is the device paused? i.e. the output thread is in the
|
* Is the device paused? i.e. the output thread is in the
|
||||||
* ao_pause() loop.
|
* ao_pause() loop.
|
||||||
|
@@ -53,7 +53,7 @@ AudioOutputControl::InternalOpen2(const AudioFormat in_audio_format)
|
|||||||
if (open && cf != output->filter_audio_format)
|
if (open && cf != output->filter_audio_format)
|
||||||
/* if the filter's output format changes, the output
|
/* if the filter's output format changes, the output
|
||||||
must be reopened as well */
|
must be reopened as well */
|
||||||
InternalCloseOutput(true);
|
InternalCloseOutput(playing);
|
||||||
|
|
||||||
output->filter_audio_format = cf;
|
output->filter_audio_format = cf;
|
||||||
|
|
||||||
@@ -64,6 +64,7 @@ AudioOutputControl::InternalOpen2(const AudioFormat in_audio_format)
|
|||||||
}
|
}
|
||||||
|
|
||||||
open = true;
|
open = true;
|
||||||
|
playing = false;
|
||||||
} else if (in_audio_format != output->out_audio_format) {
|
} else if (in_audio_format != output->out_audio_format) {
|
||||||
/* reconfigure the final ConvertFilter for its new
|
/* reconfigure the final ConvertFilter for its new
|
||||||
input AudioFormat */
|
input AudioFormat */
|
||||||
@@ -285,6 +286,9 @@ AudioOutputControl::PlayChunk(std::unique_lock<Mutex> &lock) noexcept
|
|||||||
assert(nbytes % output->out_audio_format.GetFrameSize() == 0);
|
assert(nbytes % output->out_audio_format.GetFrameSize() == 0);
|
||||||
|
|
||||||
source.ConsumeData(nbytes);
|
source.ConsumeData(nbytes);
|
||||||
|
|
||||||
|
/* there's data to be drained from now on */
|
||||||
|
playing = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
@@ -371,6 +375,9 @@ AudioOutputControl::InternalPause(std::unique_lock<Mutex> &lock) noexcept
|
|||||||
}
|
}
|
||||||
|
|
||||||
skip_delay = true;
|
skip_delay = true;
|
||||||
|
|
||||||
|
/* ignore drain commands until we got something new to play */
|
||||||
|
playing = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
static void
|
static void
|
||||||
@@ -390,6 +397,10 @@ PlayFull(FilteredAudioOutput &output, ConstBuffer<void> _buffer)
|
|||||||
inline void
|
inline void
|
||||||
AudioOutputControl::InternalDrain() noexcept
|
AudioOutputControl::InternalDrain() noexcept
|
||||||
{
|
{
|
||||||
|
/* after this method finishes, there's nothing left to be
|
||||||
|
drained */
|
||||||
|
playing = false;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
/* flush the filter and play its remaining output */
|
/* flush the filter and play its remaining output */
|
||||||
|
|
||||||
@@ -518,6 +529,7 @@ AudioOutputControl::Task() noexcept
|
|||||||
source.Cancel();
|
source.Cancel();
|
||||||
|
|
||||||
if (open) {
|
if (open) {
|
||||||
|
playing = false;
|
||||||
const ScopeUnlock unlock(mutex);
|
const ScopeUnlock unlock(mutex);
|
||||||
output->Cancel();
|
output->Cancel();
|
||||||
}
|
}
|
||||||
|
@@ -56,8 +56,6 @@ class PulseOutput final : AudioOutput {
|
|||||||
|
|
||||||
size_t writable;
|
size_t writable;
|
||||||
|
|
||||||
bool pause;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Was Interrupt() called? This will unblock Play(). It will
|
* Was Interrupt() called? This will unblock Play(). It will
|
||||||
* be reset by Cancel() and Pause(), as documented by the
|
* be reset by Cancel() and Pause(), as documented by the
|
||||||
@@ -113,6 +111,7 @@ public:
|
|||||||
|
|
||||||
[[nodiscard]] std::chrono::steady_clock::duration Delay() const noexcept override;
|
[[nodiscard]] std::chrono::steady_clock::duration Delay() const noexcept override;
|
||||||
size_t Play(const void *chunk, size_t size) override;
|
size_t Play(const void *chunk, size_t size) override;
|
||||||
|
void Drain() override;
|
||||||
void Cancel() noexcept override;
|
void Cancel() noexcept override;
|
||||||
bool Pause() override;
|
bool Pause() override;
|
||||||
|
|
||||||
@@ -688,7 +687,6 @@ PulseOutput::Open(AudioFormat &audio_format)
|
|||||||
"pa_stream_connect_playback() has failed");
|
"pa_stream_connect_playback() has failed");
|
||||||
}
|
}
|
||||||
|
|
||||||
pause = false;
|
|
||||||
interrupted = false;
|
interrupted = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -699,17 +697,6 @@ PulseOutput::Close() noexcept
|
|||||||
|
|
||||||
Pulse::LockGuard lock(mainloop);
|
Pulse::LockGuard lock(mainloop);
|
||||||
|
|
||||||
if (pa_stream_get_state(stream) == PA_STREAM_READY) {
|
|
||||||
pa_operation *o =
|
|
||||||
pa_stream_drain(stream,
|
|
||||||
pulse_output_stream_success_cb, this);
|
|
||||||
if (o == nullptr) {
|
|
||||||
LogPulseError(context,
|
|
||||||
"pa_stream_drain() has failed");
|
|
||||||
} else
|
|
||||||
pulse_wait_for_operation(mainloop, o);
|
|
||||||
}
|
|
||||||
|
|
||||||
DeleteStream();
|
DeleteStream();
|
||||||
|
|
||||||
if (context != nullptr &&
|
if (context != nullptr &&
|
||||||
@@ -780,7 +767,7 @@ PulseOutput::Delay() const noexcept
|
|||||||
Pulse::LockGuard lock(mainloop);
|
Pulse::LockGuard lock(mainloop);
|
||||||
|
|
||||||
auto result = std::chrono::steady_clock::duration::zero();
|
auto result = std::chrono::steady_clock::duration::zero();
|
||||||
if (pause && pa_stream_is_corked(stream) &&
|
if (pa_stream_is_corked(stream) &&
|
||||||
pa_stream_get_state(stream) == PA_STREAM_READY)
|
pa_stream_get_state(stream) == PA_STREAM_READY)
|
||||||
/* idle while paused */
|
/* idle while paused */
|
||||||
result = std::chrono::seconds(1);
|
result = std::chrono::seconds(1);
|
||||||
@@ -796,8 +783,6 @@ PulseOutput::Play(const void *chunk, size_t size)
|
|||||||
|
|
||||||
Pulse::LockGuard lock(mainloop);
|
Pulse::LockGuard lock(mainloop);
|
||||||
|
|
||||||
pause = false;
|
|
||||||
|
|
||||||
/* check if the stream is (already) connected */
|
/* check if the stream is (already) connected */
|
||||||
|
|
||||||
WaitStream();
|
WaitStream();
|
||||||
@@ -840,6 +825,25 @@ PulseOutput::Play(const void *chunk, size_t size)
|
|||||||
return size;
|
return size;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
PulseOutput::Drain()
|
||||||
|
{
|
||||||
|
Pulse::LockGuard lock(mainloop);
|
||||||
|
|
||||||
|
if (pa_stream_get_state(stream) != PA_STREAM_READY ||
|
||||||
|
pa_stream_is_suspended(stream) ||
|
||||||
|
pa_stream_is_corked(stream))
|
||||||
|
return;
|
||||||
|
|
||||||
|
pa_operation *o =
|
||||||
|
pa_stream_drain(stream,
|
||||||
|
pulse_output_stream_success_cb, this);
|
||||||
|
if (o == nullptr)
|
||||||
|
throw MakePulseError(context, "pa_stream_drain() failed");
|
||||||
|
|
||||||
|
pulse_wait_for_operation(mainloop, o);
|
||||||
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
PulseOutput::Cancel() noexcept
|
PulseOutput::Cancel() noexcept
|
||||||
{
|
{
|
||||||
@@ -876,7 +880,6 @@ PulseOutput::Pause()
|
|||||||
|
|
||||||
Pulse::LockGuard lock(mainloop);
|
Pulse::LockGuard lock(mainloop);
|
||||||
|
|
||||||
pause = true;
|
|
||||||
interrupted = false;
|
interrupted = false;
|
||||||
|
|
||||||
/* check if the stream is (already/still) connected */
|
/* check if the stream is (already/still) connected */
|
||||||
|
@@ -24,6 +24,7 @@
|
|||||||
#include "mixer/MixerList.hxx"
|
#include "mixer/MixerList.hxx"
|
||||||
#include "thread/Cond.hxx"
|
#include "thread/Cond.hxx"
|
||||||
#include "thread/Mutex.hxx"
|
#include "thread/Mutex.hxx"
|
||||||
|
#include "thread/Name.hxx"
|
||||||
#include "thread/Thread.hxx"
|
#include "thread/Thread.hxx"
|
||||||
#include "util/AllocatedString.hxx"
|
#include "util/AllocatedString.hxx"
|
||||||
#include "util/Domain.hxx"
|
#include "util/Domain.hxx"
|
||||||
@@ -231,6 +232,7 @@ IAudioClient *wasapi_output_get_client(WasapiOutput &output) noexcept {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void WasapiOutputThread::Work() noexcept {
|
void WasapiOutputThread::Work() noexcept {
|
||||||
|
SetThreadName("Wasapi Output Worker");
|
||||||
FormatDebug(wasapi_output_domain, "Working thread started");
|
FormatDebug(wasapi_output_domain, "Working thread started");
|
||||||
try {
|
try {
|
||||||
com.emplace();
|
com.emplace();
|
||||||
|
@@ -278,7 +278,7 @@ HttpdClient::TryWrite() noexcept
|
|||||||
metadata_current_position);
|
metadata_current_position);
|
||||||
if (nbytes < 0) {
|
if (nbytes < 0) {
|
||||||
auto e = GetSocketError();
|
auto e = GetSocketError();
|
||||||
if (IsSocketErrorAgain(e))
|
if (IsSocketErrorSendWouldBlock(e))
|
||||||
return true;
|
return true;
|
||||||
|
|
||||||
if (!IsSocketErrorClosed(e)) {
|
if (!IsSocketErrorClosed(e)) {
|
||||||
@@ -305,7 +305,7 @@ HttpdClient::TryWrite() noexcept
|
|||||||
ssize_t nbytes = GetSocket().Write(&empty_data, 1);
|
ssize_t nbytes = GetSocket().Write(&empty_data, 1);
|
||||||
if (nbytes < 0) {
|
if (nbytes < 0) {
|
||||||
auto e = GetSocketError();
|
auto e = GetSocketError();
|
||||||
if (IsSocketErrorAgain(e))
|
if (IsSocketErrorSendWouldBlock(e))
|
||||||
return true;
|
return true;
|
||||||
|
|
||||||
if (!IsSocketErrorClosed(e)) {
|
if (!IsSocketErrorClosed(e)) {
|
||||||
@@ -328,7 +328,7 @@ HttpdClient::TryWrite() noexcept
|
|||||||
bytes_to_write);
|
bytes_to_write);
|
||||||
if (nbytes < 0) {
|
if (nbytes < 0) {
|
||||||
auto e = GetSocketError();
|
auto e = GetSocketError();
|
||||||
if (IsSocketErrorAgain(e))
|
if (IsSocketErrorSendWouldBlock(e))
|
||||||
return true;
|
return true;
|
||||||
|
|
||||||
if (!IsSocketErrorClosed(e)) {
|
if (!IsSocketErrorClosed(e)) {
|
||||||
|
@@ -94,7 +94,7 @@ ParseCommandArgRange(const char *s)
|
|||||||
s);
|
s);
|
||||||
|
|
||||||
if (test == test2)
|
if (test == test2)
|
||||||
value = std::numeric_limits<int>::max();
|
return RangeArg::OpenEnded(range.start);
|
||||||
|
|
||||||
if (value < 0)
|
if (value < 0)
|
||||||
throw FormatProtocolError(ACK_ERROR_ARG,
|
throw FormatProtocolError(ACK_ERROR_ARG,
|
||||||
@@ -107,9 +107,13 @@ ParseCommandArgRange(const char *s)
|
|||||||
|
|
||||||
range.end = (unsigned)value;
|
range.end = (unsigned)value;
|
||||||
} else {
|
} else {
|
||||||
range.end = (unsigned)value + 1;
|
return RangeArg::Single(range.start);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!range.IsWellFormed())
|
||||||
|
throw FormatProtocolError(ACK_ERROR_ARG,
|
||||||
|
"Malformed range: %s", s);
|
||||||
|
|
||||||
return range;
|
return range;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -25,8 +25,22 @@
|
|||||||
struct RangeArg {
|
struct RangeArg {
|
||||||
unsigned start, end;
|
unsigned start, end;
|
||||||
|
|
||||||
static constexpr RangeArg All() {
|
/**
|
||||||
return { 0, std::numeric_limits<unsigned>::max() };
|
* Construct an open-ended range starting at the given index.
|
||||||
|
*/
|
||||||
|
static constexpr RangeArg OpenEnded(unsigned start) noexcept {
|
||||||
|
return { start, std::numeric_limits<unsigned>::max() };
|
||||||
|
}
|
||||||
|
|
||||||
|
static constexpr RangeArg All() noexcept {
|
||||||
|
return OpenEnded(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Construct an instance describing exactly one index.
|
||||||
|
*/
|
||||||
|
static constexpr RangeArg Single(unsigned i) noexcept {
|
||||||
|
return { i, i + 1 };
|
||||||
}
|
}
|
||||||
|
|
||||||
constexpr bool operator==(RangeArg other) const noexcept {
|
constexpr bool operator==(RangeArg other) const noexcept {
|
||||||
@@ -37,13 +51,45 @@ struct RangeArg {
|
|||||||
return !(*this == other);
|
return !(*this == other);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
constexpr bool IsOpenEnded() const noexcept {
|
||||||
|
return end == All().end;
|
||||||
|
}
|
||||||
|
|
||||||
constexpr bool IsAll() const noexcept {
|
constexpr bool IsAll() const noexcept {
|
||||||
return *this == All();
|
return *this == All();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
constexpr bool IsWellFormed() const noexcept {
|
||||||
|
return start <= end;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Is this range empty? A malformed range also counts as
|
||||||
|
* "empty" for this method.
|
||||||
|
*/
|
||||||
|
constexpr bool IsEmpty() const noexcept {
|
||||||
|
return start >= end;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if the range contains at least this number of items.
|
||||||
|
* Unlike Count(), this allows the object to be malformed.
|
||||||
|
*/
|
||||||
|
constexpr bool HasAtLeast(unsigned n) const noexcept {
|
||||||
|
return start + n <= end;
|
||||||
|
}
|
||||||
|
|
||||||
constexpr bool Contains(unsigned i) const noexcept {
|
constexpr bool Contains(unsigned i) const noexcept {
|
||||||
return i >= start && i < end;
|
return i >= start && i < end;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Count the number of items covered by this range. This requires the
|
||||||
|
* object to be well-formed.
|
||||||
|
*/
|
||||||
|
constexpr unsigned Count() const noexcept {
|
||||||
|
return end - start;
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
@@ -88,6 +88,19 @@ struct LightSong {
|
|||||||
LightSong(const char *_uri, const Tag &_tag) noexcept
|
LightSong(const char *_uri, const Tag &_tag) noexcept
|
||||||
:uri(_uri), tag(_tag) {}
|
:uri(_uri), tag(_tag) {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A copy constructor which copies all fields, but only sets
|
||||||
|
* the tag to a caller-provided reference. This is used by
|
||||||
|
* the #ExportedSong move constructor.
|
||||||
|
*/
|
||||||
|
LightSong(const LightSong &src, const Tag &_tag) noexcept
|
||||||
|
:directory(src.directory), uri(src.uri),
|
||||||
|
real_uri(src.real_uri),
|
||||||
|
tag(_tag),
|
||||||
|
mtime(src.mtime),
|
||||||
|
start_time(src.start_time), end_time(src.end_time),
|
||||||
|
audio_format(src.audio_format) {}
|
||||||
|
|
||||||
gcc_pure
|
gcc_pure
|
||||||
std::string GetURI() const noexcept {
|
std::string GetURI() const noexcept {
|
||||||
if (directory == nullptr)
|
if (directory == nullptr)
|
||||||
|
@@ -352,7 +352,7 @@ scan_id3_tag(const struct id3_tag *tag, TagHandler &handler) noexcept
|
|||||||
handler);
|
handler);
|
||||||
tag_id3_import_text(tag, ID3_FRAME_COMPOSER, TAG_COMPOSER,
|
tag_id3_import_text(tag, ID3_FRAME_COMPOSER, TAG_COMPOSER,
|
||||||
handler);
|
handler);
|
||||||
tag_id3_import_text(tag, "TPE3", TAG_PERFORMER,
|
tag_id3_import_text(tag, "TPE3", TAG_CONDUCTOR,
|
||||||
handler);
|
handler);
|
||||||
tag_id3_import_text(tag, "TPE4", TAG_PERFORMER, handler);
|
tag_id3_import_text(tag, "TPE4", TAG_PERFORMER, handler);
|
||||||
tag_id3_import_text(tag, "TIT1", TAG_GROUPING, handler);
|
tag_id3_import_text(tag, "TIT1", TAG_GROUPING, handler);
|
||||||
|
@@ -112,6 +112,6 @@ template <typename T>
|
|||||||
void swap(ComPtr<T> &lhs, ComPtr<T> &rhs) noexcept {
|
void swap(ComPtr<T> &lhs, ComPtr<T> &rhs) noexcept {
|
||||||
lhs.swap(rhs);
|
lhs.swap(rhs);
|
||||||
}
|
}
|
||||||
}
|
} // namespace std
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
@@ -59,6 +59,7 @@ case x:
|
|||||||
C(E_INVALIDARG);
|
C(E_INVALIDARG);
|
||||||
C(E_OUTOFMEMORY);
|
C(E_OUTOFMEMORY);
|
||||||
C(E_POINTER);
|
C(E_POINTER);
|
||||||
|
C(NO_ERROR);
|
||||||
#undef C
|
#undef C
|
||||||
}
|
}
|
||||||
return std::string_view();
|
return std::string_view();
|
||||||
|
@@ -56,6 +56,8 @@ struct CommandLine {
|
|||||||
|
|
||||||
FromNarrowPath config_path;
|
FromNarrowPath config_path;
|
||||||
|
|
||||||
|
std::size_t seek = 0;
|
||||||
|
|
||||||
std::size_t chunk_size = MAX_CHUNK_SIZE;
|
std::size_t chunk_size = MAX_CHUNK_SIZE;
|
||||||
|
|
||||||
bool verbose = false;
|
bool verbose = false;
|
||||||
@@ -67,6 +69,7 @@ enum Option {
|
|||||||
OPTION_CONFIG,
|
OPTION_CONFIG,
|
||||||
OPTION_VERBOSE,
|
OPTION_VERBOSE,
|
||||||
OPTION_SCAN,
|
OPTION_SCAN,
|
||||||
|
OPTION_SEEK,
|
||||||
OPTION_CHUNK_SIZE,
|
OPTION_CHUNK_SIZE,
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -74,6 +77,7 @@ static constexpr OptionDef option_defs[] = {
|
|||||||
{"config", 0, true, "Load a MPD configuration file"},
|
{"config", 0, true, "Load a MPD configuration file"},
|
||||||
{"verbose", 'v', false, "Verbose logging"},
|
{"verbose", 'v', false, "Verbose logging"},
|
||||||
{"scan", 0, false, "Scan tags instead of reading raw data"},
|
{"scan", 0, false, "Scan tags instead of reading raw data"},
|
||||||
|
{"seek", 0, true, "Start reading at this position"},
|
||||||
{"chunk-size", 0, true, "Read this number of bytes at a time"},
|
{"chunk-size", 0, true, "Read this number of bytes at a time"},
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -108,6 +112,10 @@ ParseCommandLine(int argc, char **argv)
|
|||||||
c.scan = true;
|
c.scan = true;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
case OPTION_SEEK:
|
||||||
|
c.seek = ParseSize(o.value);
|
||||||
|
break;
|
||||||
|
|
||||||
case OPTION_CHUNK_SIZE:
|
case OPTION_CHUNK_SIZE:
|
||||||
c.chunk_size = ParseSize(o.value);
|
c.chunk_size = ParseSize(o.value);
|
||||||
if (c.chunk_size <= 0 || c.chunk_size > MAX_CHUNK_SIZE)
|
if (c.chunk_size <= 0 || c.chunk_size > MAX_CHUNK_SIZE)
|
||||||
@@ -118,7 +126,7 @@ ParseCommandLine(int argc, char **argv)
|
|||||||
|
|
||||||
auto args = option_parser.GetRemaining();
|
auto args = option_parser.GetRemaining();
|
||||||
if (args.size != 1)
|
if (args.size != 1)
|
||||||
throw std::runtime_error("Usage: run_input [--verbose] [--config=FILE] URI");
|
throw std::runtime_error("Usage: run_input [--verbose] [--config=FILE] [--scan] [--chunk-size=BYTES] URI");
|
||||||
|
|
||||||
c.uri = args.front();
|
c.uri = args.front();
|
||||||
return c;
|
return c;
|
||||||
@@ -153,10 +161,14 @@ tag_save(FILE *file, const Tag &tag)
|
|||||||
}
|
}
|
||||||
|
|
||||||
static int
|
static int
|
||||||
dump_input_stream(InputStream &is, FileDescriptor out, size_t chunk_size)
|
dump_input_stream(InputStream &is, FileDescriptor out,
|
||||||
|
offset_type seek, size_t chunk_size)
|
||||||
{
|
{
|
||||||
std::unique_lock<Mutex> lock(is.mutex);
|
std::unique_lock<Mutex> lock(is.mutex);
|
||||||
|
|
||||||
|
if (seek > 0)
|
||||||
|
is.Seek(lock, seek);
|
||||||
|
|
||||||
/* print meta data */
|
/* print meta data */
|
||||||
|
|
||||||
if (is.HasMimeType())
|
if (is.HasMimeType())
|
||||||
@@ -256,7 +268,7 @@ try {
|
|||||||
Mutex mutex;
|
Mutex mutex;
|
||||||
auto is = InputStream::OpenReady(c.uri, mutex);
|
auto is = InputStream::OpenReady(c.uri, mutex);
|
||||||
return dump_input_stream(*is, FileDescriptor(STDOUT_FILENO),
|
return dump_input_stream(*is, FileDescriptor(STDOUT_FILENO),
|
||||||
c.chunk_size);
|
c.seek, c.chunk_size);
|
||||||
} catch (...) {
|
} catch (...) {
|
||||||
PrintException(std::current_exception());
|
PrintException(std::current_exception());
|
||||||
return EXIT_FAILURE;
|
return EXIT_FAILURE;
|
||||||
|
Reference in New Issue
Block a user