Compare commits
277 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
24741c5d06 | ||
|
|
6b3a282db4 | ||
|
|
7583cfe9b7 | ||
|
|
aafc9ce75b | ||
|
|
fea326530b | ||
|
|
8925cc17d8 | ||
|
|
14412c867f | ||
|
|
c5cc256bf2 | ||
|
|
563c7318f9 | ||
|
|
e92129f449 | ||
|
|
374cc51f77 | ||
|
|
1008d5f67c | ||
|
|
8e07ea7ad8 | ||
|
|
d751df0a73 | ||
|
|
2c084781b0 | ||
|
|
ae7d550a01 | ||
|
|
30d97fe8a0 | ||
|
|
5cb0080052 | ||
|
|
8e4ca23727 | ||
|
|
bdc861f058 | ||
|
|
8925040262 | ||
|
|
c065950ced | ||
|
|
257a77fa35 | ||
|
|
4e5d6e560b | ||
|
|
d276d8eda2 | ||
|
|
ebcb5e9368 | ||
|
|
69f09648a4 | ||
|
|
9adda30c38 | ||
|
|
d2d4a0251e | ||
|
|
f7b6431b6f | ||
|
|
03b9bd3a9e | ||
|
|
61aed60f6d | ||
|
|
2cc323c9fe | ||
|
|
f24ab120ee | ||
|
|
68349bc55c | ||
|
|
209364adf2 | ||
|
|
24afdee35c | ||
|
|
7aea285361 | ||
|
|
47a7707df1 | ||
|
|
6fdae1139f | ||
|
|
6c240f667c | ||
|
|
3040ddb5ec | ||
|
|
fdb28eb0c4 | ||
|
|
7ded244a61 | ||
|
|
8ed533acf3 | ||
|
|
a27580d0cc | ||
|
|
905db05cf9 | ||
|
|
4242aee21e | ||
|
|
e71bd2a08b | ||
|
|
e53a4d0a9e | ||
|
|
159389164a | ||
|
|
0a92fbc18e | ||
|
|
138c29320b | ||
|
|
8f00dbea45 | ||
|
|
f3fd2eb618 | ||
|
|
fc92db83cf | ||
|
|
3b0f8d5516 | ||
|
|
a5273d6992 | ||
|
|
b18074f899 | ||
|
|
3d8067a041 | ||
|
|
f6fe001fa9 | ||
|
|
32a5bf043b | ||
|
|
8d2079482f | ||
|
|
c331c75fde | ||
|
|
6080c3b4ba | ||
|
|
5ccfcffcc1 | ||
|
|
afe2aaa5f6 | ||
|
|
9b11caa0e6 | ||
|
|
a689b881d3 | ||
|
|
e94c436264 | ||
|
|
bad829509e | ||
|
|
9c66b0414a | ||
|
|
4d453a8313 | ||
|
|
61d7b436a2 | ||
|
|
cdddaf21b0 | ||
|
|
b267ba5f0a | ||
|
|
8270043053 | ||
|
|
c00ce42bca | ||
|
|
3852ddbbce | ||
|
|
672bc3ab67 | ||
|
|
62229f14da | ||
|
|
a4c925c8d7 | ||
|
|
60610e90b1 | ||
|
|
90184e0ce7 | ||
|
|
9c3e1d450a | ||
|
|
60f2116202 | ||
|
|
4ff2532330 | ||
|
|
9c15760c4d | ||
|
|
e1c43ec65f | ||
|
|
4dd10894ba | ||
|
|
608d7ec1e7 | ||
|
|
8474599ed6 | ||
|
|
ab39f64fc0 | ||
|
|
185fbca282 | ||
|
|
6e3b2fd844 | ||
|
|
dab39dc778 | ||
|
|
8cd5e79fbd | ||
|
|
1de3ac6c78 | ||
|
|
abe06a5fa6 | ||
|
|
85c27840a3 | ||
|
|
81c16273c5 | ||
|
|
801ae86b5d | ||
|
|
5619fd0bba | ||
|
|
200258c7c3 | ||
|
|
5418bb49fb | ||
|
|
3449c14ff5 | ||
|
|
36a89e8fe7 | ||
|
|
8e6a21a9c2 | ||
|
|
c560ec8ea6 | ||
|
|
56c234b410 | ||
|
|
82743dfd02 | ||
|
|
33694642bd | ||
|
|
c71242d743 | ||
|
|
c45f113856 | ||
|
|
e0a8fd398c | ||
|
|
3e97058151 | ||
|
|
51b1dd8672 | ||
|
|
98a7d8da6c | ||
|
|
acb29f792f | ||
|
|
cd364023ae | ||
|
|
8d34a1cfc6 | ||
|
|
73a1f078a6 | ||
|
|
b7ce452308 | ||
|
|
5faf76051d | ||
|
|
5fe70a3417 | ||
|
|
7a68b1e71f | ||
|
|
d5468dfe89 | ||
|
|
976372ff63 | ||
|
|
9abb686eeb | ||
|
|
f24bcc7f42 | ||
|
|
89800324cb | ||
|
|
050e30418c | ||
|
|
5397d18ed9 | ||
|
|
42eb69f46f | ||
|
|
f1ad21d2bf | ||
|
|
535a099a27 | ||
|
|
50003f6ad2 | ||
|
|
0914644d2b | ||
|
|
5ad6e7fec5 | ||
|
|
0bb943ba3e | ||
|
|
80a0cf694f | ||
|
|
0c9e25b3c4 | ||
|
|
943a67c805 | ||
|
|
881d91f86b | ||
|
|
54d57fdcc2 | ||
|
|
f6f30d6d64 | ||
|
|
4013fa15b9 | ||
|
|
ac1b844c15 | ||
|
|
b8614048d4 | ||
|
|
aed0d13591 | ||
|
|
9d02103ebe | ||
|
|
61784c2144 | ||
|
|
7059215795 | ||
|
|
2190cc7927 | ||
|
|
75dc9506c2 | ||
|
|
4f11fa0d41 | ||
|
|
ce7ec2b3f5 | ||
|
|
fada4aa529 | ||
|
|
aa0e121ade | ||
|
|
b4700039fd | ||
|
|
ab41c16eb5 | ||
|
|
04101f37b8 | ||
|
|
8c31370534 | ||
|
|
2306b0d78c | ||
|
|
cb1a9045e6 | ||
|
|
e92af06664 | ||
|
|
af20a1c994 | ||
|
|
756560eac3 | ||
|
|
dca0519336 | ||
|
|
b9a7f30443 | ||
|
|
32a17a997a | ||
|
|
bf41d1ad2b | ||
|
|
d27e534a85 | ||
|
|
6d54928d7c | ||
|
|
0dffe05bf7 | ||
|
|
9ef1f10319 | ||
|
|
23fcfdbd2a | ||
|
|
3401d26d4c | ||
|
|
256753ea46 | ||
|
|
76cd5f8595 | ||
|
|
5684025847 | ||
|
|
744bd1eadc | ||
|
|
2bc127bb43 | ||
|
|
7770298a65 | ||
|
|
fa50cdb39e | ||
|
|
816ef12088 | ||
|
|
5ff786e59c | ||
|
|
80fe88e8f6 | ||
|
|
a1afe9afc6 | ||
|
|
fe598e7d30 | ||
|
|
4475b8ca04 | ||
|
|
a714bdb0ce | ||
|
|
087874620f | ||
|
|
f1116c9258 | ||
|
|
d01fb6730a | ||
|
|
7bfe6a3304 | ||
|
|
9a577f8060 | ||
|
|
d75a0d714e | ||
|
|
9be3a1554e | ||
|
|
7764719513 | ||
|
|
dcbb9fe07c | ||
|
|
e3b347820a | ||
|
|
a84bf5a92e | ||
|
|
732bdc800d | ||
|
|
a8661b5931 | ||
|
|
5680a3a4b7 | ||
|
|
15ce8eb487 | ||
|
|
b7744be208 | ||
|
|
63c5d66016 | ||
|
|
d09bd9178f | ||
|
|
7d8b1860c3 | ||
|
|
b06825829b | ||
|
|
ba4cd47fd8 | ||
|
|
bbe403f141 | ||
|
|
5df2707d98 | ||
|
|
4859ea468f | ||
|
|
2a8830db70 | ||
|
|
fed9b6fd74 | ||
|
|
b02890eb8a | ||
|
|
da882a6eb6 | ||
|
|
aeb89aa9d6 | ||
|
|
f885807ecc | ||
|
|
b826fd71f0 | ||
|
|
ae35df1126 | ||
|
|
80e55f6bfc | ||
|
|
e7411c0c4b | ||
|
|
e9af692973 | ||
|
|
0cf90ee8b6 | ||
|
|
dc3c0c8866 | ||
|
|
1c46bb1ba6 | ||
|
|
2e8f42c6ad | ||
|
|
2b301ffd2c | ||
|
|
ef0765ca10 | ||
|
|
9766ac6db3 | ||
|
|
32799ff682 | ||
|
|
ce093be12c | ||
|
|
2c276770f0 | ||
|
|
75a592f629 | ||
|
|
13ce07d181 | ||
|
|
d659c7df19 | ||
|
|
f8403a1d29 | ||
|
|
ebb952c4ad | ||
|
|
bea3b954a5 | ||
|
|
129d8e89b9 | ||
|
|
65778a3774 | ||
|
|
d9841668ff | ||
|
|
85d27cbcb9 | ||
|
|
9b95e65bd9 | ||
|
|
12a86c4975 | ||
|
|
0b9435858b | ||
|
|
f0386459ee | ||
|
|
e98d4670b8 | ||
|
|
56cc42b752 | ||
|
|
ead208987d | ||
|
|
364acc8949 | ||
|
|
a8f4d2b6fc | ||
|
|
0eb113e7c6 | ||
|
|
96a9670c69 | ||
|
|
dcc5ce6792 | ||
|
|
23d08820a2 | ||
|
|
b9b906ab20 | ||
|
|
964804a4c2 | ||
|
|
92495d2b0b | ||
|
|
9270829b5b | ||
|
|
b6243a9945 | ||
|
|
496f88653d | ||
|
|
5ef645df97 | ||
|
|
bf49c9e4e2 | ||
|
|
0da9c91af2 | ||
|
|
193e637dd9 | ||
|
|
928bee933d | ||
|
|
4d1720c886 | ||
|
|
8f8ed87327 | ||
|
|
28a441c977 | ||
|
|
8cf50b08f2 | ||
|
|
818b7e0641 | ||
|
|
e70f40fac1 |
3
.gitignore
vendored
3
.gitignore
vendored
@@ -6,3 +6,6 @@
|
|||||||
/output/
|
/output/
|
||||||
|
|
||||||
__pycache__/
|
__pycache__/
|
||||||
|
|
||||||
|
/.clangd/
|
||||||
|
/compile_commands.json
|
||||||
|
|||||||
127
.travis.yml
127
.travis.yml
@@ -1,7 +1,73 @@
|
|||||||
language: cpp
|
language: cpp
|
||||||
|
|
||||||
matrix:
|
jobs:
|
||||||
include:
|
include:
|
||||||
|
# Ubuntu Bionic (18.04) with GCC 7
|
||||||
|
- os: linux
|
||||||
|
dist: bionic
|
||||||
|
addons:
|
||||||
|
apt:
|
||||||
|
sources:
|
||||||
|
- sourceline: 'ppa:deadsnakes/ppa' # for Python 3.7 (required by Meson)
|
||||||
|
packages:
|
||||||
|
- libgtest-dev
|
||||||
|
- libboost-dev
|
||||||
|
- python3.6
|
||||||
|
- python3-urllib3
|
||||||
|
- ninja-build
|
||||||
|
before_install:
|
||||||
|
- wget https://bootstrap.pypa.io/get-pip.py
|
||||||
|
- /usr/bin/python3.6 get-pip.py --user --no-cache-dir
|
||||||
|
install:
|
||||||
|
- /usr/bin/python3.6 $HOME/.local/bin/pip install --user meson --no-cache-dir
|
||||||
|
env:
|
||||||
|
- MATRIX_EVAL="export PATH=\$HOME/.local/bin:\$PATH"
|
||||||
|
|
||||||
|
# Ubuntu Bionic (18.04) with GCC 7 on big-endian
|
||||||
|
- os: linux
|
||||||
|
arch: s390x
|
||||||
|
dist: bionic
|
||||||
|
addons:
|
||||||
|
apt:
|
||||||
|
sources:
|
||||||
|
- sourceline: 'ppa:deadsnakes/ppa' # for Python 3.7 (required by Meson)
|
||||||
|
packages:
|
||||||
|
- libgtest-dev
|
||||||
|
- libboost-dev
|
||||||
|
- python3.6
|
||||||
|
- python3-urllib3
|
||||||
|
- ninja-build
|
||||||
|
before_install:
|
||||||
|
- wget https://bootstrap.pypa.io/get-pip.py
|
||||||
|
- /usr/bin/python3.6 get-pip.py --user --no-cache-dir
|
||||||
|
install:
|
||||||
|
- /usr/bin/python3.6 $HOME/.local/bin/pip install --user meson --no-cache-dir
|
||||||
|
env:
|
||||||
|
- MATRIX_EVAL="export PATH=\$HOME/.local/bin:\$PATH"
|
||||||
|
|
||||||
|
# Ubuntu Bionic (18.04) with GCC 7 on ARM64
|
||||||
|
- os: linux
|
||||||
|
arch: arm64
|
||||||
|
dist: bionic
|
||||||
|
addons:
|
||||||
|
apt:
|
||||||
|
sources:
|
||||||
|
- sourceline: 'ppa:deadsnakes/ppa' # for Python 3.7 (required by Meson)
|
||||||
|
packages:
|
||||||
|
- libgtest-dev
|
||||||
|
- libboost-dev
|
||||||
|
- python3.6
|
||||||
|
- python3-urllib3
|
||||||
|
- ninja-build
|
||||||
|
before_install:
|
||||||
|
- wget https://bootstrap.pypa.io/get-pip.py
|
||||||
|
- /usr/bin/python3.6 get-pip.py --user --no-cache-dir
|
||||||
|
install:
|
||||||
|
- /usr/bin/python3.6 $HOME/.local/bin/pip install --user meson --no-cache-dir
|
||||||
|
env:
|
||||||
|
- MATRIX_EVAL="export PATH=\$HOME/.local/bin:\$PATH"
|
||||||
|
|
||||||
|
# Ubuntu Trusty (16.04) with GCC 6
|
||||||
- os: linux
|
- os: linux
|
||||||
dist: trusty
|
dist: trusty
|
||||||
addons:
|
addons:
|
||||||
@@ -20,13 +86,14 @@ matrix:
|
|||||||
- ninja-build
|
- ninja-build
|
||||||
before_install:
|
before_install:
|
||||||
- wget https://bootstrap.pypa.io/get-pip.py
|
- wget https://bootstrap.pypa.io/get-pip.py
|
||||||
- /usr/bin/python3.6 get-pip.py --user
|
- /usr/bin/python3.6 get-pip.py --user --no-cache-dir
|
||||||
install:
|
install:
|
||||||
- /usr/bin/python3.6 $HOME/.local/bin/pip install --user meson
|
- /usr/bin/python3.6 $HOME/.local/bin/pip install --user meson --no-cache-dir
|
||||||
env:
|
env:
|
||||||
# use gold as workaround for https://sourceware.org/bugzilla/show_bug.cgi?id=17068
|
# use gold as workaround for https://sourceware.org/bugzilla/show_bug.cgi?id=17068
|
||||||
- MATRIX_EVAL="export CC=gcc-6 CXX=g++-6 LDFLAGS=-fuse-ld=gold PATH=$HOME/.local/bin:$PATH"
|
- MATRIX_EVAL="export CC='ccache gcc-6' CXX='ccache g++-6' LDFLAGS=-fuse-ld=gold PATH=\$HOME/.local/bin:\$PATH"
|
||||||
|
|
||||||
|
# Ubuntu Trusty (16.04) with GCC 8
|
||||||
- os: linux
|
- os: linux
|
||||||
dist: trusty
|
dist: trusty
|
||||||
addons:
|
addons:
|
||||||
@@ -45,31 +112,61 @@ matrix:
|
|||||||
- ninja-build
|
- ninja-build
|
||||||
before_install:
|
before_install:
|
||||||
- wget https://bootstrap.pypa.io/get-pip.py
|
- wget https://bootstrap.pypa.io/get-pip.py
|
||||||
- /usr/bin/python3.6 get-pip.py --user
|
- /usr/bin/python3.6 get-pip.py --user --no-cache-dir
|
||||||
install:
|
install:
|
||||||
- /usr/bin/python3.6 $HOME/.local/bin/pip install --user meson
|
- /usr/bin/python3.6 $HOME/.local/bin/pip install --user meson --no-cache-dir
|
||||||
env:
|
env:
|
||||||
# use gold as workaround for https://sourceware.org/bugzilla/show_bug.cgi?id=17068
|
# use gold as workaround for https://sourceware.org/bugzilla/show_bug.cgi?id=17068
|
||||||
- MATRIX_EVAL="export CC=gcc-8 CXX=g++-8 LDFLAGS=-fuse-ld=gold PATH=$HOME/.local/bin:$PATH"
|
- MATRIX_EVAL="export CC='ccache gcc-8' CXX='ccache g++-8' LDFLAGS=-fuse-ld=gold PATH=\$HOME/.local/bin:\$PATH"
|
||||||
|
|
||||||
- os: osx
|
- os: osx
|
||||||
osx_image: xcode9.3beta
|
osx_image: xcode9.4
|
||||||
|
addons:
|
||||||
|
homebrew:
|
||||||
|
packages:
|
||||||
|
- ccache
|
||||||
|
- meson
|
||||||
|
- icu4c
|
||||||
|
- ffmpeg
|
||||||
|
- libnfs
|
||||||
|
- yajl
|
||||||
|
- libupnp
|
||||||
|
- libid3tag
|
||||||
|
- chromaprint
|
||||||
|
- libsamplerate
|
||||||
|
- libsoxr
|
||||||
|
# libzzip appears to be broken on Homebrew: "ld: library not found for -lzzip"
|
||||||
|
#- libzzip
|
||||||
|
- flac
|
||||||
|
- opus
|
||||||
|
- libvorbis
|
||||||
|
- faad2
|
||||||
|
- wavpack
|
||||||
|
- libmpdclient
|
||||||
|
update: true
|
||||||
env:
|
env:
|
||||||
- MATRIX_EVAL=""
|
- MATRIX_EVAL="export PATH=/usr/local/opt/ccache/libexec:$PATH HOMEBREW_NO_ANALYTICS=1"
|
||||||
|
|
||||||
cache:
|
cache:
|
||||||
- apt
|
apt: true
|
||||||
- ccache
|
ccache: true
|
||||||
|
directories:
|
||||||
|
- $HOME/Library/Caches/Homebrew
|
||||||
|
|
||||||
|
before_cache:
|
||||||
|
- test "$TRAVIS_OS_NAME" != "osx" || brew cleanup
|
||||||
|
|
||||||
before_install:
|
before_install:
|
||||||
- eval "${MATRIX_EVAL}"
|
- eval "${MATRIX_EVAL}"
|
||||||
# C++14
|
|
||||||
- test "$TRAVIS_OS_NAME" != "osx" || brew update
|
|
||||||
|
|
||||||
install:
|
install:
|
||||||
# C++14
|
# C++14
|
||||||
- test "$TRAVIS_OS_NAME" != "osx" || brew install ccache meson
|
|
||||||
- test "$TRAVIS_OS_NAME" != "osx" || brew install --HEAD https://gist.githubusercontent.com/Kronuz/96ac10fbd8472eb1e7566d740c4034f8/raw/gtest.rb
|
# Work around "Target /usr/local/lib/libgtest.a is a symlink
|
||||||
|
# belonging to nss. You can unlink it" during gtest install
|
||||||
|
- test "$TRAVIS_OS_NAME" != "osx" || brew unlink nss
|
||||||
|
|
||||||
|
- test "$TRAVIS_OS_NAME" != "osx" || brew install https://gist.githubusercontent.com/Kronuz/96ac10fbd8472eb1e7566d740c4034f8/raw/gtest.rb
|
||||||
|
|
||||||
before_script:
|
before_script:
|
||||||
- ccache -s
|
- ccache -s
|
||||||
|
|||||||
120
NEWS
120
NEWS
@@ -1,3 +1,123 @@
|
|||||||
|
ver 0.21.24 (2020/06/10)
|
||||||
|
* protocol
|
||||||
|
- "tagtypes" requires no permissions
|
||||||
|
* database
|
||||||
|
- simple: fix crash when mounting twice
|
||||||
|
* decoder
|
||||||
|
- modplug: fix Windows build failure
|
||||||
|
- wildmidi: attempt to detect WildMidi using pkg-config
|
||||||
|
- wildmidi: fix Windows build failure
|
||||||
|
* player
|
||||||
|
- don't restart current song if seeking beyond end
|
||||||
|
* Android
|
||||||
|
- enable the decoder plugins GME, ModPlug and WildMidi
|
||||||
|
- fix build failure with Android NDK r21
|
||||||
|
* Windows
|
||||||
|
- fix stream playback
|
||||||
|
- enable the decoder plugins GME, ModPlug and WildMidi
|
||||||
|
- work around Meson bug breaking the Windows build with GCC 10
|
||||||
|
* fix unit test failure
|
||||||
|
|
||||||
|
ver 0.21.23 (2020/04/23)
|
||||||
|
* protocol
|
||||||
|
- add tag fallback for AlbumSort
|
||||||
|
* storage
|
||||||
|
- curl: fix corrupt "href" values in the presence of XML entities
|
||||||
|
- curl: unescape "href" values
|
||||||
|
* input
|
||||||
|
- nfs: fix crash bug
|
||||||
|
- nfs: fix freeze bug on reconnect
|
||||||
|
* decoder
|
||||||
|
- gme: adapt to API change in the upcoming version 0.7.0
|
||||||
|
* output
|
||||||
|
- alsa: implement channel mapping for 5.0 and 7.0
|
||||||
|
* player
|
||||||
|
- drain outputs at end of song in "single" mode
|
||||||
|
* Windows
|
||||||
|
- fix case insensitive search
|
||||||
|
|
||||||
|
ver 0.21.22 (2020/04/02)
|
||||||
|
* database
|
||||||
|
- simple: optimize startup
|
||||||
|
* input
|
||||||
|
- curl: fix streaming errors on Android
|
||||||
|
* playlist
|
||||||
|
- rss: support MIME type application/xml
|
||||||
|
* mixer
|
||||||
|
- android: new mixer plugin for "sles" output
|
||||||
|
* Android
|
||||||
|
- TV support
|
||||||
|
* Windows
|
||||||
|
- fix time zone offset check
|
||||||
|
* fix build failures with uClibc-ng
|
||||||
|
|
||||||
|
ver 0.21.21 (2020/03/19)
|
||||||
|
* configuration
|
||||||
|
- fix bug in "metadata_to_use" setting
|
||||||
|
* playlist
|
||||||
|
- asx, xspf: fix corrupt tags in the presence of XML entities
|
||||||
|
* archive
|
||||||
|
- iso9660: skip empty file names to work around libcdio bug
|
||||||
|
* decoder
|
||||||
|
- gme: ignore empty tags
|
||||||
|
* output
|
||||||
|
- solaris: port to NetBSD
|
||||||
|
* raise default "max_connections" value to 100
|
||||||
|
|
||||||
|
ver 0.21.20 (2020/02/16)
|
||||||
|
* decoder
|
||||||
|
- audiofile, ffmpeg, sndfile: handle MIME type "audio/wav"
|
||||||
|
- ffmpeg: fix playback of AIFF and TTA
|
||||||
|
- vorbis, opus: fix seeking in small files
|
||||||
|
* fix backwards seeking on ARM (and other non-x86 CPUs)
|
||||||
|
|
||||||
|
ver 0.21.19 (2020/01/17)
|
||||||
|
* configuration
|
||||||
|
- allow overriding top-level settings in includes
|
||||||
|
* output
|
||||||
|
- pulse: obey Pulse's maximum sample rate (fixes DSD128 playback)
|
||||||
|
* fix build failure with clang 10
|
||||||
|
* fix build failure with Android NDK r20
|
||||||
|
|
||||||
|
ver 0.21.18 (2019/12/24)
|
||||||
|
* protocol
|
||||||
|
- work around Mac OS X bug in the ISO 8601 parser
|
||||||
|
* output
|
||||||
|
- alsa: fix hang bug with ALSA "null" outputs
|
||||||
|
* storage
|
||||||
|
- curl: fix crash bug
|
||||||
|
* drop support for CURL versions older than 7.32.0
|
||||||
|
* reduce unnecessary CPU wakeups
|
||||||
|
|
||||||
|
ver 0.21.17 (2019/12/16)
|
||||||
|
* protocol
|
||||||
|
- relax the ISO 8601 parser: allow omitting field separators, the
|
||||||
|
time of day and the "Z" suffix
|
||||||
|
* archive
|
||||||
|
- zzip: improve error reporting
|
||||||
|
* outputs
|
||||||
|
- jack: mark ports as terminal
|
||||||
|
- shout: declare metadata as UTF-8
|
||||||
|
* fix build failure with -Ddatabase=false
|
||||||
|
|
||||||
|
ver 0.21.16 (2019/10/16)
|
||||||
|
* queue
|
||||||
|
- fix relative destination offset when moving a range
|
||||||
|
* storage
|
||||||
|
- curl: request the "resourcetype" property to fix database update
|
||||||
|
- curl: URL-encode more paths
|
||||||
|
- curl: follow redirects for collections without trailing slash
|
||||||
|
* update
|
||||||
|
- fix crash when music_directory is not a directory
|
||||||
|
* fix build with iconv() instead of ICU
|
||||||
|
|
||||||
|
ver 0.21.15 (2019/09/25)
|
||||||
|
* decoder
|
||||||
|
- dsdiff, dsf: fix displayed bit rate
|
||||||
|
- mpcdec: fix bogus ReplayGain values
|
||||||
|
* output
|
||||||
|
- solaris: fix build with glibc 2.30
|
||||||
|
|
||||||
ver 0.21.14 (2019/08/21)
|
ver 0.21.14 (2019/08/21)
|
||||||
* decoder
|
* decoder
|
||||||
- sidplay: show track durations in database
|
- sidplay: show track durations in database
|
||||||
|
|||||||
@@ -2,18 +2,25 @@
|
|||||||
<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="37"
|
android:versionCode="47"
|
||||||
android:versionName="0.21.14">
|
android:versionName="0.21.24">
|
||||||
|
|
||||||
<uses-sdk android:minSdkVersion="21" android:targetSdkVersion="26"/>
|
<uses-sdk android:minSdkVersion="21" android:targetSdkVersion="28"/>
|
||||||
|
|
||||||
|
<uses-feature android:name="android.software.leanback"
|
||||||
|
android:required="false" />
|
||||||
|
<uses-feature android:name="android.hardware.touchscreen"
|
||||||
|
android:required="false" />
|
||||||
|
|
||||||
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
|
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
|
||||||
<uses-permission android:name="android.permission.WAKE_LOCK"/>
|
<uses-permission android:name="android.permission.WAKE_LOCK"/>
|
||||||
<uses-permission android:name="android.permission.INTERNET"/>
|
<uses-permission android:name="android.permission.INTERNET"/>
|
||||||
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
|
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
|
||||||
|
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
|
||||||
|
|
||||||
<application android:allowBackup="true"
|
<application android:allowBackup="true"
|
||||||
android:icon="@drawable/icon"
|
android:icon="@drawable/icon"
|
||||||
|
android:banner="@drawable/icon"
|
||||||
android:label="@string/app_name">
|
android:label="@string/app_name">
|
||||||
<activity android:name=".Settings"
|
<activity android:name=".Settings"
|
||||||
android:label="@string/app_name">
|
android:label="@string/app_name">
|
||||||
@@ -22,6 +29,14 @@
|
|||||||
<category android:name="android.intent.category.LAUNCHER" />
|
<category android:name="android.intent.category.LAUNCHER" />
|
||||||
</intent-filter>
|
</intent-filter>
|
||||||
</activity>
|
</activity>
|
||||||
|
<activity android:name=".Settings"
|
||||||
|
android:label="@string/app_name" >
|
||||||
|
<intent-filter>
|
||||||
|
<action android:name="android.intent.action.MAIN" />
|
||||||
|
<category android:name="android.intent.category.LEANBACK_LAUNCHER" />
|
||||||
|
</intent-filter>
|
||||||
|
</activity>
|
||||||
|
|
||||||
<receiver android:name=".Receiver">
|
<receiver android:name=".Receiver">
|
||||||
<intent-filter>
|
<intent-filter>
|
||||||
<action android:name="android.intent.action.BOOT_COMPLETED" />
|
<action android:name="android.intent.action.BOOT_COMPLETED" />
|
||||||
|
|||||||
@@ -25,25 +25,32 @@ android_abis = {
|
|||||||
'arch': 'arm-linux-androideabi',
|
'arch': 'arm-linux-androideabi',
|
||||||
'ndk_arch': 'arm',
|
'ndk_arch': 'arm',
|
||||||
'toolchain_arch': 'arm-linux-androideabi',
|
'toolchain_arch': 'arm-linux-androideabi',
|
||||||
'llvm_triple': 'armv7-none-linux-androideabi',
|
'llvm_triple': 'armv7-linux-androideabi',
|
||||||
'cflags': '-march=armv7-a -mfpu=vfp -mfloat-abi=softfp',
|
'cflags': '-fpic -march=armv7-a -mfpu=vfpv3-d16 -mfloat-abi=softfp',
|
||||||
},
|
},
|
||||||
|
|
||||||
'arm64-v8a': {
|
'arm64-v8a': {
|
||||||
'android_api_level': '21',
|
|
||||||
'arch': 'aarch64-linux-android',
|
'arch': 'aarch64-linux-android',
|
||||||
'ndk_arch': 'arm64',
|
'ndk_arch': 'arm64',
|
||||||
'toolchain_arch': 'aarch64-linux-android',
|
'toolchain_arch': 'aarch64-linux-android',
|
||||||
'llvm_triple': 'aarch64-none-linux-android',
|
'llvm_triple': 'aarch64-linux-android',
|
||||||
'cflags': '',
|
'cflags': '-fpic',
|
||||||
},
|
},
|
||||||
|
|
||||||
'x86': {
|
'x86': {
|
||||||
'arch': 'i686-linux-android',
|
'arch': 'i686-linux-android',
|
||||||
'ndk_arch': 'x86',
|
'ndk_arch': 'x86',
|
||||||
'toolchain_arch': 'x86',
|
'toolchain_arch': 'x86',
|
||||||
'llvm_triple': 'i686-none-linux-android',
|
'llvm_triple': 'i686-linux-android',
|
||||||
'cflags': '-march=i686 -mtune=intel -mssse3 -mfpmath=sse -m32',
|
'cflags': '-fPIC -march=i686 -mtune=intel -mssse3 -mfpmath=sse -m32',
|
||||||
|
},
|
||||||
|
|
||||||
|
'x86_64': {
|
||||||
|
'arch': 'x86_64-linux-android',
|
||||||
|
'ndk_arch': 'x86_64',
|
||||||
|
'toolchain_arch': 'x86_64',
|
||||||
|
'llvm_triple': 'x86_64-linux-android',
|
||||||
|
'cflags': '-fPIC -m64',
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -76,27 +83,20 @@ class AndroidNdkToolchain:
|
|||||||
|
|
||||||
ndk_arch = abi_info['ndk_arch']
|
ndk_arch = abi_info['ndk_arch']
|
||||||
android_api_level = '21'
|
android_api_level = '21'
|
||||||
ndk_platform = 'android-' + android_api_level
|
|
||||||
|
|
||||||
# select the NDK compiler
|
# select the NDK compiler
|
||||||
gcc_version = '4.9'
|
gcc_version = '4.9'
|
||||||
|
|
||||||
ndk_platform_path = os.path.join(ndk_path, 'platforms', ndk_platform)
|
|
||||||
sysroot = os.path.join(ndk_path, 'sysroot')
|
|
||||||
target_root = os.path.join(ndk_platform_path, 'arch-' + ndk_arch)
|
|
||||||
|
|
||||||
install_prefix = os.path.join(arch_path, 'root')
|
install_prefix = os.path.join(arch_path, 'root')
|
||||||
|
|
||||||
self.arch = arch
|
self.arch = arch
|
||||||
self.install_prefix = install_prefix
|
self.install_prefix = install_prefix
|
||||||
self.sysroot = sysroot
|
|
||||||
|
|
||||||
toolchain_path = os.path.join(ndk_path, 'toolchains', abi_info['toolchain_arch'] + '-' + gcc_version, 'prebuilt', build_arch)
|
toolchain_path = os.path.join(ndk_path, 'toolchains', abi_info['toolchain_arch'] + '-' + gcc_version, 'prebuilt', build_arch)
|
||||||
llvm_path = os.path.join(ndk_path, 'toolchains', 'llvm', 'prebuilt', build_arch)
|
llvm_path = os.path.join(ndk_path, 'toolchains', 'llvm', 'prebuilt', build_arch)
|
||||||
llvm_triple = abi_info['llvm_triple']
|
llvm_triple = abi_info['llvm_triple'] + android_api_level
|
||||||
|
|
||||||
common_flags = '-Os -g'
|
common_flags = '-Os -g'
|
||||||
common_flags += ' -fPIC'
|
|
||||||
common_flags += ' ' + abi_info['cflags']
|
common_flags += ' ' + abi_info['cflags']
|
||||||
|
|
||||||
toolchain_bin = os.path.join(toolchain_path, 'bin')
|
toolchain_bin = os.path.join(toolchain_path, 'bin')
|
||||||
@@ -107,6 +107,9 @@ class AndroidNdkToolchain:
|
|||||||
|
|
||||||
common_flags += ' -fvisibility=hidden -fdata-sections -ffunction-sections'
|
common_flags += ' -fvisibility=hidden -fdata-sections -ffunction-sections'
|
||||||
|
|
||||||
|
# required flags from https://android.googlesource.com/platform/ndk/+/ndk-release-r20/docs/BuildSystemMaintainers.md#additional-required-arguments
|
||||||
|
common_flags += ' -fno-addrsig'
|
||||||
|
|
||||||
self.ar = os.path.join(toolchain_bin, arch + '-ar')
|
self.ar = os.path.join(toolchain_bin, arch + '-ar')
|
||||||
self.ranlib = os.path.join(toolchain_bin, arch + '-ranlib')
|
self.ranlib = os.path.join(toolchain_bin, arch + '-ranlib')
|
||||||
self.nm = os.path.join(toolchain_bin, arch + '-nm')
|
self.nm = os.path.join(toolchain_bin, arch + '-nm')
|
||||||
@@ -114,15 +117,11 @@ class AndroidNdkToolchain:
|
|||||||
|
|
||||||
self.cflags = common_flags
|
self.cflags = common_flags
|
||||||
self.cxxflags = common_flags
|
self.cxxflags = common_flags
|
||||||
self.cppflags = '--sysroot=' + sysroot + \
|
self.cppflags = ' -isystem ' + os.path.join(install_prefix, 'include')
|
||||||
' -isystem ' + os.path.join(install_prefix, 'include') + \
|
self.ldflags = ' -L' + os.path.join(install_prefix, 'lib') + \
|
||||||
' -isystem ' + os.path.join(sysroot, 'usr', 'include', arch) + \
|
' -Wl,--exclude-libs=ALL' + \
|
||||||
' -D__ANDROID_API__=' + android_api_level
|
|
||||||
self.ldflags = '--sysroot=' + sysroot + \
|
|
||||||
' -L' + os.path.join(install_prefix, 'lib') + \
|
|
||||||
' -L' + os.path.join(target_root, 'usr', 'lib') + \
|
|
||||||
' -B' + os.path.join(target_root, 'usr', 'lib') + \
|
|
||||||
' ' + common_flags
|
' ' + common_flags
|
||||||
|
self.ldflags = common_flags
|
||||||
self.libs = ''
|
self.libs = ''
|
||||||
|
|
||||||
self.is_arm = ndk_arch == 'arm'
|
self.is_arm = ndk_arch == 'arm'
|
||||||
@@ -130,13 +129,10 @@ class AndroidNdkToolchain:
|
|||||||
self.is_aarch64 = ndk_arch == 'arm64'
|
self.is_aarch64 = ndk_arch == 'arm64'
|
||||||
self.is_windows = False
|
self.is_windows = False
|
||||||
|
|
||||||
libcxx_path = os.path.join(ndk_path, 'sources/cxx-stl/llvm-libc++')
|
|
||||||
libcxx_libs_path = os.path.join(libcxx_path, 'libs', android_abi)
|
|
||||||
|
|
||||||
libstdcxx_flags = ''
|
libstdcxx_flags = ''
|
||||||
libstdcxx_cxxflags = libstdcxx_flags + ' -isystem ' + os.path.join(libcxx_path, 'include') + ' -isystem ' + os.path.join(ndk_path, 'sources/android/support/include')
|
libstdcxx_cxxflags = ''
|
||||||
libstdcxx_ldflags = libstdcxx_flags + ' -L' + libcxx_libs_path
|
libstdcxx_ldflags = ''
|
||||||
libstdcxx_libs = '-lc++_static -lc++abi'
|
libstdcxx_libs = '-static-libstdc++'
|
||||||
|
|
||||||
if self.is_armv7:
|
if self.is_armv7:
|
||||||
# On 32 bit ARM, clang generates no ".eh_frame" section;
|
# On 32 bit ARM, clang generates no ".eh_frame" section;
|
||||||
@@ -172,6 +168,9 @@ thirdparty_libs = [
|
|||||||
opus,
|
opus,
|
||||||
flac,
|
flac,
|
||||||
libid3tag,
|
libid3tag,
|
||||||
|
libmodplug,
|
||||||
|
wildmidi,
|
||||||
|
gme,
|
||||||
ffmpeg,
|
ffmpeg,
|
||||||
curl,
|
curl,
|
||||||
libexpat,
|
libexpat,
|
||||||
|
|||||||
@@ -21,6 +21,7 @@ package org.musicpd;
|
|||||||
|
|
||||||
import android.annotation.TargetApi;
|
import android.annotation.TargetApi;
|
||||||
import android.app.Notification;
|
import android.app.Notification;
|
||||||
|
import android.app.NotificationManager;
|
||||||
import android.app.PendingIntent;
|
import android.app.PendingIntent;
|
||||||
import android.app.Service;
|
import android.app.Service;
|
||||||
import android.content.ComponentName;
|
import android.content.ComponentName;
|
||||||
@@ -35,6 +36,9 @@ import android.os.RemoteException;
|
|||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
import android.widget.RemoteViews;
|
import android.widget.RemoteViews;
|
||||||
|
|
||||||
|
import java.lang.reflect.Constructor;
|
||||||
|
import java.lang.reflect.Method;
|
||||||
|
|
||||||
public class Main extends Service implements Runnable {
|
public class Main extends Service implements Runnable {
|
||||||
private static final String TAG = "Main";
|
private static final String TAG = "Main";
|
||||||
private static final String REMOTE_ERROR = "MPD process was killed";
|
private static final String REMOTE_ERROR = "MPD process was killed";
|
||||||
@@ -156,11 +160,36 @@ public class Main extends Service implements Runnable {
|
|||||||
sendMessage(MSG_SEND_STATUS, mStatus, 0, mError);
|
sendMessage(MSG_SEND_STATUS, mStatus, 0, mError);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private Notification.Builder createNotificationBuilderWithChannel() {
|
||||||
|
final NotificationManager notificationManager = (NotificationManager) this.getSystemService(Context.NOTIFICATION_SERVICE);
|
||||||
|
if (notificationManager == null)
|
||||||
|
return null;
|
||||||
|
|
||||||
|
final String id = "org.musicpd";
|
||||||
|
final String name = "MPD service";
|
||||||
|
final int importance = 3; /* NotificationManager.IMPORTANCE_DEFAULT */
|
||||||
|
|
||||||
|
try {
|
||||||
|
Class<?> ncClass = Class.forName("android.app.NotificationChannel");
|
||||||
|
Constructor<?> ncCtor = ncClass.getConstructor(String.class, CharSequence.class, int.class);
|
||||||
|
Object nc = ncCtor.newInstance(id, name, importance);
|
||||||
|
|
||||||
|
Method nmCreateNotificationChannelMethod =
|
||||||
|
NotificationManager.class.getMethod("createNotificationChannel", ncClass);
|
||||||
|
nmCreateNotificationChannelMethod.invoke(notificationManager, nc);
|
||||||
|
|
||||||
|
Constructor nbCtor = Notification.Builder.class.getConstructor(Context.class, String.class);
|
||||||
|
return (Notification.Builder) nbCtor.newInstance(this, id);
|
||||||
|
} catch (Exception e)
|
||||||
|
{
|
||||||
|
Log.e(TAG, "error creating the NotificationChannel", e);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private void start() {
|
private void start() {
|
||||||
if (mThread != null)
|
if (mThread != null)
|
||||||
return;
|
return;
|
||||||
mThread = new Thread(this);
|
|
||||||
mThread.start();
|
|
||||||
|
|
||||||
final Intent mainIntent = new Intent(this, Settings.class);
|
final Intent mainIntent = new Intent(this, Settings.class);
|
||||||
mainIntent.setAction("android.intent.action.MAIN");
|
mainIntent.setAction("android.intent.action.MAIN");
|
||||||
@@ -168,13 +197,25 @@ public class Main extends Service implements Runnable {
|
|||||||
final PendingIntent contentIntent = PendingIntent.getActivity(this, 0,
|
final PendingIntent contentIntent = PendingIntent.getActivity(this, 0,
|
||||||
mainIntent, PendingIntent.FLAG_CANCEL_CURRENT);
|
mainIntent, PendingIntent.FLAG_CANCEL_CURRENT);
|
||||||
|
|
||||||
Notification notification = new Notification.Builder(this)
|
Notification.Builder nBuilder;
|
||||||
.setContentTitle(getText(R.string.notification_title_mpd_running))
|
if (Build.VERSION.SDK_INT >= 26 /* Build.VERSION_CODES.O */)
|
||||||
|
{
|
||||||
|
nBuilder = createNotificationBuilderWithChannel();
|
||||||
|
if (nBuilder == null)
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
nBuilder = new Notification.Builder(this);
|
||||||
|
|
||||||
|
Notification notification = nBuilder.setContentTitle(getText(R.string.notification_title_mpd_running))
|
||||||
.setContentText(getText(R.string.notification_text_mpd_running))
|
.setContentText(getText(R.string.notification_text_mpd_running))
|
||||||
.setSmallIcon(R.drawable.notification_icon)
|
.setSmallIcon(R.drawable.notification_icon)
|
||||||
.setContentIntent(contentIntent)
|
.setContentIntent(contentIntent)
|
||||||
.build();
|
.build();
|
||||||
|
|
||||||
|
mThread = new Thread(this);
|
||||||
|
mThread.start();
|
||||||
|
|
||||||
startForeground(R.string.notification_title_mpd_running, notification);
|
startForeground(R.string.notification_title_mpd_running, notification);
|
||||||
startService(new Intent(this, Main.class));
|
startService(new Intent(this, Main.class));
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -105,12 +105,13 @@ public class Settings extends Activity {
|
|||||||
else
|
else
|
||||||
mRunButton.setChecked(false);
|
mRunButton.setChecked(false);
|
||||||
mFirstRun = true;
|
mFirstRun = true;
|
||||||
|
mTextStatus.setText("");
|
||||||
break;
|
break;
|
||||||
case MSG_STARTED:
|
case MSG_STARTED:
|
||||||
Log.d(TAG, "onStarted");
|
Log.d(TAG, "onStarted");
|
||||||
mRunButton.setChecked(true);
|
mRunButton.setChecked(true);
|
||||||
mFirstRun = true;
|
mFirstRun = true;
|
||||||
mTextStatus.setText("CAUTION: this version is EXPERIMENTAL!"); // XXX
|
mTextStatus.setText("MPD service started");
|
||||||
break;
|
break;
|
||||||
case MSG_LOG:
|
case MSG_LOG:
|
||||||
if (mLogListArray.size() > MAX_LOGS)
|
if (mLogListArray.size() > MAX_LOGS)
|
||||||
|
|||||||
@@ -38,7 +38,7 @@ author = 'Max Kellermann'
|
|||||||
# built documents.
|
# built documents.
|
||||||
#
|
#
|
||||||
# The short X.Y version.
|
# The short X.Y version.
|
||||||
version = '0.21.14'
|
version = '0.21.24'
|
||||||
# The full version, including alpha/beta/rc tags.
|
# The full version, including alpha/beta/rc tags.
|
||||||
release = version
|
release = version
|
||||||
|
|
||||||
|
|||||||
@@ -42,7 +42,7 @@ Provides access to the database of another :program:`MPD` instance using libmpdc
|
|||||||
* - **password**
|
* - **password**
|
||||||
- The password used to log in to the "master" :program:`MPD` instance.
|
- The password used to log in to the "master" :program:`MPD` instance.
|
||||||
* - **keepalive yes|no**
|
* - **keepalive yes|no**
|
||||||
- Send TCP keepalive packets to the "master" :program:`MPD` instance? This option can help avoid certain firewalls dropping inactive connections, at the expensive of a very small amount of additional network traffic. Disabled by default.
|
- Send TCP keepalive packets to the "master" :program:`MPD` instance? This option can help avoid certain firewalls dropping inactive connections, at the expense of a very small amount of additional network traffic. Disabled by default.
|
||||||
|
|
||||||
upnp
|
upnp
|
||||||
----
|
----
|
||||||
@@ -1069,7 +1069,7 @@ Filter plugins
|
|||||||
normalize
|
normalize
|
||||||
---------
|
---------
|
||||||
|
|
||||||
Normalize the volume during playback (at the expensve of quality).
|
Normalize the volume during playback (at the expense of quality).
|
||||||
|
|
||||||
|
|
||||||
null
|
null
|
||||||
|
|||||||
@@ -464,7 +464,8 @@ Querying :program:`MPD`'s status
|
|||||||
- ``songs``: number of songs
|
- ``songs``: number of songs
|
||||||
- ``uptime``: daemon uptime in seconds
|
- ``uptime``: daemon uptime in seconds
|
||||||
- ``db_playtime``: sum of all song times in the database in seconds
|
- ``db_playtime``: sum of all song times in the database in seconds
|
||||||
- ``db_update``: last db update in UNIX time
|
- ``db_update``: last db update in UNIX time (seconds since
|
||||||
|
1970-01-01 UTC)
|
||||||
- ``playtime``: time length of music played
|
- ``playtime``: time length of music played
|
||||||
|
|
||||||
Playback options
|
Playback options
|
||||||
|
|||||||
14
doc/user.rst
14
doc/user.rst
@@ -62,16 +62,16 @@ In any case, you need:
|
|||||||
Each plugin usually needs a codec library, which you also need to
|
Each plugin usually needs a codec library, which you also need to
|
||||||
install. Check the :doc:`plugins` for details about required libraries
|
install. Check the :doc:`plugins` for details about required libraries
|
||||||
|
|
||||||
For example, the following installs a fairly complete list of build dependencies on Debian Jessie:
|
For example, the following installs a fairly complete list of build dependencies on Debian Buster:
|
||||||
|
|
||||||
.. code-block:: none
|
.. code-block:: none
|
||||||
|
|
||||||
apt install g++ \
|
apt install meson g++ \
|
||||||
libpcre3-dev \
|
libpcre3-dev \
|
||||||
libmad0-dev libmpg123-dev libid3tag0-dev \
|
libmad0-dev libmpg123-dev libid3tag0-dev \
|
||||||
libflac-dev libvorbis-dev libopus-dev \
|
libflac-dev libvorbis-dev libopus-dev libogg-dev \
|
||||||
libadplug-dev libaudiofile-dev libsndfile1-dev libfaad-dev \
|
libadplug-dev libaudiofile-dev libsndfile1-dev libfaad-dev \
|
||||||
libfluidsynth-dev libgme-dev libmikmod2-dev libmodplug-dev \
|
libfluidsynth-dev libgme-dev libmikmod-dev libmodplug-dev \
|
||||||
libmpcdec-dev libwavpack-dev libwildmidi-dev \
|
libmpcdec-dev libwavpack-dev libwildmidi-dev \
|
||||||
libsidplay2-dev libsidutils-dev libresid-builder-dev \
|
libsidplay2-dev libsidutils-dev libresid-builder-dev \
|
||||||
libavcodec-dev libavformat-dev \
|
libavcodec-dev libavformat-dev \
|
||||||
@@ -91,7 +91,9 @@ For example, the following installs a fairly complete list of build dependencies
|
|||||||
libsystemd-dev \
|
libsystemd-dev \
|
||||||
libgtest-dev \
|
libgtest-dev \
|
||||||
libboost-dev \
|
libboost-dev \
|
||||||
libicu-dev
|
libicu-dev \
|
||||||
|
libchromaprint-dev \
|
||||||
|
libgcrypt20-dev
|
||||||
|
|
||||||
|
|
||||||
Now configure the source tree:
|
Now configure the source tree:
|
||||||
@@ -693,7 +695,7 @@ These settings are various limitations to prevent :program:`MPD` from using too
|
|||||||
* - **connection_timeout SECONDS**
|
* - **connection_timeout SECONDS**
|
||||||
- If a client does not send any new data in this time period, the connection is closed. Clients waiting in "idle" mode are excluded from this. Default is 60.
|
- If a client does not send any new data in this time period, the connection is closed. Clients waiting in "idle" mode are excluded from this. Default is 60.
|
||||||
* - **max_connections NUMBER**
|
* - **max_connections NUMBER**
|
||||||
- This specifies the maximum number of clients that can be connected to :program:`MPD` at the same time. Default is 5.
|
- This specifies the maximum number of clients that can be connected to :program:`MPD` at the same time. Default is 100.
|
||||||
* - **max_playlist_length NUMBER**
|
* - **max_playlist_length NUMBER**
|
||||||
- The maximum number of songs that can be in the playlist. Default is 16384.
|
- The maximum number of songs that can be in the playlist. Default is 16384.
|
||||||
* - **max_command_list_size KBYTES**
|
* - **max_command_list_size KBYTES**
|
||||||
|
|||||||
29
meson.build
29
meson.build
@@ -1,11 +1,12 @@
|
|||||||
project(
|
project(
|
||||||
'mpd',
|
'mpd',
|
||||||
['c', 'cpp'],
|
['c', 'cpp'],
|
||||||
version: '0.21.14',
|
version: '0.21.24',
|
||||||
meson_version: '>= 0.49.0',
|
meson_version: '>= 0.49.0',
|
||||||
default_options: [
|
default_options: [
|
||||||
'c_std=c99',
|
'c_std=c99',
|
||||||
'cpp_std=c++14'
|
'cpp_std=c++14',
|
||||||
|
'warning_level=2',
|
||||||
],
|
],
|
||||||
license: 'GPLv2+',
|
license: 'GPLv2+',
|
||||||
)
|
)
|
||||||
@@ -40,9 +41,6 @@ common_cxxflags = [
|
|||||||
]
|
]
|
||||||
|
|
||||||
test_common_flags = [
|
test_common_flags = [
|
||||||
'-Wall',
|
|
||||||
'-Wextra',
|
|
||||||
|
|
||||||
'-fvisibility=hidden',
|
'-fvisibility=hidden',
|
||||||
|
|
||||||
'-ffast-math',
|
'-ffast-math',
|
||||||
@@ -88,6 +86,10 @@ test_ldflags = [
|
|||||||
]
|
]
|
||||||
|
|
||||||
if get_option('buildtype') != 'debug'
|
if get_option('buildtype') != 'debug'
|
||||||
|
test_cxxflags += [
|
||||||
|
'-ffunction-sections',
|
||||||
|
'-fdata-sections',
|
||||||
|
]
|
||||||
test_cflags += [
|
test_cflags += [
|
||||||
'-ffunction-sections',
|
'-ffunction-sections',
|
||||||
'-fdata-sections',
|
'-fdata-sections',
|
||||||
@@ -138,7 +140,13 @@ conf.set('HAVE_GETPWNAM_R', compiler.has_function('getpwnam_r'))
|
|||||||
conf.set('HAVE_GETPWUID_R', compiler.has_function('getpwuid_r'))
|
conf.set('HAVE_GETPWUID_R', compiler.has_function('getpwuid_r'))
|
||||||
conf.set('HAVE_INITGROUPS', compiler.has_function('initgroups'))
|
conf.set('HAVE_INITGROUPS', compiler.has_function('initgroups'))
|
||||||
conf.set('HAVE_FNMATCH', compiler.has_function('fnmatch'))
|
conf.set('HAVE_FNMATCH', compiler.has_function('fnmatch'))
|
||||||
conf.set('HAVE_STRNDUP', compiler.has_function('strndup', prefix: '#define _GNU_SOURCE\n#include <string.h>'))
|
|
||||||
|
# Explicitly exclude Windows in this check because
|
||||||
|
# https://github.com/mesonbuild/meson/issues/3672 (reported in 2018,
|
||||||
|
# still not fixed in 2020) causes Meson to believe it exists, because
|
||||||
|
# __builtin_strndup() exists (but strndup() still cannot be used).
|
||||||
|
conf.set('HAVE_STRNDUP', not is_windows and compiler.has_function('strndup', prefix: '#define _GNU_SOURCE\n#include <string.h>'))
|
||||||
|
|
||||||
conf.set('HAVE_STRCASESTR', compiler.has_function('strcasestr'))
|
conf.set('HAVE_STRCASESTR', compiler.has_function('strcasestr'))
|
||||||
|
|
||||||
conf.set('HAVE_PRCTL', is_linux)
|
conf.set('HAVE_PRCTL', is_linux)
|
||||||
@@ -286,6 +294,7 @@ if not is_android
|
|||||||
else
|
else
|
||||||
sources += [
|
sources += [
|
||||||
'src/android/Context.cxx',
|
'src/android/Context.cxx',
|
||||||
|
'src/android/AudioManager.cxx',
|
||||||
'src/android/Environment.cxx',
|
'src/android/Environment.cxx',
|
||||||
'src/android/LogListener.cxx',
|
'src/android/LogListener.cxx',
|
||||||
]
|
]
|
||||||
@@ -304,8 +313,10 @@ if enable_database
|
|||||||
endif
|
endif
|
||||||
|
|
||||||
subdir('src/util')
|
subdir('src/util')
|
||||||
|
subdir('src/time')
|
||||||
subdir('src/system')
|
subdir('src/system')
|
||||||
subdir('src/thread')
|
subdir('src/thread')
|
||||||
|
subdir('src/net')
|
||||||
subdir('src/event')
|
subdir('src/event')
|
||||||
|
|
||||||
subdir('src/lib/dbus')
|
subdir('src/lib/dbus')
|
||||||
@@ -330,7 +341,6 @@ subdir('src/lib/yajl')
|
|||||||
|
|
||||||
subdir('src/fs')
|
subdir('src/fs')
|
||||||
subdir('src/config')
|
subdir('src/config')
|
||||||
subdir('src/net')
|
|
||||||
subdir('src/tag')
|
subdir('src/tag')
|
||||||
subdir('src/pcm')
|
subdir('src/pcm')
|
||||||
subdir('src/neighbor')
|
subdir('src/neighbor')
|
||||||
@@ -385,8 +395,11 @@ endif
|
|||||||
if archive_glue_dep.found()
|
if archive_glue_dep.found()
|
||||||
sources += [
|
sources += [
|
||||||
'src/TagArchive.cxx',
|
'src/TagArchive.cxx',
|
||||||
'src/db/update/Archive.cxx',
|
|
||||||
]
|
]
|
||||||
|
|
||||||
|
if enable_database
|
||||||
|
sources += ['src/db/update/Archive.cxx']
|
||||||
|
endif
|
||||||
endif
|
endif
|
||||||
|
|
||||||
if is_windows
|
if is_windows
|
||||||
|
|||||||
45
python/build/cmake.py
Normal file
45
python/build/cmake.py
Normal file
@@ -0,0 +1,45 @@
|
|||||||
|
import subprocess
|
||||||
|
|
||||||
|
from build.project import Project
|
||||||
|
|
||||||
|
def configure(toolchain, src, build, args=()):
|
||||||
|
cross_args = []
|
||||||
|
|
||||||
|
if toolchain.is_windows:
|
||||||
|
cross_args.append('-DCMAKE_SYSTEM_NAME=Windows')
|
||||||
|
cross_args.append('-DCMAKE_RC_COMPILER=' + toolchain.windres)
|
||||||
|
|
||||||
|
configure = [
|
||||||
|
'cmake',
|
||||||
|
src,
|
||||||
|
|
||||||
|
'-DCMAKE_INSTALL_PREFIX=' + toolchain.install_prefix,
|
||||||
|
'-DCMAKE_BUILD_TYPE=release',
|
||||||
|
|
||||||
|
'-DCMAKE_C_COMPILER=' + toolchain.cc,
|
||||||
|
'-DCMAKE_CXX_COMPILER=' + toolchain.cxx,
|
||||||
|
|
||||||
|
'-DCMAKE_C_FLAGS=' + toolchain.cflags + ' ' + toolchain.cppflags,
|
||||||
|
'-DCMAKE_CXX_FLAGS=' + toolchain.cxxflags + ' ' + toolchain.cppflags,
|
||||||
|
|
||||||
|
'-GNinja',
|
||||||
|
] + cross_args + args
|
||||||
|
|
||||||
|
subprocess.check_call(configure, env=toolchain.env, cwd=build)
|
||||||
|
|
||||||
|
class CmakeProject(Project):
|
||||||
|
def __init__(self, url, md5, installed, configure_args=[],
|
||||||
|
**kwargs):
|
||||||
|
Project.__init__(self, url, md5, installed, **kwargs)
|
||||||
|
self.configure_args = configure_args
|
||||||
|
|
||||||
|
def configure(self, toolchain):
|
||||||
|
src = self.unpack(toolchain)
|
||||||
|
build = self.make_build_path(toolchain)
|
||||||
|
configure(toolchain, src, build, self.configure_args)
|
||||||
|
return build
|
||||||
|
|
||||||
|
def build(self, toolchain):
|
||||||
|
build = self.configure(toolchain)
|
||||||
|
subprocess.check_call(['ninja', 'install'],
|
||||||
|
cwd=build, env=toolchain.env)
|
||||||
@@ -4,19 +4,20 @@ from os.path import abspath
|
|||||||
from build.project import Project
|
from build.project import Project
|
||||||
from build.zlib import ZlibProject
|
from build.zlib import ZlibProject
|
||||||
from build.meson import MesonProject
|
from build.meson import MesonProject
|
||||||
|
from build.cmake import CmakeProject
|
||||||
from build.autotools import AutotoolsProject
|
from build.autotools import AutotoolsProject
|
||||||
from build.ffmpeg import FfmpegProject
|
from build.ffmpeg import FfmpegProject
|
||||||
from build.boost import BoostProject
|
from build.boost import BoostProject
|
||||||
|
|
||||||
libmpdclient = MesonProject(
|
libmpdclient = MesonProject(
|
||||||
'https://www.musicpd.org/download/libmpdclient/2/libmpdclient-2.16.tar.xz',
|
'https://www.musicpd.org/download/libmpdclient/2/libmpdclient-2.18.tar.xz',
|
||||||
'fa6bdab67c0e0490302b38f00c27b4959735c3ec8aef7a88327adb1407654464',
|
'4cb01e1f567e0169aca94875fb6e1200e7f5ce35b63a4df768ec1591fb1081fa',
|
||||||
'lib/libmpdclient.a',
|
'lib/libmpdclient.a',
|
||||||
)
|
)
|
||||||
|
|
||||||
libogg = AutotoolsProject(
|
libogg = AutotoolsProject(
|
||||||
'http://downloads.xiph.org/releases/ogg/libogg-1.3.3.tar.xz',
|
'http://downloads.xiph.org/releases/ogg/libogg-1.3.4.tar.xz',
|
||||||
'4f3fc6178a533d392064f14776b23c397ed4b9f48f5de297aba73b643f955c08',
|
'c163bc12bc300c401b6aa35907ac682671ea376f13ae0969a220f7ddf71893fe',
|
||||||
'lib/libogg.a',
|
'lib/libogg.a',
|
||||||
[
|
[
|
||||||
'--disable-shared', '--enable-static',
|
'--disable-shared', '--enable-static',
|
||||||
@@ -38,8 +39,8 @@ libvorbis = AutotoolsProject(
|
|||||||
)
|
)
|
||||||
|
|
||||||
opus = AutotoolsProject(
|
opus = AutotoolsProject(
|
||||||
'https://archive.mozilla.org/pub/opus/opus-1.3.tar.gz',
|
'https://archive.mozilla.org/pub/opus/opus-1.3.1.tar.gz',
|
||||||
'4f3d69aefdf2dbaf9825408e452a8a414ffc60494c70633560700398820dc550',
|
'65b58e1e25b2a114157014736a3d9dfeaad8d41be1c8179866f144a2fb44ff9d',
|
||||||
'lib/libopus.a',
|
'lib/libopus.a',
|
||||||
[
|
[
|
||||||
'--disable-shared', '--enable-static',
|
'--disable-shared', '--enable-static',
|
||||||
@@ -52,8 +53,8 @@ opus = AutotoolsProject(
|
|||||||
)
|
)
|
||||||
|
|
||||||
flac = AutotoolsProject(
|
flac = AutotoolsProject(
|
||||||
'http://downloads.xiph.org/releases/flac/flac-1.3.2.tar.xz',
|
'http://downloads.xiph.org/releases/flac/flac-1.3.3.tar.xz',
|
||||||
'91cfc3ed61dc40f47f050a109b08610667d73477af6ef36dcad31c31a4a8d53f',
|
'213e82bd716c9de6db2f98bcadbc4c24c7e2efe8c75939a1a84e28539c4e1748',
|
||||||
'lib/libFLAC.a',
|
'lib/libFLAC.a',
|
||||||
[
|
[
|
||||||
'--disable-shared', '--enable-static',
|
'--disable-shared', '--enable-static',
|
||||||
@@ -111,9 +112,44 @@ liblame = AutotoolsProject(
|
|||||||
],
|
],
|
||||||
)
|
)
|
||||||
|
|
||||||
|
libmodplug = AutotoolsProject(
|
||||||
|
'https://downloads.sourceforge.net/modplug-xmms/libmodplug/0.8.9.0/libmodplug-0.8.9.0.tar.gz',
|
||||||
|
'457ca5a6c179656d66c01505c0d95fafaead4329b9dbaa0f997d00a3508ad9de',
|
||||||
|
'lib/libmodplug.a',
|
||||||
|
[
|
||||||
|
'--disable-shared', '--enable-static',
|
||||||
|
],
|
||||||
|
)
|
||||||
|
|
||||||
|
wildmidi = CmakeProject(
|
||||||
|
'https://codeload.github.com/Mindwerks/wildmidi/tar.gz/wildmidi-0.4.3',
|
||||||
|
'498e5a96455bb4b91b37188ad6dcb070824e92c44f5ed452b90adbaec8eef3c5',
|
||||||
|
'lib/libWildMidi.a',
|
||||||
|
[
|
||||||
|
'-DBUILD_SHARED_LIBS=OFF',
|
||||||
|
'-DWANT_PLAYER=OFF',
|
||||||
|
'-DWANT_STATIC=ON',
|
||||||
|
],
|
||||||
|
base='wildmidi-wildmidi-0.4.3',
|
||||||
|
name='wildmidi',
|
||||||
|
version='0.4.3',
|
||||||
|
)
|
||||||
|
|
||||||
|
gme = CmakeProject(
|
||||||
|
'https://bitbucket.org/mpyne/game-music-emu/downloads/game-music-emu-0.6.3.tar.xz',
|
||||||
|
'aba34e53ef0ec6a34b58b84e28bf8cfbccee6585cebca25333604c35db3e051d',
|
||||||
|
'lib/libgme.a',
|
||||||
|
[
|
||||||
|
'-DBUILD_SHARED_LIBS=OFF',
|
||||||
|
'-DENABLE_UBSAN=OFF',
|
||||||
|
'-DZLIB_INCLUDE_DIR=OFF',
|
||||||
|
'-DSDL2_DIR=OFF',
|
||||||
|
],
|
||||||
|
)
|
||||||
|
|
||||||
ffmpeg = FfmpegProject(
|
ffmpeg = FfmpegProject(
|
||||||
'http://ffmpeg.org/releases/ffmpeg-4.1.3.tar.xz',
|
'http://ffmpeg.org/releases/ffmpeg-4.2.3.tar.xz',
|
||||||
'0c3020452880581a8face91595b239198078645e7d7184273b8bcc7758beb63d',
|
'9df6c90aed1337634c1fb026fb01c154c29c82a64ea71291ff2da9aacb9aad31',
|
||||||
'lib/libavcodec.a',
|
'lib/libavcodec.a',
|
||||||
[
|
[
|
||||||
'--disable-shared', '--enable-static',
|
'--disable-shared', '--enable-static',
|
||||||
@@ -341,8 +377,8 @@ ffmpeg = FfmpegProject(
|
|||||||
)
|
)
|
||||||
|
|
||||||
curl = AutotoolsProject(
|
curl = AutotoolsProject(
|
||||||
'http://curl.haxx.se/download/curl-7.64.1.tar.xz',
|
'http://curl.haxx.se/download/curl-7.70.0.tar.xz',
|
||||||
'9252332a7f871ce37bfa7f78bdd0a0e3924d8187cc27cb57c76c9474a7168fb3',
|
'032f43f2674008c761af19bf536374128c16241fb234699a55f9fb603fcfbae7',
|
||||||
'lib/libcurl.a',
|
'lib/libcurl.a',
|
||||||
[
|
[
|
||||||
'--disable-shared', '--enable-static',
|
'--disable-shared', '--enable-static',
|
||||||
@@ -358,6 +394,11 @@ curl = AutotoolsProject(
|
|||||||
'--disable-manual',
|
'--disable-manual',
|
||||||
'--disable-threaded-resolver', '--disable-verbose', '--disable-sspi',
|
'--disable-threaded-resolver', '--disable-verbose', '--disable-sspi',
|
||||||
'--disable-crypto-auth', '--disable-ntlm-wb', '--disable-tls-srp', '--disable-cookies',
|
'--disable-crypto-auth', '--disable-ntlm-wb', '--disable-tls-srp', '--disable-cookies',
|
||||||
|
'--disable-doh',
|
||||||
|
'--disable-mime',
|
||||||
|
'--disable-netrc',
|
||||||
|
'--disable-progress-meter',
|
||||||
|
'--disable-alt-svc',
|
||||||
'--without-ssl', '--without-gnutls', '--without-nss', '--without-libssh2',
|
'--without-ssl', '--without-gnutls', '--without-nss', '--without-libssh2',
|
||||||
],
|
],
|
||||||
|
|
||||||
@@ -365,8 +406,8 @@ curl = AutotoolsProject(
|
|||||||
)
|
)
|
||||||
|
|
||||||
libexpat = AutotoolsProject(
|
libexpat = AutotoolsProject(
|
||||||
'https://github.com/libexpat/libexpat/releases/download/R_2_2_6/expat-2.2.6.tar.bz2',
|
'https://github.com/libexpat/libexpat/releases/download/R_2_2_9/expat-2.2.9.tar.bz2',
|
||||||
'17b43c2716d521369f82fc2dc70f359860e90fa440bea65b3b85f0b246ea81f2',
|
'f1063084dc4302a427dabcca499c8312b3a32a29b7d2506653ecc8f950a9a237',
|
||||||
'lib/libexpat.a',
|
'lib/libexpat.a',
|
||||||
[
|
[
|
||||||
'--disable-shared', '--enable-static',
|
'--disable-shared', '--enable-static',
|
||||||
@@ -392,7 +433,7 @@ libnfs = AutotoolsProject(
|
|||||||
)
|
)
|
||||||
|
|
||||||
boost = BoostProject(
|
boost = BoostProject(
|
||||||
'http://downloads.sourceforge.net/project/boost/boost/1.70.0/boost_1_70_0.tar.bz2',
|
'https://dl.bintray.com/boostorg/release/1.73.0/source/boost_1_73_0.tar.bz2',
|
||||||
'430ae8354789de4fd19ee52f3b1f739e1fba576f0aded0897c3c2bc00fb38778',
|
'4eb3b8d442b426dc35346235c8733b5ae35ba431690e38c6a8263dce9fcbb402',
|
||||||
'include/boost/version.hpp',
|
'include/boost/version.hpp',
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -20,7 +20,7 @@
|
|||||||
#ifndef MPD_AUDIO_FORMAT_HXX
|
#ifndef MPD_AUDIO_FORMAT_HXX
|
||||||
#define MPD_AUDIO_FORMAT_HXX
|
#define MPD_AUDIO_FORMAT_HXX
|
||||||
|
|
||||||
#include "pcm/SampleFormat.hxx"
|
#include "pcm/SampleFormat.hxx" // IWYU pragma: export
|
||||||
#include "util/Compiler.h"
|
#include "util/Compiler.h"
|
||||||
|
|
||||||
#include <chrono>
|
#include <chrono>
|
||||||
|
|||||||
@@ -33,11 +33,11 @@
|
|||||||
#include "playlist/PlaylistRegistry.hxx"
|
#include "playlist/PlaylistRegistry.hxx"
|
||||||
#include "playlist/PlaylistPlugin.hxx"
|
#include "playlist/PlaylistPlugin.hxx"
|
||||||
#include "fs/AllocatedPath.hxx"
|
#include "fs/AllocatedPath.hxx"
|
||||||
|
#include "fs/NarrowPath.hxx"
|
||||||
#include "fs/Traits.hxx"
|
#include "fs/Traits.hxx"
|
||||||
#include "fs/FileSystem.hxx"
|
#include "fs/FileSystem.hxx"
|
||||||
#include "fs/StandardDirectory.hxx"
|
#include "fs/StandardDirectory.hxx"
|
||||||
#include "system/Error.hxx"
|
#include "system/Error.hxx"
|
||||||
#include "util/Macros.hxx"
|
|
||||||
#include "util/RuntimeError.hxx"
|
#include "util/RuntimeError.hxx"
|
||||||
#include "util/Domain.hxx"
|
#include "util/Domain.hxx"
|
||||||
#include "util/OptionDef.hxx"
|
#include "util/OptionDef.hxx"
|
||||||
@@ -380,17 +380,7 @@ ParseCommandLine(int argc, char **argv, struct options &options,
|
|||||||
|
|
||||||
if (config_file != nullptr) {
|
if (config_file != nullptr) {
|
||||||
/* use specified configuration file */
|
/* use specified configuration file */
|
||||||
#ifdef _UNICODE
|
ReadConfigFile(config, FromNarrowPath(config_file));
|
||||||
wchar_t buffer[MAX_PATH];
|
|
||||||
auto result = MultiByteToWideChar(CP_ACP, 0, config_file, -1,
|
|
||||||
buffer, ARRAY_SIZE(buffer));
|
|
||||||
if (result <= 0)
|
|
||||||
throw MakeLastError("MultiByteToWideChar() failed");
|
|
||||||
|
|
||||||
ReadConfigFile(config, Path::FromFS(buffer));
|
|
||||||
#else
|
|
||||||
ReadConfigFile(config, Path::FromFS(config_file));
|
|
||||||
#endif
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -29,6 +29,8 @@
|
|||||||
#include "storage/StorageInterface.hxx"
|
#include "storage/StorageInterface.hxx"
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
#include <stdexcept>
|
||||||
|
|
||||||
static LocatedUri
|
static LocatedUri
|
||||||
LocateFileUri(const char *uri, const Client *client
|
LocateFileUri(const char *uri, const Client *client
|
||||||
#ifdef ENABLE_DATABASE
|
#ifdef ENABLE_DATABASE
|
||||||
|
|||||||
@@ -460,7 +460,7 @@ MainOrThrow(int argc, char *argv[])
|
|||||||
#endif
|
#endif
|
||||||
|
|
||||||
const unsigned max_clients =
|
const unsigned max_clients =
|
||||||
raw_config.GetPositive(ConfigOption::MAX_CONN, 10);
|
raw_config.GetPositive(ConfigOption::MAX_CONN, 100);
|
||||||
instance->client_list = new ClientList(max_clients);
|
instance->client_list = new ClientList(max_clients);
|
||||||
|
|
||||||
initialize_decoder_and_player(raw_config, config.replay_gain);
|
initialize_decoder_and_player(raw_config, config.replay_gain);
|
||||||
|
|||||||
@@ -21,8 +21,8 @@
|
|||||||
#include "db/PlaylistVector.hxx"
|
#include "db/PlaylistVector.hxx"
|
||||||
#include "fs/io/TextFile.hxx"
|
#include "fs/io/TextFile.hxx"
|
||||||
#include "fs/io/BufferedOutputStream.hxx"
|
#include "fs/io/BufferedOutputStream.hxx"
|
||||||
|
#include "time/ChronoUtil.hxx"
|
||||||
#include "util/StringStrip.hxx"
|
#include "util/StringStrip.hxx"
|
||||||
#include "util/ChronoUtil.hxx"
|
|
||||||
#include "util/RuntimeError.hxx"
|
#include "util/RuntimeError.hxx"
|
||||||
|
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
|
|||||||
@@ -38,6 +38,10 @@ struct ReplayGainTuple {
|
|||||||
return gain > -100;
|
return gain > -100;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static constexpr ReplayGainTuple Undefined() noexcept {
|
||||||
|
return {-200.0f, 0.0f};
|
||||||
|
}
|
||||||
|
|
||||||
gcc_pure
|
gcc_pure
|
||||||
float CalculateScale(const ReplayGainConfig &config) const noexcept;
|
float CalculateScale(const ReplayGainConfig &config) const noexcept;
|
||||||
};
|
};
|
||||||
@@ -49,6 +53,13 @@ struct ReplayGainInfo {
|
|||||||
return track.IsDefined() || album.IsDefined();
|
return track.IsDefined() || album.IsDefined();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static constexpr ReplayGainInfo Undefined() noexcept {
|
||||||
|
return {
|
||||||
|
ReplayGainTuple::Undefined(),
|
||||||
|
ReplayGainTuple::Undefined(),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
const ReplayGainTuple &Get(ReplayGainMode mode) const noexcept {
|
const ReplayGainTuple &Get(ReplayGainMode mode) const noexcept {
|
||||||
return mode == ReplayGainMode::ALBUM
|
return mode == ReplayGainMode::ALBUM
|
||||||
? (album.IsDefined() ? album : track)
|
? (album.IsDefined() ? album : track)
|
||||||
|
|||||||
@@ -27,7 +27,7 @@
|
|||||||
#include "TagPrint.hxx"
|
#include "TagPrint.hxx"
|
||||||
#include "client/Response.hxx"
|
#include "client/Response.hxx"
|
||||||
#include "fs/Traits.hxx"
|
#include "fs/Traits.hxx"
|
||||||
#include "util/ChronoUtil.hxx"
|
#include "time/ChronoUtil.hxx"
|
||||||
#include "util/UriUtil.hxx"
|
#include "util/UriUtil.hxx"
|
||||||
|
|
||||||
#define SONG_FILE "file: "
|
#define SONG_FILE "file: "
|
||||||
|
|||||||
@@ -27,7 +27,7 @@
|
|||||||
#include "tag/ParseName.hxx"
|
#include "tag/ParseName.hxx"
|
||||||
#include "tag/Tag.hxx"
|
#include "tag/Tag.hxx"
|
||||||
#include "tag/Builder.hxx"
|
#include "tag/Builder.hxx"
|
||||||
#include "util/ChronoUtil.hxx"
|
#include "time/ChronoUtil.hxx"
|
||||||
#include "util/StringAPI.hxx"
|
#include "util/StringAPI.hxx"
|
||||||
#include "util/StringBuffer.hxx"
|
#include "util/StringBuffer.hxx"
|
||||||
#include "util/StringStrip.hxx"
|
#include "util/StringStrip.hxx"
|
||||||
|
|||||||
@@ -98,8 +98,6 @@ Song::UpdateFile(Storage &storage) noexcept
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#ifdef ENABLE_ARCHIVE
|
#ifdef ENABLE_ARCHIVE
|
||||||
|
|
||||||
Song *
|
Song *
|
||||||
@@ -145,6 +143,8 @@ Song::UpdateFileInArchive(ArchiveFile &archive) noexcept
|
|||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
#endif /* ENABLE_DATABASE */
|
||||||
|
|
||||||
bool
|
bool
|
||||||
DetachedSong::LoadFile(Path path) noexcept
|
DetachedSong::LoadFile(Path path) noexcept
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -28,10 +28,10 @@
|
|||||||
#include "db/Stats.hxx"
|
#include "db/Stats.hxx"
|
||||||
#include "system/Clock.hxx"
|
#include "system/Clock.hxx"
|
||||||
#include "Log.hxx"
|
#include "Log.hxx"
|
||||||
#include "util/ChronoUtil.hxx"
|
#include "time/ChronoUtil.hxx"
|
||||||
|
#include "util/Math.hxx"
|
||||||
|
|
||||||
#include <chrono>
|
#include <chrono>
|
||||||
#include <cmath>
|
|
||||||
|
|
||||||
#ifndef _WIN32
|
#ifndef _WIN32
|
||||||
/**
|
/**
|
||||||
@@ -121,7 +121,7 @@ stats_print(Response &r, const Partition &partition)
|
|||||||
#else
|
#else
|
||||||
(unsigned)std::chrono::duration_cast<std::chrono::seconds>(std::chrono::steady_clock::now() - start_time).count(),
|
(unsigned)std::chrono::duration_cast<std::chrono::seconds>(std::chrono::steady_clock::now() - start_time).count(),
|
||||||
#endif
|
#endif
|
||||||
std::lround(partition.pc.GetTotalPlayTime().count()));
|
lround(partition.pc.GetTotalPlayTime().count()));
|
||||||
|
|
||||||
#ifdef ENABLE_DATABASE
|
#ifdef ENABLE_DATABASE
|
||||||
const Database *db = partition.instance.GetDatabase();
|
const Database *db = partition.instance.GetDatabase();
|
||||||
|
|||||||
@@ -19,7 +19,7 @@
|
|||||||
|
|
||||||
#include "TimePrint.hxx"
|
#include "TimePrint.hxx"
|
||||||
#include "client/Response.hxx"
|
#include "client/Response.hxx"
|
||||||
#include "util/TimeISO8601.hxx"
|
#include "time/ISO8601.hxx"
|
||||||
|
|
||||||
void
|
void
|
||||||
time_print(Response &r, const char *name,
|
time_print(Response &r, const char *name,
|
||||||
|
|||||||
56
src/android/AudioManager.cxx
Normal file
56
src/android/AudioManager.cxx
Normal file
@@ -0,0 +1,56 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2003-2020 The Music Player Daemon Project
|
||||||
|
* http://www.musicpd.org
|
||||||
|
*
|
||||||
|
* This program is free software; you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation; either version 2 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License along
|
||||||
|
* with this program; if not, write to the Free Software Foundation, Inc.,
|
||||||
|
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "AudioManager.hxx"
|
||||||
|
#include "java/Class.hxx"
|
||||||
|
#include "java/Exception.hxx"
|
||||||
|
#include "java/File.hxx"
|
||||||
|
|
||||||
|
#define STREAM_MUSIC 3
|
||||||
|
|
||||||
|
AudioManager::AudioManager(JNIEnv *env, jobject obj) noexcept
|
||||||
|
: Java::GlobalObject(env, obj)
|
||||||
|
{
|
||||||
|
Java::Class cls(env, env->GetObjectClass(Get()));
|
||||||
|
jmethodID method = env->GetMethodID(cls, "getStreamMaxVolume", "(I)I");
|
||||||
|
assert(method);
|
||||||
|
maxVolume = env->CallIntMethod(Get(), method, STREAM_MUSIC);
|
||||||
|
|
||||||
|
getStreamVolumeMethod = env->GetMethodID(cls, "getStreamVolume", "(I)I");
|
||||||
|
assert(getStreamVolumeMethod);
|
||||||
|
|
||||||
|
setStreamVolumeMethod = env->GetMethodID(cls, "setStreamVolume", "(III)V");
|
||||||
|
assert(setStreamVolumeMethod);
|
||||||
|
}
|
||||||
|
|
||||||
|
int
|
||||||
|
AudioManager::GetVolume(JNIEnv *env)
|
||||||
|
{
|
||||||
|
if (maxVolume == 0)
|
||||||
|
return 0;
|
||||||
|
return env->CallIntMethod(Get(), getStreamVolumeMethod, STREAM_MUSIC);
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
AudioManager::SetVolume(JNIEnv *env, int volume)
|
||||||
|
{
|
||||||
|
if (maxVolume == 0)
|
||||||
|
return;
|
||||||
|
env->CallVoidMethod(Get(), setStreamVolumeMethod, STREAM_MUSIC, volume, 0);
|
||||||
|
}
|
||||||
42
src/android/AudioManager.hxx
Normal file
42
src/android/AudioManager.hxx
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2003-2020 The Music Player Daemon Project
|
||||||
|
* http://www.musicpd.org
|
||||||
|
*
|
||||||
|
* This program is free software; you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation; either version 2 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License along
|
||||||
|
* with this program; if not, write to the Free Software Foundation, Inc.,
|
||||||
|
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef MPD_ANDROID_AUDIO_MANAGER_HXX
|
||||||
|
#define MPD_ANDROID_AUDIO_MANAGER_HXX
|
||||||
|
|
||||||
|
#include "java/Object.hxx"
|
||||||
|
|
||||||
|
class AudioManager : public Java::GlobalObject {
|
||||||
|
int maxVolume;
|
||||||
|
jmethodID getStreamVolumeMethod;
|
||||||
|
jmethodID setStreamVolumeMethod;
|
||||||
|
|
||||||
|
public:
|
||||||
|
AudioManager(JNIEnv *env, jobject obj) noexcept;
|
||||||
|
|
||||||
|
AudioManager(std::nullptr_t) noexcept { maxVolume = 0; }
|
||||||
|
|
||||||
|
~AudioManager() noexcept {}
|
||||||
|
|
||||||
|
int GetMaxVolume() { return maxVolume; }
|
||||||
|
int GetVolume(JNIEnv *env);
|
||||||
|
void SetVolume(JNIEnv *env, int);
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright 2003-2018 The Music Player Daemon Project
|
* Copyright 2003-2019 The Music Player Daemon Project
|
||||||
* http://www.musicpd.org
|
* http://www.musicpd.org
|
||||||
*
|
*
|
||||||
* This program is free software; you can redistribute it and/or modify
|
* This program is free software; you can redistribute it and/or modify
|
||||||
@@ -20,10 +20,13 @@
|
|||||||
#include "Context.hxx"
|
#include "Context.hxx"
|
||||||
#include "java/Class.hxx"
|
#include "java/Class.hxx"
|
||||||
#include "java/File.hxx"
|
#include "java/File.hxx"
|
||||||
|
#include "java/String.hxx"
|
||||||
#include "fs/AllocatedPath.hxx"
|
#include "fs/AllocatedPath.hxx"
|
||||||
|
|
||||||
|
#include "AudioManager.hxx"
|
||||||
|
|
||||||
AllocatedPath
|
AllocatedPath
|
||||||
Context::GetCacheDir(JNIEnv *env) const
|
Context::GetCacheDir(JNIEnv *env) const noexcept
|
||||||
{
|
{
|
||||||
assert(env != nullptr);
|
assert(env != nullptr);
|
||||||
|
|
||||||
@@ -40,3 +43,21 @@ Context::GetCacheDir(JNIEnv *env) const
|
|||||||
|
|
||||||
return Java::File::ToAbsolutePath(env, file);
|
return Java::File::ToAbsolutePath(env, file);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
AudioManager *
|
||||||
|
Context::GetAudioManager(JNIEnv *env) noexcept
|
||||||
|
{
|
||||||
|
assert(env != nullptr);
|
||||||
|
|
||||||
|
Java::Class cls(env, env->GetObjectClass(Get()));
|
||||||
|
jmethodID method = env->GetMethodID(cls, "getSystemService",
|
||||||
|
"(Ljava/lang/String;)Ljava/lang/Object;");
|
||||||
|
assert(method);
|
||||||
|
|
||||||
|
Java::String name(env, "audio");
|
||||||
|
jobject am = env->CallObjectMethod(Get(), method, name.Get());
|
||||||
|
if (Java::DiscardException(env) || am == nullptr)
|
||||||
|
return nullptr;
|
||||||
|
|
||||||
|
return new AudioManager(env, am);
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright 2003-2018 The Music Player Daemon Project
|
* Copyright 2003-2019 The Music Player Daemon Project
|
||||||
* http://www.musicpd.org
|
* http://www.musicpd.org
|
||||||
*
|
*
|
||||||
* This program is free software; you can redistribute it and/or modify
|
* This program is free software; you can redistribute it and/or modify
|
||||||
@@ -23,13 +23,18 @@
|
|||||||
#include "java/Object.hxx"
|
#include "java/Object.hxx"
|
||||||
|
|
||||||
class AllocatedPath;
|
class AllocatedPath;
|
||||||
|
class AudioManager;
|
||||||
|
|
||||||
class Context : public Java::Object {
|
class Context : public Java::GlobalObject {
|
||||||
public:
|
public:
|
||||||
Context(JNIEnv *env, jobject obj):Java::Object(env, obj) {}
|
Context(JNIEnv *env, jobject obj) noexcept
|
||||||
|
:Java::GlobalObject(env, obj) {}
|
||||||
|
|
||||||
gcc_pure
|
gcc_pure
|
||||||
AllocatedPath GetCacheDir(JNIEnv *env) const;
|
AllocatedPath GetCacheDir(JNIEnv *env) const noexcept;
|
||||||
|
|
||||||
|
gcc_pure
|
||||||
|
AudioManager *GetAudioManager(JNIEnv *env) noexcept;
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
@@ -22,9 +22,9 @@
|
|||||||
|
|
||||||
#include "java/Object.hxx"
|
#include "java/Object.hxx"
|
||||||
|
|
||||||
class LogListener : public Java::Object {
|
class LogListener : public Java::GlobalObject {
|
||||||
public:
|
public:
|
||||||
LogListener(JNIEnv *env, jobject obj):Java::Object(env, obj) {}
|
LogListener(JNIEnv *env, jobject obj):Java::GlobalObject(env, obj) {}
|
||||||
|
|
||||||
void OnLog(JNIEnv *env, int priority, const char *fmt, ...) const;
|
void OnLog(JNIEnv *env, int priority, const char *fmt, ...) const;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -28,6 +28,7 @@
|
|||||||
#include "input/InputStream.hxx"
|
#include "input/InputStream.hxx"
|
||||||
#include "fs/Path.hxx"
|
#include "fs/Path.hxx"
|
||||||
#include "util/RuntimeError.hxx"
|
#include "util/RuntimeError.hxx"
|
||||||
|
#include "util/StringCompare.hxx"
|
||||||
|
|
||||||
#include <cdio/iso9660.h>
|
#include <cdio/iso9660.h>
|
||||||
|
|
||||||
@@ -93,7 +94,10 @@ Iso9660ArchiveFile::Visit(char *path, size_t length, size_t capacity,
|
|||||||
auto *statbuf = (iso9660_stat_t *)
|
auto *statbuf = (iso9660_stat_t *)
|
||||||
_cdio_list_node_data(entnode);
|
_cdio_list_node_data(entnode);
|
||||||
const char *filename = statbuf->filename;
|
const char *filename = statbuf->filename;
|
||||||
if (strcmp(filename, ".") == 0 || strcmp(filename, "..") == 0)
|
if (StringIsEmpty(filename) ||
|
||||||
|
PathTraitsUTF8::IsSpecialFilename(filename))
|
||||||
|
/* skip empty names (libcdio bug?) */
|
||||||
|
/* skip special names like "." and ".." */
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
size_t filename_length = strlen(filename);
|
size_t filename_length = strlen(filename);
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright 2003-2018 The Music Player Daemon Project
|
* Copyright 2003-2019 The Music Player Daemon Project
|
||||||
* http://www.musicpd.org
|
* http://www.musicpd.org
|
||||||
*
|
*
|
||||||
* This program is free software; you can redistribute it and/or modify
|
* This program is free software; you can redistribute it and/or modify
|
||||||
@@ -27,6 +27,7 @@
|
|||||||
#include "../ArchiveVisitor.hxx"
|
#include "../ArchiveVisitor.hxx"
|
||||||
#include "input/InputStream.hxx"
|
#include "input/InputStream.hxx"
|
||||||
#include "fs/Path.hxx"
|
#include "fs/Path.hxx"
|
||||||
|
#include "system/Error.hxx"
|
||||||
#include "util/RuntimeError.hxx"
|
#include "util/RuntimeError.hxx"
|
||||||
|
|
||||||
#include <zzip/zzip.h>
|
#include <zzip/zzip.h>
|
||||||
@@ -120,9 +121,19 @@ ZzipArchiveFile::OpenStream(const char *pathname,
|
|||||||
Mutex &mutex)
|
Mutex &mutex)
|
||||||
{
|
{
|
||||||
ZZIP_FILE *_file = zzip_file_open(dir->dir, pathname, 0);
|
ZZIP_FILE *_file = zzip_file_open(dir->dir, pathname, 0);
|
||||||
if (_file == nullptr)
|
if (_file == nullptr) {
|
||||||
throw FormatRuntimeError("not found in the ZIP file: %s",
|
const auto error = (zzip_error_t)zzip_error(dir->dir);
|
||||||
pathname);
|
switch (error) {
|
||||||
|
case ZZIP_ENOENT:
|
||||||
|
throw FormatFileNotFound("Failed to open '%s' in ZIP file",
|
||||||
|
pathname);
|
||||||
|
|
||||||
|
default:
|
||||||
|
throw FormatRuntimeError("Failed to open '%s' in ZIP file: %s",
|
||||||
|
pathname,
|
||||||
|
zzip_strerror(error));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return std::make_unique<ZzipInputStream>(dir, pathname,
|
return std::make_unique<ZzipInputStream>(dir, pathname,
|
||||||
mutex,
|
mutex,
|
||||||
|
|||||||
@@ -193,7 +193,7 @@ static constexpr struct command commands[] = {
|
|||||||
{ "subscribe", PERMISSION_READ, 1, 1, handle_subscribe },
|
{ "subscribe", PERMISSION_READ, 1, 1, handle_subscribe },
|
||||||
{ "swap", PERMISSION_CONTROL, 2, 2, handle_swap },
|
{ "swap", PERMISSION_CONTROL, 2, 2, handle_swap },
|
||||||
{ "swapid", PERMISSION_CONTROL, 2, 2, handle_swapid },
|
{ "swapid", PERMISSION_CONTROL, 2, 2, handle_swapid },
|
||||||
{ "tagtypes", PERMISSION_READ, 0, -1, handle_tagtypes },
|
{ "tagtypes", PERMISSION_NONE, 0, -1, handle_tagtypes },
|
||||||
{ "toggleoutput", PERMISSION_ADMIN, 1, 1, handle_toggleoutput },
|
{ "toggleoutput", PERMISSION_ADMIN, 1, 1, handle_toggleoutput },
|
||||||
#ifdef ENABLE_DATABASE
|
#ifdef ENABLE_DATABASE
|
||||||
{ "unmount", PERMISSION_ADMIN, 1, 1, handle_unmount },
|
{ "unmount", PERMISSION_ADMIN, 1, 1, handle_unmount },
|
||||||
|
|||||||
@@ -35,7 +35,7 @@
|
|||||||
#include "decoder/DecoderPrint.hxx"
|
#include "decoder/DecoderPrint.hxx"
|
||||||
#include "ls.hxx"
|
#include "ls.hxx"
|
||||||
#include "mixer/Volume.hxx"
|
#include "mixer/Volume.hxx"
|
||||||
#include "util/ChronoUtil.hxx"
|
#include "time/ChronoUtil.hxx"
|
||||||
#include "util/UriUtil.hxx"
|
#include "util/UriUtil.hxx"
|
||||||
#include "util/StringAPI.hxx"
|
#include "util/StringAPI.hxx"
|
||||||
#include "fs/AllocatedPath.hxx"
|
#include "fs/AllocatedPath.hxx"
|
||||||
|
|||||||
@@ -34,13 +34,12 @@
|
|||||||
#include "util/StringBuffer.hxx"
|
#include "util/StringBuffer.hxx"
|
||||||
#include "util/ScopeExit.hxx"
|
#include "util/ScopeExit.hxx"
|
||||||
#include "util/Exception.hxx"
|
#include "util/Exception.hxx"
|
||||||
|
#include "util/Math.hxx"
|
||||||
|
|
||||||
#ifdef ENABLE_DATABASE
|
#ifdef ENABLE_DATABASE
|
||||||
#include "db/update/Service.hxx"
|
#include "db/update/Service.hxx"
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#include <cmath>
|
|
||||||
|
|
||||||
#define COMMAND_STATUS_STATE "state"
|
#define COMMAND_STATUS_STATE "state"
|
||||||
#define COMMAND_STATUS_REPEAT "repeat"
|
#define COMMAND_STATUS_REPEAT "repeat"
|
||||||
#define COMMAND_STATUS_SINGLE "single"
|
#define COMMAND_STATUS_SINGLE "single"
|
||||||
@@ -154,7 +153,7 @@ handle_status(Client &client, gcc_unused Request args, Response &r)
|
|||||||
|
|
||||||
if (pc.GetCrossFade() > FloatDuration::zero())
|
if (pc.GetCrossFade() > FloatDuration::zero())
|
||||||
r.Format(COMMAND_STATUS_CROSSFADE ": %lu\n",
|
r.Format(COMMAND_STATUS_CROSSFADE ": %lu\n",
|
||||||
std::lround(pc.GetCrossFade().count()));
|
lround(pc.GetCrossFade().count()));
|
||||||
|
|
||||||
if (pc.GetMixRampDelay() > FloatDuration::zero())
|
if (pc.GetMixRampDelay() > FloatDuration::zero())
|
||||||
r.Format(COMMAND_STATUS_MIXRAMPDELAY ": %f\n",
|
r.Format(COMMAND_STATUS_MIXRAMPDELAY ": %f\n",
|
||||||
@@ -173,7 +172,7 @@ handle_status(Client &client, gcc_unused Request args, Response &r)
|
|||||||
COMMAND_STATUS_BITRATE ": %u\n",
|
COMMAND_STATUS_BITRATE ": %u\n",
|
||||||
player_status.elapsed_time.RoundS(),
|
player_status.elapsed_time.RoundS(),
|
||||||
player_status.total_time.IsNegative()
|
player_status.total_time.IsNegative()
|
||||||
? 0u
|
? 0U
|
||||||
: unsigned(player_status.total_time.RoundS()),
|
: unsigned(player_status.total_time.RoundS()),
|
||||||
player_status.elapsed_time.ToDoubleS(),
|
player_status.elapsed_time.ToDoubleS(),
|
||||||
player_status.bit_rate);
|
player_status.bit_rate);
|
||||||
|
|||||||
@@ -37,9 +37,9 @@
|
|||||||
#include "client/Response.hxx"
|
#include "client/Response.hxx"
|
||||||
#include "Mapper.hxx"
|
#include "Mapper.hxx"
|
||||||
#include "fs/AllocatedPath.hxx"
|
#include "fs/AllocatedPath.hxx"
|
||||||
|
#include "time/ChronoUtil.hxx"
|
||||||
#include "util/UriUtil.hxx"
|
#include "util/UriUtil.hxx"
|
||||||
#include "util/ConstBuffer.hxx"
|
#include "util/ConstBuffer.hxx"
|
||||||
#include "util/ChronoUtil.hxx"
|
|
||||||
#include "LocateUri.hxx"
|
#include "LocateUri.hxx"
|
||||||
|
|
||||||
bool
|
bool
|
||||||
|
|||||||
@@ -23,8 +23,8 @@
|
|||||||
#include "StorageCommands.hxx"
|
#include "StorageCommands.hxx"
|
||||||
#include "Request.hxx"
|
#include "Request.hxx"
|
||||||
#include "CommandError.hxx"
|
#include "CommandError.hxx"
|
||||||
|
#include "time/ChronoUtil.hxx"
|
||||||
#include "util/UriUtil.hxx"
|
#include "util/UriUtil.hxx"
|
||||||
#include "util/ChronoUtil.hxx"
|
|
||||||
#include "util/ConstBuffer.hxx"
|
#include "util/ConstBuffer.hxx"
|
||||||
#include "fs/Traits.hxx"
|
#include "fs/Traits.hxx"
|
||||||
#include "client/Client.hxx"
|
#include "client/Client.hxx"
|
||||||
|
|||||||
@@ -153,11 +153,9 @@ ReadConfigParam(ConfigData &config_data, BufferedReader &reader,
|
|||||||
name, reader.GetLineNumber());
|
name, reader.GetLineNumber());
|
||||||
|
|
||||||
if (!option.repeatable)
|
if (!option.repeatable)
|
||||||
if (const auto *param = config_data.GetParam(o))
|
/* if the option is not repeatable, override the old
|
||||||
throw FormatRuntimeError("config parameter \"%s\" is first defined "
|
value by removing it first */
|
||||||
"on line %d and redefined on line %u\n",
|
config_data.GetParamList(o).clear();
|
||||||
name, param->line,
|
|
||||||
reader.GetLineNumber());
|
|
||||||
|
|
||||||
/* now parse the block or the value */
|
/* now parse the block or the value */
|
||||||
|
|
||||||
|
|||||||
@@ -34,7 +34,7 @@
|
|||||||
#include "PlaylistInfo.hxx"
|
#include "PlaylistInfo.hxx"
|
||||||
#include "Interface.hxx"
|
#include "Interface.hxx"
|
||||||
#include "fs/Traits.hxx"
|
#include "fs/Traits.hxx"
|
||||||
#include "util/ChronoUtil.hxx"
|
#include "time/ChronoUtil.hxx"
|
||||||
#include "util/RecursiveMap.hxx"
|
#include "util/RecursiveMap.hxx"
|
||||||
|
|
||||||
#include <functional>
|
#include <functional>
|
||||||
|
|||||||
@@ -448,7 +448,7 @@ ProxyDatabase::ProxyDatabase(EventLoop &_loop, DatabaseListener &_listener,
|
|||||||
listener(_listener),
|
listener(_listener),
|
||||||
host(block.GetBlockValue("host", "")),
|
host(block.GetBlockValue("host", "")),
|
||||||
password(block.GetBlockValue("password", "")),
|
password(block.GetBlockValue("password", "")),
|
||||||
port(block.GetBlockValue("port", 0u)),
|
port(block.GetBlockValue("port", 0U)),
|
||||||
keepalive(block.GetBlockValue("keepalive", false))
|
keepalive(block.GetBlockValue("keepalive", false))
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
@@ -493,9 +493,13 @@ ProxyDatabase::Connect()
|
|||||||
try {
|
try {
|
||||||
CheckError(connection);
|
CheckError(connection);
|
||||||
|
|
||||||
if (mpd_connection_cmp_server_version(connection, 0, 19, 0) < 0)
|
if (mpd_connection_cmp_server_version(connection, 0, 19, 0) < 0) {
|
||||||
throw FormatRuntimeError("Connect to MPD %s, but this plugin requires at least version 0.19",
|
const unsigned *version =
|
||||||
mpd_connection_get_server_version(connection));
|
mpd_connection_get_server_version(connection);
|
||||||
|
throw FormatRuntimeError("Connect to MPD %u.%u.%u, but this "
|
||||||
|
"plugin requires at least version 0.19",
|
||||||
|
version[0], version[1], version[2]);
|
||||||
|
}
|
||||||
|
|
||||||
if (!password.empty() &&
|
if (!password.empty() &&
|
||||||
!mpd_run_password(connection, password.c_str()))
|
!mpd_run_password(connection, password.c_str()))
|
||||||
@@ -517,7 +521,7 @@ ProxyDatabase::Connect()
|
|||||||
(void)keepalive;
|
(void)keepalive;
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
idle_received = ~0u;
|
idle_received = ~0U;
|
||||||
is_idle = false;
|
is_idle = false;
|
||||||
|
|
||||||
SocketMonitor::Open(SocketDescriptor(mpd_async_get_fd(mpd_connection_get_async(connection))));
|
SocketMonitor::Open(SocketDescriptor(mpd_async_get_fd(mpd_connection_get_async(connection))));
|
||||||
|
|||||||
@@ -32,6 +32,7 @@
|
|||||||
#include "fs/Traits.hxx"
|
#include "fs/Traits.hxx"
|
||||||
#include "util/Alloc.hxx"
|
#include "util/Alloc.hxx"
|
||||||
#include "util/DeleteDisposer.hxx"
|
#include "util/DeleteDisposer.hxx"
|
||||||
|
#include "util/StringCompare.hxx"
|
||||||
|
|
||||||
#include <assert.h>
|
#include <assert.h>
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
@@ -69,7 +70,15 @@ Directory::GetName() const noexcept
|
|||||||
{
|
{
|
||||||
assert(!IsRoot());
|
assert(!IsRoot());
|
||||||
|
|
||||||
return PathTraitsUTF8::GetBase(path.c_str());
|
if (parent->IsRoot())
|
||||||
|
return path.c_str();
|
||||||
|
|
||||||
|
assert(StringAfterPrefix(path.c_str(), parent->path.c_str()) != nullptr);
|
||||||
|
assert(*StringAfterPrefix(path.c_str(), parent->path.c_str()) == PathTraitsUTF8::SEPARATOR);
|
||||||
|
|
||||||
|
/* strip the parent directory path and the slash separator
|
||||||
|
from this directory's path, and the base name remains */
|
||||||
|
return path.c_str() + parent->path.length() + 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
Directory *
|
Directory *
|
||||||
|
|||||||
@@ -25,7 +25,7 @@
|
|||||||
#include "PlaylistDatabase.hxx"
|
#include "PlaylistDatabase.hxx"
|
||||||
#include "fs/io/TextFile.hxx"
|
#include "fs/io/TextFile.hxx"
|
||||||
#include "fs/io/BufferedOutputStream.hxx"
|
#include "fs/io/BufferedOutputStream.hxx"
|
||||||
#include "util/ChronoUtil.hxx"
|
#include "time/ChronoUtil.hxx"
|
||||||
#include "util/StringCompare.hxx"
|
#include "util/StringCompare.hxx"
|
||||||
#include "util/NumberParser.hxx"
|
#include "util/NumberParser.hxx"
|
||||||
#include "util/RuntimeError.hxx"
|
#include "util/RuntimeError.hxx"
|
||||||
|
|||||||
@@ -449,12 +449,7 @@ SimpleDatabase::Mount(const char *local_uri, const char *storage_uri)
|
|||||||
|
|
||||||
// TODO: update the new database instance?
|
// TODO: update the new database instance?
|
||||||
|
|
||||||
try {
|
Mount(local_uri, std::move(db));
|
||||||
Mount(local_uri, std::move(db));
|
|
||||||
} catch (...) {
|
|
||||||
db->Close();
|
|
||||||
throw;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
inline DatabasePtr
|
inline DatabasePtr
|
||||||
|
|||||||
@@ -24,6 +24,7 @@
|
|||||||
#include "storage/StorageInterface.hxx"
|
#include "storage/StorageInterface.hxx"
|
||||||
#include "fs/AllocatedPath.hxx"
|
#include "fs/AllocatedPath.hxx"
|
||||||
#include "fs/FileInfo.hxx"
|
#include "fs/FileInfo.hxx"
|
||||||
|
#include "fs/Traits.hxx"
|
||||||
#include "Log.hxx"
|
#include "Log.hxx"
|
||||||
|
|
||||||
#include <string>
|
#include <string>
|
||||||
@@ -146,8 +147,7 @@ WatchDirectory::GetUriFS() const noexcept
|
|||||||
/* we don't look at "." / ".." nor files with newlines in their name */
|
/* we don't look at "." / ".." nor files with newlines in their name */
|
||||||
static bool skip_path(const char *path)
|
static bool skip_path(const char *path)
|
||||||
{
|
{
|
||||||
return (path[0] == '.' && path[1] == 0) ||
|
return PathTraitsFS::IsSpecialFilename(path) ||
|
||||||
(path[0] == '.' && path[1] == '.' && path[2] == 0) ||
|
|
||||||
strchr(path, '\n') != nullptr;
|
strchr(path, '\n') != nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -66,7 +66,7 @@ public:
|
|||||||
|
|
||||||
~UpdateService();
|
~UpdateService();
|
||||||
|
|
||||||
EventLoop &GetEventLoop() noexcept {
|
auto &GetEventLoop() const noexcept {
|
||||||
return defer.GetEventLoop();
|
return defer.GetEventLoop();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -237,7 +237,7 @@ try {
|
|||||||
LogError(std::current_exception());
|
LogError(std::current_exception());
|
||||||
}
|
}
|
||||||
|
|
||||||
/* we don't look at "." / ".." nor files with newlines in their name */
|
/* we don't look at files with newlines in their name */
|
||||||
gcc_pure
|
gcc_pure
|
||||||
static bool
|
static bool
|
||||||
skip_path(const char *name_utf8) noexcept
|
skip_path(const char *name_utf8) noexcept
|
||||||
@@ -493,6 +493,12 @@ UpdateWalk::Walk(Directory &root, const char *path, bool discard) noexcept
|
|||||||
if (!GetInfo(storage, "", info))
|
if (!GetInfo(storage, "", info))
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
|
if (!info.IsDirectory()) {
|
||||||
|
FormatError(update_domain, "Not a directory: %s",
|
||||||
|
storage.MapUTF8("").c_str());
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
ExcludeList exclude_list;
|
ExcludeList exclude_list;
|
||||||
|
|
||||||
UpdateDirectory(root, exclude_list, info);
|
UpdateDirectory(root, exclude_list, info);
|
||||||
|
|||||||
@@ -33,6 +33,8 @@
|
|||||||
#include "util/ConstBuffer.hxx"
|
#include "util/ConstBuffer.hxx"
|
||||||
#include "util/StringBuffer.hxx"
|
#include "util/StringBuffer.hxx"
|
||||||
|
|
||||||
|
#include <stdexcept>
|
||||||
|
|
||||||
#include <assert.h>
|
#include <assert.h>
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
#include <math.h>
|
#include <math.h>
|
||||||
@@ -344,6 +346,10 @@ DecoderBridge::SeekError()
|
|||||||
/* d'oh, we can't seek to the sub-song start position,
|
/* d'oh, we can't seek to the sub-song start position,
|
||||||
what now? - no idea, ignoring the problem for now. */
|
what now? - no idea, ignoring the problem for now. */
|
||||||
initial_seek_running = false;
|
initial_seek_running = false;
|
||||||
|
|
||||||
|
if (initial_seek_essential)
|
||||||
|
error = std::make_exception_ptr(std::runtime_error("Decoder failed to seek"));
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -62,6 +62,11 @@ public:
|
|||||||
*/
|
*/
|
||||||
bool initial_seek_pending;
|
bool initial_seek_pending;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Are initial seek failures fatal?
|
||||||
|
*/
|
||||||
|
const bool initial_seek_essential;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Is the initial seek currently running? During this time,
|
* Is the initial seek currently running? During this time,
|
||||||
* the decoder command is SEEK. This flag is set by
|
* the decoder command is SEEK. This flag is set by
|
||||||
@@ -107,9 +112,11 @@ public:
|
|||||||
std::exception_ptr error;
|
std::exception_ptr error;
|
||||||
|
|
||||||
DecoderBridge(DecoderControl &_dc, bool _initial_seek_pending,
|
DecoderBridge(DecoderControl &_dc, bool _initial_seek_pending,
|
||||||
|
bool _initial_seek_essential,
|
||||||
std::unique_ptr<Tag> _tag)
|
std::unique_ptr<Tag> _tag)
|
||||||
:dc(_dc),
|
:dc(_dc),
|
||||||
initial_seek_pending(_initial_seek_pending),
|
initial_seek_pending(_initial_seek_pending),
|
||||||
|
initial_seek_essential(_initial_seek_essential),
|
||||||
song_tag(std::move(_tag)) {}
|
song_tag(std::move(_tag)) {}
|
||||||
|
|
||||||
~DecoderBridge();
|
~DecoderBridge();
|
||||||
|
|||||||
@@ -90,6 +90,7 @@ DecoderControl::IsCurrentSong(const DetachedSong &_song) const noexcept
|
|||||||
void
|
void
|
||||||
DecoderControl::Start(std::unique_ptr<DetachedSong> _song,
|
DecoderControl::Start(std::unique_ptr<DetachedSong> _song,
|
||||||
SongTime _start_time, SongTime _end_time,
|
SongTime _start_time, SongTime _end_time,
|
||||||
|
bool _initial_seek_essential,
|
||||||
MusicBuffer &_buffer,
|
MusicBuffer &_buffer,
|
||||||
std::shared_ptr<MusicPipe> _pipe) noexcept
|
std::shared_ptr<MusicPipe> _pipe) noexcept
|
||||||
{
|
{
|
||||||
@@ -99,6 +100,7 @@ DecoderControl::Start(std::unique_ptr<DetachedSong> _song,
|
|||||||
song = std::move(_song);
|
song = std::move(_song);
|
||||||
start_time = _start_time;
|
start_time = _start_time;
|
||||||
end_time = _end_time;
|
end_time = _end_time;
|
||||||
|
initial_seek_essential = _initial_seek_essential;
|
||||||
buffer = &_buffer;
|
buffer = &_buffer;
|
||||||
pipe = std::move(_pipe);
|
pipe = std::move(_pipe);
|
||||||
|
|
||||||
|
|||||||
@@ -117,6 +117,12 @@ public:
|
|||||||
|
|
||||||
bool seek_error;
|
bool seek_error;
|
||||||
bool seekable;
|
bool seekable;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @see #DecoderBridge::initial_seek_essential
|
||||||
|
*/
|
||||||
|
bool initial_seek_essential;
|
||||||
|
|
||||||
SongTime seek_time;
|
SongTime seek_time;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
@@ -398,11 +404,14 @@ public:
|
|||||||
* owned and freed by the decoder
|
* owned and freed by the decoder
|
||||||
* @param start_time see #DecoderControl
|
* @param start_time see #DecoderControl
|
||||||
* @param end_time see #DecoderControl
|
* @param end_time see #DecoderControl
|
||||||
|
* @param initial_seek_essential see
|
||||||
|
* #DecoderBridge::initial_seek_essential
|
||||||
* @param pipe the pipe which receives the decoded chunks (owned by
|
* @param pipe the pipe which receives the decoded chunks (owned by
|
||||||
* the caller)
|
* the caller)
|
||||||
*/
|
*/
|
||||||
void Start(std::unique_ptr<DetachedSong> song,
|
void Start(std::unique_ptr<DetachedSong> song,
|
||||||
SongTime start_time, SongTime end_time,
|
SongTime start_time, SongTime end_time,
|
||||||
|
bool initial_seek_essential,
|
||||||
MusicBuffer &buffer,
|
MusicBuffer &buffer,
|
||||||
std::shared_ptr<MusicPipe> pipe) noexcept;
|
std::shared_ptr<MusicPipe> pipe) noexcept;
|
||||||
|
|
||||||
|
|||||||
@@ -22,7 +22,7 @@
|
|||||||
|
|
||||||
#include "util/Compiler.h"
|
#include "util/Compiler.h"
|
||||||
|
|
||||||
#include <forward_list>
|
#include <forward_list> // IWYU pragma: export
|
||||||
|
|
||||||
struct ConfigBlock;
|
struct ConfigBlock;
|
||||||
class InputStream;
|
class InputStream;
|
||||||
|
|||||||
@@ -461,6 +461,7 @@ decoder_run_song(DecoderControl &dc,
|
|||||||
dc.start_time = dc.seek_time;
|
dc.start_time = dc.seek_time;
|
||||||
|
|
||||||
DecoderBridge bridge(dc, dc.start_time.IsPositive(),
|
DecoderBridge bridge(dc, dc.start_time.IsPositive(),
|
||||||
|
dc.initial_seek_essential,
|
||||||
/* pass the song tag only if it's
|
/* pass the song tag only if it's
|
||||||
authoritative, i.e. if it's a local
|
authoritative, i.e. if it's a local
|
||||||
file - tags on "stream" songs are just
|
file - tags on "stream" songs are just
|
||||||
|
|||||||
@@ -41,7 +41,7 @@ adplug_init(const ConfigBlock &block)
|
|||||||
FormatDebug(adplug_domain, "adplug %s",
|
FormatDebug(adplug_domain, "adplug %s",
|
||||||
CAdPlug::get_version().c_str());
|
CAdPlug::get_version().c_str());
|
||||||
|
|
||||||
sample_rate = block.GetPositiveValue("sample_rate", 48000u);
|
sample_rate = block.GetPositiveValue("sample_rate", 48000U);
|
||||||
CheckSampleRate(sample_rate);
|
CheckSampleRate(sample_rate);
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
|
|||||||
@@ -269,6 +269,8 @@ static const char *const audiofile_suffixes[] = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
static const char *const audiofile_mime_types[] = {
|
static const char *const audiofile_mime_types[] = {
|
||||||
|
"audio/wav",
|
||||||
|
"audio/aiff",
|
||||||
"audio/x-wav",
|
"audio/x-wav",
|
||||||
"audio/x-aiff",
|
"audio/x-aiff",
|
||||||
nullptr
|
nullptr
|
||||||
|
|||||||
@@ -362,6 +362,7 @@ dsdiff_decode_chunk(DecoderClient &client, InputStream &is,
|
|||||||
unsigned channels, unsigned sample_rate,
|
unsigned channels, unsigned sample_rate,
|
||||||
const offset_type total_bytes)
|
const offset_type total_bytes)
|
||||||
{
|
{
|
||||||
|
const unsigned kbit_rate = channels * sample_rate / 1000;
|
||||||
const offset_type start_offset = is.GetOffset();
|
const offset_type start_offset = is.GetOffset();
|
||||||
|
|
||||||
uint8_t buffer[8192];
|
uint8_t buffer[8192];
|
||||||
@@ -408,7 +409,7 @@ dsdiff_decode_chunk(DecoderClient &client, InputStream &is,
|
|||||||
bit_reverse_buffer(buffer, buffer + nbytes);
|
bit_reverse_buffer(buffer, buffer + nbytes);
|
||||||
|
|
||||||
cmd = client.SubmitData(is, buffer, nbytes,
|
cmd = client.SubmitData(is, buffer, nbytes,
|
||||||
sample_rate / 1000);
|
kbit_rate);
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
|
|||||||
@@ -256,6 +256,7 @@ dsf_decode_chunk(DecoderClient &client, InputStream &is,
|
|||||||
offset_type n_blocks,
|
offset_type n_blocks,
|
||||||
bool bitreverse)
|
bool bitreverse)
|
||||||
{
|
{
|
||||||
|
const unsigned kbit_rate = channels * sample_rate / 1000;
|
||||||
const size_t block_size = channels * DSF_BLOCK_SIZE;
|
const size_t block_size = channels * DSF_BLOCK_SIZE;
|
||||||
const offset_type start_offset = is.GetOffset();
|
const offset_type start_offset = is.GetOffset();
|
||||||
|
|
||||||
@@ -291,7 +292,7 @@ dsf_decode_chunk(DecoderClient &client, InputStream &is,
|
|||||||
|
|
||||||
cmd = client.SubmitData(is,
|
cmd = client.SubmitData(is,
|
||||||
interleaved_buffer, block_size,
|
interleaved_buffer, block_size,
|
||||||
sample_rate / 1000);
|
kbit_rate);
|
||||||
++i;
|
++i;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -26,11 +26,11 @@
|
|||||||
#include "util/ScopeExit.hxx"
|
#include "util/ScopeExit.hxx"
|
||||||
#include "util/ConstBuffer.hxx"
|
#include "util/ConstBuffer.hxx"
|
||||||
#include "util/Domain.hxx"
|
#include "util/Domain.hxx"
|
||||||
|
#include "util/Math.hxx"
|
||||||
#include "Log.hxx"
|
#include "Log.hxx"
|
||||||
|
|
||||||
#include <neaacdec.h>
|
#include <neaacdec.h>
|
||||||
|
|
||||||
#include <cmath>
|
|
||||||
#include <exception>
|
#include <exception>
|
||||||
|
|
||||||
#include <assert.h>
|
#include <assert.h>
|
||||||
|
|||||||
@@ -297,7 +297,7 @@ FfmpegReceiveFrames(DecoderClient &client, InputStream &is,
|
|||||||
*/
|
*/
|
||||||
static DecoderCommand
|
static DecoderCommand
|
||||||
ffmpeg_send_packet(DecoderClient &client, InputStream &is,
|
ffmpeg_send_packet(DecoderClient &client, InputStream &is,
|
||||||
AVPacket &&packet,
|
const AVPacket &packet,
|
||||||
AVCodecContext &codec_context,
|
AVCodecContext &codec_context,
|
||||||
const AVStream &stream,
|
const AVStream &stream,
|
||||||
AVFrame &frame,
|
AVFrame &frame,
|
||||||
@@ -350,24 +350,6 @@ ffmpeg_send_packet(DecoderClient &client, InputStream &is,
|
|||||||
return cmd;
|
return cmd;
|
||||||
}
|
}
|
||||||
|
|
||||||
static DecoderCommand
|
|
||||||
ffmpeg_send_packet(DecoderClient &client, InputStream &is,
|
|
||||||
const AVPacket &packet,
|
|
||||||
AVCodecContext &codec_context,
|
|
||||||
const AVStream &stream,
|
|
||||||
AVFrame &frame,
|
|
||||||
uint64_t min_frame, size_t pcm_frame_size,
|
|
||||||
FfmpegBuffer &buffer)
|
|
||||||
{
|
|
||||||
return ffmpeg_send_packet(client, is,
|
|
||||||
/* copy the AVPacket, because FFmpeg
|
|
||||||
< 3.0 requires this */
|
|
||||||
AVPacket(packet),
|
|
||||||
codec_context, stream,
|
|
||||||
frame, min_frame, pcm_frame_size,
|
|
||||||
buffer);
|
|
||||||
}
|
|
||||||
|
|
||||||
gcc_const
|
gcc_const
|
||||||
static SampleFormat
|
static SampleFormat
|
||||||
ffmpeg_sample_format(enum AVSampleFormat sample_fmt) noexcept
|
ffmpeg_sample_format(enum AVSampleFormat sample_fmt) noexcept
|
||||||
@@ -762,7 +744,7 @@ static const char *const ffmpeg_mime_types[] = {
|
|||||||
"audio/aac",
|
"audio/aac",
|
||||||
"audio/aacp",
|
"audio/aacp",
|
||||||
"audio/ac3",
|
"audio/ac3",
|
||||||
"audio/aiff"
|
"audio/aiff",
|
||||||
"audio/amr",
|
"audio/amr",
|
||||||
"audio/basic",
|
"audio/basic",
|
||||||
"audio/flac",
|
"audio/flac",
|
||||||
@@ -775,12 +757,13 @@ static const char *const ffmpeg_mime_types[] = {
|
|||||||
"audio/qcelp",
|
"audio/qcelp",
|
||||||
"audio/vorbis",
|
"audio/vorbis",
|
||||||
"audio/vorbis+ogg",
|
"audio/vorbis+ogg",
|
||||||
|
"audio/wav",
|
||||||
"audio/x-8svx",
|
"audio/x-8svx",
|
||||||
"audio/x-16sv",
|
"audio/x-16sv",
|
||||||
"audio/x-aac",
|
"audio/x-aac",
|
||||||
"audio/x-ac3",
|
"audio/x-ac3",
|
||||||
"audio/x-adx",
|
"audio/x-adx",
|
||||||
"audio/x-aiff"
|
"audio/x-aiff",
|
||||||
"audio/x-alaw",
|
"audio/x-alaw",
|
||||||
"audio/x-au",
|
"audio/x-au",
|
||||||
"audio/x-dca",
|
"audio/x-dca",
|
||||||
@@ -800,7 +783,7 @@ static const char *const ffmpeg_mime_types[] = {
|
|||||||
"audio/x-pn-realaudio",
|
"audio/x-pn-realaudio",
|
||||||
"audio/x-pn-multirate-realaudio",
|
"audio/x-pn-multirate-realaudio",
|
||||||
"audio/x-speex",
|
"audio/x-speex",
|
||||||
"audio/x-tta"
|
"audio/x-tta",
|
||||||
"audio/x-voc",
|
"audio/x-voc",
|
||||||
"audio/x-wav",
|
"audio/x-wav",
|
||||||
"audio/x-wma",
|
"audio/x-wma",
|
||||||
|
|||||||
@@ -78,7 +78,7 @@ fluidsynth_mpd_log_function(int level,
|
|||||||
static bool
|
static bool
|
||||||
fluidsynth_init(const ConfigBlock &block)
|
fluidsynth_init(const ConfigBlock &block)
|
||||||
{
|
{
|
||||||
sample_rate = block.GetPositiveValue("sample_rate", 48000u);
|
sample_rate = block.GetPositiveValue("sample_rate", 48000U);
|
||||||
CheckSampleRate(sample_rate);
|
CheckSampleRate(sample_rate);
|
||||||
|
|
||||||
soundfont_path = block.GetBlockValue("soundfont",
|
soundfont_path = block.GetBlockValue("soundfont",
|
||||||
|
|||||||
@@ -27,9 +27,10 @@
|
|||||||
#include "fs/Path.hxx"
|
#include "fs/Path.hxx"
|
||||||
#include "fs/AllocatedPath.hxx"
|
#include "fs/AllocatedPath.hxx"
|
||||||
#include "fs/FileSystem.hxx"
|
#include "fs/FileSystem.hxx"
|
||||||
|
#include "fs/NarrowPath.hxx"
|
||||||
#include "util/ScopeExit.hxx"
|
#include "util/ScopeExit.hxx"
|
||||||
|
#include "util/StringCompare.hxx"
|
||||||
#include "util/StringFormat.hxx"
|
#include "util/StringFormat.hxx"
|
||||||
#include "util/UriUtil.hxx"
|
|
||||||
#include "util/Domain.hxx"
|
#include "util/Domain.hxx"
|
||||||
#include "Log.hxx"
|
#include "Log.hxx"
|
||||||
|
|
||||||
@@ -37,7 +38,6 @@
|
|||||||
|
|
||||||
#include <assert.h>
|
#include <assert.h>
|
||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
#include <string.h>
|
|
||||||
|
|
||||||
#define SUBTUNE_PREFIX "tune_"
|
#define SUBTUNE_PREFIX "tune_"
|
||||||
|
|
||||||
@@ -75,11 +75,10 @@ gcc_pure
|
|||||||
static unsigned
|
static unsigned
|
||||||
ParseSubtuneName(const char *base) noexcept
|
ParseSubtuneName(const char *base) noexcept
|
||||||
{
|
{
|
||||||
if (memcmp(base, SUBTUNE_PREFIX, sizeof(SUBTUNE_PREFIX) - 1) != 0)
|
base = StringAfterPrefix(base, SUBTUNE_PREFIX);
|
||||||
|
if (base == nullptr)
|
||||||
return 0;
|
return 0;
|
||||||
|
|
||||||
base += sizeof(SUBTUNE_PREFIX) - 1;
|
|
||||||
|
|
||||||
char *endptr;
|
char *endptr;
|
||||||
auto track = strtoul(base, &endptr, 10);
|
auto track = strtoul(base, &endptr, 10);
|
||||||
if (endptr == base || *endptr != '.')
|
if (endptr == base || *endptr != '.')
|
||||||
@@ -98,41 +97,46 @@ ParseContainerPath(Path path_fs)
|
|||||||
const Path base = path_fs.GetBase();
|
const Path base = path_fs.GetBase();
|
||||||
unsigned track;
|
unsigned track;
|
||||||
if (base.IsNull() ||
|
if (base.IsNull() ||
|
||||||
(track = ParseSubtuneName(base.c_str())) < 1)
|
(track = ParseSubtuneName(NarrowPath(base))) < 1)
|
||||||
return { AllocatedPath(path_fs), 0 };
|
return { AllocatedPath(path_fs), 0 };
|
||||||
|
|
||||||
return { path_fs.GetDirectoryName(), track - 1 };
|
return { path_fs.GetDirectoryName(), track - 1 };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static AllocatedPath
|
||||||
|
ReplaceSuffix(Path src,
|
||||||
|
const PathTraitsFS::const_pointer_type new_suffix) noexcept
|
||||||
|
{
|
||||||
|
const auto *old_suffix = src.GetSuffix();
|
||||||
|
if (old_suffix == nullptr)
|
||||||
|
return nullptr;
|
||||||
|
|
||||||
|
PathTraitsFS::string s(src.c_str(), old_suffix);
|
||||||
|
s += new_suffix;
|
||||||
|
return AllocatedPath::FromFS(std::move(s));
|
||||||
|
}
|
||||||
|
|
||||||
static Music_Emu*
|
static Music_Emu*
|
||||||
LoadGmeAndM3u(GmeContainerPath container) {
|
LoadGmeAndM3u(GmeContainerPath container) {
|
||||||
|
|
||||||
const char *path = container.path.c_str();
|
|
||||||
const char *suffix = uri_get_suffix(path);
|
|
||||||
|
|
||||||
Music_Emu *emu;
|
Music_Emu *emu;
|
||||||
const char *gme_err =
|
const char *gme_err =
|
||||||
gme_open_file(path, &emu, GME_SAMPLE_RATE);
|
gme_open_file(NarrowPath(container.path), &emu, GME_SAMPLE_RATE);
|
||||||
if (gme_err != nullptr) {
|
if (gme_err != nullptr) {
|
||||||
LogWarning(gme_domain, gme_err);
|
LogWarning(gme_domain, gme_err);
|
||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
if(suffix == nullptr) {
|
const auto m3u_path = ReplaceSuffix(container.path,
|
||||||
return emu;
|
PATH_LITERAL("m3u"));
|
||||||
}
|
|
||||||
|
|
||||||
std::string m3u_path(path,suffix);
|
|
||||||
m3u_path += "m3u";
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Some GME formats lose metadata if you attempt to
|
* Some GME formats lose metadata if you attempt to
|
||||||
* load a non-existant M3U file, so check that one
|
* load a non-existant M3U file, so check that one
|
||||||
* exists before loading.
|
* exists before loading.
|
||||||
*/
|
*/
|
||||||
if(FileExists(Path::FromFS(m3u_path.c_str()))) {
|
if (!m3u_path.IsNull() && FileExists(m3u_path))
|
||||||
gme_load_m3u(emu,m3u_path.c_str());
|
gme_load_m3u(emu, NarrowPath(m3u_path));
|
||||||
}
|
|
||||||
return emu;
|
return emu;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -184,7 +188,11 @@ gme_file_decode(DecoderClient &client, Path path_fs)
|
|||||||
LogWarning(gme_domain, gme_err);
|
LogWarning(gme_domain, gme_err);
|
||||||
|
|
||||||
if (length > 0)
|
if (length > 0)
|
||||||
gme_set_fade(emu, length);
|
gme_set_fade(emu, length
|
||||||
|
#if GME_VERSION >= 0x000700
|
||||||
|
, 8000
|
||||||
|
#endif
|
||||||
|
);
|
||||||
|
|
||||||
/* play */
|
/* play */
|
||||||
DecoderCommand cmd;
|
DecoderCommand cmd;
|
||||||
@@ -222,7 +230,7 @@ ScanGmeInfo(const gme_info_t &info, unsigned song_num, int track_count,
|
|||||||
if (track_count > 1)
|
if (track_count > 1)
|
||||||
handler.OnTag(TAG_TRACK, StringFormat<16>("%u", song_num + 1));
|
handler.OnTag(TAG_TRACK, StringFormat<16>("%u", song_num + 1));
|
||||||
|
|
||||||
if (info.song != nullptr) {
|
if (!StringIsEmpty(info.song)) {
|
||||||
if (track_count > 1) {
|
if (track_count > 1) {
|
||||||
/* start numbering subtunes from 1 */
|
/* start numbering subtunes from 1 */
|
||||||
const auto tag_title =
|
const auto tag_title =
|
||||||
@@ -234,16 +242,16 @@ ScanGmeInfo(const gme_info_t &info, unsigned song_num, int track_count,
|
|||||||
handler.OnTag(TAG_TITLE, info.song);
|
handler.OnTag(TAG_TITLE, info.song);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (info.author != nullptr)
|
if (!StringIsEmpty(info.author))
|
||||||
handler.OnTag(TAG_ARTIST, info.author);
|
handler.OnTag(TAG_ARTIST, info.author);
|
||||||
|
|
||||||
if (info.game != nullptr)
|
if (!StringIsEmpty(info.game))
|
||||||
handler.OnTag(TAG_ALBUM, info.game);
|
handler.OnTag(TAG_ALBUM, info.game);
|
||||||
|
|
||||||
if (info.comment != nullptr)
|
if (!StringIsEmpty(info.comment))
|
||||||
handler.OnTag(TAG_COMMENT, info.comment);
|
handler.OnTag(TAG_COMMENT, info.comment);
|
||||||
|
|
||||||
if (info.copyright != nullptr)
|
if (!StringIsEmpty(info.copyright))
|
||||||
handler.OnTag(TAG_DATE, info.copyright);
|
handler.OnTag(TAG_DATE, info.copyright);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -298,7 +306,7 @@ gme_container_scan(Path path_fs)
|
|||||||
if (num_songs < 2)
|
if (num_songs < 2)
|
||||||
return list;
|
return list;
|
||||||
|
|
||||||
const char *subtune_suffix = uri_get_suffix(path_fs.c_str());
|
const auto *subtune_suffix = path_fs.GetSuffix();
|
||||||
|
|
||||||
TagBuilder tag_builder;
|
TagBuilder tag_builder;
|
||||||
|
|
||||||
|
|||||||
@@ -186,7 +186,7 @@ HybridDsdDecode(DecoderClient &client, InputStream &input)
|
|||||||
client.Ready(result.first, true, duration);
|
client.Ready(result.first, true, duration);
|
||||||
frame_size = result.first.GetFrameSize();
|
frame_size = result.first.GetFrameSize();
|
||||||
kbit_rate = frame_size * result.first.sample_rate /
|
kbit_rate = frame_size * result.first.sample_rate /
|
||||||
(1024u / 8u);
|
(1024U / 8U);
|
||||||
total_frames = result.second / frame_size;
|
total_frames = result.second / frame_size;
|
||||||
} catch (UnsupportedFile) {
|
} catch (UnsupportedFile) {
|
||||||
/* not a Hybrid-DSD file; let the next decoder plugin
|
/* not a Hybrid-DSD file; let the next decoder plugin
|
||||||
@@ -236,7 +236,7 @@ HybridDsdDecode(DecoderClient &client, InputStream &input)
|
|||||||
/* fill the buffer */
|
/* fill the buffer */
|
||||||
auto w = buffer.Write();
|
auto w = buffer.Write();
|
||||||
if (!w.empty()) {
|
if (!w.empty()) {
|
||||||
if (remaining_bytes < (1<<30ull) &&
|
if (remaining_bytes < (1<<30ULL) &&
|
||||||
w.size > size_t(remaining_bytes))
|
w.size > size_t(remaining_bytes))
|
||||||
w.size = remaining_bytes;
|
w.size = remaining_bytes;
|
||||||
|
|
||||||
|
|||||||
@@ -719,6 +719,11 @@ MadDecoder::DecodeFirstFrame(Tag *tag) noexcept
|
|||||||
{
|
{
|
||||||
struct xing xing;
|
struct xing xing;
|
||||||
|
|
||||||
|
#if GCC_CHECK_VERSION(10,0)
|
||||||
|
/* work around bogus -Wuninitialized in GCC 10 */
|
||||||
|
xing.frames = 0;
|
||||||
|
#endif
|
||||||
|
|
||||||
while (true) {
|
while (true) {
|
||||||
MadDecoderAction ret;
|
MadDecoderAction ret;
|
||||||
do {
|
do {
|
||||||
@@ -788,7 +793,7 @@ MadDecoder::DecodeFirstFrame(Tag *tag) noexcept
|
|||||||
|
|
||||||
if (max_frames > 8 * 1024 * 1024) {
|
if (max_frames > 8 * 1024 * 1024) {
|
||||||
FormatWarning(mad_domain,
|
FormatWarning(mad_domain,
|
||||||
"mp3 file header indicates too many frames: %lu",
|
"mp3 file header indicates too many frames: %zu",
|
||||||
max_frames);
|
max_frames);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -107,7 +107,7 @@ mikmod_decoder_init(const ConfigBlock &block)
|
|||||||
static char params[] = "";
|
static char params[] = "";
|
||||||
|
|
||||||
mikmod_loop = block.GetBlockValue("loop", false);
|
mikmod_loop = block.GetBlockValue("loop", false);
|
||||||
mikmod_sample_rate = block.GetPositiveValue("sample_rate", 44100u);
|
mikmod_sample_rate = block.GetPositiveValue("sample_rate", 44100U);
|
||||||
if (!audio_valid_sample_rate(mikmod_sample_rate))
|
if (!audio_valid_sample_rate(mikmod_sample_rate))
|
||||||
throw FormatRuntimeError("Invalid sample rate in line %d: %u",
|
throw FormatRuntimeError("Invalid sample rate in line %d: %u",
|
||||||
block.line, mikmod_sample_rate);
|
block.line, mikmod_sample_rate);
|
||||||
|
|||||||
@@ -26,8 +26,13 @@
|
|||||||
#include "util/RuntimeError.hxx"
|
#include "util/RuntimeError.hxx"
|
||||||
#include "Log.hxx"
|
#include "Log.hxx"
|
||||||
|
|
||||||
#include <libmodplug/modplug.h>
|
#ifdef _WIN32
|
||||||
|
/* assume ModPlug is built as static library on Windows; without
|
||||||
|
this, linking to the static library would fail */
|
||||||
|
#define MODPLUG_STATIC
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#include <libmodplug/modplug.h>
|
||||||
|
|
||||||
#include <assert.h>
|
#include <assert.h>
|
||||||
|
|
||||||
|
|||||||
@@ -137,6 +137,28 @@ mpc_to_mpd_buffer(MpcdecSampleTraits::pointer_type dest,
|
|||||||
*dest++ = mpc_to_mpd_sample(*src++);
|
*dest++ = mpc_to_mpd_sample(*src++);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static constexpr ReplayGainTuple
|
||||||
|
ImportMpcdecReplayGain(mpc_uint16_t gain, mpc_uint16_t peak) noexcept
|
||||||
|
{
|
||||||
|
auto t = ReplayGainTuple::Undefined();
|
||||||
|
|
||||||
|
if (gain != 0 && peak != 0) {
|
||||||
|
t.gain = MPC_OLD_GAIN_REF - (gain / 256.);
|
||||||
|
t.peak = pow(10, peak / 256. / 20) / 32767;
|
||||||
|
}
|
||||||
|
|
||||||
|
return t;
|
||||||
|
}
|
||||||
|
|
||||||
|
static constexpr ReplayGainInfo
|
||||||
|
ImportMpcdecReplayGain(const mpc_streaminfo &info) noexcept
|
||||||
|
{
|
||||||
|
auto rgi = ReplayGainInfo::Undefined();
|
||||||
|
rgi.album = ImportMpcdecReplayGain(info.gain_album, info.peak_album);
|
||||||
|
rgi.track = ImportMpcdecReplayGain(info.gain_title, info.peak_title);
|
||||||
|
return rgi;
|
||||||
|
}
|
||||||
|
|
||||||
static void
|
static void
|
||||||
mpcdec_decode(DecoderClient &client, InputStream &is)
|
mpcdec_decode(DecoderClient &client, InputStream &is)
|
||||||
{
|
{
|
||||||
@@ -167,14 +189,11 @@ mpcdec_decode(DecoderClient &client, InputStream &is)
|
|||||||
mpcdec_sample_format,
|
mpcdec_sample_format,
|
||||||
info.channels);
|
info.channels);
|
||||||
|
|
||||||
ReplayGainInfo rgi;
|
{
|
||||||
rgi.Clear();
|
const auto rgi = ImportMpcdecReplayGain(info);
|
||||||
rgi.album.gain = MPC_OLD_GAIN_REF - (info.gain_album / 256.);
|
if (rgi.IsDefined())
|
||||||
rgi.album.peak = pow(10, info.peak_album / 256. / 20) / 32767;
|
client.SubmitReplayGain(&rgi);
|
||||||
rgi.track.gain = MPC_OLD_GAIN_REF - (info.gain_title / 256.);
|
}
|
||||||
rgi.track.peak = pow(10, info.peak_title / 256. / 20) / 32767;
|
|
||||||
|
|
||||||
client.SubmitReplayGain(&rgi);
|
|
||||||
|
|
||||||
client.Ready(audio_format, is.IsSeekable(),
|
client.Ready(audio_format, is.IsSeekable(),
|
||||||
SongTime::FromS(mpc_streaminfo_get_length(&info)));
|
SongTime::FromS(mpc_streaminfo_get_length(&info)));
|
||||||
|
|||||||
@@ -47,8 +47,12 @@ OggDecoder::LoadEndPacket(ogg_packet &packet) const
|
|||||||
DecoderReader reader(client, input_stream);
|
DecoderReader reader(client, input_stream);
|
||||||
OggSyncState sync2(reader);
|
OggSyncState sync2(reader);
|
||||||
OggStreamState stream2(GetSerialNo());
|
OggStreamState stream2(GetSerialNo());
|
||||||
|
|
||||||
|
/* passing synced=false because we're inside an
|
||||||
|
OggVisitor callback, and our InputStream may be in
|
||||||
|
the middle of an Ogg packet */
|
||||||
result = OggSeekFindEOS(sync2, stream2, packet,
|
result = OggSeekFindEOS(sync2, stream2, packet,
|
||||||
input_stream);
|
input_stream, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* restore the previous file position */
|
/* restore the previous file position */
|
||||||
|
|||||||
@@ -115,7 +115,7 @@ sidplay_init(const ConfigBlock &block)
|
|||||||
if (!database_path.IsNull())
|
if (!database_path.IsNull())
|
||||||
songlength_database = sidplay_load_songlength_db(database_path);
|
songlength_database = sidplay_load_songlength_db(database_path);
|
||||||
|
|
||||||
default_songlength = block.GetPositiveValue("default_songlength", 0u);
|
default_songlength = block.GetPositiveValue("default_songlength", 0U);
|
||||||
|
|
||||||
all_files_are_containers =
|
all_files_are_containers =
|
||||||
block.GetBlockValue("all_files_are_containers", true);
|
block.GetBlockValue("all_files_are_containers", true);
|
||||||
@@ -387,7 +387,7 @@ sidplay_file_decode(DecoderClient &client, Path path_fs)
|
|||||||
const unsigned timebase = player.timebase();
|
const unsigned timebase = player.timebase();
|
||||||
#endif
|
#endif
|
||||||
const unsigned end = duration.IsNegative()
|
const unsigned end = duration.IsNegative()
|
||||||
? 0u
|
? 0U
|
||||||
: duration.ToScale<uint64_t>(timebase);
|
: duration.ToScale<uint64_t>(timebase);
|
||||||
|
|
||||||
DecoderCommand cmd;
|
DecoderCommand cmd;
|
||||||
|
|||||||
@@ -322,6 +322,8 @@ static const char *const sndfile_suffixes[] = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
static const char *const sndfile_mime_types[] = {
|
static const char *const sndfile_mime_types[] = {
|
||||||
|
"audio/wav",
|
||||||
|
"audio/aiff",
|
||||||
"audio/x-wav",
|
"audio/x-wav",
|
||||||
"audio/x-aiff",
|
"audio/x-aiff",
|
||||||
|
|
||||||
|
|||||||
@@ -25,9 +25,16 @@
|
|||||||
#include "fs/AllocatedPath.hxx"
|
#include "fs/AllocatedPath.hxx"
|
||||||
#include "fs/FileSystem.hxx"
|
#include "fs/FileSystem.hxx"
|
||||||
#include "fs/Path.hxx"
|
#include "fs/Path.hxx"
|
||||||
|
#include "fs/NarrowPath.hxx"
|
||||||
#include "Log.hxx"
|
#include "Log.hxx"
|
||||||
#include "PluginUnavailable.hxx"
|
#include "PluginUnavailable.hxx"
|
||||||
|
|
||||||
|
#ifdef _WIN32
|
||||||
|
/* assume WildMidi is built as static library on Windows; without
|
||||||
|
this, linking to the static library would fail */
|
||||||
|
#define WILDMIDI_STATIC
|
||||||
|
#endif
|
||||||
|
|
||||||
extern "C" {
|
extern "C" {
|
||||||
#include <wildmidi_lib.h>
|
#include <wildmidi_lib.h>
|
||||||
}
|
}
|
||||||
@@ -53,7 +60,8 @@ wildmidi_init(const ConfigBlock &block)
|
|||||||
AtScopeExit() { WildMidi_ClearError(); };
|
AtScopeExit() { WildMidi_ClearError(); };
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
if (WildMidi_Init(path.c_str(), wildmidi_audio_format.sample_rate,
|
if (WildMidi_Init(NarrowPath(path),
|
||||||
|
wildmidi_audio_format.sample_rate,
|
||||||
0) != 0) {
|
0) != 0) {
|
||||||
#ifdef LIBWILDMIDI_VERSION
|
#ifdef LIBWILDMIDI_VERSION
|
||||||
/* WildMidi_GetError() requires libwildmidi 0.4 */
|
/* WildMidi_GetError() requires libwildmidi 0.4 */
|
||||||
@@ -96,7 +104,7 @@ wildmidi_file_decode(DecoderClient &client, Path path_fs)
|
|||||||
midi *wm;
|
midi *wm;
|
||||||
const struct _WM_Info *info;
|
const struct _WM_Info *info;
|
||||||
|
|
||||||
wm = WildMidi_Open(path_fs.c_str());
|
wm = WildMidi_Open(NarrowPath(path_fs));
|
||||||
if (wm == nullptr)
|
if (wm == nullptr)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
@@ -136,7 +144,7 @@ wildmidi_file_decode(DecoderClient &client, Path path_fs)
|
|||||||
static bool
|
static bool
|
||||||
wildmidi_scan_file(Path path_fs, TagHandler &handler) noexcept
|
wildmidi_scan_file(Path path_fs, TagHandler &handler) noexcept
|
||||||
{
|
{
|
||||||
midi *wm = WildMidi_Open(path_fs.c_str());
|
midi *wm = WildMidi_Open(NarrowPath(path_fs));
|
||||||
if (wm == nullptr)
|
if (wm == nullptr)
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
|
|||||||
@@ -129,7 +129,16 @@ if wavpack_dep.found()
|
|||||||
decoder_plugins_sources += 'WavpackDecoderPlugin.cxx'
|
decoder_plugins_sources += 'WavpackDecoderPlugin.cxx'
|
||||||
endif
|
endif
|
||||||
|
|
||||||
wildmidi_dep = c_compiler.find_library('WildMidi', required: get_option('wildmidi'))
|
wildmidi_required = get_option('wildmidi')
|
||||||
|
if wildmidi_required.enabled()
|
||||||
|
# if the user has force-enabled WildMidi, allow the pkg-config test
|
||||||
|
# to fail; after that, the find_library() check must succeed
|
||||||
|
wildmidi_required = false
|
||||||
|
endif
|
||||||
|
wildmidi_dep = dependency('wildmidi', required: wildmidi_required)
|
||||||
|
if not wildmidi_dep.found()
|
||||||
|
wildmidi_dep = c_compiler.find_library('WildMidi', required: get_option('wildmidi'))
|
||||||
|
endif
|
||||||
conf.set('ENABLE_WILDMIDI', wildmidi_dep.found())
|
conf.set('ENABLE_WILDMIDI', wildmidi_dep.found())
|
||||||
if wildmidi_dep.found()
|
if wildmidi_dep.found()
|
||||||
decoder_plugins_sources += 'WildmidiDecoderPlugin.cxx'
|
decoder_plugins_sources += 'WildmidiDecoderPlugin.cxx'
|
||||||
|
|||||||
@@ -94,7 +94,7 @@ public:
|
|||||||
};
|
};
|
||||||
|
|
||||||
PreparedFlacEncoder::PreparedFlacEncoder(const ConfigBlock &block)
|
PreparedFlacEncoder::PreparedFlacEncoder(const ConfigBlock &block)
|
||||||
:compression(block.GetBlockValue("compression", 5u))
|
:compression(block.GetBlockValue("compression", 5U))
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -107,7 +107,7 @@ PreparedOpusEncoder::PreparedOpusEncoder(const ConfigBlock &block)
|
|||||||
throw std::runtime_error("Invalid bit rate");
|
throw std::runtime_error("Invalid bit rate");
|
||||||
}
|
}
|
||||||
|
|
||||||
complexity = block.GetBlockValue("complexity", 10u);
|
complexity = block.GetBlockValue("complexity", 10U);
|
||||||
if (complexity > 10)
|
if (complexity > 10)
|
||||||
throw std::runtime_error("Invalid complexity");
|
throw std::runtime_error("Invalid complexity");
|
||||||
|
|
||||||
|
|||||||
@@ -50,7 +50,7 @@ public:
|
|||||||
Cancel();
|
Cancel();
|
||||||
}
|
}
|
||||||
|
|
||||||
EventLoop &GetEventLoop() noexcept {
|
EventLoop &GetEventLoop() const noexcept {
|
||||||
return loop;
|
return loop;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -137,7 +137,8 @@ static constexpr int
|
|||||||
ExportTimeoutMS(std::chrono::steady_clock::duration timeout)
|
ExportTimeoutMS(std::chrono::steady_clock::duration timeout)
|
||||||
{
|
{
|
||||||
return timeout >= timeout.zero()
|
return timeout >= timeout.zero()
|
||||||
? int(std::chrono::duration_cast<std::chrono::milliseconds>(timeout).count())
|
/* round up (+1) to avoid unnecessary wakeups */
|
||||||
|
? int(std::chrono::duration_cast<std::chrono::milliseconds>(timeout).count()) + 1
|
||||||
: -1;
|
: -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -220,7 +221,6 @@ EventLoop::Run() noexcept
|
|||||||
} while (!quit);
|
} while (!quit);
|
||||||
|
|
||||||
#ifndef NDEBUG
|
#ifndef NDEBUG
|
||||||
assert(busy);
|
|
||||||
assert(thread.IsInside());
|
assert(thread.IsInside());
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -44,7 +44,7 @@ public:
|
|||||||
:defer(_loop, BIND_THIS_METHOD(RunDeferred)),
|
:defer(_loop, BIND_THIS_METHOD(RunDeferred)),
|
||||||
callback(_callback), pending_mask(0) {}
|
callback(_callback), pending_mask(0) {}
|
||||||
|
|
||||||
EventLoop &GetEventLoop() {
|
auto &GetEventLoop() const noexcept {
|
||||||
return defer.GetEventLoop();
|
return defer.GetEventLoop();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -22,6 +22,10 @@
|
|||||||
|
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
|
|
||||||
|
#ifdef USE_EPOLL
|
||||||
|
#include <errno.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
#ifndef _WIN32
|
#ifndef _WIN32
|
||||||
#include <poll.h>
|
#include <poll.h>
|
||||||
#endif
|
#endif
|
||||||
@@ -37,17 +41,42 @@ MultiSocketMonitor::Reset() noexcept
|
|||||||
assert(GetEventLoop().IsInside());
|
assert(GetEventLoop().IsInside());
|
||||||
|
|
||||||
fds.clear();
|
fds.clear();
|
||||||
|
#ifdef USE_EPOLL
|
||||||
|
always_ready_fds.clear();
|
||||||
|
#endif
|
||||||
IdleMonitor::Cancel();
|
IdleMonitor::Cancel();
|
||||||
timeout_event.Cancel();
|
timeout_event.Cancel();
|
||||||
ready = refresh = false;
|
ready = refresh = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool
|
||||||
|
MultiSocketMonitor::AddSocket(SocketDescriptor fd, unsigned events) noexcept
|
||||||
|
{
|
||||||
|
fds.emplace_front(*this, fd);
|
||||||
|
bool success = fds.front().Schedule(events);
|
||||||
|
if (!success) {
|
||||||
|
fds.pop_front();
|
||||||
|
|
||||||
|
#ifdef USE_EPOLL
|
||||||
|
if (errno == EPERM)
|
||||||
|
/* not supported by epoll (e.g. "/dev/null"):
|
||||||
|
add it to the "always ready" list */
|
||||||
|
always_ready_fds.push_front({fd, events});
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
return success;
|
||||||
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
MultiSocketMonitor::ClearSocketList() noexcept
|
MultiSocketMonitor::ClearSocketList() noexcept
|
||||||
{
|
{
|
||||||
assert(GetEventLoop().IsInside());
|
assert(GetEventLoop().IsInside());
|
||||||
|
|
||||||
fds.clear();
|
fds.clear();
|
||||||
|
#ifdef USE_EPOLL
|
||||||
|
always_ready_fds.clear();
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
#ifndef _WIN32
|
#ifndef _WIN32
|
||||||
@@ -55,6 +84,10 @@ MultiSocketMonitor::ClearSocketList() noexcept
|
|||||||
void
|
void
|
||||||
MultiSocketMonitor::ReplaceSocketList(pollfd *pfds, unsigned n) noexcept
|
MultiSocketMonitor::ReplaceSocketList(pollfd *pfds, unsigned n) noexcept
|
||||||
{
|
{
|
||||||
|
#ifdef USE_EPOLL
|
||||||
|
always_ready_fds.clear();
|
||||||
|
#endif
|
||||||
|
|
||||||
pollfd *const end = pfds + n;
|
pollfd *const end = pfds + n;
|
||||||
|
|
||||||
UpdateSocketList([pfds, end](SocketDescriptor fd) -> unsigned {
|
UpdateSocketList([pfds, end](SocketDescriptor fd) -> unsigned {
|
||||||
@@ -64,9 +97,7 @@ MultiSocketMonitor::ReplaceSocketList(pollfd *pfds, unsigned n) noexcept
|
|||||||
if (i == end)
|
if (i == end)
|
||||||
return 0;
|
return 0;
|
||||||
|
|
||||||
auto events = i->events;
|
return std::exchange(i->events, 0);
|
||||||
i->events = 0;
|
|
||||||
return events;
|
|
||||||
});
|
});
|
||||||
|
|
||||||
for (auto i = pfds; i != end; ++i)
|
for (auto i = pfds; i != end; ++i)
|
||||||
@@ -79,7 +110,20 @@ MultiSocketMonitor::ReplaceSocketList(pollfd *pfds, unsigned n) noexcept
|
|||||||
void
|
void
|
||||||
MultiSocketMonitor::Prepare() noexcept
|
MultiSocketMonitor::Prepare() noexcept
|
||||||
{
|
{
|
||||||
const auto timeout = PrepareSockets();
|
auto timeout = PrepareSockets();
|
||||||
|
|
||||||
|
#ifdef USE_EPOLL
|
||||||
|
if (!always_ready_fds.empty()) {
|
||||||
|
/* if there was at least one file descriptor not
|
||||||
|
supported by epoll, install a very short timeout
|
||||||
|
because we assume it's always ready */
|
||||||
|
constexpr std::chrono::steady_clock::duration ready_timeout =
|
||||||
|
std::chrono::milliseconds(1);
|
||||||
|
if (timeout < timeout.zero() || timeout > ready_timeout)
|
||||||
|
timeout = ready_timeout;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
if (timeout >= timeout.zero())
|
if (timeout >= timeout.zero())
|
||||||
timeout_event.Schedule(timeout);
|
timeout_event.Schedule(timeout);
|
||||||
else
|
else
|
||||||
|
|||||||
@@ -50,12 +50,10 @@ class MultiSocketMonitor : IdleMonitor
|
|||||||
unsigned revents;
|
unsigned revents;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
SingleFD(MultiSocketMonitor &_multi, SocketDescriptor _fd,
|
SingleFD(MultiSocketMonitor &_multi,
|
||||||
unsigned events) noexcept
|
SocketDescriptor _fd) noexcept
|
||||||
:SocketMonitor(_fd, _multi.GetEventLoop()),
|
:SocketMonitor(_fd, _multi.GetEventLoop()),
|
||||||
multi(_multi), revents(0) {
|
multi(_multi), revents(0) {}
|
||||||
Schedule(events);
|
|
||||||
}
|
|
||||||
|
|
||||||
SocketDescriptor GetSocket() const noexcept {
|
SocketDescriptor GetSocket() const noexcept {
|
||||||
return SocketMonitor::GetSocket();
|
return SocketMonitor::GetSocket();
|
||||||
@@ -86,8 +84,6 @@ class MultiSocketMonitor : IdleMonitor
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
friend class SingleFD;
|
|
||||||
|
|
||||||
TimerEvent timeout_event;
|
TimerEvent timeout_event;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -106,6 +102,21 @@ class MultiSocketMonitor : IdleMonitor
|
|||||||
|
|
||||||
std::forward_list<SingleFD> fds;
|
std::forward_list<SingleFD> fds;
|
||||||
|
|
||||||
|
#ifdef USE_EPOLL
|
||||||
|
struct AlwaysReady {
|
||||||
|
const SocketDescriptor fd;
|
||||||
|
const unsigned revents;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A list of file descriptors which are always ready. This is
|
||||||
|
* a kludge needed because the ALSA output plugin gives us a
|
||||||
|
* file descriptor to /dev/null, which is incompatible with
|
||||||
|
* epoll (epoll_ctl() returns -EPERM).
|
||||||
|
*/
|
||||||
|
std::forward_list<AlwaysReady> always_ready_fds;
|
||||||
|
#endif
|
||||||
|
|
||||||
public:
|
public:
|
||||||
static constexpr unsigned READ = SocketMonitor::READ;
|
static constexpr unsigned READ = SocketMonitor::READ;
|
||||||
static constexpr unsigned WRITE = SocketMonitor::WRITE;
|
static constexpr unsigned WRITE = SocketMonitor::WRITE;
|
||||||
@@ -147,9 +158,7 @@ public:
|
|||||||
*
|
*
|
||||||
* May only be called from PrepareSockets().
|
* May only be called from PrepareSockets().
|
||||||
*/
|
*/
|
||||||
void AddSocket(SocketDescriptor fd, unsigned events) noexcept {
|
bool AddSocket(SocketDescriptor fd, unsigned events) noexcept;
|
||||||
fds.emplace_front(*this, fd, events);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Remove all sockets.
|
* Remove all sockets.
|
||||||
@@ -204,6 +213,11 @@ public:
|
|||||||
i.ClearReturnedEvents();
|
i.ClearReturnedEvents();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#ifdef USE_EPOLL
|
||||||
|
for (const auto &i : always_ready_fds)
|
||||||
|
f(i.fd, i.revents);
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
@@ -232,7 +246,6 @@ private:
|
|||||||
|
|
||||||
void OnTimeout() noexcept {
|
void OnTimeout() noexcept {
|
||||||
SetReady();
|
SetReady();
|
||||||
IdleMonitor::Schedule();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
virtual void OnIdle() noexcept final;
|
virtual void OnIdle() noexcept final;
|
||||||
|
|||||||
@@ -23,8 +23,8 @@
|
|||||||
|
|
||||||
#include "PollGroupWinSelect.hxx"
|
#include "PollGroupWinSelect.hxx"
|
||||||
|
|
||||||
constexpr int EVENT_READ = 0;
|
static constexpr int EVENT_READ = 0;
|
||||||
constexpr int EVENT_WRITE = 1;
|
static constexpr int EVENT_WRITE = 1;
|
||||||
|
|
||||||
static constexpr
|
static constexpr
|
||||||
bool HasEvent(unsigned events, int event_id) noexcept
|
bool HasEvent(unsigned events, int event_id) noexcept
|
||||||
|
|||||||
@@ -20,6 +20,10 @@
|
|||||||
#include "SocketMonitor.hxx"
|
#include "SocketMonitor.hxx"
|
||||||
#include "Loop.hxx"
|
#include "Loop.hxx"
|
||||||
|
|
||||||
|
#ifdef USE_EPOLL
|
||||||
|
#include <cerrno>
|
||||||
|
#endif
|
||||||
|
|
||||||
#include <assert.h>
|
#include <assert.h>
|
||||||
|
|
||||||
#ifdef _WIN32
|
#ifdef _WIN32
|
||||||
@@ -68,20 +72,39 @@ SocketMonitor::Close() noexcept
|
|||||||
Steal().Close();
|
Steal().Close();
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
bool
|
||||||
SocketMonitor::Schedule(unsigned flags) noexcept
|
SocketMonitor::Schedule(unsigned flags) noexcept
|
||||||
{
|
{
|
||||||
assert(IsDefined());
|
assert(IsDefined());
|
||||||
|
|
||||||
if (flags == GetScheduledFlags())
|
if (flags == GetScheduledFlags())
|
||||||
return;
|
return true;
|
||||||
|
|
||||||
|
bool success;
|
||||||
if (scheduled_flags == 0)
|
if (scheduled_flags == 0)
|
||||||
loop.AddFD(fd.Get(), flags, *this);
|
success = loop.AddFD(fd.Get(), flags, *this);
|
||||||
else if (flags == 0)
|
else if (flags == 0)
|
||||||
loop.RemoveFD(fd.Get(), *this);
|
success = loop.RemoveFD(fd.Get(), *this);
|
||||||
else
|
else
|
||||||
loop.ModifyFD(fd.Get(), flags, *this);
|
success = loop.ModifyFD(fd.Get(), flags, *this);
|
||||||
|
|
||||||
scheduled_flags = flags;
|
if (success)
|
||||||
|
scheduled_flags = flags;
|
||||||
|
#ifdef USE_EPOLL
|
||||||
|
else if (errno == EBADF || errno == ENOENT)
|
||||||
|
/* the socket was probably closed by somebody else
|
||||||
|
(EBADF) or a new file descriptor with the same
|
||||||
|
number was created but not registered already
|
||||||
|
(ENOENT) - we can assume that there are no
|
||||||
|
scheduled events */
|
||||||
|
/* note that when this happens, we're actually lucky
|
||||||
|
that it has failed - imagine another thread may
|
||||||
|
meanwhile have created something on the same file
|
||||||
|
descriptor number, and has registered it; the
|
||||||
|
epoll_ctl() call above would then have succeeded,
|
||||||
|
but broke the other thread's epoll registration */
|
||||||
|
scheduled_flags = 0;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
return success;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -68,7 +68,7 @@ public:
|
|||||||
|
|
||||||
~SocketMonitor() noexcept;
|
~SocketMonitor() noexcept;
|
||||||
|
|
||||||
EventLoop &GetEventLoop() noexcept {
|
auto &GetEventLoop() const noexcept {
|
||||||
return loop;
|
return loop;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -98,22 +98,26 @@ public:
|
|||||||
return scheduled_flags;
|
return scheduled_flags;
|
||||||
}
|
}
|
||||||
|
|
||||||
void Schedule(unsigned flags) noexcept;
|
/**
|
||||||
|
* @return true on success, false on error (with errno set if
|
||||||
|
* USE_EPOLL is defined)
|
||||||
|
*/
|
||||||
|
bool Schedule(unsigned flags) noexcept;
|
||||||
|
|
||||||
void Cancel() noexcept {
|
void Cancel() noexcept {
|
||||||
Schedule(0);
|
Schedule(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
void ScheduleRead() noexcept {
|
bool ScheduleRead() noexcept {
|
||||||
Schedule(GetScheduledFlags() | READ | HANGUP | ERROR);
|
return Schedule(GetScheduledFlags() | READ);
|
||||||
}
|
}
|
||||||
|
|
||||||
void ScheduleWrite() noexcept {
|
bool ScheduleWrite() noexcept {
|
||||||
Schedule(GetScheduledFlags() | WRITE);
|
return Schedule(GetScheduledFlags() | WRITE);
|
||||||
}
|
}
|
||||||
|
|
||||||
void CancelRead() noexcept {
|
void CancelRead() noexcept {
|
||||||
Schedule(GetScheduledFlags() & ~(READ|HANGUP|ERROR));
|
Schedule(GetScheduledFlags() & ~READ);
|
||||||
}
|
}
|
||||||
|
|
||||||
void CancelWrite() noexcept {
|
void CancelWrite() noexcept {
|
||||||
|
|||||||
@@ -62,7 +62,7 @@ public:
|
|||||||
Cancel();
|
Cancel();
|
||||||
}
|
}
|
||||||
|
|
||||||
EventLoop &GetEventLoop() noexcept {
|
auto &GetEventLoop() const noexcept {
|
||||||
return loop;
|
return loop;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -25,6 +25,7 @@ event_dep = declare_dependency(
|
|||||||
link_with: event,
|
link_with: event,
|
||||||
dependencies: [
|
dependencies: [
|
||||||
thread_dep,
|
thread_dep,
|
||||||
|
net_dep,
|
||||||
system_dep,
|
system_dep,
|
||||||
boost_dep,
|
boost_dep,
|
||||||
],
|
],
|
||||||
|
|||||||
@@ -285,6 +285,11 @@ public:
|
|||||||
bool IsAbsolute() const noexcept {
|
bool IsAbsolute() const noexcept {
|
||||||
return Traits::IsAbsolute(c_str());
|
return Traits::IsAbsolute(c_str());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
gcc_pure
|
||||||
|
const_pointer_type GetSuffix() const noexcept {
|
||||||
|
return ((Path)*this).GetSuffix();
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
55
src/fs/NarrowPath.cxx
Normal file
55
src/fs/NarrowPath.cxx
Normal file
@@ -0,0 +1,55 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2003-2018 The Music Player Daemon Project
|
||||||
|
* http://www.musicpd.org
|
||||||
|
*
|
||||||
|
* This program is free software; you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation; either version 2 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License along
|
||||||
|
* with this program; if not, write to the Free Software Foundation, Inc.,
|
||||||
|
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "NarrowPath.hxx"
|
||||||
|
|
||||||
|
#ifdef _UNICODE
|
||||||
|
|
||||||
|
#include "lib/icu/Win32.hxx"
|
||||||
|
#include "system/Error.hxx"
|
||||||
|
#include "util/Macros.hxx"
|
||||||
|
|
||||||
|
#include <windows.h>
|
||||||
|
|
||||||
|
NarrowPath::NarrowPath(Path _path) noexcept
|
||||||
|
:value(WideCharToMultiByte(CP_ACP, _path.c_str()))
|
||||||
|
{
|
||||||
|
if (value.IsNull())
|
||||||
|
/* fall back to empty string */
|
||||||
|
value = Value::Empty();
|
||||||
|
}
|
||||||
|
|
||||||
|
static AllocatedPath
|
||||||
|
AcpToAllocatedPath(const char *s)
|
||||||
|
{
|
||||||
|
wchar_t buffer[MAX_PATH];
|
||||||
|
auto result = MultiByteToWideChar(CP_ACP, 0, s, -1,
|
||||||
|
buffer, ARRAY_SIZE(buffer));
|
||||||
|
if (result <= 0)
|
||||||
|
throw MakeLastError("MultiByteToWideChar() failed");
|
||||||
|
|
||||||
|
return AllocatedPath::FromFS(buffer);
|
||||||
|
}
|
||||||
|
|
||||||
|
FromNarrowPath::FromNarrowPath(const char *s)
|
||||||
|
:value(AcpToAllocatedPath(s))
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif /* _UNICODE */
|
||||||
@@ -21,12 +21,10 @@
|
|||||||
#define MPD_FS_NARROW_PATH_HXX
|
#define MPD_FS_NARROW_PATH_HXX
|
||||||
|
|
||||||
#include "Path.hxx"
|
#include "Path.hxx"
|
||||||
#include "util/Macros.hxx"
|
|
||||||
|
|
||||||
#ifdef _UNICODE
|
#ifdef _UNICODE
|
||||||
#include "lib/icu/Win32.hxx"
|
#include "AllocatedPath.hxx"
|
||||||
#include "util/AllocatedString.hxx"
|
#include "util/AllocatedString.hxx"
|
||||||
#include <windows.h>
|
|
||||||
#else
|
#else
|
||||||
#include "util/StringPointer.hxx"
|
#include "util/StringPointer.hxx"
|
||||||
#endif
|
#endif
|
||||||
@@ -48,12 +46,7 @@ class NarrowPath {
|
|||||||
|
|
||||||
public:
|
public:
|
||||||
#ifdef _UNICODE
|
#ifdef _UNICODE
|
||||||
explicit NarrowPath(Path _path)
|
explicit NarrowPath(Path _path) noexcept;
|
||||||
:value(WideCharToMultiByte(CP_ACP, _path.c_str())) {
|
|
||||||
if (value.IsNull())
|
|
||||||
/* fall back to empty string */
|
|
||||||
value = Value::Empty();
|
|
||||||
}
|
|
||||||
#else
|
#else
|
||||||
explicit NarrowPath(Path _path):value(_path.c_str()) {}
|
explicit NarrowPath(Path _path):value(_path.c_str()) {}
|
||||||
#endif
|
#endif
|
||||||
@@ -67,4 +60,43 @@ public:
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A path name converted from a "narrow" string. This is used to
|
||||||
|
* import an existing narrow string to a #Path.
|
||||||
|
*/
|
||||||
|
class FromNarrowPath {
|
||||||
|
#ifdef _UNICODE
|
||||||
|
using Value = AllocatedPath;
|
||||||
|
#else
|
||||||
|
using Value = Path;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
Value value{nullptr};
|
||||||
|
|
||||||
|
public:
|
||||||
|
FromNarrowPath() = default;
|
||||||
|
|
||||||
|
#ifdef _UNICODE
|
||||||
|
/**
|
||||||
|
* Throws on error.
|
||||||
|
*/
|
||||||
|
FromNarrowPath(const char *s);
|
||||||
|
#else
|
||||||
|
constexpr FromNarrowPath(const char *s) noexcept
|
||||||
|
:value(Value::FromFS(s)) {}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifndef _UNICODE
|
||||||
|
constexpr
|
||||||
|
#endif
|
||||||
|
operator Path() const noexcept {
|
||||||
|
#ifdef _UNICODE
|
||||||
|
if (value.IsNull())
|
||||||
|
return nullptr;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
@@ -108,6 +108,12 @@ struct PathTraitsFS {
|
|||||||
return IsSeparator(*p);
|
return IsSeparator(*p);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
gcc_pure gcc_nonnull_all
|
||||||
|
static bool IsSpecialFilename(const_pointer_type name) noexcept {
|
||||||
|
return (name[0] == '.' && name[1] == 0) ||
|
||||||
|
(name[0] == '.' && name[1] == '.' && name[2] == 0);
|
||||||
|
}
|
||||||
|
|
||||||
gcc_pure gcc_nonnull_all
|
gcc_pure gcc_nonnull_all
|
||||||
static size_t GetLength(const_pointer_type p) noexcept {
|
static size_t GetLength(const_pointer_type p) noexcept {
|
||||||
return StringLength(p);
|
return StringLength(p);
|
||||||
@@ -216,6 +222,12 @@ struct PathTraitsUTF8 {
|
|||||||
return IsSeparator(*p);
|
return IsSeparator(*p);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
gcc_pure gcc_nonnull_all
|
||||||
|
static bool IsSpecialFilename(const_pointer_type name) noexcept {
|
||||||
|
return (name[0] == '.' && name[1] == 0) ||
|
||||||
|
(name[0] == '.' && name[1] == '.' && name[2] == 0);
|
||||||
|
}
|
||||||
|
|
||||||
gcc_pure gcc_nonnull_all
|
gcc_pure gcc_nonnull_all
|
||||||
static size_t GetLength(const_pointer_type p) noexcept {
|
static size_t GetLength(const_pointer_type p) noexcept {
|
||||||
return StringLength(p);
|
return StringLength(p);
|
||||||
|
|||||||
@@ -40,7 +40,7 @@ class BufferedReader {
|
|||||||
|
|
||||||
public:
|
public:
|
||||||
explicit BufferedReader(Reader &_reader) noexcept
|
explicit BufferedReader(Reader &_reader) noexcept
|
||||||
:reader(_reader), buffer(4096) {}
|
:reader(_reader), buffer(16384) {}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Reset the internal state. Should be called after rewinding
|
* Reset the internal state. Should be called after rewinding
|
||||||
|
|||||||
@@ -36,7 +36,7 @@ class GunzipReader final : public Reader {
|
|||||||
|
|
||||||
z_stream z;
|
z_stream z;
|
||||||
|
|
||||||
StaticFifoBuffer<Bytef, 4096> buffer;
|
StaticFifoBuffer<Bytef, 65536> buffer;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -62,7 +62,7 @@ GzipOutputStream::Flush()
|
|||||||
z.avail_in = 0;
|
z.avail_in = 0;
|
||||||
|
|
||||||
while (true) {
|
while (true) {
|
||||||
Bytef output[4096];
|
Bytef output[16384];
|
||||||
z.next_out = output;
|
z.next_out = output;
|
||||||
z.avail_out = sizeof(output);
|
z.avail_out = sizeof(output);
|
||||||
|
|
||||||
@@ -87,7 +87,7 @@ GzipOutputStream::Write(const void *_data, size_t size)
|
|||||||
z.avail_in = size;
|
z.avail_in = size;
|
||||||
|
|
||||||
while (z.avail_in > 0) {
|
while (z.avail_in > 0) {
|
||||||
Bytef output[4096];
|
Bytef output[16384];
|
||||||
z.next_out = output;
|
z.next_out = output;
|
||||||
z.avail_out = sizeof(output);
|
z.avail_out = sizeof(output);
|
||||||
|
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ fs_sources = [
|
|||||||
'Path.cxx',
|
'Path.cxx',
|
||||||
'Path2.cxx',
|
'Path2.cxx',
|
||||||
'AllocatedPath.cxx',
|
'AllocatedPath.cxx',
|
||||||
|
'NarrowPath.cxx',
|
||||||
'FileSystem.cxx',
|
'FileSystem.cxx',
|
||||||
'List.cxx',
|
'List.cxx',
|
||||||
'StandardDirectory.cxx',
|
'StandardDirectory.cxx',
|
||||||
|
|||||||
@@ -76,7 +76,7 @@ public:
|
|||||||
|
|
||||||
virtual ~AsyncInputStream();
|
virtual ~AsyncInputStream();
|
||||||
|
|
||||||
EventLoop &GetEventLoop() {
|
auto &GetEventLoop() const noexcept {
|
||||||
return deferred_resume.GetEventLoop();
|
return deferred_resume.GetEventLoop();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -35,6 +35,9 @@ input_glue = static_library(
|
|||||||
'BufferedInputStream.cxx',
|
'BufferedInputStream.cxx',
|
||||||
'MaybeBufferedInputStream.cxx',
|
'MaybeBufferedInputStream.cxx',
|
||||||
include_directories: inc,
|
include_directories: inc,
|
||||||
|
dependencies: [
|
||||||
|
boost_dep,
|
||||||
|
],
|
||||||
)
|
)
|
||||||
|
|
||||||
input_glue_dep = declare_dependency(
|
input_glue_dep = declare_dependency(
|
||||||
|
|||||||
@@ -113,7 +113,7 @@ input_cdio_init(EventLoop &, const ConfigBlock &block)
|
|||||||
throw FormatRuntimeError("Unrecognized 'default_byte_order' setting: %s",
|
throw FormatRuntimeError("Unrecognized 'default_byte_order' setting: %s",
|
||||||
value);
|
value);
|
||||||
}
|
}
|
||||||
speed = block.GetBlockValue("speed",0u);
|
speed = block.GetBlockValue("speed",0U);
|
||||||
}
|
}
|
||||||
|
|
||||||
struct CdioUri {
|
struct CdioUri {
|
||||||
|
|||||||
@@ -180,7 +180,6 @@ CurlInputStream::FreeEasyIndirect() noexcept
|
|||||||
{
|
{
|
||||||
BlockingCall(GetEventLoop(), [this](){
|
BlockingCall(GetEventLoop(), [this](){
|
||||||
FreeEasy();
|
FreeEasy();
|
||||||
(*curl_init)->InvalidateSockets();
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -321,7 +320,7 @@ input_curl_init(EventLoop &event_loop, const ConfigBlock &block)
|
|||||||
http_200_aliases = curl_slist_append(http_200_aliases, "ICY 200 OK");
|
http_200_aliases = curl_slist_append(http_200_aliases, "ICY 200 OK");
|
||||||
|
|
||||||
proxy = block.GetBlockValue("proxy");
|
proxy = block.GetBlockValue("proxy");
|
||||||
proxy_port = block.GetBlockValue("proxy_port", 0u);
|
proxy_port = block.GetBlockValue("proxy_port", 0U);
|
||||||
proxy_user = block.GetBlockValue("proxy_user");
|
proxy_user = block.GetBlockValue("proxy_user");
|
||||||
proxy_password = block.GetBlockValue("proxy_password");
|
proxy_password = block.GetBlockValue("proxy_password");
|
||||||
|
|
||||||
@@ -366,9 +365,9 @@ CurlInputStream::InitEasy()
|
|||||||
request = new CurlRequest(**curl_init, GetURI(), *this);
|
request = new CurlRequest(**curl_init, GetURI(), *this);
|
||||||
|
|
||||||
request->SetOption(CURLOPT_HTTP200ALIASES, http_200_aliases);
|
request->SetOption(CURLOPT_HTTP200ALIASES, http_200_aliases);
|
||||||
request->SetOption(CURLOPT_FOLLOWLOCATION, 1l);
|
request->SetOption(CURLOPT_FOLLOWLOCATION, 1L);
|
||||||
request->SetOption(CURLOPT_MAXREDIRS, 5l);
|
request->SetOption(CURLOPT_MAXREDIRS, 5L);
|
||||||
request->SetOption(CURLOPT_FAILONERROR, 1l);
|
request->SetOption(CURLOPT_FAILONERROR, 1L);
|
||||||
|
|
||||||
if (proxy != nullptr)
|
if (proxy != nullptr)
|
||||||
request->SetOption(CURLOPT_PROXY, proxy);
|
request->SetOption(CURLOPT_PROXY, proxy);
|
||||||
@@ -381,8 +380,8 @@ CurlInputStream::InitEasy()
|
|||||||
StringFormat<1024>("%s:%s", proxy_user,
|
StringFormat<1024>("%s:%s", proxy_user,
|
||||||
proxy_password).c_str());
|
proxy_password).c_str());
|
||||||
|
|
||||||
request->SetOption(CURLOPT_SSL_VERIFYPEER, verify_peer ? 1l : 0l);
|
request->SetOption(CURLOPT_SSL_VERIFYPEER, verify_peer ? 1L : 0L);
|
||||||
request->SetOption(CURLOPT_SSL_VERIFYHOST, verify_host ? 2l : 0l);
|
request->SetOption(CURLOPT_SSL_VERIFYHOST, verify_host ? 2L : 0L);
|
||||||
request->SetOption(CURLOPT_HTTPHEADER, request_headers.Get());
|
request->SetOption(CURLOPT_HTTPHEADER, request_headers.Get());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -102,7 +102,7 @@ public:
|
|||||||
|
|
||||||
~TidalSessionManager() noexcept;
|
~TidalSessionManager() noexcept;
|
||||||
|
|
||||||
EventLoop &GetEventLoop() noexcept {
|
auto &GetEventLoop() const noexcept {
|
||||||
return defer_invoke_handlers.GetEventLoop();
|
return defer_invoke_handlers.GetEventLoop();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -40,15 +40,15 @@ namespace Java {
|
|||||||
*/
|
*/
|
||||||
typedef LocalRef<jobject> LocalObject;
|
typedef LocalRef<jobject> LocalObject;
|
||||||
|
|
||||||
class Object : public GlobalRef<jobject> {
|
class GlobalObject : public GlobalRef<jobject> {
|
||||||
public:
|
public:
|
||||||
/**
|
/**
|
||||||
* Constructs an uninitialized object. The method
|
* Constructs an uninitialized object. The method
|
||||||
* set() must be called before it is destructed.
|
* set() must be called before it is destructed.
|
||||||
*/
|
*/
|
||||||
Object() = default;
|
GlobalObject() = default;
|
||||||
|
|
||||||
Object(JNIEnv *env, jobject obj) noexcept
|
GlobalObject(JNIEnv *env, jobject obj) noexcept
|
||||||
:GlobalRef<jobject>(env, obj) {}
|
:GlobalRef<jobject>(env, obj) {}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright (C) 2016-2018 Max Kellermann <max.kellermann@gmail.com>
|
* Copyright 2016-2018 Max Kellermann <max.kellermann@gmail.com>
|
||||||
*
|
*
|
||||||
* Redistribution and use in source and binary forms, with or without
|
* Redistribution and use in source and binary forms, with or without
|
||||||
* modification, are permitted provided that the following conditions
|
* modification, are permitted provided that the following conditions
|
||||||
@@ -30,6 +30,8 @@
|
|||||||
#ifndef CURL_EASY_HXX
|
#ifndef CURL_EASY_HXX
|
||||||
#define CURL_EASY_HXX
|
#define CURL_EASY_HXX
|
||||||
|
|
||||||
|
#include "String.hxx"
|
||||||
|
|
||||||
#include <curl/curl.h>
|
#include <curl/curl.h>
|
||||||
|
|
||||||
#include <utility>
|
#include <utility>
|
||||||
@@ -88,8 +90,84 @@ public:
|
|||||||
throw std::runtime_error(curl_easy_strerror(code));
|
throw std::runtime_error(curl_easy_strerror(code));
|
||||||
}
|
}
|
||||||
|
|
||||||
char *Escape(const char *string, int length=0) const noexcept {
|
void SetPrivate(void *pointer) {
|
||||||
return curl_easy_escape(handle, string, length);
|
SetOption(CURLOPT_PRIVATE, pointer);
|
||||||
|
}
|
||||||
|
|
||||||
|
void SetErrorBuffer(char *buf) {
|
||||||
|
SetOption(CURLOPT_ERRORBUFFER, buf);
|
||||||
|
}
|
||||||
|
|
||||||
|
void SetURL(const char *value) {
|
||||||
|
SetOption(CURLOPT_URL, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
void SetUserAgent(const char *value) {
|
||||||
|
SetOption(CURLOPT_USERAGENT, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
void SetRequestHeaders(struct curl_slist *headers) {
|
||||||
|
SetOption(CURLOPT_HTTPHEADER, headers);
|
||||||
|
}
|
||||||
|
|
||||||
|
void SetBasicAuth(const char *userpwd) {
|
||||||
|
SetOption(CURLOPT_USERPWD, userpwd);
|
||||||
|
}
|
||||||
|
|
||||||
|
void SetNoProgress(bool value=true) {
|
||||||
|
SetOption(CURLOPT_NOPROGRESS, (long)value);
|
||||||
|
}
|
||||||
|
|
||||||
|
void SetNoSignal(bool value=true) {
|
||||||
|
SetOption(CURLOPT_NOSIGNAL, (long)value);
|
||||||
|
}
|
||||||
|
|
||||||
|
void SetFailOnError(bool value=true) {
|
||||||
|
SetOption(CURLOPT_FAILONERROR, (long)value);
|
||||||
|
}
|
||||||
|
|
||||||
|
void SetConnectTimeout(long timeout) {
|
||||||
|
SetOption(CURLOPT_CONNECTTIMEOUT, timeout);
|
||||||
|
}
|
||||||
|
|
||||||
|
void SetHeaderFunction(size_t (*function)(char *buffer, size_t size,
|
||||||
|
size_t nitems,
|
||||||
|
void *userdata),
|
||||||
|
void *userdata) {
|
||||||
|
SetOption(CURLOPT_HEADERFUNCTION, function);
|
||||||
|
SetOption(CURLOPT_HEADERDATA, userdata);
|
||||||
|
}
|
||||||
|
|
||||||
|
void SetWriteFunction(size_t (*function)(char *ptr, size_t size,
|
||||||
|
size_t nmemb, void *userdata),
|
||||||
|
void *userdata) {
|
||||||
|
SetOption(CURLOPT_WRITEFUNCTION, function);
|
||||||
|
SetOption(CURLOPT_WRITEDATA, userdata);
|
||||||
|
}
|
||||||
|
|
||||||
|
void SetNoBody(bool value=true) {
|
||||||
|
SetOption(CURLOPT_NOBODY, (long)value);
|
||||||
|
}
|
||||||
|
|
||||||
|
void SetPost(bool value=true) {
|
||||||
|
SetOption(CURLOPT_POST, (long)value);
|
||||||
|
}
|
||||||
|
|
||||||
|
void SetRequestBody(const void *data, size_t size) {
|
||||||
|
SetOption(CURLOPT_POSTFIELDS, data);
|
||||||
|
SetOption(CURLOPT_POSTFIELDSIZE, (long)size);
|
||||||
|
}
|
||||||
|
|
||||||
|
void SetHttpPost(const struct curl_httppost *post) {
|
||||||
|
SetOption(CURLOPT_HTTPPOST, post);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Unpause() noexcept {
|
||||||
|
return ::curl_easy_pause(handle, CURLPAUSE_CONT) == CURLE_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
CurlString Escape(const char *string, int length=0) const noexcept {
|
||||||
|
return CurlString(curl_easy_escape(handle, string, length));
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
71
src/lib/curl/Escape.cxx
Normal file
71
src/lib/curl/Escape.cxx
Normal file
@@ -0,0 +1,71 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2018 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 "Escape.hxx"
|
||||||
|
#include "Easy.hxx"
|
||||||
|
#include "String.hxx"
|
||||||
|
#include "util/IterableSplitString.hxx"
|
||||||
|
|
||||||
|
std::string
|
||||||
|
CurlEscapeUriPath(CURL *curl, StringView src) noexcept
|
||||||
|
{
|
||||||
|
std::string dest;
|
||||||
|
|
||||||
|
for (const auto i : IterableSplitString(src, '/')) {
|
||||||
|
CurlString escaped(curl_easy_escape(curl, i.data, i.size));
|
||||||
|
if (!dest.empty())
|
||||||
|
dest.push_back('/');
|
||||||
|
dest += escaped.c_str();
|
||||||
|
}
|
||||||
|
|
||||||
|
return dest;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string
|
||||||
|
CurlEscapeUriPath(StringView src) noexcept
|
||||||
|
{
|
||||||
|
CurlEasy easy;
|
||||||
|
return CurlEscapeUriPath(easy.Get(), src);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string
|
||||||
|
CurlUnescape(CURL *curl, StringView src) noexcept
|
||||||
|
{
|
||||||
|
int outlength;
|
||||||
|
CurlString tmp(curl_easy_unescape(curl, src.data, src.size,
|
||||||
|
&outlength));
|
||||||
|
return std::string(tmp.c_str(), outlength);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string
|
||||||
|
CurlUnescape(StringView src) noexcept
|
||||||
|
{
|
||||||
|
CurlEasy easy;
|
||||||
|
return CurlUnescape(easy.Get(), src);
|
||||||
|
}
|
||||||
51
src/lib/curl/Escape.hxx
Normal file
51
src/lib/curl/Escape.hxx
Normal file
@@ -0,0 +1,51 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2018 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 CURL_ESCAPE_HXX
|
||||||
|
#define CURL_ESCAPE_HXX
|
||||||
|
|
||||||
|
#include <curl/curl.h>
|
||||||
|
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
struct StringView;
|
||||||
|
|
||||||
|
std::string
|
||||||
|
CurlEscapeUriPath(CURL *curl, StringView src) noexcept;
|
||||||
|
|
||||||
|
std::string
|
||||||
|
CurlEscapeUriPath(StringView src) noexcept;
|
||||||
|
|
||||||
|
std::string
|
||||||
|
CurlUnescape(CURL *curl, StringView src) noexcept;
|
||||||
|
|
||||||
|
std::string
|
||||||
|
CurlUnescape(StringView src) noexcept;
|
||||||
|
|
||||||
|
#endif
|
||||||
@@ -28,6 +28,7 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
#include "Form.hxx"
|
#include "Form.hxx"
|
||||||
|
#include "String.hxx"
|
||||||
|
|
||||||
std::string
|
std::string
|
||||||
EncodeForm(CURL *curl,
|
EncodeForm(CURL *curl,
|
||||||
@@ -43,12 +44,10 @@ EncodeForm(CURL *curl,
|
|||||||
result.push_back('=');
|
result.push_back('=');
|
||||||
|
|
||||||
if (!i.second.empty()) {
|
if (!i.second.empty()) {
|
||||||
char *value = curl_easy_escape(curl, i.second.data(),
|
CurlString value(curl_easy_escape(curl, i.second.data(),
|
||||||
i.second.length());
|
i.second.length()));
|
||||||
if (value != nullptr) {
|
if (value)
|
||||||
result.append(value);
|
result.append(value);
|
||||||
curl_free(value);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user