Compare commits
435 Commits
Author | SHA1 | Date | |
---|---|---|---|
![]() |
566787f041 | ||
![]() |
79b2366387 | ||
![]() |
5acea014b0 | ||
![]() |
b72801abf3 | ||
![]() |
23d5a2b862 | ||
![]() |
7715311117 | ||
![]() |
4c1cfca95b | ||
![]() |
e113ce9621 | ||
![]() |
e8213220e2 | ||
![]() |
83f9d2a963 | ||
![]() |
bf97ebf89f | ||
![]() |
5b22d27cbb | ||
![]() |
e907ff43ae | ||
![]() |
b18fc3a8d0 | ||
![]() |
a8e23c4140 | ||
![]() |
fc3861b421 | ||
![]() |
e81bb5d8f1 | ||
![]() |
32f4f15831 | ||
![]() |
e29c06b718 | ||
![]() |
d9d511f33e | ||
![]() |
c61a3b8d13 | ||
![]() |
e10b867fe6 | ||
![]() |
43e230f543 | ||
![]() |
b2ae5298a7 | ||
![]() |
17dd21ac7f | ||
![]() |
1a5e0ef7c9 | ||
![]() |
979a7a1dcc | ||
![]() |
962cf32ba7 | ||
![]() |
ae23682372 | ||
![]() |
540919f256 | ||
![]() |
398281cd76 | ||
![]() |
88446ccde9 | ||
![]() |
6238cc0734 | ||
![]() |
fd4823c507 | ||
![]() |
68bcfd8bf0 | ||
![]() |
1d332746af | ||
![]() |
f3e133c617 | ||
![]() |
1678a6eb59 | ||
![]() |
b4dc2c07d5 | ||
![]() |
d7838950d8 | ||
![]() |
2e93a83dd5 | ||
![]() |
db8b419b8c | ||
![]() |
990f631cbc | ||
![]() |
db46d84458 | ||
![]() |
9e6c4f8d80 | ||
![]() |
41b47f95c5 | ||
![]() |
15939fd87c | ||
![]() |
f63c343f68 | ||
![]() |
1a516e7744 | ||
![]() |
5c9d97775f | ||
![]() |
64aadcd13f | ||
![]() |
1f6a7d6462 | ||
![]() |
e44b953d9a | ||
![]() |
6c85020630 | ||
![]() |
9d910320f3 | ||
![]() |
c53074efc9 | ||
![]() |
3b51c53eca | ||
![]() |
0aa0ffb67b | ||
![]() |
33f70931dd | ||
![]() |
8830ea319f | ||
![]() |
cbcdc73f9a | ||
![]() |
4f6c54ecb3 | ||
![]() |
2bdf1b2284 | ||
![]() |
c876d6a51c | ||
![]() |
3c745b4bc6 | ||
![]() |
3a08a6ad72 | ||
![]() |
448b397cb8 | ||
![]() |
64a1386eb6 | ||
![]() |
77c2efe171 | ||
![]() |
587c0f6232 | ||
![]() |
64e8abf203 | ||
![]() |
6c40d2a656 | ||
![]() |
cf674e9273 | ||
![]() |
9bda0379af | ||
![]() |
c67372f8af | ||
![]() |
00789de7d4 | ||
![]() |
5ece9685c2 | ||
![]() |
e7c5a42821 | ||
![]() |
36e6079c57 | ||
![]() |
e5f23678ca | ||
![]() |
749ad7cd83 | ||
![]() |
0b59f4eaee | ||
![]() |
402663de74 | ||
![]() |
eaa66c7ee3 | ||
![]() |
996714d6ff | ||
![]() |
fe48e5596f | ||
![]() |
d7744d2b8e | ||
![]() |
33ee35ab92 | ||
![]() |
5b291ff768 | ||
![]() |
39d6816a6d | ||
![]() |
6517b2d2ac | ||
![]() |
bfdf13dca3 | ||
![]() |
daefc61aa4 | ||
![]() |
6fed6e50e4 | ||
![]() |
bc9e074822 | ||
![]() |
8047102542 | ||
![]() |
fe5b81e180 | ||
![]() |
f032925c2d | ||
![]() |
8125a5dddb | ||
![]() |
154170e475 | ||
![]() |
fb83936feb | ||
![]() |
db8bf52f7d | ||
![]() |
756f0b8027 | ||
![]() |
b1fba8d3d7 | ||
![]() |
e606044271 | ||
![]() |
bcbb3371ff | ||
![]() |
de632882d1 | ||
![]() |
745e492d15 | ||
![]() |
c5dc615efe | ||
![]() |
beeb02025e | ||
![]() |
cdf7062597 | ||
![]() |
346084da1e | ||
![]() |
bbceb5eb91 | ||
![]() |
90d85319c2 | ||
![]() |
3d03683e7d | ||
![]() |
d8a74802d1 | ||
![]() |
191919d1b1 | ||
![]() |
df38e7565b | ||
![]() |
cb49a03fd7 | ||
![]() |
faee5bbb78 | ||
![]() |
7befab7e83 | ||
![]() |
4244e61214 | ||
![]() |
46eab05045 | ||
![]() |
5ca137c73c | ||
![]() |
760238fe16 | ||
![]() |
a99b4abae8 | ||
![]() |
472881cb95 | ||
![]() |
c4efc37ad8 | ||
![]() |
691b6a236e | ||
![]() |
5c7243d3ad | ||
![]() |
44cfdff39a | ||
![]() |
5eedda691a | ||
![]() |
a30d5e1b6a | ||
![]() |
8ef09a0a71 | ||
![]() |
e8044663b3 | ||
![]() |
8444c33514 | ||
![]() |
2b7328b434 | ||
![]() |
ca705e1e37 | ||
![]() |
d9f9b3df10 | ||
![]() |
a43ee97746 | ||
![]() |
43c32372e7 | ||
![]() |
5716cde1fb | ||
![]() |
b7a99b4a4b | ||
![]() |
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 | ||
![]() |
bc89ca92b4 | ||
![]() |
b968e1b6de | ||
![]() |
6c9f9c136b | ||
![]() |
9bff5f9e36 | ||
![]() |
2bf26a2ff8 | ||
![]() |
e33b50d9c5 | ||
![]() |
21fa44c0d5 | ||
![]() |
44444e1b89 | ||
![]() |
ca450663d0 | ||
![]() |
f3d16f6d1b | ||
![]() |
4464cdcc67 | ||
![]() |
2d61e526de | ||
![]() |
7723c481db | ||
![]() |
0ed10542cc | ||
![]() |
ab830f9afd |
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
|
||||||
|
176
NEWS
176
NEWS
@@ -1,3 +1,179 @@
|
|||||||
|
ver 0.21.26 (2020/09/21)
|
||||||
|
* database
|
||||||
|
- inotify: obey ".mpdignore" files
|
||||||
|
* output
|
||||||
|
- osx: fix crash bug
|
||||||
|
- sles: support floating point samples
|
||||||
|
* archive
|
||||||
|
- bzip2: fix crash on corrupt bzip2 file
|
||||||
|
- bzip2: flush output at end of input file
|
||||||
|
- iso9660: fix unaligned reads
|
||||||
|
- iso9660: support seeking
|
||||||
|
- zzip: fix crash on corrupt ZIP file
|
||||||
|
* decoder
|
||||||
|
- ffmpeg: remove "rtsp://" from the list of supported protocols
|
||||||
|
- ffmpeg: add "hls+http://" to the list of supported protocols
|
||||||
|
- opus: support the gain value from the Opus header
|
||||||
|
- sndfile: fix lost samples at end of file
|
||||||
|
* fix "single" mode bug after resuming playback
|
||||||
|
* the default log_level is "default", not "info"
|
||||||
|
|
||||||
|
ver 0.21.25 (2020/07/06)
|
||||||
|
* protocol:
|
||||||
|
- fix crash when using "rangeid" while playing
|
||||||
|
* database
|
||||||
|
- simple: automatically scan new mounts
|
||||||
|
- upnp: fix compatibility with Plex DLNA
|
||||||
|
* storage
|
||||||
|
- fix disappearing mounts after mounting twice
|
||||||
|
- udisks: fix reading ".mpdignore"
|
||||||
|
* input
|
||||||
|
- file: detect premature end of file
|
||||||
|
- smbclient: don't send credentials to MPD clients
|
||||||
|
* decoder
|
||||||
|
- opus: apply pre-skip and end trimming
|
||||||
|
- opus: fix memory leak
|
||||||
|
- opus: fix crash bug
|
||||||
|
- vorbis: fix crash bug
|
||||||
|
* output
|
||||||
|
- osx: improve sample rate selection
|
||||||
|
- osx: fix noise while stopping
|
||||||
|
* neighbor
|
||||||
|
- upnp: fix crash during shutdown
|
||||||
|
* Windows/Android:
|
||||||
|
- fix Boost detection after breaking change in Meson 0.54
|
||||||
|
|
||||||
|
ver 0.21.24 (2020/06/10)
|
||||||
|
* protocol
|
||||||
|
- "tagtypes" requires no permissions
|
||||||
|
* 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)
|
||||||
|
* decoder
|
||||||
|
- sidplay: show track durations in database
|
||||||
|
- sidplay: convert tag values from Windows-1252 charset
|
||||||
|
- sidplay: strip text from "Date" tag
|
||||||
|
* player
|
||||||
|
- fix crash after song change
|
||||||
|
- fix seek position after restarting the decoder
|
||||||
|
* protocol
|
||||||
|
- include command name in error responses
|
||||||
|
|
||||||
ver 0.21.13 (2019/08/06)
|
ver 0.21.13 (2019/08/06)
|
||||||
* input
|
* input
|
||||||
- cdio_paranoia: require libcdio-paranoia 10.2+0.93+1
|
- cdio_paranoia: require libcdio-paranoia 10.2+0.93+1
|
||||||
|
@@ -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="36"
|
android:versionCode="49"
|
||||||
android:versionName="0.21.13">
|
android:versionName="0.21.26">
|
||||||
|
|
||||||
<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.13'
|
version = '0.21.26'
|
||||||
# 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
|
||||||
----
|
----
|
||||||
@@ -60,25 +60,25 @@ The default plugin which gives :program:`MPD` access to local files. It is used
|
|||||||
curl
|
curl
|
||||||
----
|
----
|
||||||
|
|
||||||
A WebDAV client using libcurl. It is used when :code:`music_directory` contains a http:// or https:// URI, for example :samp:`https://the.server/dav/`.
|
A WebDAV client using libcurl. It is used when :code:`music_directory`
|
||||||
|
contains a ``http://`` or ``https://`` URI, for example
|
||||||
|
:samp:`https://the.server/dav/`.
|
||||||
|
|
||||||
smbclient
|
smbclient
|
||||||
---------
|
---------
|
||||||
|
|
||||||
Load music files from a SMB/CIFS server. It is used when :code:`music_directory` contains a smb:// URI, for example :samp:`smb://myfileserver/Music`.
|
Load music files from a SMB/CIFS server. It is used when
|
||||||
|
:code:`music_directory` contains a ``smb://`` URI, for example
|
||||||
|
:samp:`smb://myfileserver/Music`.
|
||||||
|
|
||||||
nfs
|
nfs
|
||||||
---
|
---
|
||||||
|
|
||||||
Load music files from a NFS server. It is used when :code:`music_directory` contains a nfs:// URI according to RFC2224, for example :samp:`nfs://servername/path`.
|
Load music files from a NFS server. It is used when
|
||||||
|
:code:`music_directory` contains a ``nfs://`` URI according to
|
||||||
|
RFC2224, for example :samp:`nfs://servername/path`.
|
||||||
|
|
||||||
This plugin uses libnfs, which supports only NFS version 3. Since :program:`MPD` is not allowed to bind to "privileged ports", the NFS server needs to enable the "insecure" setting; example :file:`/etc/exports`:
|
See :ref:`input_nfs` for more information.
|
||||||
|
|
||||||
.. code-block:: none
|
|
||||||
|
|
||||||
/srv/mp3 192.168.1.55(ro,insecure)
|
|
||||||
|
|
||||||
Don't fear: "insecure" does not mean that your NFS server is insecure. A few decades ago, people thought the concept of "privileged ports" would make network services "secure", which was a fallacy. The absence of this obsolete "security" measure means little.
|
|
||||||
|
|
||||||
udisks
|
udisks
|
||||||
------
|
------
|
||||||
@@ -162,7 +162,10 @@ curl
|
|||||||
|
|
||||||
Opens remote files or streams over HTTP using libcurl.
|
Opens remote files or streams over HTTP using libcurl.
|
||||||
|
|
||||||
Note that unless overridden by the below settings (e.g. by setting them to a blank value), general curl configuration from environment variables such as http_proxy or specified in :file:`~/.curlrc` will be in effect.
|
Note that unless overridden by the below settings (e.g. by setting
|
||||||
|
them to a blank value), general curl configuration from environment
|
||||||
|
variables such as ``http_proxy`` or specified in :file:`~/.curlrc`
|
||||||
|
will be in effect.
|
||||||
|
|
||||||
.. list-table::
|
.. list-table::
|
||||||
:widths: 20 80
|
:widths: 20 80
|
||||||
@@ -182,7 +185,9 @@ Note that unless overridden by the below settings (e.g. by setting them to a bla
|
|||||||
ffmpeg
|
ffmpeg
|
||||||
------
|
------
|
||||||
|
|
||||||
Access to various network protocols implemented by the FFmpeg library: gopher://, rtp://, rtsp://, rtmp://, rtmpt://, rtmps://
|
Access to various network protocols implemented by the FFmpeg library:
|
||||||
|
``gopher://``, ``rtp://``, ``rtsp://``, ``rtmp://``, ``rtmpt://``,
|
||||||
|
``rtmps://``
|
||||||
|
|
||||||
file
|
file
|
||||||
----
|
----
|
||||||
@@ -194,30 +199,51 @@ mms
|
|||||||
|
|
||||||
Plays streams with the MMS protocol using `libmms <https://launchpad.net/libmms>`_.
|
Plays streams with the MMS protocol using `libmms <https://launchpad.net/libmms>`_.
|
||||||
|
|
||||||
|
.. _input_nfs:
|
||||||
|
|
||||||
nfs
|
nfs
|
||||||
---
|
---
|
||||||
|
|
||||||
Allows :program:`MPD` to access files on NFSv3 servers without actually mounting them (i.e. in userspace, without help from the kernel's VFS layer). All URIs with the nfs:// scheme are used according to RFC2224. Example:
|
Allows :program:`MPD` to access files on NFS servers without actually
|
||||||
|
mounting them (i.e. with :program:`libnfs` in userspace, without help
|
||||||
|
from the kernel's VFS layer). All URIs with the ``nfs://`` scheme are
|
||||||
|
used according to RFC2224. Example:
|
||||||
|
|
||||||
.. code-block:: none
|
.. code-block:: none
|
||||||
|
|
||||||
mpc add nfs://servername/path/filename.ogg
|
mpc add nfs://servername/path/filename.ogg
|
||||||
|
|
||||||
Note that this usually requires enabling the "insecure" flag in the server's /etc/exports file, because :program:`MPD` cannot bind to so-called "privileged" ports. Don't fear: this will not make your file server insecure; the flag was named in a time long ago when privileged ports were thought to be meaningful for security. By today's standards, NFSv3 is not secure at all, and if you believe it is, you're already doomed.
|
This plugin uses :program:`libnfs`, which supports only NFS version 3.
|
||||||
|
Since :program:`MPD` is not allowed to bind to so-called "privileged
|
||||||
|
ports", the NFS server needs to enable the ``insecure`` setting;
|
||||||
|
example :file:`/etc/exports`:
|
||||||
|
|
||||||
|
.. code-block:: none
|
||||||
|
|
||||||
|
/srv/mp3 192.168.1.55(ro,insecure)
|
||||||
|
|
||||||
|
Don't fear: this will not make your file server insecure; the flag was
|
||||||
|
named a time long ago when privileged ports were thought to be
|
||||||
|
meaningful for security. By today's standards, NFSv3 is not secure at
|
||||||
|
all, and if you believe it is, you're already doomed.
|
||||||
|
|
||||||
smbclient
|
smbclient
|
||||||
---------
|
---------
|
||||||
|
|
||||||
Allows :program:`MPD` to access files on SMB/CIFS servers (e.g. Samba or Microsoft Windows). All URIs with the smb:// scheme are used. Example:
|
Allows :program:`MPD` to access files on SMB/CIFS servers (e.g. Samba
|
||||||
|
or Microsoft Windows). All URIs with the ``smb://`` scheme are
|
||||||
|
used. Example:
|
||||||
|
|
||||||
.. code-block:: none
|
.. code-block:: none
|
||||||
|
|
||||||
mpc add smb://servername/sharename/filename.ogg
|
mpc add smb://servername/sharename/filename.ogg
|
||||||
|
mpc add smb://username:password@servername/sharename/filename.ogg
|
||||||
|
|
||||||
qobuz
|
qobuz
|
||||||
-----
|
-----
|
||||||
|
|
||||||
Play songs from the commercial streaming service Qobuz. It plays URLs in the form qobuz://track/ID, e.g.:
|
Play songs from the commercial streaming service Qobuz. It plays URLs
|
||||||
|
in the form ``qobuz://track/ID``, e.g.:
|
||||||
|
|
||||||
.. code-block:: none
|
.. code-block:: none
|
||||||
|
|
||||||
@@ -243,7 +269,9 @@ Play songs from the commercial streaming service Qobuz. It plays URLs in the for
|
|||||||
tidal
|
tidal
|
||||||
-----
|
-----
|
||||||
|
|
||||||
Play songs from the commercial streaming service `Tidal <http://tidal.com/>`_. It plays URLs in the form tidal://track/ID, e.g.:
|
Play songs from the commercial streaming service `Tidal
|
||||||
|
<http://tidal.com/>`_. It plays URLs in the form ``tidal://track/ID``,
|
||||||
|
e.g.:
|
||||||
|
|
||||||
.. warning::
|
.. warning::
|
||||||
|
|
||||||
@@ -1044,7 +1072,8 @@ sles
|
|||||||
|
|
||||||
Plugin using the `OpenSL ES <https://www.khronos.org/opensles/>`__
|
Plugin using the `OpenSL ES <https://www.khronos.org/opensles/>`__
|
||||||
audio API. Its primary use is local playback on Android, where
|
audio API. Its primary use is local playback on Android, where
|
||||||
:ref:`ALSA <alsa_plugin>` is not available.
|
:ref:`ALSA <alsa_plugin>` is not available. It supports 16 bit and
|
||||||
|
floating point samples.
|
||||||
|
|
||||||
|
|
||||||
solaris
|
solaris
|
||||||
@@ -1069,7 +1098,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
|
||||||
@@ -824,7 +825,8 @@ The music database
|
|||||||
albumart
|
albumart
|
||||||
size: 1024768
|
size: 1024768
|
||||||
binary: 8192
|
binary: 8192
|
||||||
<8192 bytes>OK
|
<8192 bytes>
|
||||||
|
OK
|
||||||
|
|
||||||
:command:`count {FILTER} [group {GROUPTYPE}]`
|
:command:`count {FILTER} [group {GROUPTYPE}]`
|
||||||
Count the number of songs and their total playtime in
|
Count the number of songs and their total playtime in
|
||||||
|
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**
|
||||||
|
31
meson.build
31
meson.build
@@ -1,11 +1,12 @@
|
|||||||
project(
|
project(
|
||||||
'mpd',
|
'mpd',
|
||||||
['c', 'cpp'],
|
['c', 'cpp'],
|
||||||
version: '0.21.13',
|
version: '0.21.26',
|
||||||
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,10 +313,14 @@ 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/apple')
|
||||||
|
|
||||||
subdir('src/lib/dbus')
|
subdir('src/lib/dbus')
|
||||||
subdir('src/lib/icu')
|
subdir('src/lib/icu')
|
||||||
subdir('src/lib/smbclient')
|
subdir('src/lib/smbclient')
|
||||||
@@ -330,7 +343,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 +397,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.19.tar.xz',
|
||||||
'fa6bdab67c0e0490302b38f00c27b4959735c3ec8aef7a88327adb1407654464',
|
'158aad4c2278ab08e76a3f2b0166c99b39fae00ee17231bd225c5a36e977a189',
|
||||||
'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',
|
||||||
@@ -24,8 +25,8 @@ libogg = AutotoolsProject(
|
|||||||
)
|
)
|
||||||
|
|
||||||
libvorbis = AutotoolsProject(
|
libvorbis = AutotoolsProject(
|
||||||
'http://downloads.xiph.org/releases/vorbis/libvorbis-1.3.6.tar.xz',
|
'http://downloads.xiph.org/releases/vorbis/libvorbis-1.3.7.tar.xz',
|
||||||
'af00bb5a784e7c9e69f56823de4637c350643deedaf333d0fa86ecdba6fcb415',
|
'b33cc4934322bcbf6efcbacf49e3ca01aadbea4114ec9589d1b1e9d20f72954b',
|
||||||
'lib/libvorbis.a',
|
'lib/libvorbis.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.3.1.tar.xz',
|
||||||
'0c3020452880581a8face91595b239198078645e7d7184273b8bcc7758beb63d',
|
'ad009240d46e307b4e03a213a0f49c11b650e445b1f8be0dda2a9212b34d2ffb',
|
||||||
'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.72.0.tar.xz',
|
||||||
'9252332a7f871ce37bfa7f78bdd0a0e3924d8187cc27cb57c76c9474a7168fb3',
|
'0ded0808c4d85f2ee0db86980ae610cc9d165e9ca9da466196cc73c346513713',
|
||||||
'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.74.0/source/boost_1_74_0.tar.bz2',
|
||||||
'430ae8354789de4fd19ee52f3b1f739e1fba576f0aded0897c3c2bc00fb38778',
|
'83bfc1507731a0906e387fc28b7ef5417d591429e51e788417fe9ff025e116b1',
|
||||||
'include/boost/version.hpp',
|
'include/boost/version.hpp',
|
||||||
)
|
)
|
||||||
|
@@ -91,7 +91,12 @@ def configure(toolchain, src, build, args=()):
|
|||||||
'--cross-file', cross_file,
|
'--cross-file', cross_file,
|
||||||
] + args
|
] + args
|
||||||
|
|
||||||
subprocess.check_call(configure, env=toolchain.env)
|
env = toolchain.env.copy()
|
||||||
|
|
||||||
|
# Meson 0.54 requires the BOOST_ROOT environment variable
|
||||||
|
env['BOOST_ROOT'] = toolchain.install_prefix
|
||||||
|
|
||||||
|
subprocess.check_call(configure, env=env)
|
||||||
|
|
||||||
class MesonProject(Project):
|
class MesonProject(Project):
|
||||||
def __init__(self, url, md5, installed, configure_args=[],
|
def __init__(self, url, md5, installed, configure_args=[],
|
||||||
|
@@ -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
|
||||||
|
82
src/Log.cxx
82
src/Log.cxx
@@ -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
|
||||||
@@ -19,8 +19,7 @@
|
|||||||
|
|
||||||
#include "LogV.hxx"
|
#include "LogV.hxx"
|
||||||
#include "util/Domain.hxx"
|
#include "util/Domain.hxx"
|
||||||
|
#include "util/Exception.hxx"
|
||||||
#include <exception>
|
|
||||||
|
|
||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
@@ -29,20 +28,20 @@
|
|||||||
static constexpr Domain exception_domain("exception");
|
static constexpr Domain exception_domain("exception");
|
||||||
|
|
||||||
void
|
void
|
||||||
LogFormatV(const Domain &domain, LogLevel level,
|
LogFormatV(LogLevel level, const Domain &domain,
|
||||||
const char *fmt, va_list ap) noexcept
|
const char *fmt, va_list ap) noexcept
|
||||||
{
|
{
|
||||||
char msg[1024];
|
char msg[1024];
|
||||||
vsnprintf(msg, sizeof(msg), fmt, ap);
|
vsnprintf(msg, sizeof(msg), fmt, ap);
|
||||||
Log(domain, level, msg);
|
Log(level, domain, msg);
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
LogFormat(const Domain &domain, LogLevel level, const char *fmt, ...) noexcept
|
LogFormat(LogLevel level, const Domain &domain, const char *fmt, ...) noexcept
|
||||||
{
|
{
|
||||||
va_list ap;
|
va_list ap;
|
||||||
va_start(ap, fmt);
|
va_start(ap, fmt);
|
||||||
LogFormatV(domain, level, fmt, ap);
|
LogFormatV(level, domain, fmt, ap);
|
||||||
va_end(ap);
|
va_end(ap);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -51,7 +50,7 @@ FormatDebug(const Domain &domain, const char *fmt, ...) noexcept
|
|||||||
{
|
{
|
||||||
va_list ap;
|
va_list ap;
|
||||||
va_start(ap, fmt);
|
va_start(ap, fmt);
|
||||||
LogFormatV(domain, LogLevel::DEBUG, fmt, ap);
|
LogFormatV(LogLevel::DEBUG, domain, fmt, ap);
|
||||||
va_end(ap);
|
va_end(ap);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -60,7 +59,7 @@ FormatInfo(const Domain &domain, const char *fmt, ...) noexcept
|
|||||||
{
|
{
|
||||||
va_list ap;
|
va_list ap;
|
||||||
va_start(ap, fmt);
|
va_start(ap, fmt);
|
||||||
LogFormatV(domain, LogLevel::INFO, fmt, ap);
|
LogFormatV(LogLevel::INFO, domain, fmt, ap);
|
||||||
va_end(ap);
|
va_end(ap);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -69,7 +68,7 @@ FormatDefault(const Domain &domain, const char *fmt, ...) noexcept
|
|||||||
{
|
{
|
||||||
va_list ap;
|
va_list ap;
|
||||||
va_start(ap, fmt);
|
va_start(ap, fmt);
|
||||||
LogFormatV(domain, LogLevel::DEFAULT, fmt, ap);
|
LogFormatV(LogLevel::DEFAULT, domain, fmt, ap);
|
||||||
va_end(ap);
|
va_end(ap);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -78,7 +77,7 @@ FormatWarning(const Domain &domain, const char *fmt, ...) noexcept
|
|||||||
{
|
{
|
||||||
va_list ap;
|
va_list ap;
|
||||||
va_start(ap, fmt);
|
va_start(ap, fmt);
|
||||||
LogFormatV(domain, LogLevel::WARNING, fmt, ap);
|
LogFormatV(LogLevel::WARNING, domain, fmt, ap);
|
||||||
va_end(ap);
|
va_end(ap);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -87,42 +86,24 @@ FormatError(const Domain &domain, const char *fmt, ...) noexcept
|
|||||||
{
|
{
|
||||||
va_list ap;
|
va_list ap;
|
||||||
va_start(ap, fmt);
|
va_start(ap, fmt);
|
||||||
LogFormatV(domain, LogLevel::ERROR, fmt, ap);
|
LogFormatV(LogLevel::ERROR, domain, fmt, ap);
|
||||||
va_end(ap);
|
va_end(ap);
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
LogError(const std::exception &e) noexcept
|
Log(LogLevel level, const std::exception &e) noexcept
|
||||||
{
|
{
|
||||||
Log(exception_domain, LogLevel::ERROR, e.what());
|
Log(level, exception_domain, GetFullMessage(e).c_str());
|
||||||
|
|
||||||
try {
|
|
||||||
std::rethrow_if_nested(e);
|
|
||||||
} catch (const std::exception &nested) {
|
|
||||||
LogError(nested, "nested");
|
|
||||||
} catch (...) {
|
|
||||||
Log(exception_domain, LogLevel::ERROR,
|
|
||||||
"Unrecognized nested exception");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
LogError(const std::exception &e, const char *msg) noexcept
|
Log(LogLevel level, const std::exception &e, const char *msg) noexcept
|
||||||
{
|
{
|
||||||
FormatError(exception_domain, "%s: %s", msg, e.what());
|
LogFormat(level, exception_domain, "%s: %s", msg, GetFullMessage(e).c_str());
|
||||||
|
|
||||||
try {
|
|
||||||
std::rethrow_if_nested(e);
|
|
||||||
} catch (const std::exception &nested) {
|
|
||||||
LogError(nested);
|
|
||||||
} catch (...) {
|
|
||||||
Log(exception_domain, LogLevel::ERROR,
|
|
||||||
"Unrecognized nested exception");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
FormatError(const std::exception &e, const char *fmt, ...) noexcept
|
LogFormat(LogLevel level, const std::exception &e, const char *fmt, ...) noexcept
|
||||||
{
|
{
|
||||||
char msg[1024];
|
char msg[1024];
|
||||||
va_list ap;
|
va_list ap;
|
||||||
@@ -130,37 +111,24 @@ FormatError(const std::exception &e, const char *fmt, ...) noexcept
|
|||||||
vsnprintf(msg, sizeof(msg), fmt, ap);
|
vsnprintf(msg, sizeof(msg), fmt, ap);
|
||||||
va_end(ap);
|
va_end(ap);
|
||||||
|
|
||||||
LogError(e, msg);
|
Log(level, e, msg);
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
LogError(const std::exception_ptr &ep) noexcept
|
Log(LogLevel level, const std::exception_ptr &ep) noexcept
|
||||||
{
|
{
|
||||||
try {
|
Log(level, exception_domain, GetFullMessage(ep).c_str());
|
||||||
std::rethrow_exception(ep);
|
|
||||||
} catch (const std::exception &e) {
|
|
||||||
LogError(e);
|
|
||||||
} catch (...) {
|
|
||||||
Log(exception_domain, LogLevel::ERROR,
|
|
||||||
"Unrecognized exception");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
LogError(const std::exception_ptr &ep, const char *msg) noexcept
|
Log(LogLevel level, const std::exception_ptr &ep, const char *msg) noexcept
|
||||||
{
|
{
|
||||||
try {
|
LogFormat(level, exception_domain, "%s: %s", msg,
|
||||||
std::rethrow_exception(ep);
|
GetFullMessage(ep).c_str());
|
||||||
} catch (const std::exception &e) {
|
|
||||||
LogError(e, msg);
|
|
||||||
} catch (...) {
|
|
||||||
FormatError(exception_domain,
|
|
||||||
"%s: Unrecognized exception", msg);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
FormatError(const std::exception_ptr &ep, const char *fmt, ...) noexcept
|
LogFormat(LogLevel level, const std::exception_ptr &ep, const char *fmt, ...) noexcept
|
||||||
{
|
{
|
||||||
char msg[1024];
|
char msg[1024];
|
||||||
va_list ap;
|
va_list ap;
|
||||||
@@ -168,13 +136,13 @@ FormatError(const std::exception_ptr &ep, const char *fmt, ...) noexcept
|
|||||||
vsnprintf(msg, sizeof(msg), fmt, ap);
|
vsnprintf(msg, sizeof(msg), fmt, ap);
|
||||||
va_end(ap);
|
va_end(ap);
|
||||||
|
|
||||||
LogError(ep, msg);
|
Log(level, ep, msg);
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
LogErrno(const Domain &domain, int e, const char *msg) noexcept
|
LogErrno(const Domain &domain, int e, const char *msg) noexcept
|
||||||
{
|
{
|
||||||
LogFormat(domain, LogLevel::ERROR, "%s: %s", msg, strerror(e));
|
LogFormat(LogLevel::ERROR, domain, "%s: %s", msg, strerror(e));
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
|
85
src/Log.hxx
85
src/Log.hxx
@@ -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
|
||||||
@@ -28,16 +28,38 @@
|
|||||||
class Domain;
|
class Domain;
|
||||||
|
|
||||||
void
|
void
|
||||||
Log(const Domain &domain, LogLevel level, const char *msg) noexcept;
|
Log(LogLevel level, const Domain &domain, const char *msg) noexcept;
|
||||||
|
|
||||||
gcc_printf(3,4)
|
gcc_printf(3,4)
|
||||||
void
|
void
|
||||||
LogFormat(const Domain &domain, LogLevel level, const char *fmt, ...) noexcept;
|
LogFormat(LogLevel level, const Domain &domain, const char *fmt, ...) noexcept;
|
||||||
|
|
||||||
|
void
|
||||||
|
Log(LogLevel level, const std::exception &e) noexcept;
|
||||||
|
|
||||||
|
void
|
||||||
|
Log(LogLevel level, const std::exception &e, const char *msg) noexcept;
|
||||||
|
|
||||||
|
gcc_printf(3,4)
|
||||||
|
void
|
||||||
|
LogFormat(LogLevel level, const std::exception &e,
|
||||||
|
const char *fmt, ...) noexcept;
|
||||||
|
|
||||||
|
void
|
||||||
|
Log(LogLevel level, const std::exception_ptr &ep) noexcept;
|
||||||
|
|
||||||
|
void
|
||||||
|
Log(LogLevel level, const std::exception_ptr &ep, const char *msg) noexcept;
|
||||||
|
|
||||||
|
gcc_printf(3,4)
|
||||||
|
void
|
||||||
|
LogFormat(LogLevel level, const std::exception_ptr &ep,
|
||||||
|
const char *fmt, ...) noexcept;
|
||||||
|
|
||||||
static inline void
|
static inline void
|
||||||
LogDebug(const Domain &domain, const char *msg) noexcept
|
LogDebug(const Domain &domain, const char *msg) noexcept
|
||||||
{
|
{
|
||||||
Log(domain, LogLevel::DEBUG, msg);
|
Log(LogLevel::DEBUG, domain, msg);
|
||||||
}
|
}
|
||||||
|
|
||||||
gcc_printf(2,3)
|
gcc_printf(2,3)
|
||||||
@@ -47,7 +69,7 @@ FormatDebug(const Domain &domain, const char *fmt, ...) noexcept;
|
|||||||
static inline void
|
static inline void
|
||||||
LogInfo(const Domain &domain, const char *msg) noexcept
|
LogInfo(const Domain &domain, const char *msg) noexcept
|
||||||
{
|
{
|
||||||
Log(domain, LogLevel::INFO, msg);
|
Log(LogLevel::INFO, domain, msg);
|
||||||
}
|
}
|
||||||
|
|
||||||
gcc_printf(2,3)
|
gcc_printf(2,3)
|
||||||
@@ -57,7 +79,7 @@ FormatInfo(const Domain &domain, const char *fmt, ...) noexcept;
|
|||||||
static inline void
|
static inline void
|
||||||
LogDefault(const Domain &domain, const char *msg) noexcept
|
LogDefault(const Domain &domain, const char *msg) noexcept
|
||||||
{
|
{
|
||||||
Log(domain, LogLevel::DEFAULT, msg);
|
Log(LogLevel::DEFAULT, domain, msg);
|
||||||
}
|
}
|
||||||
|
|
||||||
gcc_printf(2,3)
|
gcc_printf(2,3)
|
||||||
@@ -67,7 +89,7 @@ FormatDefault(const Domain &domain, const char *fmt, ...) noexcept;
|
|||||||
static inline void
|
static inline void
|
||||||
LogWarning(const Domain &domain, const char *msg) noexcept
|
LogWarning(const Domain &domain, const char *msg) noexcept
|
||||||
{
|
{
|
||||||
Log(domain, LogLevel::WARNING, msg);
|
Log(LogLevel::WARNING, domain, msg);
|
||||||
}
|
}
|
||||||
|
|
||||||
gcc_printf(2,3)
|
gcc_printf(2,3)
|
||||||
@@ -77,28 +99,47 @@ FormatWarning(const Domain &domain, const char *fmt, ...) noexcept;
|
|||||||
static inline void
|
static inline void
|
||||||
LogError(const Domain &domain, const char *msg) noexcept
|
LogError(const Domain &domain, const char *msg) noexcept
|
||||||
{
|
{
|
||||||
Log(domain, LogLevel::ERROR, msg);
|
Log(LogLevel::ERROR, domain, msg);
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
inline void
|
||||||
LogError(const std::exception &e) noexcept;
|
LogError(const std::exception &e) noexcept
|
||||||
|
{
|
||||||
|
Log(LogLevel::ERROR, e);
|
||||||
|
}
|
||||||
|
|
||||||
void
|
inline void
|
||||||
LogError(const std::exception &e, const char *msg) noexcept;
|
LogError(const std::exception &e, const char *msg) noexcept
|
||||||
|
{
|
||||||
|
Log(LogLevel::ERROR, e, msg);
|
||||||
|
}
|
||||||
|
|
||||||
gcc_printf(2,3)
|
template<typename... Args>
|
||||||
void
|
inline void
|
||||||
FormatError(const std::exception &e, const char *fmt, ...) noexcept;
|
FormatError(const std::exception &e, const char *fmt, Args&&... args) noexcept
|
||||||
|
{
|
||||||
|
LogFormat(LogLevel::ERROR, e, fmt, std::forward<Args>(args)...);
|
||||||
|
}
|
||||||
|
|
||||||
void
|
inline void
|
||||||
LogError(const std::exception_ptr &ep) noexcept;
|
LogError(const std::exception_ptr &ep) noexcept
|
||||||
|
{
|
||||||
|
Log(LogLevel::ERROR, ep);
|
||||||
|
}
|
||||||
|
|
||||||
void
|
inline void
|
||||||
LogError(const std::exception_ptr &ep, const char *msg) noexcept;
|
LogError(const std::exception_ptr &ep, const char *msg) noexcept
|
||||||
|
{
|
||||||
|
Log(LogLevel::ERROR, ep, msg);
|
||||||
|
}
|
||||||
|
|
||||||
gcc_printf(2,3)
|
template<typename... Args>
|
||||||
void
|
inline void
|
||||||
FormatError(const std::exception_ptr &ep, const char *fmt, ...) noexcept;
|
FormatError(const std::exception_ptr &ep,
|
||||||
|
const char *fmt, Args&&... args) noexcept
|
||||||
|
{
|
||||||
|
LogFormat(LogLevel::ERROR, ep, fmt, std::forward<Args>(args)...);
|
||||||
|
}
|
||||||
|
|
||||||
gcc_printf(2,3)
|
gcc_printf(2,3)
|
||||||
void
|
void
|
||||||
|
@@ -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
|
||||||
@@ -61,7 +61,7 @@ ToAndroidLogLevel(LogLevel log_level) noexcept
|
|||||||
|
|
||||||
#else
|
#else
|
||||||
|
|
||||||
static LogLevel log_threshold = LogLevel::INFO;
|
static LogLevel log_threshold = LogLevel::DEFAULT;
|
||||||
|
|
||||||
static bool enable_timestamp;
|
static bool enable_timestamp;
|
||||||
|
|
||||||
@@ -176,7 +176,7 @@ FileLog(const Domain &domain, const char *message) noexcept
|
|||||||
#endif /* !ANDROID */
|
#endif /* !ANDROID */
|
||||||
|
|
||||||
void
|
void
|
||||||
Log(const Domain &domain, LogLevel level, const char *msg) noexcept
|
Log(LogLevel level, const Domain &domain, const char *msg) noexcept
|
||||||
{
|
{
|
||||||
#ifdef ANDROID
|
#ifdef ANDROID
|
||||||
__android_log_print(ToAndroidLogLevel(level), "MPD",
|
__android_log_print(ToAndroidLogLevel(level), "MPD",
|
||||||
|
@@ -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
|
||||||
|
@@ -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
|
||||||
@@ -25,7 +25,7 @@
|
|||||||
#include <stdarg.h>
|
#include <stdarg.h>
|
||||||
|
|
||||||
void
|
void
|
||||||
LogFormatV(const Domain &domain, LogLevel level,
|
LogFormatV(LogLevel level, const Domain &domain,
|
||||||
const char *fmt, va_list ap) noexcept;
|
const char *fmt, va_list ap) noexcept;
|
||||||
|
|
||||||
#endif /* LOG_H */
|
#endif /* LOG_H */
|
||||||
|
@@ -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);
|
||||||
|
@@ -101,7 +101,7 @@ initPermissions(const ConfigData &config)
|
|||||||
const char *separator = strchr(param.value.c_str(),
|
const char *separator = strchr(param.value.c_str(),
|
||||||
PERMISSION_PASSWORD_CHAR);
|
PERMISSION_PASSWORD_CHAR);
|
||||||
|
|
||||||
if (separator == NULL)
|
if (separator == nullptr)
|
||||||
throw FormatRuntimeError("\"%c\" not found in password string "
|
throw FormatRuntimeError("\"%c\" not found in password string "
|
||||||
"\"%s\", line %i",
|
"\"%s\", line %i",
|
||||||
PERMISSION_PASSWORD_CHAR,
|
PERMISSION_PASSWORD_CHAR,
|
||||||
|
@@ -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>
|
||||||
|
@@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright 2003-2018 The Music Player Daemon Project
|
* Copyright 2003-2020 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,10 +27,20 @@
|
|||||||
* that this plugin is unavailable. It will be disabled, and MPD can
|
* that this plugin is unavailable. It will be disabled, and MPD can
|
||||||
* continue initialization.
|
* continue initialization.
|
||||||
*/
|
*/
|
||||||
class PluginUnavailable final : public std::runtime_error {
|
class PluginUnavailable : public std::runtime_error {
|
||||||
public:
|
public:
|
||||||
explicit PluginUnavailable(const char *msg)
|
using std::runtime_error::runtime_error;
|
||||||
:std::runtime_error(msg) {}
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Like #PluginUnavailable, but denotes that the plugin is not
|
||||||
|
* available because it was not explicitly enabled in the
|
||||||
|
* configuration. The message may describe the necessary steps to
|
||||||
|
* enable it.
|
||||||
|
*/
|
||||||
|
class PluginUnconfigured : public PluginUnavailable {
|
||||||
|
public:
|
||||||
|
using PluginUnavailable::PluginUnavailable;
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
@@ -23,9 +23,9 @@
|
|||||||
#include "config/Data.hxx"
|
#include "config/Data.hxx"
|
||||||
#include "util/RuntimeError.hxx"
|
#include "util/RuntimeError.hxx"
|
||||||
|
|
||||||
#include <assert.h>
|
#include <cassert>
|
||||||
#include <stdlib.h>
|
#include <cstdlib>
|
||||||
#include <math.h>
|
#include <cmath>
|
||||||
|
|
||||||
static float
|
static float
|
||||||
ParsePreamp(const char *s)
|
ParsePreamp(const char *s)
|
||||||
@@ -33,14 +33,14 @@ ParsePreamp(const char *s)
|
|||||||
assert(s != nullptr);
|
assert(s != nullptr);
|
||||||
|
|
||||||
char *endptr;
|
char *endptr;
|
||||||
float f = strtod(s, &endptr);
|
float f = std::strtof(s, &endptr);
|
||||||
if (endptr == s || *endptr != '\0')
|
if (endptr == s || *endptr != '\0')
|
||||||
throw std::invalid_argument("Not a numeric value");
|
throw std::invalid_argument("Not a numeric value");
|
||||||
|
|
||||||
if (f < -15 || f > 15)
|
if (f < -15.0f || f > 15.0f)
|
||||||
throw std::invalid_argument("Number must be between -15 and 15");
|
throw std::invalid_argument("Number must be between -15 and 15");
|
||||||
|
|
||||||
return pow(10, f / 20.0);
|
return std::pow(10.0f, f / 20.0f);
|
||||||
}
|
}
|
||||||
|
|
||||||
static float
|
static float
|
||||||
|
@@ -20,7 +20,7 @@
|
|||||||
#include "ReplayGainInfo.hxx"
|
#include "ReplayGainInfo.hxx"
|
||||||
#include "ReplayGainConfig.hxx"
|
#include "ReplayGainConfig.hxx"
|
||||||
|
|
||||||
#include <math.h>
|
#include <cmath>
|
||||||
|
|
||||||
float
|
float
|
||||||
ReplayGainTuple::CalculateScale(const ReplayGainConfig &config) const noexcept
|
ReplayGainTuple::CalculateScale(const ReplayGainConfig &config) const noexcept
|
||||||
@@ -28,13 +28,13 @@ ReplayGainTuple::CalculateScale(const ReplayGainConfig &config) const noexcept
|
|||||||
float scale;
|
float scale;
|
||||||
|
|
||||||
if (IsDefined()) {
|
if (IsDefined()) {
|
||||||
scale = pow(10.0, gain / 20.0);
|
scale = std::pow(10.0f, gain / 20.0f);
|
||||||
scale *= config.preamp;
|
scale *= config.preamp;
|
||||||
if (scale > 15.0)
|
if (scale > 15.0f)
|
||||||
scale = 15.0;
|
scale = 15.0f;
|
||||||
|
|
||||||
if (config.limit && scale * peak > 1.0)
|
if (config.limit && scale * peak > 1.0f)
|
||||||
scale = 1.0 / peak;
|
scale = 1.0f / peak;
|
||||||
} else
|
} else
|
||||||
scale = config.missing_preamp;
|
scale = config.missing_preamp;
|
||||||
|
|
||||||
|
@@ -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"
|
||||||
|
@@ -79,17 +79,22 @@ Song::UpdateFile(Storage &storage) noexcept
|
|||||||
TagBuilder tag_builder;
|
TagBuilder tag_builder;
|
||||||
auto new_audio_format = AudioFormat::Undefined();
|
auto new_audio_format = AudioFormat::Undefined();
|
||||||
|
|
||||||
const auto path_fs = storage.MapFS(relative_uri.c_str());
|
try {
|
||||||
if (path_fs.IsNull()) {
|
const auto path_fs = storage.MapFS(relative_uri.c_str());
|
||||||
const auto absolute_uri =
|
if (path_fs.IsNull()) {
|
||||||
storage.MapUTF8(relative_uri.c_str());
|
const auto absolute_uri =
|
||||||
if (!tag_stream_scan(absolute_uri.c_str(), tag_builder,
|
storage.MapUTF8(relative_uri.c_str());
|
||||||
&new_audio_format))
|
if (!tag_stream_scan(absolute_uri.c_str(), tag_builder,
|
||||||
return false;
|
|
||||||
} else {
|
|
||||||
if (!ScanFileTagsWithGeneric(path_fs, tag_builder,
|
|
||||||
&new_audio_format))
|
&new_audio_format))
|
||||||
return false;
|
return false;
|
||||||
|
} else {
|
||||||
|
if (!ScanFileTagsWithGeneric(path_fs, tag_builder,
|
||||||
|
&new_audio_format))
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
} catch (...) {
|
||||||
|
// TODO: log or propagate I/O errors?
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
mtime = info.mtime;
|
mtime = info.mtime;
|
||||||
@@ -98,8 +103,6 @@ Song::UpdateFile(Storage &storage) noexcept
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#ifdef ENABLE_ARCHIVE
|
#ifdef ENABLE_ARCHIVE
|
||||||
|
|
||||||
Song *
|
Song *
|
||||||
@@ -145,6 +148,8 @@ Song::UpdateFileInArchive(ArchiveFile &archive) noexcept
|
|||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
#endif /* ENABLE_DATABASE */
|
||||||
|
|
||||||
bool
|
bool
|
||||||
DetachedSong::LoadFile(Path path) noexcept
|
DetachedSong::LoadFile(Path path) noexcept
|
||||||
{
|
{
|
||||||
@@ -153,8 +158,14 @@ DetachedSong::LoadFile(Path path) noexcept
|
|||||||
return false;
|
return false;
|
||||||
|
|
||||||
TagBuilder tag_builder;
|
TagBuilder tag_builder;
|
||||||
if (!ScanFileTagsWithGeneric(path, tag_builder))
|
|
||||||
|
try {
|
||||||
|
if (!ScanFileTagsWithGeneric(path, tag_builder))
|
||||||
|
return false;
|
||||||
|
} catch (...) {
|
||||||
|
// TODO: log or propagate I/O errors?
|
||||||
return false;
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
mtime = fi.GetModificationTime();
|
mtime = fi.GetModificationTime();
|
||||||
tag_builder.Commit(tag);
|
tag_builder.Commit(tag);
|
||||||
@@ -173,8 +184,14 @@ DetachedSong::Update() noexcept
|
|||||||
return LoadFile(path_fs);
|
return LoadFile(path_fs);
|
||||||
} else if (IsRemote()) {
|
} else if (IsRemote()) {
|
||||||
TagBuilder tag_builder;
|
TagBuilder tag_builder;
|
||||||
if (!tag_stream_scan(uri.c_str(), tag_builder))
|
|
||||||
|
try {
|
||||||
|
if (!tag_stream_scan(uri.c_str(), tag_builder))
|
||||||
|
return false;
|
||||||
|
} catch (...) {
|
||||||
|
// TODO: log or propagate I/O errors?
|
||||||
return false;
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
mtime = std::chrono::system_clock::time_point::min();
|
mtime = std::chrono::system_clock::time_point::min();
|
||||||
tag_builder.Commit(tag);
|
tag_builder.Commit(tag);
|
||||||
|
@@ -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();
|
||||||
|
@@ -47,11 +47,11 @@ public:
|
|||||||
handler(_handler),
|
handler(_handler),
|
||||||
is(nullptr) {}
|
is(nullptr) {}
|
||||||
|
|
||||||
bool ScanFile(const DecoderPlugin &plugin) noexcept {
|
bool ScanFile(const DecoderPlugin &plugin) {
|
||||||
return plugin.ScanFile(path_fs, handler);
|
return plugin.ScanFile(path_fs, handler);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool ScanStream(const DecoderPlugin &plugin) noexcept {
|
bool ScanStream(const DecoderPlugin &plugin) {
|
||||||
if (plugin.scan_stream == nullptr)
|
if (plugin.scan_stream == nullptr)
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
@@ -73,14 +73,14 @@ public:
|
|||||||
return plugin.ScanStream(*is, handler);
|
return plugin.ScanStream(*is, handler);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool Scan(const DecoderPlugin &plugin) noexcept {
|
bool Scan(const DecoderPlugin &plugin) {
|
||||||
return plugin.SupportsSuffix(suffix) &&
|
return plugin.SupportsSuffix(suffix) &&
|
||||||
(ScanFile(plugin) || ScanStream(plugin));
|
(ScanFile(plugin) || ScanStream(plugin));
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
bool
|
bool
|
||||||
ScanFileTagsNoGeneric(Path path_fs, TagHandler &handler) noexcept
|
ScanFileTagsNoGeneric(Path path_fs, TagHandler &handler)
|
||||||
{
|
{
|
||||||
assert(!path_fs.IsNull());
|
assert(!path_fs.IsNull());
|
||||||
|
|
||||||
@@ -100,7 +100,7 @@ ScanFileTagsNoGeneric(Path path_fs, TagHandler &handler) noexcept
|
|||||||
|
|
||||||
bool
|
bool
|
||||||
ScanFileTagsWithGeneric(Path path, TagBuilder &builder,
|
ScanFileTagsWithGeneric(Path path, TagBuilder &builder,
|
||||||
AudioFormat *audio_format) noexcept
|
AudioFormat *audio_format)
|
||||||
{
|
{
|
||||||
FullTagHandler h(builder, audio_format);
|
FullTagHandler h(builder, audio_format);
|
||||||
|
|
||||||
|
@@ -30,22 +30,26 @@ class TagBuilder;
|
|||||||
* but does not fall back to generic scanners (APE and ID3) if no tags
|
* but does not fall back to generic scanners (APE and ID3) if no tags
|
||||||
* were found (but the file was recognized).
|
* were found (but the file was recognized).
|
||||||
*
|
*
|
||||||
|
* Throws on I/O error.
|
||||||
|
*
|
||||||
* @return true if the file was recognized (even if no metadata was
|
* @return true if the file was recognized (even if no metadata was
|
||||||
* found)
|
* found)
|
||||||
*/
|
*/
|
||||||
bool
|
bool
|
||||||
ScanFileTagsNoGeneric(Path path, TagHandler &handler) noexcept;
|
ScanFileTagsNoGeneric(Path path, TagHandler &handler);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Scan the tags of a song file. Invokes matching decoder plugins,
|
* Scan the tags of a song file. Invokes matching decoder plugins,
|
||||||
* and falls back to generic scanners (APE and ID3) if no tags were
|
* and falls back to generic scanners (APE and ID3) if no tags were
|
||||||
* found (but the file was recognized).
|
* found (but the file was recognized).
|
||||||
*
|
*
|
||||||
|
* Throws on I/O error.
|
||||||
|
*
|
||||||
* @return true if the file was recognized (even if no metadata was
|
* @return true if the file was recognized (even if no metadata was
|
||||||
* found)
|
* found)
|
||||||
*/
|
*/
|
||||||
bool
|
bool
|
||||||
ScanFileTagsWithGeneric(Path path, TagBuilder &builder,
|
ScanFileTagsWithGeneric(Path path, TagBuilder &builder,
|
||||||
AudioFormat *audio_format=nullptr) noexcept;
|
AudioFormat *audio_format=nullptr);
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
@@ -45,7 +45,7 @@ CheckDecoderPlugin(const DecoderPlugin &plugin,
|
|||||||
}
|
}
|
||||||
|
|
||||||
bool
|
bool
|
||||||
tag_stream_scan(InputStream &is, TagHandler &handler) noexcept
|
tag_stream_scan(InputStream &is, TagHandler &handler)
|
||||||
{
|
{
|
||||||
assert(is.IsReady());
|
assert(is.IsReady());
|
||||||
|
|
||||||
@@ -73,19 +73,17 @@ tag_stream_scan(InputStream &is, TagHandler &handler) noexcept
|
|||||||
}
|
}
|
||||||
|
|
||||||
bool
|
bool
|
||||||
tag_stream_scan(const char *uri, TagHandler &handler) noexcept
|
tag_stream_scan(const char *uri, TagHandler &handler)
|
||||||
try {
|
{
|
||||||
Mutex mutex;
|
Mutex mutex;
|
||||||
|
|
||||||
auto is = InputStream::OpenReady(uri, mutex);
|
auto is = InputStream::OpenReady(uri, mutex);
|
||||||
return tag_stream_scan(*is, handler);
|
return tag_stream_scan(*is, handler);
|
||||||
} catch (const std::exception &e) {
|
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bool
|
bool
|
||||||
tag_stream_scan(InputStream &is, TagBuilder &builder,
|
tag_stream_scan(InputStream &is, TagBuilder &builder,
|
||||||
AudioFormat *audio_format) noexcept
|
AudioFormat *audio_format)
|
||||||
{
|
{
|
||||||
assert(is.IsReady());
|
assert(is.IsReady());
|
||||||
|
|
||||||
@@ -102,12 +100,10 @@ tag_stream_scan(InputStream &is, TagBuilder &builder,
|
|||||||
|
|
||||||
bool
|
bool
|
||||||
tag_stream_scan(const char *uri, TagBuilder &builder,
|
tag_stream_scan(const char *uri, TagBuilder &builder,
|
||||||
AudioFormat *audio_format) noexcept
|
AudioFormat *audio_format)
|
||||||
try {
|
{
|
||||||
Mutex mutex;
|
Mutex mutex;
|
||||||
|
|
||||||
auto is = InputStream::OpenReady(uri, mutex);
|
auto is = InputStream::OpenReady(uri, mutex);
|
||||||
return tag_stream_scan(*is, builder, audio_format);
|
return tag_stream_scan(*is, builder, audio_format);
|
||||||
} catch (const std::exception &e) {
|
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
|
@@ -29,29 +29,39 @@ class TagBuilder;
|
|||||||
* Scan the tags of an #InputStream. Invokes matching decoder
|
* Scan the tags of an #InputStream. Invokes matching decoder
|
||||||
* plugins, but does not invoke the special "APE" and "ID3" scanners.
|
* plugins, but does not invoke the special "APE" and "ID3" scanners.
|
||||||
*
|
*
|
||||||
|
* Throws on I/O error.
|
||||||
|
*
|
||||||
* @return true if the file was recognized (even if no metadata was
|
* @return true if the file was recognized (even if no metadata was
|
||||||
* found)
|
* found)
|
||||||
*/
|
*/
|
||||||
bool
|
bool
|
||||||
tag_stream_scan(InputStream &is, TagHandler &handler) noexcept;
|
tag_stream_scan(InputStream &is, TagHandler &handler);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Throws on I/O error.
|
||||||
|
*/
|
||||||
bool
|
bool
|
||||||
tag_stream_scan(const char *uri, TagHandler &handler) noexcept;
|
tag_stream_scan(const char *uri, TagHandler &handler);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Scan the tags of an #InputStream. Invokes matching decoder
|
* Scan the tags of an #InputStream. Invokes matching decoder
|
||||||
* plugins, and falls back to generic scanners (APE and ID3) if no
|
* plugins, and falls back to generic scanners (APE and ID3) if no
|
||||||
* tags were found (but the file was recognized).
|
* tags were found (but the file was recognized).
|
||||||
*
|
*
|
||||||
|
* Throws on I/O error.
|
||||||
|
*
|
||||||
* @return true if the file was recognized (even if no metadata was
|
* @return true if the file was recognized (even if no metadata was
|
||||||
* found)
|
* found)
|
||||||
*/
|
*/
|
||||||
bool
|
bool
|
||||||
tag_stream_scan(InputStream &is, TagBuilder &builder,
|
tag_stream_scan(InputStream &is, TagBuilder &builder,
|
||||||
AudioFormat *audio_format=nullptr) noexcept;
|
AudioFormat *audio_format=nullptr);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Throws on I/O error.
|
||||||
|
*/
|
||||||
bool
|
bool
|
||||||
tag_stream_scan(const char *uri, TagBuilder &builder,
|
tag_stream_scan(const char *uri, TagBuilder &builder,
|
||||||
AudioFormat *audio_format=nullptr) noexcept;
|
AudioFormat *audio_format=nullptr);
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
@@ -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;
|
||||||
};
|
};
|
||||||
|
40
src/apple/AudioObject.cxx
Normal file
40
src/apple/AudioObject.cxx
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2020 Max Kellermann <max.kellermann@gmail.com>
|
||||||
|
*
|
||||||
|
* Redistribution and use in source and binary forms, with or without
|
||||||
|
* modification, are permitted provided that the following conditions
|
||||||
|
* are met:
|
||||||
|
*
|
||||||
|
* - Redistributions of source code must retain the above copyright
|
||||||
|
* notice, this list of conditions and the following disclaimer.
|
||||||
|
*
|
||||||
|
* - Redistributions in binary form must reproduce the above copyright
|
||||||
|
* notice, this list of conditions and the following disclaimer in the
|
||||||
|
* documentation and/or other materials provided with the
|
||||||
|
* distribution.
|
||||||
|
*
|
||||||
|
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||||
|
* ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||||
|
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
|
||||||
|
* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
|
||||||
|
* FOUNDATION OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
|
||||||
|
* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||||
|
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||||
|
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
|
||||||
|
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
|
||||||
|
* STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
||||||
|
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
|
||||||
|
* OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "AudioObject.hxx"
|
||||||
|
#include "StringRef.hxx"
|
||||||
|
|
||||||
|
Apple::StringRef
|
||||||
|
AudioObjectGetStringProperty(AudioObjectID inObjectID,
|
||||||
|
const AudioObjectPropertyAddress &inAddress)
|
||||||
|
{
|
||||||
|
auto s = AudioObjectGetPropertyDataT<CFStringRef>(inObjectID,
|
||||||
|
inAddress);
|
||||||
|
return Apple::StringRef(s);
|
||||||
|
}
|
105
src/apple/AudioObject.hxx
Normal file
105
src/apple/AudioObject.hxx
Normal file
@@ -0,0 +1,105 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2020 Max Kellermann <max.kellermann@gmail.com>
|
||||||
|
*
|
||||||
|
* Redistribution and use in source and binary forms, with or without
|
||||||
|
* modification, are permitted provided that the following conditions
|
||||||
|
* are met:
|
||||||
|
*
|
||||||
|
* - Redistributions of source code must retain the above copyright
|
||||||
|
* notice, this list of conditions and the following disclaimer.
|
||||||
|
*
|
||||||
|
* - Redistributions in binary form must reproduce the above copyright
|
||||||
|
* notice, this list of conditions and the following disclaimer in the
|
||||||
|
* documentation and/or other materials provided with the
|
||||||
|
* distribution.
|
||||||
|
*
|
||||||
|
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||||
|
* ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||||
|
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
|
||||||
|
* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
|
||||||
|
* FOUNDATION OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
|
||||||
|
* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||||
|
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||||
|
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
|
||||||
|
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
|
||||||
|
* STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
||||||
|
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
|
||||||
|
* OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef APPLE_AUDIO_OBJECT_HXX
|
||||||
|
#define APPLE_AUDIO_OBJECT_HXX
|
||||||
|
|
||||||
|
#include "Throw.hxx"
|
||||||
|
#include "util/AllocatedArray.hxx"
|
||||||
|
|
||||||
|
#include <CoreAudio/AudioHardware.h>
|
||||||
|
|
||||||
|
#include <cstddef>
|
||||||
|
|
||||||
|
namespace Apple {
|
||||||
|
class StringRef;
|
||||||
|
}
|
||||||
|
|
||||||
|
inline std::size_t
|
||||||
|
AudioObjectGetPropertyDataSize(AudioObjectID inObjectID,
|
||||||
|
const AudioObjectPropertyAddress &inAddress)
|
||||||
|
{
|
||||||
|
UInt32 size;
|
||||||
|
OSStatus status = AudioObjectGetPropertyDataSize(inObjectID,
|
||||||
|
&inAddress,
|
||||||
|
0, nullptr, &size);
|
||||||
|
if (status != noErr)
|
||||||
|
Apple::ThrowOSStatus(status);
|
||||||
|
|
||||||
|
return size;
|
||||||
|
}
|
||||||
|
|
||||||
|
template<typename T>
|
||||||
|
T
|
||||||
|
AudioObjectGetPropertyDataT(AudioObjectID inObjectID,
|
||||||
|
const AudioObjectPropertyAddress &inAddress)
|
||||||
|
{
|
||||||
|
OSStatus status;
|
||||||
|
UInt32 size = sizeof(T);
|
||||||
|
T value;
|
||||||
|
|
||||||
|
status = AudioObjectGetPropertyData(inObjectID, &inAddress,
|
||||||
|
0, nullptr,
|
||||||
|
&size, &value);
|
||||||
|
if (status != noErr)
|
||||||
|
Apple::ThrowOSStatus(status);
|
||||||
|
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
Apple::StringRef
|
||||||
|
AudioObjectGetStringProperty(AudioObjectID inObjectID,
|
||||||
|
const AudioObjectPropertyAddress &inAddress);
|
||||||
|
|
||||||
|
template<typename T>
|
||||||
|
AllocatedArray<T>
|
||||||
|
AudioObjectGetPropertyDataArray(AudioObjectID inObjectID,
|
||||||
|
const AudioObjectPropertyAddress &inAddress)
|
||||||
|
{
|
||||||
|
OSStatus status;
|
||||||
|
UInt32 size;
|
||||||
|
|
||||||
|
status = AudioObjectGetPropertyDataSize(inObjectID,
|
||||||
|
&inAddress,
|
||||||
|
0, nullptr, &size);
|
||||||
|
if (status != noErr)
|
||||||
|
Apple::ThrowOSStatus(status);
|
||||||
|
|
||||||
|
AllocatedArray<T> result(size / sizeof(T));
|
||||||
|
|
||||||
|
status = AudioObjectGetPropertyData(inObjectID, &inAddress,
|
||||||
|
0, nullptr,
|
||||||
|
&size, result.begin());
|
||||||
|
if (status != noErr)
|
||||||
|
Apple::ThrowOSStatus(status);
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
111
src/apple/AudioUnit.hxx
Normal file
111
src/apple/AudioUnit.hxx
Normal file
@@ -0,0 +1,111 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2020 Max Kellermann <max.kellermann@gmail.com>
|
||||||
|
*
|
||||||
|
* Redistribution and use in source and binary forms, with or without
|
||||||
|
* modification, are permitted provided that the following conditions
|
||||||
|
* are met:
|
||||||
|
*
|
||||||
|
* - Redistributions of source code must retain the above copyright
|
||||||
|
* notice, this list of conditions and the following disclaimer.
|
||||||
|
*
|
||||||
|
* - Redistributions in binary form must reproduce the above copyright
|
||||||
|
* notice, this list of conditions and the following disclaimer in the
|
||||||
|
* documentation and/or other materials provided with the
|
||||||
|
* distribution.
|
||||||
|
*
|
||||||
|
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||||
|
* ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||||
|
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
|
||||||
|
* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
|
||||||
|
* FOUNDATION OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
|
||||||
|
* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||||
|
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||||
|
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
|
||||||
|
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
|
||||||
|
* STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
||||||
|
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
|
||||||
|
* OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef APPLE_AUDIO_UNIT_HXX
|
||||||
|
#define APPLE_AUDIO_UNIT_HXX
|
||||||
|
|
||||||
|
#include "Throw.hxx"
|
||||||
|
|
||||||
|
#include <AudioUnit/AudioUnit.h>
|
||||||
|
|
||||||
|
template<typename T>
|
||||||
|
T
|
||||||
|
AudioUnitGetPropertyT(AudioUnit inUnit, AudioUnitPropertyID inID,
|
||||||
|
AudioUnitScope inScope,
|
||||||
|
AudioUnitElement inElement)
|
||||||
|
{
|
||||||
|
UInt32 size = sizeof(T);
|
||||||
|
T value;
|
||||||
|
|
||||||
|
OSStatus status = AudioUnitGetProperty(inUnit, inID, inScope,
|
||||||
|
inElement,
|
||||||
|
&value, &size);
|
||||||
|
if (status != noErr)
|
||||||
|
Apple::ThrowOSStatus(status);
|
||||||
|
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
template<typename T>
|
||||||
|
void
|
||||||
|
AudioUnitSetPropertyT(AudioUnit inUnit, AudioUnitPropertyID inID,
|
||||||
|
AudioUnitScope inScope,
|
||||||
|
AudioUnitElement inElement,
|
||||||
|
const T &value)
|
||||||
|
{
|
||||||
|
OSStatus status = AudioUnitSetProperty(inUnit, inID, inScope,
|
||||||
|
inElement,
|
||||||
|
&value, sizeof(value));
|
||||||
|
if (status != noErr)
|
||||||
|
Apple::ThrowOSStatus(status);
|
||||||
|
}
|
||||||
|
|
||||||
|
inline void
|
||||||
|
AudioUnitSetCurrentDevice(AudioUnit inUnit, const AudioDeviceID &value)
|
||||||
|
{
|
||||||
|
AudioUnitSetPropertyT(inUnit, kAudioOutputUnitProperty_CurrentDevice,
|
||||||
|
kAudioUnitScope_Global, 0,
|
||||||
|
value);
|
||||||
|
}
|
||||||
|
|
||||||
|
inline void
|
||||||
|
AudioUnitSetInputStreamFormat(AudioUnit inUnit,
|
||||||
|
const AudioStreamBasicDescription &value)
|
||||||
|
{
|
||||||
|
AudioUnitSetPropertyT(inUnit, kAudioUnitProperty_StreamFormat,
|
||||||
|
kAudioUnitScope_Input, 0,
|
||||||
|
value);
|
||||||
|
}
|
||||||
|
|
||||||
|
inline void
|
||||||
|
AudioUnitSetInputRenderCallback(AudioUnit inUnit,
|
||||||
|
const AURenderCallbackStruct &value)
|
||||||
|
{
|
||||||
|
AudioUnitSetPropertyT(inUnit, kAudioUnitProperty_SetRenderCallback,
|
||||||
|
kAudioUnitScope_Input, 0,
|
||||||
|
value);
|
||||||
|
}
|
||||||
|
|
||||||
|
inline UInt32
|
||||||
|
AudioUnitGetBufferFrameSize(AudioUnit inUnit)
|
||||||
|
{
|
||||||
|
return AudioUnitGetPropertyT<UInt32>(inUnit,
|
||||||
|
kAudioDevicePropertyBufferFrameSize,
|
||||||
|
kAudioUnitScope_Global, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
inline void
|
||||||
|
AudioUnitSetBufferFrameSize(AudioUnit inUnit, const UInt32 &value)
|
||||||
|
{
|
||||||
|
AudioUnitSetPropertyT(inUnit, kAudioDevicePropertyBufferFrameSize,
|
||||||
|
kAudioUnitScope_Global, 0,
|
||||||
|
value);
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
75
src/apple/ErrorRef.hxx
Normal file
75
src/apple/ErrorRef.hxx
Normal file
@@ -0,0 +1,75 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2020 Max Kellermann <max.kellermann@gmail.com>
|
||||||
|
*
|
||||||
|
* Redistribution and use in source and binary forms, with or without
|
||||||
|
* modification, are permitted provided that the following conditions
|
||||||
|
* are met:
|
||||||
|
*
|
||||||
|
* - Redistributions of source code must retain the above copyright
|
||||||
|
* notice, this list of conditions and the following disclaimer.
|
||||||
|
*
|
||||||
|
* - Redistributions in binary form must reproduce the above copyright
|
||||||
|
* notice, this list of conditions and the following disclaimer in the
|
||||||
|
* documentation and/or other materials provided with the
|
||||||
|
* distribution.
|
||||||
|
*
|
||||||
|
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||||
|
* ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||||
|
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
|
||||||
|
* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
|
||||||
|
* FOUNDATION OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
|
||||||
|
* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||||
|
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||||
|
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
|
||||||
|
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
|
||||||
|
* STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
||||||
|
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
|
||||||
|
* OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef APPLE_ERROR_REF_HXX
|
||||||
|
#define APPLE_ERROR_REF_HXX
|
||||||
|
|
||||||
|
#include <CoreFoundation/CFError.h>
|
||||||
|
|
||||||
|
#include <utility>
|
||||||
|
|
||||||
|
namespace Apple {
|
||||||
|
|
||||||
|
class ErrorRef {
|
||||||
|
CFErrorRef ref = nullptr;
|
||||||
|
|
||||||
|
public:
|
||||||
|
explicit ErrorRef(CFErrorRef _ref) noexcept
|
||||||
|
:ref(_ref) {}
|
||||||
|
|
||||||
|
ErrorRef(CFAllocatorRef allocator, CFErrorDomain domain,
|
||||||
|
CFIndex code, CFDictionaryRef userInfo) noexcept
|
||||||
|
:ref(CFErrorCreate(allocator, domain, code, userInfo)) {}
|
||||||
|
|
||||||
|
ErrorRef(ErrorRef &&src) noexcept
|
||||||
|
:ref(std::exchange(src.ref, nullptr)) {}
|
||||||
|
|
||||||
|
~ErrorRef() noexcept {
|
||||||
|
if (ref)
|
||||||
|
CFRelease(ref);
|
||||||
|
}
|
||||||
|
|
||||||
|
ErrorRef &operator=(ErrorRef &&src) noexcept {
|
||||||
|
using std::swap;
|
||||||
|
swap(ref, src.ref);
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
operator bool() const noexcept {
|
||||||
|
return ref != nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
CFStringRef CopyDescription() const noexcept {
|
||||||
|
return CFErrorCopyDescription(ref);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace Apple
|
||||||
|
|
||||||
|
#endif
|
73
src/apple/StringRef.hxx
Normal file
73
src/apple/StringRef.hxx
Normal file
@@ -0,0 +1,73 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2020 Max Kellermann <max.kellermann@gmail.com>
|
||||||
|
*
|
||||||
|
* Redistribution and use in source and binary forms, with or without
|
||||||
|
* modification, are permitted provided that the following conditions
|
||||||
|
* are met:
|
||||||
|
*
|
||||||
|
* - Redistributions of source code must retain the above copyright
|
||||||
|
* notice, this list of conditions and the following disclaimer.
|
||||||
|
*
|
||||||
|
* - Redistributions in binary form must reproduce the above copyright
|
||||||
|
* notice, this list of conditions and the following disclaimer in the
|
||||||
|
* documentation and/or other materials provided with the
|
||||||
|
* distribution.
|
||||||
|
*
|
||||||
|
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||||
|
* ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||||
|
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
|
||||||
|
* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
|
||||||
|
* FOUNDATION OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
|
||||||
|
* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||||
|
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||||
|
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
|
||||||
|
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
|
||||||
|
* STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
||||||
|
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
|
||||||
|
* OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef APPLE_STRING_REF_HXX
|
||||||
|
#define APPLE_STRING_REF_HXX
|
||||||
|
|
||||||
|
#include <CoreFoundation/CFString.h>
|
||||||
|
|
||||||
|
#include <utility>
|
||||||
|
|
||||||
|
namespace Apple {
|
||||||
|
|
||||||
|
class StringRef {
|
||||||
|
CFStringRef ref = nullptr;
|
||||||
|
|
||||||
|
public:
|
||||||
|
explicit StringRef(CFStringRef _ref) noexcept
|
||||||
|
:ref(_ref) {}
|
||||||
|
|
||||||
|
StringRef(StringRef &&src) noexcept
|
||||||
|
:ref(std::exchange(src.ref, nullptr)) {}
|
||||||
|
|
||||||
|
~StringRef() noexcept {
|
||||||
|
if (ref)
|
||||||
|
CFRelease(ref);
|
||||||
|
}
|
||||||
|
|
||||||
|
StringRef &operator=(StringRef &&src) noexcept {
|
||||||
|
using std::swap;
|
||||||
|
swap(ref, src.ref);
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
operator bool() const noexcept {
|
||||||
|
return ref != nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool GetCString(char *buffer, std::size_t size,
|
||||||
|
CFStringEncoding encoding=kCFStringEncodingUTF8) const noexcept
|
||||||
|
{
|
||||||
|
return CFStringGetCString(ref, buffer, size, encoding);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace Apple
|
||||||
|
|
||||||
|
#endif
|
67
src/apple/Throw.cxx
Normal file
67
src/apple/Throw.cxx
Normal file
@@ -0,0 +1,67 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2020 Max Kellermann <max.kellermann@gmail.com>
|
||||||
|
*
|
||||||
|
* Redistribution and use in source and binary forms, with or without
|
||||||
|
* modification, are permitted provided that the following conditions
|
||||||
|
* are met:
|
||||||
|
*
|
||||||
|
* - Redistributions of source code must retain the above copyright
|
||||||
|
* notice, this list of conditions and the following disclaimer.
|
||||||
|
*
|
||||||
|
* - Redistributions in binary form must reproduce the above copyright
|
||||||
|
* notice, this list of conditions and the following disclaimer in the
|
||||||
|
* documentation and/or other materials provided with the
|
||||||
|
* distribution.
|
||||||
|
*
|
||||||
|
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||||
|
* ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||||
|
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
|
||||||
|
* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
|
||||||
|
* FOUNDATION OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
|
||||||
|
* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||||
|
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||||
|
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
|
||||||
|
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
|
||||||
|
* STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
||||||
|
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
|
||||||
|
* OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "Throw.hxx"
|
||||||
|
#include "ErrorRef.hxx"
|
||||||
|
#include "StringRef.hxx"
|
||||||
|
|
||||||
|
#include <stdexcept>
|
||||||
|
|
||||||
|
namespace Apple {
|
||||||
|
|
||||||
|
void
|
||||||
|
ThrowOSStatus(OSStatus status)
|
||||||
|
{
|
||||||
|
const Apple::ErrorRef cferr(nullptr, kCFErrorDomainOSStatus,
|
||||||
|
status, nullptr);
|
||||||
|
const Apple::StringRef cfstr(cferr.CopyDescription());
|
||||||
|
|
||||||
|
char msg[1024];
|
||||||
|
if (!cfstr.GetCString(msg, sizeof(msg)))
|
||||||
|
throw std::runtime_error("Unknown OSStatus");
|
||||||
|
|
||||||
|
throw std::runtime_error(msg);
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
ThrowOSStatus(OSStatus status, const char *_msg)
|
||||||
|
{
|
||||||
|
const Apple::ErrorRef cferr(nullptr, kCFErrorDomainOSStatus,
|
||||||
|
status, nullptr);
|
||||||
|
const Apple::StringRef cfstr(cferr.CopyDescription());
|
||||||
|
|
||||||
|
char msg[1024];
|
||||||
|
strcpy(msg, _msg);
|
||||||
|
size_t length = strlen(msg);
|
||||||
|
|
||||||
|
cfstr.GetCString(msg + length, sizeof(msg) - length);
|
||||||
|
throw std::runtime_error(msg);
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace Apple
|
45
src/apple/Throw.hxx
Normal file
45
src/apple/Throw.hxx
Normal file
@@ -0,0 +1,45 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2020 Max Kellermann <max.kellermann@gmail.com>
|
||||||
|
*
|
||||||
|
* Redistribution and use in source and binary forms, with or without
|
||||||
|
* modification, are permitted provided that the following conditions
|
||||||
|
* are met:
|
||||||
|
*
|
||||||
|
* - Redistributions of source code must retain the above copyright
|
||||||
|
* notice, this list of conditions and the following disclaimer.
|
||||||
|
*
|
||||||
|
* - Redistributions in binary form must reproduce the above copyright
|
||||||
|
* notice, this list of conditions and the following disclaimer in the
|
||||||
|
* documentation and/or other materials provided with the
|
||||||
|
* distribution.
|
||||||
|
*
|
||||||
|
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||||
|
* ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||||
|
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
|
||||||
|
* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
|
||||||
|
* FOUNDATION OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
|
||||||
|
* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||||
|
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||||
|
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
|
||||||
|
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
|
||||||
|
* STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
||||||
|
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
|
||||||
|
* OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef APPLE_THROW_HXX
|
||||||
|
#define APPLE_THROW_HXX
|
||||||
|
|
||||||
|
#include <CoreFoundation/CFBase.h>
|
||||||
|
|
||||||
|
namespace Apple {
|
||||||
|
|
||||||
|
void
|
||||||
|
ThrowOSStatus(OSStatus status);
|
||||||
|
|
||||||
|
void
|
||||||
|
ThrowOSStatus(OSStatus status, const char *msg);
|
||||||
|
|
||||||
|
} // namespace Apple
|
||||||
|
|
||||||
|
#endif
|
28
src/apple/meson.build
Normal file
28
src/apple/meson.build
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
if not is_darwin
|
||||||
|
apple_dep = dependency('', required: false)
|
||||||
|
subdir_done()
|
||||||
|
endif
|
||||||
|
|
||||||
|
audiounit_dep = declare_dependency(
|
||||||
|
link_args: ['-framework', 'AudioUnit', '-framework', 'CoreAudio', '-framework', 'CoreServices'],
|
||||||
|
dependencies: [
|
||||||
|
boost_dep,
|
||||||
|
],
|
||||||
|
)
|
||||||
|
|
||||||
|
apple = static_library(
|
||||||
|
'apple',
|
||||||
|
'AudioObject.cxx',
|
||||||
|
'Throw.cxx',
|
||||||
|
include_directories: inc,
|
||||||
|
dependencies: [
|
||||||
|
audiounit_dep,
|
||||||
|
],
|
||||||
|
)
|
||||||
|
|
||||||
|
apple_dep = declare_dependency(
|
||||||
|
link_with: apple,
|
||||||
|
dependencies: [
|
||||||
|
audiounit_dep,
|
||||||
|
],
|
||||||
|
)
|
@@ -58,9 +58,9 @@ public:
|
|||||||
class Bzip2InputStream final : public InputStream {
|
class Bzip2InputStream final : public InputStream {
|
||||||
std::shared_ptr<InputStream> input;
|
std::shared_ptr<InputStream> input;
|
||||||
|
|
||||||
bool eof = false;
|
bz_stream bzstream{};
|
||||||
|
|
||||||
bz_stream bzstream;
|
bool eof = false;
|
||||||
|
|
||||||
char buffer[5000];
|
char buffer[5000];
|
||||||
|
|
||||||
@@ -68,7 +68,7 @@ public:
|
|||||||
Bzip2InputStream(const std::shared_ptr<InputStream> &_input,
|
Bzip2InputStream(const std::shared_ptr<InputStream> &_input,
|
||||||
const char *uri,
|
const char *uri,
|
||||||
Mutex &mutex);
|
Mutex &mutex);
|
||||||
~Bzip2InputStream();
|
~Bzip2InputStream() noexcept override;
|
||||||
|
|
||||||
/* virtual methods from InputStream */
|
/* virtual methods from InputStream */
|
||||||
bool IsEOF() noexcept override;
|
bool IsEOF() noexcept override;
|
||||||
@@ -79,25 +79,6 @@ private:
|
|||||||
bool FillBuffer();
|
bool FillBuffer();
|
||||||
};
|
};
|
||||||
|
|
||||||
/* single archive handling allocation helpers */
|
|
||||||
|
|
||||||
inline void
|
|
||||||
Bzip2InputStream::Open()
|
|
||||||
{
|
|
||||||
bzstream.bzalloc = nullptr;
|
|
||||||
bzstream.bzfree = nullptr;
|
|
||||||
bzstream.opaque = nullptr;
|
|
||||||
|
|
||||||
bzstream.next_in = (char *)buffer;
|
|
||||||
bzstream.avail_in = 0;
|
|
||||||
|
|
||||||
int ret = BZ2_bzDecompressInit(&bzstream, 0, 0);
|
|
||||||
if (ret != BZ_OK)
|
|
||||||
throw std::runtime_error("BZ2_bzDecompressInit() has failed");
|
|
||||||
|
|
||||||
SetReady();
|
|
||||||
}
|
|
||||||
|
|
||||||
/* archive open && listing routine */
|
/* archive open && listing routine */
|
||||||
|
|
||||||
static std::unique_ptr<ArchiveFile>
|
static std::unique_ptr<ArchiveFile>
|
||||||
@@ -116,10 +97,16 @@ Bzip2InputStream::Bzip2InputStream(const std::shared_ptr<InputStream> &_input,
|
|||||||
:InputStream(_uri, _mutex),
|
:InputStream(_uri, _mutex),
|
||||||
input(_input)
|
input(_input)
|
||||||
{
|
{
|
||||||
Open();
|
bzstream.next_in = (char *)buffer;
|
||||||
|
|
||||||
|
int ret = BZ2_bzDecompressInit(&bzstream, 0, 0);
|
||||||
|
if (ret != BZ_OK)
|
||||||
|
throw std::runtime_error("BZ2_bzDecompressInit() has failed");
|
||||||
|
|
||||||
|
SetReady();
|
||||||
}
|
}
|
||||||
|
|
||||||
Bzip2InputStream::~Bzip2InputStream()
|
Bzip2InputStream::~Bzip2InputStream() noexcept
|
||||||
{
|
{
|
||||||
BZ2_bzDecompressEnd(&bzstream);
|
BZ2_bzDecompressEnd(&bzstream);
|
||||||
}
|
}
|
||||||
@@ -149,22 +136,18 @@ Bzip2InputStream::FillBuffer()
|
|||||||
size_t
|
size_t
|
||||||
Bzip2InputStream::Read(void *ptr, size_t length)
|
Bzip2InputStream::Read(void *ptr, size_t length)
|
||||||
{
|
{
|
||||||
const ScopeUnlock unlock(mutex);
|
|
||||||
|
|
||||||
int bz_result;
|
|
||||||
size_t nbytes = 0;
|
|
||||||
|
|
||||||
if (eof)
|
if (eof)
|
||||||
return 0;
|
return 0;
|
||||||
|
|
||||||
|
const ScopeUnlock unlock(mutex);
|
||||||
|
|
||||||
bzstream.next_out = (char *)ptr;
|
bzstream.next_out = (char *)ptr;
|
||||||
bzstream.avail_out = length;
|
bzstream.avail_out = length;
|
||||||
|
|
||||||
do {
|
do {
|
||||||
if (!FillBuffer())
|
const bool had_input = FillBuffer();
|
||||||
return 0;
|
|
||||||
|
|
||||||
bz_result = BZ2_bzDecompress(&bzstream);
|
const int bz_result = BZ2_bzDecompress(&bzstream);
|
||||||
|
|
||||||
if (bz_result == BZ_STREAM_END) {
|
if (bz_result == BZ_STREAM_END) {
|
||||||
eof = true;
|
eof = true;
|
||||||
@@ -173,9 +156,12 @@ Bzip2InputStream::Read(void *ptr, size_t length)
|
|||||||
|
|
||||||
if (bz_result != BZ_OK)
|
if (bz_result != BZ_OK)
|
||||||
throw std::runtime_error("BZ2_bzDecompress() has failed");
|
throw std::runtime_error("BZ2_bzDecompress() has failed");
|
||||||
|
|
||||||
|
if (!had_input && bzstream.avail_out == length)
|
||||||
|
throw std::runtime_error("Unexpected end of bzip2 file");
|
||||||
} while (bzstream.avail_out == length);
|
} while (bzstream.avail_out == length);
|
||||||
|
|
||||||
nbytes = length - bzstream.avail_out;
|
const size_t nbytes = length - bzstream.avail_out;
|
||||||
offset += nbytes;
|
offset += nbytes;
|
||||||
|
|
||||||
return nbytes;
|
return nbytes;
|
||||||
|
@@ -28,14 +28,16 @@
|
|||||||
#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 "util/WritableBuffer.hxx"
|
||||||
|
|
||||||
#include <cdio/iso9660.h>
|
#include <cdio/iso9660.h>
|
||||||
|
|
||||||
|
#include <array>
|
||||||
|
|
||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
|
|
||||||
#define CEILING(x, y) ((x+(y-1))/y)
|
|
||||||
|
|
||||||
struct Iso9660 {
|
struct Iso9660 {
|
||||||
iso9660_t *const iso;
|
iso9660_t *const iso;
|
||||||
|
|
||||||
@@ -93,7 +95,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);
|
||||||
@@ -138,26 +143,86 @@ Iso9660ArchiveFile::Visit(ArchiveVisitor &visitor)
|
|||||||
class Iso9660InputStream final : public InputStream {
|
class Iso9660InputStream final : public InputStream {
|
||||||
std::shared_ptr<Iso9660> iso;
|
std::shared_ptr<Iso9660> iso;
|
||||||
|
|
||||||
iso9660_stat_t *statbuf;
|
const lsn_t lsn;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* libiso9660 can only read whole sectors at a time, and this
|
||||||
|
* buffer is used to store one whole sector and allow Read()
|
||||||
|
* to handle partial sector reads.
|
||||||
|
*/
|
||||||
|
class BlockBuffer {
|
||||||
|
size_t position = 0, fill = 0;
|
||||||
|
|
||||||
|
std::array<uint8_t, ISO_BLOCKSIZE> data;
|
||||||
|
|
||||||
|
public:
|
||||||
|
ConstBuffer<uint8_t> Read() const noexcept {
|
||||||
|
assert(fill <= data.size());
|
||||||
|
assert(position <= fill);
|
||||||
|
|
||||||
|
return {&data[position], &data[fill]};
|
||||||
|
}
|
||||||
|
|
||||||
|
void Consume(size_t nbytes) noexcept {
|
||||||
|
assert(nbytes <= Read().size);
|
||||||
|
|
||||||
|
position += nbytes;
|
||||||
|
}
|
||||||
|
|
||||||
|
WritableBuffer<uint8_t> Write() noexcept {
|
||||||
|
assert(Read().empty());
|
||||||
|
|
||||||
|
return {data.data(), data.size()};
|
||||||
|
}
|
||||||
|
|
||||||
|
void Append(size_t nbytes) noexcept {
|
||||||
|
assert(Read().empty());
|
||||||
|
assert(nbytes <= data.size());
|
||||||
|
|
||||||
|
fill = nbytes;
|
||||||
|
position = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Clear() noexcept {
|
||||||
|
position = fill = 0;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
BlockBuffer buffer;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Skip this number of bytes of the first sector after filling
|
||||||
|
* the buffer next time. This is used for seeking into the
|
||||||
|
* middle of a sector.
|
||||||
|
*/
|
||||||
|
size_t skip = 0;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
Iso9660InputStream(const std::shared_ptr<Iso9660> &_iso,
|
Iso9660InputStream(const std::shared_ptr<Iso9660> &_iso,
|
||||||
const char *_uri,
|
const char *_uri,
|
||||||
Mutex &_mutex,
|
Mutex &_mutex,
|
||||||
iso9660_stat_t *_statbuf)
|
lsn_t _lsn, offset_type _size)
|
||||||
:InputStream(_uri, _mutex),
|
:InputStream(_uri, _mutex),
|
||||||
iso(_iso), statbuf(_statbuf) {
|
iso(_iso),
|
||||||
size = statbuf->size;
|
lsn(_lsn)
|
||||||
|
{
|
||||||
|
size = _size;
|
||||||
|
seekable = true;
|
||||||
SetReady();
|
SetReady();
|
||||||
}
|
}
|
||||||
|
|
||||||
~Iso9660InputStream() {
|
|
||||||
free(statbuf);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* virtual methods from InputStream */
|
/* virtual methods from InputStream */
|
||||||
bool IsEOF() noexcept override;
|
bool IsEOF() noexcept override;
|
||||||
size_t Read(void *ptr, size_t size) override;
|
size_t Read(void *ptr, size_t size) override;
|
||||||
|
|
||||||
|
void Seek(offset_type new_offset) override {
|
||||||
|
if (new_offset > size)
|
||||||
|
throw std::runtime_error("Invalid seek offset");
|
||||||
|
|
||||||
|
skip = new_offset % ISO_BLOCKSIZE;
|
||||||
|
offset = new_offset - skip;
|
||||||
|
buffer.Clear();
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
InputStreamPtr
|
InputStreamPtr
|
||||||
@@ -169,42 +234,78 @@ Iso9660ArchiveFile::OpenStream(const char *pathname,
|
|||||||
throw FormatRuntimeError("not found in the ISO file: %s",
|
throw FormatRuntimeError("not found in the ISO file: %s",
|
||||||
pathname);
|
pathname);
|
||||||
|
|
||||||
|
const lsn_t lsn = statbuf->lsn;
|
||||||
|
const offset_type size = statbuf->size;
|
||||||
|
free(statbuf);
|
||||||
|
|
||||||
return std::make_unique<Iso9660InputStream>(iso, pathname, mutex,
|
return std::make_unique<Iso9660InputStream>(iso, pathname, mutex,
|
||||||
statbuf);
|
lsn, size);
|
||||||
}
|
}
|
||||||
|
|
||||||
size_t
|
size_t
|
||||||
Iso9660InputStream::Read(void *ptr, size_t read_size)
|
Iso9660InputStream::Read(void *ptr, size_t read_size)
|
||||||
{
|
{
|
||||||
const ScopeUnlock unlock(mutex);
|
const offset_type remaining = size - offset;
|
||||||
|
if (remaining == 0)
|
||||||
int readed = 0;
|
|
||||||
int no_blocks, cur_block;
|
|
||||||
size_t left_bytes = statbuf->size - offset;
|
|
||||||
|
|
||||||
if (left_bytes < read_size) {
|
|
||||||
no_blocks = CEILING(left_bytes, ISO_BLOCKSIZE);
|
|
||||||
} else {
|
|
||||||
no_blocks = read_size / ISO_BLOCKSIZE;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (no_blocks == 0)
|
|
||||||
return 0;
|
return 0;
|
||||||
|
|
||||||
cur_block = offset / ISO_BLOCKSIZE;
|
if (offset_type(read_size) > remaining)
|
||||||
|
read_size = remaining;
|
||||||
|
|
||||||
readed = iso->SeekRead(ptr, statbuf->lsn + cur_block, no_blocks);
|
auto r = buffer.Read();
|
||||||
|
|
||||||
if (readed != no_blocks * ISO_BLOCKSIZE)
|
if (r.empty()) {
|
||||||
throw FormatRuntimeError("error reading ISO file at lsn %lu",
|
/* the buffer is empty - read more data from the ISO file */
|
||||||
(unsigned long)cur_block);
|
|
||||||
|
|
||||||
if (left_bytes < read_size) {
|
assert(offset % ISO_BLOCKSIZE == 0);
|
||||||
readed = left_bytes;
|
|
||||||
|
const ScopeUnlock unlock(mutex);
|
||||||
|
|
||||||
|
const lsn_t read_lsn = lsn + offset / ISO_BLOCKSIZE;
|
||||||
|
|
||||||
|
if (read_size >= ISO_BLOCKSIZE) {
|
||||||
|
/* big read - read right into the caller's buffer */
|
||||||
|
|
||||||
|
auto nbytes = iso->SeekRead(ptr, read_lsn,
|
||||||
|
read_size / ISO_BLOCKSIZE);
|
||||||
|
if (nbytes <= 0)
|
||||||
|
throw std::runtime_error("Failed to read ISO9660 file");
|
||||||
|
|
||||||
|
offset += nbytes;
|
||||||
|
return nbytes;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* fill the buffer */
|
||||||
|
|
||||||
|
auto w = buffer.Write();
|
||||||
|
auto nbytes = iso->SeekRead(w.data, read_lsn,
|
||||||
|
w.size / ISO_BLOCKSIZE);
|
||||||
|
if (nbytes <= 0)
|
||||||
|
throw std::runtime_error("Failed to read ISO9660 file");
|
||||||
|
|
||||||
|
buffer.Append(nbytes);
|
||||||
|
|
||||||
|
r = buffer.Read();
|
||||||
|
|
||||||
|
if (skip > 0) {
|
||||||
|
if (skip >= r.size)
|
||||||
|
throw std::runtime_error("Premature end of ISO9660 track");
|
||||||
|
|
||||||
|
buffer.Consume(skip);
|
||||||
|
skip = 0;
|
||||||
|
|
||||||
|
r = buffer.Read();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
offset += readed;
|
assert(!r.empty());
|
||||||
return readed;
|
assert(skip == 0);
|
||||||
|
|
||||||
|
size_t nbytes = std::min(read_size, r.size);
|
||||||
|
memcpy(ptr, r.data, nbytes);
|
||||||
|
buffer.Consume(nbytes);
|
||||||
|
offset += nbytes;
|
||||||
|
return nbytes;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool
|
bool
|
||||||
|
@@ -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,10 +27,13 @@
|
|||||||
#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>
|
||||||
|
|
||||||
|
#include <inttypes.h> /* for PRIoffset (PRIu64) */
|
||||||
|
|
||||||
struct ZzipDir {
|
struct ZzipDir {
|
||||||
ZZIP_DIR *const dir;
|
ZZIP_DIR *const dir;
|
||||||
|
|
||||||
@@ -53,10 +56,11 @@ class ZzipArchiveFile final : public ArchiveFile {
|
|||||||
std::shared_ptr<ZzipDir> dir;
|
std::shared_ptr<ZzipDir> dir;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
ZzipArchiveFile(std::shared_ptr<ZzipDir> &&_dir)
|
template<typename D>
|
||||||
:dir(std::move(_dir)) {}
|
explicit ZzipArchiveFile(D &&_dir) noexcept
|
||||||
|
:dir(std::forward<D>(_dir)) {}
|
||||||
|
|
||||||
virtual void Visit(ArchiveVisitor &visitor) override;
|
void Visit(ArchiveVisitor &visitor) override;
|
||||||
|
|
||||||
InputStreamPtr OpenStream(const char *path,
|
InputStreamPtr OpenStream(const char *path,
|
||||||
Mutex &mutex) override;
|
Mutex &mutex) override;
|
||||||
@@ -90,11 +94,12 @@ class ZzipInputStream final : public InputStream {
|
|||||||
ZZIP_FILE *const file;
|
ZZIP_FILE *const file;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
ZzipInputStream(const std::shared_ptr<ZzipDir> _dir, const char *_uri,
|
template<typename D>
|
||||||
|
ZzipInputStream(D &&_dir, const char *_uri,
|
||||||
Mutex &_mutex,
|
Mutex &_mutex,
|
||||||
ZZIP_FILE *_file)
|
ZZIP_FILE *_file)
|
||||||
:InputStream(_uri, _mutex),
|
:InputStream(_uri, _mutex),
|
||||||
dir(_dir), file(_file) {
|
dir(std::forward<D>(_dir)), file(_file) {
|
||||||
//we are seekable (but its not recommendent to do so)
|
//we are seekable (but its not recommendent to do so)
|
||||||
seekable = true;
|
seekable = true;
|
||||||
|
|
||||||
@@ -105,7 +110,7 @@ public:
|
|||||||
SetReady();
|
SetReady();
|
||||||
}
|
}
|
||||||
|
|
||||||
~ZzipInputStream() {
|
~ZzipInputStream() noexcept override {
|
||||||
zzip_file_close(file);
|
zzip_file_close(file);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -120,9 +125,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,
|
||||||
@@ -134,12 +149,17 @@ ZzipInputStream::Read(void *ptr, size_t read_size)
|
|||||||
{
|
{
|
||||||
const ScopeUnlock unlock(mutex);
|
const ScopeUnlock unlock(mutex);
|
||||||
|
|
||||||
int ret = zzip_file_read(file, ptr, read_size);
|
zzip_ssize_t nbytes = zzip_file_read(file, ptr, read_size);
|
||||||
if (ret < 0)
|
if (nbytes < 0)
|
||||||
throw std::runtime_error("zzip_file_read() has failed");
|
throw std::runtime_error("zzip_file_read() has failed");
|
||||||
|
|
||||||
|
if (nbytes == 0 && !IsEOF())
|
||||||
|
throw FormatRuntimeError("Unexpected end of file %s"
|
||||||
|
" at %" PRIoffset " of %" PRIoffset,
|
||||||
|
GetURI(), GetOffset(), GetSize());
|
||||||
|
|
||||||
offset = zzip_tell(file);
|
offset = zzip_tell(file);
|
||||||
return ret;
|
return nbytes;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool
|
bool
|
||||||
|
@@ -35,6 +35,6 @@ extern size_t client_max_command_list_size;
|
|||||||
extern size_t client_max_output_buffer_size;
|
extern size_t client_max_output_buffer_size;
|
||||||
|
|
||||||
CommandResult
|
CommandResult
|
||||||
client_process_line(Client &client, char *line);
|
client_process_line(Client &client, char *line) noexcept;
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
@@ -30,7 +30,7 @@
|
|||||||
|
|
||||||
static CommandResult
|
static CommandResult
|
||||||
client_process_command_list(Client &client, bool list_ok,
|
client_process_command_list(Client &client, bool list_ok,
|
||||||
std::list<std::string> &&list)
|
std::list<std::string> &&list) noexcept
|
||||||
{
|
{
|
||||||
CommandResult ret = CommandResult::OK;
|
CommandResult ret = CommandResult::OK;
|
||||||
unsigned num = 0;
|
unsigned num = 0;
|
||||||
@@ -51,7 +51,7 @@ client_process_command_list(Client &client, bool list_ok,
|
|||||||
}
|
}
|
||||||
|
|
||||||
CommandResult
|
CommandResult
|
||||||
client_process_line(Client &client, char *line)
|
client_process_line(Client &client, char *line) noexcept
|
||||||
{
|
{
|
||||||
CommandResult ret;
|
CommandResult ret;
|
||||||
|
|
||||||
|
@@ -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 },
|
||||||
@@ -206,9 +206,10 @@ static constexpr struct command commands[] = {
|
|||||||
|
|
||||||
static constexpr unsigned num_commands = ARRAY_SIZE(commands);
|
static constexpr unsigned num_commands = ARRAY_SIZE(commands);
|
||||||
|
|
||||||
|
gcc_pure
|
||||||
static bool
|
static bool
|
||||||
command_available(gcc_unused const Partition &partition,
|
command_available(gcc_unused const Partition &partition,
|
||||||
gcc_unused const struct command *cmd)
|
gcc_unused const struct command *cmd) noexcept
|
||||||
{
|
{
|
||||||
#ifdef ENABLE_SQLITE
|
#ifdef ENABLE_SQLITE
|
||||||
if (StringIsEqual(cmd->cmd, "sticker"))
|
if (StringIsEqual(cmd->cmd, "sticker"))
|
||||||
@@ -235,7 +236,7 @@ command_available(gcc_unused const Partition &partition,
|
|||||||
|
|
||||||
static CommandResult
|
static CommandResult
|
||||||
PrintAvailableCommands(Response &r, const Partition &partition,
|
PrintAvailableCommands(Response &r, const Partition &partition,
|
||||||
unsigned permission)
|
unsigned permission) noexcept
|
||||||
{
|
{
|
||||||
for (unsigned i = 0; i < num_commands; ++i) {
|
for (unsigned i = 0; i < num_commands; ++i) {
|
||||||
const struct command *cmd = &commands[i];
|
const struct command *cmd = &commands[i];
|
||||||
@@ -249,7 +250,7 @@ PrintAvailableCommands(Response &r, const Partition &partition,
|
|||||||
}
|
}
|
||||||
|
|
||||||
static CommandResult
|
static CommandResult
|
||||||
PrintUnavailableCommands(Response &r, unsigned permission)
|
PrintUnavailableCommands(Response &r, unsigned permission) noexcept
|
||||||
{
|
{
|
||||||
for (unsigned i = 0; i < num_commands; ++i) {
|
for (unsigned i = 0; i < num_commands; ++i) {
|
||||||
const struct command *cmd = &commands[i];
|
const struct command *cmd = &commands[i];
|
||||||
@@ -276,7 +277,7 @@ handle_not_commands(Client &client, gcc_unused Request request, Response &r)
|
|||||||
}
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
command_init()
|
command_init() noexcept
|
||||||
{
|
{
|
||||||
#ifndef NDEBUG
|
#ifndef NDEBUG
|
||||||
/* ensure that the command list is sorted */
|
/* ensure that the command list is sorted */
|
||||||
@@ -285,8 +286,9 @@ command_init()
|
|||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
|
gcc_pure
|
||||||
static const struct command *
|
static const struct command *
|
||||||
command_lookup(const char *name)
|
command_lookup(const char *name) noexcept
|
||||||
{
|
{
|
||||||
unsigned a = 0, b = num_commands, i;
|
unsigned a = 0, b = num_commands, i;
|
||||||
|
|
||||||
@@ -308,7 +310,7 @@ command_lookup(const char *name)
|
|||||||
|
|
||||||
static bool
|
static bool
|
||||||
command_check_request(const struct command *cmd, Response &r,
|
command_check_request(const struct command *cmd, Response &r,
|
||||||
unsigned permission, Request args)
|
unsigned permission, Request args) noexcept
|
||||||
{
|
{
|
||||||
if (cmd->permission != (permission & cmd->permission)) {
|
if (cmd->permission != (permission & cmd->permission)) {
|
||||||
r.FormatError(ACK_ERROR_PERMISSION,
|
r.FormatError(ACK_ERROR_PERMISSION,
|
||||||
@@ -342,7 +344,7 @@ command_check_request(const struct command *cmd, Response &r,
|
|||||||
|
|
||||||
static const struct command *
|
static const struct command *
|
||||||
command_checked_lookup(Response &r, unsigned permission,
|
command_checked_lookup(Response &r, unsigned permission,
|
||||||
const char *cmd_name, Request args)
|
const char *cmd_name, Request args) noexcept
|
||||||
{
|
{
|
||||||
const struct command *cmd = command_lookup(cmd_name);
|
const struct command *cmd = command_lookup(cmd_name);
|
||||||
if (cmd == nullptr) {
|
if (cmd == nullptr) {
|
||||||
@@ -360,8 +362,8 @@ command_checked_lookup(Response &r, unsigned permission,
|
|||||||
}
|
}
|
||||||
|
|
||||||
CommandResult
|
CommandResult
|
||||||
command_process(Client &client, unsigned num, char *line)
|
command_process(Client &client, unsigned num, char *line) noexcept
|
||||||
try {
|
{
|
||||||
Response r(client, num);
|
Response r(client, num);
|
||||||
|
|
||||||
/* get the command name (first word on the line) */
|
/* get the command name (first word on the line) */
|
||||||
@@ -389,34 +391,33 @@ try {
|
|||||||
char *argv[COMMAND_ARGV_MAX];
|
char *argv[COMMAND_ARGV_MAX];
|
||||||
Request args(argv, 0);
|
Request args(argv, 0);
|
||||||
|
|
||||||
/* now parse the arguments (quoted or unquoted) */
|
try {
|
||||||
|
/* now parse the arguments (quoted or unquoted) */
|
||||||
|
|
||||||
while (true) {
|
while (true) {
|
||||||
if (args.size == COMMAND_ARGV_MAX) {
|
if (args.size == COMMAND_ARGV_MAX) {
|
||||||
r.Error(ACK_ERROR_ARG, "Too many arguments");
|
r.Error(ACK_ERROR_ARG, "Too many arguments");
|
||||||
return CommandResult::ERROR;
|
return CommandResult::ERROR;
|
||||||
|
}
|
||||||
|
|
||||||
|
char *a = tokenizer.NextParam();
|
||||||
|
if (a == nullptr)
|
||||||
|
break;
|
||||||
|
|
||||||
|
argv[args.size++] = a;
|
||||||
}
|
}
|
||||||
|
|
||||||
char *a = tokenizer.NextParam();
|
/* look up and invoke the command handler */
|
||||||
if (a == nullptr)
|
|
||||||
break;
|
|
||||||
|
|
||||||
argv[args.size++] = a;
|
const struct command *cmd =
|
||||||
|
command_checked_lookup(r, client.GetPermission(),
|
||||||
|
cmd_name, args);
|
||||||
|
if (cmd == nullptr)
|
||||||
|
return CommandResult::ERROR;
|
||||||
|
|
||||||
|
return cmd->handler(client, args, r);
|
||||||
|
} catch (...) {
|
||||||
|
PrintError(r, std::current_exception());
|
||||||
|
return CommandResult::ERROR;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* look up and invoke the command handler */
|
|
||||||
|
|
||||||
const struct command *cmd =
|
|
||||||
command_checked_lookup(r, client.GetPermission(),
|
|
||||||
cmd_name, args);
|
|
||||||
|
|
||||||
CommandResult ret = cmd
|
|
||||||
? cmd->handler(client, args, r)
|
|
||||||
: CommandResult::ERROR;
|
|
||||||
|
|
||||||
return ret;
|
|
||||||
} catch (const std::exception &e) {
|
|
||||||
Response r(client, num);
|
|
||||||
PrintError(r, std::current_exception());
|
|
||||||
return CommandResult::ERROR;
|
|
||||||
}
|
}
|
||||||
|
@@ -25,12 +25,9 @@
|
|||||||
class Client;
|
class Client;
|
||||||
|
|
||||||
void
|
void
|
||||||
command_init();
|
command_init() noexcept;
|
||||||
|
|
||||||
void
|
|
||||||
command_finish();
|
|
||||||
|
|
||||||
CommandResult
|
CommandResult
|
||||||
command_process(Client &client, unsigned num, char *line);
|
command_process(Client &client, unsigned num, char *line) noexcept;
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
@@ -50,9 +50,7 @@ gcc_pure
|
|||||||
static bool
|
static bool
|
||||||
SkipNameFS(PathTraitsFS::const_pointer_type name_fs) noexcept
|
SkipNameFS(PathTraitsFS::const_pointer_type name_fs) noexcept
|
||||||
{
|
{
|
||||||
return name_fs[0] == '.' &&
|
return PathTraitsFS::IsSpecialFilename(name_fs);
|
||||||
(name_fs[1] == 0 ||
|
|
||||||
(name_fs[1] == '.' && name_fs[2] == 0));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
gcc_pure
|
gcc_pure
|
||||||
|
@@ -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"
|
||||||
@@ -149,12 +148,12 @@ handle_status(Client &client, gcc_unused Request args, Response &r)
|
|||||||
playlist.GetConsume(),
|
playlist.GetConsume(),
|
||||||
(unsigned long)playlist.GetVersion(),
|
(unsigned long)playlist.GetVersion(),
|
||||||
playlist.GetLength(),
|
playlist.GetLength(),
|
||||||
pc.GetMixRampDb(),
|
(double)pc.GetMixRampDb(),
|
||||||
state);
|
state);
|
||||||
|
|
||||||
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"
|
||||||
@@ -198,6 +198,16 @@ handle_mount(Client &client, Request args, Response &r)
|
|||||||
return CommandResult::ERROR;
|
return CommandResult::ERROR;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (composite.IsMountPoint(local_uri)) {
|
||||||
|
r.Error(ACK_ERROR_ARG, "Mount point busy");
|
||||||
|
return CommandResult::ERROR;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (composite.IsMounted(remote_uri)) {
|
||||||
|
r.Error(ACK_ERROR_ARG, "This storage is already mounted");
|
||||||
|
return CommandResult::ERROR;
|
||||||
|
}
|
||||||
|
|
||||||
auto &event_loop = instance.io_thread.GetEventLoop();
|
auto &event_loop = instance.io_thread.GetEventLoop();
|
||||||
auto storage = CreateStorageURI(event_loop, remote_uri);
|
auto storage = CreateStorageURI(event_loop, remote_uri);
|
||||||
if (storage == nullptr) {
|
if (storage == nullptr) {
|
||||||
@@ -210,8 +220,10 @@ handle_mount(Client &client, Request args, Response &r)
|
|||||||
|
|
||||||
#ifdef ENABLE_DATABASE
|
#ifdef ENABLE_DATABASE
|
||||||
if (auto *db = dynamic_cast<SimpleDatabase *>(instance.GetDatabase())) {
|
if (auto *db = dynamic_cast<SimpleDatabase *>(instance.GetDatabase())) {
|
||||||
|
bool need_update;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
db->Mount(local_uri, remote_uri);
|
need_update = !db->Mount(local_uri, remote_uri);
|
||||||
} catch (...) {
|
} catch (...) {
|
||||||
composite.Unmount(local_uri);
|
composite.Unmount(local_uri);
|
||||||
throw;
|
throw;
|
||||||
@@ -220,6 +232,12 @@ handle_mount(Client &client, Request args, Response &r)
|
|||||||
// TODO: call Instance::OnDatabaseModified()?
|
// TODO: call Instance::OnDatabaseModified()?
|
||||||
// TODO: trigger database update?
|
// TODO: trigger database update?
|
||||||
instance.EmitIdle(IDLE_DATABASE);
|
instance.EmitIdle(IDLE_DATABASE);
|
||||||
|
|
||||||
|
if (need_update) {
|
||||||
|
UpdateService *update = client.GetInstance().update;
|
||||||
|
if (update != nullptr)
|
||||||
|
update->Enqueue(local_uri, false);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
@@ -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"
|
||||||
|
@@ -428,7 +428,7 @@ IsUnsafeChar(char ch)
|
|||||||
return !IsSafeChar(ch);
|
return !IsSafeChar(ch);
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
bool
|
||||||
SimpleDatabase::Mount(const char *local_uri, const char *storage_uri)
|
SimpleDatabase::Mount(const char *local_uri, const char *storage_uri)
|
||||||
{
|
{
|
||||||
if (cache_path.IsNull())
|
if (cache_path.IsNull())
|
||||||
@@ -447,14 +447,11 @@ SimpleDatabase::Mount(const char *local_uri, const char *storage_uri)
|
|||||||
compress);
|
compress);
|
||||||
db->Open();
|
db->Open();
|
||||||
|
|
||||||
// TODO: update the new database instance?
|
bool exists = db->FileExists();
|
||||||
|
|
||||||
try {
|
Mount(local_uri, std::move(db));
|
||||||
Mount(local_uri, std::move(db));
|
|
||||||
} catch (...) {
|
return exists;
|
||||||
db->Close();
|
|
||||||
throw;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
inline DatabasePtr
|
inline DatabasePtr
|
||||||
|
@@ -103,9 +103,11 @@ public:
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Throws #std::runtime_error on error.
|
* Throws #std::runtime_error on error.
|
||||||
|
*
|
||||||
|
* @return false if the mounted database needs to be updated
|
||||||
*/
|
*/
|
||||||
gcc_nonnull_all
|
gcc_nonnull_all
|
||||||
void Mount(const char *local_uri, const char *storage_uri);
|
bool Mount(const char *local_uri, const char *storage_uri);
|
||||||
|
|
||||||
gcc_nonnull_all
|
gcc_nonnull_all
|
||||||
bool Unmount(const char *uri) noexcept;
|
bool Unmount(const char *uri) noexcept;
|
||||||
|
@@ -65,7 +65,7 @@ ContentDirectoryService::readDirSlice(UpnpClient_Handle hdl,
|
|||||||
|
|
||||||
IXML_Document *response;
|
IXML_Document *response;
|
||||||
int code = UpnpSendAction(hdl, m_actionURL.c_str(), m_serviceType.c_str(),
|
int code = UpnpSendAction(hdl, m_actionURL.c_str(), m_serviceType.c_str(),
|
||||||
0 /*devUDN*/, request, &response);
|
nullptr /*devUDN*/, request, &response);
|
||||||
if (code != UPNP_E_SUCCESS)
|
if (code != UPNP_E_SUCCESS)
|
||||||
throw FormatRuntimeError("UpnpSendAction() failed: %s",
|
throw FormatRuntimeError("UpnpSendAction() failed: %s",
|
||||||
UpnpGetErrorMessage(code));
|
UpnpGetErrorMessage(code));
|
||||||
@@ -124,7 +124,7 @@ ContentDirectoryService::search(UpnpClient_Handle hdl,
|
|||||||
IXML_Document *_response;
|
IXML_Document *_response;
|
||||||
auto code = UpnpSendAction(hdl, m_actionURL.c_str(),
|
auto code = UpnpSendAction(hdl, m_actionURL.c_str(),
|
||||||
m_serviceType.c_str(),
|
m_serviceType.c_str(),
|
||||||
0 /*devUDN*/,
|
nullptr /*devUDN*/,
|
||||||
request.get(), &_response);
|
request.get(), &_response);
|
||||||
if (code != UPNP_E_SUCCESS)
|
if (code != UPNP_E_SUCCESS)
|
||||||
throw FormatRuntimeError("UpnpSendAction() failed: %s",
|
throw FormatRuntimeError("UpnpSendAction() failed: %s",
|
||||||
@@ -170,7 +170,7 @@ ContentDirectoryService::getMetadata(UpnpClient_Handle hdl,
|
|||||||
IXML_Document *_response;
|
IXML_Document *_response;
|
||||||
auto code = UpnpSendAction(hdl, m_actionURL.c_str(),
|
auto code = UpnpSendAction(hdl, m_actionURL.c_str(),
|
||||||
m_serviceType.c_str(),
|
m_serviceType.c_str(),
|
||||||
0 /*devUDN*/, request.get(), &_response);
|
nullptr /*devUDN*/, request.get(), &_response);
|
||||||
if (code != UPNP_E_SUCCESS)
|
if (code != UPNP_E_SUCCESS)
|
||||||
throw FormatRuntimeError("UpnpSendAction() failed: %s",
|
throw FormatRuntimeError("UpnpSendAction() failed: %s",
|
||||||
UpnpGetErrorMessage(code));
|
UpnpGetErrorMessage(code));
|
||||||
|
@@ -89,9 +89,18 @@ public:
|
|||||||
tag.Clear();
|
tag.Clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
gcc_pure
|
||||||
|
bool IsRoot() const noexcept {
|
||||||
|
return type == Type::CONTAINER && id == "0";
|
||||||
|
}
|
||||||
|
|
||||||
gcc_pure
|
gcc_pure
|
||||||
bool Check() const noexcept {
|
bool Check() const noexcept {
|
||||||
return !id.empty() && !parent_id.empty() && !name.empty() &&
|
return !id.empty() &&
|
||||||
|
/* root nodes don't need a parent id and a
|
||||||
|
name */
|
||||||
|
(IsRoot() || (!parent_id.empty() &&
|
||||||
|
!name.empty())) &&
|
||||||
(type != UPnPDirObject::Type::ITEM ||
|
(type != UPnPDirObject::Type::ITEM ||
|
||||||
item_class != UPnPDirObject::ItemClass::UNKNOWN);
|
item_class != UPnPDirObject::ItemClass::UNKNOWN);
|
||||||
}
|
}
|
||||||
|
@@ -79,7 +79,7 @@ path_in(const char *path, const char *possible_parent) noexcept
|
|||||||
}
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
InotifyQueue::Enqueue(const char *uri_utf8)
|
InotifyQueue::Enqueue(const char *uri_utf8) noexcept
|
||||||
{
|
{
|
||||||
delay_event.Schedule(INOTIFY_UPDATE_DELAY);
|
delay_event.Schedule(INOTIFY_UPDATE_DELAY);
|
||||||
|
|
||||||
|
@@ -35,11 +35,11 @@ class InotifyQueue final {
|
|||||||
TimerEvent delay_event;
|
TimerEvent delay_event;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
InotifyQueue(EventLoop &_loop, UpdateService &_update)
|
InotifyQueue(EventLoop &_loop, UpdateService &_update) noexcept
|
||||||
:update(_update),
|
:update(_update),
|
||||||
delay_event(_loop, BIND_THIS_METHOD(OnDelay)) {}
|
delay_event(_loop, BIND_THIS_METHOD(OnDelay)) {}
|
||||||
|
|
||||||
void Enqueue(const char *uri_utf8);
|
void Enqueue(const char *uri_utf8) noexcept;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void OnDelay() noexcept;
|
void OnDelay() noexcept;
|
||||||
|
@@ -24,11 +24,11 @@
|
|||||||
#include "system/Error.hxx"
|
#include "system/Error.hxx"
|
||||||
#include "Log.hxx"
|
#include "Log.hxx"
|
||||||
|
|
||||||
|
#include <cerrno>
|
||||||
|
#include <climits>
|
||||||
|
#include <cstdint>
|
||||||
|
|
||||||
#include <sys/inotify.h>
|
#include <sys/inotify.h>
|
||||||
#include <unistd.h>
|
|
||||||
#include <errno.h>
|
|
||||||
#include <stdint.h>
|
|
||||||
#include <limits.h>
|
|
||||||
|
|
||||||
bool
|
bool
|
||||||
InotifySource::OnSocketReady(gcc_unused unsigned flags) noexcept
|
InotifySource::OnSocketReady(gcc_unused unsigned flags) noexcept
|
||||||
@@ -48,7 +48,7 @@ InotifySource::OnSocketReady(gcc_unused unsigned flags) noexcept
|
|||||||
|
|
||||||
while (true) {
|
while (true) {
|
||||||
const size_t remaining = end - p;
|
const size_t remaining = end - p;
|
||||||
const struct inotify_event *event =
|
const auto *event =
|
||||||
(const struct inotify_event *)p;
|
(const struct inotify_event *)p;
|
||||||
if (remaining < sizeof(*event) ||
|
if (remaining < sizeof(*event) ||
|
||||||
remaining < sizeof(*event) + event->len)
|
remaining < sizeof(*event) + event->len)
|
||||||
@@ -98,7 +98,7 @@ InotifySource::Add(const char *path_fs, unsigned mask)
|
|||||||
}
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
InotifySource::Remove(unsigned wd)
|
InotifySource::Remove(unsigned wd) noexcept
|
||||||
{
|
{
|
||||||
auto ifd = GetSocket().ToFileDescriptor();
|
auto ifd = GetSocket().ToFileDescriptor();
|
||||||
int ret = inotify_rm_watch(ifd.Get(), wd);
|
int ret = inotify_rm_watch(ifd.Get(), wd);
|
||||||
|
@@ -21,9 +21,6 @@
|
|||||||
#define MPD_INOTIFY_SOURCE_HXX
|
#define MPD_INOTIFY_SOURCE_HXX
|
||||||
|
|
||||||
#include "event/SocketMonitor.hxx"
|
#include "event/SocketMonitor.hxx"
|
||||||
#include "util/Compiler.h"
|
|
||||||
|
|
||||||
class FileDescriptor;
|
|
||||||
|
|
||||||
typedef void (*mpd_inotify_callback_t)(int wd, unsigned mask,
|
typedef void (*mpd_inotify_callback_t)(int wd, unsigned mask,
|
||||||
const char *name, void *ctx);
|
const char *name, void *ctx);
|
||||||
@@ -45,7 +42,7 @@ public:
|
|||||||
InotifySource(EventLoop &_loop,
|
InotifySource(EventLoop &_loop,
|
||||||
mpd_inotify_callback_t callback, void *ctx);
|
mpd_inotify_callback_t callback, void *ctx);
|
||||||
|
|
||||||
~InotifySource() {
|
~InotifySource() noexcept {
|
||||||
Close();
|
Close();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -63,7 +60,7 @@ public:
|
|||||||
*
|
*
|
||||||
* @param wd the watch descriptor returned by mpd_inotify_source_add()
|
* @param wd the watch descriptor returned by mpd_inotify_source_add()
|
||||||
*/
|
*/
|
||||||
void Remove(unsigned wd);
|
void Remove(unsigned wd) noexcept;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
bool OnSocketReady(unsigned flags) noexcept override;
|
bool OnSocketReady(unsigned flags) noexcept override;
|
||||||
|
@@ -21,18 +21,25 @@
|
|||||||
#include "InotifySource.hxx"
|
#include "InotifySource.hxx"
|
||||||
#include "InotifyQueue.hxx"
|
#include "InotifyQueue.hxx"
|
||||||
#include "InotifyDomain.hxx"
|
#include "InotifyDomain.hxx"
|
||||||
|
#include "ExcludeList.hxx"
|
||||||
#include "storage/StorageInterface.hxx"
|
#include "storage/StorageInterface.hxx"
|
||||||
|
#include "input/InputStream.hxx"
|
||||||
|
#include "input/Error.hxx"
|
||||||
#include "fs/AllocatedPath.hxx"
|
#include "fs/AllocatedPath.hxx"
|
||||||
|
#include "fs/DirectoryReader.hxx"
|
||||||
#include "fs/FileInfo.hxx"
|
#include "fs/FileInfo.hxx"
|
||||||
|
#include "fs/Traits.hxx"
|
||||||
|
#include "thread/Mutex.hxx"
|
||||||
|
#include "util/Compiler.h"
|
||||||
#include "Log.hxx"
|
#include "Log.hxx"
|
||||||
|
|
||||||
#include <string>
|
#include <cassert>
|
||||||
#include <map>
|
#include <cstring>
|
||||||
#include <forward_list>
|
#include <forward_list>
|
||||||
|
#include <map>
|
||||||
|
#include <string>
|
||||||
|
|
||||||
#include <assert.h>
|
|
||||||
#include <sys/inotify.h>
|
#include <sys/inotify.h>
|
||||||
#include <string.h>
|
|
||||||
#include <dirent.h>
|
#include <dirent.h>
|
||||||
|
|
||||||
static constexpr unsigned IN_MASK =
|
static constexpr unsigned IN_MASK =
|
||||||
@@ -49,17 +56,28 @@ struct WatchDirectory {
|
|||||||
|
|
||||||
int descriptor;
|
int descriptor;
|
||||||
|
|
||||||
|
ExcludeList exclude_list;
|
||||||
|
|
||||||
std::forward_list<WatchDirectory> children;
|
std::forward_list<WatchDirectory> children;
|
||||||
|
|
||||||
template<typename N>
|
template<typename N>
|
||||||
WatchDirectory(WatchDirectory *_parent, N &&_name,
|
WatchDirectory(N &&_name,
|
||||||
int _descriptor)
|
int _descriptor)
|
||||||
:parent(_parent), name(std::forward<N>(_name)),
|
:parent(nullptr), name(std::forward<N>(_name)),
|
||||||
descriptor(_descriptor) {}
|
descriptor(_descriptor) {}
|
||||||
|
|
||||||
|
template<typename N>
|
||||||
|
WatchDirectory(WatchDirectory &_parent, N &&_name,
|
||||||
|
int _descriptor)
|
||||||
|
:parent(&_parent), name(std::forward<N>(_name)),
|
||||||
|
descriptor(_descriptor),
|
||||||
|
exclude_list(_parent.exclude_list) {}
|
||||||
|
|
||||||
WatchDirectory(const WatchDirectory &) = delete;
|
WatchDirectory(const WatchDirectory &) = delete;
|
||||||
WatchDirectory &operator=(const WatchDirectory &) = delete;
|
WatchDirectory &operator=(const WatchDirectory &) = delete;
|
||||||
|
|
||||||
|
void LoadExcludeList(Path directory_path) noexcept;
|
||||||
|
|
||||||
gcc_pure
|
gcc_pure
|
||||||
unsigned GetDepth() const noexcept;
|
unsigned GetDepth() const noexcept;
|
||||||
|
|
||||||
@@ -67,6 +85,18 @@ struct WatchDirectory {
|
|||||||
AllocatedPath GetUriFS() const noexcept;
|
AllocatedPath GetUriFS() const noexcept;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
void
|
||||||
|
WatchDirectory::LoadExcludeList(Path directory_path) noexcept
|
||||||
|
try {
|
||||||
|
Mutex mutex;
|
||||||
|
auto is = InputStream::OpenReady((directory_path / Path::FromFS(".mpdignore")).c_str(),
|
||||||
|
mutex);
|
||||||
|
exclude_list.Load(std::move(is));
|
||||||
|
} catch (...) {
|
||||||
|
if (!IsFileNotFound(std::current_exception()))
|
||||||
|
LogError(std::current_exception());
|
||||||
|
}
|
||||||
|
|
||||||
static InotifySource *inotify_source;
|
static InotifySource *inotify_source;
|
||||||
static InotifyQueue *inotify_queue;
|
static InotifyQueue *inotify_queue;
|
||||||
|
|
||||||
@@ -144,21 +174,19 @@ 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)
|
gcc_pure
|
||||||
|
static bool
|
||||||
|
SkipFilename(Path name) noexcept
|
||||||
{
|
{
|
||||||
return (path[0] == '.' && path[1] == 0) ||
|
return PathTraitsFS::IsSpecialFilename(name.c_str()) ||
|
||||||
(path[0] == '.' && path[1] == '.' && path[2] == 0) ||
|
name.HasNewline();
|
||||||
strchr(path, '\n') != nullptr;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static void
|
static void
|
||||||
recursive_watch_subdirectories(WatchDirectory *directory,
|
recursive_watch_subdirectories(WatchDirectory &parent,
|
||||||
const AllocatedPath &path_fs, unsigned depth)
|
const Path path_fs,
|
||||||
{
|
unsigned depth)
|
||||||
DIR *dir;
|
try {
|
||||||
struct dirent *ent;
|
|
||||||
|
|
||||||
assert(directory != nullptr);
|
|
||||||
assert(depth <= inotify_max_depth);
|
assert(depth <= inotify_max_depth);
|
||||||
assert(!path_fs.IsNull());
|
assert(!path_fs.IsNull());
|
||||||
|
|
||||||
@@ -167,20 +195,17 @@ recursive_watch_subdirectories(WatchDirectory *directory,
|
|||||||
if (depth > inotify_max_depth)
|
if (depth > inotify_max_depth)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
dir = opendir(path_fs.c_str());
|
DirectoryReader dir(path_fs);
|
||||||
if (dir == nullptr) {
|
while (dir.ReadEntry()) {
|
||||||
FormatErrno(inotify_domain,
|
|
||||||
"Failed to open directory %s", path_fs.c_str());
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
while ((ent = readdir(dir))) {
|
|
||||||
int ret;
|
int ret;
|
||||||
|
|
||||||
if (skip_path(ent->d_name))
|
const Path name_fs = dir.GetEntry();
|
||||||
|
if (SkipFilename(name_fs))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
if (parent.exclude_list.Check(name_fs))
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
const auto name_fs = Path::FromFS(ent->d_name);
|
|
||||||
const auto child_path_fs = path_fs / name_fs;
|
const auto child_path_fs = path_fs / name_fs;
|
||||||
|
|
||||||
FileInfo fi;
|
FileInfo fi;
|
||||||
@@ -209,17 +234,18 @@ recursive_watch_subdirectories(WatchDirectory *directory,
|
|||||||
/* already being watched */
|
/* already being watched */
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
directory->children.emplace_front(directory,
|
parent.children.emplace_front(parent,
|
||||||
name_fs,
|
name_fs,
|
||||||
ret);
|
ret);
|
||||||
child = &directory->children.front();
|
child = &parent.children.front();
|
||||||
|
child->LoadExcludeList(child_path_fs);
|
||||||
|
|
||||||
tree_add_watch_directory(child);
|
tree_add_watch_directory(child);
|
||||||
|
|
||||||
recursive_watch_subdirectories(child, child_path_fs, depth);
|
recursive_watch_subdirectories(*child, child_path_fs, depth);
|
||||||
}
|
}
|
||||||
|
} catch (...) {
|
||||||
closedir(dir);
|
LogError(std::current_exception());
|
||||||
}
|
}
|
||||||
|
|
||||||
gcc_pure
|
gcc_pure
|
||||||
@@ -240,8 +266,6 @@ mpd_inotify_callback(int wd, unsigned mask,
|
|||||||
{
|
{
|
||||||
WatchDirectory *directory;
|
WatchDirectory *directory;
|
||||||
|
|
||||||
/*FormatDebug(inotify_domain, "wd=%d mask=0x%x name='%s'", wd, mask, name);*/
|
|
||||||
|
|
||||||
directory = tree_find_watch_directory(wd);
|
directory = tree_find_watch_directory(wd);
|
||||||
if (directory == nullptr)
|
if (directory == nullptr)
|
||||||
return;
|
return;
|
||||||
@@ -263,7 +287,7 @@ mpd_inotify_callback(int wd, unsigned mask,
|
|||||||
? root
|
? root
|
||||||
: (root / uri_fs);
|
: (root / uri_fs);
|
||||||
|
|
||||||
recursive_watch_subdirectories(directory, path_fs,
|
recursive_watch_subdirectories(*directory, path_fs,
|
||||||
directory->GetDepth());
|
directory->GetDepth());
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -318,11 +342,12 @@ mpd_inotify_init(EventLoop &loop, Storage &storage, UpdateService &update,
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
inotify_root = new WatchDirectory(nullptr, path, descriptor);
|
inotify_root = new WatchDirectory(path, descriptor);
|
||||||
|
inotify_root->LoadExcludeList(path);
|
||||||
|
|
||||||
tree_add_watch_directory(inotify_root);
|
tree_add_watch_directory(inotify_root);
|
||||||
|
|
||||||
recursive_watch_subdirectories(inotify_root, path, 0);
|
recursive_watch_subdirectories(*inotify_root, path, 0);
|
||||||
|
|
||||||
inotify_queue = new InotifyQueue(loop, update);
|
inotify_queue = new InotifyQueue(loop, update);
|
||||||
|
|
||||||
|
@@ -20,8 +20,6 @@
|
|||||||
#ifndef MPD_INOTIFY_UPDATE_HXX
|
#ifndef MPD_INOTIFY_UPDATE_HXX
|
||||||
#define MPD_INOTIFY_UPDATE_HXX
|
#define MPD_INOTIFY_UPDATE_HXX
|
||||||
|
|
||||||
#include "util/Compiler.h"
|
|
||||||
|
|
||||||
class EventLoop;
|
class EventLoop;
|
||||||
class Storage;
|
class Storage;
|
||||||
class UpdateService;
|
class UpdateService;
|
||||||
|
@@ -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
|
||||||
@@ -341,8 +341,8 @@ UpdateWalk::UpdateDirectory(Directory &directory,
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
Mutex mutex;
|
Mutex mutex;
|
||||||
auto is = InputStream::OpenReady(PathTraitsUTF8::Build(storage.MapUTF8(directory.GetPath()).c_str(),
|
auto is = InputStream::OpenReady(storage.MapUTF8(PathTraitsUTF8::Build(directory.GetPath(),
|
||||||
".mpdignore").c_str(),
|
".mpdignore").c_str()).c_str(),
|
||||||
mutex);
|
mutex);
|
||||||
child_exclude_list.Load(std::move(is));
|
child_exclude_list.Load(std::move(is));
|
||||||
} catch (...) {
|
} catch (...) {
|
||||||
@@ -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,9 +33,11 @@
|
|||||||
#include "util/ConstBuffer.hxx"
|
#include "util/ConstBuffer.hxx"
|
||||||
#include "util/StringBuffer.hxx"
|
#include "util/StringBuffer.hxx"
|
||||||
|
|
||||||
|
#include <cmath>
|
||||||
|
#include <stdexcept>
|
||||||
|
|
||||||
#include <assert.h>
|
#include <assert.h>
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
#include <math.h>
|
|
||||||
|
|
||||||
DecoderBridge::~DecoderBridge()
|
DecoderBridge::~DecoderBridge()
|
||||||
{
|
{
|
||||||
@@ -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;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -591,7 +597,7 @@ DecoderBridge::SubmitReplayGain(const ReplayGainInfo *new_replay_gain_info)
|
|||||||
const auto &tuple = new_replay_gain_info->Get(rgm);
|
const auto &tuple = new_replay_gain_info->Get(rgm);
|
||||||
const auto scale =
|
const auto scale =
|
||||||
tuple.CalculateScale(dc.replay_gain_config);
|
tuple.CalculateScale(dc.replay_gain_config);
|
||||||
dc.replay_gain_db = 20.0 * log10f(scale);
|
dc.replay_gain_db = 20.0f * std::log10(scale);
|
||||||
}
|
}
|
||||||
|
|
||||||
replay_gain_info = *new_replay_gain_info;
|
replay_gain_info = *new_replay_gain_info;
|
||||||
|
@@ -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);
|
||||||
|
|
||||||
@@ -147,6 +149,18 @@ DecoderControl::Seek(SongTime t)
|
|||||||
seek_error = false;
|
seek_error = false;
|
||||||
SynchronousCommandLocked(DecoderCommand::SEEK);
|
SynchronousCommandLocked(DecoderCommand::SEEK);
|
||||||
|
|
||||||
|
while (state == DecoderState::START)
|
||||||
|
/* If the decoder falls back to DecoderState::START,
|
||||||
|
this means that our SEEK command arrived too late,
|
||||||
|
and the decoder had meanwhile finished decoding and
|
||||||
|
went idle. Our SEEK command is finished, but that
|
||||||
|
means only that the decoder thread has launched the
|
||||||
|
decoder. To work around illegal states, we wait
|
||||||
|
until the decoder plugin has become ready. This is
|
||||||
|
a kludge, built on top of the "late seek" kludge.
|
||||||
|
Not exactly elegant, sorry. */
|
||||||
|
WaitForDecoder();
|
||||||
|
|
||||||
if (seek_error)
|
if (seek_error)
|
||||||
throw std::runtime_error("Decoder failed to seek");
|
throw std::runtime_error("Decoder failed to seek");
|
||||||
}
|
}
|
||||||
|
@@ -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;
|
||||||
|
|
||||||
|
@@ -26,7 +26,7 @@
|
|||||||
size_t
|
size_t
|
||||||
decoder_read(DecoderClient *client,
|
decoder_read(DecoderClient *client,
|
||||||
InputStream &is,
|
InputStream &is,
|
||||||
void *buffer, size_t length)
|
void *buffer, size_t length) noexcept
|
||||||
{
|
{
|
||||||
assert(buffer != nullptr);
|
assert(buffer != nullptr);
|
||||||
|
|
||||||
@@ -42,9 +42,30 @@ decoder_read(DecoderClient *client,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
size_t
|
||||||
|
decoder_read_much(DecoderClient *client, InputStream &is,
|
||||||
|
void *_buffer, size_t size) noexcept
|
||||||
|
{
|
||||||
|
uint8_t *buffer = (uint8_t *)_buffer;
|
||||||
|
|
||||||
|
size_t total = 0;
|
||||||
|
|
||||||
|
while (size > 0 && !is.LockIsEOF()) {
|
||||||
|
size_t nbytes = decoder_read(client, is, buffer, size);
|
||||||
|
if (nbytes == 0)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
total += nbytes;
|
||||||
|
buffer += nbytes;
|
||||||
|
size -= nbytes;
|
||||||
|
}
|
||||||
|
|
||||||
|
return total;
|
||||||
|
}
|
||||||
|
|
||||||
bool
|
bool
|
||||||
decoder_read_full(DecoderClient *client, InputStream &is,
|
decoder_read_full(DecoderClient *client, InputStream &is,
|
||||||
void *_buffer, size_t size)
|
void *_buffer, size_t size) noexcept
|
||||||
{
|
{
|
||||||
uint8_t *buffer = (uint8_t *)_buffer;
|
uint8_t *buffer = (uint8_t *)_buffer;
|
||||||
|
|
||||||
@@ -61,7 +82,7 @@ decoder_read_full(DecoderClient *client, InputStream &is,
|
|||||||
}
|
}
|
||||||
|
|
||||||
bool
|
bool
|
||||||
decoder_skip(DecoderClient *client, InputStream &is, size_t size)
|
decoder_skip(DecoderClient *client, InputStream &is, size_t size) noexcept
|
||||||
{
|
{
|
||||||
while (size > 0) {
|
while (size > 0) {
|
||||||
char buffer[1024];
|
char buffer[1024];
|
||||||
|
@@ -65,15 +65,27 @@ class StopDecoder {};
|
|||||||
*/
|
*/
|
||||||
size_t
|
size_t
|
||||||
decoder_read(DecoderClient *decoder, InputStream &is,
|
decoder_read(DecoderClient *decoder, InputStream &is,
|
||||||
void *buffer, size_t length);
|
void *buffer, size_t length) noexcept;
|
||||||
|
|
||||||
static inline size_t
|
static inline size_t
|
||||||
decoder_read(DecoderClient &decoder, InputStream &is,
|
decoder_read(DecoderClient &decoder, InputStream &is,
|
||||||
void *buffer, size_t length)
|
void *buffer, size_t length) noexcept
|
||||||
{
|
{
|
||||||
return decoder_read(&decoder, is, buffer, length);
|
return decoder_read(&decoder, is, buffer, length);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Blocking read from the input stream. Attempts to fill the buffer
|
||||||
|
* as much as possible, until either end-of-file is reached or an
|
||||||
|
* error occurs.
|
||||||
|
*
|
||||||
|
* @return the number of bytes read, or 0 if one of the following
|
||||||
|
* occurs: end of file; error; command (like SEEK or STOP).
|
||||||
|
*/
|
||||||
|
size_t
|
||||||
|
decoder_read_much(DecoderClient *decoder, InputStream &is,
|
||||||
|
void *buffer, size_t size) noexcept;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Blocking read from the input stream. Attempts to fill the buffer
|
* Blocking read from the input stream. Attempts to fill the buffer
|
||||||
* completely; there is no partial result.
|
* completely; there is no partial result.
|
||||||
@@ -83,7 +95,7 @@ decoder_read(DecoderClient &decoder, InputStream &is,
|
|||||||
*/
|
*/
|
||||||
bool
|
bool
|
||||||
decoder_read_full(DecoderClient *decoder, InputStream &is,
|
decoder_read_full(DecoderClient *decoder, InputStream &is,
|
||||||
void *buffer, size_t size);
|
void *buffer, size_t size) noexcept;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Skip data on the #InputStream.
|
* Skip data on the #InputStream.
|
||||||
@@ -91,6 +103,6 @@ decoder_read_full(DecoderClient *decoder, InputStream &is,
|
|||||||
* @return true on success, false on error or command
|
* @return true on success, false on error or command
|
||||||
*/
|
*/
|
||||||
bool
|
bool
|
||||||
decoder_skip(DecoderClient *decoder, InputStream &is, size_t size);
|
decoder_skip(DecoderClient *decoder, InputStream &is, size_t size) noexcept;
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
@@ -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;
|
||||||
@@ -67,18 +67,22 @@ struct DecoderPlugin {
|
|||||||
void (*file_decode)(DecoderClient &client, Path path_fs);
|
void (*file_decode)(DecoderClient &client, Path path_fs);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Scan metadata of a file.
|
* Scan metadata of a file.
|
||||||
|
*
|
||||||
|
* Throws on I/O error.
|
||||||
*
|
*
|
||||||
* @return false if the operation has failed
|
* @return false if the file was not recognized
|
||||||
*/
|
*/
|
||||||
bool (*scan_file)(Path path_fs, TagHandler &handler) noexcept;
|
bool (*scan_file)(Path path_fs, TagHandler &handler);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Scan metadata of a file.
|
* Scan metadata of a stream.
|
||||||
|
*
|
||||||
|
* Throws on I/O error.
|
||||||
*
|
*
|
||||||
* @return false if the operation has failed
|
* @return false if the stream was not recognized
|
||||||
*/
|
*/
|
||||||
bool (*scan_stream)(InputStream &is, TagHandler &handler) noexcept;
|
bool (*scan_stream)(InputStream &is, TagHandler &handler);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Return a "virtual" filename for subtracks in
|
* @brief Return a "virtual" filename for subtracks in
|
||||||
@@ -135,7 +139,7 @@ struct DecoderPlugin {
|
|||||||
* Read the tag of a file.
|
* Read the tag of a file.
|
||||||
*/
|
*/
|
||||||
template<typename P>
|
template<typename P>
|
||||||
bool ScanFile(P path_fs, TagHandler &handler) const noexcept {
|
bool ScanFile(P path_fs, TagHandler &handler) const {
|
||||||
return scan_file != nullptr
|
return scan_file != nullptr
|
||||||
? scan_file(path_fs, handler)
|
? scan_file(path_fs, handler)
|
||||||
: false;
|
: false;
|
||||||
@@ -144,7 +148,7 @@ struct DecoderPlugin {
|
|||||||
/**
|
/**
|
||||||
* Read the tag of a stream.
|
* Read the tag of a stream.
|
||||||
*/
|
*/
|
||||||
bool ScanStream(InputStream &is, TagHandler &handler) const noexcept {
|
bool ScanStream(InputStream &is, TagHandler &handler) const {
|
||||||
return scan_stream != nullptr
|
return scan_stream != nullptr
|
||||||
? scan_stream(is, handler)
|
? scan_stream(is, handler)
|
||||||
: false;
|
: false;
|
||||||
|
@@ -455,7 +455,13 @@ static void
|
|||||||
decoder_run_song(DecoderControl &dc,
|
decoder_run_song(DecoderControl &dc,
|
||||||
const DetachedSong &song, const char *uri, Path path_fs)
|
const DetachedSong &song, const char *uri, Path path_fs)
|
||||||
{
|
{
|
||||||
|
if (dc.command == DecoderCommand::SEEK)
|
||||||
|
/* if the SEEK command arrived too late, start the
|
||||||
|
decoder at the seek position */
|
||||||
|
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;
|
||||||
|
@@ -241,7 +241,7 @@ audiofile_stream_decode(DecoderClient &client, InputStream &is)
|
|||||||
}
|
}
|
||||||
|
|
||||||
static bool
|
static bool
|
||||||
audiofile_scan_stream(InputStream &is, TagHandler &handler) noexcept
|
audiofile_scan_stream(InputStream &is, TagHandler &handler)
|
||||||
{
|
{
|
||||||
if (!is.IsSeekable() || !is.KnownSize())
|
if (!is.IsSeekable() || !is.KnownSize())
|
||||||
return false;
|
return false;
|
||||||
@@ -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
|
||||||
|
@@ -20,7 +20,7 @@
|
|||||||
#ifndef MPD_DECODER_DSDLIB_HXX
|
#ifndef MPD_DECODER_DSDLIB_HXX
|
||||||
#define MPD_DECODER_DSDLIB_HXX
|
#define MPD_DECODER_DSDLIB_HXX
|
||||||
|
|
||||||
#include "system/ByteOrder.hxx"
|
#include "util/ByteOrder.hxx"
|
||||||
#include "input/Offset.hxx"
|
#include "input/Offset.hxx"
|
||||||
#include "util/Compiler.h"
|
#include "util/Compiler.h"
|
||||||
|
|
||||||
|
@@ -32,7 +32,7 @@
|
|||||||
#include "input/InputStream.hxx"
|
#include "input/InputStream.hxx"
|
||||||
#include "CheckAudioFormat.hxx"
|
#include "CheckAudioFormat.hxx"
|
||||||
#include "util/bit_reverse.h"
|
#include "util/bit_reverse.h"
|
||||||
#include "system/ByteOrder.hxx"
|
#include "util/ByteOrder.hxx"
|
||||||
#include "tag/Handler.hxx"
|
#include "tag/Handler.hxx"
|
||||||
#include "DsdLib.hxx"
|
#include "DsdLib.hxx"
|
||||||
#include "Log.hxx"
|
#include "Log.hxx"
|
||||||
@@ -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;
|
||||||
@@ -448,7 +449,7 @@ dsdiff_stream_decode(DecoderClient &client, InputStream &is)
|
|||||||
}
|
}
|
||||||
|
|
||||||
static bool
|
static bool
|
||||||
dsdiff_scan_stream(InputStream &is, TagHandler &handler) noexcept
|
dsdiff_scan_stream(InputStream &is, TagHandler &handler)
|
||||||
{
|
{
|
||||||
DsdiffMetaData metadata;
|
DsdiffMetaData metadata;
|
||||||
DsdiffChunkHeader chunk_header;
|
DsdiffChunkHeader chunk_header;
|
||||||
|
@@ -33,7 +33,7 @@
|
|||||||
#include "input/InputStream.hxx"
|
#include "input/InputStream.hxx"
|
||||||
#include "CheckAudioFormat.hxx"
|
#include "CheckAudioFormat.hxx"
|
||||||
#include "util/bit_reverse.h"
|
#include "util/bit_reverse.h"
|
||||||
#include "system/ByteOrder.hxx"
|
#include "util/ByteOrder.hxx"
|
||||||
#include "DsdLib.hxx"
|
#include "DsdLib.hxx"
|
||||||
#include "tag/Handler.hxx"
|
#include "tag/Handler.hxx"
|
||||||
#include "Log.hxx"
|
#include "Log.hxx"
|
||||||
@@ -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;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -325,7 +326,7 @@ dsf_stream_decode(DecoderClient &client, InputStream &is)
|
|||||||
}
|
}
|
||||||
|
|
||||||
static bool
|
static bool
|
||||||
dsf_scan_stream(InputStream &is, TagHandler &handler) noexcept
|
dsf_scan_stream(InputStream &is, TagHandler &handler)
|
||||||
{
|
{
|
||||||
/* check DSF metadata */
|
/* check DSF metadata */
|
||||||
DsfMetaData metadata;
|
DsfMetaData metadata;
|
||||||
|
@@ -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>
|
||||||
@@ -414,7 +414,7 @@ faad_stream_decode(DecoderClient &client, InputStream &is)
|
|||||||
}
|
}
|
||||||
|
|
||||||
static bool
|
static bool
|
||||||
faad_scan_stream(InputStream &is, TagHandler &handler) noexcept
|
faad_scan_stream(InputStream &is, TagHandler &handler)
|
||||||
{
|
{
|
||||||
auto result = faad_get_file_time(is);
|
auto result = faad_get_file_time(is);
|
||||||
if (!result.first)
|
if (!result.first)
|
||||||
|
@@ -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
|
||||||
@@ -664,8 +646,7 @@ ffmpeg_decode(DecoderClient &client, InputStream &input)
|
|||||||
}
|
}
|
||||||
|
|
||||||
static bool
|
static bool
|
||||||
FfmpegScanStream(AVFormatContext &format_context,
|
FfmpegScanStream(AVFormatContext &format_context, TagHandler &handler)
|
||||||
TagHandler &handler) noexcept
|
|
||||||
{
|
{
|
||||||
const int find_result =
|
const int find_result =
|
||||||
avformat_find_stream_info(&format_context, nullptr);
|
avformat_find_stream_info(&format_context, nullptr);
|
||||||
@@ -698,7 +679,7 @@ FfmpegScanStream(AVFormatContext &format_context,
|
|||||||
}
|
}
|
||||||
|
|
||||||
static bool
|
static bool
|
||||||
ffmpeg_scan_stream(InputStream &is, TagHandler &handler) noexcept
|
ffmpeg_scan_stream(InputStream &is, TagHandler &handler)
|
||||||
{
|
{
|
||||||
AvioStream stream(nullptr, is);
|
AvioStream stream(nullptr, is);
|
||||||
if (!stream.Open())
|
if (!stream.Open())
|
||||||
@@ -762,7 +743,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 +756,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 +782,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",
|
||||||
|
@@ -69,7 +69,7 @@ flac_write_cb(const FLAC__StreamDecoder *dec, const FLAC__Frame *frame,
|
|||||||
}
|
}
|
||||||
|
|
||||||
static bool
|
static bool
|
||||||
flac_scan_file(Path path_fs, TagHandler &handler) noexcept
|
flac_scan_file(Path path_fs, TagHandler &handler)
|
||||||
{
|
{
|
||||||
FlacMetadataChain chain;
|
FlacMetadataChain chain;
|
||||||
if (!chain.Read(NarrowPath(path_fs))) {
|
if (!chain.Read(NarrowPath(path_fs))) {
|
||||||
@@ -84,7 +84,7 @@ flac_scan_file(Path path_fs, TagHandler &handler) noexcept
|
|||||||
}
|
}
|
||||||
|
|
||||||
static bool
|
static bool
|
||||||
flac_scan_stream(InputStream &is, TagHandler &handler) noexcept
|
flac_scan_stream(InputStream &is, TagHandler &handler)
|
||||||
{
|
{
|
||||||
FlacMetadataChain chain;
|
FlacMetadataChain chain;
|
||||||
if (!chain.Read(is)) {
|
if (!chain.Read(is)) {
|
||||||
@@ -313,7 +313,7 @@ oggflac_init(gcc_unused const ConfigBlock &block)
|
|||||||
}
|
}
|
||||||
|
|
||||||
static bool
|
static bool
|
||||||
oggflac_scan_file(Path path_fs, TagHandler &handler) noexcept
|
oggflac_scan_file(Path path_fs, TagHandler &handler)
|
||||||
{
|
{
|
||||||
FlacMetadataChain chain;
|
FlacMetadataChain chain;
|
||||||
if (!chain.ReadOgg(NarrowPath(path_fs))) {
|
if (!chain.ReadOgg(NarrowPath(path_fs))) {
|
||||||
@@ -328,7 +328,7 @@ oggflac_scan_file(Path path_fs, TagHandler &handler) noexcept
|
|||||||
}
|
}
|
||||||
|
|
||||||
static bool
|
static bool
|
||||||
oggflac_scan_stream(InputStream &is, TagHandler &handler) noexcept
|
oggflac_scan_stream(InputStream &is, TagHandler &handler)
|
||||||
{
|
{
|
||||||
FlacMetadataChain chain;
|
FlacMetadataChain chain;
|
||||||
if (!chain.ReadOgg(is)) {
|
if (!chain.ReadOgg(is)) {
|
||||||
|
@@ -70,15 +70,15 @@ fluidsynth_mpd_log_function(int level,
|
|||||||
char *message,
|
char *message,
|
||||||
void *)
|
void *)
|
||||||
{
|
{
|
||||||
Log(fluidsynth_domain,
|
Log(fluidsynth_level_to_mpd(fluid_log_level(level)),
|
||||||
fluidsynth_level_to_mpd(fluid_log_level(level)),
|
fluidsynth_domain,
|
||||||
message);
|
message);
|
||||||
}
|
}
|
||||||
|
|
||||||
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;
|
||||||
|
|
||||||
|
@@ -20,7 +20,7 @@
|
|||||||
#include "HybridDsdDecoderPlugin.hxx"
|
#include "HybridDsdDecoderPlugin.hxx"
|
||||||
#include "../DecoderAPI.hxx"
|
#include "../DecoderAPI.hxx"
|
||||||
#include "input/InputStream.hxx"
|
#include "input/InputStream.hxx"
|
||||||
#include "system/ByteOrder.hxx"
|
#include "util/ByteOrder.hxx"
|
||||||
#include "util/Domain.hxx"
|
#include "util/Domain.hxx"
|
||||||
#include "util/WritableBuffer.hxx"
|
#include "util/WritableBuffer.hxx"
|
||||||
#include "util/StaticFifoBuffer.hxx"
|
#include "util/StaticFifoBuffer.hxx"
|
||||||
@@ -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;
|
||||||
|
|
||||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user