Compare commits
724 Commits
Author | SHA1 | Date | |
---|---|---|---|
![]() |
fff25ac753 | ||
![]() |
4f1e79b6b8 | ||
![]() |
aa9933c0b5 | ||
![]() |
0697d1f859 | ||
![]() |
df033fa4aa | ||
![]() |
b941a7df83 | ||
![]() |
31151cec3c | ||
![]() |
07e8c338df | ||
![]() |
b22d7218aa | ||
![]() |
d5be8c74b0 | ||
![]() |
c112cb60da | ||
![]() |
677fa4f9bc | ||
![]() |
907af2ad02 | ||
![]() |
6a2e7bbc02 | ||
![]() |
771c46032f | ||
![]() |
85611aa456 | ||
![]() |
466b5cb08d | ||
![]() |
3f2f3251cb | ||
![]() |
8ae85f3991 | ||
![]() |
781fe4ff28 | ||
![]() |
163c59128e | ||
![]() |
608896571c | ||
![]() |
2e5ca1cbd2 | ||
![]() |
680fb51c37 | ||
![]() |
77d74b404e | ||
![]() |
a636d2127a | ||
![]() |
9a766f4cd9 | ||
![]() |
8ad17d25ef | ||
![]() |
46d00dd85f | ||
![]() |
ec52b13449 | ||
![]() |
cf6ca1b0ba | ||
![]() |
37bd6de658 | ||
![]() |
f7622ca332 | ||
![]() |
b82b56970b | ||
![]() |
e4eb5b79c9 | ||
![]() |
1cfea20b22 | ||
![]() |
efa3ffa8d8 | ||
![]() |
1b8c94d6b9 | ||
![]() |
cd5c1f3f45 | ||
![]() |
9200fa6d06 | ||
![]() |
1bbe9896f6 | ||
![]() |
2d8847f428 | ||
![]() |
72f6e018e7 | ||
![]() |
2fbbd540bb | ||
![]() |
18f64b5fb7 | ||
![]() |
d2a8b1e8a5 | ||
![]() |
184e8eca7c | ||
![]() |
0a8886704a | ||
![]() |
0712314d23 | ||
![]() |
f8cbba1850 | ||
![]() |
635ec3ce37 | ||
![]() |
8e71130e8a | ||
![]() |
ed7baf3ae1 | ||
![]() |
1e159af2ce | ||
![]() |
dffed6e393 | ||
![]() |
bf656af555 | ||
![]() |
d2b8852d19 | ||
![]() |
7d4de71899 | ||
![]() |
e1c16d78e4 | ||
![]() |
a49b49cba7 | ||
![]() |
f510564d9d | ||
![]() |
1c4b484a56 | ||
![]() |
b394d8d059 | ||
![]() |
a15c1c71d5 | ||
![]() |
8d679e7e00 | ||
![]() |
2b30ac2351 | ||
![]() |
1c97793b49 | ||
![]() |
4dae8b41da | ||
![]() |
be8ed2f59e | ||
![]() |
08491fcd86 | ||
![]() |
abed633fcb | ||
![]() |
db2a9cb6d5 | ||
![]() |
7caeb3b0d8 | ||
![]() |
08500be239 | ||
![]() |
3ef7d8fecb | ||
![]() |
ffde7223b9 | ||
![]() |
4e84fa4a00 | ||
![]() |
78e49928b6 | ||
![]() |
c0bcfe244c | ||
![]() |
e63ecd81ec | ||
![]() |
c47a858d15 | ||
![]() |
076c9a0dd9 | ||
![]() |
3993176b76 | ||
![]() |
16cad48641 | ||
![]() |
7a6d0c2efc | ||
![]() |
f6035f2dda | ||
![]() |
c34a1e29de | ||
![]() |
41a69027c2 | ||
![]() |
711c614528 | ||
![]() |
6acb240f69 | ||
![]() |
45f3dd8b7a | ||
![]() |
acc1bd6297 | ||
![]() |
49ed9dae34 | ||
![]() |
cf554d306d | ||
![]() |
ef24cfa523 | ||
![]() |
5d35983298 | ||
![]() |
2dacb36789 | ||
![]() |
57a1403f08 | ||
![]() |
bad3283182 | ||
![]() |
6ed4aff4d3 | ||
![]() |
e525465592 | ||
![]() |
10782f4c84 | ||
![]() |
2a02576d6d | ||
![]() |
9ea1578a97 | ||
![]() |
520028dcfc | ||
![]() |
e98cef06c7 | ||
![]() |
aef0535c55 | ||
![]() |
6b1d0cb01d | ||
![]() |
f23ecf00da | ||
![]() |
a1c1e26875 | ||
![]() |
410b8711f2 | ||
![]() |
6acf81d5ae | ||
![]() |
4eb56d844e | ||
![]() |
5faf6d061f | ||
![]() |
d5a9f6d79d | ||
![]() |
2699889342 | ||
![]() |
e4f933361e | ||
![]() |
6f278977e9 | ||
![]() |
4f2f705dca | ||
![]() |
f31e38145d | ||
![]() |
0231622169 | ||
![]() |
937423dbcf | ||
![]() |
40483d8478 | ||
![]() |
6ec5089cc9 | ||
![]() |
15f419e1cb | ||
![]() |
bdd8c34c67 | ||
![]() |
c9a9248c9f | ||
![]() |
31f7fede30 | ||
![]() |
917fe549b0 | ||
![]() |
8e430e55af | ||
![]() |
9e61bda592 | ||
![]() |
56997290d7 | ||
![]() |
d2f84f3df8 | ||
![]() |
9da28e5c73 | ||
![]() |
d1f9b06f84 | ||
![]() |
f9f3306db9 | ||
![]() |
19d19cd737 | ||
![]() |
b1175acb59 | ||
![]() |
672278e5fd | ||
![]() |
da155f8822 | ||
![]() |
4026ef63b6 | ||
![]() |
b282682ba5 | ||
![]() |
ad00926e1b | ||
![]() |
0b774df375 | ||
![]() |
53ffcf455c | ||
![]() |
9ca64d5fb3 | ||
![]() |
bd79354f32 | ||
![]() |
49dcac5c21 | ||
![]() |
38a4b0d8d5 | ||
![]() |
a224225e48 | ||
![]() |
7d7fe756b3 | ||
![]() |
1cb7fe12ff | ||
![]() |
8a29805767 | ||
![]() |
94c196108d | ||
![]() |
263d1ba002 | ||
![]() |
2dba06dc34 | ||
![]() |
811860c3b4 | ||
![]() |
8439119e24 | ||
![]() |
b5b40d8235 | ||
![]() |
b904f8af03 | ||
![]() |
ebfbb74f9e | ||
![]() |
7b4225aa1f | ||
![]() |
71a5311b06 | ||
![]() |
a62a35e1db | ||
![]() |
ca2439f595 | ||
![]() |
f9a0db716a | ||
![]() |
34aa67ea87 | ||
![]() |
18be8c3318 | ||
![]() |
1d7a8f992f | ||
![]() |
da1783cdff | ||
![]() |
20d74bb07e | ||
![]() |
0f7a0b04ca | ||
![]() |
40c6a214e3 | ||
![]() |
cfe024ea13 | ||
![]() |
993d85125e | ||
![]() |
bedcf1cce5 | ||
![]() |
30e3ef4c8e | ||
![]() |
4c5fea96e4 | ||
![]() |
46600931e4 | ||
![]() |
a2387210bf | ||
![]() |
d7e7adb496 | ||
![]() |
45354a421c | ||
![]() |
9fc3c60910 | ||
![]() |
1976003e91 | ||
![]() |
488afc47d4 | ||
![]() |
017814adc7 | ||
![]() |
7f94af8b2c | ||
![]() |
09d74f05c3 | ||
![]() |
1af8694ef6 | ||
![]() |
b8eb9b466a | ||
![]() |
bd9e449b69 | ||
![]() |
f3d67115d7 | ||
![]() |
ee6603ed38 | ||
![]() |
0dacde32f2 | ||
![]() |
528e05f025 | ||
![]() |
269583f5dd | ||
![]() |
7c9f4f7e4f | ||
![]() |
00fd692eba | ||
![]() |
668c3782b2 | ||
![]() |
1e0af2dadf | ||
![]() |
4ea2ea2a52 | ||
![]() |
8a243e6e28 | ||
![]() |
d33aa01000 | ||
![]() |
bd893e6336 | ||
![]() |
64c39af556 | ||
![]() |
04eb911a51 | ||
![]() |
351b39e0c5 | ||
![]() |
3b6d4e6673 | ||
![]() |
e8f328d8ad | ||
![]() |
5f5b5f63af | ||
![]() |
ad6e303047 | ||
![]() |
b0e9538855 | ||
![]() |
694debd4cc | ||
![]() |
0f56ddb805 | ||
![]() |
dde77ec6bd | ||
![]() |
5d73eda115 | ||
![]() |
1985786ed2 | ||
![]() |
8e0d39ae94 | ||
![]() |
1761fb14af | ||
![]() |
ef2fc4e6f6 | ||
![]() |
b979245d6c | ||
![]() |
a74b07728e | ||
![]() |
7d69cbbda7 | ||
![]() |
955502f881 | ||
![]() |
dee5d1b87b | ||
![]() |
d42342e0ba | ||
![]() |
8da3f8c6a7 | ||
![]() |
c8c553c75c | ||
![]() |
c97aabe43a | ||
![]() |
17b0ac75ca | ||
![]() |
bde64a13e2 | ||
![]() |
96875921b7 | ||
![]() |
551c941b5a | ||
![]() |
624c77ab43 | ||
![]() |
ba13b4b5d6 | ||
![]() |
4b2d9e544c | ||
![]() |
97c43954e8 | ||
![]() |
b77acd35f7 | ||
![]() |
4873159872 | ||
![]() |
968624035c | ||
![]() |
b838bf3109 | ||
![]() |
4d1ce7023b | ||
![]() |
52577ac87a | ||
![]() |
9fa3984a2f | ||
![]() |
239698cb5a | ||
![]() |
e55de6e9f0 | ||
![]() |
cfaf2ed03c | ||
![]() |
6015960871 | ||
![]() |
26328cc915 | ||
![]() |
cd512f0b40 | ||
![]() |
be14dd59a8 | ||
![]() |
97e5787ff7 | ||
![]() |
6975d3ca4b | ||
![]() |
cdca27e6bb | ||
![]() |
5355335f19 | ||
![]() |
5b775ca5b4 | ||
![]() |
ea95da3b1a | ||
![]() |
57687779be | ||
![]() |
64fa76c568 | ||
![]() |
19a44076cf | ||
![]() |
809a18913a | ||
![]() |
5eab2d96f4 | ||
![]() |
716784f632 | ||
![]() |
d39b11ba5d | ||
![]() |
b29a43b4d7 | ||
![]() |
f60a42e0b6 | ||
![]() |
85b0029ba2 | ||
![]() |
0e0f46a1e0 | ||
![]() |
6f539cfcd6 | ||
![]() |
0185d58a2b | ||
![]() |
eb630ca655 | ||
![]() |
d7df5e1c90 | ||
![]() |
e4e4576a39 | ||
![]() |
18628bf89e | ||
![]() |
2052b461af | ||
![]() |
5019bdcd52 | ||
![]() |
8be0bcbdb9 | ||
![]() |
af72a22ed8 | ||
![]() |
6ed9668fea | ||
![]() |
175d2c6d29 | ||
![]() |
f789451007 | ||
![]() |
36680607d0 | ||
![]() |
fc54877c6b | ||
![]() |
6af7be4a45 | ||
![]() |
ab487b9a99 | ||
![]() |
ac59ec34f9 | ||
![]() |
82da57b7ce | ||
![]() |
aa6dac9bd2 | ||
![]() |
220d2bf026 | ||
![]() |
9ef1cf15a9 | ||
![]() |
679b3bc00f | ||
![]() |
1f9e32c35e | ||
![]() |
36410daaa4 | ||
![]() |
38bfef7d0b | ||
![]() |
724754f16c | ||
![]() |
4d32454697 | ||
![]() |
4db882f666 | ||
![]() |
a83bf97b98 | ||
![]() |
262e1957b7 | ||
![]() |
792411384d | ||
![]() |
78b0ff83e8 | ||
![]() |
23613355f3 | ||
![]() |
0d97eba7a5 | ||
![]() |
18efda719e | ||
![]() |
42239a30eb | ||
![]() |
a26bf261a9 | ||
![]() |
c692286c67 | ||
![]() |
43a9dc7082 | ||
![]() |
6f64fa070d | ||
![]() |
dc5b9d989b | ||
![]() |
9e407f5989 | ||
![]() |
fec6aac0f1 | ||
![]() |
541c31c879 | ||
![]() |
4ee0a06e18 | ||
![]() |
3775766605 | ||
![]() |
38e24208f6 | ||
![]() |
fbaedf2262 | ||
![]() |
8f3341cefb | ||
![]() |
4ec4bab3a9 | ||
![]() |
6d567bcd35 | ||
![]() |
4f75eb9bfe | ||
![]() |
d2bd12822f | ||
![]() |
363d9f0180 | ||
![]() |
db0682a469 | ||
![]() |
7a6823dcdf | ||
![]() |
bce144a232 | ||
![]() |
0cef84cac6 | ||
![]() |
56c0733b42 | ||
![]() |
0b0acb3981 | ||
![]() |
1375dcc4ec | ||
![]() |
6aeb0e335b | ||
![]() |
c1e2537851 | ||
![]() |
8c690fb737 | ||
![]() |
dad1c21b59 | ||
![]() |
dd10b2bd61 | ||
![]() |
48c7c540df | ||
![]() |
281270cd2a | ||
![]() |
02502514f6 | ||
![]() |
1bc02123f9 | ||
![]() |
3488a47c41 | ||
![]() |
fd82d67678 | ||
![]() |
e66c12105b | ||
![]() |
8a9d678bac | ||
![]() |
dbe12a6b90 | ||
![]() |
0440c41cba | ||
![]() |
a9c704b76e | ||
![]() |
d3a680cc87 | ||
![]() |
62fc4d5cf4 | ||
![]() |
0cca1b138c | ||
![]() |
d3576a1b71 | ||
![]() |
96707c0426 | ||
![]() |
e016cc8940 | ||
![]() |
34f636ffc3 | ||
![]() |
a134f692bf | ||
![]() |
d747576793 | ||
![]() |
d9578f6427 | ||
![]() |
b2cec7a0a3 | ||
![]() |
85db2d6704 | ||
![]() |
22ebb2bdd5 | ||
![]() |
e108568082 | ||
![]() |
360381e65d | ||
![]() |
3ead778664 | ||
![]() |
4fc08e39b4 | ||
![]() |
a2bdac571a | ||
![]() |
87fa6bca54 | ||
![]() |
c3226a3195 | ||
![]() |
51671af5a4 | ||
![]() |
2908f6565b | ||
![]() |
a0334d1d94 | ||
![]() |
423f2df5e0 | ||
![]() |
0122dc8452 | ||
![]() |
95ad1b0cc6 | ||
![]() |
52f46b94e9 | ||
![]() |
e07e0bc9c1 | ||
![]() |
4a1c231734 | ||
![]() |
fd0e958e95 | ||
![]() |
3d814115c8 | ||
![]() |
ca726a0110 | ||
![]() |
e01710cbd1 | ||
![]() |
c87a4a7d08 | ||
![]() |
b59170b702 | ||
![]() |
a237db556a | ||
![]() |
285ba54fe5 | ||
![]() |
ee86434a89 | ||
![]() |
95d5efbfe6 | ||
![]() |
c33f206ce8 | ||
![]() |
2d95ac2e94 | ||
![]() |
f58c14a74a | ||
![]() |
a52ce7bb7b | ||
![]() |
16d187b7ed | ||
![]() |
296ec4d07c | ||
![]() |
6e58fd1583 | ||
![]() |
c5fec4ac2a | ||
![]() |
fe2ca1ddef | ||
![]() |
e960626804 | ||
![]() |
7dd2dce6ad | ||
![]() |
a7ba10423d | ||
![]() |
8f1e7385b7 | ||
![]() |
25354b9d8c | ||
![]() |
ee720064a7 | ||
![]() |
e1b62fb90d | ||
![]() |
422cf5f182 | ||
![]() |
ef1acb4e2f | ||
![]() |
d4bbb8c851 | ||
![]() |
428f769c38 | ||
![]() |
133c8834df | ||
![]() |
99217593bf | ||
![]() |
1c6e4a2b18 | ||
![]() |
a6eb264770 | ||
![]() |
f5f296b13a | ||
![]() |
0091c4e12b | ||
![]() |
80172e17ac | ||
![]() |
2d96b05403 | ||
![]() |
ec0c1f0d02 | ||
![]() |
946b3c1f80 | ||
![]() |
a0dc398f36 | ||
![]() |
b54d2d984a | ||
![]() |
4ab73f9de9 | ||
![]() |
5ebe23e4bb | ||
![]() |
aa227cded1 | ||
![]() |
e406bdbb80 | ||
![]() |
1048f23680 | ||
![]() |
8fe8f09027 | ||
![]() |
78670c0941 | ||
![]() |
34f735890e | ||
![]() |
f08810b202 | ||
![]() |
7a68775e6c | ||
![]() |
e4fccc85c8 | ||
![]() |
2efa142ec9 | ||
![]() |
29b49dd630 | ||
![]() |
9d6bf7e720 | ||
![]() |
5f34508aae | ||
![]() |
2d8ecd561b | ||
![]() |
2059195ae9 | ||
![]() |
d89856f77b | ||
![]() |
975d5be046 | ||
![]() |
b01ef1b9a6 | ||
![]() |
ceb76b6a82 | ||
![]() |
a7e697b588 | ||
![]() |
3ecd918442 | ||
![]() |
4fbdb3a2d5 | ||
![]() |
0157643667 | ||
![]() |
fe741bd767 | ||
![]() |
06b9bdba2c | ||
![]() |
bd0aa74bdd | ||
![]() |
47461df59c | ||
![]() |
04d5588fe5 | ||
![]() |
40d061621b | ||
![]() |
a312629aad | ||
![]() |
d527d4b530 | ||
![]() |
978d2638d8 | ||
![]() |
cfcafdf822 | ||
![]() |
07865d0707 | ||
![]() |
1ac16516a1 | ||
![]() |
75e8795e3f | ||
![]() |
4912466d50 | ||
![]() |
664674913e | ||
![]() |
31e3658823 | ||
![]() |
abd416735d | ||
![]() |
6090bd2095 | ||
![]() |
1777592ec0 | ||
![]() |
8e8fbe14b1 | ||
![]() |
a8a39b6a38 | ||
![]() |
f84cb6de5e | ||
![]() |
dfc67c45c7 | ||
![]() |
e875da5d38 | ||
![]() |
9b9522e3f5 | ||
![]() |
87963685fb | ||
![]() |
0405a57f26 | ||
![]() |
f29c69d6a9 | ||
![]() |
7ec4de841e | ||
![]() |
1f08d2d03c | ||
![]() |
c1a695d1ac | ||
![]() |
ec05056e38 | ||
![]() |
c0b9339d31 | ||
![]() |
00a1731085 | ||
![]() |
6e3da00874 | ||
![]() |
38df01b266 | ||
![]() |
c729f16dcd | ||
![]() |
81d0c04ed4 | ||
![]() |
0924b63e10 | ||
![]() |
ce6afe9379 | ||
![]() |
6f04b2230a | ||
![]() |
8d90b831e1 | ||
![]() |
d4710604c4 | ||
![]() |
9c8da03c5c | ||
![]() |
85adefd9a4 | ||
![]() |
22804cfbe8 | ||
![]() |
8a4b88a59d | ||
![]() |
d2371af120 | ||
![]() |
aa2e1bb310 | ||
![]() |
6153fca4fc | ||
![]() |
f090af0a22 | ||
![]() |
58f420fdca | ||
![]() |
ded2b31fbc | ||
![]() |
75c8d2235b | ||
![]() |
f679961564 | ||
![]() |
471c37be59 | ||
![]() |
157ddcbab1 | ||
![]() |
ab160aa359 | ||
![]() |
ecc07e4e98 | ||
![]() |
cbc830fd65 | ||
![]() |
98a9f81d61 | ||
![]() |
f5460b35a3 | ||
![]() |
3456b1e50d | ||
![]() |
fe6abe1750 | ||
![]() |
6cdb3ff21e | ||
![]() |
01af2778ab | ||
![]() |
ad03c70753 | ||
![]() |
7fe0095fa7 | ||
![]() |
a4b236348f | ||
![]() |
aa40aae5bd | ||
![]() |
5a16e3ffa3 | ||
![]() |
d1957b83c8 | ||
![]() |
1b4fd74575 | ||
![]() |
def962b6cb | ||
![]() |
e802f1f61a | ||
![]() |
271b287356 | ||
![]() |
2a30acd99c | ||
![]() |
a8e70f18fd | ||
![]() |
ddd9f20a0b | ||
![]() |
f4a5d671fe | ||
![]() |
c72006dbcc | ||
![]() |
06fe30e2bd | ||
![]() |
08e76815ba | ||
![]() |
33ac3eb551 | ||
![]() |
d56a51cb5e | ||
![]() |
065a0c09f8 | ||
![]() |
04731fb7cc | ||
![]() |
12ff5a547f | ||
![]() |
9b2eb74f95 | ||
![]() |
84084baa65 | ||
![]() |
3bc45fbf68 | ||
![]() |
36168a24f5 | ||
![]() |
5e67443a1a | ||
![]() |
17858143b3 | ||
![]() |
c44a7b2705 | ||
![]() |
0ded23591b | ||
![]() |
c1a7aa652d | ||
![]() |
8d47f51399 | ||
![]() |
a81c9bfb81 | ||
![]() |
1caf57644f | ||
![]() |
c70b63c183 | ||
![]() |
1b89b4ef83 | ||
![]() |
8279cafd6d | ||
![]() |
014c2a82bd | ||
![]() |
594dfe572b | ||
![]() |
906e82f600 | ||
![]() |
bcb7e954e9 | ||
![]() |
866c87c65e | ||
![]() |
4ba36d7cb9 | ||
![]() |
13f8a912e3 | ||
![]() |
51f110a990 | ||
![]() |
17eae74c1c | ||
![]() |
cd4b673b6c | ||
![]() |
0d606c743b | ||
![]() |
81ea749248 | ||
![]() |
e009ad1a72 | ||
![]() |
abbd980671 | ||
![]() |
937da63ba6 | ||
![]() |
1f312b2e42 | ||
![]() |
1e3089ffb7 | ||
![]() |
5d7ff150dd | ||
![]() |
c767501c12 | ||
![]() |
00602d28a4 | ||
![]() |
7a56837141 | ||
![]() |
ed1caffc79 | ||
![]() |
65473b5113 | ||
![]() |
178d115ccb | ||
![]() |
10e5b0759c | ||
![]() |
0a81e462db | ||
![]() |
5cbbe8ae2e | ||
![]() |
00fafa16c7 | ||
![]() |
cea8db7eaa | ||
![]() |
b56c0e69e4 | ||
![]() |
b27e82e4a9 | ||
![]() |
ad48834469 | ||
![]() |
9d6b5e2ba1 | ||
![]() |
33ba190bec | ||
![]() |
3783350d25 | ||
![]() |
173405a343 | ||
![]() |
7bc1c9925b | ||
![]() |
ce4c69dd95 | ||
![]() |
8eea825462 | ||
![]() |
49e1ce7c43 | ||
![]() |
618f94f589 | ||
![]() |
ad2c22844c | ||
![]() |
b8df851414 | ||
![]() |
a584141cae | ||
![]() |
4e88f95f94 | ||
![]() |
790e540c19 | ||
![]() |
16074c565f | ||
![]() |
2a1dd55b11 | ||
![]() |
be20f760ab | ||
![]() |
8050394003 | ||
![]() |
ff8b5bc61b | ||
![]() |
ef8797821f | ||
![]() |
5f2797e7cc | ||
![]() |
e286702f4c | ||
![]() |
c58aaf545f | ||
![]() |
990f2dc1cf | ||
![]() |
774b4313f2 | ||
![]() |
1ecbc2ff0f | ||
![]() |
fd8e38f8cd | ||
![]() |
e86d4db55c | ||
![]() |
9420c74101 | ||
![]() |
b1bef9c21d | ||
![]() |
5b0ef7ea98 | ||
![]() |
ab53c414bc | ||
![]() |
d547ace749 | ||
![]() |
b47e0cffdd | ||
![]() |
3af35aee9e | ||
![]() |
02314ac7dd | ||
![]() |
e7c4e87ac4 | ||
![]() |
de58bfbb7f | ||
![]() |
0dda4c06b1 | ||
![]() |
79fd6143ec | ||
![]() |
8f89e3f7f4 | ||
![]() |
fc01d11b8d | ||
![]() |
0c28d8dcbe | ||
![]() |
764eaadd25 | ||
![]() |
273771ffec | ||
![]() |
32ce9ce919 | ||
![]() |
34a070f5a6 | ||
![]() |
ac4975cd7a | ||
![]() |
fbbbfb9668 | ||
![]() |
eb9f5339b6 | ||
![]() |
a9714e73c8 | ||
![]() |
a99bc91eb0 | ||
![]() |
071d3c71d8 | ||
![]() |
afbcac9fb1 | ||
![]() |
51e5b56b3a | ||
![]() |
bb07fd42ce | ||
![]() |
bab626c325 | ||
![]() |
2a9131498f | ||
![]() |
35a232105e | ||
![]() |
19dd1a25d7 | ||
![]() |
53396c0e50 | ||
![]() |
0b8208fe7f | ||
![]() |
3d276d50b4 | ||
![]() |
b1b731340e | ||
![]() |
b9b02b4ff2 | ||
![]() |
ab5d23da11 | ||
![]() |
0554fe3652 | ||
![]() |
b0282fe36f | ||
![]() |
69b45e693b | ||
![]() |
9e97acc28d | ||
![]() |
b1e446a931 | ||
![]() |
938319cd44 | ||
![]() |
fee29001fa | ||
![]() |
6d894a1806 | ||
![]() |
f1fc5d79ca | ||
![]() |
0fd2c74a66 | ||
![]() |
bb99cf37e3 | ||
![]() |
7c47fe746c | ||
![]() |
65a1c4a016 | ||
![]() |
46418d0f2d | ||
![]() |
ad585e179f | ||
![]() |
8348a1ec8f | ||
![]() |
c18e00daa4 | ||
![]() |
418ba96334 | ||
![]() |
a60e782959 | ||
![]() |
8bab5733d7 | ||
![]() |
e3270dfd68 | ||
![]() |
a14997ffb8 | ||
![]() |
dd94f97572 | ||
![]() |
7d502fb448 | ||
![]() |
3ac87bbcda | ||
![]() |
f64799622d | ||
![]() |
6f0ad2b6c5 | ||
![]() |
b5750afb24 | ||
![]() |
442dd5e955 | ||
![]() |
172c2ae1aa | ||
![]() |
4f0e0af319 | ||
![]() |
cb382b1e7d | ||
![]() |
871bf3b88f | ||
![]() |
08360e401d | ||
![]() |
b611b1824a | ||
![]() |
1473d8474f | ||
![]() |
0ecc3394c3 | ||
![]() |
725985379a | ||
![]() |
8849b9b62c | ||
![]() |
caa2611ad5 | ||
![]() |
f8ff597963 | ||
![]() |
ff6e434caf | ||
![]() |
95bb12880d | ||
![]() |
257196664a | ||
![]() |
643bf95366 | ||
![]() |
36a187da39 | ||
![]() |
fec80f2835 | ||
![]() |
4e47653cf6 | ||
![]() |
c13004f985 | ||
![]() |
baba41e304 | ||
![]() |
d87e09a8b4 | ||
![]() |
33146ac353 | ||
![]() |
bb20af8f20 | ||
![]() |
9355ec44e0 | ||
![]() |
c63bd323ce | ||
![]() |
55db7105c5 | ||
![]() |
1c079e554b | ||
![]() |
48afb68f3a | ||
![]() |
21f409d5e2 | ||
![]() |
521e573be9 | ||
![]() |
abf9ae2dd9 | ||
![]() |
9f013f7de4 | ||
![]() |
7fc04fd5cd | ||
![]() |
7901b04c78 | ||
![]() |
653eea5840 | ||
![]() |
5a4055fb08 | ||
![]() |
4d68a12f03 | ||
![]() |
0e951da64b | ||
![]() |
38dab040b3 | ||
![]() |
e9f6af61f9 | ||
![]() |
b06c4e2711 | ||
![]() |
1686f4e857 | ||
![]() |
9f57732af2 | ||
![]() |
329382c1da | ||
![]() |
fadc03df21 | ||
![]() |
54ee0e28ab | ||
![]() |
92fc53ebef | ||
![]() |
7e7a1613cf | ||
![]() |
f73c4643ef | ||
![]() |
8b94e8f260 | ||
![]() |
41bc17a27f |
.travis.ymlNEWSREADME.md
android
doc
meson.buildmeson_options.txtpython/build
autotools.pyboost.pycmake.pyffmpeg.pyjack.pylibs.pymakeproject.pymeson.pyopenssl.pyproject.pyverify.pyzlib.py
src
CommandLine.cxxIdleFlags.cxxIdleFlags.hxxInstance.cxxInstance.hxxListen.cxxLocateUri.cxxLog.cxxLog.hxxLogBackend.cxxLogInit.cxxMain.cxxMain.hxxMapper.hxxMixRampInfo.hxxMusicBuffer.hxxMusicChunk.hxxMusicPipe.hxxPartition.cxxPartition.hxxPermission.cxxPermission.hxxPlaylistPrint.cxxPlaylistPrint.hxxRemoteTagCache.cxxRemoteTagCache.hxxReplayGainInfo.hxxReplayGainMode.cxxReplayGainMode.hxxSingleMode.cxxSingleMode.hxxSongLoader.hxxSongPrint.cxxSongSave.cxxSongSave.hxxSongUpdate.cxxStateFile.cxxStateFile.hxxStats.cxxTagAny.cxxTagPrint.cxxTagStream.cxxTimePrint.cxxls.cxxls.hxx
android
archive
client
Client.hxxEvent.cxxExpire.cxxIdle.cxxListener.cxxMessage.hxxNew.cxxProcess.cxxResponse.cxxResponse.hxxThreadBackgroundCommand.cxxThreadBackgroundCommand.hxx
command
AllCommands.cxxClientCommands.cxxCommandError.cxxDatabaseCommands.cxxFileCommands.cxxFingerprintCommands.cxxMessageCommands.cxxNeighborCommands.cxxNeighborCommands.hxxOtherCommands.cxxOtherCommands.hxxPartitionCommands.cxxPlayerCommands.cxxPlaylistCommands.cxxPlaylistCommands.hxxPositionArg.cxxPositionArg.hxxQueueCommands.cxxStorageCommands.cxxTagCommands.cxx
config
db
Count.cxxDatabaseLock.hxxDatabasePrint.cxxInterface.hxxLightDirectory.hxxPlaylistInfo.hxxPlaylistVector.hxxRegistry.cxxRegistry.hxxSelection.hxxmeson.build
plugins
ProxyDatabasePlugin.cxxmeson.build
simple
Directory.cxxDirectory.hxxDirectorySave.cxxExportedSong.hxxSimpleDatabasePlugin.cxxSimpleDatabasePlugin.hxxSong.cxxSong.hxx
upnp
update
decoder
Bridge.cxxBridge.hxxClient.hxxControl.cxxControl.hxxDecoderBuffer.hxxDecoderList.cxxDecoderList.hxxDecoderPlugin.cxxDecoderPlugin.hxxDecoderPrint.cxxThread.cxxmeson.build
plugins
AdPlugDecoderPlugin.cxxAudiofileDecoderPlugin.cxxDsdLib.hxxFaadDecoderPlugin.cxxFfmpegDecoderPlugin.cxxFfmpegMetaData.cxxFlacDecoderPlugin.cxxGmeDecoderPlugin.cxxHybridDsdDecoderPlugin.cxxMadDecoderPlugin.cxxMikmodDecoderPlugin.cxxModCommon.cxxModCommon.hxxModplugDecoderPlugin.cxxMpg123DecoderPlugin.cxxOpenmptDecoderPlugin.cxxOpenmptDecoderPlugin.hxxOpusDecoderPlugin.cxxPcmDecoderPlugin.cxxSidplayDecoderPlugin.cxxSndfileDecoderPlugin.cxxVorbisDecoderPlugin.cxxWavpackDecoderPlugin.cxxmeson.build
encoder
event
Backend.hxxBackendEvents.hxxBufferedSocket.cxxBufferedSocket.hxxCall.cxxChrono.hxxCoarseTimerEvent.cxxCoarseTimerEvent.hxxDeferEvent.cxxDeferEvent.hxxEpollBackend.hxxEpollEvents.hxxFarTimerEvent.hxxFineTimerEvent.cxxFineTimerEvent.hxxFullyBufferedSocket.cxxFullyBufferedSocket.hxxIdleEvent.cxxIdleEvent.hxxInjectEvent.cxxInjectEvent.hxxLoop.cxxLoop.hxxMaskMonitor.cxxMaskMonitor.hxxMultiSocketMonitor.cxxMultiSocketMonitor.hxxPipeEvent.hxxPollBackend.cxxPollBackend.hxxPollEvents.hxxPollResultGeneric.hxxServerSocket.cxxServerSocket.hxxSignalMonitor.cxxSignalMonitor.hxxSocketEvent.cxxSocketEvent.hxxThread.cxxTimerEvent.hxxTimerList.cxxTimerList.hxxTimerWheel.cxxTimerWheel.hxxUringManager.cxxUringManager.hxxWakeFD.hxxWinSelectBackend.cxxWinSelectBackend.hxxWinSelectEvents.hxxmeson.build
filter
fs
AllocatedPath.hxxCharset.cxxCharset.hxxCheckFile.cxxGlob.hxxPath.hxxStandardDirectory.hxxTraits.cxxTraits.hxx
io
input
AsyncInputStream.hxxError.hxxIcyInputStream.cxxIcyInputStream.hxxInit.cxxInputPlugin.hxxInputStream.cxxInputStream.hxxLastInputStream.cxxLastInputStream.hxxRegistry.cxxRegistry.hxx
cache
meson.buildplugins
AlsaInputPlugin.cxxArchiveInputPlugin.cxxCdioParanoiaInputPlugin.cxxCurlInputPlugin.cxxFfmpegInputPlugin.cxxMmsInputPlugin.cxxNfsInputPlugin.cxxQobuzClient.hxxQobuzInputPlugin.cxxTidalError.hxxTidalErrorParser.cxxTidalErrorParser.hxxTidalInputPlugin.cxxTidalLoginRequest.cxxTidalLoginRequest.hxxTidalSessionManager.cxxTidalSessionManager.hxxTidalTagScanner.cxxTidalTagScanner.hxxTidalTrackRequest.cxxTidalTrackRequest.hxxmeson.build
io
java
Class.hxxException.cxxException.hxxFile.cxxFile.hxxGlobal.cxxGlobal.hxxObject.hxxRef.hxxString.cxxString.hxx
lib
alsa
cdio
crypto
curl
dbus
AppendIter.hxxError.hxxFilterHelper.cxxFilterHelper.hxxGlue.cxxGlue.hxxReadIter.hxxTypes.hxxUDisks2.cxxValues.hxxWatch.cxxWatch.hxxmeson.build
expat
ffmpeg
Buffer.hxxChannelLayout.hxxDetectFilterFormat.cxxDetectFilterFormat.hxxFilter.cxxFilter.hxxIOContext.hxxInterleave.cxxLogCallback.cxxLogError.cxxTime.hxxmeson.build
fmt
gcrypt
icu
nfs
pcre
pipewire
pulse
sqlite
systemd
upnp
Action.hxxContentDirectoryService.cxxContentDirectoryService.hxxDiscovery.cxxDiscovery.hxxInit.cxxUniqueIxml.hxxixmlwrap.cxxixmlwrap.hxxmeson.build
xiph
yajl
zlib
mixer
neighbor
net
AddressInfo.cxxAddressInfo.hxxAllocatedSocketAddress.cxxAllocatedSocketAddress.hxxHostParser.hxxIPv4Address.cxxIPv4Address.hxxIPv6Address.cxxIPv6Address.hxxResolver.cxxResolver.hxxSocketAddress.cxxSocketAddress.hxxSocketDescriptor.cxxSocketDescriptor.hxxSocketError.hxxStaticSocketAddress.hxxToString.cxxToString.hxxUniqueSocketDescriptor.hxx
output
Control.cxxControl.hxxFiltered.cxxFiltered.hxxInit.cxxMultipleOutputs.cxxMultipleOutputs.hxxPrint.cxxRegistry.cxxSharedPipeConsumer.hxxSource.cxxState.cxxThread.cxxmeson.build
plugins
AlsaOutputPlugin.cxxAoOutputPlugin.cxxFifoOutputPlugin.cxxHaikuOutputPlugin.cxxJackOutputPlugin.cxxOSXOutputPlugin.cxxOssOutputPlugin.cxxPipeWireOutputPlugin.cxxPipeWireOutputPlugin.hxxPulseOutputPlugin.cxxRecorderOutputPlugin.cxxShoutOutputPlugin.cxxSndioOutputPlugin.cxxWinmmOutputPlugin.hxx
httpd
meson.buildsles
snapcast
Chunk.hxxClient.cxxClient.hxxInternal.hxxProtocol.hxxSnapcastOutputPlugin.cxxSnapcastOutputPlugin.hxxTimestamp.hxx
wasapi
pcm
AudioFormat.hxxBuffer.hxxChannelsConverter.hxxExport.hxxFormatConverter.hxxLibsamplerateResampler.cxxPcmFormat.hxxResampler.hxxSampleFormat.hxxSoxrResampler.cxxVolume.cxxVolume.hxxmeson.build
player
playlist
PlaylistPlugin.cxxPlaylistPlugin.hxxPlaylistRegistry.cxxPlaylistRegistry.hxxPlaylistSong.cxxPlaylistStream.cxxSongEnumerator.hxx
cue
plugins
protocol
queue
song
AndSongFilter.hxxDetachedSong.cxxDetachedSong.hxxFilter.cxxFilter.hxxISongFilter.hxxLightSong.hxxStringFilter.hxx
sticker
storage
CompositeStorage.hxxConfigured.hxxRegistry.cxxRegistry.hxxStorageInterface.hxxStoragePlugin.cxxStoragePlugin.hxxStorageState.cxxmeson.build
plugins
system
Clock.hxxError.hxxEventFD.hxxEventPipe.cxxEventPipe.hxxFatalError.cxxKernelVersion.cxxKernelVersion.hxxmeson.build
tag
Builder.cxxBuilder.hxxFixString.cxxFixString.hxxFormat.cxxFormat.hxxId3Load.cxxId3Scan.cxxId3String.hxxNames.cParseName.hxxPool.hxxRiffFormat.hxxRiffId3.cxxRiffId3.hxxSettings.hxxTable.hxxTag.cxxTag.hxxType.hVorbisComment.hxxmeson.build
thread
time
Calendar.hxxClockCache.hxxConvert.cxxConvert.hxxFileTime.hxxISO8601.cxxISO8601.hxxMath.cxxMath.hxxSystemClock.hxxmeson.build
unix
util
Alloc.cxxAlloc.hxxAllocatedArray.hxxAllocatedString.hxxBindMethod.hxxBitReverse.hxxByteOrder.hxxCast.hxxConstBuffer.hxxDereferenceIterator.hxxException.hxxHexFormat.hxxHugeAllocator.cxxIntrusiveList.hxxIterableSplitString.hxxMimeType.cxxMimeType.hxxOffsetPointer.hxxOptionParser.hxxRuntimeError.hxxSerial.cxxSerial.hxxStringAPI.hxxStringBuffer.hxxStringCompare.cxxStringCompare.hxxStringPointer.hxxStringStrip.cxxStringStrip.hxxStringView.hxxTemplateString.hxxUTF8.cxxUTF8.hxxUriExtract.cxxUriExtract.hxxUriRelative.cxxUriRelative.hxxUriUtil.cxxUriUtil.hxxVarSize.hxxWStringAPI.hxxWStringCompare.hxxWStringView.hxxWritableBuffer.hxxmeson.build
win32
zeroconf
subprojects
test
ContainerScan.cxxRunCurl.cxxRunZeroconf.cxxShutdownHandler.cxxTestMimeType.cxxTestUriExtract.cxxmeson.build
time
util
win32
@@ -11,6 +11,7 @@ jobs:
|
||||
- meson
|
||||
- libgtest-dev
|
||||
- libboost-dev
|
||||
- libfmt-dev
|
||||
|
||||
# Ubuntu Focal (20.04) with GCC 9.3 on big-endian
|
||||
- os: linux
|
||||
@@ -22,6 +23,7 @@ jobs:
|
||||
- meson
|
||||
- libgtest-dev
|
||||
- libboost-dev
|
||||
- libfmt-dev
|
||||
|
||||
# Ubuntu Focal (20.04) with GCC 9.3 on ARM64
|
||||
- os: linux
|
||||
@@ -33,6 +35,7 @@ jobs:
|
||||
- meson
|
||||
- libgtest-dev
|
||||
- libboost-dev
|
||||
- libfmt-dev
|
||||
|
||||
# Ubuntu Trusty (16.04) with GCC 8
|
||||
- os: linux
|
||||
@@ -67,6 +70,7 @@ jobs:
|
||||
packages:
|
||||
- ccache
|
||||
- meson
|
||||
- fmt
|
||||
- googletest
|
||||
- icu4c
|
||||
- ffmpeg
|
||||
|
86
NEWS
86
NEWS
@@ -1,3 +1,89 @@
|
||||
ver 0.23.1 (2021/10/19)
|
||||
* protocol
|
||||
- use decimal notation instead of scientific notation
|
||||
- "load" supports relative positions
|
||||
* output
|
||||
- emit "mixer" idle event when replay gain changes volume
|
||||
- pipewire: emit "mixer" idle events on external volume change
|
||||
- pipewire: attempt to change the graph sample rate
|
||||
- snapcast: fix time stamp bug which caused "Failed to get chunk"
|
||||
* fix libfmt linker problems
|
||||
* fix broken password authentication
|
||||
|
||||
ver 0.23 (2021/10/14)
|
||||
* protocol
|
||||
- new command "getvol"
|
||||
- show the audio format in "playlistinfo"
|
||||
- support "listfiles" with arbitrary storage plugins
|
||||
- support relative positions in "addid"
|
||||
- fix relative positions in "move" and "moveid"
|
||||
- add "position" parameter to "findadd" and "searchadd"
|
||||
- add position parameter to "load"
|
||||
* database
|
||||
- proxy: require MPD 0.20 or later
|
||||
- proxy: require libmpdclient 2.11 or later
|
||||
- proxy: split search into chunks to avoid exceeding the output buffer
|
||||
- simple: add option to hide CUE target songs
|
||||
- upnp: support libnpupnp instead of libupnp
|
||||
* archive
|
||||
- zzip, iso9660: ignore file names which are invalid UTF-8
|
||||
* decoder
|
||||
- openmpt: new plugin
|
||||
- wavpack: fix WVC file support
|
||||
* player
|
||||
- do not cross-fade songs shorter than 20 seconds
|
||||
* output
|
||||
- oss: support DSD over PCM
|
||||
- pipewire: new plugin
|
||||
- snapcast: new plugin
|
||||
* tags
|
||||
- new tags "ComposerSort", "Ensemble", "Movement", "MovementNumber", and "Location"
|
||||
* split permission "player" from "control"
|
||||
* add option "host_permissions"
|
||||
* new build-time dependency: libfmt
|
||||
|
||||
ver 0.22.11 (2021/08/24)
|
||||
* protocol
|
||||
- fix "albumart" crash
|
||||
* filter
|
||||
- ffmpeg: pass "channel_layout" instead of "channels" to buffersrc
|
||||
- ffmpeg: fix "av_buffersink_get_frame() failed: Resource temporarily unavailable"
|
||||
- ffmpeg: support double-precision samples (by converting to single precision)
|
||||
* Android
|
||||
- build with NDK r23
|
||||
- playlist_directory defaults to "/sdcard/Android/data/org.musicpd/files/playlists"
|
||||
|
||||
ver 0.22.10 (2021/08/06)
|
||||
* protocol
|
||||
- support "albumart" for virtual tracks in CUE sheets
|
||||
* database
|
||||
- simple: fix crash bug
|
||||
- simple: fix absolute paths in CUE "as_directory" entries
|
||||
- simple: prune CUE entries from database for non-existent songs
|
||||
* input
|
||||
- curl: fix crash bug after stream with Icy metadata was closed by peer
|
||||
- tidal: remove defunct unmaintained plugin
|
||||
* tags
|
||||
- fix crash caused by bug in TagBuilder and a few potential reference leaks
|
||||
* output
|
||||
- httpd: fix missing tag after seeking into a new song
|
||||
- oss: fix channel order of multi-channel files
|
||||
* mixer
|
||||
- alsa: fix yet more rounding errors
|
||||
|
||||
ver 0.22.9 (2021/06/23)
|
||||
* database
|
||||
- simple: load all .mpdignore files of all parent directories
|
||||
* tags
|
||||
- fix "readcomments" and "readpicture" on remote files with ID3 tags
|
||||
* decoder
|
||||
- ffmpeg: support the tags "sort_album", "album-sort", "artist-sort"
|
||||
- ffmpeg: fix build failure with FFmpeg 3.4
|
||||
* Android
|
||||
- fix auto-start on boot in Android 8 or later
|
||||
* Windows
|
||||
- fix build failure with SQLite
|
||||
|
||||
ver 0.22.8 (2021/05/22)
|
||||
* fix crash bug in "albumart" command (0.22.7 regression)
|
||||
|
||||
|
@@ -14,7 +14,7 @@ For basic installation instructions
|
||||
|
||||
- [Manual](http://www.musicpd.org/doc/user/)
|
||||
- [Forum](http://forum.musicpd.org/)
|
||||
- [IRC](irc://chat.freenode.net/#mpd)
|
||||
- [IRC](ircs://irc.libera.chat:6697/#mpd)
|
||||
- [Bug tracker](https://github.com/MusicPlayerDaemon/MPD/issues/)
|
||||
|
||||
# Developers
|
||||
|
@@ -2,8 +2,8 @@
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
package="org.musicpd"
|
||||
android:installLocation="auto"
|
||||
android:versionCode="56"
|
||||
android:versionName="0.22.8">
|
||||
android:versionCode="61"
|
||||
android:versionName="0.23.1">
|
||||
|
||||
<uses-sdk android:minSdkVersion="21" android:targetSdkVersion="29"/>
|
||||
|
||||
@@ -17,6 +17,8 @@
|
||||
<uses-permission android:name="android.permission.INTERNET"/>
|
||||
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
|
||||
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
|
||||
<uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS" />
|
||||
<uses-permission android:name="android.permission.BLUETOOTH" />
|
||||
|
||||
<application android:allowBackup="true"
|
||||
android:requestLegacyExternalStorage="true"
|
||||
@@ -41,6 +43,7 @@
|
||||
<receiver android:name=".Receiver">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.BOOT_COMPLETED" />
|
||||
<action android:name="android.intent.action.HEADSET_PLUG" />
|
||||
</intent-filter>
|
||||
</receiver>
|
||||
<service android:name=".Main" android:process=":main"/>
|
||||
|
@@ -24,15 +24,13 @@ android_abis = {
|
||||
'armeabi-v7a': {
|
||||
'arch': 'arm-linux-androideabi',
|
||||
'ndk_arch': 'arm',
|
||||
'toolchain_arch': 'arm-linux-androideabi',
|
||||
'llvm_triple': 'armv7-linux-androideabi',
|
||||
'cflags': '-fpic -march=armv7-a -mfpu=vfpv3-d16 -mfloat-abi=softfp',
|
||||
'cflags': '-fpic -mfpu=neon -mfloat-abi=softfp',
|
||||
},
|
||||
|
||||
'arm64-v8a': {
|
||||
'arch': 'aarch64-linux-android',
|
||||
'ndk_arch': 'arm64',
|
||||
'toolchain_arch': 'aarch64-linux-android',
|
||||
'llvm_triple': 'aarch64-linux-android',
|
||||
'cflags': '-fpic',
|
||||
},
|
||||
@@ -40,7 +38,6 @@ android_abis = {
|
||||
'x86': {
|
||||
'arch': 'i686-linux-android',
|
||||
'ndk_arch': 'x86',
|
||||
'toolchain_arch': 'x86',
|
||||
'llvm_triple': 'i686-linux-android',
|
||||
'cflags': '-fPIC -march=i686 -mtune=intel -mssse3 -mfpmath=sse -m32',
|
||||
},
|
||||
@@ -48,7 +45,6 @@ android_abis = {
|
||||
'x86_64': {
|
||||
'arch': 'x86_64-linux-android',
|
||||
'ndk_arch': 'x86_64',
|
||||
'toolchain_arch': 'x86_64',
|
||||
'llvm_triple': 'x86_64-linux-android',
|
||||
'cflags': '-fPIC -m64',
|
||||
},
|
||||
@@ -84,36 +80,29 @@ class AndroidNdkToolchain:
|
||||
ndk_arch = abi_info['ndk_arch']
|
||||
android_api_level = '21'
|
||||
|
||||
# select the NDK compiler
|
||||
gcc_version = '4.9'
|
||||
|
||||
install_prefix = os.path.join(arch_path, 'root')
|
||||
|
||||
self.arch = arch
|
||||
self.actual_arch = arch
|
||||
self.install_prefix = install_prefix
|
||||
|
||||
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_triple = abi_info['llvm_triple'] + android_api_level
|
||||
|
||||
common_flags = '-Os -g'
|
||||
common_flags += ' ' + abi_info['cflags']
|
||||
|
||||
toolchain_bin = os.path.join(toolchain_path, 'bin')
|
||||
llvm_bin = os.path.join(llvm_path, 'bin')
|
||||
self.cc = os.path.join(llvm_bin, 'clang')
|
||||
self.cxx = os.path.join(llvm_bin, 'clang++')
|
||||
common_flags += ' -target ' + llvm_triple + ' -gcc-toolchain ' + toolchain_path
|
||||
common_flags += ' -target ' + llvm_triple
|
||||
|
||||
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.ranlib = os.path.join(toolchain_bin, arch + '-ranlib')
|
||||
self.nm = os.path.join(toolchain_bin, arch + '-nm')
|
||||
self.strip = os.path.join(toolchain_bin, arch + '-strip')
|
||||
self.ar = os.path.join(llvm_bin, 'llvm-ar')
|
||||
self.ranlib = os.path.join(llvm_bin, 'llvm-ranlib')
|
||||
self.nm = os.path.join(llvm_bin, 'llvm-nm')
|
||||
self.strip = os.path.join(llvm_bin, 'llvm-strip')
|
||||
|
||||
self.cflags = common_flags
|
||||
self.cxxflags = common_flags
|
||||
@@ -151,10 +140,7 @@ class AndroidNdkToolchain:
|
||||
# default one on the build host
|
||||
import shutil
|
||||
bin_dir = os.path.join(install_prefix, 'bin')
|
||||
try:
|
||||
os.makedirs(bin_dir)
|
||||
except:
|
||||
pass
|
||||
os.makedirs(bin_dir, exist_ok=True)
|
||||
self.pkg_config = shutil.copy(os.path.join(mpd_path, 'build', 'pkg-config.sh'),
|
||||
os.path.join(bin_dir, 'pkg-config'))
|
||||
self.env['PKG_CONFIG'] = self.pkg_config
|
||||
@@ -164,7 +150,6 @@ from build.libs import *
|
||||
thirdparty_libs = [
|
||||
libmpdclient,
|
||||
libogg,
|
||||
libvorbis,
|
||||
opus,
|
||||
flac,
|
||||
libid3tag,
|
||||
@@ -174,7 +159,6 @@ thirdparty_libs = [
|
||||
ffmpeg,
|
||||
openssl,
|
||||
curl,
|
||||
libexpat,
|
||||
libnfs,
|
||||
boost,
|
||||
]
|
||||
|
@@ -23,6 +23,12 @@
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/checkbox_wakelock" />
|
||||
|
||||
<CheckBox
|
||||
android:id="@+id/pause_on_headphones_disconnect"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/checkbox_pause_on_headphones_disconnect" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/status"
|
||||
android:layout_width="wrap_content"
|
||||
|
@@ -8,4 +8,5 @@
|
||||
<string name="toggle_button_run_off">MPD is not running</string>
|
||||
<string name="checkbox_run_on_boot">Run MPD automatically on boot</string>
|
||||
<string name="checkbox_wakelock">Prevent suspend when MPD is running (Wakelock)</string>
|
||||
<string name="checkbox_pause_on_headphones_disconnect">Pause MPD when headphones disconnect</string>
|
||||
</resources>
|
||||
|
@@ -33,4 +33,5 @@ public class Bridge {
|
||||
|
||||
public static native void run(Context context, LogListener logListener);
|
||||
public static native void shutdown();
|
||||
public static native void pause();
|
||||
}
|
||||
|
@@ -5,6 +5,7 @@ interface IMain
|
||||
{
|
||||
void start();
|
||||
void stop();
|
||||
void setPauseOnHeadphonesDisconnect(boolean enabled);
|
||||
void setWakelockEnabled(boolean enabled);
|
||||
boolean isRunning();
|
||||
void registerCallback(IMainCallback cb);
|
||||
|
@@ -24,9 +24,13 @@ import android.app.Notification;
|
||||
import android.app.NotificationManager;
|
||||
import android.app.PendingIntent;
|
||||
import android.app.Service;
|
||||
import android.bluetooth.BluetoothDevice;
|
||||
import android.bluetooth.BluetoothClass;
|
||||
import android.content.BroadcastReceiver;
|
||||
import android.content.ComponentName;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.IntentFilter;
|
||||
import android.content.ServiceConnection;
|
||||
import android.os.Build;
|
||||
import android.os.IBinder;
|
||||
@@ -55,6 +59,7 @@ public class Main extends Service implements Runnable {
|
||||
private String mError = null;
|
||||
private final RemoteCallbackList<IMainCallback> mCallbacks = new RemoteCallbackList<IMainCallback>();
|
||||
private final IBinder mBinder = new MainStub(this);
|
||||
private boolean mPauseOnHeadphonesDisconnect = false;
|
||||
private PowerManager.WakeLock mWakelock = null;
|
||||
|
||||
static class MainStub extends IMain.Stub {
|
||||
@@ -68,6 +73,9 @@ public class Main extends Service implements Runnable {
|
||||
public void stop() {
|
||||
mService.stop();
|
||||
}
|
||||
public void setPauseOnHeadphonesDisconnect(boolean enabled) {
|
||||
mService.setPauseOnHeadphonesDisconnect(enabled);
|
||||
}
|
||||
public void setWakelockEnabled(boolean enabled) {
|
||||
mService.setWakelockEnabled(enabled);
|
||||
}
|
||||
@@ -191,6 +199,28 @@ public class Main extends Service implements Runnable {
|
||||
if (mThread != null)
|
||||
return;
|
||||
|
||||
IntentFilter filter = new IntentFilter();
|
||||
filter.addAction(Intent.ACTION_HEADSET_PLUG);
|
||||
filter.addAction(BluetoothDevice.ACTION_ACL_DISCONNECT_REQUESTED);
|
||||
filter.addAction(BluetoothDevice.ACTION_ACL_DISCONNECTED);
|
||||
registerReceiver(new BroadcastReceiver() {
|
||||
@Override
|
||||
public void onReceive(Context context, Intent intent) {
|
||||
if (!mPauseOnHeadphonesDisconnect) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (intent.getAction().equals(Intent.ACTION_HEADSET_PLUG)) {
|
||||
if (intent.hasExtra("state") && intent.getIntExtra("state", 0) == 0)
|
||||
pause();
|
||||
} else {
|
||||
BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
|
||||
if (device.getBluetoothClass().hasService(BluetoothClass.Service.AUDIO))
|
||||
pause();
|
||||
}
|
||||
}
|
||||
}, filter);
|
||||
|
||||
final Intent mainIntent = new Intent(this, Settings.class);
|
||||
mainIntent.setAction("android.intent.action.MAIN");
|
||||
mainIntent.addCategory("android.intent.category.LAUNCHER");
|
||||
@@ -241,6 +271,21 @@ public class Main extends Service implements Runnable {
|
||||
stopSelf();
|
||||
}
|
||||
|
||||
private void pause() {
|
||||
if (mThread != null) {
|
||||
if (mThread.isAlive()) {
|
||||
synchronized (this) {
|
||||
if (mStatus == MAIN_STATUS_STARTED)
|
||||
Bridge.pause();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void setPauseOnHeadphonesDisconnect(boolean enabled) {
|
||||
mPauseOnHeadphonesDisconnect = enabled;
|
||||
}
|
||||
|
||||
private void setWakelockEnabled(boolean enabled) {
|
||||
if (enabled && mWakelock == null) {
|
||||
PowerManager pm = (PowerManager)getSystemService(Context.POWER_SERVICE);
|
||||
@@ -368,6 +413,19 @@ public class Main extends Service implements Runnable {
|
||||
}
|
||||
}
|
||||
|
||||
public boolean setPauseOnHeadphonesDisconnect(boolean enabled) {
|
||||
synchronized (this) {
|
||||
if (mIMain != null) {
|
||||
try {
|
||||
mIMain.setPauseOnHeadphonesDisconnect(enabled);
|
||||
return true;
|
||||
} catch (RemoteException e) {
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public boolean setWakelockEnabled(boolean enabled) {
|
||||
synchronized (this) {
|
||||
if (mIMain != null) {
|
||||
@@ -414,6 +472,15 @@ public class Main extends Service implements Runnable {
|
||||
* start Main service without any callback
|
||||
*/
|
||||
public static void start(Context context, boolean wakelock) {
|
||||
context.startService(new Intent(context, Main.class).putExtra("wakelock", wakelock));
|
||||
Intent intent = new Intent(context, Main.class)
|
||||
.putExtra("wakelock", wakelock);
|
||||
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O)
|
||||
/* in Android 8+, we need to use this method
|
||||
or else we'll get "IllegalStateException:
|
||||
app is in background" */
|
||||
context.startForegroundService(intent);
|
||||
else
|
||||
context.startService(intent);
|
||||
}
|
||||
}
|
||||
|
@@ -59,6 +59,7 @@ public class Settings extends Activity {
|
||||
public static class Preferences {
|
||||
public static final String KEY_RUN_ON_BOOT ="run_on_boot";
|
||||
public static final String KEY_WAKELOCK ="wakelock";
|
||||
public static final String KEY_PAUSE_ON_HEADPHONES_DISCONNECT ="pause_on_headphones_disconnect";
|
||||
|
||||
public static SharedPreferences get(Context context) {
|
||||
return context.getSharedPreferences(TAG, MODE_PRIVATE);
|
||||
@@ -154,6 +155,9 @@ public class Settings extends Activity {
|
||||
if (Preferences.getBoolean(Settings.this,
|
||||
Preferences.KEY_WAKELOCK, false))
|
||||
mClient.setWakelockEnabled(true);
|
||||
if (Preferences.getBoolean(Settings.this,
|
||||
Preferences.KEY_PAUSE_ON_HEADPHONES_DISCONNECT, false))
|
||||
mClient.setPauseOnHeadphonesDisconnect(true);
|
||||
} else {
|
||||
mClient.stop();
|
||||
}
|
||||
@@ -179,6 +183,15 @@ public class Settings extends Activity {
|
||||
}
|
||||
};
|
||||
|
||||
private final OnCheckedChangeListener mOnPauseOnHeadphonesDisconnectChangeListener = new OnCheckedChangeListener() {
|
||||
@Override
|
||||
public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
|
||||
Preferences.putBoolean(Settings.this, Preferences.KEY_PAUSE_ON_HEADPHONES_DISCONNECT, isChecked);
|
||||
if (mClient != null && mClient.isRunning())
|
||||
mClient.setPauseOnHeadphonesDisconnect(isChecked);
|
||||
}
|
||||
};
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
/* TODO: this sure is the wrong place to request
|
||||
@@ -211,6 +224,11 @@ public class Settings extends Activity {
|
||||
if (Preferences.getBoolean(this, Preferences.KEY_WAKELOCK, false))
|
||||
checkbox.setChecked(true);
|
||||
|
||||
checkbox = (CheckBox) findViewById(R.id.pause_on_headphones_disconnect);
|
||||
checkbox.setOnCheckedChangeListener(mOnPauseOnHeadphonesDisconnectChangeListener);
|
||||
if (Preferences.getBoolean(this, Preferences.KEY_PAUSE_ON_HEADPHONES_DISCONNECT, false))
|
||||
checkbox.setChecked(true);
|
||||
|
||||
super.onCreate(savedInstanceState);
|
||||
}
|
||||
|
||||
|
66
doc/client.rst
Normal file
66
doc/client.rst
Normal file
@@ -0,0 +1,66 @@
|
||||
Client Developer's Manual
|
||||
#########################
|
||||
|
||||
Introduction
|
||||
************
|
||||
|
||||
MPD is a music player without a user interface. The user interface
|
||||
will be provided by independent clients, which connect to MPD over
|
||||
socket connections (TCP or local sockets).
|
||||
|
||||
This chapter describes how to develop a client.
|
||||
|
||||
Before you develop a new client, consider joining an existing client
|
||||
project. There are many clients, but few are mature; we need fewer,
|
||||
but better clients.
|
||||
|
||||
Client Libraries
|
||||
****************
|
||||
|
||||
There are many libraries which help with connecting to MPD. If you
|
||||
develop a MPD client, use a library instead of reinventing the wheel.
|
||||
The MPD website has a list of libraries: https://www.musicpd.org/libs/
|
||||
|
||||
|
||||
Connecting to MPD
|
||||
*****************
|
||||
|
||||
Do not hard-code your client to connect to ``localhost:6600``.
|
||||
Instead, use the defaults of the client library. For example, with
|
||||
:program:`libmpdclient`, don't do::
|
||||
|
||||
c = mpd_connection_new("localhost", 6600, 30000);
|
||||
|
||||
Instead, do::
|
||||
|
||||
c = mpd_connection_new(NULL, 0, 0);
|
||||
|
||||
This way, the library can choose the best defaults, maybe derived from
|
||||
environment variables, so all MPD clients use the same settings.
|
||||
|
||||
If you need to reimplement those defaults (or if you are developing a
|
||||
client library), this is a good set of addresses to attempt to connect
|
||||
to:
|
||||
|
||||
- if the environment variable :envvar:`MPD_HOST` is set:
|
||||
``$MPD_HOST:$MPD_PORT`` (:envvar:`MPD_PORT` defaulting to 6600)
|
||||
- if the environment variable :envvar:`XDG_RUNTIME_DIR` is set:
|
||||
``$XDG_RUNTIME_DIR/mpd/socket``
|
||||
- :file:`/run/mpd/socket`
|
||||
- ``localhost:$MPD_PORT`` (:envvar:`MPD_PORT` defaulting to 6600)
|
||||
|
||||
Environment Variables
|
||||
*********************
|
||||
|
||||
The following environment variables should be obeyed by all clients
|
||||
(preferably by the client library):
|
||||
|
||||
- :envvar:`MPD_HOST`: the host (or local socket path) to connect to;
|
||||
on Linux, this may start with a ``@`` to connect to an abstract
|
||||
socket. To use a password with MPD, set :envvar:`MPD_HOST` to
|
||||
``password@host`` (then abstract socket requires double ``@``:
|
||||
``password@@socket``).
|
||||
- :envvar:`MPD_PORT`: the port number; defaults to 6600.
|
||||
- :envvar:`MPD_TIMEOUT`: timeout for connecting to MPD and for waiting
|
||||
for MPD's response in seconds. A good default is 30 seconds.
|
||||
|
@@ -30,7 +30,7 @@ master_doc = 'index'
|
||||
|
||||
# General information about the project.
|
||||
project = 'Music Player Daemon'
|
||||
copyright = '2003-2020 The Music Player Daemon Project'
|
||||
copyright = '2003-2021 The Music Player Daemon Project'
|
||||
author = 'Max Kellermann'
|
||||
|
||||
# The version info for the project you're documenting, acts as replacement for
|
||||
@@ -38,9 +38,9 @@ author = 'Max Kellermann'
|
||||
# built documents.
|
||||
#
|
||||
# The short X.Y version.
|
||||
version = '0.22.8'
|
||||
version = '0.23.1'
|
||||
# The full version, including alpha/beta/rc tags.
|
||||
release = version
|
||||
#release = version + '~git'
|
||||
|
||||
# The language for content autogenerated by Sphinx. Refer to documentation
|
||||
# for a list of supported languages.
|
||||
|
@@ -8,6 +8,7 @@ Music Player Daemon
|
||||
user
|
||||
plugins
|
||||
developer
|
||||
client
|
||||
protocol
|
||||
|
||||
|
||||
|
@@ -14,6 +14,7 @@ if get_option('html_manual')
|
||||
input: [
|
||||
'index.rst', 'user.rst', 'developer.rst',
|
||||
'plugins.rst',
|
||||
'client.rst',
|
||||
'protocol.rst',
|
||||
'conf.py',
|
||||
],
|
||||
|
143
doc/plugins.rst
143
doc/plugins.rst
@@ -23,11 +23,23 @@ The default plugin. Stores a copy of the database in memory. A file is used for
|
||||
- The path of the cache directory for additional storages mounted at runtime. This setting is necessary for the **mount** protocol command.
|
||||
* - **compress yes|no**
|
||||
- Compress the database file using gzip? Enabled by default (if built with zlib).
|
||||
* - **hide_playlist_targets yes|no**
|
||||
- Hide songs which are referenced by playlists? Thas is,
|
||||
playlist files which are represented in the database as virtual
|
||||
directories (playlist plugin setting ``as_directory``). This
|
||||
option is enabled by default and avoids duplicate songs; one
|
||||
copy for the original file, and another copy in the virtual
|
||||
directory of a CUE file referring to it.
|
||||
|
||||
proxy
|
||||
-----
|
||||
|
||||
Provides access to the database of another :program:`MPD` instance using libmpdclient. This is useful when you mount the music directory via NFS/SMB, and the file server already runs a :program:`MPD` instance. Only the file server needs to update the database.
|
||||
Provides access to the database of another :program:`MPD` instance
|
||||
using `libmpdclient
|
||||
<https://www.musicpd.org/libs/libmpdclient/>`_. This is useful when
|
||||
you mount the music directory via NFS/SMB, and the file server already
|
||||
runs a :program:`MPD` (0.20 or newer) instance. Only the file server
|
||||
needs to update the database.
|
||||
|
||||
.. list-table::
|
||||
:widths: 20 80
|
||||
@@ -210,6 +222,8 @@ will be in effect.
|
||||
- Verify the peer's SSL certificate? `More information <http://curl.haxx.se/libcurl/c/CURLOPT_SSL_VERIFYPEER.html>`_.
|
||||
* - **verify_host yes|no**
|
||||
- Verify the certificate's name against host? `More information <http://curl.haxx.se/libcurl/c/CURLOPT_SSL_VERIFYHOST.html>`_.
|
||||
* - **cacert**
|
||||
- Set path to Certificate Authority (CA) bundle `More information <https://curl.se/libcurl/c/CURLOPT_CAINFO.html>`_.
|
||||
|
||||
ffmpeg
|
||||
------
|
||||
@@ -295,37 +309,6 @@ in the form ``qobuz://track/ID``, e.g.:
|
||||
* - **format_id N**
|
||||
- The `Qobuz format identifier <https://github.com/Qobuz/api-documentation/blob/master/endpoints/track/getFileUrl.md#parameters>`_, i.e. a number which chooses the format and quality to be requested from Qobuz. The default is "5" (320 kbit/s MP3).
|
||||
|
||||
tidal
|
||||
-----
|
||||
|
||||
Play songs from the commercial streaming service `Tidal
|
||||
<http://tidal.com/>`_. It plays URLs in the form ``tidal://track/ID``,
|
||||
e.g.:
|
||||
|
||||
.. warning::
|
||||
|
||||
This plugin is currently defunct because Tidal has changed the
|
||||
protocol and decided not to share documentation.
|
||||
|
||||
.. code-block:: none
|
||||
|
||||
mpc add tidal://track/59727857
|
||||
|
||||
.. list-table::
|
||||
:widths: 20 80
|
||||
:header-rows: 1
|
||||
|
||||
* - Setting
|
||||
- Description
|
||||
* - **token TOKEN**
|
||||
- The Tidal application token. Since Tidal is unwilling to assign a token to MPD, this needs to be reverse-engineered from another (approved) Tidal client.
|
||||
* - **username USERNAME**
|
||||
- The Tidal user name.
|
||||
* - **password PASSWORD**
|
||||
- The Tidal password.
|
||||
* - **audioquality Q**
|
||||
- The Tidal "audioquality" parameter. Possible values: HI_RES, LOSSLESS, HIGH, LOW. Default is HIGH.
|
||||
|
||||
.. _decoder_plugins:
|
||||
|
||||
Decoder plugins
|
||||
@@ -482,9 +465,39 @@ Module player based on MODPlug.
|
||||
|
||||
* - Setting
|
||||
- Description
|
||||
* - **resampling_mode nearest|linear|spline|fir**
|
||||
- Sets the resampling mode. "nearest" disables interpolation (good for chiptunes). "linear" makes modplug use linear interpolation (fast, good quality). "spline" makes modplug use cubic spline interpolation (high quality). "fir" makes modplug use 8-tap fir filter (extremely high quality). Defaults to "fir".
|
||||
* - **loop_count**
|
||||
- Number of times to loop the module if it uses backward loops. Default is 0 which prevents looping. -1 loops forever.
|
||||
|
||||
openmpt
|
||||
-------
|
||||
|
||||
Module player based on `libopenmpt <https://lib.openmpt.org>`_.
|
||||
|
||||
.. list-table::
|
||||
:widths: 20 80
|
||||
:header-rows: 1
|
||||
|
||||
* - Setting
|
||||
- Description
|
||||
* - **repeat_count**
|
||||
- Set how many times the module repeats. -1: repeat forever. 0: play once, repeat zero times (the default). n>0: play once and repeat n times after that.
|
||||
* - **stereo_separation**
|
||||
- Sets the stereo separation. The supported value range is [0,200]. Defaults to 100.
|
||||
* - **interpolation_filter 0|1|2|4|8**
|
||||
- Sets the interpolation filter. 0: internal default. 1: no interpolation (zero order hold). 2: linear interpolation. 4: cubic interpolation. 8: windowed sinc with 8 taps. Defaults to 0.
|
||||
* - **override_mptm_interp_filter yes|no**
|
||||
- If `interpolation_filter` has been changed, setting this to yes will force all MPTM modules to use that interpolation filter. If set to no, MPTM modules will play with their own interpolation filter regardless of the value of `interpolation_filter`. Defaults to no.
|
||||
* - **volume_ramping**
|
||||
- Sets the amount of volume ramping done by the libopenmpt mixer. The default value is -1, which indicates a recommended default value. The meaningful value range is [-1..10]. A value of 0 completely disables volume ramping. This might cause clicks in sound output. Higher values imply slower/softer volume ramps.
|
||||
* - **sync_samples yes|no**
|
||||
- Syncs sample playback when seeking. Defaults to yes.
|
||||
* - **emulate_amiga yes|no**
|
||||
- Enables the Amiga resampler for Amiga modules. This emulates the sound characteristics of the Paula chip and overrides the selected interpolation filter. Non-Amiga module formats are not affected by this setting. Defaults to yes.
|
||||
* - **emulate_amiga_type**
|
||||
- Configures the filter type to use for the Amiga resampler. Supported values are: "auto": Filter type is chosen by the library and might change. This is the default. "a500": Amiga A500 filter. "a1200": Amiga A1200 filter. "unfiltered": BLEP synthesis without model-specific filters. The LED filter is ignored by this setting. This filter mode is considered to be experimental and might change in the future. Defaults to "auto". Requires libopenmpt 0.5 or higher.
|
||||
|
||||
mpcdec
|
||||
------
|
||||
|
||||
@@ -579,6 +592,10 @@ Encodes into `FLAC <https://xiph.org/flac/>`_ (lossless).
|
||||
- Description
|
||||
* - **compression**
|
||||
- Sets the libFLAC compression level. The levels range from 0 (fastest, least compression) to 8 (slowest, most compression).
|
||||
* - **oggflac yes|no**
|
||||
- Configures if the stream should be Ogg FLAC versus native FLAC. Defaults to "no" (use native FLAC).
|
||||
* - **oggchaining yes|no**
|
||||
- Configures if the stream should use Ogg Chaining for in-stream metadata. Defaults to "no". Setting this to "yes" also enables Ogg FLAC.
|
||||
|
||||
lame
|
||||
----
|
||||
@@ -643,11 +660,15 @@ Encodes into `Ogg Opus <http://www.opus-codec.org/>`_.
|
||||
* - Setting
|
||||
- Description
|
||||
* - **bitrate**
|
||||
- Sets the data rate in bit per second. The special value "auto" lets libopus choose a rate (which is the default), and "max" uses the maximum possible data rate.
|
||||
- Sets the data rate in bits per second. The special value "auto" lets libopus choose a rate (which is the default), and "max" uses the maximum possible data rate.
|
||||
* - **complexity**
|
||||
- Sets the `Opus complexity <https://wiki.xiph.org/OpusFAQ#What_is_the_complexity_of_Opus.3F>`_.
|
||||
* - **signal**
|
||||
- Sets the Opus signal type. Valid values are "auto" (the default), "voice" and "music".
|
||||
* - **vbr yes|no|constrained**
|
||||
- Sets the vbr mode. Setting to "yes" (default) enables variable bitrate, "no" forces constant bitrate and frame sizes, "constrained" uses constant bitrate analogous to CBR in AAC and MP3.
|
||||
* - **packet_loss**
|
||||
- Sets the expected packet loss percentage. This value can be increased from the default "0" for a more redundant stream at the expense of quality.
|
||||
* - **opustags yes|no**
|
||||
- Configures how metadata is interleaved into the stream. If set to yes, then metadata is inserted using ogg stream chaining, as specified in :rfc:`7845`. If set to no (the default), then ogg stream chaining is avoided and other output-dependent method is used, if available.
|
||||
|
||||
@@ -715,7 +736,7 @@ A resampler using `libsamplerate <http://www.mega-nerd.com/SRC/>`_ a.k.a. Secret
|
||||
* - Name
|
||||
- Description
|
||||
* - **type**
|
||||
- The interpolator type. See below for a list of known types.
|
||||
- The interpolator type. Defaults to :samp:`2`. See below for a list of known types.
|
||||
|
||||
The following converter types are provided by libsamplerate:
|
||||
|
||||
@@ -990,6 +1011,8 @@ On Linux, OSS has been superseded by ALSA. Use the ALSA output plugin :ref:`alsa
|
||||
- Description
|
||||
* - **device PATH**
|
||||
- Sets the path of the PCM device. If not specified, then MPD will attempt to open /dev/sound/dsp and /dev/dsp.
|
||||
* - **dop yes|no**
|
||||
- If set to yes, then DSD over PCM according to the `DoP standard <http://dsd-guide.com/dop-open-standard>`_ is enabled. This wraps DSD samples in fake 24 bit PCM, and is understood by some DSD capable products, but may be harmful to other hardware. Therefore, the default is no and you can enable the option at your own risk.
|
||||
|
||||
The according hardware mixer plugin understands the following settings:
|
||||
|
||||
@@ -1052,6 +1075,26 @@ The pipe plugin starts a program and writes raw PCM data into its standard input
|
||||
* - **command CMD**
|
||||
- This command is invoked with the shell.
|
||||
|
||||
pipewire
|
||||
--------
|
||||
|
||||
Connect to a `PipeWire <https://pipewire.org/>`_ server. Requires
|
||||
``libpipewire``.
|
||||
|
||||
.. list-table::
|
||||
:widths: 20 80
|
||||
:header-rows: 1
|
||||
|
||||
* - Setting
|
||||
- Description
|
||||
* - **target NAME**
|
||||
- Link to the given target. If not specified, let the PipeWire
|
||||
manager select a target. To get a list of available targets,
|
||||
type ``pw-cli dump short Node``
|
||||
* - **remote NAME**
|
||||
- The name of the remote to connect to. The default is
|
||||
``pipewire-0``.
|
||||
|
||||
.. _pulse_plugin:
|
||||
|
||||
pulse
|
||||
@@ -1107,8 +1150,6 @@ You must set a format.
|
||||
- Sets the host name of the `ShoutCast <http://www.shoutcast.com/>`_ / `IceCast <http://icecast.org/>`_ server.
|
||||
* - **port PORTNUMBER**
|
||||
- Connect to this port number on the specified host.
|
||||
* - **timeout SECONDS**
|
||||
- Set the timeout for the shout connection in seconds. Defaults to 2 seconds.
|
||||
* - **protocol icecast2|icecast1|shoutcast**
|
||||
- Specifies the protocol that wil be used to connect to the server. The default is "icecast2".
|
||||
* - **tls disabled|auto|auto_no_plain|rfc2818|rfc2817**
|
||||
@@ -1144,6 +1185,34 @@ audio API. Its primary use is local playback on Android, where
|
||||
floating point samples.
|
||||
|
||||
|
||||
snapcast
|
||||
--------
|
||||
|
||||
Snapcast is a multiroom client-server audio player. This plugin
|
||||
allows MPD to act as a `Snapcast
|
||||
<https://github.com/badaix/snapcast>`__ server. Snapcast clients
|
||||
connect to it and receive audio data from MPD.
|
||||
|
||||
You must set a format.
|
||||
|
||||
.. list-table::
|
||||
:widths: 20 80
|
||||
:header-rows: 1
|
||||
|
||||
* - Setting
|
||||
- Description
|
||||
* - **port P**
|
||||
- Binds the Snapcast server to the specified port. The default
|
||||
port is :samp:`1704`.
|
||||
* - **bind_to_address ADDR**
|
||||
- Binds the Snapcast server to the specified address. Multiple
|
||||
addresses in parallel are not supported. The default is to
|
||||
bind on all addresses on port :samp:`1704`.
|
||||
* - **zeroconf yes|no**
|
||||
- Publish the Snapcast server as service type ``_snapcast._tcp``
|
||||
via Zeroconf (Avahi or Bonjour). Default is :samp:`yes`.
|
||||
|
||||
|
||||
solaris
|
||||
-------
|
||||
The "Solaris" plugin runs only on SUN Solaris, and plays via /dev/audio.
|
||||
@@ -1200,7 +1269,7 @@ This plugin requires building with ``libavfilter`` (FFmpeg).
|
||||
* - **graph "..."**
|
||||
- Specifies the ``libavfilter`` graph; read the `FFmpeg
|
||||
documentation
|
||||
<https://libav.org/documentation/libavfilter.html#Filtergraph-syntax-1>`_
|
||||
<https://ffmpeg.org/ffmpeg-filters.html#Filtergraph-syntax-1>`_
|
||||
for details
|
||||
|
||||
|
||||
|
@@ -286,10 +286,15 @@ The following tags are supported by :program:`MPD`:
|
||||
* **date**: the song's release date. This is usually a 4-digit year.
|
||||
* **originaldate**: the song's original release date.
|
||||
* **composer**: the artist who composed the song.
|
||||
* **composersort**: same as composer, but for sorting.
|
||||
* **performer**: the artist who performed the song.
|
||||
* **conductor**: the conductor who conducted the song.
|
||||
* **work**: `"a work is a distinct intellectual or artistic creation,
|
||||
which can be expressed in the form of one or more audio recordings" <https://musicbrainz.org/doc/Work>`_
|
||||
* **ensemble**: the ensemble performing this song, e.g. "Wiener Philharmoniker".
|
||||
* **movement**: name of the movement, e.g. "Andante con moto".
|
||||
* **movementnumber**: movement number, e.g. "2" or "II".
|
||||
* **location**: location of the recording, e.g. "Royal Albert Hall".
|
||||
* **grouping**: "used if the sound belongs to a larger category of
|
||||
sounds/music" (`from the IDv2.4.0 TIT1 description
|
||||
<http://id3.org/id3v2.4.0-frames>`_).
|
||||
@@ -544,6 +549,18 @@ Playback options
|
||||
Sets volume to ``VOL``, the range of
|
||||
volume is 0-100.
|
||||
|
||||
.. _command_getvol:
|
||||
|
||||
:command:`getvol`
|
||||
|
||||
Read the volume. The result is a ``volume:`` line like in
|
||||
:ref:`status <command_status>`. If there is no mixer, MPD will
|
||||
emit an empty response. Example::
|
||||
|
||||
getvol
|
||||
volume: 42
|
||||
OK
|
||||
|
||||
.. _command_single:
|
||||
|
||||
:command:`single {STATE}` [#since_0_15]_
|
||||
@@ -692,6 +709,13 @@ Whenever possible, ids should be used.
|
||||
Id: 999
|
||||
OK
|
||||
|
||||
If the second parameter is given, then the song is inserted at the
|
||||
specified position. If the parameter starts with ``+`` or ``-``,
|
||||
then it is relative to the current song; e.g. ``+0`` inserts right
|
||||
after the current song and ``-0`` inserts right before the current
|
||||
song (i.e. zero songs between the current song and the newly added
|
||||
song).
|
||||
|
||||
.. _command_clear:
|
||||
|
||||
:command:`clear`
|
||||
@@ -715,14 +739,22 @@ Whenever possible, ids should be used.
|
||||
at ``START:END`` [#since_0_15]_ to ``TO``
|
||||
in the playlist.
|
||||
|
||||
If ``TO`` starts with ``+`` or ``-``, then it is relative to the
|
||||
current song; e.g. ``+0`` moves to right after the current song
|
||||
and ``-0`` moves to right before the current song (i.e. zero songs
|
||||
between the current song and the moved range).
|
||||
|
||||
.. _command_moveid:
|
||||
|
||||
:command:`moveid {FROM} {TO}`
|
||||
Moves the song with ``FROM`` (songid) to
|
||||
``TO`` (playlist index) in the
|
||||
playlist. If ``TO`` is negative, it
|
||||
is relative to the current song in the playlist (if
|
||||
there is one).
|
||||
playlist.
|
||||
|
||||
If ``TO`` starts with ``+`` or ``-``, then it is relative to the
|
||||
current song; e.g. ``+0`` moves to right after the current song
|
||||
and ``-0`` moves to right before the current song (i.e. zero songs
|
||||
between the current song and the moved song).
|
||||
|
||||
.. _command_playlist:
|
||||
|
||||
@@ -885,11 +917,17 @@ remote playlists (absolute URI with a supported scheme).
|
||||
|
||||
.. _command_load:
|
||||
|
||||
:command:`load {NAME} [START:END]`
|
||||
:command:`load {NAME} [START:END] [POSITION]`
|
||||
Loads the playlist into the current queue. Playlist
|
||||
plugins are supported. A range may be specified to load
|
||||
only a part of the playlist.
|
||||
|
||||
The ``POSITION`` parameter specifies where the songs will be
|
||||
inserted into the queue; it can be relative as described in
|
||||
:ref:`addid <command_addid>`. (This requires specifying the range
|
||||
as well; the special value `0:` can be used if the whole playlist
|
||||
shall be loaded at a certain queue position.)
|
||||
|
||||
.. _command_playlistadd:
|
||||
|
||||
:command:`playlistadd {NAME} {URI}`
|
||||
@@ -1022,11 +1060,11 @@ The music database
|
||||
|
||||
.. _command_findadd:
|
||||
|
||||
:command:`findadd {FILTER} [sort {TYPE}] [window {START:END}]`
|
||||
:command:`findadd {FILTER} [sort {TYPE}] [window {START:END}] [position POS]`
|
||||
Search the database for songs matching
|
||||
``FILTER`` (see :ref:`Filters <filter_syntax>`) and add them to
|
||||
the queue. Parameters have the same meaning as for
|
||||
:ref:`find <command_find>`.
|
||||
:ref:`find <command_find>` and :ref:`searchadd <command_searchadd>`.
|
||||
|
||||
.. _command_list:
|
||||
|
||||
@@ -1159,7 +1197,7 @@ The music database
|
||||
|
||||
.. _command_search:
|
||||
|
||||
:command:`search {FILTER} [sort {TYPE}] [window {START:END}]`
|
||||
:command:`search {FILTER} [sort {TYPE}] [window {START:END}]
|
||||
Search the database for songs matching
|
||||
``FILTER`` (see :ref:`Filters <filter_syntax>`). Parameters
|
||||
have the same meaning as for :ref:`find <command_find>`,
|
||||
@@ -1167,13 +1205,16 @@ The music database
|
||||
|
||||
.. _command_searchadd:
|
||||
|
||||
:command:`searchadd {FILTER} [sort {TYPE}] [window {START:END}]`
|
||||
:command:`searchadd {FILTER} [sort {TYPE}] [window {START:END}] [position POS]`
|
||||
Search the database for songs matching
|
||||
``FILTER`` (see :ref:`Filters <filter_syntax>`) and add them to
|
||||
the queue.
|
||||
|
||||
Parameters have the same meaning as for :ref:`search <command_search>`.
|
||||
|
||||
The ``position`` parameter specifies where the songs will be
|
||||
inserted.
|
||||
|
||||
.. _command_searchaddpl:
|
||||
|
||||
:command:`searchaddpl {NAME} {FILTER} [sort {TYPE}] [window {START:END}]`
|
||||
|
67
doc/user.rst
67
doc/user.rst
@@ -55,8 +55,8 @@ and unpack it (or `clone the git repository
|
||||
|
||||
In any case, you need:
|
||||
|
||||
* a C++17 compiler (e.g. GCC 8 or clang 5)
|
||||
* `Meson 0.49.0 <http://mesonbuild.com/>`__ and `Ninja
|
||||
* a C++17 compiler (e.g. GCC 8 or clang 7)
|
||||
* `Meson 0.56.0 <http://mesonbuild.com/>`__ and `Ninja
|
||||
<https://ninja-build.org/>`__
|
||||
* Boost 1.58
|
||||
* pkg-config
|
||||
@@ -69,6 +69,7 @@ For example, the following installs a fairly complete list of build dependencies
|
||||
.. code-block:: none
|
||||
|
||||
apt install meson g++ \
|
||||
libfmt-dev \
|
||||
libpcre3-dev \
|
||||
libmad0-dev libmpg123-dev libid3tag0-dev \
|
||||
libflac-dev libvorbis-dev libopus-dev libogg-dev \
|
||||
@@ -110,6 +111,19 @@ The following command shows a list of compile-time options:
|
||||
|
||||
meson configure output/release
|
||||
|
||||
NB: Check the sysconfdir setting to determine where mpd will look for mpd.conf; if you expect mpd to look for /etc/mpd.conf the sysconfdir must be '/etc' (i.e., not 'etc' which will result in mpd looking for /usr/local/etc/mpd.conf):
|
||||
|
||||
.. code-block:: none
|
||||
|
||||
meson configure output/release |grep sysconfdir
|
||||
|
||||
If this is not /etc (or another path you wish to specify):
|
||||
|
||||
.. code-block:: none
|
||||
|
||||
$ meson configure output/release -Dsysconfdir='/etc' ; meson configure output/release |grep syscon
|
||||
sysconfdir /etc Sysconf data directory
|
||||
|
||||
When everything is ready and configured, compile:
|
||||
|
||||
.. code-block:: none
|
||||
@@ -144,7 +158,7 @@ This section is about the latter.
|
||||
You need:
|
||||
|
||||
* `mingw-w64 <http://mingw-w64.org/doku.php>`__
|
||||
* `Meson 0.49.0 <http://mesonbuild.com/>`__ and `Ninja
|
||||
* `Meson 0.56.0 <http://mesonbuild.com/>`__ and `Ninja
|
||||
<https://ninja-build.org/>`__
|
||||
* cmake
|
||||
* pkg-config
|
||||
@@ -176,8 +190,8 @@ Compiling for Android
|
||||
You need:
|
||||
|
||||
* Android SDK
|
||||
* `Android NDK r22 <https://developer.android.com/ndk/downloads>`_
|
||||
* `Meson 0.49.0 <http://mesonbuild.com/>`__ and `Ninja
|
||||
* `Android NDK r23 <https://developer.android.com/ndk/downloads>`_
|
||||
* `Meson 0.56.0 <http://mesonbuild.com/>`__ and `Ninja
|
||||
<https://ninja-build.org/>`__
|
||||
* cmake
|
||||
* pkg-config
|
||||
@@ -625,13 +639,20 @@ By default, all clients are unauthenticated and have a full set of permissions.
|
||||
- Allows reading of the database, displaying the current playlist, and current status of :program:`MPD`.
|
||||
* - **add**
|
||||
- Allows adding songs and loading playlists.
|
||||
* - **player**
|
||||
- Allows any player and queue manipulation (start/pause/stop
|
||||
playback etc.).
|
||||
* - **control**
|
||||
- Allows all other player and playlist manipulations.
|
||||
* - **admin**
|
||||
- Allows database updates and allows shutting down :program:`MPD`.
|
||||
- Allows manipulating outputs, stickers and partitions,
|
||||
mounting/unmounting storage and shutting down :program:`MPD`.
|
||||
|
||||
:code:`local_permissions` may be used to assign other permissions to clients connecting on a local socket.
|
||||
|
||||
:code:`host_permissions` may be used to assign permissions to clients
|
||||
with a certain IP address.
|
||||
|
||||
:code:`password` allows the client to send a password to gain other permissions. This option may be specified multiple times with different passwords.
|
||||
|
||||
Note that the :code:`password` option is not secure: passwords are sent in clear-text over the connection, and the client cannot verify the server's identity.
|
||||
@@ -641,6 +662,8 @@ Example:
|
||||
.. code-block:: none
|
||||
|
||||
default_permissions "read"
|
||||
host_permissions "192.168.0.100 read,add,control,admin"
|
||||
host_permissions "2003:1234:4567::1 read,add,control,admin"
|
||||
password "the_password@read,add,control"
|
||||
password "the_admin_password@read,add,control,admin"
|
||||
|
||||
@@ -688,6 +711,8 @@ The State File
|
||||
- Specify the state file location. The parent directory must be writable by the :program:`MPD` user (+wx).
|
||||
* - **state_file_interval SECONDS**
|
||||
- Auto-save the state file this number of seconds after each state change. Defaults to 120 (2 minutes).
|
||||
* - **restore_paused yes|no**
|
||||
- If set to :samp:`yes`, then :program:`MPD` is put into pause mode instead of starting playback after startup. Default is :samp:`no`.
|
||||
|
||||
The Sticker Database
|
||||
^^^^^^^^^^^^^^^^^^^^
|
||||
@@ -1120,7 +1145,7 @@ Support
|
||||
Getting Help
|
||||
^^^^^^^^^^^^
|
||||
|
||||
The :program:`MPD` project runs a `forum <https://forum.musicpd.org/>`_ and an IRC channel (#mpd on Freenode) for requesting help. Visit the MPD help page for details on how to get help.
|
||||
The :program:`MPD` project runs a `forum <https://forum.musicpd.org/>`_ and an IRC channel (#mpd on Libera.Chat) for requesting help. Visit the MPD help page for details on how to get help.
|
||||
|
||||
Common Problems
|
||||
^^^^^^^^^^^^^^^
|
||||
@@ -1202,6 +1227,34 @@ Your bug report should contain:
|
||||
* relevant portions of the log file (:option:`--verbose`)
|
||||
* be clear about what you expect MPD to do, and what is actually happening
|
||||
|
||||
.. _profiler:
|
||||
|
||||
Too Much CPU Usage
|
||||
^^^^^^^^^^^^^^^^^^
|
||||
|
||||
If you believe MPD consumes too much CPU, `write a bug report
|
||||
<https://github.com/MusicPlayerDaemon/MPD/issues>`_ with a profiling
|
||||
information.
|
||||
|
||||
On Linux, this can be obtained with :program:`perf` (on Debian,
|
||||
installed the package :file:`linux-perf`), for example::
|
||||
|
||||
perf record -p `pidof mpd`
|
||||
|
||||
Run this command while MPD consumes much CPU, let it run for a minute
|
||||
or so, and stop it by pressing ``Ctrl-C``. Then type::
|
||||
|
||||
perf report >mpd_perf.txt
|
||||
|
||||
Upload the output file to the bug report.
|
||||
|
||||
.. note::
|
||||
|
||||
This requires having debug symbols for MPD and all relevant
|
||||
libraries. See :ref:`crash` for details.
|
||||
|
||||
.. _crash:
|
||||
|
||||
MPD crashes
|
||||
^^^^^^^^^^^
|
||||
|
||||
|
133
meson.build
133
meson.build
@@ -1,8 +1,8 @@
|
||||
project(
|
||||
'mpd',
|
||||
['c', 'cpp'],
|
||||
version: '0.22.8',
|
||||
meson_version: '>= 0.49.0',
|
||||
version: '0.23.1',
|
||||
meson_version: '>= 0.56.0',
|
||||
default_options: [
|
||||
'c_std=c11',
|
||||
'build.c_std=c11',
|
||||
@@ -10,9 +10,21 @@ project(
|
||||
'build.cpp_std=c++17',
|
||||
'warning_level=3',
|
||||
|
||||
# This is only here to build subprojects as static libraries; MPD
|
||||
# itself doesn't ship any libraries.
|
||||
'default_library=static',
|
||||
# If we build those libraries as Meson subproject, they shall be
|
||||
# linked statically into the MPD executable.
|
||||
'expat:default_library=static',
|
||||
'fmt:default_library=static',
|
||||
'gtest:default_library=static',
|
||||
'sqlite3:default_library=static',
|
||||
'vorbis:default_library=static',
|
||||
|
||||
# Not interested in compiler warnings from subprojects.
|
||||
'expat:werror=false',
|
||||
'expat:warning_level=0',
|
||||
'fmt:warning_level=0',
|
||||
'gtest:warning_level=0',
|
||||
'sqlite3:warning_level=0',
|
||||
'vorbis:warning_level=0',
|
||||
],
|
||||
license: 'GPLv2+',
|
||||
)
|
||||
@@ -24,15 +36,15 @@ c_compiler = meson.get_compiler('c')
|
||||
|
||||
if compiler.get_id() == 'gcc' and compiler.version().version_compare('<8')
|
||||
warning('Your GCC version is too old. You need at least version 8.')
|
||||
elif compiler.get_id() == 'clang' and compiler.version().version_compare('<5')
|
||||
warning('Your clang version is too old. You need at least version 5.')
|
||||
elif compiler.get_id() == 'clang' and compiler.version().version_compare('<7')
|
||||
warning('Your clang version is too old. You need at least version 7.')
|
||||
endif
|
||||
|
||||
version_conf = configuration_data()
|
||||
version_conf.set_quoted('PACKAGE', meson.project_name())
|
||||
version_conf.set_quoted('PACKAGE_NAME', meson.project_name())
|
||||
version_conf.set_quoted('VERSION', meson.project_version())
|
||||
version_conf.set_quoted('PROTOCOL_VERSION', '0.22.4')
|
||||
version_conf.set_quoted('PROTOCOL_VERSION', '0.23.1')
|
||||
configure_file(output: 'Version.h', configuration: version_conf)
|
||||
|
||||
conf = configuration_data()
|
||||
@@ -42,57 +54,63 @@ common_cppflags = [
|
||||
'-D_GNU_SOURCE',
|
||||
]
|
||||
|
||||
common_cflags = [
|
||||
]
|
||||
|
||||
common_cxxflags = [
|
||||
test_global_common_flags = [
|
||||
'-fvisibility=hidden',
|
||||
]
|
||||
|
||||
test_common_flags = [
|
||||
'-Wvla',
|
||||
'-Wdouble-promotion',
|
||||
|
||||
'-fvisibility=hidden',
|
||||
|
||||
'-ffast-math',
|
||||
'-ftree-vectorize',
|
||||
|
||||
'-Wcast-qual',
|
||||
'-Wdouble-promotion',
|
||||
'-Wmissing-declarations',
|
||||
'-Wshadow',
|
||||
'-Wunused',
|
||||
'-Wvla',
|
||||
'-Wwrite-strings',
|
||||
|
||||
# clang specific warning options:
|
||||
'-Wunreachable-code-aggressive',
|
||||
'-Wused-but-marked-unused',
|
||||
]
|
||||
|
||||
test_global_cxxflags = test_global_common_flags + [
|
||||
]
|
||||
|
||||
test_global_cflags = test_global_common_flags + [
|
||||
]
|
||||
|
||||
test_cxxflags = test_common_flags + [
|
||||
'-fno-threadsafe-statics',
|
||||
'-fmerge-all-constants',
|
||||
|
||||
'-Wmissing-declarations',
|
||||
'-Wshadow',
|
||||
'-Wpointer-arith',
|
||||
'-Wcast-qual',
|
||||
'-Wwrite-strings',
|
||||
'-Wsign-compare',
|
||||
'-Wcomma',
|
||||
'-Wcomma-subscript',
|
||||
'-Wextra-semi',
|
||||
'-Wmismatched-tags',
|
||||
'-Woverloaded-virtual',
|
||||
'-Wsign-promo',
|
||||
'-Wvolatile',
|
||||
'-Wvirtual-inheritance',
|
||||
|
||||
# a vtable without a dtor is just fine
|
||||
'-Wno-non-virtual-dtor',
|
||||
|
||||
# clang specific warning options:
|
||||
'-Wcomma',
|
||||
'-Wheader-hygiene',
|
||||
'-Winconsistent-missing-destructor-override',
|
||||
'-Wunreachable-code-break',
|
||||
'-Wunused',
|
||||
'-Wused-but-marked-unused',
|
||||
|
||||
'-Wno-non-virtual-dtor',
|
||||
]
|
||||
|
||||
if compiler.get_id() == 'clang'
|
||||
# Workaround for clang bug
|
||||
# https://bugs.llvm.org/show_bug.cgi?id=32611
|
||||
test_cxxflags += '-funwind-tables'
|
||||
if compiler.get_id() != 'gcc' or compiler.version().version_compare('>=9')
|
||||
# The GCC 8 implementation of this flag is buggy: it complains even
|
||||
# if "final" is present, which implies "override".
|
||||
test_cxxflags += '-Wsuggest-override'
|
||||
endif
|
||||
|
||||
test_cflags = test_common_flags + [
|
||||
'-Wmissing-prototypes',
|
||||
'-Wshadow',
|
||||
'-Wpointer-arith',
|
||||
'-Wstrict-prototypes',
|
||||
'-Wcast-qual',
|
||||
'-Wwrite-strings',
|
||||
'-pedantic',
|
||||
]
|
||||
|
||||
test_ldflags = [
|
||||
@@ -104,11 +122,11 @@ test_ldflags = [
|
||||
]
|
||||
|
||||
if get_option('buildtype') != 'debug'
|
||||
test_cxxflags += [
|
||||
test_global_cxxflags += [
|
||||
'-ffunction-sections',
|
||||
'-fdata-sections',
|
||||
]
|
||||
test_cflags += [
|
||||
test_global_cflags += [
|
||||
'-ffunction-sections',
|
||||
'-fdata-sections',
|
||||
]
|
||||
@@ -118,15 +136,20 @@ if get_option('buildtype') != 'debug'
|
||||
endif
|
||||
|
||||
if get_option('fuzzer')
|
||||
fuzzer_flags = ['-fsanitize=fuzzer,address,undefined']
|
||||
fuzzer_flags = ['-fsanitize=fuzzer']
|
||||
if get_option('b_sanitize') == 'none'
|
||||
fuzzer_flags += ['-fsanitize=address,undefined']
|
||||
endif
|
||||
add_global_arguments(fuzzer_flags, language: 'cpp')
|
||||
add_global_arguments(fuzzer_flags, language: 'c')
|
||||
add_global_link_arguments(fuzzer_flags, language: 'cpp')
|
||||
endif
|
||||
|
||||
add_global_arguments(common_cxxflags + compiler.get_supported_arguments(test_cxxflags), language: 'cpp')
|
||||
add_global_arguments(common_cflags + c_compiler.get_supported_arguments(test_cflags), language: 'c')
|
||||
add_global_link_arguments(compiler.get_supported_link_arguments(test_ldflags), language: 'cpp')
|
||||
add_global_arguments(compiler.get_supported_arguments(test_global_cxxflags), language: 'cpp')
|
||||
add_global_arguments(c_compiler.get_supported_arguments(test_global_cflags), language: 'c')
|
||||
add_project_arguments(compiler.get_supported_arguments(test_cxxflags), language: 'cpp')
|
||||
add_project_arguments(c_compiler.get_supported_arguments(test_cflags), language: 'c')
|
||||
add_project_link_arguments(compiler.get_supported_link_arguments(test_ldflags), language: 'cpp')
|
||||
|
||||
is_linux = host_machine.system() == 'linux'
|
||||
is_android = get_option('android_ndk') != ''
|
||||
@@ -193,17 +216,6 @@ conf.set('HAVE_STRCASESTR', compiler.has_function('strcasestr'))
|
||||
|
||||
conf.set('HAVE_PRCTL', is_linux)
|
||||
|
||||
conf.set('USE_EVENTFD', is_linux and get_option('eventfd'))
|
||||
conf.set('USE_SIGNALFD', is_linux and get_option('signalfd'))
|
||||
|
||||
if is_windows
|
||||
conf.set('USE_WINSELECT', true)
|
||||
elif is_linux and get_option('epoll')
|
||||
conf.set('USE_EPOLL', true)
|
||||
else
|
||||
conf.set('USE_POLL', true)
|
||||
endif
|
||||
|
||||
if not get_option('syslog').disabled()
|
||||
if compiler.has_function('syslog')
|
||||
conf.set('HAVE_SYSLOG', true)
|
||||
@@ -234,15 +246,19 @@ if boost_dep.version() == '1.67'
|
||||
warning('Your Boost version 1.67 is known to be buggy, and the MPD build will fail. Please upgrade to Boost 1.68 or later.')
|
||||
endif
|
||||
|
||||
fmt_dep = dependency('fmt', fallback: ['fmt', 'fmt_dep'])
|
||||
|
||||
log = static_library(
|
||||
'log',
|
||||
'src/Log.cxx',
|
||||
'src/LogBackend.cxx',
|
||||
include_directories: inc,
|
||||
dependencies: fmt_dep,
|
||||
)
|
||||
|
||||
log_dep = declare_dependency(
|
||||
link_with: log,
|
||||
dependencies: fmt_dep,
|
||||
)
|
||||
|
||||
sources = [
|
||||
@@ -251,6 +267,7 @@ sources = [
|
||||
'src/protocol/ArgParser.cxx',
|
||||
'src/protocol/Result.cxx',
|
||||
'src/command/CommandError.cxx',
|
||||
'src/command/PositionArg.cxx',
|
||||
'src/command/AllCommands.cxx',
|
||||
'src/command/QueueCommands.cxx',
|
||||
'src/command/TagCommands.cxx',
|
||||
@@ -265,7 +282,6 @@ sources = [
|
||||
'src/command/CommandListBuilder.cxx',
|
||||
'src/Idle.cxx',
|
||||
'src/IdleFlags.cxx',
|
||||
'src/decoder/Domain.cxx',
|
||||
'src/decoder/Thread.cxx',
|
||||
'src/decoder/Control.cxx',
|
||||
'src/decoder/Bridge.cxx',
|
||||
@@ -396,6 +412,7 @@ subdir('src/lib/gcrypt')
|
||||
subdir('src/lib/nfs')
|
||||
subdir('src/lib/oss')
|
||||
subdir('src/lib/pcre')
|
||||
subdir('src/lib/pipewire')
|
||||
subdir('src/lib/pulse')
|
||||
subdir('src/lib/sndio')
|
||||
subdir('src/lib/sqlite')
|
||||
@@ -405,6 +422,8 @@ subdir('src/lib/yajl')
|
||||
|
||||
subdir('src/lib/crypto')
|
||||
|
||||
subdir('src/zeroconf')
|
||||
|
||||
subdir('src/fs')
|
||||
subdir('src/config')
|
||||
subdir('src/tag')
|
||||
@@ -420,7 +439,6 @@ subdir('src/decoder')
|
||||
subdir('src/encoder')
|
||||
subdir('src/song')
|
||||
subdir('src/playlist')
|
||||
subdir('src/zeroconf')
|
||||
|
||||
if curl_dep.found()
|
||||
sources += 'src/RemoteTagCache.cxx'
|
||||
@@ -536,6 +554,7 @@ mpd = build_target(
|
||||
zeroconf_dep,
|
||||
more_deps,
|
||||
chromaprint_dep,
|
||||
fmt_dep,
|
||||
],
|
||||
link_args: link_args,
|
||||
build_by_default: not get_option('fuzzer'),
|
||||
|
@@ -62,7 +62,10 @@ option('dsd', type: 'boolean', value: true, description: 'Support the DSD audio
|
||||
#
|
||||
|
||||
option('database', type: 'boolean', value: true, description: 'enable support for the music database')
|
||||
option('upnp', type: 'feature', description: 'UPnP client support')
|
||||
option('upnp', type: 'combo',
|
||||
choices: ['auto', 'pupnp', 'npupnp', 'disabled'],
|
||||
value: 'auto',
|
||||
description: 'UPnP client support')
|
||||
option('libmpdclient', type: 'feature', description: 'libmpdclient support (for the proxy database plugin)')
|
||||
|
||||
#
|
||||
@@ -104,7 +107,6 @@ option('smbclient', type: 'feature', value: 'disabled', description: 'SMB suppor
|
||||
|
||||
option('qobuz', type: 'feature', description: 'Qobuz client')
|
||||
option('soundcloud', type: 'feature', description: 'SoundCloud client')
|
||||
option('tidal', type: 'feature', description: 'Tidal client')
|
||||
|
||||
#
|
||||
# Archive plugins
|
||||
@@ -135,6 +137,7 @@ option('gme', type: 'feature', description: 'Game Music Emulator decoder plugin'
|
||||
option('mad', type: 'feature', description: 'MP3 decoder using libmad')
|
||||
option('mikmod', type: 'feature', description: 'MikMod decoder plugin')
|
||||
option('modplug', type: 'feature', description: 'Modplug decoder plugin')
|
||||
option('openmpt', type: 'feature', description: 'OpenMPT decoder plugin')
|
||||
option('mpcdec', type: 'feature', description: 'Musepack decoder plugin')
|
||||
option('mpg123', type: 'feature', description: 'MP3 decoder using libmpg123')
|
||||
option('opus', type: 'feature', description: 'Opus decoder plugin')
|
||||
@@ -174,9 +177,11 @@ option('jack', type: 'feature', description: 'JACK output plugin')
|
||||
option('openal', type: 'feature', description: 'OpenAL output plugin')
|
||||
option('oss', type: 'feature', description: 'Open Sound System support')
|
||||
option('pipe', type: 'boolean', value: true, description: 'Pipe output plugin')
|
||||
option('pipewire', type: 'feature', description: 'PipeWire support')
|
||||
option('pulse', type: 'feature', description: 'PulseAudio support')
|
||||
option('recorder', type: 'boolean', value: true, description: 'Recorder output plugin')
|
||||
option('shout', type: 'feature', description: 'Shoutcast streaming support using libshout')
|
||||
option('snapcast', type: 'boolean', value: true, description: 'Snapcast output plugin')
|
||||
option('sndio', type: 'feature', description: 'sndio output plugin')
|
||||
option('solaris_output', type: 'feature', description: 'Solaris /dev/audio support')
|
||||
|
||||
|
@@ -55,10 +55,10 @@ class AutotoolsProject(MakeProject):
|
||||
subprocess.check_call(configure, cwd=build, env=toolchain.env)
|
||||
return build
|
||||
|
||||
def build(self, toolchain):
|
||||
def _build(self, toolchain):
|
||||
build = self.configure(toolchain)
|
||||
if self.subdirs is not None:
|
||||
for subdir in self.subdirs:
|
||||
MakeProject.build(self, toolchain, os.path.join(build, subdir))
|
||||
self.build_make(toolchain, os.path.join(build, subdir))
|
||||
else:
|
||||
MakeProject.build(self, toolchain, build)
|
||||
self.build_make(toolchain, build)
|
||||
|
@@ -12,7 +12,7 @@ class BoostProject(Project):
|
||||
name='boost', version=version,
|
||||
**kwargs)
|
||||
|
||||
def build(self, toolchain):
|
||||
def _build(self, toolchain):
|
||||
src = self.unpack(toolchain)
|
||||
|
||||
# install the headers manually; don't build any library
|
||||
|
@@ -1,27 +1,63 @@
|
||||
import os
|
||||
import subprocess
|
||||
|
||||
from build.project import Project
|
||||
|
||||
def __write_cmake_compiler(f, language, compiler):
|
||||
s = compiler.split(' ', 1)
|
||||
if len(s) == 2:
|
||||
print(f'set(CMAKE_{language}_COMPILER_LAUNCHER {s[0]})', file=f)
|
||||
compiler = s[1]
|
||||
print(f'set(CMAKE_{language}_COMPILER {compiler})', file=f)
|
||||
|
||||
def __write_cmake_toolchain_file(f, toolchain):
|
||||
if '-darwin' in toolchain.actual_arch:
|
||||
cmake_system_name = 'Darwin'
|
||||
elif toolchain.is_windows:
|
||||
cmake_system_name = 'Windows'
|
||||
else:
|
||||
cmake_system_name = 'Linux'
|
||||
|
||||
f.write(f"""
|
||||
set(CMAKE_SYSTEM_NAME {cmake_system_name})
|
||||
set(CMAKE_SYSTEM_PROCESSOR {toolchain.actual_arch.split('-', 1)[0]})
|
||||
|
||||
set(CMAKE_C_COMPILER_TARGET {toolchain.actual_arch})
|
||||
set(CMAKE_CXX_COMPILER_TARGET {toolchain.actual_arch})
|
||||
|
||||
set(CMAKE_C_FLAGS "{toolchain.cflags} {toolchain.cppflags}")
|
||||
set(CMAKE_CXX_FLAGS "{toolchain.cxxflags} {toolchain.cppflags}")
|
||||
""")
|
||||
__write_cmake_compiler(f, 'C', toolchain.cc)
|
||||
__write_cmake_compiler(f, 'CXX', toolchain.cxx)
|
||||
|
||||
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)
|
||||
|
||||
# Several targets need a sysroot to prevent pkg-config from
|
||||
# looking for libraries on the build host (TODO: fix this
|
||||
# properly); but we must not do that on Android because the NDK
|
||||
# has a sysroot already
|
||||
if '-android' not in toolchain.actual_arch and '-darwin' not in toolchain.actual_arch:
|
||||
cross_args.append('-DCMAKE_SYSROOT=' + toolchain.install_prefix)
|
||||
|
||||
os.makedirs(build, exist_ok=True)
|
||||
cmake_toolchain_file = os.path.join(build, 'cmake_toolchain_file')
|
||||
with open(cmake_toolchain_file, 'w') as f:
|
||||
__write_cmake_toolchain_file(f, toolchain)
|
||||
|
||||
configure = [
|
||||
'cmake',
|
||||
src,
|
||||
|
||||
'-DCMAKE_TOOLCHAIN_FILE=' + cmake_toolchain_file,
|
||||
|
||||
'-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
|
||||
|
||||
@@ -29,17 +65,22 @@ def configure(toolchain, src, build, args=()):
|
||||
|
||||
class CmakeProject(Project):
|
||||
def __init__(self, url, md5, installed, configure_args=[],
|
||||
windows_configure_args=[],
|
||||
**kwargs):
|
||||
Project.__init__(self, url, md5, installed, **kwargs)
|
||||
self.configure_args = configure_args
|
||||
self.windows_configure_args = windows_configure_args
|
||||
|
||||
def configure(self, toolchain):
|
||||
src = self.unpack(toolchain)
|
||||
build = self.make_build_path(toolchain)
|
||||
configure(toolchain, src, build, self.configure_args)
|
||||
configure_args = self.configure_args
|
||||
if toolchain.is_windows:
|
||||
configure_args = configure_args + self.windows_configure_args
|
||||
configure(toolchain, src, build, configure_args)
|
||||
return build
|
||||
|
||||
def build(self, toolchain):
|
||||
def _build(self, toolchain):
|
||||
build = self.configure(toolchain)
|
||||
subprocess.check_call(['ninja', 'install'],
|
||||
cwd=build, env=toolchain.env)
|
||||
|
@@ -10,7 +10,7 @@ class FfmpegProject(Project):
|
||||
self.configure_args = configure_args
|
||||
self.cppflags = cppflags
|
||||
|
||||
def build(self, toolchain):
|
||||
def _build(self, toolchain):
|
||||
src = self.unpack(toolchain)
|
||||
build = self.make_build_path(toolchain)
|
||||
|
||||
|
@@ -26,7 +26,7 @@ class JackProject(Project):
|
||||
base='jack2-' + self.version,
|
||||
**kwargs)
|
||||
|
||||
def build(self, toolchain):
|
||||
def _build(self, toolchain):
|
||||
src = self.unpack(toolchain)
|
||||
|
||||
includes = ['jack.h', 'ringbuffer.h', 'systemdeps.h', 'transport.h', 'types.h', 'weakmacros.h']
|
||||
|
@@ -17,29 +17,17 @@ libmpdclient = MesonProject(
|
||||
'lib/libmpdclient.a',
|
||||
)
|
||||
|
||||
libogg = AutotoolsProject(
|
||||
libogg = CmakeProject(
|
||||
'http://downloads.xiph.org/releases/ogg/libogg-1.3.4.tar.xz',
|
||||
'c163bc12bc300c401b6aa35907ac682671ea376f13ae0969a220f7ddf71893fe',
|
||||
'lib/libogg.a',
|
||||
[
|
||||
'--disable-shared', '--enable-static',
|
||||
'-DBUILD_SHARED_LIBS=OFF',
|
||||
'-DINSTALL_DOCS=OFF',
|
||||
'-DINSTALL_CMAKE_PACKAGE_MODULE=OFF',
|
||||
],
|
||||
)
|
||||
|
||||
libvorbis = AutotoolsProject(
|
||||
'http://downloads.xiph.org/releases/vorbis/libvorbis-1.3.7.tar.xz',
|
||||
'b33cc4934322bcbf6efcbacf49e3ca01aadbea4114ec9589d1b1e9d20f72954b',
|
||||
'lib/libvorbis.a',
|
||||
[
|
||||
'--disable-shared', '--enable-static',
|
||||
],
|
||||
|
||||
edits={
|
||||
# this option is not understood by clang
|
||||
'configure': lambda data: data.replace('-mno-ieee-fp', ' '),
|
||||
}
|
||||
)
|
||||
|
||||
opus = AutotoolsProject(
|
||||
'https://archive.mozilla.org/pub/opus/opus-1.3.1.tar.gz',
|
||||
'65b58e1e25b2a114157014736a3d9dfeaad8d41be1c8179866f144a2fb44ff9d',
|
||||
@@ -123,18 +111,27 @@ libmodplug = AutotoolsProject(
|
||||
],
|
||||
)
|
||||
|
||||
libopenmpt = AutotoolsProject(
|
||||
'https://lib.openmpt.org/files/libopenmpt/src/libopenmpt-0.5.8+release.autotools.tar.gz',
|
||||
'61de7cc0c011b10472ca16adcc123689',
|
||||
'lib/libopenmpt.a',
|
||||
[
|
||||
'--disable-shared', '--enable-static'
|
||||
],
|
||||
)
|
||||
|
||||
wildmidi = CmakeProject(
|
||||
'https://codeload.github.com/Mindwerks/wildmidi/tar.gz/wildmidi-0.4.3',
|
||||
'498e5a96455bb4b91b37188ad6dcb070824e92c44f5ed452b90adbaec8eef3c5',
|
||||
'https://codeload.github.com/Mindwerks/wildmidi/tar.gz/wildmidi-0.4.4',
|
||||
'6f267c8d331e9859906837e2c197093fddec31829d2ebf7b958cf6b7ae935430',
|
||||
'lib/libWildMidi.a',
|
||||
[
|
||||
'-DBUILD_SHARED_LIBS=OFF',
|
||||
'-DWANT_PLAYER=OFF',
|
||||
'-DWANT_STATIC=ON',
|
||||
],
|
||||
base='wildmidi-wildmidi-0.4.3',
|
||||
base='wildmidi-wildmidi-0.4.4',
|
||||
name='wildmidi',
|
||||
version='0.4.3',
|
||||
version='0.4.4',
|
||||
)
|
||||
|
||||
gme = CmakeProject(
|
||||
@@ -379,53 +376,46 @@ ffmpeg = FfmpegProject(
|
||||
)
|
||||
|
||||
openssl = OpenSSLProject(
|
||||
'https://www.openssl.org/source/openssl-3.0.0-alpha16.tar.gz',
|
||||
'08ce8244b59d75f40f91170dfcb012bf25309cdcb1fef9502e39d694f883d1d1',
|
||||
'https://www.openssl.org/source/openssl-3.0.0.tar.gz',
|
||||
'59eedfcb46c25214c9bd37ed6078297b4df01d012267fe9e9eee31f61bc70536',
|
||||
'include/openssl/ossl_typ.h',
|
||||
)
|
||||
|
||||
curl = AutotoolsProject(
|
||||
'https://curl.se/download/curl-7.76.1.tar.xz',
|
||||
'64bb5288c39f0840c07d077e30d9052e1cbb9fa6c2dc52523824cc859e679145',
|
||||
curl = CmakeProject(
|
||||
'https://curl.se/download/curl-7.79.1.tar.xz',
|
||||
'0606f74b1182ab732a17c11613cbbaf7084f2e6cca432642d0e3ad7c224c3689',
|
||||
'lib/libcurl.a',
|
||||
[
|
||||
'--disable-shared', '--enable-static',
|
||||
'--disable-debug',
|
||||
'--enable-http',
|
||||
'--enable-ipv6',
|
||||
'--disable-ftp', '--disable-file',
|
||||
'--disable-ldap', '--disable-ldaps',
|
||||
'--disable-rtsp', '--disable-proxy', '--disable-dict', '--disable-telnet',
|
||||
'--disable-tftp', '--disable-pop3', '--disable-imap', '--disable-smtp',
|
||||
'--disable-smb',
|
||||
'--disable-gopher',
|
||||
'--disable-manual',
|
||||
'--disable-threaded-resolver', '--disable-verbose', '--disable-sspi',
|
||||
'--disable-crypto-auth', '--disable-ntlm-wb', '--disable-tls-srp', '--disable-cookies',
|
||||
'--disable-doh',
|
||||
'--disable-mime',
|
||||
'--disable-netrc',
|
||||
'--disable-progress-meter',
|
||||
'--disable-alt-svc',
|
||||
'--without-gnutls', '--without-nss', '--without-libssh2',
|
||||
|
||||
# native Windows SSL/TLS support, option ignored on non-Windows builds
|
||||
'--with-schannel',
|
||||
'-DBUILD_CURL_EXE=OFF',
|
||||
'-DBUILD_SHARED_LIBS=OFF',
|
||||
'-DCURL_DISABLE_VERBOSE_STRINGS=ON',
|
||||
'-DCURL_DISABLE_LDAP=ON',
|
||||
'-DCURL_DISABLE_TELNET=ON',
|
||||
'-DCURL_DISABLE_DICT=ON',
|
||||
'-DCURL_DISABLE_FILE=ON',
|
||||
'-DCURL_DISABLE_FTP=ON',
|
||||
'-DCURL_DISABLE_TFTP=ON',
|
||||
'-DCURL_DISABLE_LDAPS=ON',
|
||||
'-DCURL_DISABLE_RTSP=ON',
|
||||
'-DCURL_DISABLE_PROXY=ON',
|
||||
'-DCURL_DISABLE_POP3=ON',
|
||||
'-DCURL_DISABLE_IMAP=ON',
|
||||
'-DCURL_DISABLE_SMTP=ON',
|
||||
'-DCURL_DISABLE_GOPHER=ON',
|
||||
'-DCURL_DISABLE_COOKIES=ON',
|
||||
'-DCURL_DISABLE_CRYPTO_AUTH=ON',
|
||||
'-DCURL_DISABLE_ALTSVC=ON',
|
||||
'-DCMAKE_USE_LIBSSH2=OFF',
|
||||
'-DCURL_WINDOWS_SSPI=OFF',
|
||||
'-DCURL_DISABLE_NTLM=ON',
|
||||
'-DBUILD_TESTING=OFF',
|
||||
],
|
||||
windows_configure_args=[
|
||||
'-DCMAKE_USE_SCHANNEL=ON',
|
||||
],
|
||||
|
||||
patches='src/lib/curl/patches',
|
||||
)
|
||||
|
||||
libexpat = AutotoolsProject(
|
||||
'https://github.com/libexpat/libexpat/releases/download/R_2_2_9/expat-2.2.9.tar.bz2',
|
||||
'f1063084dc4302a427dabcca499c8312b3a32a29b7d2506653ecc8f950a9a237',
|
||||
'lib/libexpat.a',
|
||||
[
|
||||
'--disable-shared', '--enable-static',
|
||||
'--without-docbook',
|
||||
],
|
||||
)
|
||||
|
||||
libnfs = AutotoolsProject(
|
||||
'https://github.com/sahlberg/libnfs/archive/libnfs-4.0.0.tar.gz',
|
||||
'6ee77e9fe220e2d3e3b1f53cfea04fb319828cc7dbb97dd9df09e46e901d797d',
|
||||
@@ -451,7 +441,7 @@ jack = JackProject(
|
||||
)
|
||||
|
||||
boost = BoostProject(
|
||||
'https://boostorg.jfrog.io/artifactory/main/release/1.76.0/source/boost_1_76_0.tar.bz2',
|
||||
'f0397ba6e982c4450f27bf32a2a83292aba035b827a5623a14636ea583318c41',
|
||||
'https://boostorg.jfrog.io/artifactory/main/release/1.77.0/source/boost_1_77_0.tar.bz2',
|
||||
'fc9f85fc030e233142908241af7a846e60630aa7388de9a5fafb1f3a26840854',
|
||||
'include/boost/version.hpp',
|
||||
)
|
||||
|
@@ -22,7 +22,7 @@ class MakeProject(Project):
|
||||
subprocess.check_call(['/usr/bin/make'] + args,
|
||||
cwd=wd, env=toolchain.env)
|
||||
|
||||
def build(self, toolchain, wd, install=True):
|
||||
def build_make(self, toolchain, wd, install=True):
|
||||
self.make(toolchain, wd, self.get_make_args(toolchain))
|
||||
if install:
|
||||
self.make(toolchain, wd, self.get_make_install_args(toolchain))
|
||||
|
@@ -1,4 +1,5 @@
|
||||
import os.path, subprocess, sys
|
||||
import platform
|
||||
|
||||
from build.project import Project
|
||||
|
||||
@@ -34,41 +35,46 @@ def make_cross_file(toolchain):
|
||||
path = os.path.join(toolchain.build_path, 'meson.cross')
|
||||
os.makedirs(toolchain.build_path, exist_ok=True)
|
||||
with open(path, 'w') as f:
|
||||
f.write("""
|
||||
f.write(f"""
|
||||
[binaries]
|
||||
c = '%s'
|
||||
cpp = '%s'
|
||||
ar = '%s'
|
||||
strip = '%s'
|
||||
pkgconfig = '%s'
|
||||
%s
|
||||
c = '{toolchain.cc}'
|
||||
cpp = '{toolchain.cxx}'
|
||||
ar = '{toolchain.ar}'
|
||||
strip = '{toolchain.strip}'
|
||||
pkgconfig = '{toolchain.pkg_config}'
|
||||
""")
|
||||
|
||||
if toolchain.is_windows and platform.system() != 'Windows':
|
||||
f.write(f"windres = '{toolchain.windres}'\n")
|
||||
|
||||
# Run unit tests with WINE when cross-building for Windows
|
||||
print("exe_wrapper = 'wine'", file=f)
|
||||
|
||||
f.write(f"""
|
||||
[properties]
|
||||
root = '%s'
|
||||
root = '{toolchain.install_prefix}'
|
||||
|
||||
c_args = %s
|
||||
c_link_args = %s
|
||||
[built-in options]
|
||||
c_args = {repr((toolchain.cppflags + ' ' + toolchain.cflags).split())}
|
||||
c_link_args = {repr(toolchain.ldflags.split() + toolchain.libs.split())}
|
||||
|
||||
cpp_args = %s
|
||||
cpp_link_args = %s
|
||||
cpp_args = {repr((toolchain.cppflags + ' ' + toolchain.cxxflags).split())}
|
||||
cpp_link_args = {repr(toolchain.ldflags.split() + toolchain.libs.split())}
|
||||
""")
|
||||
|
||||
if 'android' in toolchain.arch:
|
||||
f.write("""
|
||||
# Keep Meson from executing Android-x86 test binariees
|
||||
needs_exe_wrapper = true
|
||||
""")
|
||||
|
||||
f.write(f"""
|
||||
[host_machine]
|
||||
system = '%s'
|
||||
cpu_family = '%s'
|
||||
cpu = '%s'
|
||||
endian = '%s'
|
||||
""" % (toolchain.cc, toolchain.cxx, toolchain.ar, toolchain.strip,
|
||||
toolchain.pkg_config,
|
||||
windres,
|
||||
toolchain.install_prefix,
|
||||
repr((toolchain.cppflags + ' ' + toolchain.cflags).split()),
|
||||
repr(toolchain.ldflags.split() + toolchain.libs.split()),
|
||||
repr((toolchain.cppflags + ' ' + toolchain.cxxflags).split()),
|
||||
repr(toolchain.ldflags.split() + toolchain.libs.split()),
|
||||
system, cpu_family, cpu, endian))
|
||||
system = '{system}'
|
||||
cpu_family = '{cpu_family}'
|
||||
cpu = '{cpu}'
|
||||
endian = '{endian}'
|
||||
""")
|
||||
return path
|
||||
|
||||
def configure(toolchain, src, build, args=()):
|
||||
@@ -79,11 +85,6 @@ def configure(toolchain, src, build, args=()):
|
||||
|
||||
'--prefix', toolchain.install_prefix,
|
||||
|
||||
# this is necessary because Meson uses Debian's build machine
|
||||
# MultiArch path (e.g. "lib/x86_64-linux-gnu") for cross
|
||||
# builds, which is obviously wrong
|
||||
'--libdir', 'lib',
|
||||
|
||||
'--buildtype', 'plain',
|
||||
|
||||
'--default-library=static',
|
||||
@@ -110,7 +111,7 @@ class MesonProject(Project):
|
||||
configure(toolchain, src, build, self.configure_args)
|
||||
return build
|
||||
|
||||
def build(self, toolchain):
|
||||
def _build(self, toolchain):
|
||||
build = self.configure(toolchain)
|
||||
subprocess.check_call(['ninja', 'install'],
|
||||
cwd=build, env=toolchain.env)
|
||||
|
@@ -17,7 +17,13 @@ class OpenSSLProject(MakeProject):
|
||||
'build_libs',
|
||||
]
|
||||
|
||||
def build(self, toolchain):
|
||||
def get_make_install_args(self, toolchain):
|
||||
# OpenSSL's Makefile runs "ranlib" during installation
|
||||
return MakeProject.get_make_install_args(self, toolchain) + [
|
||||
'RANLIB=' + toolchain.ranlib,
|
||||
]
|
||||
|
||||
def _build(self, toolchain):
|
||||
src = self.unpack(toolchain, out_of_tree=False)
|
||||
|
||||
# OpenSSL has a weird target architecture scheme with lots of
|
||||
@@ -52,4 +58,4 @@ class OpenSSLProject(MakeProject):
|
||||
openssl_arch,
|
||||
'--prefix=' + toolchain.install_prefix],
|
||||
cwd=src, env=toolchain.env)
|
||||
MakeProject.build(self, toolchain, src)
|
||||
self.build_make(toolchain, src)
|
||||
|
@@ -20,7 +20,7 @@ class Project:
|
||||
self.base = base
|
||||
|
||||
if name is None or version is None:
|
||||
m = re.match(r'^([-\w]+)-(\d[\d.]*[a-z]?[\d.]*(?:-alpha\d+)?)$', self.base)
|
||||
m = re.match(r'^([-\w]+)-(\d[\d.]*[a-z]?[\d.]*(?:-(?:alpha|beta)\d+)?)(\+.*)?$', self.base)
|
||||
if name is None: name = m.group(1)
|
||||
if version is None: version = m.group(2)
|
||||
|
||||
@@ -79,3 +79,6 @@ class Project:
|
||||
pass
|
||||
os.makedirs(path, exist_ok=True)
|
||||
return path
|
||||
|
||||
def build(self, toolchain):
|
||||
self._build(toolchain)
|
||||
|
@@ -31,6 +31,8 @@ def guess_digest_algorithm(digest):
|
||||
return hashlib.sha1
|
||||
elif l == 64:
|
||||
return hashlib.sha256
|
||||
elif l == 128:
|
||||
return hashlib.sha512
|
||||
else:
|
||||
return None
|
||||
|
||||
|
@@ -7,7 +7,7 @@ class ZlibProject(Project):
|
||||
**kwargs):
|
||||
Project.__init__(self, url, md5, installed, **kwargs)
|
||||
|
||||
def build(self, toolchain):
|
||||
def _build(self, toolchain):
|
||||
src = self.unpack(toolchain, out_of_tree=False)
|
||||
|
||||
subprocess.check_call(['/usr/bin/make', '--quiet',
|
||||
|
@@ -37,6 +37,7 @@
|
||||
#include "fs/Traits.hxx"
|
||||
#include "fs/FileSystem.hxx"
|
||||
#include "fs/StandardDirectory.hxx"
|
||||
#include "event/Features.h"
|
||||
#include "io/uring/Features.h"
|
||||
#include "util/Domain.hxx"
|
||||
#include "util/OptionDef.hxx"
|
||||
|
@@ -27,7 +27,7 @@
|
||||
|
||||
#include <cassert>
|
||||
|
||||
static const char *const idle_names[] = {
|
||||
static constexpr const char * idle_names[] = {
|
||||
"database",
|
||||
"stored_playlist",
|
||||
"playlist",
|
||||
@@ -42,7 +42,7 @@ static const char *const idle_names[] = {
|
||||
"neighbor",
|
||||
"mount",
|
||||
"partition",
|
||||
nullptr
|
||||
nullptr,
|
||||
};
|
||||
|
||||
const char*const*
|
||||
|
@@ -25,8 +25,6 @@
|
||||
#ifndef MPD_IDLE_FLAGS_HXX
|
||||
#define MPD_IDLE_FLAGS_HXX
|
||||
|
||||
#include "util/Compiler.h"
|
||||
|
||||
/** song database has been updated*/
|
||||
static constexpr unsigned IDLE_DATABASE = 0x1;
|
||||
|
||||
@@ -73,7 +71,7 @@ static constexpr unsigned IDLE_PARTITION = 0x2000;
|
||||
/**
|
||||
* Get idle names
|
||||
*/
|
||||
gcc_const
|
||||
[[gnu::const]]
|
||||
const char*const*
|
||||
idle_get_names() noexcept;
|
||||
|
||||
@@ -81,7 +79,7 @@ idle_get_names() noexcept;
|
||||
* Parse an idle name and return its mask. Returns 0 if the given
|
||||
* name is unknown.
|
||||
*/
|
||||
gcc_nonnull_all gcc_pure
|
||||
[[gnu::nonnull]] [[gnu::pure]]
|
||||
unsigned
|
||||
idle_parse_name(const char *name) noexcept;
|
||||
|
||||
|
@@ -37,6 +37,10 @@
|
||||
#include "db/update/Service.hxx"
|
||||
#include "storage/StorageInterface.hxx"
|
||||
|
||||
#ifdef ENABLE_INOTIFY
|
||||
#include "db/update/InotifyUpdate.hxx"
|
||||
#endif
|
||||
|
||||
#ifdef ENABLE_NEIGHBOR_PLUGINS
|
||||
#include "neighbor/Glue.hxx"
|
||||
#endif
|
||||
|
@@ -24,7 +24,6 @@
|
||||
#include "event/Loop.hxx"
|
||||
#include "event/Thread.hxx"
|
||||
#include "event/MaskMonitor.hxx"
|
||||
#include "util/Compiler.h"
|
||||
|
||||
#ifdef ENABLE_SYSTEMD_DAEMON
|
||||
#include "lib/systemd/Watchdog.hxx"
|
||||
@@ -44,6 +43,9 @@ class NeighborGlue;
|
||||
#include "db/Ptr.hxx"
|
||||
class Storage;
|
||||
class UpdateService;
|
||||
#ifdef ENABLE_INOTIFY
|
||||
class InotifyUpdate;
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#include <memory>
|
||||
@@ -121,6 +123,10 @@ struct Instance final
|
||||
Storage *storage = nullptr;
|
||||
|
||||
UpdateService *update = nullptr;
|
||||
|
||||
#ifdef ENABLE_INOTIFY
|
||||
std::unique_ptr<InotifyUpdate> inotify_update;
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#ifdef ENABLE_CURL
|
||||
@@ -168,7 +174,7 @@ struct Instance final
|
||||
* Find a #Partition with the given name. Returns nullptr if
|
||||
* no such partition was found.
|
||||
*/
|
||||
gcc_pure
|
||||
[[gnu::pure]]
|
||||
Partition *FindPartition(const char *name) noexcept;
|
||||
|
||||
void DeletePartition(Partition &partition) noexcept;
|
||||
@@ -194,7 +200,7 @@ struct Instance final
|
||||
#endif
|
||||
|
||||
#ifdef ENABLE_SQLITE
|
||||
bool HasStickerDatabase() noexcept {
|
||||
bool HasStickerDatabase() const noexcept {
|
||||
return sticker_database != nullptr;
|
||||
}
|
||||
#endif
|
||||
|
@@ -25,13 +25,16 @@
|
||||
#include "config/Data.hxx"
|
||||
#include "config/Option.hxx"
|
||||
#include "config/Net.hxx"
|
||||
#include "lib/fmt/ExceptionFormatter.hxx"
|
||||
#include "lib/fmt/PathFormatter.hxx"
|
||||
#include "net/AllocatedSocketAddress.hxx"
|
||||
#include "net/UniqueSocketDescriptor.hxx"
|
||||
#include "net/SocketUtil.hxx"
|
||||
#include "system/Error.hxx"
|
||||
#include "util/RuntimeError.hxx"
|
||||
#include "fs/AllocatedPath.hxx"
|
||||
#include "fs/XDG.hxx"
|
||||
#include "util/Domain.hxx"
|
||||
#include "util/RuntimeError.hxx"
|
||||
|
||||
#include <sys/stat.h>
|
||||
|
||||
@@ -41,6 +44,10 @@
|
||||
|
||||
#define DEFAULT_PORT 6600
|
||||
|
||||
#if defined(USE_XDG) && defined(HAVE_UN)
|
||||
static constexpr Domain listen_domain("listen");
|
||||
#endif
|
||||
|
||||
int listen_port;
|
||||
|
||||
#ifdef ENABLE_SYSTEMD_DAEMON
|
||||
@@ -98,9 +105,9 @@ ListenXdgRuntimeDir(ClientListener &listener) noexcept
|
||||
listener.AddFD(std::move(fd), std::move(address));
|
||||
return true;
|
||||
} catch (...) {
|
||||
FormatError(std::current_exception(),
|
||||
"Failed to listen on '%s' (not fatal)",
|
||||
socket_path.c_str());
|
||||
FmtError(listen_domain,
|
||||
"Failed to listen on '{}' (not fatal): {}",
|
||||
socket_path, std::current_exception());
|
||||
return false;
|
||||
}
|
||||
#else
|
||||
|
@@ -22,6 +22,7 @@
|
||||
#include "client/Client.hxx"
|
||||
#include "fs/AllocatedPath.hxx"
|
||||
#include "ls.hxx"
|
||||
#include "storage/Registry.hxx"
|
||||
#include "util/ASCII.hxx"
|
||||
#include "util/UriExtract.hxx"
|
||||
|
||||
@@ -67,9 +68,13 @@ LocateAbsoluteUri(UriPluginKind kind, const char *uri
|
||||
{
|
||||
switch (kind) {
|
||||
case UriPluginKind::INPUT:
|
||||
case UriPluginKind::STORAGE: // TODO: separate check for storage plugins
|
||||
if (!uri_supported_scheme(uri))
|
||||
throw std::runtime_error("Unsupported URI scheme");
|
||||
throw std::invalid_argument("Unsupported URI scheme");
|
||||
break;
|
||||
|
||||
case UriPluginKind::STORAGE:
|
||||
/* plugin support will be checked after the
|
||||
Storage::MapToRelativeUTF8() call */
|
||||
break;
|
||||
|
||||
case UriPluginKind::PLAYLIST:
|
||||
@@ -88,6 +93,10 @@ LocateAbsoluteUri(UriPluginKind kind, const char *uri
|
||||
return LocatedUri(LocatedUri::Type::RELATIVE,
|
||||
suffix.data());
|
||||
}
|
||||
|
||||
if (kind == UriPluginKind::STORAGE &&
|
||||
GetStoragePluginByUri(uri) == nullptr)
|
||||
throw std::invalid_argument("Unsupported URI scheme");
|
||||
#endif
|
||||
|
||||
return LocatedUri(LocatedUri::Type::ABSOLUTE, uri);
|
||||
@@ -105,7 +114,7 @@ LocateUri(UriPluginKind kind,
|
||||
const char *path_utf8 = StringAfterPrefixCaseASCII(uri, "file://");
|
||||
if (path_utf8 != nullptr) {
|
||||
if (!PathTraitsUTF8::IsAbsolute(path_utf8))
|
||||
throw std::runtime_error("Malformed file:// URI");
|
||||
throw std::invalid_argument("Malformed file:// URI");
|
||||
|
||||
return LocateFileUri(path_utf8, client
|
||||
#ifdef ENABLE_DATABASE
|
||||
|
159
src/Log.cxx
159
src/Log.cxx
@@ -17,166 +17,35 @@
|
||||
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*/
|
||||
|
||||
#include "LogV.hxx"
|
||||
#include "Log.hxx"
|
||||
#include "lib/fmt/ExceptionFormatter.hxx"
|
||||
#include "util/Domain.hxx"
|
||||
#include "util/Exception.hxx"
|
||||
|
||||
#include <cerrno>
|
||||
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <fmt/format.h>
|
||||
|
||||
static constexpr Domain exception_domain("exception");
|
||||
|
||||
void
|
||||
LogFormatV(LogLevel level, const Domain &domain,
|
||||
const char *fmt, std::va_list ap) noexcept
|
||||
LogVFmt(LogLevel level, const Domain &domain,
|
||||
fmt::string_view format_str, fmt::format_args args) noexcept
|
||||
{
|
||||
char msg[1024];
|
||||
vsnprintf(msg, sizeof(msg), fmt, ap);
|
||||
Log(level, domain, msg);
|
||||
}
|
||||
|
||||
void
|
||||
LogFormat(LogLevel level, const Domain &domain, const char *fmt, ...) noexcept
|
||||
{
|
||||
std::va_list ap;
|
||||
va_start(ap, fmt);
|
||||
LogFormatV(level, domain, fmt, ap);
|
||||
va_end(ap);
|
||||
}
|
||||
|
||||
void
|
||||
FormatDebug(const Domain &domain, const char *fmt, ...) noexcept
|
||||
{
|
||||
std::va_list ap;
|
||||
va_start(ap, fmt);
|
||||
LogFormatV(LogLevel::DEBUG, domain, fmt, ap);
|
||||
va_end(ap);
|
||||
}
|
||||
|
||||
void
|
||||
FormatInfo(const Domain &domain, const char *fmt, ...) noexcept
|
||||
{
|
||||
std::va_list ap;
|
||||
va_start(ap, fmt);
|
||||
LogFormatV(LogLevel::INFO, domain, fmt, ap);
|
||||
va_end(ap);
|
||||
}
|
||||
|
||||
void
|
||||
FormatNotice(const Domain &domain, const char *fmt, ...) noexcept
|
||||
{
|
||||
std::va_list ap;
|
||||
va_start(ap, fmt);
|
||||
LogFormatV(LogLevel::NOTICE, domain, fmt, ap);
|
||||
va_end(ap);
|
||||
}
|
||||
|
||||
void
|
||||
FormatWarning(const Domain &domain, const char *fmt, ...) noexcept
|
||||
{
|
||||
std::va_list ap;
|
||||
va_start(ap, fmt);
|
||||
LogFormatV(LogLevel::WARNING, domain, fmt, ap);
|
||||
va_end(ap);
|
||||
}
|
||||
|
||||
void
|
||||
FormatError(const Domain &domain, const char *fmt, ...) noexcept
|
||||
{
|
||||
std::va_list ap;
|
||||
va_start(ap, fmt);
|
||||
LogFormatV(LogLevel::ERROR, domain, fmt, ap);
|
||||
va_end(ap);
|
||||
}
|
||||
|
||||
void
|
||||
Log(LogLevel level, const std::exception &e) noexcept
|
||||
{
|
||||
Log(level, exception_domain, GetFullMessage(e).c_str());
|
||||
}
|
||||
|
||||
void
|
||||
Log(LogLevel level, const std::exception &e, const char *msg) noexcept
|
||||
{
|
||||
LogFormat(level, exception_domain, "%s: %s", msg, GetFullMessage(e).c_str());
|
||||
}
|
||||
|
||||
void
|
||||
LogFormat(LogLevel level, const std::exception &e, const char *fmt, ...) noexcept
|
||||
{
|
||||
char msg[1024];
|
||||
std::va_list ap;
|
||||
va_start(ap, fmt);
|
||||
vsnprintf(msg, sizeof(msg), fmt, ap);
|
||||
va_end(ap);
|
||||
|
||||
Log(level, e, msg);
|
||||
fmt::memory_buffer buffer;
|
||||
#if FMT_VERSION >= 80000
|
||||
fmt::vformat_to(std::back_inserter(buffer), format_str, args);
|
||||
#else
|
||||
fmt::vformat_to(buffer, format_str, args);
|
||||
#endif
|
||||
Log(level, domain, {buffer.data(), buffer.size()});
|
||||
}
|
||||
|
||||
void
|
||||
Log(LogLevel level, const std::exception_ptr &ep) noexcept
|
||||
{
|
||||
Log(level, exception_domain, GetFullMessage(ep).c_str());
|
||||
Log(level, exception_domain, GetFullMessage(ep));
|
||||
}
|
||||
|
||||
void
|
||||
Log(LogLevel level, const std::exception_ptr &ep, const char *msg) noexcept
|
||||
{
|
||||
LogFormat(level, exception_domain, "%s: %s", msg,
|
||||
GetFullMessage(ep).c_str());
|
||||
}
|
||||
|
||||
void
|
||||
LogFormat(LogLevel level, const std::exception_ptr &ep, const char *fmt, ...) noexcept
|
||||
{
|
||||
char msg[1024];
|
||||
std::va_list ap;
|
||||
va_start(ap, fmt);
|
||||
vsnprintf(msg, sizeof(msg), fmt, ap);
|
||||
va_end(ap);
|
||||
|
||||
Log(level, ep, msg);
|
||||
}
|
||||
|
||||
void
|
||||
LogErrno(const Domain &domain, int e, const char *msg) noexcept
|
||||
{
|
||||
LogFormat(LogLevel::ERROR, domain, "%s: %s", msg, strerror(e));
|
||||
}
|
||||
|
||||
void
|
||||
LogErrno(const Domain &domain, const char *msg) noexcept
|
||||
{
|
||||
LogErrno(domain, errno, msg);
|
||||
}
|
||||
|
||||
static void
|
||||
FormatErrnoV(const Domain &domain, int e, const char *fmt, std::va_list ap) noexcept
|
||||
{
|
||||
char msg[1024];
|
||||
vsnprintf(msg, sizeof(msg), fmt, ap);
|
||||
|
||||
LogErrno(domain, e, msg);
|
||||
}
|
||||
|
||||
void
|
||||
FormatErrno(const Domain &domain, int e, const char *fmt, ...) noexcept
|
||||
{
|
||||
std::va_list ap;
|
||||
va_start(ap, fmt);
|
||||
FormatErrnoV(domain, e, fmt, ap);
|
||||
va_end(ap);
|
||||
}
|
||||
|
||||
void
|
||||
FormatErrno(const Domain &domain, const char *fmt, ...) noexcept
|
||||
{
|
||||
const int e = errno;
|
||||
|
||||
std::va_list ap;
|
||||
va_start(ap, fmt);
|
||||
FormatErrnoV(domain, e, fmt, ap);
|
||||
va_end(ap);
|
||||
LogFmt(level, exception_domain, "{}: {}", msg, ep);
|
||||
}
|
||||
|
139
src/Log.hxx
139
src/Log.hxx
@@ -21,29 +21,80 @@
|
||||
#define MPD_LOG_HXX
|
||||
|
||||
#include "LogLevel.hxx"
|
||||
#include "util/Compiler.h"
|
||||
|
||||
#include <fmt/core.h>
|
||||
#if FMT_VERSION < 70000 || FMT_VERSION >= 80000
|
||||
#include <fmt/format.h>
|
||||
#endif
|
||||
|
||||
#include <exception>
|
||||
#include <string_view>
|
||||
#include <utility>
|
||||
|
||||
class Domain;
|
||||
|
||||
void
|
||||
Log(LogLevel level, const Domain &domain, const char *msg) noexcept;
|
||||
|
||||
gcc_printf(3,4)
|
||||
void
|
||||
LogFormat(LogLevel level, const Domain &domain, const char *fmt, ...) noexcept;
|
||||
Log(LogLevel level, const Domain &domain, std::string_view msg) noexcept;
|
||||
|
||||
void
|
||||
Log(LogLevel level, const std::exception &e) noexcept;
|
||||
LogVFmt(LogLevel level, const Domain &domain,
|
||||
fmt::string_view format_str, fmt::format_args args) noexcept;
|
||||
|
||||
template<typename S, typename... Args>
|
||||
void
|
||||
Log(LogLevel level, const std::exception &e, const char *msg) noexcept;
|
||||
LogFmt(LogLevel level, const Domain &domain,
|
||||
const S &format_str, Args&&... args) noexcept
|
||||
{
|
||||
#if FMT_VERSION >= 70000
|
||||
return LogVFmt(level, domain, fmt::to_string_view(format_str),
|
||||
fmt::make_args_checked<Args...>(format_str,
|
||||
args...));
|
||||
#else
|
||||
/* expensive fallback for older libfmt versions */
|
||||
const auto result = fmt::format(format_str, args...);
|
||||
return Log(level, domain, result);
|
||||
#endif
|
||||
}
|
||||
|
||||
gcc_printf(3,4)
|
||||
template<typename S, typename... Args>
|
||||
void
|
||||
LogFormat(LogLevel level, const std::exception &e,
|
||||
const char *fmt, ...) noexcept;
|
||||
FmtDebug(const Domain &domain,
|
||||
const S &format_str, Args&&... args) noexcept
|
||||
{
|
||||
LogFmt(LogLevel::DEBUG, domain, format_str, args...);
|
||||
}
|
||||
|
||||
template<typename S, typename... Args>
|
||||
void
|
||||
FmtInfo(const Domain &domain,
|
||||
const S &format_str, Args&&... args) noexcept
|
||||
{
|
||||
LogFmt(LogLevel::INFO, domain, format_str, args...);
|
||||
}
|
||||
|
||||
template<typename S, typename... Args>
|
||||
void
|
||||
FmtNotice(const Domain &domain,
|
||||
const S &format_str, Args&&... args) noexcept
|
||||
{
|
||||
LogFmt(LogLevel::NOTICE, domain, format_str, args...);
|
||||
}
|
||||
|
||||
template<typename S, typename... Args>
|
||||
void
|
||||
FmtWarning(const Domain &domain,
|
||||
const S &format_str, Args&&... args) noexcept
|
||||
{
|
||||
LogFmt(LogLevel::WARNING, domain, format_str, args...);
|
||||
}
|
||||
|
||||
template<typename S, typename... Args>
|
||||
void
|
||||
FmtError(const Domain &domain,
|
||||
const S &format_str, Args&&... args) noexcept
|
||||
{
|
||||
LogFmt(LogLevel::ERROR, domain, format_str, args...);
|
||||
}
|
||||
|
||||
void
|
||||
Log(LogLevel level, const std::exception_ptr &ep) noexcept;
|
||||
@@ -51,76 +102,36 @@ Log(LogLevel level, const std::exception_ptr &ep) noexcept;
|
||||
void
|
||||
Log(LogLevel level, const std::exception_ptr &ep, const char *msg) noexcept;
|
||||
|
||||
gcc_printf(3,4)
|
||||
void
|
||||
LogFormat(LogLevel level, const std::exception_ptr &ep,
|
||||
const char *fmt, ...) noexcept;
|
||||
|
||||
static inline void
|
||||
LogDebug(const Domain &domain, const char *msg) noexcept
|
||||
{
|
||||
Log(LogLevel::DEBUG, domain, msg);
|
||||
}
|
||||
|
||||
gcc_printf(2,3)
|
||||
void
|
||||
FormatDebug(const Domain &domain, const char *fmt, ...) noexcept;
|
||||
|
||||
static inline void
|
||||
LogInfo(const Domain &domain, const char *msg) noexcept
|
||||
{
|
||||
Log(LogLevel::INFO, domain, msg);
|
||||
}
|
||||
|
||||
gcc_printf(2,3)
|
||||
void
|
||||
FormatInfo(const Domain &domain, const char *fmt, ...) noexcept;
|
||||
|
||||
static inline void
|
||||
LogNotice(const Domain &domain, const char *msg) noexcept
|
||||
{
|
||||
Log(LogLevel::NOTICE, domain, msg);
|
||||
}
|
||||
|
||||
gcc_printf(2,3)
|
||||
void
|
||||
FormatNotice(const Domain &domain, const char *fmt, ...) noexcept;
|
||||
|
||||
static inline void
|
||||
LogWarning(const Domain &domain, const char *msg) noexcept
|
||||
{
|
||||
Log(LogLevel::WARNING, domain, msg);
|
||||
}
|
||||
|
||||
gcc_printf(2,3)
|
||||
void
|
||||
FormatWarning(const Domain &domain, const char *fmt, ...) noexcept;
|
||||
|
||||
static inline void
|
||||
LogError(const Domain &domain, const char *msg) noexcept
|
||||
{
|
||||
Log(LogLevel::ERROR, domain, msg);
|
||||
}
|
||||
|
||||
inline void
|
||||
LogError(const std::exception &e) noexcept
|
||||
{
|
||||
Log(LogLevel::ERROR, e);
|
||||
}
|
||||
|
||||
inline void
|
||||
LogError(const std::exception &e, const char *msg) noexcept
|
||||
{
|
||||
Log(LogLevel::ERROR, e, msg);
|
||||
}
|
||||
|
||||
template<typename... Args>
|
||||
inline void
|
||||
FormatError(const std::exception &e, const char *fmt, Args&&... args) noexcept
|
||||
{
|
||||
LogFormat(LogLevel::ERROR, e, fmt, std::forward<Args>(args)...);
|
||||
}
|
||||
|
||||
inline void
|
||||
LogError(const std::exception_ptr &ep) noexcept
|
||||
{
|
||||
@@ -133,30 +144,4 @@ LogError(const std::exception_ptr &ep, const char *msg) noexcept
|
||||
Log(LogLevel::ERROR, ep, msg);
|
||||
}
|
||||
|
||||
template<typename... Args>
|
||||
inline void
|
||||
FormatError(const std::exception_ptr &ep,
|
||||
const char *fmt, Args&&... args) noexcept
|
||||
{
|
||||
LogFormat(LogLevel::ERROR, ep, fmt, std::forward<Args>(args)...);
|
||||
}
|
||||
|
||||
gcc_printf(2,3)
|
||||
void
|
||||
FormatError(const Domain &domain, const char *fmt, ...) noexcept;
|
||||
|
||||
void
|
||||
LogErrno(const Domain &domain, int e, const char *msg) noexcept;
|
||||
|
||||
void
|
||||
LogErrno(const Domain &domain, const char *msg) noexcept;
|
||||
|
||||
gcc_printf(3,4)
|
||||
void
|
||||
FormatErrno(const Domain &domain, int e, const char *fmt, ...) noexcept;
|
||||
|
||||
gcc_printf(2,3)
|
||||
void
|
||||
FormatErrno(const Domain &domain, const char *fmt, ...) noexcept;
|
||||
|
||||
#endif /* LOG_H */
|
||||
|
@@ -103,10 +103,9 @@ log_date() noexcept
|
||||
* characters.
|
||||
*/
|
||||
static int
|
||||
chomp_length(const char *p) noexcept
|
||||
chomp_length(std::string_view p) noexcept
|
||||
{
|
||||
size_t length = strlen(p);
|
||||
return StripRight(p, length);
|
||||
return StripRight(p.data(), p.size());
|
||||
}
|
||||
|
||||
#ifdef HAVE_SYSLOG
|
||||
@@ -137,11 +136,11 @@ ToSysLogLevel(LogLevel log_level) noexcept
|
||||
}
|
||||
|
||||
static void
|
||||
SysLog(const Domain &domain, LogLevel log_level, const char *message) noexcept
|
||||
SysLog(const Domain &domain, LogLevel log_level, std::string_view message) noexcept
|
||||
{
|
||||
syslog(ToSysLogLevel(log_level), "%s: %.*s",
|
||||
domain.GetName(),
|
||||
chomp_length(message), message);
|
||||
chomp_length(message), message.data());
|
||||
}
|
||||
|
||||
void
|
||||
@@ -161,12 +160,12 @@ LogFinishSysLog() noexcept
|
||||
#endif
|
||||
|
||||
static void
|
||||
FileLog(const Domain &domain, const char *message) noexcept
|
||||
FileLog(const Domain &domain, std::string_view message) noexcept
|
||||
{
|
||||
fprintf(stderr, "%s%s: %.*s\n",
|
||||
enable_timestamp ? log_date() : "",
|
||||
domain.GetName(),
|
||||
chomp_length(message), message);
|
||||
chomp_length(message), message.data());
|
||||
|
||||
#ifdef _WIN32
|
||||
/* force-flush the log file, because setvbuf() does not seem
|
||||
@@ -178,14 +177,20 @@ FileLog(const Domain &domain, const char *message) noexcept
|
||||
#endif /* !ANDROID */
|
||||
|
||||
void
|
||||
Log(LogLevel level, const Domain &domain, const char *msg) noexcept
|
||||
Log(LogLevel level, const Domain &domain, std::string_view msg) noexcept
|
||||
{
|
||||
#ifdef ANDROID
|
||||
__android_log_print(ToAndroidLogLevel(level), "MPD",
|
||||
"%s: %s", domain.GetName(), msg);
|
||||
if (logListener != nullptr)
|
||||
"%s: %.*s", domain.GetName(),
|
||||
(int)msg.size(), msg.data());
|
||||
if (logListener != nullptr) {
|
||||
char buffer[1024];
|
||||
snprintf(buffer, sizeof(buffer), "%s: %.*s",
|
||||
domain.GetName(), (int)msg.size(), msg.data());
|
||||
|
||||
logListener->OnLog(Java::GetEnv(), ToAndroidLogLevel(level),
|
||||
"%s: %s", domain.GetName(), msg);
|
||||
buffer);
|
||||
}
|
||||
#else
|
||||
|
||||
if (level < log_threshold)
|
||||
|
@@ -234,22 +234,22 @@ cycle_log_files() noexcept
|
||||
if (out_path.IsNull())
|
||||
return 0;
|
||||
|
||||
FormatDebug(log_domain, "Cycling log files");
|
||||
LogDebug(log_domain, "Cycling log files");
|
||||
close_log_files();
|
||||
|
||||
fd = open_log_file();
|
||||
if (fd < 0) {
|
||||
const std::string out_path_utf8 = out_path.ToUTF8();
|
||||
FormatError(log_domain,
|
||||
"error re-opening log file: %s",
|
||||
out_path_utf8.c_str());
|
||||
FmtError(log_domain,
|
||||
"error re-opening log file: {}",
|
||||
out_path_utf8);
|
||||
return -1;
|
||||
}
|
||||
|
||||
redirect_logs(fd);
|
||||
close(fd);
|
||||
|
||||
FormatDebug(log_domain, "Done cycling log files");
|
||||
LogDebug(log_domain, "Done cycling log files");
|
||||
return 0;
|
||||
#endif
|
||||
}
|
||||
|
102
src/Main.cxx
102
src/Main.cxx
@@ -40,10 +40,11 @@
|
||||
#include "input/cache/Config.hxx"
|
||||
#include "input/cache/Manager.hxx"
|
||||
#include "event/Loop.hxx"
|
||||
#include "event/Call.hxx"
|
||||
#include "fs/AllocatedPath.hxx"
|
||||
#include "fs/Config.hxx"
|
||||
#include "playlist/PlaylistRegistry.hxx"
|
||||
#include "zeroconf/ZeroconfGlue.hxx"
|
||||
#include "zeroconf/Glue.hxx"
|
||||
#include "decoder/DecoderList.hxx"
|
||||
#include "pcm/AudioParser.hxx"
|
||||
#include "pcm/Convert.hxx"
|
||||
@@ -157,7 +158,17 @@ glue_daemonize_init(const struct options *options,
|
||||
static void
|
||||
glue_mapper_init(const ConfigData &config)
|
||||
{
|
||||
mapper_init(config.GetPath(ConfigOption::PLAYLIST_DIR));
|
||||
auto playlist_directory = config.GetPath(ConfigOption::PLAYLIST_DIR);
|
||||
|
||||
#ifdef ANDROID
|
||||
/* if there is no explicit configuration, store playlists in
|
||||
"/sdcard/Android/data/org.musicpd/files/playlists" */
|
||||
if (playlist_directory.IsNull())
|
||||
playlist_directory = context->GetExternalFilesDir(Java::GetEnv(),
|
||||
"playlists");
|
||||
#endif
|
||||
|
||||
mapper_init(std::move(playlist_directory));
|
||||
}
|
||||
|
||||
#ifdef ENABLE_DATABASE
|
||||
@@ -286,9 +297,8 @@ initialize_decoder_and_player(Instance &instance,
|
||||
"positive integer", s);
|
||||
|
||||
if (result < MIN_BUFFER_SIZE) {
|
||||
FormatWarning(config_domain, "buffer size %lu is too small, using %lu bytes instead",
|
||||
(unsigned long)result,
|
||||
(unsigned long)MIN_BUFFER_SIZE);
|
||||
FmtWarning(config_domain, "buffer size {} is too small, using {} bytes instead",
|
||||
result, MIN_BUFFER_SIZE);
|
||||
result = MIN_BUFFER_SIZE;
|
||||
}
|
||||
|
||||
@@ -334,7 +344,7 @@ Instance::BeginShutdownUpdate() noexcept
|
||||
{
|
||||
#ifdef ENABLE_DATABASE
|
||||
#ifdef ENABLE_INOTIFY
|
||||
mpd_inotify_finish();
|
||||
inotify_update.reset();
|
||||
#endif
|
||||
|
||||
if (update != nullptr)
|
||||
@@ -441,7 +451,8 @@ MainConfigured(const struct options &options, const ConfigData &raw_config)
|
||||
command_init();
|
||||
|
||||
for (auto &partition : instance.partitions) {
|
||||
partition.outputs.Configure(instance.rtio_thread.GetEventLoop(),
|
||||
partition.outputs.Configure(instance.io_thread.GetEventLoop(),
|
||||
instance.rtio_thread.GetEventLoop(),
|
||||
raw_config,
|
||||
config.replay_gain);
|
||||
partition.UpdateEffectiveReplayGainMode();
|
||||
@@ -476,7 +487,27 @@ MainConfigured(const struct options &options, const ConfigData &raw_config)
|
||||
};
|
||||
#endif
|
||||
|
||||
ZeroconfInit(raw_config, instance.event_loop);
|
||||
#ifdef HAVE_ZEROCONF
|
||||
std::unique_ptr<ZeroconfHelper> zeroconf;
|
||||
try {
|
||||
auto &event_loop = instance.io_thread.GetEventLoop();
|
||||
BlockingCall(event_loop, [&](){
|
||||
zeroconf = ZeroconfInit(raw_config, event_loop);
|
||||
});
|
||||
} catch (...) {
|
||||
LogError(std::current_exception(),
|
||||
"Zeroconf initialization failed");
|
||||
}
|
||||
|
||||
AtScopeExit(&zeroconf, &instance) {
|
||||
if (zeroconf) {
|
||||
auto &event_loop = instance.io_thread.GetEventLoop();
|
||||
BlockingCall(event_loop, [&](){
|
||||
zeroconf.reset();
|
||||
});
|
||||
}
|
||||
};
|
||||
#endif
|
||||
|
||||
#ifdef ENABLE_DATABASE
|
||||
if (create_db) {
|
||||
@@ -492,15 +523,21 @@ MainConfigured(const struct options &options, const ConfigData &raw_config)
|
||||
if (raw_config.GetBool(ConfigOption::AUTO_UPDATE, false)) {
|
||||
#ifdef ENABLE_INOTIFY
|
||||
if (instance.storage != nullptr &&
|
||||
instance.update != nullptr)
|
||||
mpd_inotify_init(instance.event_loop,
|
||||
*instance.storage,
|
||||
*instance.update,
|
||||
raw_config.GetUnsigned(ConfigOption::AUTO_UPDATE_DEPTH,
|
||||
INT_MAX));
|
||||
instance.update != nullptr) {
|
||||
try {
|
||||
instance.inotify_update =
|
||||
mpd_inotify_init(instance.event_loop,
|
||||
*instance.storage,
|
||||
*instance.update,
|
||||
raw_config.GetUnsigned(ConfigOption::AUTO_UPDATE_DEPTH,
|
||||
INT_MAX));
|
||||
} catch (...) {
|
||||
LogError(std::current_exception());
|
||||
}
|
||||
}
|
||||
#else
|
||||
FormatWarning(config_domain,
|
||||
"inotify: auto_update was disabled. enable during compilation phase");
|
||||
LogWarning(config_domain,
|
||||
"inotify: auto_update was disabled. enable during compilation phase");
|
||||
#endif
|
||||
}
|
||||
#endif
|
||||
@@ -537,9 +574,6 @@ MainConfigured(const struct options &options, const ConfigData &raw_config)
|
||||
instance.state_file->Write();
|
||||
|
||||
instance.BeginShutdownUpdate();
|
||||
|
||||
ZeroconfDeinit();
|
||||
|
||||
instance.BeginShutdownPartitions();
|
||||
}
|
||||
|
||||
@@ -594,6 +628,15 @@ Java_org_musicpd_Bridge_shutdown(JNIEnv *, jclass)
|
||||
global_instance->Break();
|
||||
}
|
||||
|
||||
gcc_visibility_default
|
||||
JNIEXPORT void JNICALL
|
||||
Java_org_musicpd_Bridge_pause(JNIEnv *, jclass)
|
||||
{
|
||||
if (global_instance != nullptr)
|
||||
for (auto &partition : global_instance->partitions)
|
||||
partition.pc.LockSetPause(true);
|
||||
}
|
||||
|
||||
#else
|
||||
|
||||
static inline void
|
||||
@@ -607,27 +650,26 @@ MainOrThrow(int argc, char *argv[])
|
||||
MainConfigured(options, raw_config);
|
||||
}
|
||||
|
||||
int mpd_main(int argc, char *argv[]) noexcept
|
||||
int
|
||||
mpd_main(int argc, char *argv[])
|
||||
{
|
||||
AtScopeExit() { log_deinit(); };
|
||||
|
||||
try {
|
||||
MainOrThrow(argc, argv);
|
||||
return EXIT_SUCCESS;
|
||||
} catch (...) {
|
||||
LogError(std::current_exception());
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
MainOrThrow(argc, argv);
|
||||
return EXIT_SUCCESS;
|
||||
}
|
||||
|
||||
int
|
||||
main(int argc, char *argv[]) noexcept
|
||||
{
|
||||
try {
|
||||
AtScopeExit() { log_deinit(); };
|
||||
|
||||
#ifdef _WIN32
|
||||
return win32_main(argc, argv);
|
||||
#else
|
||||
return mpd_main(argc, argv);
|
||||
#endif
|
||||
} catch (...) {
|
||||
LogError(std::current_exception());
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
|
||||
#endif
|
||||
|
@@ -40,7 +40,7 @@ extern Instance *global_instance;
|
||||
* after doing some initialization.
|
||||
*/
|
||||
int
|
||||
mpd_main(int argc, char *argv[]) noexcept;
|
||||
mpd_main(int argc, char *argv[]);
|
||||
|
||||
#endif
|
||||
|
||||
|
@@ -24,7 +24,6 @@
|
||||
#ifndef MPD_MAPPER_HXX
|
||||
#define MPD_MAPPER_HXX
|
||||
|
||||
#include "util/Compiler.h"
|
||||
#include "config.h"
|
||||
|
||||
#include <string>
|
||||
@@ -44,7 +43,7 @@ mapper_init(AllocatedPath &&playlist_dir);
|
||||
* is basically done by converting the URI to the file system charset
|
||||
* and prepending the music directory.
|
||||
*/
|
||||
gcc_pure
|
||||
[[gnu::pure]]
|
||||
AllocatedPath
|
||||
map_uri_fs(const char *uri) noexcept;
|
||||
|
||||
@@ -56,7 +55,7 @@ map_uri_fs(const char *uri) noexcept;
|
||||
* @return the relative path in UTF-8, or an empty string if mapping
|
||||
* failed
|
||||
*/
|
||||
gcc_pure
|
||||
[[gnu::pure]]
|
||||
std::string
|
||||
map_fs_to_utf8(Path path_fs) noexcept;
|
||||
|
||||
@@ -65,7 +64,7 @@ map_fs_to_utf8(Path path_fs) noexcept;
|
||||
/**
|
||||
* Returns the playlist directory.
|
||||
*/
|
||||
gcc_const
|
||||
[[gnu::const]]
|
||||
const AllocatedPath &
|
||||
map_spl_path() noexcept;
|
||||
|
||||
@@ -75,7 +74,7 @@ map_spl_path() noexcept;
|
||||
*
|
||||
* @return the path in file system encoding, or nullptr if mapping failed
|
||||
*/
|
||||
gcc_pure
|
||||
[[gnu::pure]]
|
||||
AllocatedPath
|
||||
map_spl_utf8_to_fs(const char *name) noexcept;
|
||||
|
||||
|
@@ -20,8 +20,6 @@
|
||||
#ifndef MPD_MIX_RAMP_INFO_HXX
|
||||
#define MPD_MIX_RAMP_INFO_HXX
|
||||
|
||||
#include "util/Compiler.h"
|
||||
|
||||
#include <string>
|
||||
|
||||
class MixRampInfo {
|
||||
@@ -35,17 +33,17 @@ public:
|
||||
end.clear();
|
||||
}
|
||||
|
||||
gcc_pure
|
||||
[[gnu::pure]]
|
||||
bool IsDefined() const noexcept {
|
||||
return !start.empty() || !end.empty();
|
||||
}
|
||||
|
||||
gcc_pure
|
||||
[[gnu::pure]]
|
||||
const char *GetStart() const noexcept {
|
||||
return start.empty() ? nullptr : start.c_str();
|
||||
}
|
||||
|
||||
gcc_pure
|
||||
[[gnu::pure]]
|
||||
const char *GetEnd() const noexcept {
|
||||
return end.empty() ? nullptr : end.c_str();
|
||||
}
|
||||
|
@@ -63,7 +63,7 @@ public:
|
||||
* is the same value which was passed to the constructor
|
||||
* music_buffer_new().
|
||||
*/
|
||||
gcc_pure
|
||||
[[gnu::pure]]
|
||||
unsigned GetSize() const noexcept {
|
||||
return buffer.GetCapacity();
|
||||
}
|
||||
|
@@ -105,7 +105,7 @@ struct MusicChunkInfo {
|
||||
* Checks if the audio format if the chunk is equal to the
|
||||
* specified audio_format.
|
||||
*/
|
||||
gcc_pure
|
||||
[[gnu::pure]]
|
||||
bool CheckFormat(AudioFormat audio_format) const noexcept;
|
||||
#endif
|
||||
};
|
||||
|
@@ -22,7 +22,6 @@
|
||||
|
||||
#include "MusicChunkPtr.hxx"
|
||||
#include "thread/Mutex.hxx"
|
||||
#include "util/Compiler.h"
|
||||
|
||||
#ifndef NDEBUG
|
||||
#include "pcm/AudioFormat.hxx"
|
||||
@@ -59,7 +58,7 @@ public:
|
||||
* Checks if the audio format if the chunk is equal to the specified
|
||||
* audio_format.
|
||||
*/
|
||||
gcc_pure
|
||||
[[gnu::pure]]
|
||||
bool CheckFormat(AudioFormat other) const noexcept {
|
||||
return !audio_format.IsDefined() ||
|
||||
audio_format == other;
|
||||
@@ -68,7 +67,7 @@ public:
|
||||
/**
|
||||
* Checks if the specified chunk is enqueued in the music pipe.
|
||||
*/
|
||||
gcc_pure
|
||||
[[gnu::pure]]
|
||||
bool Contains(const MusicChunk *chunk) const noexcept;
|
||||
#endif
|
||||
|
||||
@@ -76,7 +75,7 @@ public:
|
||||
* Returns the first #MusicChunk from the pipe. Returns
|
||||
* nullptr if the pipe is empty.
|
||||
*/
|
||||
gcc_pure
|
||||
[[gnu::pure]]
|
||||
const MusicChunk *Peek() const noexcept {
|
||||
const std::lock_guard<Mutex> protect(mutex);
|
||||
return head.get();
|
||||
@@ -100,13 +99,13 @@ public:
|
||||
/**
|
||||
* Returns the number of chunks currently in this pipe.
|
||||
*/
|
||||
gcc_pure
|
||||
[[gnu::pure]]
|
||||
unsigned GetSize() const noexcept {
|
||||
const std::lock_guard<Mutex> protect(mutex);
|
||||
return size;
|
||||
}
|
||||
|
||||
gcc_pure
|
||||
[[gnu::pure]]
|
||||
bool IsEmpty() const noexcept {
|
||||
return GetSize() == 0;
|
||||
}
|
||||
|
@@ -21,6 +21,7 @@
|
||||
#include "Partition.hxx"
|
||||
#include "Instance.hxx"
|
||||
#include "Log.hxx"
|
||||
#include "lib/fmt/ExceptionFormatter.hxx"
|
||||
#include "song/DetachedSong.hxx"
|
||||
#include "mixer/Volume.hxx"
|
||||
#include "IdleFlags.hxx"
|
||||
@@ -67,13 +68,14 @@ PrefetchSong(InputCacheManager &cache, const char *uri) noexcept
|
||||
if (cache.Contains(uri))
|
||||
return;
|
||||
|
||||
FormatDebug(cache_domain, "Prefetch '%s'", uri);
|
||||
FmtDebug(cache_domain, "Prefetch '{}'", uri);
|
||||
|
||||
try {
|
||||
cache.Prefetch(uri);
|
||||
} catch (...) {
|
||||
FormatError(std::current_exception(),
|
||||
"Prefetch '%s' failed", uri);
|
||||
FmtError(cache_domain,
|
||||
"Prefetch '{}' failed: {}",
|
||||
uri, std::current_exception());
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -27,6 +27,7 @@
|
||||
#include "mixer/Listener.hxx"
|
||||
#include "player/Control.hxx"
|
||||
#include "player/Listener.hxx"
|
||||
#include "protocol/RangeArg.hxx"
|
||||
#include "ReplayGainMode.hxx"
|
||||
#include "SingleMode.hxx"
|
||||
#include "Chrono.hxx"
|
||||
@@ -38,6 +39,7 @@
|
||||
#include <memory>
|
||||
|
||||
struct Instance;
|
||||
struct RangeArg;
|
||||
class MultipleOutputs;
|
||||
class SongLoader;
|
||||
class ClientListener;
|
||||
@@ -133,24 +135,20 @@ struct Partition final : QueueListener, PlayerListener, MixerListener {
|
||||
* @param start the position of the first song to delete
|
||||
* @param end the position after the last song to delete
|
||||
*/
|
||||
void DeleteRange(unsigned start, unsigned end) {
|
||||
playlist.DeleteRange(pc, start, end);
|
||||
void DeleteRange(RangeArg range) {
|
||||
playlist.DeleteRange(pc, range);
|
||||
}
|
||||
|
||||
void StaleSong(const char *uri) noexcept {
|
||||
playlist.StaleSong(pc, uri);
|
||||
}
|
||||
|
||||
void Shuffle(unsigned start, unsigned end) noexcept {
|
||||
playlist.Shuffle(pc, start, end);
|
||||
void Shuffle(RangeArg range) {
|
||||
playlist.Shuffle(pc, range);
|
||||
}
|
||||
|
||||
void MoveRange(unsigned start, unsigned end, int to) {
|
||||
playlist.MoveRange(pc, start, end, to);
|
||||
}
|
||||
|
||||
void MoveId(unsigned id, int to) {
|
||||
playlist.MoveId(pc, id, to);
|
||||
void MoveRange(RangeArg range, unsigned to) {
|
||||
playlist.MoveRange(pc, range, to);
|
||||
}
|
||||
|
||||
void SwapPositions(unsigned song1, unsigned song2) {
|
||||
@@ -161,10 +159,8 @@ struct Partition final : QueueListener, PlayerListener, MixerListener {
|
||||
playlist.SwapIds(pc, id1, id2);
|
||||
}
|
||||
|
||||
void SetPriorityRange(unsigned start_position, unsigned end_position,
|
||||
uint8_t priority) {
|
||||
playlist.SetPriorityRange(pc, start_position, end_position,
|
||||
priority);
|
||||
void SetPriorityRange(RangeArg position_range, uint8_t priority) {
|
||||
playlist.SetPriorityRange(pc, position_range, priority);
|
||||
}
|
||||
|
||||
void SetPriorityId(unsigned song_id, uint8_t priority) {
|
||||
|
@@ -22,6 +22,9 @@
|
||||
#include "config/Param.hxx"
|
||||
#include "config/Data.hxx"
|
||||
#include "config/Option.hxx"
|
||||
#include "net/AddressInfo.hxx"
|
||||
#include "net/Resolver.hxx"
|
||||
#include "net/ToString.hxx"
|
||||
#include "util/IterableSplitString.hxx"
|
||||
#include "util/RuntimeError.hxx"
|
||||
#include "util/StringView.hxx"
|
||||
@@ -41,6 +44,7 @@ static constexpr struct {
|
||||
} permission_names[] = {
|
||||
{ "read", PERMISSION_READ },
|
||||
{ "add", PERMISSION_ADD },
|
||||
{ "player", PERMISSION_PLAYER },
|
||||
{ "control", PERMISSION_CONTROL },
|
||||
{ "admin", PERMISSION_ADMIN },
|
||||
{ nullptr, 0 },
|
||||
@@ -54,6 +58,10 @@ static unsigned permission_default;
|
||||
static unsigned local_permissions;
|
||||
#endif
|
||||
|
||||
#ifdef HAVE_TCP
|
||||
static std::map<std::string, unsigned> host_passwords;
|
||||
#endif
|
||||
|
||||
static unsigned
|
||||
ParsePermission(StringView s)
|
||||
{
|
||||
@@ -65,16 +73,20 @@ ParsePermission(StringView s)
|
||||
int(s.size), s.data);
|
||||
}
|
||||
|
||||
static unsigned parsePermissions(const char *string)
|
||||
static unsigned
|
||||
parsePermissions(std::string_view string)
|
||||
{
|
||||
assert(string != nullptr);
|
||||
|
||||
unsigned permission = 0;
|
||||
|
||||
for (const auto i : IterableSplitString(string, PERMISSION_SEPARATOR))
|
||||
if (!i.empty())
|
||||
permission |= ParsePermission(i);
|
||||
|
||||
/* for backwards compatiblity with MPD 0.22 and older,
|
||||
"control" implies "play" */
|
||||
if (permission & PERMISSION_CONTROL)
|
||||
permission |= PERMISSION_PLAYER;
|
||||
|
||||
return permission;
|
||||
}
|
||||
|
||||
@@ -82,6 +94,7 @@ void
|
||||
initPermissions(const ConfigData &config)
|
||||
{
|
||||
permission_default = PERMISSION_READ | PERMISSION_ADD |
|
||||
PERMISSION_PLAYER |
|
||||
PERMISSION_CONTROL | PERMISSION_ADMIN;
|
||||
|
||||
for (const auto ¶m : config.GetParamList(ConfigOption::PASSWORD)) {
|
||||
@@ -98,8 +111,7 @@ initPermissions(const ConfigData &config)
|
||||
std::string password(value, separator);
|
||||
|
||||
unsigned permission = parsePermissions(separator + 1);
|
||||
permission_passwords.insert(std::make_pair(std::move(password),
|
||||
permission));
|
||||
permission_passwords.emplace(std::move(password), permission);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -115,17 +127,48 @@ initPermissions(const ConfigData &config)
|
||||
: permission_default;
|
||||
});
|
||||
#endif
|
||||
|
||||
#ifdef HAVE_TCP
|
||||
for (const auto ¶m : config.GetParamList(ConfigOption::HOST_PERMISSIONS)) {
|
||||
permission_default = 0;
|
||||
|
||||
param.With([](StringView value){
|
||||
auto [host_sv, permissions_s] = value.Split(' ');
|
||||
unsigned permissions = parsePermissions(permissions_s);
|
||||
|
||||
const std::string host_s{host_sv};
|
||||
|
||||
for (const auto &i : Resolve(host_s.c_str(), 0,
|
||||
AI_PASSIVE, SOCK_STREAM))
|
||||
host_passwords.emplace(HostToString(i),
|
||||
permissions);
|
||||
});
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
#ifdef HAVE_TCP
|
||||
|
||||
int
|
||||
getPermissionFromPassword(const char *password, unsigned *permission) noexcept
|
||||
GetPermissionsFromAddress(SocketAddress address) noexcept
|
||||
{
|
||||
if (auto i = host_passwords.find(HostToString(address));
|
||||
i != host_passwords.end())
|
||||
return i->second;
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
std::optional<unsigned>
|
||||
GetPermissionFromPassword(const char *password) noexcept
|
||||
{
|
||||
auto i = permission_passwords.find(password);
|
||||
if (i == permission_passwords.end())
|
||||
return -1;
|
||||
return std::nullopt;
|
||||
|
||||
*permission = i->second;
|
||||
return 0;
|
||||
return i->second;
|
||||
}
|
||||
|
||||
unsigned
|
||||
|
@@ -22,25 +22,42 @@
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#include <optional>
|
||||
|
||||
struct ConfigData;
|
||||
class SocketAddress;
|
||||
|
||||
static constexpr unsigned PERMISSION_NONE = 0;
|
||||
static constexpr unsigned PERMISSION_READ = 1;
|
||||
static constexpr unsigned PERMISSION_ADD = 2;
|
||||
static constexpr unsigned PERMISSION_CONTROL = 4;
|
||||
static constexpr unsigned PERMISSION_ADMIN = 8;
|
||||
static constexpr unsigned PERMISSION_PLAYER = 16;
|
||||
|
||||
int
|
||||
getPermissionFromPassword(const char *password, unsigned *permission) noexcept;
|
||||
/**
|
||||
* @return the permissions for the given password or std::nullopt if
|
||||
* the password is not accepted
|
||||
*/
|
||||
[[gnu::pure]]
|
||||
std::optional<unsigned>
|
||||
GetPermissionFromPassword(const char *password) noexcept;
|
||||
|
||||
[[gnu::const]]
|
||||
unsigned
|
||||
getDefaultPermissions() noexcept;
|
||||
|
||||
#ifdef HAVE_UN
|
||||
[[gnu::const]]
|
||||
unsigned
|
||||
GetLocalPermissions() noexcept;
|
||||
#endif
|
||||
|
||||
#ifdef HAVE_TCP
|
||||
[[gnu::pure]]
|
||||
int
|
||||
GetPermissionsFromAddress(SocketAddress address) noexcept;
|
||||
#endif
|
||||
|
||||
void
|
||||
initPermissions(const ConfigData &config);
|
||||
|
||||
|
@@ -22,6 +22,7 @@
|
||||
#include "PlaylistError.hxx"
|
||||
#include "queue/Playlist.hxx"
|
||||
#include "queue/QueuePrint.hxx"
|
||||
#include "protocol/RangeArg.hxx"
|
||||
|
||||
#define SONG_FILE "file: "
|
||||
#define SONG_TIME "Time: "
|
||||
@@ -35,20 +36,17 @@ playlist_print_uris(Response &r, const playlist &playlist)
|
||||
}
|
||||
|
||||
void
|
||||
playlist_print_info(Response &r, const playlist &playlist,
|
||||
unsigned start, unsigned end)
|
||||
playlist_print_info(Response &r, const playlist &playlist, RangeArg range)
|
||||
{
|
||||
const Queue &queue = playlist.queue;
|
||||
|
||||
if (end > queue.GetLength())
|
||||
/* correct the "end" offset */
|
||||
end = queue.GetLength();
|
||||
|
||||
if (start > end)
|
||||
/* an invalid "start" offset is fatal */
|
||||
if (!range.CheckClip(queue.GetLength()))
|
||||
throw PlaylistError::BadRange();
|
||||
|
||||
queue_print_info(r, queue, start, end);
|
||||
if (range.IsEmpty())
|
||||
return;
|
||||
|
||||
queue_print_info(r, queue, range.start, range.end);
|
||||
}
|
||||
|
||||
void
|
||||
@@ -62,7 +60,7 @@ playlist_print_id(Response &r, const playlist &playlist,
|
||||
/* no such song */
|
||||
throw PlaylistError::NoSuchSong();
|
||||
|
||||
playlist_print_info(r, playlist, position, position + 1);
|
||||
playlist_print_info(r, playlist, {unsigned(position), position + 1U});
|
||||
}
|
||||
|
||||
bool
|
||||
@@ -87,18 +85,24 @@ playlist_print_find(Response &r, const playlist &playlist,
|
||||
void
|
||||
playlist_print_changes_info(Response &r, const playlist &playlist,
|
||||
uint32_t version,
|
||||
unsigned start, unsigned end)
|
||||
RangeArg range)
|
||||
{
|
||||
queue_print_changes_info(r, playlist.queue, version,
|
||||
start, end);
|
||||
const Queue &queue = playlist.queue;
|
||||
range.ClipRelaxed(queue.GetLength());
|
||||
|
||||
queue_print_changes_info(r, queue, version,
|
||||
range.start, range.end);
|
||||
}
|
||||
|
||||
void
|
||||
playlist_print_changes_position(Response &r,
|
||||
const playlist &playlist,
|
||||
uint32_t version,
|
||||
unsigned start, unsigned end)
|
||||
RangeArg range)
|
||||
{
|
||||
const Queue &queue = playlist.queue;
|
||||
range.ClipRelaxed(queue.GetLength());
|
||||
|
||||
queue_print_changes_position(r, playlist.queue, version,
|
||||
start, end);
|
||||
range.start, range.end);
|
||||
}
|
||||
|
@@ -23,6 +23,7 @@
|
||||
#include <cstdint>
|
||||
|
||||
struct playlist;
|
||||
struct RangeArg;
|
||||
class SongFilter;
|
||||
class Response;
|
||||
|
||||
@@ -41,8 +42,7 @@ playlist_print_uris(Response &r, const playlist &playlist);
|
||||
* Throws #PlaylistError if the range is invalid.
|
||||
*/
|
||||
void
|
||||
playlist_print_info(Response &r, const playlist &playlist,
|
||||
unsigned start, unsigned end);
|
||||
playlist_print_info(Response &r, const playlist &playlist, RangeArg range);
|
||||
|
||||
/**
|
||||
* Sends the song with the specified id to the client.
|
||||
@@ -73,7 +73,7 @@ playlist_print_find(Response &r, const playlist &playlist,
|
||||
void
|
||||
playlist_print_changes_info(Response &r, const playlist &playlist,
|
||||
uint32_t version,
|
||||
unsigned start, unsigned end);
|
||||
RangeArg range);
|
||||
|
||||
/**
|
||||
* Print changes since the specified playlist version, position only.
|
||||
@@ -82,6 +82,6 @@ void
|
||||
playlist_print_changes_position(Response &r,
|
||||
const playlist &playlist,
|
||||
uint32_t version,
|
||||
unsigned start, unsigned end);
|
||||
RangeArg range);
|
||||
|
||||
#endif
|
||||
|
@@ -19,10 +19,14 @@
|
||||
|
||||
#include "RemoteTagCache.hxx"
|
||||
#include "RemoteTagCacheHandler.hxx"
|
||||
#include "lib/fmt/ExceptionFormatter.hxx"
|
||||
#include "input/ScanTags.hxx"
|
||||
#include "util/DeleteDisposer.hxx"
|
||||
#include "util/Domain.hxx"
|
||||
#include "Log.hxx"
|
||||
|
||||
static constexpr Domain remote_tag_cache_domain("remote_tag_cache");
|
||||
|
||||
RemoteTagCache::RemoteTagCache(EventLoop &event_loop,
|
||||
RemoteTagCacheHandler &_handler) noexcept
|
||||
:handler(_handler),
|
||||
@@ -60,9 +64,9 @@ RemoteTagCache::Lookup(const std::string &uri) noexcept
|
||||
|
||||
item->scanner->Start();
|
||||
} catch (...) {
|
||||
FormatError(std::current_exception(),
|
||||
"Failed to scan tags of '%s'",
|
||||
uri.c_str());
|
||||
FmtError(remote_tag_cache_domain,
|
||||
"Failed to scan tags of '{}': {}",
|
||||
uri, std::current_exception());
|
||||
|
||||
item->scanner.reset();
|
||||
|
||||
@@ -128,7 +132,8 @@ RemoteTagCache::Item::OnRemoteTag(Tag &&_tag) noexcept
|
||||
void
|
||||
RemoteTagCache::Item::OnRemoteTagError(std::exception_ptr e) noexcept
|
||||
{
|
||||
FormatError(e, "Failed to scan tags of '%s'", uri.c_str());
|
||||
FmtError(remote_tag_cache_domain,
|
||||
"Failed to scan tags of '{}': {}", uri, e);
|
||||
|
||||
scanner.reset();
|
||||
|
||||
|
@@ -22,7 +22,7 @@
|
||||
|
||||
#include "input/RemoteTagScanner.hxx"
|
||||
#include "tag/Tag.hxx"
|
||||
#include "event/DeferEvent.hxx"
|
||||
#include "event/InjectEvent.hxx"
|
||||
#include "thread/Mutex.hxx"
|
||||
|
||||
#include <boost/intrusive/list.hpp>
|
||||
@@ -40,7 +40,7 @@ class RemoteTagCache final {
|
||||
|
||||
RemoteTagCacheHandler &handler;
|
||||
|
||||
DeferEvent defer_invoke_handler;
|
||||
InjectEvent defer_invoke_handler;
|
||||
|
||||
Mutex mutex;
|
||||
|
||||
@@ -68,20 +68,20 @@ class RemoteTagCache final {
|
||||
struct Hash : std::hash<std::string> {
|
||||
using std::hash<std::string>::operator();
|
||||
|
||||
gcc_pure
|
||||
[[gnu::pure]]
|
||||
std::size_t operator()(const Item &item) const noexcept {
|
||||
return std::hash<std::string>::operator()(item.uri);
|
||||
}
|
||||
};
|
||||
|
||||
struct Equal {
|
||||
gcc_pure
|
||||
[[gnu::pure]]
|
||||
bool operator()(const Item &a,
|
||||
const Item &b) const noexcept {
|
||||
return a.uri == b.uri;
|
||||
}
|
||||
|
||||
gcc_pure
|
||||
[[gnu::pure]]
|
||||
bool operator()(const std::string &a,
|
||||
const Item &b) const noexcept {
|
||||
return a == b.uri;
|
||||
|
@@ -20,7 +20,6 @@
|
||||
#ifndef MPD_REPLAY_GAIN_INFO_HXX
|
||||
#define MPD_REPLAY_GAIN_INFO_HXX
|
||||
|
||||
#include "util/Compiler.h"
|
||||
#include "ReplayGainMode.hxx"
|
||||
|
||||
struct ReplayGainConfig;
|
||||
@@ -42,7 +41,7 @@ struct ReplayGainTuple {
|
||||
return {-200.0f, 0.0f};
|
||||
}
|
||||
|
||||
gcc_pure
|
||||
[[gnu::pure]]
|
||||
float CalculateScale(const ReplayGainConfig &config) const noexcept;
|
||||
};
|
||||
|
||||
|
@@ -18,6 +18,7 @@
|
||||
*/
|
||||
|
||||
#include "ReplayGainMode.hxx"
|
||||
#include "util/Compiler.h"
|
||||
|
||||
#include <cassert>
|
||||
#include <stdexcept>
|
||||
|
@@ -20,8 +20,6 @@
|
||||
#ifndef MPD_REPLAY_GAIN_MODE_HXX
|
||||
#define MPD_REPLAY_GAIN_MODE_HXX
|
||||
|
||||
#include "util/Compiler.h"
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
enum class ReplayGainMode : uint8_t {
|
||||
@@ -34,7 +32,7 @@ enum class ReplayGainMode : uint8_t {
|
||||
/**
|
||||
* Return the string representation of a #ReplayGainMode.
|
||||
*/
|
||||
gcc_pure
|
||||
[[gnu::pure]]
|
||||
const char *
|
||||
ToString(ReplayGainMode mode) noexcept;
|
||||
|
||||
|
@@ -18,6 +18,7 @@
|
||||
*/
|
||||
|
||||
#include "SingleMode.hxx"
|
||||
#include "util/Compiler.h"
|
||||
|
||||
#include <cassert>
|
||||
#include <stdexcept>
|
||||
|
@@ -20,8 +20,6 @@
|
||||
#ifndef MPD_SINGLE_MODE_HXX
|
||||
#define MPD_SINGLE_MODE_HXX
|
||||
|
||||
#include "util/Compiler.h"
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
enum class SingleMode : uint8_t {
|
||||
@@ -33,7 +31,7 @@ enum class SingleMode : uint8_t {
|
||||
/**
|
||||
* Return the string representation of a #SingleMode.
|
||||
*/
|
||||
gcc_pure
|
||||
[[gnu::pure]]
|
||||
const char *
|
||||
SingleToString(SingleMode mode) noexcept;
|
||||
|
||||
|
@@ -20,7 +20,6 @@
|
||||
#ifndef MPD_SONG_LOADER_HXX
|
||||
#define MPD_SONG_LOADER_HXX
|
||||
|
||||
#include "util/Compiler.h"
|
||||
#include "config.h"
|
||||
|
||||
#include <cstddef>
|
||||
@@ -72,14 +71,14 @@ public:
|
||||
/**
|
||||
* Throws #std::runtime_error on error.
|
||||
*/
|
||||
gcc_nonnull_all
|
||||
[[gnu::nonnull]]
|
||||
DetachedSong LoadSong(const char *uri_utf8) const;
|
||||
|
||||
private:
|
||||
gcc_nonnull_all
|
||||
[[gnu::nonnull]]
|
||||
DetachedSong LoadFromDatabase(const char *uri) const;
|
||||
|
||||
gcc_nonnull_all
|
||||
[[gnu::nonnull]]
|
||||
DetachedSong LoadFile(const char *path_utf8, Path path_fs) const;
|
||||
};
|
||||
|
||||
|
@@ -25,8 +25,11 @@
|
||||
#include "client/Response.hxx"
|
||||
#include "fs/Traits.hxx"
|
||||
#include "time/ChronoUtil.hxx"
|
||||
#include "util/StringBuffer.hxx"
|
||||
#include "util/UriUtil.hxx"
|
||||
|
||||
#include <fmt/format.h>
|
||||
|
||||
#define SONG_FILE "file: "
|
||||
|
||||
static void
|
||||
@@ -42,14 +45,15 @@ song_print_uri(Response &r, const char *uri, bool base) noexcept
|
||||
uri = allocated.c_str();
|
||||
}
|
||||
|
||||
r.Format(SONG_FILE "%s\n", uri);
|
||||
r.Fmt(FMT_STRING(SONG_FILE "{}\n"), uri);
|
||||
}
|
||||
|
||||
void
|
||||
song_print_uri(Response &r, const LightSong &song, bool base) noexcept
|
||||
{
|
||||
if (!base && song.directory != nullptr)
|
||||
r.Format(SONG_FILE "%s/%s\n", song.directory, song.uri);
|
||||
r.Fmt(FMT_STRING(SONG_FILE "{}/{}\n"),
|
||||
song.directory, song.uri);
|
||||
else
|
||||
song_print_uri(r, song.uri, base);
|
||||
}
|
||||
@@ -67,15 +71,15 @@ PrintRange(Response &r, SongTime start_time, SongTime end_time) noexcept
|
||||
const unsigned end_ms = end_time.ToMS();
|
||||
|
||||
if (end_ms > 0)
|
||||
r.Format("Range: %u.%03u-%u.%03u\n",
|
||||
start_ms / 1000,
|
||||
start_ms % 1000,
|
||||
end_ms / 1000,
|
||||
end_ms % 1000);
|
||||
r.Fmt(FMT_STRING("Range: {}.{:03}-{}.{:03}\n"),
|
||||
start_ms / 1000,
|
||||
start_ms % 1000,
|
||||
end_ms / 1000,
|
||||
end_ms % 1000);
|
||||
else if (start_ms > 0)
|
||||
r.Format("Range: %u.%03u-\n",
|
||||
start_ms / 1000,
|
||||
start_ms % 1000);
|
||||
r.Fmt(FMT_STRING("Range: {}.{:03}-\n"),
|
||||
start_ms / 1000,
|
||||
start_ms % 1000);
|
||||
}
|
||||
|
||||
void
|
||||
@@ -89,16 +93,16 @@ song_print_info(Response &r, const LightSong &song, bool base) noexcept
|
||||
time_print(r, "Last-Modified", song.mtime);
|
||||
|
||||
if (song.audio_format.IsDefined())
|
||||
r.Format("Format: %s\n", ToString(song.audio_format).c_str());
|
||||
r.Fmt(FMT_STRING("Format: {}\n"), ToString(song.audio_format));
|
||||
|
||||
tag_print_values(r, song.tag);
|
||||
|
||||
const auto duration = song.GetDuration();
|
||||
if (!duration.IsNegative())
|
||||
r.Format("Time: %i\n"
|
||||
"duration: %1.3f\n",
|
||||
duration.RoundS(),
|
||||
duration.ToDoubleS());
|
||||
r.Fmt(FMT_STRING("Time: {}\n"
|
||||
"duration: {:1.3f}\n"),
|
||||
duration.RoundS(),
|
||||
duration.ToDoubleS());
|
||||
}
|
||||
|
||||
void
|
||||
@@ -111,12 +115,15 @@ song_print_info(Response &r, const DetachedSong &song, bool base) noexcept
|
||||
if (!IsNegative(song.GetLastModified()))
|
||||
time_print(r, "Last-Modified", song.GetLastModified());
|
||||
|
||||
if (const auto &f = song.GetAudioFormat(); f.IsDefined())
|
||||
r.Fmt(FMT_STRING("Format: {}\n"), ToString(f));
|
||||
|
||||
tag_print_values(r, song.GetTag());
|
||||
|
||||
const auto duration = song.GetDuration();
|
||||
if (!duration.IsNegative())
|
||||
r.Format("Time: %i\n"
|
||||
"duration: %1.3f\n",
|
||||
duration.RoundS(),
|
||||
duration.ToDoubleS());
|
||||
r.Fmt(FMT_STRING("Time: {}\n"
|
||||
"duration: {:1.3f}\n"),
|
||||
duration.RoundS(),
|
||||
duration.ToDoubleS());
|
||||
}
|
||||
|
@@ -86,8 +86,7 @@ song_save(BufferedOutputStream &os, const DetachedSong &song)
|
||||
|
||||
DetachedSong
|
||||
song_load(TextFile &file, const char *uri,
|
||||
std::string *target_r,
|
||||
AudioFormat *audio_format_r)
|
||||
std::string *target_r)
|
||||
{
|
||||
DetachedSong song(uri);
|
||||
|
||||
@@ -113,13 +112,11 @@ song_load(TextFile &file, const char *uri,
|
||||
if (target_r != nullptr)
|
||||
*target_r = value;
|
||||
} else if (StringIsEqual(line, "Format")) {
|
||||
if (audio_format_r != nullptr) {
|
||||
try {
|
||||
*audio_format_r =
|
||||
ParseAudioFormat(value, false);
|
||||
} catch (...) {
|
||||
/* ignore parser errors */
|
||||
}
|
||||
try {
|
||||
song.SetAudioFormat(ParseAudioFormat(value,
|
||||
false));
|
||||
} catch (...) {
|
||||
/* ignore parser errors */
|
||||
}
|
||||
} else if (StringIsEqual(line, "Playlist")) {
|
||||
tag.SetHasPlaylist(StringIsEqual(value, "yes"));
|
||||
|
@@ -44,7 +44,6 @@ song_save(BufferedOutputStream &os, const DetachedSong &song);
|
||||
*/
|
||||
DetachedSong
|
||||
song_load(TextFile &file, const char *uri,
|
||||
std::string *target_r=nullptr,
|
||||
AudioFormat *audio_format_r=nullptr);
|
||||
std::string *target_r=nullptr);
|
||||
|
||||
#endif
|
||||
|
@@ -153,9 +153,10 @@ DetachedSong::LoadFile(Path path)
|
||||
return false;
|
||||
|
||||
TagBuilder tag_builder;
|
||||
auto new_audio_format = AudioFormat::Undefined();
|
||||
|
||||
try {
|
||||
if (!ScanFileTagsWithGeneric(path, tag_builder))
|
||||
if (!ScanFileTagsWithGeneric(path, tag_builder, &new_audio_format))
|
||||
return false;
|
||||
} catch (...) {
|
||||
// TODO: log or propagate I/O errors?
|
||||
@@ -163,6 +164,7 @@ DetachedSong::LoadFile(Path path)
|
||||
}
|
||||
|
||||
mtime = fi.GetModificationTime();
|
||||
audio_format = new_audio_format;
|
||||
tag_builder.Commit(tag);
|
||||
return true;
|
||||
}
|
||||
@@ -177,9 +179,11 @@ DetachedSong::Update()
|
||||
return LoadFile(path_fs);
|
||||
} else if (IsRemote()) {
|
||||
TagBuilder tag_builder;
|
||||
auto new_audio_format = AudioFormat::Undefined();
|
||||
|
||||
try {
|
||||
if (!tag_stream_scan(uri.c_str(), tag_builder))
|
||||
if (!tag_stream_scan(uri.c_str(), tag_builder,
|
||||
&new_audio_format))
|
||||
return false;
|
||||
} catch (...) {
|
||||
// TODO: log or propagate I/O errors?
|
||||
@@ -187,6 +191,7 @@ DetachedSong::Update()
|
||||
}
|
||||
|
||||
mtime = std::chrono::system_clock::time_point::min();
|
||||
audio_format = new_audio_format;
|
||||
tag_builder.Commit(tag);
|
||||
return true;
|
||||
} else
|
||||
|
@@ -93,8 +93,8 @@ StateFile::Write(OutputStream &os)
|
||||
void
|
||||
StateFile::Write()
|
||||
{
|
||||
FormatDebug(state_file_domain,
|
||||
"Saving state file %s", path_utf8.c_str());
|
||||
FmtDebug(state_file_domain,
|
||||
"Saving state file {}", path_utf8);
|
||||
|
||||
try {
|
||||
FileOutputStream fos(config.path);
|
||||
@@ -112,7 +112,7 @@ StateFile::Read()
|
||||
try {
|
||||
bool success;
|
||||
|
||||
FormatDebug(state_file_domain, "Loading state file %s", path_utf8.c_str());
|
||||
FmtDebug(state_file_domain, "Loading state file {}", path_utf8);
|
||||
|
||||
TextFile file(config.path);
|
||||
|
||||
@@ -135,9 +135,9 @@ try {
|
||||
#endif
|
||||
|
||||
if (!success)
|
||||
FormatError(state_file_domain,
|
||||
"Unrecognized line in state file: %s",
|
||||
line);
|
||||
FmtError(state_file_domain,
|
||||
"Unrecognized line in state file: {}",
|
||||
line);
|
||||
}
|
||||
|
||||
RememberVersions();
|
||||
@@ -148,7 +148,7 @@ try {
|
||||
void
|
||||
StateFile::CheckModified() noexcept
|
||||
{
|
||||
if (!timer_event.IsActive() && IsModified())
|
||||
if (!timer_event.IsPending() && IsModified())
|
||||
timer_event.Schedule(config.interval);
|
||||
}
|
||||
|
||||
|
@@ -21,8 +21,7 @@
|
||||
#define MPD_STATE_FILE_HXX
|
||||
|
||||
#include "StateFileConfig.hxx"
|
||||
#include "event/TimerEvent.hxx"
|
||||
#include "util/Compiler.h"
|
||||
#include "event/FarTimerEvent.hxx"
|
||||
#include "config.h"
|
||||
|
||||
#include <string>
|
||||
@@ -36,7 +35,7 @@ class StateFile final {
|
||||
|
||||
const std::string path_utf8;
|
||||
|
||||
TimerEvent timer_event;
|
||||
FarTimerEvent timer_event;
|
||||
|
||||
Partition &partition;
|
||||
|
||||
@@ -76,7 +75,7 @@ private:
|
||||
* Check if MPD's state was modified since the last
|
||||
* RememberVersions() call.
|
||||
*/
|
||||
gcc_pure
|
||||
[[gnu::pure]]
|
||||
bool IsModified() const noexcept;
|
||||
|
||||
/* callback for #timer_event */
|
||||
|
@@ -34,6 +34,8 @@
|
||||
#include "system/Clock.hxx"
|
||||
#endif
|
||||
|
||||
#include <fmt/format.h>
|
||||
|
||||
#include <chrono>
|
||||
|
||||
#ifndef _WIN32
|
||||
@@ -97,19 +99,19 @@ db_stats_print(Response &r, const Database &db)
|
||||
unsigned total_duration_s =
|
||||
std::chrono::duration_cast<std::chrono::seconds>(stats.total_duration).count();
|
||||
|
||||
r.Format("artists: %u\n"
|
||||
"albums: %u\n"
|
||||
"songs: %u\n"
|
||||
"db_playtime: %u\n",
|
||||
stats.artist_count,
|
||||
stats.album_count,
|
||||
stats.song_count,
|
||||
total_duration_s);
|
||||
r.Fmt(FMT_STRING("artists: {}\n"
|
||||
"albums: {}\n"
|
||||
"songs: {}\n"
|
||||
"db_playtime: {}\n"),
|
||||
stats.artist_count,
|
||||
stats.album_count,
|
||||
stats.song_count,
|
||||
total_duration_s);
|
||||
|
||||
const auto update_stamp = db.GetUpdateStamp();
|
||||
if (!IsNegative(update_stamp))
|
||||
r.Format("db_update: %lu\n",
|
||||
(unsigned long)std::chrono::system_clock::to_time_t(update_stamp));
|
||||
r.Fmt(FMT_STRING("db_update: {}\n"),
|
||||
std::chrono::system_clock::to_time_t(update_stamp));
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -123,10 +125,10 @@ stats_print(Response &r, const Partition &partition)
|
||||
const auto uptime = std::chrono::steady_clock::now() - start_time;
|
||||
#endif
|
||||
|
||||
r.Format("uptime: %u\n"
|
||||
"playtime: %lu\n",
|
||||
(unsigned)std::chrono::duration_cast<std::chrono::seconds>(uptime).count(),
|
||||
lround(partition.pc.GetTotalPlayTime().count()));
|
||||
r.Fmt(FMT_STRING("uptime: {}\n"
|
||||
"playtime: {}\n"),
|
||||
std::chrono::duration_cast<std::chrono::seconds>(uptime).count(),
|
||||
lround(partition.pc.GetTotalPlayTime().count()));
|
||||
|
||||
#ifdef ENABLE_DATABASE
|
||||
const Database *db = partition.instance.GetDatabase();
|
||||
|
@@ -25,6 +25,7 @@
|
||||
#include "client/Client.hxx"
|
||||
#include "protocol/Ack.hxx"
|
||||
#include "fs/AllocatedPath.hxx"
|
||||
#include "input/InputStream.hxx"
|
||||
#include "util/Compiler.h"
|
||||
#include "util/UriExtract.hxx"
|
||||
#include "LocateUri.hxx"
|
||||
@@ -32,8 +33,13 @@
|
||||
static void
|
||||
TagScanStream(const char *uri, TagHandler &handler)
|
||||
{
|
||||
if (!tag_stream_scan(uri, handler))
|
||||
Mutex mutex;
|
||||
|
||||
auto is = InputStream::OpenReady(uri, mutex);
|
||||
if (!tag_stream_scan(*is, handler))
|
||||
throw ProtocolError(ACK_ERROR_NO_EXIST, "Failed to load file");
|
||||
|
||||
ScanGenericTags(*is, handler);
|
||||
}
|
||||
|
||||
static void
|
||||
|
@@ -23,26 +23,27 @@
|
||||
#include "client/Response.hxx"
|
||||
#include "util/StringView.hxx"
|
||||
|
||||
#include <fmt/format.h>
|
||||
|
||||
void
|
||||
tag_print_types(Response &r) noexcept
|
||||
{
|
||||
const auto tag_mask = global_tag_mask & r.GetTagMask();
|
||||
for (unsigned i = 0; i < TAG_NUM_OF_ITEM_TYPES; i++)
|
||||
if (tag_mask.Test(TagType(i)))
|
||||
r.Format("tagtype: %s\n", tag_item_names[i]);
|
||||
r.Fmt(FMT_STRING("tagtype: {}\n"), tag_item_names[i]);
|
||||
}
|
||||
|
||||
void
|
||||
tag_print(Response &r, TagType type, StringView value) noexcept
|
||||
{
|
||||
r.Format("%s: %.*s\n", tag_item_names[type],
|
||||
int(value.size), value.data);
|
||||
r.Fmt(FMT_STRING("{}: {}\n"), tag_item_names[type], value);
|
||||
}
|
||||
|
||||
void
|
||||
tag_print(Response &r, TagType type, const char *value) noexcept
|
||||
{
|
||||
r.Format("%s: %s\n", tag_item_names[type], value);
|
||||
r.Fmt(FMT_STRING("{}: {}\n"), tag_item_names[type], value);
|
||||
}
|
||||
|
||||
void
|
||||
@@ -58,10 +59,10 @@ void
|
||||
tag_print(Response &r, const Tag &tag) noexcept
|
||||
{
|
||||
if (!tag.duration.IsNegative())
|
||||
r.Format("Time: %i\n"
|
||||
"duration: %1.3f\n",
|
||||
tag.duration.RoundS(),
|
||||
tag.duration.ToDoubleS());
|
||||
r.Fmt(FMT_STRING("Time: {}\n"
|
||||
"duration: {:1.3f}\n"),
|
||||
tag.duration.RoundS(),
|
||||
tag.duration.ToDoubleS());
|
||||
|
||||
tag_print_values(r, tag);
|
||||
}
|
||||
|
@@ -36,10 +36,10 @@
|
||||
gcc_pure
|
||||
static bool
|
||||
CheckDecoderPlugin(const DecoderPlugin &plugin,
|
||||
const char *suffix, const char *mime) noexcept
|
||||
std::string_view suffix, std::string_view mime) noexcept
|
||||
{
|
||||
return (mime != nullptr && plugin.SupportsMimeType(mime)) ||
|
||||
(suffix != nullptr && plugin.SupportsSuffix(suffix));
|
||||
return (!mime.empty() && plugin.SupportsMimeType(mime)) ||
|
||||
(!suffix.empty() && plugin.SupportsSuffix(suffix));
|
||||
}
|
||||
|
||||
bool
|
||||
@@ -47,25 +47,24 @@ tag_stream_scan(InputStream &is, TagHandler &handler)
|
||||
{
|
||||
assert(is.IsReady());
|
||||
|
||||
UriSuffixBuffer suffix_buffer;
|
||||
const char *const suffix = uri_get_suffix(is.GetURI(), suffix_buffer);
|
||||
const char *mime = is.GetMimeType();
|
||||
const auto suffix = uri_get_suffix(is.GetURI());
|
||||
const char *full_mime = is.GetMimeType();
|
||||
|
||||
if (suffix == nullptr && mime == nullptr)
|
||||
if (suffix.empty() && full_mime == nullptr)
|
||||
return false;
|
||||
|
||||
std::string mime_base;
|
||||
if (mime != nullptr)
|
||||
mime = (mime_base = GetMimeTypeBase(mime)).c_str();
|
||||
std::string_view mime_base{};
|
||||
if (full_mime != nullptr)
|
||||
mime_base = GetMimeTypeBase(full_mime);
|
||||
|
||||
return decoder_plugins_try([suffix, mime, &is,
|
||||
return decoder_plugins_try([suffix, mime_base, &is,
|
||||
&handler](const DecoderPlugin &plugin){
|
||||
try {
|
||||
is.LockRewind();
|
||||
} catch (...) {
|
||||
}
|
||||
|
||||
return CheckDecoderPlugin(plugin, suffix, mime) &&
|
||||
return CheckDecoderPlugin(plugin, suffix, mime_base) &&
|
||||
plugin.ScanStream(is, handler);
|
||||
});
|
||||
}
|
||||
|
@@ -20,6 +20,9 @@
|
||||
#include "TimePrint.hxx"
|
||||
#include "client/Response.hxx"
|
||||
#include "time/ISO8601.hxx"
|
||||
#include "util/StringBuffer.hxx"
|
||||
|
||||
#include <fmt/format.h>
|
||||
|
||||
void
|
||||
time_print(Response &r, const char *name,
|
||||
@@ -33,5 +36,5 @@ time_print(Response &r, const char *name,
|
||||
return;
|
||||
}
|
||||
|
||||
r.Format("%s: %s\n", name, s.c_str());
|
||||
r.Fmt(FMT_STRING("{}: {}\n"), name, s);
|
||||
}
|
||||
|
@@ -30,10 +30,6 @@ class AudioManager : public Java::GlobalObject {
|
||||
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);
|
||||
|
@@ -26,6 +26,25 @@
|
||||
|
||||
#include "AudioManager.hxx"
|
||||
|
||||
AllocatedPath
|
||||
Context::GetExternalFilesDir(JNIEnv *env, const char *_type) noexcept
|
||||
{
|
||||
assert(_type != nullptr);
|
||||
|
||||
Java::Class cls{env, env->GetObjectClass(Get())};
|
||||
jmethodID method = env->GetMethodID(cls, "getExternalFilesDir",
|
||||
"(Ljava/lang/String;)Ljava/io/File;");
|
||||
assert(method);
|
||||
|
||||
Java::String type{env, _type};
|
||||
|
||||
jobject file = env->CallObjectMethod(Get(), method, type.Get());
|
||||
if (Java::DiscardException(env) || file == nullptr)
|
||||
return nullptr;
|
||||
|
||||
return Java::File::ToAbsolutePath(env, file);
|
||||
}
|
||||
|
||||
AllocatedPath
|
||||
Context::GetCacheDir(JNIEnv *env) const noexcept
|
||||
{
|
||||
|
@@ -30,10 +30,14 @@ public:
|
||||
Context(JNIEnv *env, jobject obj) noexcept
|
||||
:Java::GlobalObject(env, obj) {}
|
||||
|
||||
gcc_pure
|
||||
[[gnu::pure]]
|
||||
AllocatedPath GetExternalFilesDir(JNIEnv *env,
|
||||
const char *type) noexcept;
|
||||
|
||||
[[gnu::pure]]
|
||||
AllocatedPath GetCacheDir(JNIEnv *env) const noexcept;
|
||||
|
||||
gcc_pure
|
||||
[[gnu::pure]]
|
||||
AudioManager *GetAudioManager(JNIEnv *env) noexcept;
|
||||
};
|
||||
|
||||
|
@@ -33,10 +33,10 @@ namespace Environment {
|
||||
/**
|
||||
* Determine the mount point of the external SD card.
|
||||
*/
|
||||
gcc_pure
|
||||
[[gnu::pure]]
|
||||
AllocatedPath getExternalStorageDirectory() noexcept;
|
||||
|
||||
gcc_pure
|
||||
[[gnu::pure]]
|
||||
AllocatedPath getExternalStoragePublicDirectory(const char *type) noexcept;
|
||||
}
|
||||
|
||||
|
@@ -20,27 +20,21 @@
|
||||
#include "LogListener.hxx"
|
||||
#include "java/Class.hxx"
|
||||
#include "java/String.hxx"
|
||||
#include "util/AllocatedString.hxx"
|
||||
#include "util/FormatString.hxx"
|
||||
|
||||
LogListener::LogListener(JNIEnv *env, jobject obj) noexcept
|
||||
:Java::GlobalObject(env, obj)
|
||||
{
|
||||
Java::Class cls(env, env->GetObjectClass(Get()));
|
||||
|
||||
onLogMethod = env->GetMethodID(cls, "onLog", "(ILjava/lang/String;)V");
|
||||
assert(onLogMethod);
|
||||
}
|
||||
|
||||
void
|
||||
LogListener::OnLog(JNIEnv *env, int priority,
|
||||
const char *fmt, ...) const noexcept
|
||||
LogListener::OnLog(JNIEnv *env, int priority, const char *msg) const noexcept
|
||||
{
|
||||
assert(env != nullptr);
|
||||
|
||||
Java::Class cls(env, env->GetObjectClass(Get()));
|
||||
|
||||
jmethodID method = env->GetMethodID(cls, "onLog",
|
||||
"(ILjava/lang/String;)V");
|
||||
|
||||
assert(method);
|
||||
|
||||
std::va_list args;
|
||||
va_start(args, fmt);
|
||||
const auto log = FormatStringV(fmt, args);
|
||||
va_end(args);
|
||||
|
||||
env->CallVoidMethod(Get(), method, priority,
|
||||
Java::String(env, log.c_str()).Get());
|
||||
env->CallVoidMethod(Get(), onLogMethod, priority,
|
||||
Java::String(env, msg).Get());
|
||||
}
|
||||
|
@@ -23,12 +23,12 @@
|
||||
#include "java/Object.hxx"
|
||||
|
||||
class LogListener : public Java::GlobalObject {
|
||||
public:
|
||||
LogListener(JNIEnv *env, jobject obj) noexcept
|
||||
:Java::GlobalObject(env, obj) {}
|
||||
jmethodID onLogMethod;
|
||||
|
||||
void OnLog(JNIEnv *env, int priority,
|
||||
const char *fmt, ...) const noexcept;
|
||||
public:
|
||||
LogListener(JNIEnv *env, jobject obj) noexcept;
|
||||
|
||||
void OnLog(JNIEnv *env, int priority, const char *msg) const noexcept;
|
||||
};
|
||||
|
||||
#endif
|
||||
|
@@ -30,7 +30,7 @@
|
||||
|
||||
#include <string.h>
|
||||
|
||||
const ArchivePlugin *const archive_plugins[] = {
|
||||
constexpr const ArchivePlugin *archive_plugins[] = {
|
||||
#ifdef ENABLE_BZ2
|
||||
&bz2_archive_plugin,
|
||||
#endif
|
||||
@@ -55,10 +55,8 @@ static bool archive_plugins_enabled[std::max(n_archive_plugins, std::size_t(1))]
|
||||
if (archive_plugins_enabled[archive_plugin_iterator - archive_plugins])
|
||||
|
||||
const ArchivePlugin *
|
||||
archive_plugin_from_suffix(const char *suffix) noexcept
|
||||
archive_plugin_from_suffix(std::string_view suffix) noexcept
|
||||
{
|
||||
assert(suffix != nullptr);
|
||||
|
||||
archive_plugins_for_each_enabled(plugin)
|
||||
if (plugin->suffixes != nullptr &&
|
||||
StringArrayContainsCase(plugin->suffixes, suffix))
|
||||
|
@@ -20,6 +20,8 @@
|
||||
#ifndef MPD_ARCHIVE_LIST_HXX
|
||||
#define MPD_ARCHIVE_LIST_HXX
|
||||
|
||||
#include <string_view>
|
||||
|
||||
struct ArchivePlugin;
|
||||
|
||||
extern const ArchivePlugin *const archive_plugins[];
|
||||
@@ -33,7 +35,7 @@ extern const ArchivePlugin *const archive_plugins[];
|
||||
/* interface for using plugins */
|
||||
|
||||
const ArchivePlugin *
|
||||
archive_plugin_from_suffix(const char *suffix) noexcept;
|
||||
archive_plugin_from_suffix(std::string_view suffix) noexcept;
|
||||
|
||||
const ArchivePlugin *
|
||||
archive_plugin_from_name(const char *name) noexcept;
|
||||
|
@@ -25,6 +25,9 @@ archive_glue = static_library(
|
||||
'ArchivePlugin.cxx',
|
||||
'../input/plugins/ArchiveInputPlugin.cxx',
|
||||
include_directories: inc,
|
||||
dependencies: [
|
||||
log_dep,
|
||||
],
|
||||
)
|
||||
|
||||
archive_glue_dep = declare_dependency(
|
||||
|
@@ -71,6 +71,9 @@ public:
|
||||
Mutex &mutex);
|
||||
~Bzip2InputStream() noexcept override;
|
||||
|
||||
Bzip2InputStream(const Bzip2InputStream &) = delete;
|
||||
Bzip2InputStream &operator=(const Bzip2InputStream &) = delete;
|
||||
|
||||
/* virtual methods from InputStream */
|
||||
[[nodiscard]] bool IsEOF() const noexcept override;
|
||||
size_t Read(std::unique_lock<Mutex> &lock,
|
||||
@@ -177,7 +180,7 @@ Bzip2InputStream::IsEOF() const noexcept
|
||||
|
||||
/* exported structures */
|
||||
|
||||
static const char *const bz2_extensions[] = {
|
||||
static constexpr const char *bz2_extensions[] = {
|
||||
"bz2",
|
||||
nullptr
|
||||
};
|
||||
|
@@ -29,6 +29,7 @@
|
||||
#include "fs/Path.hxx"
|
||||
#include "util/RuntimeError.hxx"
|
||||
#include "util/StringCompare.hxx"
|
||||
#include "util/UTF8.hxx"
|
||||
#include "util/WritableBuffer.hxx"
|
||||
|
||||
#include <cdio/iso9660.h>
|
||||
@@ -102,6 +103,10 @@ Iso9660ArchiveFile::Visit(char *path, size_t length, size_t capacity,
|
||||
/* skip special names like "." and ".." */
|
||||
continue;
|
||||
|
||||
if (!ValidateUTF8(filename))
|
||||
/* ignore file names which are not valid UTF-8 */
|
||||
continue;
|
||||
|
||||
size_t filename_length = strlen(filename);
|
||||
if (length + filename_length + 1 >= capacity)
|
||||
/* file name is too long */
|
||||
@@ -319,7 +324,7 @@ Iso9660InputStream::IsEOF() const noexcept
|
||||
|
||||
/* exported structures */
|
||||
|
||||
static const char *const iso9660_archive_extensions[] = {
|
||||
static constexpr const char * iso9660_archive_extensions[] = {
|
||||
"iso",
|
||||
nullptr
|
||||
};
|
||||
|
@@ -29,6 +29,7 @@
|
||||
#include "fs/Path.hxx"
|
||||
#include "system/Error.hxx"
|
||||
#include "util/RuntimeError.hxx"
|
||||
#include "util/UTF8.hxx"
|
||||
|
||||
#include <zzip/zzip.h>
|
||||
|
||||
@@ -84,7 +85,7 @@ ZzipArchiveFile::Visit(ArchiveVisitor &visitor)
|
||||
ZZIP_DIRENT dirent;
|
||||
while (zzip_dir_read(dir->dir, &dirent))
|
||||
//add only files
|
||||
if (dirent.st_size > 0)
|
||||
if (dirent.st_size > 0 && ValidateUTF8(dirent.d_name))
|
||||
visitor.VisitArchiveEntry(dirent.d_name);
|
||||
}
|
||||
|
||||
@@ -116,6 +117,9 @@ public:
|
||||
zzip_file_close(file);
|
||||
}
|
||||
|
||||
ZzipInputStream(const ZzipInputStream &) = delete;
|
||||
ZzipInputStream &operator=(const ZzipInputStream &) = delete;
|
||||
|
||||
/* virtual methods from InputStream */
|
||||
[[nodiscard]] bool IsEOF() const noexcept override;
|
||||
size_t Read(std::unique_lock<Mutex> &lock,
|
||||
@@ -185,7 +189,7 @@ ZzipInputStream::Seek(std::unique_lock<Mutex> &, offset_type new_offset)
|
||||
|
||||
/* exported structures */
|
||||
|
||||
static const char *const zzip_archive_extensions[] = {
|
||||
static constexpr const char *zzip_archive_extensions[] = {
|
||||
"zip",
|
||||
nullptr
|
||||
};
|
||||
|
@@ -26,8 +26,7 @@
|
||||
#include "input/LastInputStream.hxx"
|
||||
#include "tag/Mask.hxx"
|
||||
#include "event/FullyBufferedSocket.hxx"
|
||||
#include "event/TimerEvent.hxx"
|
||||
#include "util/Compiler.h"
|
||||
#include "event/CoarseTimerEvent.hxx"
|
||||
|
||||
#include <boost/intrusive/link_mode.hpp>
|
||||
#include <boost/intrusive/list_hook.hpp>
|
||||
@@ -55,7 +54,7 @@ class Client final
|
||||
public boost::intrusive::list_base_hook<boost::intrusive::tag<Partition>,
|
||||
boost::intrusive::link_mode<boost::intrusive::normal_link>>,
|
||||
public boost::intrusive::list_base_hook<boost::intrusive::link_mode<boost::intrusive::normal_link>> {
|
||||
TimerEvent timeout_event;
|
||||
CoarseTimerEvent timeout_event;
|
||||
|
||||
Partition *partition;
|
||||
|
||||
@@ -138,7 +137,7 @@ public:
|
||||
using FullyBufferedSocket::GetEventLoop;
|
||||
using FullyBufferedSocket::GetOutputMaxSize;
|
||||
|
||||
gcc_pure
|
||||
[[gnu::pure]]
|
||||
bool IsExpired() const noexcept {
|
||||
return !FullyBufferedSocket::IsDefined();
|
||||
}
|
||||
@@ -211,7 +210,7 @@ public:
|
||||
FULL,
|
||||
};
|
||||
|
||||
gcc_pure
|
||||
[[gnu::pure]]
|
||||
bool IsSubscribed(const char *channel_name) const noexcept {
|
||||
return subscriptions.find(channel_name) != subscriptions.end();
|
||||
}
|
||||
@@ -252,19 +251,19 @@ public:
|
||||
|
||||
void SetPartition(Partition &new_partition) noexcept;
|
||||
|
||||
gcc_pure
|
||||
[[gnu::pure]]
|
||||
Instance &GetInstance() const noexcept;
|
||||
|
||||
gcc_pure
|
||||
[[gnu::pure]]
|
||||
playlist &GetPlaylist() const noexcept;
|
||||
|
||||
gcc_pure
|
||||
[[gnu::pure]]
|
||||
PlayerControl &GetPlayerControl() const noexcept;
|
||||
|
||||
/**
|
||||
* Wrapper for Instance::GetDatabase().
|
||||
*/
|
||||
gcc_pure
|
||||
[[gnu::pure]]
|
||||
const Database *GetDatabase() const noexcept;
|
||||
|
||||
/**
|
||||
@@ -272,7 +271,7 @@ public:
|
||||
*/
|
||||
const Database &GetDatabaseOrThrow() const;
|
||||
|
||||
gcc_pure
|
||||
[[gnu::pure]]
|
||||
const Storage *GetStorage() const noexcept;
|
||||
|
||||
private:
|
||||
|
@@ -18,12 +18,14 @@
|
||||
*/
|
||||
|
||||
#include "Client.hxx"
|
||||
#include "Domain.hxx"
|
||||
#include "lib/fmt/ExceptionFormatter.hxx"
|
||||
#include "Log.hxx"
|
||||
|
||||
void
|
||||
Client::OnSocketError(std::exception_ptr ep) noexcept
|
||||
{
|
||||
FormatError(ep, "error on client %d", num);
|
||||
FmtError(client_domain, "error on client {}: {}", num, ep);
|
||||
|
||||
SetExpired();
|
||||
}
|
||||
|
@@ -44,7 +44,7 @@ Client::OnTimeout() noexcept
|
||||
assert(!idle_waiting);
|
||||
assert(!background_command);
|
||||
|
||||
FormatDebug(client_domain, "[%u] timeout", num);
|
||||
FmtDebug(client_domain, "[{}] timeout", num);
|
||||
}
|
||||
|
||||
Close();
|
||||
|
@@ -22,6 +22,8 @@
|
||||
#include "Response.hxx"
|
||||
#include "Idle.hxx"
|
||||
|
||||
#include <fmt/format.h>
|
||||
|
||||
#include <cassert>
|
||||
|
||||
static void
|
||||
@@ -30,7 +32,7 @@ WriteIdleResponse(Response &r, unsigned flags) noexcept
|
||||
const char *const*idle_names = idle_get_names();
|
||||
for (unsigned i = 0; idle_names[i]; ++i) {
|
||||
if (flags & (1 << i))
|
||||
r.Format("changed: %s\n", idle_names[i]);
|
||||
r.Fmt(FMT_STRING("changed: {}\n"), idle_names[i]);
|
||||
}
|
||||
|
||||
r.Write("OK\n");
|
||||
|
@@ -32,8 +32,12 @@ GetPermissions(SocketAddress address, int uid) noexcept
|
||||
#ifdef HAVE_UN
|
||||
if (address.GetFamily() == AF_LOCAL)
|
||||
return GetLocalPermissions();
|
||||
#else
|
||||
(void)address;
|
||||
#endif
|
||||
|
||||
#ifdef HAVE_TCP
|
||||
if (int permissions = GetPermissionsFromAddress(address);
|
||||
permissions >= 0)
|
||||
return permissions;
|
||||
#endif
|
||||
|
||||
return getDefaultPermissions();
|
||||
|
@@ -20,8 +20,6 @@
|
||||
#ifndef MPD_CLIENT_MESSAGE_HXX
|
||||
#define MPD_CLIENT_MESSAGE_HXX
|
||||
|
||||
#include "util/Compiler.h"
|
||||
|
||||
#include <string>
|
||||
|
||||
#ifdef _WIN32
|
||||
@@ -51,7 +49,7 @@ public:
|
||||
}
|
||||
};
|
||||
|
||||
gcc_pure
|
||||
[[gnu::pure]]
|
||||
bool
|
||||
client_message_valid_channel_name(const char *name) noexcept;
|
||||
|
||||
|
@@ -76,8 +76,8 @@ client_new(EventLoop &loop, Partition &partition,
|
||||
client_list.Add(*client);
|
||||
partition.clients.push_back(*client);
|
||||
|
||||
FormatInfo(client_domain, "[%u] opened from %s",
|
||||
num, remote.c_str());
|
||||
FmtInfo(client_domain, "[{}] opened from {}",
|
||||
num, remote);
|
||||
}
|
||||
|
||||
void
|
||||
@@ -89,6 +89,6 @@ Client::Close() noexcept
|
||||
if (FullyBufferedSocket::IsDefined())
|
||||
FullyBufferedSocket::Close();
|
||||
|
||||
FormatInfo(client_domain, "[%u] closed", num);
|
||||
FmtInfo(client_domain, "[{}] closed", num);
|
||||
delete this;
|
||||
}
|
||||
|
@@ -39,9 +39,9 @@ Client::ProcessCommandList(bool list_ok,
|
||||
for (auto &&i : list) {
|
||||
char *cmd = &*i.begin();
|
||||
|
||||
FormatDebug(client_domain, "process command \"%s\"", cmd);
|
||||
FmtDebug(client_domain, "process command \"{}\"", cmd);
|
||||
auto ret = command_process(*this, n++, cmd);
|
||||
FormatDebug(client_domain, "command returned %i", int(ret));
|
||||
FmtDebug(client_domain, "command returned {}", unsigned(ret));
|
||||
if (IsExpired())
|
||||
return CommandResult::CLOSE;
|
||||
else if (ret != CommandResult::OK)
|
||||
@@ -62,9 +62,9 @@ Client::ProcessLine(char *line) noexcept
|
||||
/* all valid MPD commands begin with a lower case
|
||||
letter; this could be a badly routed HTTP
|
||||
request */
|
||||
FormatWarning(client_domain,
|
||||
"[%u] malformed command \"%s\"",
|
||||
num, line);
|
||||
FmtWarning(client_domain,
|
||||
"[{}] malformed command \"{}\"",
|
||||
num, line);
|
||||
return CommandResult::CLOSE;
|
||||
}
|
||||
|
||||
@@ -83,9 +83,9 @@ Client::ProcessLine(char *line) noexcept
|
||||
} else if (idle_waiting) {
|
||||
/* during idle mode, clients must not send anything
|
||||
except "noidle" */
|
||||
FormatWarning(client_domain,
|
||||
"[%u] command \"%s\" during idle",
|
||||
num, line);
|
||||
FmtWarning(client_domain,
|
||||
"[{}] command \"{}\" during idle",
|
||||
num, line);
|
||||
return CommandResult::CLOSE;
|
||||
}
|
||||
|
||||
@@ -93,9 +93,9 @@ Client::ProcessLine(char *line) noexcept
|
||||
if (StringIsEqual(line, CLIENT_LIST_MODE_END)) {
|
||||
const unsigned id = num;
|
||||
|
||||
FormatDebug(client_domain,
|
||||
"[%u] process command list",
|
||||
id);
|
||||
FmtDebug(client_domain,
|
||||
"[{}] process command list",
|
||||
id);
|
||||
|
||||
const bool ok_mode = cmd_list.IsOKMode();
|
||||
auto list = cmd_list.Commit();
|
||||
@@ -103,9 +103,9 @@ Client::ProcessLine(char *line) noexcept
|
||||
|
||||
auto ret = ProcessCommandList(ok_mode,
|
||||
std::move(list));
|
||||
FormatDebug(client_domain,
|
||||
"[%u] process command "
|
||||
"list returned %i", id, int(ret));
|
||||
FmtDebug(client_domain,
|
||||
"[{}] process command "
|
||||
"list returned {}", id, unsigned(ret));
|
||||
|
||||
if (ret == CommandResult::OK)
|
||||
command_success(*this);
|
||||
@@ -113,11 +113,10 @@ Client::ProcessLine(char *line) noexcept
|
||||
return ret;
|
||||
} else {
|
||||
if (!cmd_list.Add(line)) {
|
||||
FormatWarning(client_domain,
|
||||
"[%u] command list size "
|
||||
"is larger than the max (%lu)",
|
||||
num,
|
||||
(unsigned long)client_max_command_list_size);
|
||||
FmtWarning(client_domain,
|
||||
"[{}] command list size "
|
||||
"is larger than the max ({})",
|
||||
num, client_max_command_list_size);
|
||||
return CommandResult::CLOSE;
|
||||
}
|
||||
|
||||
@@ -133,13 +132,13 @@ Client::ProcessLine(char *line) noexcept
|
||||
} else {
|
||||
const unsigned id = num;
|
||||
|
||||
FormatDebug(client_domain,
|
||||
"[%u] process command \"%s\"",
|
||||
id, line);
|
||||
FmtDebug(client_domain,
|
||||
"[{}] process command \"{}\"",
|
||||
id, line);
|
||||
auto ret = command_process(*this, 0, line);
|
||||
FormatDebug(client_domain,
|
||||
"[%u] command returned %i",
|
||||
id, int(ret));
|
||||
FmtDebug(client_domain,
|
||||
"[{}] command returned {}",
|
||||
id, unsigned(ret));
|
||||
|
||||
if (IsExpired())
|
||||
return CommandResult::CLOSE;
|
||||
|
@@ -19,8 +19,8 @@
|
||||
|
||||
#include "Response.hxx"
|
||||
#include "Client.hxx"
|
||||
#include "util/FormatString.hxx"
|
||||
#include "util/AllocatedString.hxx"
|
||||
|
||||
#include <fmt/format.h>
|
||||
|
||||
TagMask
|
||||
Response::GetTagMask() const noexcept
|
||||
@@ -41,19 +41,15 @@ Response::Write(const char *data) noexcept
|
||||
}
|
||||
|
||||
bool
|
||||
Response::FormatV(const char *fmt, std::va_list args) noexcept
|
||||
Response::VFmt(fmt::string_view format_str, fmt::format_args args) noexcept
|
||||
{
|
||||
return Write(FormatStringV(fmt, args).c_str());
|
||||
}
|
||||
|
||||
bool
|
||||
Response::Format(const char *fmt, ...) noexcept
|
||||
{
|
||||
std::va_list args;
|
||||
va_start(args, fmt);
|
||||
bool success = FormatV(fmt, args);
|
||||
va_end(args);
|
||||
return success;
|
||||
fmt::memory_buffer buffer;
|
||||
#if FMT_VERSION >= 80000
|
||||
fmt::vformat_to(std::back_inserter(buffer), format_str, args);
|
||||
#else
|
||||
fmt::vformat_to(buffer, format_str, args);
|
||||
#endif
|
||||
return Write(buffer.data(), buffer.size());
|
||||
}
|
||||
|
||||
bool
|
||||
@@ -62,11 +58,7 @@ Response::WriteBinary(ConstBuffer<void> payload) noexcept
|
||||
assert(payload.size <= client.binary_limit);
|
||||
|
||||
return
|
||||
#ifdef _WIN32
|
||||
Format("binary: %lu\n", (unsigned long)payload.size) &&
|
||||
#else
|
||||
Format("binary: %zu\n", payload.size) &&
|
||||
#endif
|
||||
Fmt("binary: {}\n", payload.size) &&
|
||||
Write(payload.data, payload.size) &&
|
||||
Write("\n");
|
||||
}
|
||||
@@ -74,19 +66,17 @@ Response::WriteBinary(ConstBuffer<void> payload) noexcept
|
||||
void
|
||||
Response::Error(enum ack code, const char *msg) noexcept
|
||||
{
|
||||
FormatError(code, "%s", msg);
|
||||
FmtError(code, FMT_STRING("{}"), msg);
|
||||
}
|
||||
|
||||
void
|
||||
Response::FormatError(enum ack code, const char *fmt, ...) noexcept
|
||||
Response::VFmtError(enum ack code,
|
||||
fmt::string_view format_str, fmt::format_args args) noexcept
|
||||
{
|
||||
Format("ACK [%i@%u] {%s} ",
|
||||
(int)code, list_index, command);
|
||||
Fmt(FMT_STRING("ACK [{}@{}] {{{}}} "),
|
||||
(int)code, list_index, command);
|
||||
|
||||
std::va_list args;
|
||||
va_start(args, fmt);
|
||||
FormatV(fmt, args);
|
||||
va_end(args);
|
||||
VFmt(format_str, std::move(args));
|
||||
|
||||
Write("\n");
|
||||
}
|
||||
|
@@ -21,9 +21,12 @@
|
||||
#define MPD_RESPONSE_HXX
|
||||
|
||||
#include "protocol/Ack.hxx"
|
||||
#include "util/Compiler.h"
|
||||
|
||||
#include <cstdarg>
|
||||
#include <fmt/core.h>
|
||||
#if FMT_VERSION < 70000 || FMT_VERSION >= 80000
|
||||
#include <fmt/format.h>
|
||||
#endif
|
||||
|
||||
#include <cstddef>
|
||||
|
||||
template<typename T> struct ConstBuffer;
|
||||
@@ -65,7 +68,7 @@ public:
|
||||
* Accessor for Client::tag_mask. Can be used if caller wants
|
||||
* to avoid including Client.hxx.
|
||||
*/
|
||||
gcc_pure
|
||||
[[gnu::pure]]
|
||||
TagMask GetTagMask() const noexcept;
|
||||
|
||||
void SetCommand(const char *_command) noexcept {
|
||||
@@ -74,10 +77,21 @@ public:
|
||||
|
||||
bool Write(const void *data, size_t length) noexcept;
|
||||
bool Write(const char *data) noexcept;
|
||||
bool FormatV(const char *fmt, std::va_list args) noexcept;
|
||||
|
||||
gcc_printf(2,3)
|
||||
bool Format(const char *fmt, ...) noexcept;
|
||||
bool VFmt(fmt::string_view format_str, fmt::format_args args) noexcept;
|
||||
|
||||
template<typename S, typename... Args>
|
||||
bool Fmt(const S &format_str, Args&&... args) noexcept {
|
||||
#if FMT_VERSION >= 70000
|
||||
return VFmt(fmt::to_string_view(format_str),
|
||||
fmt::make_args_checked<Args...>(format_str,
|
||||
args...));
|
||||
#else
|
||||
/* expensive fallback for older libfmt versions */
|
||||
const auto result = fmt::format(format_str, args...);
|
||||
return Write(result.data(), result.size());
|
||||
#endif
|
||||
}
|
||||
|
||||
/**
|
||||
* Write a binary chunk; this writes the "binary" line, the
|
||||
@@ -88,7 +102,23 @@ public:
|
||||
bool WriteBinary(ConstBuffer<void> payload) noexcept;
|
||||
|
||||
void Error(enum ack code, const char *msg) noexcept;
|
||||
void FormatError(enum ack code, const char *fmt, ...) noexcept;
|
||||
|
||||
void VFmtError(enum ack code,
|
||||
fmt::string_view format_str, fmt::format_args args) noexcept;
|
||||
|
||||
template<typename S, typename... Args>
|
||||
void FmtError(enum ack code,
|
||||
const S &format_str, Args&&... args) noexcept {
|
||||
#if FMT_VERSION >= 70000
|
||||
return VFmtError(code, fmt::to_string_view(format_str),
|
||||
fmt::make_args_checked<Args...>(format_str,
|
||||
args...));
|
||||
#else
|
||||
/* expensive fallback for older libfmt versions */
|
||||
const auto result = fmt::format(format_str, args...);
|
||||
return Error(code, result.c_str());
|
||||
#endif
|
||||
}
|
||||
};
|
||||
|
||||
#endif
|
||||
|
@@ -70,7 +70,7 @@ ThreadBackgroundCommand::Cancel() noexcept
|
||||
CancelThread();
|
||||
thread.Join();
|
||||
|
||||
/* cancel the DeferEvent, just in case the Thread has
|
||||
/* cancel the InjectEvent, just in case the Thread has
|
||||
meanwhile finished execution */
|
||||
defer_finish.Cancel();
|
||||
}
|
||||
|
@@ -21,7 +21,7 @@
|
||||
#define MPD_THREAD_BACKGROUND_COMMAND_HXX
|
||||
|
||||
#include "BackgroundCommand.hxx"
|
||||
#include "event/DeferEvent.hxx"
|
||||
#include "event/InjectEvent.hxx"
|
||||
#include "thread/Thread.hxx"
|
||||
|
||||
#include <exception>
|
||||
@@ -34,7 +34,7 @@ class Response;
|
||||
*/
|
||||
class ThreadBackgroundCommand : public BackgroundCommand {
|
||||
Thread thread;
|
||||
DeferEvent defer_finish;
|
||||
InjectEvent defer_finish;
|
||||
Client &client;
|
||||
|
||||
/**
|
||||
|
@@ -48,6 +48,8 @@
|
||||
#include "StickerCommands.hxx"
|
||||
#endif
|
||||
|
||||
#include <fmt/format.h>
|
||||
|
||||
#include <cassert>
|
||||
#include <iterator>
|
||||
|
||||
@@ -89,21 +91,21 @@ static constexpr struct command commands[] = {
|
||||
{ "albumart", PERMISSION_READ, 2, 2, handle_album_art },
|
||||
{ "binarylimit", PERMISSION_NONE, 1, 1, handle_binary_limit },
|
||||
{ "channels", PERMISSION_READ, 0, 0, handle_channels },
|
||||
{ "clear", PERMISSION_CONTROL, 0, 0, handle_clear },
|
||||
{ "clearerror", PERMISSION_CONTROL, 0, 0, handle_clearerror },
|
||||
{ "clear", PERMISSION_PLAYER, 0, 0, handle_clear },
|
||||
{ "clearerror", PERMISSION_PLAYER, 0, 0, handle_clearerror },
|
||||
{ "cleartagid", PERMISSION_ADD, 1, 2, handle_cleartagid },
|
||||
{ "close", PERMISSION_NONE, -1, -1, handle_close },
|
||||
{ "commands", PERMISSION_NONE, 0, 0, handle_commands },
|
||||
{ "config", PERMISSION_ADMIN, 0, 0, handle_config },
|
||||
{ "consume", PERMISSION_CONTROL, 1, 1, handle_consume },
|
||||
{ "consume", PERMISSION_PLAYER, 1, 1, handle_consume },
|
||||
#ifdef ENABLE_DATABASE
|
||||
{ "count", PERMISSION_READ, 1, -1, handle_count },
|
||||
#endif
|
||||
{ "crossfade", PERMISSION_CONTROL, 1, 1, handle_crossfade },
|
||||
{ "crossfade", PERMISSION_PLAYER, 1, 1, handle_crossfade },
|
||||
{ "currentsong", PERMISSION_READ, 0, 0, handle_currentsong },
|
||||
{ "decoders", PERMISSION_READ, 0, 0, handle_decoders },
|
||||
{ "delete", PERMISSION_CONTROL, 1, 1, handle_delete },
|
||||
{ "deleteid", PERMISSION_CONTROL, 1, 1, handle_deleteid },
|
||||
{ "delete", PERMISSION_PLAYER, 1, 1, handle_delete },
|
||||
{ "deleteid", PERMISSION_PLAYER, 1, 1, handle_deleteid },
|
||||
{ "delpartition", PERMISSION_ADMIN, 1, 1, handle_delpartition },
|
||||
{ "disableoutput", PERMISSION_ADMIN, 1, 1, handle_disableoutput },
|
||||
{ "enableoutput", PERMISSION_ADMIN, 1, 1, handle_enableoutput },
|
||||
@@ -114,6 +116,7 @@ static constexpr struct command commands[] = {
|
||||
#ifdef ENABLE_CHROMAPRINT
|
||||
{ "getfingerprint", PERMISSION_READ, 1, 1, handle_getfingerprint },
|
||||
#endif
|
||||
{ "getvol", PERMISSION_READ, 0, 0, handle_getvol },
|
||||
{ "idle", PERMISSION_READ, 0, -1, handle_idle },
|
||||
{ "kill", PERMISSION_ADMIN, -1, -1, handle_kill },
|
||||
#ifdef ENABLE_DATABASE
|
||||
@@ -132,27 +135,27 @@ static constexpr struct command commands[] = {
|
||||
{ "listplaylist", PERMISSION_READ, 1, 1, handle_listplaylist },
|
||||
{ "listplaylistinfo", PERMISSION_READ, 1, 1, handle_listplaylistinfo },
|
||||
{ "listplaylists", PERMISSION_READ, 0, 0, handle_listplaylists },
|
||||
{ "load", PERMISSION_ADD, 1, 2, handle_load },
|
||||
{ "load", PERMISSION_ADD, 1, 3, handle_load },
|
||||
{ "lsinfo", PERMISSION_READ, 0, 1, handle_lsinfo },
|
||||
{ "mixrampdb", PERMISSION_CONTROL, 1, 1, handle_mixrampdb },
|
||||
{ "mixrampdelay", PERMISSION_CONTROL, 1, 1, handle_mixrampdelay },
|
||||
{ "mixrampdb", PERMISSION_PLAYER, 1, 1, handle_mixrampdb },
|
||||
{ "mixrampdelay", PERMISSION_PLAYER, 1, 1, handle_mixrampdelay },
|
||||
#ifdef ENABLE_DATABASE
|
||||
{ "mount", PERMISSION_ADMIN, 2, 2, handle_mount },
|
||||
#endif
|
||||
{ "move", PERMISSION_CONTROL, 2, 2, handle_move },
|
||||
{ "moveid", PERMISSION_CONTROL, 2, 2, handle_moveid },
|
||||
{ "move", PERMISSION_PLAYER, 2, 2, handle_move },
|
||||
{ "moveid", PERMISSION_PLAYER, 2, 2, handle_moveid },
|
||||
{ "moveoutput", PERMISSION_ADMIN, 1, 1, handle_moveoutput },
|
||||
{ "newpartition", PERMISSION_ADMIN, 1, 1, handle_newpartition },
|
||||
{ "next", PERMISSION_CONTROL, 0, 0, handle_next },
|
||||
{ "next", PERMISSION_PLAYER, 0, 0, handle_next },
|
||||
{ "notcommands", PERMISSION_NONE, 0, 0, handle_not_commands },
|
||||
{ "outputs", PERMISSION_READ, 0, 0, handle_devices },
|
||||
{ "outputset", PERMISSION_ADMIN, 3, 3, handle_outputset },
|
||||
{ "partition", PERMISSION_READ, 1, 1, handle_partition },
|
||||
{ "password", PERMISSION_NONE, 1, 1, handle_password },
|
||||
{ "pause", PERMISSION_CONTROL, 0, 1, handle_pause },
|
||||
{ "pause", PERMISSION_PLAYER, 0, 1, handle_pause },
|
||||
{ "ping", PERMISSION_NONE, 0, 0, handle_ping },
|
||||
{ "play", PERMISSION_CONTROL, 0, 1, handle_play },
|
||||
{ "playid", PERMISSION_CONTROL, 0, 1, handle_playid },
|
||||
{ "play", PERMISSION_PLAYER, 0, 1, handle_play },
|
||||
{ "playid", PERMISSION_PLAYER, 0, 1, handle_playid },
|
||||
{ "playlist", PERMISSION_READ, 0, 0, handle_playlist },
|
||||
{ "playlistadd", PERMISSION_CONTROL, 2, 2, handle_playlistadd },
|
||||
{ "playlistclear", PERMISSION_CONTROL, 1, 1, handle_playlistclear },
|
||||
@@ -164,17 +167,17 @@ static constexpr struct command commands[] = {
|
||||
{ "playlistsearch", PERMISSION_READ, 1, -1, handle_playlistsearch },
|
||||
{ "plchanges", PERMISSION_READ, 1, 2, handle_plchanges },
|
||||
{ "plchangesposid", PERMISSION_READ, 1, 2, handle_plchangesposid },
|
||||
{ "previous", PERMISSION_CONTROL, 0, 0, handle_previous },
|
||||
{ "prio", PERMISSION_CONTROL, 2, -1, handle_prio },
|
||||
{ "prioid", PERMISSION_CONTROL, 2, -1, handle_prioid },
|
||||
{ "random", PERMISSION_CONTROL, 1, 1, handle_random },
|
||||
{ "previous", PERMISSION_PLAYER, 0, 0, handle_previous },
|
||||
{ "prio", PERMISSION_PLAYER, 2, -1, handle_prio },
|
||||
{ "prioid", PERMISSION_PLAYER, 2, -1, handle_prioid },
|
||||
{ "random", PERMISSION_PLAYER, 1, 1, handle_random },
|
||||
{ "rangeid", PERMISSION_ADD, 2, 2, handle_rangeid },
|
||||
{ "readcomments", PERMISSION_READ, 1, 1, handle_read_comments },
|
||||
{ "readmessages", PERMISSION_READ, 0, 0, handle_read_messages },
|
||||
{ "readpicture", PERMISSION_READ, 2, 2, handle_read_picture },
|
||||
{ "rename", PERMISSION_CONTROL, 2, 2, handle_rename },
|
||||
{ "repeat", PERMISSION_CONTROL, 1, 1, handle_repeat },
|
||||
{ "replay_gain_mode", PERMISSION_CONTROL, 1, 1,
|
||||
{ "repeat", PERMISSION_PLAYER, 1, 1, handle_repeat },
|
||||
{ "replay_gain_mode", PERMISSION_PLAYER, 1, 1,
|
||||
handle_replay_gain_mode },
|
||||
{ "replay_gain_status", PERMISSION_READ, 0, 0,
|
||||
handle_replay_gain_status },
|
||||
@@ -186,22 +189,22 @@ static constexpr struct command commands[] = {
|
||||
{ "searchadd", PERMISSION_ADD, 1, -1, handle_searchadd },
|
||||
{ "searchaddpl", PERMISSION_CONTROL, 2, -1, handle_searchaddpl },
|
||||
#endif
|
||||
{ "seek", PERMISSION_CONTROL, 2, 2, handle_seek },
|
||||
{ "seekcur", PERMISSION_CONTROL, 1, 1, handle_seekcur },
|
||||
{ "seekid", PERMISSION_CONTROL, 2, 2, handle_seekid },
|
||||
{ "seek", PERMISSION_PLAYER, 2, 2, handle_seek },
|
||||
{ "seekcur", PERMISSION_PLAYER, 1, 1, handle_seekcur },
|
||||
{ "seekid", PERMISSION_PLAYER, 2, 2, handle_seekid },
|
||||
{ "sendmessage", PERMISSION_CONTROL, 2, 2, handle_send_message },
|
||||
{ "setvol", PERMISSION_CONTROL, 1, 1, handle_setvol },
|
||||
{ "shuffle", PERMISSION_CONTROL, 0, 1, handle_shuffle },
|
||||
{ "single", PERMISSION_CONTROL, 1, 1, handle_single },
|
||||
{ "setvol", PERMISSION_PLAYER, 1, 1, handle_setvol },
|
||||
{ "shuffle", PERMISSION_PLAYER, 0, 1, handle_shuffle },
|
||||
{ "single", PERMISSION_PLAYER, 1, 1, handle_single },
|
||||
{ "stats", PERMISSION_READ, 0, 0, handle_stats },
|
||||
{ "status", PERMISSION_READ, 0, 0, handle_status },
|
||||
#ifdef ENABLE_SQLITE
|
||||
{ "sticker", PERMISSION_ADMIN, 3, -1, handle_sticker },
|
||||
#endif
|
||||
{ "stop", PERMISSION_CONTROL, 0, 0, handle_stop },
|
||||
{ "stop", PERMISSION_PLAYER, 0, 0, handle_stop },
|
||||
{ "subscribe", PERMISSION_READ, 1, 1, handle_subscribe },
|
||||
{ "swap", PERMISSION_CONTROL, 2, 2, handle_swap },
|
||||
{ "swapid", PERMISSION_CONTROL, 2, 2, handle_swapid },
|
||||
{ "swap", PERMISSION_PLAYER, 2, 2, handle_swap },
|
||||
{ "swapid", PERMISSION_PLAYER, 2, 2, handle_swapid },
|
||||
{ "tagtypes", PERMISSION_NONE, 0, -1, handle_tagtypes },
|
||||
{ "toggleoutput", PERMISSION_ADMIN, 1, 1, handle_toggleoutput },
|
||||
#ifdef ENABLE_DATABASE
|
||||
@@ -210,7 +213,7 @@ static constexpr struct command commands[] = {
|
||||
{ "unsubscribe", PERMISSION_READ, 1, 1, handle_unsubscribe },
|
||||
{ "update", PERMISSION_CONTROL, 0, 1, handle_update },
|
||||
{ "urlhandlers", PERMISSION_READ, 0, 0, handle_urlhandlers },
|
||||
{ "volume", PERMISSION_CONTROL, 1, 1, handle_volume },
|
||||
{ "volume", PERMISSION_PLAYER, 1, 1, handle_volume },
|
||||
};
|
||||
|
||||
static constexpr unsigned num_commands = std::size(commands);
|
||||
@@ -252,7 +255,7 @@ PrintAvailableCommands(Response &r, const Partition &partition,
|
||||
|
||||
if (cmd->permission == (permission & cmd->permission) &&
|
||||
command_available(partition, cmd))
|
||||
r.Format("command: %s\n", cmd->cmd);
|
||||
r.Fmt(FMT_STRING("command: {}\n"), cmd->cmd);
|
||||
}
|
||||
|
||||
return CommandResult::OK;
|
||||
@@ -265,7 +268,7 @@ PrintUnavailableCommands(Response &r, unsigned permission) noexcept
|
||||
const struct command *cmd = &i;
|
||||
|
||||
if (cmd->permission != (permission & cmd->permission))
|
||||
r.Format("command: %s\n", cmd->cmd);
|
||||
r.Fmt(FMT_STRING("command: {}\n"), cmd->cmd);
|
||||
}
|
||||
|
||||
return CommandResult::OK;
|
||||
@@ -322,9 +325,9 @@ command_check_request(const struct command *cmd, Response &r,
|
||||
unsigned permission, Request args) noexcept
|
||||
{
|
||||
if (cmd->permission != (permission & cmd->permission)) {
|
||||
r.FormatError(ACK_ERROR_PERMISSION,
|
||||
"you don't have permission for \"%s\"",
|
||||
cmd->cmd);
|
||||
r.FmtError(ACK_ERROR_PERMISSION,
|
||||
FMT_STRING("you don't have permission for \"{}\""),
|
||||
cmd->cmd);
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -335,17 +338,19 @@ command_check_request(const struct command *cmd, Response &r,
|
||||
return true;
|
||||
|
||||
if (min == max && unsigned(max) != args.size) {
|
||||
r.FormatError(ACK_ERROR_ARG,
|
||||
"wrong number of arguments for \"%s\"",
|
||||
cmd->cmd);
|
||||
r.FmtError(ACK_ERROR_ARG,
|
||||
FMT_STRING("wrong number of arguments for \"{}\""),
|
||||
cmd->cmd);
|
||||
return false;
|
||||
} else if (args.size < unsigned(min)) {
|
||||
r.FormatError(ACK_ERROR_ARG,
|
||||
"too few arguments for \"%s\"", cmd->cmd);
|
||||
r.FmtError(ACK_ERROR_ARG,
|
||||
FMT_STRING("too few arguments for \"{}\""),
|
||||
cmd->cmd);
|
||||
return false;
|
||||
} else if (max >= 0 && args.size > unsigned(max)) {
|
||||
r.FormatError(ACK_ERROR_ARG,
|
||||
"too many arguments for \"%s\"", cmd->cmd);
|
||||
r.FmtError(ACK_ERROR_ARG,
|
||||
FMT_STRING("too many arguments for \"{}\""),
|
||||
cmd->cmd);
|
||||
return false;
|
||||
} else
|
||||
return true;
|
||||
@@ -357,8 +362,8 @@ command_checked_lookup(Response &r, unsigned permission,
|
||||
{
|
||||
const struct command *cmd = command_lookup(cmd_name);
|
||||
if (cmd == nullptr) {
|
||||
r.FormatError(ACK_ERROR_UNKNOWN,
|
||||
"unknown command \"%s\"", cmd_name);
|
||||
r.FmtError(ACK_ERROR_UNKNOWN,
|
||||
FMT_STRING("unknown command \"{}\""), cmd_name);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user