Compare commits
1255 Commits
Author | SHA1 | Date | |
---|---|---|---|
feac1a3f56 | |||
f3c37e484e | |||
49130c2018 | |||
94af199c49 | |||
2d25f6f57f | |||
cf179ec294 | |||
4d6f220a2f | |||
0ffbe5b5ea | |||
5b83c834ac | |||
da7f32bddb | |||
9a5eac4ea9 | |||
6571b5d118 | |||
12dff8e382 | |||
c4da87a0cb | |||
446f8f29d3 | |||
48cc76f114 | |||
a0892b852e | |||
485c7805eb | |||
23802f4489 | |||
3fedd978a2 | |||
a9f1bed922 | |||
eb23788fec | |||
f6d73555a6 | |||
a56a709406 | |||
5f253e66f6 | |||
4669f7e2b9 | |||
4c90f88704 | |||
a7213b78d6 | |||
719333e16e | |||
100e471b49 | |||
3f2016e552 | |||
dd89ea4505 | |||
101e12cf9a | |||
f382808450 | |||
0cbe3c2a93 | |||
4f0ae28359 | |||
6a4250f485 | |||
3322b29e6a | |||
33ac472601 | |||
561d6fd478 | |||
42a01822bf | |||
38f1237d49 | |||
8df77122e5 | |||
fef6b9df80 | |||
d52eac66db | |||
70879f0abc | |||
bcb393628e | |||
18d3a5c12b | |||
6ee3d0102b | |||
fc9626e2f4 | |||
3bedd94fc8 | |||
8842650c33 | |||
d5bf128cee | |||
5cd86e272f | |||
740cbe9e02 | |||
ed890a273a | |||
068cd559e1 | |||
dc127f39a7 | |||
7a99a7008c | |||
70b451db7b | |||
2ab03a0914 | |||
2fa8c7d2db | |||
7c759ba8b0 | |||
6d9b452fde | |||
f7eb1c9a83 | |||
2d22e6dee4 | |||
4587bf759d | |||
e1e37cfe3c | |||
381934985a | |||
a8042885ac | |||
a71e68db50 | |||
1417578b3d | |||
96befa138c | |||
16a99804de | |||
75a39ed279 | |||
4d357ab77c | |||
d4f3dd49b4 | |||
4ec6d0555a | |||
a6a1182c4c | |||
a59c9c602b | |||
0c4d824d64 | |||
a5281856c9 | |||
0206a46d39 | |||
9475ef2202 | |||
edae00e719 | |||
fb695bc55f | |||
23a5b8fd3c | |||
273a93cfcf | |||
d105985d78 | |||
f8cfeb39e9 | |||
d5d3982d3c | |||
47341107ea | |||
90eaa87a4d | |||
b09a54b2c2 | |||
10aec174d5 | |||
d32ed194e8 | |||
70d0fbd715 | |||
302432e157 | |||
4ab8a677dc | |||
52e4a4c904 | |||
a0f6932ebe | |||
6e700dab69 | |||
35eaed7206 | |||
e7c963f2ce | |||
949d72e368 | |||
8d2a184658 | |||
c877a32d97 | |||
541468f0ca | |||
d2797effa3 | |||
1170fb1e1e | |||
65b9b3195c | |||
258830e913 | |||
d91da96798 | |||
b3897df682 | |||
3cacb56bb7 | |||
15a1973e28 | |||
ad7d47a8ba | |||
0948c607b6 | |||
60d04052c5 | |||
c1780ac657 | |||
e49cf0ec38 | |||
e1d641f684 | |||
4efd0a9f77 | |||
f6f8751332 | |||
abb28593ce | |||
115693b046 | |||
e4b055eb6d | |||
9866adff95 | |||
a8b0c55818 | |||
cac88e8be5 | |||
e9f6a3482c | |||
5d2e80f188 | |||
cfd4d5b13e | |||
06514aec63 | |||
4ded1ae67b | |||
1da974e3fa | |||
94f06f0946 | |||
d9eec8a455 | |||
eaecbcafb2 | |||
73b5d0a9b9 | |||
c2d0f35e7a | |||
ab99a57997 | |||
c8ebaf3521 | |||
52d00f7e30 | |||
309491a6d8 | |||
e7bfd32ccc | |||
6f283b52ab | |||
32bddfabea | |||
1944c826bc | |||
619bb60b26 | |||
c549e16ed1 | |||
01c9c4507f | |||
8c9d7bf07e | |||
44ef34db88 | |||
5781f223f6 | |||
e4c8ebe056 | |||
76b25a1377 | |||
ccc3ee663b | |||
0626661764 | |||
31db04a3ca | |||
0c7163b9db | |||
7d78cad8af | |||
912530ed20 | |||
d3f37199b9 | |||
a4748d84b0 | |||
8f847ec381 | |||
3a70f09dd3 | |||
568f63100b | |||
3e25916b37 | |||
5f9438dae6 | |||
99e65c58ce | |||
df71b07e9d | |||
2694195215 | |||
66450d1f3c | |||
76efea3aa7 | |||
7ab0dfc8ce | |||
15ff7c4cad | |||
9ab9b97f20 | |||
88d92aceab | |||
a2ce4352c8 | |||
84f43ccde8 | |||
38704c9cf3 | |||
910d0ec92b | |||
3b05c89765 | |||
e77b3fa46f | |||
12147f6d58 | |||
40bc60d6ae | |||
7778210269 | |||
6229210d51 | |||
5d0d5b5d97 | |||
1aa3c1e543 | |||
b90e32fe4e | |||
1f4df2a64d | |||
2efc1db6a9 | |||
e2d4654e20 | |||
2b8f1170a6 | |||
5c4743441e | |||
cb288439a4 | |||
69f741e8a6 | |||
4b4f47002b | |||
615c301961 | |||
dc07180e48 | |||
d3b235bab5 | |||
7c920ddebe | |||
bbc088ae4e | |||
fe195257d8 | |||
57d5df8118 | |||
59792cb0b8 | |||
cc557c4d60 | |||
956c5faebb | |||
cd0396c1f1 | |||
79f9b268bb | |||
b45f3c8deb | |||
f8a8de87e4 | |||
2183f0553c | |||
1f28790476 | |||
c8dae95eff | |||
547a084c7e | |||
493677ff81 | |||
6b430ba271 | |||
bc6924d303 | |||
02b00f9146 | |||
e807ed5870 | |||
f08944253b | |||
792d6584b9 | |||
7b45d01462 | |||
5c17b2966a | |||
0c54f29446 | |||
9c3cf39fdd | |||
d2fb229685 | |||
f55bc6682f | |||
6857286b42 | |||
c0d5bd2048 | |||
666e5d7904 | |||
3613407ac5 | |||
c32dceb4d4 | |||
5573e78364 | |||
807a19889f | |||
df7242de91 | |||
d62426f168 | |||
1714cf3417 | |||
1080c917be | |||
8eb3164878 | |||
915c5442d1 | |||
be0360d5e8 | |||
4d6ae6ffdd | |||
ecee6f415b | |||
47680f936b | |||
2d7181105d | |||
9bdc75524b | |||
2f6ceb4949 | |||
cd933aa35f | |||
138738075b | |||
2ee57f9b0d | |||
5a5655b790 | |||
b88d1e6820 | |||
19d2864c34 | |||
29e3a17f26 | |||
252e9f736f | |||
5d08988dda | |||
47ca4246aa | |||
f8338d4f00 | |||
5cf6032c90 | |||
8d8b77412d | |||
fd9114e7e2 | |||
a3fba2f8f7 | |||
e2b671f1b2 | |||
2a35fbe29e | |||
81cde72fd0 | |||
bf9ffba4f7 | |||
c975d8b943 | |||
2730f91872 | |||
97ca85e155 | |||
39bb4c5871 | |||
bdceb90c59 | |||
8bd1b5228c | |||
a009e95afd | |||
32aafb3572 | |||
b577783cf0 | |||
aa7b872a14 | |||
c6f7f57776 | |||
106ad08cd2 | |||
0341ca1b6a | |||
7581ea55db | |||
fc9cee38d8 | |||
b175e4128d | |||
97b07798b0 | |||
112fcd206d | |||
11d1f56062 | |||
bd840d4638 | |||
c3d393f214 | |||
f88fc0ca1a | |||
fb8d8242ab | |||
f2a3dfd700 | |||
85f9863e0a | |||
83572701f4 | |||
fa7d7e9187 | |||
f818cde32c | |||
9da93cd887 | |||
026e7ea32a | |||
9659d19718 | |||
50d35c9677 | |||
4260e78861 | |||
7342ae2e33 | |||
35dbc1a90c | |||
c7a4355153 | |||
33a84a8ca2 | |||
1d04490ed3 | |||
4a30c2d79c | |||
83072d6b9c | |||
c779fc37eb | |||
e08c13ad7e | |||
2c82a6b2e0 | |||
3929f17aef | |||
ee39af3419 | |||
3882a5a263 | |||
ac06088948 | |||
a757eebfbb | |||
2be4f89555 | |||
4a5c7d8261 | |||
f591193dda | |||
434869900e | |||
2aed7378cc | |||
71cd6e6248 | |||
c83294916a | |||
603bbe0afd | |||
c361e235eb | |||
8a59493d96 | |||
7ef86cbf9f | |||
c9530118a4 | |||
878d9abeb7 | |||
2d705efe1c | |||
aeaef85507 | |||
ebae25d175 | |||
5ad1a01d7a | |||
8f84e1befd | |||
9975905faf | |||
233184568c | |||
59da778009 | |||
108ce95b7c | |||
86e9ed5f3a | |||
fbecb05bf4 | |||
4983703375 | |||
3856224df9 | |||
6d4bedfc56 | |||
bea821f194 | |||
4e276256c0 | |||
d0f9062b56 | |||
b9cc036703 | |||
4e9b88559b | |||
3452682a42 | |||
9262b24504 | |||
a5fa43b526 | |||
8681a3d74c | |||
f9c4d88b12 | |||
799032505e | |||
c8f174ac92 | |||
047e169f3e | |||
687327c9e8 | |||
26dc37bd76 | |||
c693e4aa64 | |||
acab731fef | |||
7e4ba3cb72 | |||
172c4d9c7d | |||
bd5f6cbc7b | |||
6fcd1c734b | |||
eca097dbfb | |||
51ffafa011 | |||
8dca602346 | |||
0ed24f3a05 | |||
e25e0030e7 | |||
df4b6b92f2 | |||
1c69913eca | |||
cb5c6259fd | |||
bf287fefb5 | |||
20bf1d68e6 | |||
9bc4c168fd | |||
3415049d1c | |||
a45949b597 | |||
6009d4abab | |||
16fb843c9b | |||
36b333459b | |||
4d3320233e | |||
933a1a41e6 | |||
1ff8626716 | |||
c30466b84a | |||
868f1a4431 | |||
05f529fffd | |||
f01388559f | |||
27edd4a610 | |||
cc421b04cd | |||
3f2bc325a1 | |||
54686dfd79 | |||
f22cf02ed8 | |||
5b51d0f733 | |||
e03f82636a | |||
d53d85bd79 | |||
4682ae0898 | |||
fd5b195879 | |||
bb5df9839d | |||
be34d55291 | |||
c13911b171 | |||
6f83bdd6f3 | |||
9bcd425a85 | |||
ec917f70d2 | |||
40ce4eeb43 | |||
29ae84e199 | |||
250011f016 | |||
e08c85ae2d | |||
dcb5ca203c | |||
77df5a8f24 | |||
d6bebd2507 | |||
f74996c02f | |||
eea2d35d3a | |||
d94e8bd82d | |||
b0c92e1a34 | |||
ead5bcf048 | |||
bdd268a524 | |||
e783c2bd2c | |||
837fc98638 | |||
5deca66fdc | |||
cfe2dd4147 | |||
00f8d65a17 | |||
4e0e4c00bf | |||
a8c77a6fba | |||
31aa6d0c4f | |||
d051c4931d | |||
94b0baceb0 | |||
16feb261e2 | |||
f084bf7872 | |||
1112d3907a | |||
3464497880 | |||
651f57bced | |||
b4e72aba6c | |||
061dd2dfef | |||
5f4ec7de5b | |||
6f81bb4b09 | |||
4ed60a5711 | |||
c93195c94b | |||
f30adac4bb | |||
a4e4217204 | |||
8754d705a1 | |||
23d4a2d6a5 | |||
ce77b148d9 | |||
be3eca39e8 | |||
3413b1aeb4 | |||
356d13e9dd | |||
fa34bf0aaf | |||
5d0941476a | |||
5ff0bbd0f8 | |||
a3764e533c | |||
3e05cba30e | |||
14b3c0f0af | |||
67aff05051 | |||
19a101c3ac | |||
8da17a8211 | |||
2748929039 | |||
0c900a4bfa | |||
f1d5d70010 | |||
56ebc7637d | |||
996dd9fc8b | |||
056514d598 | |||
9a21bdfd6a | |||
03f99dd26e | |||
bfb1b641f9 | |||
72ba98c464 | |||
dcd19c0592 | |||
109159e0f7 | |||
409b877eea | |||
c5bf7948ff | |||
b9f7127691 | |||
1e6f5f012c | |||
225d85fd9b | |||
1bb22f118d | |||
552c30eae4 | |||
48e8a26813 | |||
ade847bc89 | |||
a6173e0eae | |||
4529bb4a83 | |||
258ecb764f | |||
6f595e9abb | |||
35c4c7e8bf | |||
293ed924d1 | |||
c8121176b3 | |||
ee270f9b00 | |||
bf1d77a4d8 | |||
a9344fafe9 | |||
b8890726f2 | |||
0f84332654 | |||
46c82259f7 | |||
2d03823283 | |||
bba144eca5 | |||
9af73dad93 | |||
f0d66bf6a6 | |||
5ad53a7554 | |||
7b2e3331f2 | |||
3cb44f6652 | |||
b7fdff46f2 | |||
e16109330d | |||
72621531e0 | |||
0a48146efc | |||
0c4bf12bfd | |||
b8e0855ef3 | |||
6467502b9d | |||
15b67f20e5 | |||
0825179f00 | |||
97211d0aad | |||
029c499bfa | |||
0ba867ec16 | |||
866d147122 | |||
32851d1bc7 | |||
78257408b4 | |||
f447b7615e | |||
1f780b7209 | |||
04bf8a6b1a | |||
c4c64854d4 | |||
17562dc90b | |||
7b24316734 | |||
5fab107fd3 | |||
f31920e092 | |||
eb111a10e7 | |||
80b09360c6 | |||
5ccf78855d | |||
fd5a3b5880 | |||
6120c1360c | |||
a8087dc12c | |||
070c03dbf7 | |||
0a9bec3754 | |||
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 | |||
14465be847 | |||
0e49de867d | |||
f2e4529707 | |||
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 |
.github
.travis.ymlNEWSREADME.mdandroid
doc
meson.buildmeson_options.txtpython/build
autotools.pyboost.pycmake.pydownload.pyffmpeg.pyjack.pylibs.pymakeproject.pymeson.pyopenssl.pyproject.pyquilt.pytar.pytoolchain.pyverify.pyzlib.py
src
CommandLine.cxxCommandLine.hxxIdleFlags.cxxIdleFlags.hxxInstance.cxxInstance.hxxListen.cxxLocateUri.cxxLog.cxxLog.hxxLogBackend.cxxLogInit.cxxMain.cxxMain.hxxMapper.cxxMapper.hxxMusicBuffer.cxxMusicBuffer.hxxMusicChunk.hxxMusicPipe.cxxMusicPipe.hxxPartition.cxxPartition.hxxPermission.cxxPermission.hxxPlaylistDatabase.cxxPlaylistDatabase.hxxPlaylistFile.cxxPlaylistFile.hxxPlaylistPrint.cxxPlaylistPrint.hxxPlaylistSave.cxxRemoteTagCache.cxxRemoteTagCache.hxxReplayGainInfo.hxxReplayGainMode.cxxReplayGainMode.hxxSingleMode.cxxSingleMode.hxxSongLoader.hxxSongPrint.cxxSongSave.cxxSongSave.hxxSongUpdate.cxxStateFile.cxxStateFile.hxxStateFileConfig.cxxStats.cxxTagAny.cxxTagPrint.cxxTagSave.cxxTagStream.cxxTimePrint.cxxls.cxxls.hxx
android
apple
archive
client
Client.hxxEvent.cxxExpire.cxxIdle.cxxListener.cxxMessage.hxxNew.cxxProcess.cxxResponse.cxxResponse.hxxThreadBackgroundCommand.cxxThreadBackgroundCommand.hxxWrite.cxx
command
AllCommands.cxxClientCommands.cxxCommandError.cxxDatabaseCommands.cxxFileCommands.cxxFingerprintCommands.cxxMessageCommands.cxxNeighborCommands.cxxNeighborCommands.hxxOtherCommands.cxxOtherCommands.hxxOutputCommands.cxxPartitionCommands.cxxPlayerCommands.cxxPlaylistCommands.cxxPlaylistCommands.hxxPositionArg.cxxPositionArg.hxxQueueCommands.cxxStorageCommands.cxxTagCommands.cxx
config
db
Configured.cxxCount.cxxDatabaseLock.hxxDatabasePlaylist.cxxDatabasePlaylist.hxxDatabasePrint.cxxInterface.hxxLightDirectory.hxxPlaylistInfo.hxxPlaylistVector.hxxRegistry.cxxRegistry.hxxSelection.hxxmeson.build
plugins
ProxyDatabasePlugin.cxxmeson.build
simple
DatabaseSave.cxxDatabaseSave.hxxDirectory.cxxDirectory.hxxDirectorySave.cxxDirectorySave.hxxExportedSong.hxxSimpleDatabasePlugin.cxxSimpleDatabasePlugin.hxxSong.cxxSong.hxx
upnp
update
decoder
Bridge.cxxBridge.hxxClient.hxxControl.cxxControl.hxxDecoderAPI.hxxDecoderBuffer.hxxDecoderList.cxxDecoderList.hxxDecoderPlugin.cxxDecoderPlugin.hxxDecoderPrint.cxxReader.hxxThread.cxxmeson.build
plugins
AdPlugDecoderPlugin.cxxAudiofileDecoderPlugin.cxxDsdLib.hxxFaadDecoderPlugin.cxxFfmpegDecoderPlugin.cxxFfmpegIo.cxxFfmpegMetaData.cxxFlacCommon.cxxFlacCommon.hxxFlacDecoderPlugin.cxxFlacInput.cxxFlacInput.hxxFlacPcm.cxxFlacPcm.hxxGmeDecoderPlugin.cxxHybridDsdDecoderPlugin.cxxMadDecoderPlugin.cxxMikmodDecoderPlugin.cxxModCommon.cxxModCommon.hxxModplugDecoderPlugin.cxxMpg123DecoderPlugin.cxxOpenmptDecoderPlugin.cxxOpenmptDecoderPlugin.hxxOpusDecoderPlugin.cxxOpusTags.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.cxxAllocatedPath.hxxCharset.cxxCharset.hxxCheckFile.cxxFileSystem.hxxGlob.hxxPath.cxxPath.hxxStandardDirectory.cxxStandardDirectory.hxxTraits.cxxTraits.hxx
io
meson.buildinput
AsyncInputStream.cxxAsyncInputStream.hxxBufferingInputStream.cxxError.hxxIcyInputStream.cxxIcyInputStream.hxxInit.cxxInputPlugin.hxxInputStream.cxxInputStream.hxxLastInputStream.cxxLastInputStream.hxxReader.hxxRegistry.cxxRegistry.hxxThreadInputStream.cxx
cache
meson.buildplugins
AlsaInputPlugin.cxxArchiveInputPlugin.cxxCdioParanoiaInputPlugin.cxxCurlInputPlugin.cxxCurlInputPlugin.hxxFfmpegInputPlugin.cxxFileInputPlugin.cxxMmsInputPlugin.cxxNfsInputPlugin.cxxQobuzClient.cxxQobuzClient.hxxQobuzErrorParser.cxxQobuzErrorParser.hxxQobuzInputPlugin.cxxQobuzLoginRequest.cxxQobuzLoginRequest.hxxQobuzTagScanner.cxxQobuzTagScanner.hxxQobuzTrackRequest.cxxQobuzTrackRequest.hxxTidalError.hxxTidalErrorParser.cxxTidalErrorParser.hxxTidalInputPlugin.cxxTidalLoginRequest.cxxTidalLoginRequest.hxxTidalSessionManager.cxxTidalSessionManager.hxxTidalTagScanner.cxxTidalTagScanner.hxxTidalTrackRequest.cxxTidalTrackRequest.hxxUringInputPlugin.cxxmeson.build
io
BufferedOutputStream.cxxBufferedOutputStream.hxxBufferedReader.cxxBufferedReader.hxxFileDescriptor.cxxFileDescriptor.hxxFileOutputStream.cxxFileOutputStream.hxxFileReader.cxxFileReader.hxxLineReader.hxxOpen.cxxOpen.hxxOutputStream.hxxPeekReader.cxxPeekReader.hxxReader.hxxStdioOutputStream.hxxUniqueFileDescriptor.hxxmeson.build
uring
java
Class.hxxException.cxxException.hxxFile.cxxFile.hxxGlobal.cxxGlobal.hxxObject.hxxRef.hxxString.cxxString.hxx
lib
alsa
AllowedFormat.cxxAllowedFormat.hxxError.cxxError.hxxFormat.hxxHwSetup.cxxNonBlock.cxxVersion.cxxVersion.hxxmeson.build
cdio
crypto
curl
Adapter.cxxAdapter.hxxDelegate.cxxDelegate.hxxEasy.hxxEscape.cxxForm.cxxForm.hxxGlobal.cxxGlobal.hxxHandler.hxxHeaders.hxxInit.cxxMulti.hxxRequest.cxxRequest.hxxSetup.cxxSetup.hxxSlist.hxxVersion.hxxmeson.build
patches
dbus
AppendIter.hxxError.hxxFilterHelper.cxxFilterHelper.hxxGlue.cxxGlue.hxxPendingCall.hxxReadIter.hxxTypes.hxxUDisks2.cxxValues.hxxWatch.cxxWatch.hxxmeson.build
expat
ffmpeg
Buffer.hxxChannelLayout.hxxCodec.hxxDetectFilterFormat.cxxDetectFilterFormat.hxxFilter.cxxFilter.hxxIOContext.hxxInterleave.cxxLibFmt.hxxLogCallback.cxxTime.hxxmeson.build
fmt
gcrypt
icu
modplug
patches
nfs
Base.hxxBlocking.hxxCancellable.hxxConnection.cxxConnection.hxxFileReader.hxxGlue.hxxManager.cxxManager.hxxmeson.build
patches
pcre
pipewire
pulse
smbclient
sqlite
systemd
upnp
Action.hxxClientInit.cxxClientInit.hxxCompat.hxxContentDirectoryService.cxxContentDirectoryService.hxxDiscovery.cxxDiscovery.hxxInit.cxxInit.hxxUniqueIxml.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.hxxOutputCommand.cxxOutputCommand.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
AudioCompress
AudioFormat.hxxBuffer.hxxChannelsConverter.hxxDsd2Pcm.cxxDsd2Pcm.hxxExport.hxxFormatConverter.hxxLibsamplerateResampler.cxxPcmFormat.hxxResampler.hxxSampleFormat.hxxSoxrResampler.cxxVolume.cxxVolume.hxxmeson.buildplayer
playlist
PlaylistPlugin.cxxPlaylistPlugin.hxxPlaylistRegistry.cxxPlaylistRegistry.hxxPlaylistSong.cxxPlaylistStream.cxxSongEnumerator.hxx
cue
plugins
protocol
queue
IdTable.hxxPlaylist.cxxPlaylist.hxxPlaylistControl.cxxPlaylistEdit.cxxPlaylistState.cxxPlaylistState.hxxQueue.cxxQueuePrint.cxxQueueSave.cxxQueueSave.hxx
song
AndSongFilter.hxxDetachedSong.cxxDetachedSong.hxxFilter.cxxFilter.hxxISongFilter.hxxLightSong.cxxLightSong.hxxStringFilter.hxx
sticker
storage
CompositeStorage.cxxCompositeStorage.hxxConfigured.hxxRegistry.cxxRegistry.hxxStorageInterface.hxxStoragePlugin.cxxStoragePlugin.hxxStorageState.cxxStorageState.hxxmeson.build
plugins
system
Clock.hxxError.hxxEventFD.hxxEventPipe.cxxEventPipe.hxxFatalError.cxxKernelVersion.cxxKernelVersion.hxxmeson.build
tag
ApeLoader.cxxBuilder.cxxBuilder.hxxFixString.cxxFixString.hxxFormat.cxxFormat.hxxIcyMetaDataParser.cxxIcyMetaDataParser.hxxId3Load.cxxId3MixRamp.cxxId3MixRamp.hxxId3Picture.cxxId3Scan.cxxId3String.hxxMask.hxxMixRampInfo.hxxMixRampParser.cxxMixRampParser.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.hxxLazyRandomEngine.cxxLazyRandomEngine.hxxMimeType.cxxMimeType.hxxOffsetPointer.hxxOptionParser.hxxRuntimeError.hxxScopeExit.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
systemd
test
ContainerScan.cxxDumpOgg.cxxRunCurl.cxxRunZeroconf.cxxShutdownHandler.cxxTestMimeType.cxxTestUriExtract.cxxWriteFile.cxxdump_playlist.cxxdump_text_file.cxxmeson.build
net
run_decoder.cxxrun_encoder.cxxrun_gunzip.cxxrun_gzip.cxxrun_input.cxxrun_normalize.cxxtag
test_icy_parser.cxxtest_translate_song.cxxtest_vorbis_encoder.cxxtime
util
win32
12
.github/FUNDING.yml
vendored
12
.github/FUNDING.yml
vendored
@ -1,12 +0,0 @@
|
||||
# These are supported funding model platforms
|
||||
|
||||
github: # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2]
|
||||
patreon: # Replace with a single Patreon username
|
||||
open_collective: # Replace with a single Open Collective username
|
||||
ko_fi: # Replace with a single Ko-fi username
|
||||
tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel
|
||||
community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry
|
||||
liberapay: MaxK
|
||||
issuehunt: # Replace with a single IssueHunt username
|
||||
otechie: # Replace with a single Otechie username
|
||||
custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2']
|
4
.github/ISSUE_TEMPLATE/bug_report.md
vendored
4
.github/ISSUE_TEMPLATE/bug_report.md
vendored
@ -18,5 +18,9 @@ about: Create a bug report
|
||||
<!-- Paste the output of "mpd --version" here -->
|
||||
|
||||
|
||||
## Configuration
|
||||
<!-- Paste your MPD configuration here -->
|
||||
|
||||
|
||||
## Log
|
||||
<!-- Paste relevant portions of the log file here (--verbose) -->
|
||||
|
9
.github/ISSUE_TEMPLATE/question.md
vendored
9
.github/ISSUE_TEMPLATE/question.md
vendored
@ -1,9 +0,0 @@
|
||||
---
|
||||
name: Question
|
||||
about: Ask a question about MPD
|
||||
---
|
||||
|
||||
<!-- Before you ask a question on GitHub, please read MPD's
|
||||
documentation. A copy is available at
|
||||
https://www.musicpd.org/doc/html/ -->
|
||||
## Question
|
155
.github/workflows/build.yml
vendored
Normal file
155
.github/workflows/build.yml
vendored
Normal file
@ -0,0 +1,155 @@
|
||||
---
|
||||
on:
|
||||
workflow_dispatch:
|
||||
push:
|
||||
paths-ignore:
|
||||
- 'android/**'
|
||||
- 'build/**'
|
||||
- 'doc/**'
|
||||
- 'python/**'
|
||||
- 'subprojects/**'
|
||||
- 'systemd/**'
|
||||
- 'win32/**'
|
||||
branches:
|
||||
- master
|
||||
- v0.23.x
|
||||
pull_request:
|
||||
paths-ignore:
|
||||
- 'android/**'
|
||||
- 'build/**'
|
||||
- 'doc/**'
|
||||
- 'python/**'
|
||||
- 'subprojects/**'
|
||||
- 'systemd/**'
|
||||
- 'win32/**'
|
||||
branches:
|
||||
- master
|
||||
- v0.23.x
|
||||
|
||||
jobs:
|
||||
build-linux:
|
||||
runs-on: ubuntu-latest
|
||||
env:
|
||||
CC: 'ccache gcc-10'
|
||||
CXX: 'ccache g++-10'
|
||||
steps:
|
||||
- id: checkout
|
||||
uses: actions/checkout@v2
|
||||
- id: cache-ccache
|
||||
uses: hendrikmuhs/ccache-action@v1
|
||||
with:
|
||||
key: linux
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
sudo apt-get update
|
||||
sudo apt-get install -y --no-install-recommends \
|
||||
g++-10 libfmt-dev libboost-dev \
|
||||
libgtest-dev \
|
||||
libpcre2-dev \
|
||||
libsystemd-dev libdbus-1-dev \
|
||||
libicu-dev \
|
||||
libcurl4-gnutls-dev \
|
||||
libpcre2-dev \
|
||||
libavahi-client-dev \
|
||||
libmad0-dev libmpg123-dev libid3tag0-dev \
|
||||
libflac-dev libvorbis-dev libopus-dev libogg-dev \
|
||||
libadplug-dev libaudiofile-dev libsndfile1-dev libfaad-dev \
|
||||
libfluidsynth-dev libgme-dev libmikmod-dev libmodplug-dev \
|
||||
libmpcdec-dev libwavpack-dev libwildmidi-dev \
|
||||
libsidplay2-dev libsidutils-dev libresid-builder-dev \
|
||||
libavcodec-dev libavformat-dev \
|
||||
libmp3lame-dev libtwolame-dev libshine-dev \
|
||||
libsamplerate0-dev libsoxr-dev \
|
||||
libbz2-dev libcdio-paranoia-dev libiso9660-dev libmms-dev \
|
||||
libzzip-dev \
|
||||
libyajl-dev libexpat-dev \
|
||||
libasound2-dev libao-dev libjack-jackd2-dev libopenal-dev \
|
||||
libpulse-dev libshout3-dev \
|
||||
libsndio-dev \
|
||||
libmpdclient-dev \
|
||||
libnfs-dev \
|
||||
libupnp-dev \
|
||||
libsqlite3-dev \
|
||||
libchromaprint-dev \
|
||||
libgcrypt20-dev
|
||||
|
||||
- name: Full Build
|
||||
uses: BSFishy/meson-build@v1.0.3
|
||||
with:
|
||||
action: build
|
||||
directory: output/full
|
||||
setup-options: -Ddocumentation=disabled -Dtest=true -Dsystemd=enabled -Dpcre=enabled
|
||||
options: --verbose
|
||||
meson-version: 0.56.0
|
||||
|
||||
- name: Unit Tests
|
||||
uses: BSFishy/meson-build@v1.0.3
|
||||
with:
|
||||
action: test
|
||||
directory: output/full
|
||||
setup-options: -Ddocumentation=disabled -Dtest=true -Dsystemd=enabled -Dpcre=enabled
|
||||
options: --verbose
|
||||
meson-version: 0.56.0
|
||||
|
||||
- name: Mini Build
|
||||
uses: BSFishy/meson-build@v1.0.3
|
||||
with:
|
||||
action: build
|
||||
directory: output/mini
|
||||
setup-options: -Dbuildtype=minsize -Dauto_features=disabled -Dtest=true -Ddaemon=false -Dinotify=false -Depoll=false -Deventfd=false -Dsignalfd=false -Dtcp=false -Ddsd=false -Ddatabase=false -Dneighbor=false -Dcue=false -Dfifo=false -Dhttpd=false -Dpipe=false -Drecorder=false -Dsnapcast=false
|
||||
options: --verbose
|
||||
meson-version: 0.56.0
|
||||
|
||||
build-macos:
|
||||
runs-on: macos-latest
|
||||
steps:
|
||||
- id: checkout
|
||||
uses: actions/checkout@v2
|
||||
|
||||
- id: cache-ccache
|
||||
uses: hendrikmuhs/ccache-action@v1
|
||||
with:
|
||||
key: macos
|
||||
|
||||
- uses: actions/setup-python@v1
|
||||
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
brew install \
|
||||
meson ninja \
|
||||
fmt \
|
||||
boost \
|
||||
googletest \
|
||||
icu4c \
|
||||
ffmpeg \
|
||||
libnfs \
|
||||
yajl \
|
||||
libupnp \
|
||||
libid3tag \
|
||||
chromaprint \
|
||||
libsamplerate \
|
||||
libsoxr \
|
||||
flac \
|
||||
opus \
|
||||
libvorbis \
|
||||
faad2 \
|
||||
wavpack \
|
||||
libmpdclient
|
||||
|
||||
- name: Build
|
||||
uses: BSFishy/meson-build@v1.0.3
|
||||
with:
|
||||
action: build
|
||||
directory: output
|
||||
setup-options: -Ddocumentation=disabled -Dtest=true
|
||||
options: --verbose
|
||||
meson-version: 0.56.0
|
||||
|
||||
- name: Unit Tests
|
||||
uses: BSFishy/meson-build@v1.0.3
|
||||
with:
|
||||
action: test
|
||||
directory: output
|
||||
setup-options: -Ddocumentation=disabled -Dtest=true
|
||||
options: --verbose
|
||||
meson-version: 0.56.0
|
@ -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
|
||||
|
263
NEWS
263
NEWS
@ -1,3 +1,266 @@
|
||||
ver 0.23.14 (2023/10/08)
|
||||
* decoder
|
||||
- flac: fix scanning files with non-ASCII names on Windows
|
||||
- mad: fix calculation of LAME peak values
|
||||
* mixer
|
||||
- wasapi: fix problem setting volume
|
||||
* more libfmt 10 fixes
|
||||
* fix auto-detected systemd unit directory
|
||||
* Android
|
||||
- require Android 7 or newer
|
||||
|
||||
ver 0.23.13 (2023/05/22)
|
||||
* input
|
||||
- curl: fix busy loop after connection failed
|
||||
- curl: hide "404" log messages for non-existent ".mpdignore" files
|
||||
* archive
|
||||
- zzip: fix crash bug
|
||||
* database
|
||||
- simple: reveal hidden songs after deleting containing CUE
|
||||
* decoder
|
||||
- ffmpeg: reorder to a lower priority than "gme"
|
||||
- gme: require GME 0.6 or later
|
||||
* output
|
||||
- pipewire: fix corruption bug due to missing lock
|
||||
* Linux
|
||||
- shut down if parent process dies in --no-daemon mode
|
||||
- determine systemd unit directories via pkg-config
|
||||
* support libfmt 10
|
||||
|
||||
ver 0.23.12 (2023/01/17)
|
||||
* input
|
||||
- curl: require CURL 7.55.0 or later
|
||||
* decoder
|
||||
- mad: fix integer underflow with very small files
|
||||
* tags
|
||||
- fix crash bug due to race condition
|
||||
* output
|
||||
- pipewire: adjust to PipeWire 0.3.64 API change
|
||||
* fix build failures with GCC 13
|
||||
|
||||
ver 0.23.11 (2022/11/28)
|
||||
* database
|
||||
- simple: move default database to ~/.cache/mpd/db from ~/.cache/mpd.db
|
||||
- simple: default "cache_directory" to ~/.cache/mpd/mounts
|
||||
* macOS: fix build failure "no archive members specified"
|
||||
* Windows
|
||||
- fix crash bug (stack buffer overflow) after I/O errors
|
||||
- fix path traversal bug because backslash was allowed in playlist names
|
||||
* Android/Windows
|
||||
- update OpenSSL to 3.0.7
|
||||
- re-enable CURL's verbose error strings
|
||||
|
||||
ver 0.23.10 (2022/10/14)
|
||||
* storage
|
||||
- curl: fix file time stamps
|
||||
* decoder
|
||||
- ffmpeg: fix libfmt 9 compiler warning
|
||||
* encoder
|
||||
- flac: fix failure when libFLAC is built without Ogg support
|
||||
* output
|
||||
- alsa: fix crash bug
|
||||
* Windows
|
||||
- log to stdout by default, don't require "log_file" setting
|
||||
|
||||
ver 0.23.9 (2022/08/18)
|
||||
* input
|
||||
- cdio_paranoia: add options "mode" and "skip"
|
||||
* decoder
|
||||
- ffmpeg: support FFmpeg 5.1
|
||||
* filter
|
||||
- replay gain: fix delayed volume display with handler=mixer
|
||||
* output
|
||||
- pipewire: set app icon
|
||||
* fix bogus volume levels with multiple partitions
|
||||
* improve iconv detection
|
||||
* macOS: fix macOS 10 build problem (0.23.8 regression)
|
||||
* Android
|
||||
- load mpd.conf from app data directory
|
||||
|
||||
ver 0.23.8 (2022/07/09)
|
||||
* storage
|
||||
- curl: fix crash if web server does not understand WebDAV
|
||||
* input
|
||||
- cdio_paranoia: fix crash if no drive was found
|
||||
- cdio_paranoia: faster cancellation
|
||||
- cdio_paranoia: don't scan for replay gain tags
|
||||
- pipewire: fix playback of very short tracks
|
||||
- pipewire: drop all buffers before manual song change
|
||||
- pipewire: fix stuttering after manual song change
|
||||
- snapcast: fix busy loop while paused
|
||||
- snapcast: fix stuttering after resuming playback
|
||||
* mixer
|
||||
- better error messages
|
||||
- alsa: fix setting volume before playback starts
|
||||
- pipewire: fix crash bug
|
||||
- pipewire: fix volume change events with PipeWire 0.3.53
|
||||
- pipewire: don't force initial volume=100%
|
||||
* support libfmt 9
|
||||
|
||||
ver 0.23.7 (2022/05/09)
|
||||
* database
|
||||
- upnp: support pupnp 1.14
|
||||
* decoder
|
||||
- ffmpeg: fix HLS seeking
|
||||
- opus: fix missing song length on high-latency files
|
||||
* output
|
||||
- shout: require at least libshout 2.4.0
|
||||
* mixer
|
||||
- pipewire: fix volume restore
|
||||
- software: update volume of disabled outputs
|
||||
* support libiconv
|
||||
|
||||
ver 0.23.6 (2022/03/14)
|
||||
* protocol
|
||||
- support filename "cover.webp" for "albumart" command
|
||||
- support "readcomments" and "readpicture" on CUE tracks
|
||||
* decoder
|
||||
- ffmpeg: fix end-of-file check (update stuck at empty files)
|
||||
- opus: fix "readpicture" on Opus files
|
||||
* output
|
||||
- pipewire: fix crash bug if setting volume before playback starts
|
||||
- wasapi: fix resume after pause
|
||||
|
||||
ver 0.23.5 (2021/12/01)
|
||||
* protocol
|
||||
- support relative offsets for "searchadd"
|
||||
- fix "searchaddpl" bug (bogus error "Bad position")
|
||||
* database
|
||||
- upnp: fix crash bug
|
||||
* tags
|
||||
- fix MixRamp support
|
||||
* migrate to PCRE2
|
||||
* GCC 12 build fixes
|
||||
|
||||
ver 0.23.4 (2021/11/11)
|
||||
* protocol
|
||||
- add optional position parameter to "searchaddpl"
|
||||
* decoder
|
||||
- ffmpeg: support libavcodec 59
|
||||
* output
|
||||
- alsa: add option "thesycon_dsd_workaround" to work around device bug
|
||||
* fix crash on debug builds if startup fails
|
||||
* systemd
|
||||
- remove "RuntimeDirectory" directive because it caused problems
|
||||
- ignore the "pid_file" setting if started as systemd service
|
||||
* Windows
|
||||
- enable the "openmpt" decoder plugin
|
||||
|
||||
ver 0.23.3 (2021/10/31)
|
||||
* protocol
|
||||
- add optional position parameter to "add" and "playlistadd"
|
||||
- allow range in "playlistdelete"
|
||||
* database
|
||||
- fix scanning files with question mark in the name
|
||||
- inotify: fix use-after-free bug
|
||||
* output
|
||||
- alsa: add option "stop_dsd_silence" to work around DSD DAC noise
|
||||
* macOS: fix libfmt related build failure
|
||||
* systemd: add "RuntimeDirectory" directive
|
||||
|
||||
ver 0.23.2 (2021/10/22)
|
||||
* protocol
|
||||
- fix "albumart" timeout bug
|
||||
* input
|
||||
- nfs: fix playback bug
|
||||
* output
|
||||
- pipewire: send artist and title to PipeWire
|
||||
- pipewire: DSD support
|
||||
* neighbor
|
||||
- mention failed plugin name in error message
|
||||
* player
|
||||
- fix cross-fade regression
|
||||
* fix crash with libfmt versions older than 7
|
||||
|
||||
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)
|
||||
|
||||
ver 0.22.7 (2021/05/19)
|
||||
* protocol
|
||||
- don't use glibc extension to parse time stamps
|
||||
|
@ -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,10 +2,10 @@
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
package="org.musicpd"
|
||||
android:installLocation="auto"
|
||||
android:versionCode="55"
|
||||
android:versionName="0.22.7">
|
||||
android:versionCode="72"
|
||||
android:versionName="0.23.14">
|
||||
|
||||
<uses-sdk android:minSdkVersion="21" android:targetSdkVersion="29"/>
|
||||
<uses-sdk android:minSdkVersion="24" android:targetSdkVersion="30"/>
|
||||
|
||||
<uses-feature android:name="android.software.leanback"
|
||||
android:required="false" />
|
||||
@ -19,6 +19,7 @@
|
||||
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
|
||||
|
||||
<application android:allowBackup="true"
|
||||
android:debuggable="true"
|
||||
android:requestLegacyExternalStorage="true"
|
||||
android:icon="@drawable/icon"
|
||||
android:banner="@drawable/icon"
|
||||
|
@ -12,18 +12,30 @@ unsigned_apk = custom_target(
|
||||
],
|
||||
)
|
||||
|
||||
aligned_apk = custom_target(
|
||||
'mpd-aligned.apk',
|
||||
output: 'mpd-aligned.apk',
|
||||
input: unsigned_apk,
|
||||
command: [
|
||||
android_zipalign,
|
||||
'-f', '4',
|
||||
'@INPUT@', '@OUTPUT@',
|
||||
],
|
||||
)
|
||||
|
||||
if get_option('android_debug_keystore') != ''
|
||||
debug_apk = custom_target(
|
||||
'mpd-debug.apk',
|
||||
output: 'mpd-debug.apk',
|
||||
input: unsigned_apk,
|
||||
input: aligned_apk,
|
||||
command: [
|
||||
jarsigner,
|
||||
'-keystore', get_option('android_debug_keystore'),
|
||||
'-storepass', 'android',
|
||||
'-signedjar', '@OUTPUT@',
|
||||
'@INPUT@',
|
||||
'androiddebugkey',
|
||||
apksigner, 'sign',
|
||||
'--in', '@INPUT@',
|
||||
'--out', '@OUTPUT@',
|
||||
'--debuggable-apk-permitted',
|
||||
'-ks', get_option('android_debug_keystore'),
|
||||
'--ks-key-alias', 'androiddebugkey',
|
||||
'--ks-pass', 'pass:android',
|
||||
],
|
||||
build_by_default: true
|
||||
)
|
||||
@ -31,29 +43,16 @@ endif
|
||||
|
||||
if get_option('android_keystore') != '' and get_option('android_keyalias') != '' and get_option('android_keypass') != ''
|
||||
unaligned_apk = custom_target(
|
||||
'mpd-unaligned.apk',
|
||||
output: 'mpd-unaligned.apk',
|
||||
input: unsigned_apk,
|
||||
command: [
|
||||
jarsigner,
|
||||
'-digestalg', 'SHA1', '-sigalg', 'MD5withRSA',
|
||||
'-keystore', get_option('android_keystore'),
|
||||
'-storepass', get_option('android_keypass'),
|
||||
'-signedjar', '@OUTPUT@',
|
||||
'@INPUT@',
|
||||
get_option('android_keyalias'),
|
||||
],
|
||||
)
|
||||
|
||||
apk = custom_target(
|
||||
'mpd.apk',
|
||||
output: 'mpd.apk',
|
||||
input: unaligned_apk,
|
||||
input: aligned_apk,
|
||||
command: [
|
||||
android_zipalign,
|
||||
'-f', '4',
|
||||
'@INPUT@', '@OUTPUT@',
|
||||
apksigner, 'sign',
|
||||
'--in', '@INPUT@',
|
||||
'--out', '@OUTPUT@',
|
||||
'-ks', get_option('android_keystore'),
|
||||
'--ks-key-alias', get_option('android_keyalias'),
|
||||
'--ks-pass', 'pass:' + get_option('android_keypass'),
|
||||
],
|
||||
build_by_default: true
|
||||
)
|
||||
endif
|
||||
|
147
android/build.py
147
android/build.py
@ -1,4 +1,4 @@
|
||||
#!/usr/bin/env python3
|
||||
#!/usr/bin/env -S python3 -u
|
||||
|
||||
import os, os.path
|
||||
import sys, subprocess
|
||||
@ -13,158 +13,26 @@ android_abi = sys.argv[3]
|
||||
configure_args = sys.argv[4:]
|
||||
|
||||
if not os.path.isfile(os.path.join(sdk_path, 'tools', 'android')):
|
||||
print("SDK not found in", ndk_path, file=sys.stderr)
|
||||
print("SDK not found in", sdk_path, file=sys.stderr)
|
||||
sys.exit(1)
|
||||
|
||||
if not os.path.isdir(ndk_path):
|
||||
print("NDK not found in", ndk_path, file=sys.stderr)
|
||||
sys.exit(1)
|
||||
|
||||
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',
|
||||
},
|
||||
|
||||
'arm64-v8a': {
|
||||
'arch': 'aarch64-linux-android',
|
||||
'ndk_arch': 'arm64',
|
||||
'toolchain_arch': 'aarch64-linux-android',
|
||||
'llvm_triple': 'aarch64-linux-android',
|
||||
'cflags': '-fpic',
|
||||
},
|
||||
|
||||
'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',
|
||||
},
|
||||
|
||||
'x86_64': {
|
||||
'arch': 'x86_64-linux-android',
|
||||
'ndk_arch': 'x86_64',
|
||||
'toolchain_arch': 'x86_64',
|
||||
'llvm_triple': 'x86_64-linux-android',
|
||||
'cflags': '-fPIC -m64',
|
||||
},
|
||||
}
|
||||
|
||||
# select the NDK target
|
||||
abi_info = android_abis[android_abi]
|
||||
arch = abi_info['arch']
|
||||
|
||||
# the path to the MPD sources
|
||||
mpd_path = os.path.abspath(os.path.join(os.path.dirname(sys.argv[0]) or '.', '..'))
|
||||
sys.path[0] = os.path.join(mpd_path, 'python')
|
||||
|
||||
# output directories
|
||||
from build.dirs import lib_path, tarball_path, src_path
|
||||
from build.meson import configure as run_meson
|
||||
|
||||
arch_path = os.path.join(lib_path, arch)
|
||||
build_path = os.path.join(arch_path, 'build')
|
||||
|
||||
# build host configuration
|
||||
build_arch = 'linux-x86_64'
|
||||
|
||||
# set up the NDK toolchain
|
||||
|
||||
class AndroidNdkToolchain:
|
||||
def __init__(self, tarball_path, src_path, build_path,
|
||||
use_cxx):
|
||||
self.tarball_path = tarball_path
|
||||
self.src_path = src_path
|
||||
self.build_path = build_path
|
||||
|
||||
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.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 += ' -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.cflags = common_flags
|
||||
self.cxxflags = common_flags
|
||||
self.cppflags = ' -isystem ' + os.path.join(install_prefix, 'include')
|
||||
self.ldflags = ' -L' + os.path.join(install_prefix, 'lib') + \
|
||||
' -Wl,--exclude-libs=ALL' + \
|
||||
' ' + common_flags
|
||||
self.ldflags = common_flags
|
||||
self.libs = ''
|
||||
|
||||
self.is_arm = ndk_arch == 'arm'
|
||||
self.is_armv7 = self.is_arm and 'armv7' in self.cflags
|
||||
self.is_aarch64 = ndk_arch == 'arm64'
|
||||
self.is_windows = False
|
||||
|
||||
libstdcxx_flags = ''
|
||||
libstdcxx_cxxflags = ''
|
||||
libstdcxx_ldflags = ''
|
||||
libstdcxx_libs = '-static-libstdc++'
|
||||
|
||||
if self.is_armv7:
|
||||
# On 32 bit ARM, clang generates no ".eh_frame" section;
|
||||
# instead, the LLVM unwinder library is used for unwinding
|
||||
# the stack after a C++ exception was thrown
|
||||
libstdcxx_libs += ' -lunwind'
|
||||
|
||||
if use_cxx:
|
||||
self.cxxflags += ' ' + libstdcxx_cxxflags
|
||||
self.ldflags += ' ' + libstdcxx_ldflags
|
||||
self.libs += ' ' + libstdcxx_libs
|
||||
|
||||
self.env = dict(os.environ)
|
||||
|
||||
# redirect pkg-config to use our root directory instead of the
|
||||
# default one on the build host
|
||||
import shutil
|
||||
bin_dir = os.path.join(install_prefix, 'bin')
|
||||
try:
|
||||
os.makedirs(bin_dir)
|
||||
except:
|
||||
pass
|
||||
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
|
||||
from build.toolchain import AndroidNdkToolchain
|
||||
|
||||
# a list of third-party libraries to be used by MPD on Android
|
||||
from build.libs import *
|
||||
thirdparty_libs = [
|
||||
libmpdclient,
|
||||
libogg,
|
||||
libvorbis,
|
||||
opus,
|
||||
flac,
|
||||
libid3tag,
|
||||
@ -174,20 +42,23 @@ thirdparty_libs = [
|
||||
ffmpeg,
|
||||
openssl,
|
||||
curl,
|
||||
libexpat,
|
||||
libnfs,
|
||||
boost,
|
||||
]
|
||||
|
||||
# build the third-party libraries
|
||||
for x in thirdparty_libs:
|
||||
toolchain = AndroidNdkToolchain(tarball_path, src_path, build_path,
|
||||
toolchain = AndroidNdkToolchain(mpd_path, lib_path,
|
||||
tarball_path, src_path,
|
||||
ndk_path, android_abi,
|
||||
use_cxx=x.use_cxx)
|
||||
if not x.is_installed(toolchain):
|
||||
x.build(toolchain)
|
||||
|
||||
# configure and build MPD
|
||||
toolchain = AndroidNdkToolchain(tarball_path, src_path, build_path,
|
||||
toolchain = AndroidNdkToolchain(mpd_path, lib_path,
|
||||
tarball_path, src_path,
|
||||
ndk_path, android_abi,
|
||||
use_cxx=True)
|
||||
|
||||
configure_args += [
|
||||
|
54
android/gdb.sh
Executable file
54
android/gdb.sh
Executable file
@ -0,0 +1,54 @@
|
||||
#!/bin/sh
|
||||
|
||||
# This script need the following modification in ANDROID_NDK in order to attach
|
||||
# to the good :main pid
|
||||
#--- a/prebuilt/linux-x86_64/bin/ndk-gdb.py
|
||||
#+++ b/prebuilt/linux-x86_64/bin/ndk-gdb.py
|
||||
#@@ -669,7 +669,7 @@
|
||||
# log("Sleeping for {} seconds.".format(args.delay))
|
||||
# time.sleep(args.delay)
|
||||
#
|
||||
#- pids = gdbrunner.get_pids(device, pkg_name)
|
||||
#+ pids = gdbrunner.get_pids(device, pkg_name + ":main")
|
||||
# if len(pids) == 0:
|
||||
# error("Failed to find running process '{}'".format(pkg_name))
|
||||
# if len(pids) > 1:
|
||||
|
||||
SCRIPT_PATH=$(dirname $0)
|
||||
BUILD_PATH="`pwd`"
|
||||
TMP_PATH="$BUILD_PATH/gdb"
|
||||
NDK_GDB_ARGS="--force"
|
||||
ANDROID_NDK=$1
|
||||
|
||||
if [ ! -f $ANDROID_NDK/source.properties ];then
|
||||
echo "usage: $0 ANDROID_NDK"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [ ! -f $BUILD_PATH/libmpd.so ];then
|
||||
echo "This script need to be executed from the android build directory"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
rm -rf "$TMP_PATH"
|
||||
mkdir -p "$TMP_PATH"
|
||||
|
||||
ANDROID_MANIFEST="$SCRIPT_PATH/AndroidManifest.xml"
|
||||
ABI=`ls "$BUILD_PATH/android/apk/apk/lib" --sort=time | head -n 1`
|
||||
|
||||
if [ ! -f "$ANDROID_MANIFEST" -o "$ABI" = "" ]; then
|
||||
echo "Invalid manifest/ABI, did you try building first ?"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
mkdir -p "$TMP_PATH"/jni
|
||||
touch "$TMP_PATH"/jni/Android.mk
|
||||
echo "APP_ABI := $ABI" > "$TMP_PATH"/jni/Application.mk
|
||||
|
||||
DEST=obj/local/$ABI
|
||||
mkdir -p "$TMP_PATH/$DEST"
|
||||
|
||||
cp "$BUILD_PATH/libmpd.so" "$TMP_PATH/$DEST"
|
||||
cp "$ANDROID_MANIFEST" "$TMP_PATH"
|
||||
|
||||
(cd "$TMP_PATH" && bash $ANDROID_NDK/ndk-gdb $NDK_GDB_ARGS)
|
@ -17,7 +17,7 @@ android_dx = join_paths(android_build_tools_dir, 'dx')
|
||||
android_zipalign = join_paths(android_build_tools_dir, 'zipalign')
|
||||
|
||||
javac = find_program('javac')
|
||||
jarsigner = find_program('jarsigner')
|
||||
apksigner = find_program('apksigner')
|
||||
rsvg_convert = find_program('rsvg-convert')
|
||||
convert = find_program('convert')
|
||||
zip = find_program('zip')
|
||||
|
@ -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>
|
||||
|
@ -13,7 +13,7 @@ GENCLASS="$D/classes"
|
||||
GENINCLUDE="$D/include"
|
||||
|
||||
mkdir -p "$GENSRC/$JAVA_PKG_PATH"
|
||||
"$JAVAC" -source 1.6 -target 1.6 -Xlint:-options \
|
||||
"$JAVAC" -source 1.7 -target 1.7 -Xlint:-options \
|
||||
-cp "$CLASSPATH" \
|
||||
-h "$GENINCLUDE" \
|
||||
-d "$GENCLASS" \
|
||||
|
@ -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,10 +24,13 @@ import android.app.Notification;
|
||||
import android.app.NotificationManager;
|
||||
import android.app.PendingIntent;
|
||||
import android.app.Service;
|
||||
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.media.AudioManager;
|
||||
import android.os.Build;
|
||||
import android.os.IBinder;
|
||||
import android.os.PowerManager;
|
||||
@ -55,6 +58,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 +72,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 +198,18 @@ public class Main extends Service implements Runnable {
|
||||
if (mThread != null)
|
||||
return;
|
||||
|
||||
IntentFilter filter = new IntentFilter();
|
||||
filter.addAction(AudioManager.ACTION_AUDIO_BECOMING_NOISY);
|
||||
registerReceiver(new BroadcastReceiver() {
|
||||
@Override
|
||||
public void onReceive(Context context, Intent intent) {
|
||||
if (!mPauseOnHeadphonesDisconnect)
|
||||
return;
|
||||
if (intent.getAction() == AudioManager.ACTION_AUDIO_BECOMING_NOISY)
|
||||
pause();
|
||||
}
|
||||
}, filter);
|
||||
|
||||
final Intent mainIntent = new Intent(this, Settings.class);
|
||||
mainIntent.setAction("android.intent.action.MAIN");
|
||||
mainIntent.addCategory("android.intent.category.LAUNCHER");
|
||||
@ -241,6 +260,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 +402,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 +461,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);
|
||||
}
|
||||
}
|
||||
|
@ -25,16 +25,18 @@ import android.content.Intent;
|
||||
import android.util.Log;
|
||||
|
||||
public class Receiver extends BroadcastReceiver {
|
||||
@Override
|
||||
public void onReceive(Context context, Intent intent) {
|
||||
Log.d("Receiver", "onReceive: " + intent);
|
||||
if (intent.getAction() == "android.intent.action.BOOT_COMPLETED") {
|
||||
if (Settings.Preferences.getBoolean(context,
|
||||
Settings.Preferences.KEY_RUN_ON_BOOT, false)) {
|
||||
final boolean wakelock = Settings.Preferences.getBoolean(context,
|
||||
Settings.Preferences.KEY_WAKELOCK, false);
|
||||
Main.start(context, wakelock);
|
||||
}
|
||||
}
|
||||
}
|
||||
@Override
|
||||
public void onReceive(Context context, Intent intent) {
|
||||
Log.d("Receiver", "onReceive: " + intent);
|
||||
if (intent.getAction() == "android.intent.action.BOOT_COMPLETED") {
|
||||
if (Settings.Preferences.getBoolean(context,
|
||||
Settings.Preferences.KEY_RUN_ON_BOOT,
|
||||
false)) {
|
||||
final boolean wakelock =
|
||||
Settings.Preferences.getBoolean(context,
|
||||
Settings.Preferences.KEY_WAKELOCK, false);
|
||||
Main.start(context, wakelock);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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.
|
||||
|
12
doc/conf.py
12
doc/conf.py
@ -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,16 +38,19 @@ author = 'Max Kellermann'
|
||||
# built documents.
|
||||
#
|
||||
# The short X.Y version.
|
||||
version = '0.22.7'
|
||||
with open('../meson.build') as f:
|
||||
import re
|
||||
version = re.match(r"project\([^\)]*\bversion:\s*'([^']+)'",
|
||||
f.read(4096)).group(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.
|
||||
#
|
||||
# This is also used if you do content translation via gettext catalogs.
|
||||
# Usually you set "language" from the command line for these cases.
|
||||
language = None
|
||||
language = "en"
|
||||
|
||||
# There are two options for replacing |today|: either, you set today to some
|
||||
# non-false value, then it is used:
|
||||
@ -107,6 +110,7 @@ html_theme = 'classic'
|
||||
# documentation.
|
||||
#
|
||||
# html_theme_options = {}
|
||||
html_theme_options = {"sidebarwidth": "300px"}
|
||||
|
||||
# Add any paths that contain custom themes here, relative to this directory.
|
||||
# html_theme_path = []
|
||||
|
@ -8,8 +8,15 @@ Music Player Daemon
|
||||
user
|
||||
plugins
|
||||
developer
|
||||
client
|
||||
protocol
|
||||
|
||||
.. toctree::
|
||||
:maxdepth: 1
|
||||
:caption: man pages:
|
||||
|
||||
mpd.1
|
||||
mpd.conf.5
|
||||
|
||||
Indices and tables
|
||||
==================
|
||||
|
@ -14,6 +14,7 @@ if get_option('html_manual')
|
||||
input: [
|
||||
'index.rst', 'user.rst', 'developer.rst',
|
||||
'plugins.rst',
|
||||
'client.rst',
|
||||
'protocol.rst',
|
||||
'conf.py',
|
||||
],
|
||||
|
@ -128,23 +128,6 @@ audio_output
|
||||
no audio_output section is specified, then MPD will scan for a usable audio
|
||||
output.
|
||||
|
||||
replaygain <off or album or track or auto>
|
||||
If specified, mpd will adjust the volume of songs played using ReplayGain
|
||||
tags (see https://wiki.hydrogenaud.io/index.php?title=Replaygain).
|
||||
Setting this to "album" will
|
||||
adjust volume using the album's ReplayGain tags, while setting it to "track"
|
||||
will adjust it using the track ReplayGain tags. "auto" uses the track
|
||||
ReplayGain tags if random play is activated otherwise the album ReplayGain
|
||||
tags. Currently only FLAC, Ogg Vorbis, Musepack, and MP3 (through ID3v2
|
||||
ReplayGain tags, not APEv2) are supported.
|
||||
|
||||
replaygain_preamp <-15 to 15>
|
||||
This is the gain (in dB) applied to songs with ReplayGain tags.
|
||||
|
||||
volume_normalization <yes or no>
|
||||
If yes, mpd will normalize the volume of songs as they play. The default is
|
||||
no.
|
||||
|
||||
filesystem_charset <charset>
|
||||
This specifies the character set used for the filesystem. A list of supported
|
||||
character sets can be obtained by running "iconv -l". The default is
|
||||
|
@ -26,22 +26,25 @@
|
||||
# files over an accepted protocol.
|
||||
#
|
||||
#db_file "~/.mpd/database"
|
||||
#
|
||||
|
||||
# These settings are the locations for the daemon log files for the daemon.
|
||||
# These logs are great for troubleshooting, depending on your log_level
|
||||
# settings.
|
||||
#
|
||||
# The special value "syslog" makes MPD use the local syslog daemon. This
|
||||
# setting defaults to logging to syslog.
|
||||
#
|
||||
#log_file "~/.mpd/log"
|
||||
# If you use systemd, do not configure a log_file. With systemd, MPD
|
||||
# defaults to the systemd journal, which is fine.
|
||||
#
|
||||
#log_file "~/.mpd/log"
|
||||
|
||||
# This setting sets the location of the file which stores the process ID
|
||||
# for use of mpd --kill and some init scripts. This setting is disabled by
|
||||
# default and the pid file will not be stored.
|
||||
#
|
||||
#pid_file "~/.mpd/pid"
|
||||
# If you use systemd, do not configure a pid_file.
|
||||
#
|
||||
#pid_file "~/.mpd/pid"
|
||||
|
||||
# This setting sets the location of the file which contains information about
|
||||
# most variables to get MPD back into the same general shape it was in before
|
||||
# it was brought down. This setting is disabled by default and the server
|
||||
@ -76,7 +79,7 @@
|
||||
# This setting sets the address for the daemon to listen on. Careful attention
|
||||
# should be paid if this is assigned to anything other than the default, any.
|
||||
# This setting can deny access to control of the daemon. Not effective if
|
||||
# systemd socket activiation is in use.
|
||||
# systemd socket activation is in use.
|
||||
#
|
||||
# For network
|
||||
#bind_to_address "any"
|
||||
@ -178,11 +181,11 @@
|
||||
#
|
||||
#database {
|
||||
# plugin "simple"
|
||||
# path "~/.local/share/mpd/db
|
||||
# path "~/.local/share/mpd/db"
|
||||
# cache_directory "~/.local/share/mpd/cache"
|
||||
#}
|
||||
#
|
||||
# An example of database config for a sattelite setup
|
||||
# An example of database config for a satellite setup
|
||||
#
|
||||
#music_directory "nfs://fileserver.local/srv/mp3"
|
||||
#database {
|
||||
@ -311,6 +314,7 @@ input {
|
||||
## device "Digital Audio (S/PDIF) (High Definition Audio Device)" # optional
|
||||
# or
|
||||
## device "0" # optional
|
||||
## mixer_type "hardware" # optional
|
||||
## Exclusive mode blocks all other audio source, and get best audio quality without resampling.
|
||||
## exclusive "no" # optional
|
||||
## Enumerate all devices in log.
|
||||
|
174
doc/plugins.rst
174
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
|
||||
@ -49,6 +61,15 @@ upnp
|
||||
|
||||
Provides access to UPnP media servers.
|
||||
|
||||
.. list-table::
|
||||
:widths: 20 80
|
||||
:header-rows: 1
|
||||
|
||||
* - Setting
|
||||
- Description
|
||||
* - **interface**
|
||||
- Interface used to discover media servers. Decided by upnp if left unconfigured.
|
||||
|
||||
Storage plugins
|
||||
===============
|
||||
|
||||
@ -185,6 +206,11 @@ Plays audio CDs using libcdio. The URI has the form: "cdda://[DEVICE][/TRACK]".
|
||||
- If the CD drive does not specify a byte order, MPD assumes it is the CPU's native byte order. This setting allows overriding this.
|
||||
* - **speed N**
|
||||
- Request CDParanoia cap the extraction speed to Nx normal CD audio rotation speed, keeping the drive quiet.
|
||||
* - **mode disable|overlap|full**
|
||||
- Set the paranoia mode; ``disable`` means no fixups, ``overlap``
|
||||
performs overlapped reads, and ``full`` enables all options.
|
||||
* - **skip yes|no**
|
||||
- If set to ``no``, then never skip failed reads.
|
||||
|
||||
curl
|
||||
----
|
||||
@ -193,8 +219,9 @@ Opens remote files or streams over HTTP using libcurl.
|
||||
|
||||
Note that unless overridden by the below settings (e.g. by setting
|
||||
them to a blank value), general curl configuration from environment
|
||||
variables such as ``http_proxy`` or specified in :file:`~/.curlrc`
|
||||
will be in effect.
|
||||
variables such as ``http_proxy`` will be in effect.
|
||||
|
||||
User name and password are read from an optional :file:`~/.netrc`, :file:`~/.curlrc` is not read.
|
||||
|
||||
.. list-table::
|
||||
:widths: 20 80
|
||||
@ -210,6 +237,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 +324,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 +480,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 +607,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 +675,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 +751,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:
|
||||
|
||||
@ -815,6 +851,16 @@ The `Advanced Linux Sound Architecture (ALSA) <http://www.alsa-project.org/>`_ p
|
||||
- If set to no, then libasound will not attempt to convert between different sample formats (16 bit, 24 bit, floating point, ...).
|
||||
* - **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.
|
||||
* - **stop_dsd_silence yes|no**
|
||||
- If enabled, silence is played before manually stopping playback
|
||||
("stop" or "pause") in DSD mode (native DSD or DoP). This is a
|
||||
workaround for some DACs which emit noise when stopping DSD
|
||||
playback.
|
||||
* - **thesycon_dsd_workaround yes|no**
|
||||
- If enabled, enables a workaround for a bug in Thesycon USB
|
||||
audio receivers. On these devices, playing DSD512 or PCM
|
||||
causes all subsequent attempts to play other DSD rates to fail,
|
||||
which can be fixed by briefly playing PCM at 44.1 kHz.
|
||||
* - **allowed_formats F1 F2 ...**
|
||||
- Specifies a list of allowed audio formats, separated by a space. All items may contain asterisks as a wild card, and may be followed by "=dop" to enable DoP (DSD over PCM) for this particular format. The first matching format is used, and if none matches, MPD chooses the best fallback of this list.
|
||||
|
||||
@ -990,6 +1036,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 +1100,28 @@ 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 ls Node``
|
||||
* - **remote NAME**
|
||||
- The name of the remote to connect to. The default is
|
||||
``pipewire-0``.
|
||||
* - **dsd yes|no**
|
||||
- Enable DSD playback. This requires PipeWire 0.38.
|
||||
|
||||
.. _pulse_plugin:
|
||||
|
||||
pulse
|
||||
@ -1107,8 +1177,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 +1212,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 +1296,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
|
||||
|
||||
|
||||
|
104
doc/protocol.rst
104
doc/protocol.rst
@ -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>`_).
|
||||
@ -474,7 +479,7 @@ Querying :program:`MPD`'s status
|
||||
current song in seconds, but with higher resolution.
|
||||
- ``duration`` [#since_0_20]_: Duration of the current song in seconds.
|
||||
- ``bitrate``: instantaneous bitrate in kbps
|
||||
- ``xfade``: ``crossfade`` in seconds
|
||||
- ``xfade``: ``crossfade`` in seconds (see :ref:`crossfading`)
|
||||
- ``mixrampdb``: ``mixramp`` threshold in dB
|
||||
- ``mixrampdelay``: ``mixrampdelay`` in seconds
|
||||
- ``audio``: The format emitted by the decoder plugin during
|
||||
@ -514,17 +519,19 @@ Playback options
|
||||
.. _command_crossfade:
|
||||
|
||||
:command:`crossfade {SECONDS}`
|
||||
Sets crossfading between songs.
|
||||
Sets crossfading between songs. See :ref:`crossfading`.
|
||||
|
||||
.. _command_mixrampdb:
|
||||
|
||||
:command:`mixrampdb {deciBels}`
|
||||
Sets the threshold at which songs will be overlapped. Like crossfading but doesn't fade the track volume, just overlaps. The songs need to have MixRamp tags added by an external tool. 0dB is the normalized maximum volume so use negative values, I prefer -17dB. In the absence of mixramp tags crossfading will be used. See http://sourceforge.net/projects/mixramp
|
||||
Sets the threshold at which songs will be overlapped.
|
||||
See :ref:`mixramp`.
|
||||
|
||||
.. _command_mixrampdelay:
|
||||
|
||||
:command:`mixrampdelay {SECONDS}`
|
||||
Additional time subtracted from the overlap calculated by mixrampdb. A value of "nan" disables MixRamp overlapping and falls back to crossfading.
|
||||
See :ref:`mixramp`.
|
||||
|
||||
.. _command_random:
|
||||
|
||||
@ -538,12 +545,31 @@ Playback options
|
||||
Sets repeat state to ``STATE``,
|
||||
``STATE`` should be 0 or 1.
|
||||
|
||||
If enabled, MPD keeps repeating the whole queue (:ref:`single mode
|
||||
<command_single>` disabled) or the current song (:ref:`single mode
|
||||
<command_single>` enabled).
|
||||
|
||||
If :ref:`random mode <command_random>` is also enabled, the
|
||||
playback order will be shuffled each time the queue gets repeated.
|
||||
|
||||
.. _command_setvol:
|
||||
|
||||
:command:`setvol {VOL}`
|
||||
Sets volume to ``VOL``, the range of
|
||||
volume is 0-100.
|
||||
|
||||
.. _command_getvol:
|
||||
|
||||
:command:`getvol` [#since_0_23]_
|
||||
|
||||
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]_
|
||||
@ -672,11 +698,14 @@ Whenever possible, ids should be used.
|
||||
|
||||
.. _command_add:
|
||||
|
||||
:command:`add {URI}`
|
||||
:command:`add {URI} [POSITION]`
|
||||
Adds the file ``URI`` to the playlist
|
||||
(directories add recursively). ``URI``
|
||||
can also be a single file.
|
||||
|
||||
The position parameter is the same as in :ref:`addid
|
||||
<command_addid>`. [#since_0_23_3]_
|
||||
|
||||
Clients that are connected via local socket may add arbitrary
|
||||
local files (URI is an absolute path). Example::
|
||||
|
||||
@ -692,6 +721,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 [#since_0_23]_; 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 +751,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:
|
||||
|
||||
@ -736,8 +780,8 @@ Whenever possible, ids should be used.
|
||||
.. _command_playlistfind:
|
||||
|
||||
:command:`playlistfind {FILTER}`
|
||||
Finds songs in the queue with strict
|
||||
matching.
|
||||
Search the queue for songs matching
|
||||
``FILTER`` (see :ref:`Filters <filter_syntax>`).
|
||||
|
||||
.. _command_playlistid:
|
||||
|
||||
@ -757,8 +801,10 @@ Whenever possible, ids should be used.
|
||||
.. _command_playlistsearch:
|
||||
|
||||
:command:`playlistsearch {FILTER}`
|
||||
Searches case-insensitively for partial matches in the
|
||||
queue.
|
||||
Search the queue for songs matching
|
||||
``FILTER`` (see :ref:`Filters <filter_syntax>`).
|
||||
Parameters have the same meaning as for :ref:`find
|
||||
<command_playlistfind>`, except that search is not case sensitive.
|
||||
|
||||
.. _command_plchanges:
|
||||
|
||||
@ -885,19 +931,28 @@ 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.) [#since_0_23_1]_
|
||||
|
||||
.. _command_playlistadd:
|
||||
|
||||
:command:`playlistadd {NAME} {URI}`
|
||||
:command:`playlistadd {NAME} {URI} [POSITION]`
|
||||
Adds ``URI`` to the playlist
|
||||
`NAME.m3u`.
|
||||
`NAME.m3u` will be created if it does
|
||||
not exist.
|
||||
|
||||
The ``POSITION`` parameter specifies where the songs will be
|
||||
inserted into the playlist. [#since_0_23_3]_
|
||||
|
||||
.. _command_playlistclear:
|
||||
|
||||
:command:`playlistclear {NAME}`
|
||||
@ -909,6 +964,8 @@ remote playlists (absolute URI with a supported scheme).
|
||||
Deletes ``SONGPOS`` from the
|
||||
playlist `NAME.m3u`.
|
||||
|
||||
The second parameter can be a range. [#since_0_23_3]_
|
||||
|
||||
.. _command_playlistmove:
|
||||
|
||||
:command:`playlistmove {NAME} {FROM} {TO}`
|
||||
@ -1022,11 +1079,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:
|
||||
|
||||
@ -1167,16 +1224,21 @@ 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. [#since_0_23]_
|
||||
It can be relative to the current song as in :ref:`addid
|
||||
<command_addid>`. [#since_0_23_5]_
|
||||
|
||||
.. _command_searchaddpl:
|
||||
|
||||
:command:`searchaddpl {NAME} {FILTER} [sort {TYPE}] [window {START:END}]`
|
||||
:command:`searchaddpl {NAME} {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 playlist named ``NAME``.
|
||||
@ -1185,6 +1247,9 @@ The music database
|
||||
|
||||
Parameters have the same meaning as for :ref:`search <command_search>`.
|
||||
|
||||
The ``position`` parameter specifies where the songs will be
|
||||
inserted. [#since_0_23_4]_
|
||||
|
||||
.. _command_update:
|
||||
|
||||
:command:`update [URI]`
|
||||
@ -1603,3 +1668,8 @@ client-to-client messages are local to the current partition.
|
||||
.. [#since_0_20] Since :program:`MPD` 0.20
|
||||
.. [#since_0_21] Since :program:`MPD` 0.21
|
||||
.. [#since_0_22_4] Since :program:`MPD` 0.22.4
|
||||
.. [#since_0_23] Since :program:`MPD` 0.23
|
||||
.. [#since_0_23_1] Since :program:`MPD` 0.23.1
|
||||
.. [#since_0_23_3] Since :program:`MPD` 0.23.3
|
||||
.. [#since_0_23_4] Since :program:`MPD` 0.23.4
|
||||
.. [#since_0_23_5] Since :program:`MPD` 0.23.5
|
||||
|
209
doc/user.rst
209
doc/user.rst
@ -36,7 +36,9 @@ Installing on Android
|
||||
|
||||
An experimental Android build is available on Google Play. After installing and launching it, :program:`MPD` will scan the music in your Music directory and you can control it as usual with a :program:`MPD` client.
|
||||
|
||||
If you need to tweak the configuration, you can create a file called :file:`mpd.conf` on the data partition (the directory which is returned by Android's :dfn:`getExternalStorageDirectory()` API function).
|
||||
If you need to tweak the configuration, you can create a file called
|
||||
:file:`mpd.conf` in MPD's data directory on the external storage
|
||||
(usually :file:`Android/data/org.musicpd/files/mpd.conf`).
|
||||
|
||||
ALSA is not available on Android; only the :ref:`OpenSL ES
|
||||
<sles_output>` output plugin can be used for local playback.
|
||||
@ -55,8 +57,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
|
||||
@ -64,12 +66,13 @@ In any case, you need:
|
||||
Each plugin usually needs a codec library, which you also need to
|
||||
install. Check the :doc:`plugins` for details about required libraries
|
||||
|
||||
For example, the following installs a fairly complete list of build dependencies on Debian Buster:
|
||||
For example, the following installs a fairly complete list of build dependencies on Debian Bullseye:
|
||||
|
||||
.. code-block:: none
|
||||
|
||||
apt install meson g++ \
|
||||
libpcre3-dev \
|
||||
libfmt-dev \
|
||||
libpcre2-dev \
|
||||
libmad0-dev libmpg123-dev libid3tag0-dev \
|
||||
libflac-dev libvorbis-dev libopus-dev libogg-dev \
|
||||
libadplug-dev libaudiofile-dev libsndfile1-dev libfaad-dev \
|
||||
@ -110,6 +113,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 +160,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
|
||||
@ -158,7 +174,9 @@ tarball and change into the directory. Then, instead of
|
||||
|
||||
mkdir -p output/win64
|
||||
cd output/win64
|
||||
../../win32/build.py --64
|
||||
../../win32/build.py --64 \
|
||||
--buildtype=debugoptimized -Db_ndebug=true \
|
||||
-Dwrap_mode=forcefallback
|
||||
|
||||
This downloads various library sources, and then configures and builds
|
||||
:program:`MPD` (for x64; to build a 32 bit binary, pass
|
||||
@ -168,6 +186,11 @@ around. It is large, but easy to use. If you wish to have a small
|
||||
mpd.exe with DLLs, you need to compile manually, without the
|
||||
:file:`build.py` script.
|
||||
|
||||
The option ``-Dwrap_mode=forcefallback`` tells Meson to download and
|
||||
cross-compile several libraries used by MPD instead of looking for
|
||||
them on your computer.
|
||||
|
||||
|
||||
Compiling for Android
|
||||
---------------------
|
||||
|
||||
@ -176,8 +199,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 r25b <https://developer.android.com/ndk/downloads>`_
|
||||
* `Meson 0.56.0 <http://mesonbuild.com/>`__ and `Ninja
|
||||
<https://ninja-build.org/>`__
|
||||
* cmake
|
||||
* pkg-config
|
||||
@ -191,8 +214,10 @@ tarball and change into the directory. Then, instead of
|
||||
|
||||
mkdir -p output/android
|
||||
cd output/android
|
||||
../../android/build.py SDK_PATH NDK_PATH ABI
|
||||
meson configure -Dandroid_debug_keystore=$HOME/.android/debug.keystore
|
||||
../../android/build.py SDK_PATH NDK_PATH ABI \
|
||||
--buildtype=debugoptimized -Db_ndebug=true \
|
||||
-Dwrap_mode=forcefallback \
|
||||
-Dandroid_debug_keystore=$HOME/.android/debug.keystore
|
||||
ninja android/apk/mpd-debug.apk
|
||||
|
||||
:envvar:`SDK_PATH` is the absolute path where you installed the
|
||||
@ -278,7 +303,7 @@ Configuring neighbor plugins
|
||||
----------------------------
|
||||
|
||||
All neighbor plugins are disabled by default to avoid unwanted
|
||||
overhead. To enable (and configure) a plugin, add a :code:`neighbor`
|
||||
overhead. To enable (and configure) a plugin, add a :code:`neighbors`
|
||||
block to :file:`mpd.conf`:
|
||||
|
||||
.. code-block:: none
|
||||
@ -442,6 +467,11 @@ The following table lists the audio_output options valid for all plugins:
|
||||
implement an external mixer, see :ref:`external_mixer`) or no mixer
|
||||
(:samp:`none`). By default, the hardware mixer is used for
|
||||
devices which support it, and none for the others.
|
||||
* - **replay_gain_handler software|mixer|none**
|
||||
- Specifies how :ref:`replay_gain` is applied. The default is
|
||||
``software``, which uses an internal software volume control.
|
||||
``mixer`` uses the configured (hardware) mixer control.
|
||||
``none`` disables replay gain on this audio output.
|
||||
* - **filters "name,...**"
|
||||
- The specified configured filters are instantiated in the given
|
||||
order. Each filter name refers to a ``filter`` block, see
|
||||
@ -510,7 +540,7 @@ The following table lists the playlist_plugin options valid for all plugins:
|
||||
|
||||
* - Name
|
||||
- Description
|
||||
* - **plugin**
|
||||
* - **name**
|
||||
- The name of the plugin
|
||||
* - **enabled yes|no**
|
||||
- Allows you to disable a playlist plugin without recompiling. By default, all plugins are enabled.
|
||||
@ -560,6 +590,92 @@ Sometimes, music needs to be resampled before it can be played; for example, CDs
|
||||
Check the :ref:`resampler_plugins` reference for a list of resamplers
|
||||
and how to configure them.
|
||||
|
||||
Volume Normalization Settings
|
||||
-----------------------------
|
||||
|
||||
.. _replay_gain:
|
||||
|
||||
Replay Gain
|
||||
^^^^^^^^^^^
|
||||
|
||||
The setting ``replaygain`` specifies whether MPD shall adjust the
|
||||
volume of songs played using `ReplayGain
|
||||
<https://wiki.hydrogenaud.io/index.php?title=Replaygain>`__ tags.
|
||||
Setting this to ``album`` will adjust volume using the album's
|
||||
ReplayGain tags, while setting it to ``track`` will adjust it using
|
||||
the "track" ReplayGain tags. ``auto`` uses the track ReplayGain tags
|
||||
if random play is activated otherwise the album ReplayGain
|
||||
tags.
|
||||
|
||||
If ReplayGain is enabled, then the setting ``replaygain_preamp`` is
|
||||
set to a value (in dB) between ``-15`` and ``15``. This is the gain
|
||||
applied to songs with ReplayGain tags.
|
||||
|
||||
On songs without ReplayGain tags, the setting
|
||||
``replaygain_missing_preamp`` is used instead. If this setting is not
|
||||
configured, then no ReplayGain is applied to such songs, and they will
|
||||
appear too loud.
|
||||
|
||||
ReplayGain is usually implemented with a software volume filter (which
|
||||
prevents `Bit-perfect playback`_). To use a hardware mixer, set
|
||||
``replay_gain_handler`` to ``mixer`` in the ``audio_output`` section
|
||||
(see :ref:`config_audio_output` for details).
|
||||
|
||||
Simple Volume Normalization
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
MPD implements a very simple volume normalization method which can be
|
||||
enabled by setting ``volume_normalization`` to ``yes``. It supports
|
||||
16 bit PCM only.
|
||||
|
||||
|
||||
.. _crossfading:
|
||||
|
||||
Cross-Fading
|
||||
------------
|
||||
|
||||
If ``crossfade`` is set to a positive number, then adjacent songs are
|
||||
cross-faded by this number of seconds. This is a run-time setting
|
||||
:ref:`which can be controlled by clients <command_crossfade>`,
|
||||
e.g. with :program:`mpc`::
|
||||
|
||||
mpc crossfade 10
|
||||
mpc crossfade 0
|
||||
|
||||
Zero means cross-fading is disabled.
|
||||
|
||||
Cross-fading is only possible if both songs have the same audio
|
||||
format. At the cost of quality loss and higher CPU usage, you can
|
||||
make sure this is always given by configuring
|
||||
:ref:`audio_output_format`.
|
||||
|
||||
.. _mixramp:
|
||||
|
||||
MixRamp
|
||||
^^^^^^^
|
||||
|
||||
MixRamp tags describe the loudness levels at start and end of a song
|
||||
and can be used by MPD to find the best time to begin cross-fading.
|
||||
MPD enables MixRamp if:
|
||||
|
||||
- Cross-fade is enabled
|
||||
- :ref:`mixrampdelay <command_mixrampdelay>` is set to a positive
|
||||
value, e.g.::
|
||||
|
||||
mpc mixrampdelay 1
|
||||
- :ref:`mixrampdb <command_mixrampdb>` is set to a reasonable value,
|
||||
e.g.::
|
||||
|
||||
mpc mixrampdb -17
|
||||
- both songs have MixRamp tags
|
||||
- both songs have the same audio format (or :ref:`audio_output_format`
|
||||
is configured)
|
||||
|
||||
The `MixRamp <http://sourceforge.net/projects/mixramp>`__ tool can be
|
||||
used to add MixRamp tags to your song files.
|
||||
|
||||
|
||||
|
||||
Client Connections
|
||||
------------------
|
||||
|
||||
@ -625,13 +741,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 +764,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 +813,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
|
||||
^^^^^^^^^^^^^^^^^^^^
|
||||
@ -945,7 +1072,19 @@ The "music directory" is where you store your music files. :program:`MPD` stores
|
||||
|
||||
Depending on the size of your music collection and the speed of the storage, this can take a while.
|
||||
|
||||
To exclude a file from the update, create a file called :file:`.mpdignore` in its parent directory. Each line of that file may contain a list of shell wildcards. Matching files in the current directory and all subdirectories are excluded.
|
||||
To exclude a file from the update, create a file called
|
||||
:file:`.mpdignore` in its parent directory. Each line of that file
|
||||
may contain a list of shell wildcards. Matching files (or
|
||||
directories) in the current directory and all subdirectories are
|
||||
excluded. Example::
|
||||
|
||||
*.opus
|
||||
99*
|
||||
|
||||
Subject to pattern matching is the file/directory name. It is (not
|
||||
yet) possible to match nested path names, e.g. something like
|
||||
``foo/*.flac`` is not possible.
|
||||
|
||||
|
||||
Mounting other storages into the music directory
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
@ -985,6 +1124,15 @@ See :ref:`tags` for a list of supported tags.
|
||||
The :ref:`metadata_to_use <metadata_to_use>` setting can be used to
|
||||
enable or disable certain tags.
|
||||
|
||||
Note that :program:`MPD` may not necessarily read metadata itself,
|
||||
instead relying on data reported by the decoder that was used to read
|
||||
a file. For example, this is the case for the FFmpeg decoder: both
|
||||
:program:`MPD` and FFmpeg need to support a given metadata format in
|
||||
order for metadata to be picked up correctly.
|
||||
|
||||
Only if a decoder does not have metadata support will :program:`MPD`
|
||||
attempt to parse a song's metadata itself.
|
||||
|
||||
The queue
|
||||
---------
|
||||
|
||||
@ -1042,6 +1190,7 @@ Check list for bit-perfect playback:
|
||||
* Disable sound processing inside ALSA by configuring a "hardware"
|
||||
device (:samp:`hw:0,0` or similar).
|
||||
* Don't use software volume (setting :code:`mixer_type`).
|
||||
* Don't use :ref:`replay_gain`.
|
||||
* Don't force :program:`MPD` to use a specific audio format (settings
|
||||
:code:`format`, :ref:`audio_output_format <audio_output_format>`).
|
||||
* Verify that you are really doing bit-perfect playback using :program:`MPD`'s verbose log and :file:`/proc/asound/card*/pcm*p/sub*/hw_params`. Some DACs can also indicate the audio format.
|
||||
@ -1120,7 +1269,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 +1351,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
|
||||
^^^^^^^^^^^
|
||||
|
||||
|
149
meson.build
149
meson.build
@ -1,8 +1,8 @@
|
||||
project(
|
||||
'mpd',
|
||||
['c', 'cpp'],
|
||||
version: '0.22.7',
|
||||
meson_version: '>= 0.49.0',
|
||||
version: '0.23.14',
|
||||
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.5')
|
||||
configure_file(output: 'Version.h', configuration: version_conf)
|
||||
|
||||
conf = configuration_data()
|
||||
@ -42,57 +54,66 @@ 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',
|
||||
|
||||
# suppress bogus GCC12 warnings in libfmt headers
|
||||
'-Wno-stringop-overflow',
|
||||
]
|
||||
|
||||
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 +125,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 +139,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') != ''
|
||||
@ -179,7 +205,6 @@ enable_daemon = not is_windows and not is_android and get_option('daemon')
|
||||
conf.set('ENABLE_DAEMON', enable_daemon)
|
||||
|
||||
conf.set('HAVE_GETPWNAM_R', compiler.has_function('getpwnam_r'))
|
||||
conf.set('HAVE_GETPWUID_R', compiler.has_function('getpwuid_r'))
|
||||
conf.set('HAVE_INITGROUPS', compiler.has_function('initgroups'))
|
||||
conf.set('HAVE_FNMATCH', compiler.has_function('fnmatch'))
|
||||
|
||||
@ -193,17 +218,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,23 +248,35 @@ 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'])
|
||||
|
||||
if compiler.get_id() == 'clang' and compiler.version().version_compare('<15')
|
||||
fmt_dep = declare_dependency(
|
||||
dependencies: fmt_dep,
|
||||
# suppress bogus clang 14 warning (the version in Android NDK r25b)
|
||||
compile_args: ['-Wno-unused-local-typedef'],
|
||||
)
|
||||
endif
|
||||
|
||||
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 = [
|
||||
version_cxx,
|
||||
'src/Main.cxx',
|
||||
'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 +291,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',
|
||||
@ -334,7 +359,7 @@ sources = [
|
||||
'src/TagStream.cxx',
|
||||
'src/TagAny.cxx',
|
||||
'src/TimePrint.cxx',
|
||||
'src/mixer/Volume.cxx',
|
||||
'src/mixer/Memento.cxx',
|
||||
'src/PlaylistFile.cxx',
|
||||
]
|
||||
|
||||
@ -364,6 +389,7 @@ endif
|
||||
|
||||
if enable_database
|
||||
sources += [
|
||||
'src/storage/StorageState.cxx',
|
||||
'src/queue/PlaylistUpdate.cxx',
|
||||
'src/command/StorageCommands.cxx',
|
||||
'src/command/DatabaseCommands.cxx',
|
||||
@ -396,6 +422,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 +432,8 @@ subdir('src/lib/yajl')
|
||||
|
||||
subdir('src/lib/crypto')
|
||||
|
||||
subdir('src/zeroconf')
|
||||
|
||||
subdir('src/fs')
|
||||
subdir('src/config')
|
||||
subdir('src/tag')
|
||||
@ -420,7 +449,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 +564,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')
|
||||
|
||||
|
@ -1,26 +1,32 @@
|
||||
import os.path, subprocess, sys
|
||||
from typing import Collection, Iterable, Optional, Sequence, Union
|
||||
from collections.abc import Mapping
|
||||
|
||||
from build.makeproject import MakeProject
|
||||
from .toolchain import AnyToolchain
|
||||
|
||||
class AutotoolsProject(MakeProject):
|
||||
def __init__(self, url, md5, installed, configure_args=[],
|
||||
autogen=False,
|
||||
autoreconf=False,
|
||||
cppflags='',
|
||||
ldflags='',
|
||||
libs='',
|
||||
subdirs=None,
|
||||
def __init__(self, url: Union[str, Sequence[str]], md5: str, installed: str,
|
||||
configure_args: Iterable[str]=[],
|
||||
autogen: bool=False,
|
||||
autoreconf: bool=False,
|
||||
per_arch_cflags: Optional[Mapping[str, str]]=None,
|
||||
cppflags: str='',
|
||||
ldflags: str='',
|
||||
libs: str='',
|
||||
subdirs: Optional[Collection[str]]=None,
|
||||
**kwargs):
|
||||
MakeProject.__init__(self, url, md5, installed, **kwargs)
|
||||
self.configure_args = configure_args
|
||||
self.autogen = autogen
|
||||
self.autoreconf = autoreconf
|
||||
self.per_arch_cflags = per_arch_cflags
|
||||
self.cppflags = cppflags
|
||||
self.ldflags = ldflags
|
||||
self.libs = libs
|
||||
self.subdirs = subdirs
|
||||
|
||||
def configure(self, toolchain):
|
||||
def configure(self, toolchain: AnyToolchain) -> str:
|
||||
src = self.unpack(toolchain)
|
||||
if self.autogen:
|
||||
if sys.platform == 'darwin':
|
||||
@ -35,30 +41,51 @@ class AutotoolsProject(MakeProject):
|
||||
|
||||
build = self.make_build_path(toolchain)
|
||||
|
||||
arch_cflags = ''
|
||||
if self.per_arch_cflags is not None and toolchain.host_triplet is not None:
|
||||
arch_cflags = self.per_arch_cflags.get(toolchain.host_triplet, '')
|
||||
|
||||
configure = [
|
||||
os.path.join(src, 'configure'),
|
||||
'CC=' + toolchain.cc,
|
||||
'CXX=' + toolchain.cxx,
|
||||
'CFLAGS=' + toolchain.cflags,
|
||||
'CXXFLAGS=' + toolchain.cxxflags,
|
||||
'CFLAGS=' + toolchain.cflags + ' ' + arch_cflags,
|
||||
'CXXFLAGS=' + toolchain.cxxflags + ' ' + arch_cflags,
|
||||
'CPPFLAGS=' + toolchain.cppflags + ' ' + self.cppflags,
|
||||
'LDFLAGS=' + toolchain.ldflags + ' ' + self.ldflags,
|
||||
'LIBS=' + toolchain.libs + ' ' + self.libs,
|
||||
'AR=' + toolchain.ar,
|
||||
'ARFLAGS=' + toolchain.arflags,
|
||||
'RANLIB=' + toolchain.ranlib,
|
||||
'STRIP=' + toolchain.strip,
|
||||
'--host=' + toolchain.arch,
|
||||
'--prefix=' + toolchain.install_prefix,
|
||||
'--enable-silent-rules',
|
||||
] + self.configure_args
|
||||
'--disable-silent-rules',
|
||||
]
|
||||
|
||||
if toolchain.host_triplet is not None:
|
||||
configure.append('--host=' + toolchain.host_triplet)
|
||||
|
||||
configure.extend(self.configure_args)
|
||||
|
||||
try:
|
||||
print(configure)
|
||||
subprocess.check_call(configure, cwd=build, env=toolchain.env)
|
||||
except subprocess.CalledProcessError:
|
||||
# dump config.log after a failed configure run
|
||||
try:
|
||||
with open(os.path.join(build, 'config.log')) as f:
|
||||
sys.stdout.write(f.read())
|
||||
except:
|
||||
pass
|
||||
# re-raise the exception
|
||||
raise
|
||||
|
||||
subprocess.check_call(configure, cwd=build, env=toolchain.env)
|
||||
return build
|
||||
|
||||
def build(self, toolchain):
|
||||
def _build(self, toolchain: AnyToolchain) -> None:
|
||||
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,13 +1,65 @@
|
||||
import os
|
||||
import re
|
||||
import subprocess
|
||||
from typing import cast, Optional, Sequence, TextIO, Union
|
||||
from collections.abc import Mapping
|
||||
|
||||
from build.project import Project
|
||||
from .toolchain import AnyToolchain
|
||||
|
||||
def configure(toolchain, src, build, args=()):
|
||||
cross_args = []
|
||||
def __write_cmake_compiler(f: TextIO, language: str, compiler: str) -> None:
|
||||
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: TextIO, toolchain: AnyToolchain) -> None:
|
||||
if toolchain.is_darwin:
|
||||
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.host_triplet.split('-', 1)[0]})
|
||||
|
||||
set(CMAKE_C_COMPILER_TARGET {toolchain.host_triplet})
|
||||
set(CMAKE_CXX_COMPILER_TARGET {toolchain.host_triplet})
|
||||
|
||||
set(CMAKE_C_FLAGS_INIT "{toolchain.cflags} {toolchain.cppflags}")
|
||||
set(CMAKE_CXX_FLAGS_INIT "{toolchain.cxxflags} {toolchain.cppflags}")
|
||||
""")
|
||||
__write_cmake_compiler(f, 'C', toolchain.cc)
|
||||
__write_cmake_compiler(f, 'CXX', toolchain.cxx)
|
||||
|
||||
if cmake_system_name == 'Darwin':
|
||||
# On macOS, cmake forcibly adds an "-isysroot" flag even if
|
||||
# one is already present in the flags variable; this breaks
|
||||
# cross-compiling for iOS, and can be worked around by setting
|
||||
# the CMAKE_OSX_SYSROOT variable
|
||||
# (https://cmake.org/cmake/help/latest/variable/CMAKE_OSX_SYSROOT.html).
|
||||
m = re.search(r'-isysroot +(\S+)', toolchain.cflags)
|
||||
if m:
|
||||
sysroot = m.group(1)
|
||||
|
||||
print(f'set(CMAKE_OSX_SYSROOT {sysroot})', file=f)
|
||||
|
||||
# search libraries and headers only in the sysroot, not on
|
||||
# the build host
|
||||
f.write(f"""
|
||||
set(CMAKE_FIND_ROOT_PATH "{toolchain.install_prefix};{sysroot}")
|
||||
set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY)
|
||||
set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY)
|
||||
""")
|
||||
|
||||
def configure(toolchain: AnyToolchain, src: str, build: str, args: list[str]=[], env: Optional[Mapping[str, str]]=None) -> None:
|
||||
cross_args: list[str] = []
|
||||
|
||||
if toolchain.is_windows:
|
||||
cross_args.append('-DCMAKE_SYSTEM_NAME=Windows')
|
||||
cross_args.append('-DCMAKE_RC_COMPILER=' + toolchain.windres)
|
||||
cross_args.append('-DCMAKE_RC_COMPILER=' + cast(str, toolchain.windres))
|
||||
|
||||
configure = [
|
||||
'cmake',
|
||||
@ -16,30 +68,55 @@ def configure(toolchain, src, build, args=()):
|
||||
'-DCMAKE_INSTALL_PREFIX=' + toolchain.install_prefix,
|
||||
'-DCMAKE_BUILD_TYPE=release',
|
||||
|
||||
'-DCMAKE_C_COMPILER=' + toolchain.cc,
|
||||
'-DCMAKE_CXX_COMPILER=' + toolchain.cxx,
|
||||
|
||||
'-DCMAKE_C_FLAGS=' + toolchain.cflags + ' ' + toolchain.cppflags,
|
||||
'-DCMAKE_CXX_FLAGS=' + toolchain.cxxflags + ' ' + toolchain.cppflags,
|
||||
|
||||
'-GNinja',
|
||||
] + cross_args + args
|
||||
|
||||
subprocess.check_call(configure, env=toolchain.env, cwd=build)
|
||||
if toolchain.host_triplet is not None:
|
||||
# cross-compiling: write a toolchain file
|
||||
os.makedirs(build, exist_ok=True)
|
||||
|
||||
# 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 not toolchain.is_android and not toolchain.is_darwin:
|
||||
cross_args.append('-DCMAKE_SYSROOT=' + toolchain.install_prefix)
|
||||
|
||||
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.append('-DCMAKE_TOOLCHAIN_FILE=' + cmake_toolchain_file)
|
||||
|
||||
if env is None:
|
||||
env = toolchain.env
|
||||
else:
|
||||
env = {**toolchain.env, **env}
|
||||
|
||||
print(configure)
|
||||
subprocess.check_call(configure, env=env, cwd=build)
|
||||
|
||||
class CmakeProject(Project):
|
||||
def __init__(self, url, md5, installed, configure_args=[],
|
||||
def __init__(self, url: Union[str, Sequence[str]], md5: str, installed: str,
|
||||
configure_args: list[str]=[],
|
||||
windows_configure_args: list[str]=[],
|
||||
env: Optional[Mapping[str, str]]=None,
|
||||
**kwargs):
|
||||
Project.__init__(self, url, md5, installed, **kwargs)
|
||||
self.configure_args = configure_args
|
||||
self.windows_configure_args = windows_configure_args
|
||||
self.env = env
|
||||
|
||||
def configure(self, toolchain):
|
||||
def configure(self, toolchain: AnyToolchain) -> str:
|
||||
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, self.env)
|
||||
return build
|
||||
|
||||
def build(self, toolchain):
|
||||
def _build(self, toolchain: AnyToolchain) -> None:
|
||||
build = self.configure(toolchain)
|
||||
subprocess.check_call(['ninja', 'install'],
|
||||
subprocess.check_call(['ninja', '-v', 'install'],
|
||||
cwd=build, env=toolchain.env)
|
||||
|
@ -1,12 +1,50 @@
|
||||
from build.verify import verify_file_digest
|
||||
from typing import Sequence, Union
|
||||
import os
|
||||
import sys
|
||||
import urllib.request
|
||||
|
||||
def download_and_verify(url, md5, parent_path):
|
||||
from .verify import verify_file_digest
|
||||
|
||||
def __to_string_sequence(x: Union[str, Sequence[str]]) -> Sequence[str]:
|
||||
if isinstance(x, str):
|
||||
return (x,)
|
||||
else:
|
||||
return x
|
||||
|
||||
def __get_any(x: Union[str, Sequence[str]]) -> str:
|
||||
if isinstance(x, str):
|
||||
return x
|
||||
else:
|
||||
return x[0]
|
||||
|
||||
def __download_one(url: str, path: str) -> None:
|
||||
print("download", url)
|
||||
urllib.request.urlretrieve(url, path)
|
||||
|
||||
def __download(urls: Sequence[str], path: str) -> None:
|
||||
for url in urls[:-1]:
|
||||
try:
|
||||
__download_one(url, path)
|
||||
return
|
||||
except:
|
||||
print("download error:", sys.exc_info()[0])
|
||||
__download_one(urls[-1], path)
|
||||
|
||||
def __download_and_verify_to(urls: Sequence[str], md5: str, path: str) -> None:
|
||||
__download(urls, path)
|
||||
if not verify_file_digest(path, md5):
|
||||
raise RuntimeError("Digest mismatch")
|
||||
|
||||
def download_basename(urls: Union[str, Sequence[str]]) -> str:
|
||||
return os.path.basename(__get_any(urls))
|
||||
|
||||
def download_and_verify(urls: Union[str, Sequence[str]], md5: str, parent_path: str) -> str:
|
||||
"""Download a file, verify its MD5 checksum and return the local path."""
|
||||
|
||||
base = download_basename(urls)
|
||||
|
||||
os.makedirs(parent_path, exist_ok=True)
|
||||
path = os.path.join(parent_path, os.path.basename(url))
|
||||
path = os.path.join(parent_path, base)
|
||||
|
||||
try:
|
||||
if verify_file_digest(path, md5): return path
|
||||
@ -16,11 +54,6 @@ def download_and_verify(url, md5, parent_path):
|
||||
|
||||
tmp_path = path + '.tmp'
|
||||
|
||||
print("download", url)
|
||||
urllib.request.urlretrieve(url, tmp_path)
|
||||
if not verify_file_digest(tmp_path, md5):
|
||||
os.unlink(tmp_path)
|
||||
raise RuntimeError("Digest mismatch")
|
||||
|
||||
__download_and_verify_to(__to_string_sequence(urls), md5, tmp_path)
|
||||
os.rename(tmp_path, path)
|
||||
return path
|
||||
|
@ -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']
|
||||
|
@ -12,37 +12,25 @@ from build.boost import BoostProject
|
||||
from build.jack import JackProject
|
||||
|
||||
libmpdclient = MesonProject(
|
||||
'https://www.musicpd.org/download/libmpdclient/2/libmpdclient-2.19.tar.xz',
|
||||
'158aad4c2278ab08e76a3f2b0166c99b39fae00ee17231bd225c5a36e977a189',
|
||||
'https://www.musicpd.org/download/libmpdclient/2/libmpdclient-2.20.tar.xz',
|
||||
'18793f68e939c3301e34d8fcadea1f7daa24143941263cecadb80126194e277d',
|
||||
'lib/libmpdclient.a',
|
||||
)
|
||||
|
||||
libogg = AutotoolsProject(
|
||||
'http://downloads.xiph.org/releases/ogg/libogg-1.3.4.tar.xz',
|
||||
'c163bc12bc300c401b6aa35907ac682671ea376f13ae0969a220f7ddf71893fe',
|
||||
libogg = CmakeProject(
|
||||
'http://downloads.xiph.org/releases/ogg/libogg-1.3.5.tar.xz',
|
||||
'c4d91be36fc8e54deae7575241e03f4211eb102afb3fc0775fbbc1b740016705',
|
||||
'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',
|
||||
'https://downloads.xiph.org/releases/opus/opus-1.4.tar.gz',
|
||||
'c9b32b4253be5ae63d1ff16eea06b94b5f0f2951b7a02aceef58e3a3ce49c51f',
|
||||
'lib/libopus.a',
|
||||
[
|
||||
'--disable-shared', '--enable-static',
|
||||
@ -55,20 +43,23 @@ opus = AutotoolsProject(
|
||||
)
|
||||
|
||||
flac = AutotoolsProject(
|
||||
'http://downloads.xiph.org/releases/flac/flac-1.3.3.tar.xz',
|
||||
'213e82bd716c9de6db2f98bcadbc4c24c7e2efe8c75939a1a84e28539c4e1748',
|
||||
'http://downloads.xiph.org/releases/flac/flac-1.4.3.tar.xz',
|
||||
'6c58e69cd22348f441b861092b825e591d0b822e106de6eb0ee4d05d27205b70',
|
||||
'lib/libFLAC.a',
|
||||
[
|
||||
'--disable-shared', '--enable-static',
|
||||
'--disable-stack-smash-protection',
|
||||
'--disable-xmms-plugin', '--disable-cpplibs',
|
||||
'--disable-doxygen-docs',
|
||||
'--disable-programs',
|
||||
],
|
||||
subdirs=['include', 'src/libFLAC'],
|
||||
)
|
||||
|
||||
zlib = ZlibProject(
|
||||
'http://zlib.net/zlib-1.2.11.tar.xz',
|
||||
'4ff941449631ace0d4d203e3483be9dbc9da454084111f97ea0a2114e19bf066',
|
||||
('http://zlib.net/zlib-1.3.tar.xz',
|
||||
'https://github.com/madler/zlib/releases/download/v1.3/zlib-1.3.tar.xz'),
|
||||
'8a9ba2898e1d0d774eca6ba5b4627a11e5588ba85c8851336eb38de4683050a7',
|
||||
'lib/libz.a',
|
||||
)
|
||||
|
||||
@ -121,20 +112,35 @@ libmodplug = AutotoolsProject(
|
||||
[
|
||||
'--disable-shared', '--enable-static',
|
||||
],
|
||||
patches='src/lib/modplug/patches',
|
||||
)
|
||||
|
||||
libopenmpt = AutotoolsProject(
|
||||
'https://lib.openmpt.org/files/libopenmpt/src/libopenmpt-0.6.6+release.autotools.tar.gz',
|
||||
'6ddb9e26a430620944891796fefb1bbb38bd9148f6cfc558810c0d3f269876c7',
|
||||
'lib/libopenmpt.a',
|
||||
[
|
||||
'--disable-shared', '--enable-static',
|
||||
'--disable-openmpt123',
|
||||
'--disable-examples',
|
||||
'--disable-tests',
|
||||
'--disable-doxygen-doc',
|
||||
'--without-mpg123', '--without-ogg', '--without-vorbis', '--without-vorbisfile',
|
||||
'--without-portaudio', '--without-portaudiocpp', '--without-sndfile',
|
||||
'--without-flac',
|
||||
],
|
||||
base='libopenmpt-0.6.6+release.autotools',
|
||||
)
|
||||
|
||||
wildmidi = CmakeProject(
|
||||
'https://codeload.github.com/Mindwerks/wildmidi/tar.gz/wildmidi-0.4.3',
|
||||
'498e5a96455bb4b91b37188ad6dcb070824e92c44f5ed452b90adbaec8eef3c5',
|
||||
'https://github.com/Mindwerks/wildmidi/releases/download/wildmidi-0.4.5/wildmidi-0.4.5.tar.gz',
|
||||
'd5e7bef00a7aa47534a53d43b1265f8d3d27f6a28e7f563c1cdf02ff4fa35b99',
|
||||
'lib/libWildMidi.a',
|
||||
[
|
||||
'-DBUILD_SHARED_LIBS=OFF',
|
||||
'-DWANT_PLAYER=OFF',
|
||||
'-DWANT_STATIC=ON',
|
||||
],
|
||||
base='wildmidi-wildmidi-0.4.3',
|
||||
name='wildmidi',
|
||||
version='0.4.3',
|
||||
)
|
||||
|
||||
gme = CmakeProject(
|
||||
@ -145,13 +151,13 @@ gme = CmakeProject(
|
||||
'-DBUILD_SHARED_LIBS=OFF',
|
||||
'-DENABLE_UBSAN=OFF',
|
||||
'-DZLIB_INCLUDE_DIR=OFF',
|
||||
'-DSDL2_DIR=OFF',
|
||||
'-DCMAKE_DISABLE_FIND_PACKAGE_SDL2=ON',
|
||||
],
|
||||
)
|
||||
|
||||
ffmpeg = FfmpegProject(
|
||||
'http://ffmpeg.org/releases/ffmpeg-4.4.tar.xz',
|
||||
'06b10a183ce5371f915c6bb15b7b1fffbe046e8275099c96affc29e17645d909',
|
||||
'http://ffmpeg.org/releases/ffmpeg-6.0.tar.xz',
|
||||
'57be87c22d9b49c112b6d24bc67d42508660e6b718b3db89c44e47e289137082',
|
||||
'lib/libavcodec.a',
|
||||
[
|
||||
'--disable-shared', '--enable-static',
|
||||
@ -165,17 +171,21 @@ ffmpeg = FfmpegProject(
|
||||
'--disable-swscale',
|
||||
'--disable-postproc',
|
||||
'--disable-avfilter',
|
||||
'--disable-lzo',
|
||||
'--disable-faan',
|
||||
'--disable-pixelutils',
|
||||
'--disable-network',
|
||||
'--disable-encoders',
|
||||
'--disable-hwaccels',
|
||||
'--disable-muxers',
|
||||
'--disable-protocols',
|
||||
'--disable-devices',
|
||||
'--disable-filters',
|
||||
'--disable-v4l2_m2m',
|
||||
|
||||
'--disable-sdl2',
|
||||
'--disable-vulkan',
|
||||
'--disable-xlib',
|
||||
|
||||
'--disable-parser=bmp',
|
||||
'--disable-parser=cavsvideo',
|
||||
'--disable-parser=dvbsub',
|
||||
@ -188,17 +198,22 @@ ffmpeg = FfmpegProject(
|
||||
'--disable-parser=h263',
|
||||
'--disable-parser=h264',
|
||||
'--disable-parser=hevc',
|
||||
'--disable-parser=jpeg2000',
|
||||
'--disable-parser=mjpeg',
|
||||
'--disable-parser=mlp',
|
||||
'--disable-parser=mpeg4video',
|
||||
'--disable-parser=mpegvideo',
|
||||
'--disable-parser=opus',
|
||||
'--disable-parser=qoi',
|
||||
'--disable-parser=rv30',
|
||||
'--disable-parser=rv40',
|
||||
'--disable-parser=vc1',
|
||||
'--disable-parser=vp3',
|
||||
'--disable-parser=vp8',
|
||||
'--disable-parser=vp9',
|
||||
'--disable-parser=png',
|
||||
'--disable-parser=pnm',
|
||||
'--disable-parser=webp',
|
||||
'--disable-parser=xma',
|
||||
|
||||
'--disable-demuxer=aqtitle',
|
||||
@ -214,6 +229,42 @@ ffmpeg = FfmpegProject(
|
||||
'--disable-demuxer=h264',
|
||||
'--disable-demuxer=ico',
|
||||
'--disable-demuxer=image2',
|
||||
'--disable-demuxer=image2pipe',
|
||||
'--disable-demuxer=image_bmp_pipe',
|
||||
'--disable-demuxer=image_cri_pipe',
|
||||
'--disable-demuxer=image_dds_pipe',
|
||||
'--disable-demuxer=image_dpx_pipe',
|
||||
'--disable-demuxer=image_exr_pipe',
|
||||
'--disable-demuxer=image_gem_pipe',
|
||||
'--disable-demuxer=image_gif_pipe',
|
||||
'--disable-demuxer=image_j2k_pipe',
|
||||
'--disable-demuxer=image_jpeg_pipe',
|
||||
'--disable-demuxer=image_jpegls_pipe',
|
||||
'--disable-demuxer=image_jpegxl_pipe',
|
||||
'--disable-demuxer=image_pam_pipe',
|
||||
'--disable-demuxer=image_pbm_pipe',
|
||||
'--disable-demuxer=image_pcx_pipe',
|
||||
'--disable-demuxer=image_pfm_pipe',
|
||||
'--disable-demuxer=image_pgm_pipe',
|
||||
'--disable-demuxer=image_pgmyuv_pipe',
|
||||
'--disable-demuxer=image_pgx_pipe',
|
||||
'--disable-demuxer=image_phm_pipe',
|
||||
'--disable-demuxer=image_photocd_pipe',
|
||||
'--disable-demuxer=image_pictor_pipe',
|
||||
'--disable-demuxer=image_png_pipe',
|
||||
'--disable-demuxer=image_ppm_pipe',
|
||||
'--disable-demuxer=image_psd_pipe',
|
||||
'--disable-demuxer=image_qdraw_pipe',
|
||||
'--disable-demuxer=image_qoi_pipe',
|
||||
'--disable-demuxer=image_sgi_pipe',
|
||||
'--disable-demuxer=image_sunrast_pipe',
|
||||
'--disable-demuxer=image_svg_pipe',
|
||||
'--disable-demuxer=image_tiff_pipe',
|
||||
'--disable-demuxer=image_vbn_pipe',
|
||||
'--disable-demuxer=image_webp_pipe',
|
||||
'--disable-demuxer=image_xbm_pipe',
|
||||
'--disable-demuxer=image_xpm_pipe',
|
||||
'--disable-demuxer=image_xwd_pipe',
|
||||
'--disable-demuxer=jacosub',
|
||||
'--disable-demuxer=lrc',
|
||||
'--disable-demuxer=microdvd',
|
||||
@ -236,6 +287,7 @@ ffmpeg = FfmpegProject(
|
||||
'--disable-demuxer=tedcaptions',
|
||||
'--disable-demuxer=vobsub',
|
||||
'--disable-demuxer=vplayer',
|
||||
'--disable-demuxer=webm_dash_manifest',
|
||||
'--disable-demuxer=webvtt',
|
||||
'--disable-demuxer=yuv4mpegpipe',
|
||||
|
||||
@ -265,78 +317,179 @@ ffmpeg = FfmpegProject(
|
||||
'--disable-decoder=qdmc',
|
||||
|
||||
# disable lots of image and video codecs
|
||||
'--disable-decoder=acelp_kelvin',
|
||||
'--disable-decoder=agm',
|
||||
'--disable-decoder=aic',
|
||||
'--disable-decoder=alias_pix',
|
||||
'--disable-decoder=ansi',
|
||||
'--disable-decoder=apng',
|
||||
'--disable-decoder=arbc',
|
||||
'--disable-decoder=argo',
|
||||
'--disable-decoder=ass',
|
||||
'--disable-decoder=asv1',
|
||||
'--disable-decoder=asv2',
|
||||
'--disable-decoder=apng',
|
||||
'--disable-decoder=aura',
|
||||
'--disable-decoder=aura2',
|
||||
'--disable-decoder=avrn',
|
||||
'--disable-decoder=avrp',
|
||||
'--disable-decoder=avui',
|
||||
'--disable-decoder=ayuv',
|
||||
'--disable-decoder=bethsoftvid',
|
||||
'--disable-decoder=bfi',
|
||||
'--disable-decoder=bink',
|
||||
'--disable-decoder=bintext',
|
||||
'--disable-decoder=bitpacked',
|
||||
'--disable-decoder=bmp',
|
||||
'--disable-decoder=bmv_video',
|
||||
'--disable-decoder=brender_pix',
|
||||
'--disable-decoder=c93',
|
||||
'--disable-decoder=cavs',
|
||||
'--disable-decoder=ccaption',
|
||||
'--disable-decoder=cdgraphics',
|
||||
'--disable-decoder=cdtoons',
|
||||
'--disable-decoder=cdxl',
|
||||
'--disable-decoder=cfhd',
|
||||
'--disable-decoder=cinepak',
|
||||
'--disable-decoder=clearvideo',
|
||||
'--disable-decoder=cljr',
|
||||
'--disable-decoder=cllc',
|
||||
'--disable-decoder=cpia',
|
||||
'--disable-decoder=cscd',
|
||||
'--disable-decoder=cyuv',
|
||||
'--disable-decoder=dds',
|
||||
'--disable-decoder=dirac',
|
||||
'--disable-decoder=dnxhd',
|
||||
'--disable-decoder=dpx',
|
||||
'--disable-decoder=dsicinvideo',
|
||||
'--disable-decoder=dvbsub',
|
||||
'--disable-decoder=dvdsub',
|
||||
'--disable-decoder=dvvideo',
|
||||
'--disable-decoder=dxa',
|
||||
'--disable-decoder=dxtory',
|
||||
'--disable-decoder=dxv',
|
||||
'--disable-decoder=eacmv',
|
||||
'--disable-decoder=eamad',
|
||||
'--disable-decoder=eatgq',
|
||||
'--disable-decoder=eatgv',
|
||||
'--disable-decoder=eatqi',
|
||||
'--disable-decoder=eightbps',
|
||||
'--disable-decoder=escape124',
|
||||
'--disable-decoder=escape130',
|
||||
'--disable-decoder=exr',
|
||||
'--disable-decoder=ffv1',
|
||||
'--disable-decoder=ffvhuff',
|
||||
'--disable-decoder=ffwavesynth',
|
||||
'--disable-decoder=fic',
|
||||
'--disable-decoder=fits',
|
||||
'--disable-decoder=flashsv',
|
||||
'--disable-decoder=flashsv2',
|
||||
'--disable-decoder=flic',
|
||||
'--disable-decoder=flv',
|
||||
'--disable-decoder=fmvc',
|
||||
'--disable-decoder=fraps',
|
||||
'--disable-decoder=fourxm',
|
||||
'--disable-decoder=frwu',
|
||||
'--disable-decoder=g2m',
|
||||
'--disable-decoder=gdv',
|
||||
'--disable-decoder=gem',
|
||||
'--disable-decoder=gif',
|
||||
'--disable-decoder=h261',
|
||||
'--disable-decoder=h263',
|
||||
'--disable-decoder=h263i',
|
||||
'--disable-decoder=h263p',
|
||||
'--disable-decoder=h264',
|
||||
'--disable-decoder=hap',
|
||||
'--disable-decoder=hevc',
|
||||
'--disable-decoder=hnm4_video',
|
||||
'--disable-decoder=hq_hqa',
|
||||
'--disable-decoder=hqx',
|
||||
'--disable-decoder=huffyuv',
|
||||
'--disable-decoder=hymt',
|
||||
'--disable-decoder=idcin',
|
||||
'--disable-decoder=idf',
|
||||
'--disable-decoder=iff_ilbm',
|
||||
'--disable-decoder=imm4',
|
||||
'--disable-decoder=indeo2',
|
||||
'--disable-decoder=indeo3',
|
||||
'--disable-decoder=indeo4',
|
||||
'--disable-decoder=indeo5',
|
||||
'--disable-decoder=interplay_video',
|
||||
'--disable-decoder=ipu',
|
||||
'--disable-decoder=jacosub',
|
||||
'--disable-decoder=jpeg2000',
|
||||
'--disable-decoder=jpegls',
|
||||
'--disable-decoder=jv',
|
||||
'--disable-decoder=kgv1',
|
||||
'--disable-decoder=kmvc',
|
||||
'--disable-decoder=lagarith',
|
||||
'--disable-decoder=loco',
|
||||
'--disable-decoder=lscr',
|
||||
'--disable-decoder=m101',
|
||||
'--disable-decoder=magicyuv',
|
||||
'--disable-decoder=mdec',
|
||||
'--disable-decoder=microdvd',
|
||||
'--disable-decoder=mimic',
|
||||
'--disable-decoder=mjpeg',
|
||||
'--disable-decoder=mmvideo',
|
||||
'--disable-decoder=mpl2',
|
||||
'--disable-decoder=mobiclip',
|
||||
'--disable-decoder=motionpixels',
|
||||
'--disable-decoder=movtext',
|
||||
'--disable-decoder=mpeg1video',
|
||||
'--disable-decoder=mpeg2video',
|
||||
'--disable-decoder=mpeg4',
|
||||
'--disable-decoder=mpegvideo',
|
||||
'--disable-decoder=msa1',
|
||||
'--disable-decoder=mscc',
|
||||
'--disable-decoder=msmpeg4_crystalhd',
|
||||
'--disable-decoder=msmpeg4v1',
|
||||
'--disable-decoder=msmpeg4v2',
|
||||
'--disable-decoder=msmpeg4v3',
|
||||
'--disable-decoder=msp2',
|
||||
'--disable-decoder=msrle',
|
||||
'--disable-decoder=mss1',
|
||||
'--disable-decoder=msvideo1',
|
||||
'--disable-decoder=mszh',
|
||||
'--disable-decoder=mts2',
|
||||
'--disable-decoder=mv30',
|
||||
'--disable-decoder=mvc1',
|
||||
'--disable-decoder=mvc2',
|
||||
'--disable-decoder=mvdv',
|
||||
'--disable-decoder=mvha',
|
||||
'--disable-decoder=mwsc',
|
||||
'--disable-decoder=notchlc',
|
||||
'--disable-decoder=nuv',
|
||||
'--disable-decoder=on2avc',
|
||||
'--disable-decoder=paf_video',
|
||||
'--disable-decoder=pam',
|
||||
'--disable-decoder=pbm',
|
||||
'--disable-decoder=pcx',
|
||||
'--disable-decoder=pgm',
|
||||
'--disable-decoder=pgmyuv',
|
||||
'--disable-decoder=pgssub',
|
||||
'--disable-decoder=pgx',
|
||||
'--disable-decoder=phm',
|
||||
'--disable-decoder=photocd',
|
||||
'--disable-decoder=png',
|
||||
'--disable-decoder=pictor',
|
||||
'--disable-decoder=pixlet',
|
||||
'--disable-decoder=pjs',
|
||||
'--disable-decoder=ppm',
|
||||
'--disable-decoder=prores',
|
||||
'--disable-decoder=prosumer',
|
||||
'--disable-decoder=psd',
|
||||
'--disable-decoder=ptx',
|
||||
'--disable-decoder=qdraw',
|
||||
'--disable-decoder=qoi',
|
||||
'--disable-decoder=qpeg',
|
||||
'--disable-decoder=qtrle',
|
||||
'--disable-decoder=rawvideo',
|
||||
'--disable-decoder=r10k',
|
||||
'--disable-decoder=r210',
|
||||
'--disable-decoder=rasc',
|
||||
'--disable-decoder=realtext',
|
||||
'--disable-decoder=rl2',
|
||||
'--disable-decoder=rpza',
|
||||
'--disable-decoder=roq',
|
||||
'--disable-decoder=roq_dpcm',
|
||||
'--disable-decoder=rscc',
|
||||
@ -345,90 +498,152 @@ ffmpeg = FfmpegProject(
|
||||
'--disable-decoder=rv30',
|
||||
'--disable-decoder=rv40',
|
||||
'--disable-decoder=sami',
|
||||
'--disable-decoder=sanm',
|
||||
'--disable-decoder=scpr',
|
||||
'--disable-decoder=screenpresso',
|
||||
'--disable-decoder=sga',
|
||||
'--disable-decoder=sgi',
|
||||
'--disable-decoder=sgirle',
|
||||
'--disable-decoder=sheervideo',
|
||||
'--disable-decoder=simbiosis_imx',
|
||||
'--disable-decoder=smc',
|
||||
'--disable-decoder=snow',
|
||||
'--disable-decoder=speedhq',
|
||||
'--disable-decoder=srgc',
|
||||
'--disable-decoder=srt',
|
||||
'--disable-decoder=ssa',
|
||||
'--disable-decoder=stl',
|
||||
'--disable-decoder=subrip',
|
||||
'--disable-decoder=subviewer',
|
||||
'--disable-decoder=subviewer1',
|
||||
'--disable-decoder=sunrast',
|
||||
'--disable-decoder=svq1',
|
||||
'--disable-decoder=svq3',
|
||||
'--disable-decoder=targa',
|
||||
'--disable-decoder=targa_y216',
|
||||
'--disable-decoder=text',
|
||||
'--disable-decoder=tiff',
|
||||
'--disable-decoder=tiertexseqvideo',
|
||||
'--disable-decoder=tmv',
|
||||
'--disable-decoder=truemotion1',
|
||||
'--disable-decoder=truemotion2',
|
||||
'--disable-decoder=truemotion2rt',
|
||||
'--disable-decoder=tscc',
|
||||
'--disable-decoder=tscc2',
|
||||
'--disable-decoder=twinvq',
|
||||
'--disable-decoder=txd',
|
||||
'--disable-decoder=ulti',
|
||||
'--disable-decoder=utvideo',
|
||||
'--disable-decoder=v210',
|
||||
'--disable-decoder=v210x',
|
||||
'--disable-decoder=v308',
|
||||
'--disable-decoder=v408',
|
||||
'--disable-decoder=v410',
|
||||
'--disable-decoder=vb',
|
||||
'--disable-decoder=vble',
|
||||
'--disable-decoder=vbn',
|
||||
'--disable-decoder=vc1',
|
||||
'--disable-decoder=vcr1',
|
||||
'--disable-decoder=vmdvideo',
|
||||
'--disable-decoder=vmnc',
|
||||
'--disable-decoder=vp3',
|
||||
'--disable-decoder=vp5',
|
||||
'--disable-decoder=vp6',
|
||||
'--disable-decoder=vp7',
|
||||
'--disable-decoder=vp8',
|
||||
'--disable-decoder=vp9',
|
||||
'--disable-decoder=vplayer',
|
||||
'--disable-decoder=vqa',
|
||||
'--disable-decoder=webvtt',
|
||||
'--disable-decoder=wcmv',
|
||||
'--disable-decoder=wmv1',
|
||||
'--disable-decoder=wmv2',
|
||||
'--disable-decoder=wmv3',
|
||||
'--disable-decoder=wnv1',
|
||||
'--disable-decoder=wrapped_avframe',
|
||||
'--disable-decoder=xan_wc3',
|
||||
'--disable-decoder=xan_wc4',
|
||||
'--disable-decoder=xbin',
|
||||
'--disable-decoder=xbm',
|
||||
'--disable-decoder=xface',
|
||||
'--disable-decoder=xl',
|
||||
'--disable-decoder=xpm',
|
||||
'--disable-decoder=xsub',
|
||||
'--disable-decoder=xwd',
|
||||
'--disable-decoder=y41p',
|
||||
'--disable-decoder=ylc',
|
||||
'--disable-decoder=yop',
|
||||
'--disable-decoder=yuv4',
|
||||
'--disable-decoder=zero12v',
|
||||
'--disable-decoder=zerocodec',
|
||||
'--disable-decoder=zlib',
|
||||
'--disable-decoder=zmbv',
|
||||
|
||||
'--disable-bsf=av1_frame_merge',
|
||||
'--disable-bsf=av1_frame_split',
|
||||
'--disable-bsf=av1_metadata',
|
||||
'--disable-bsf=dts2pts',
|
||||
'--disable-bsf=h264_metadata',
|
||||
'--disable-bsf=h264_mp4toannexb',
|
||||
'--disable-bsf=h264_redundant_pps',
|
||||
'--disable-bsf=hevc_metadata',
|
||||
'--disable-bsf=hevc_mp4toannexb',
|
||||
'--disable-bsf=mjpeg2jpeg',
|
||||
'--disable-bsf=opus_metadata',
|
||||
'--disable-bsf=pgs_frame_merge',
|
||||
'--disable-bsf=text2movsub',
|
||||
'--disable-bsf=vp9_metadata',
|
||||
'--disable-bsf=vp9_raw_reorder',
|
||||
'--disable-bsf=vp9_superframe',
|
||||
'--disable-bsf=vp9_superframe_split',
|
||||
],
|
||||
)
|
||||
|
||||
openssl = OpenSSLProject(
|
||||
'https://www.openssl.org/source/openssl-3.0.0-alpha16.tar.gz',
|
||||
'08ce8244b59d75f40f91170dfcb012bf25309cdcb1fef9502e39d694f883d1d1',
|
||||
('https://www.openssl.org/source/openssl-3.1.3.tar.gz',
|
||||
'https://artfiles.org/openssl.org/source/openssl-3.1.3.tar.gz'),
|
||||
'f0316a2ebd89e7f2352976445458689f80302093788c466692fb2a188b2eacf6',
|
||||
'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-8.2.1.tar.xz',
|
||||
'https://github.com/curl/curl/releases/download/curl-8_2_1/curl-8.2.1.tar.xz'),
|
||||
'dd322f6bd0a20e6cebdfd388f69e98c3d183bed792cf4713c8a7ef498cba4894',
|
||||
'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_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=[
|
||||
'-DCURL_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',
|
||||
'https://github.com/sahlberg/libnfs/archive/libnfs-5.0.2.tar.gz',
|
||||
'637e56643b19da9fba98f06847788c4dad308b723156a64748041035dcdf9bd3',
|
||||
'lib/libnfs.a',
|
||||
[
|
||||
'--disable-shared', '--enable-static',
|
||||
@ -439,8 +654,7 @@ libnfs = AutotoolsProject(
|
||||
|
||||
'--disable-utils', '--disable-examples',
|
||||
],
|
||||
base='libnfs-libnfs-4.0.0',
|
||||
patches='src/lib/nfs/patches',
|
||||
base='libnfs-libnfs-5.0.2',
|
||||
autoreconf=True,
|
||||
)
|
||||
|
||||
@ -451,7 +665,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.81.0/source/boost_1_81_0.tar.bz2',
|
||||
'71feeed900fbccca04a3b4f2f84a7c217186f28a940ed8b7ed4725986baf99fa',
|
||||
'include/boost/version.hpp',
|
||||
)
|
||||
|
@ -1,28 +1,35 @@
|
||||
import subprocess
|
||||
import subprocess, multiprocessing
|
||||
from typing import Optional, Sequence, Union
|
||||
|
||||
from build.project import Project
|
||||
from .toolchain import AnyToolchain
|
||||
|
||||
class MakeProject(Project):
|
||||
def __init__(self, url, md5, installed,
|
||||
install_target='install',
|
||||
def __init__(self, url: Union[str, Sequence[str]], md5: str, installed: str,
|
||||
install_target: str='install',
|
||||
**kwargs):
|
||||
Project.__init__(self, url, md5, installed, **kwargs)
|
||||
self.install_target = install_target
|
||||
|
||||
def get_simultaneous_jobs(self):
|
||||
return 12
|
||||
def get_simultaneous_jobs(self) -> int:
|
||||
try:
|
||||
# use twice as many simultaneous jobs as we have CPU cores
|
||||
return multiprocessing.cpu_count() * 2
|
||||
except NotImplementedError:
|
||||
# default to 12, if multiprocessing.cpu_count() is not implemented
|
||||
return 12
|
||||
|
||||
def get_make_args(self, toolchain):
|
||||
def get_make_args(self, toolchain: AnyToolchain) -> list[str]:
|
||||
return ['--quiet', '-j' + str(self.get_simultaneous_jobs())]
|
||||
|
||||
def get_make_install_args(self, toolchain):
|
||||
def get_make_install_args(self, toolchain: AnyToolchain) -> list[str]:
|
||||
return ['--quiet', self.install_target]
|
||||
|
||||
def make(self, toolchain, wd, args):
|
||||
subprocess.check_call(['/usr/bin/make'] + args,
|
||||
def make(self, toolchain: AnyToolchain, wd: str, args: list[str]) -> None:
|
||||
subprocess.check_call(['make'] + args,
|
||||
cwd=wd, env=toolchain.env)
|
||||
|
||||
def build(self, toolchain, wd, install=True):
|
||||
def build_make(self, toolchain: AnyToolchain, wd: str, install: bool=True) -> None:
|
||||
self.make(toolchain, wd, self.get_make_args(toolchain))
|
||||
if install:
|
||||
self.make(toolchain, wd, self.get_make_install_args(toolchain))
|
||||
|
@ -1,8 +1,17 @@
|
||||
import os.path, subprocess, sys
|
||||
import os
|
||||
import subprocess
|
||||
import platform
|
||||
from typing import Optional, Sequence, Union
|
||||
|
||||
from build.project import Project
|
||||
from .toolchain import AnyToolchain
|
||||
|
||||
def make_cross_file(toolchain):
|
||||
def __no_ccache(cmd: str) -> str:
|
||||
if cmd.startswith('ccache '):
|
||||
cmd = cmd[7:]
|
||||
return cmd
|
||||
|
||||
def make_cross_file(toolchain: AnyToolchain) -> str:
|
||||
if toolchain.is_windows:
|
||||
system = 'windows'
|
||||
windres = "windres = '%s'" % toolchain.windres
|
||||
@ -21,7 +30,7 @@ def make_cross_file(toolchain):
|
||||
cpu = 'arm64-v8a'
|
||||
else:
|
||||
cpu_family = 'x86'
|
||||
if 'x86_64' in toolchain.arch:
|
||||
if 'x86_64' in toolchain.host_triplet:
|
||||
cpu = 'x86_64'
|
||||
else:
|
||||
cpu = 'i686'
|
||||
@ -34,63 +43,67 @@ 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 = '{__no_ccache(toolchain.cc)}'
|
||||
cpp = '{__no_ccache(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'
|
||||
|
||||
c_args = %s
|
||||
c_link_args = %s
|
||||
|
||||
cpp_args = %s
|
||||
cpp_link_args = %s
|
||||
root = '{toolchain.install_prefix}'
|
||||
""")
|
||||
|
||||
if toolchain.is_android:
|
||||
f.write("""
|
||||
# Keep Meson from executing Android-x86 test binariees
|
||||
needs_exe_wrapper = true
|
||||
""")
|
||||
|
||||
f.write(f"""
|
||||
[built-in options]
|
||||
c_args = {repr((toolchain.cppflags + ' ' + toolchain.cflags).split())}
|
||||
c_link_args = {repr(toolchain.ldflags.split() + toolchain.libs.split())}
|
||||
|
||||
cpp_args = {repr((toolchain.cppflags + ' ' + toolchain.cxxflags).split())}
|
||||
cpp_link_args = {repr(toolchain.ldflags.split() + toolchain.libs.split())}
|
||||
""")
|
||||
|
||||
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=()):
|
||||
cross_file = make_cross_file(toolchain)
|
||||
def configure(toolchain: AnyToolchain, src: str, build: str, args: list[str]=[]) -> None:
|
||||
configure = [
|
||||
'meson',
|
||||
src, build,
|
||||
'meson', 'setup',
|
||||
build, src,
|
||||
|
||||
'--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',
|
||||
|
||||
'--cross-file', cross_file,
|
||||
] + args
|
||||
|
||||
if toolchain.host_triplet is not None:
|
||||
# cross-compiling: write a cross-file
|
||||
cross_file = make_cross_file(toolchain)
|
||||
configure.append(f'--cross-file={cross_file}')
|
||||
|
||||
env = toolchain.env.copy()
|
||||
|
||||
# Meson 0.54 requires the BOOST_ROOT environment variable
|
||||
@ -99,18 +112,19 @@ def configure(toolchain, src, build, args=()):
|
||||
subprocess.check_call(configure, env=env)
|
||||
|
||||
class MesonProject(Project):
|
||||
def __init__(self, url, md5, installed, configure_args=[],
|
||||
def __init__(self, url: Union[str, Sequence[str]], md5: str, installed: str,
|
||||
configure_args: list[str]=[],
|
||||
**kwargs):
|
||||
Project.__init__(self, url, md5, installed, **kwargs)
|
||||
self.configure_args = configure_args
|
||||
|
||||
def configure(self, toolchain):
|
||||
def configure(self, toolchain: AnyToolchain) -> str:
|
||||
src = self.unpack(toolchain)
|
||||
build = self.make_build_path(toolchain)
|
||||
configure(toolchain, src, build, self.configure_args)
|
||||
return build
|
||||
|
||||
def build(self, toolchain):
|
||||
def _build(self, toolchain: AnyToolchain) -> None:
|
||||
build = self.configure(toolchain)
|
||||
subprocess.check_call(['ninja', 'install'],
|
||||
subprocess.check_call(['ninja', '-v', 'install'],
|
||||
cwd=build, env=toolchain.env)
|
||||
|
@ -1,13 +1,15 @@
|
||||
import subprocess
|
||||
from typing import Optional, Sequence, Union
|
||||
|
||||
from build.makeproject import MakeProject
|
||||
from .toolchain import AnyToolchain
|
||||
|
||||
class OpenSSLProject(MakeProject):
|
||||
def __init__(self, url, md5, installed,
|
||||
def __init__(self, url: Union[str, Sequence[str]], md5: str, installed: str,
|
||||
**kwargs):
|
||||
MakeProject.__init__(self, url, md5, installed, install_target='install_dev', **kwargs)
|
||||
|
||||
def get_make_args(self, toolchain):
|
||||
def get_make_args(self, toolchain: AnyToolchain) -> list[str]:
|
||||
return MakeProject.get_make_args(self, toolchain) + [
|
||||
'CC=' + toolchain.cc,
|
||||
'CFLAGS=' + toolchain.cflags,
|
||||
@ -17,39 +19,60 @@ class OpenSSLProject(MakeProject):
|
||||
'build_libs',
|
||||
]
|
||||
|
||||
def build(self, toolchain):
|
||||
def get_make_install_args(self, toolchain: AnyToolchain) -> list[str]:
|
||||
# OpenSSL's Makefile runs "ranlib" during installation
|
||||
return MakeProject.get_make_install_args(self, toolchain) + [
|
||||
'RANLIB=' + toolchain.ranlib,
|
||||
]
|
||||
|
||||
def _build(self, toolchain: AnyToolchain) -> None:
|
||||
src = self.unpack(toolchain, out_of_tree=False)
|
||||
|
||||
# OpenSSL has a weird target architecture scheme with lots of
|
||||
# hard-coded architectures; this table translates between our
|
||||
# "toolchain_arch" (HOST_TRIPLET) and the OpenSSL target
|
||||
# host triplet and the OpenSSL target
|
||||
openssl_archs = {
|
||||
# not using "android-*" because those OpenSSL targets want
|
||||
# to know where the SDK is, but our own build scripts
|
||||
# prepared everything already to look like a regular Linux
|
||||
# build
|
||||
'arm-linux-androideabi': 'linux-generic32',
|
||||
'armv7a-linux-androideabi': 'linux-generic32',
|
||||
'aarch64-linux-android': 'linux-aarch64',
|
||||
'i686-linux-android': 'linux-x86-clang',
|
||||
'x86_64-linux-android': 'linux-x86_64-clang',
|
||||
|
||||
# Kobo
|
||||
# generic Linux
|
||||
'arm-linux-gnueabihf': 'linux-generic32',
|
||||
|
||||
# Windows
|
||||
'i686-w64-mingw32': 'mingw',
|
||||
'x86_64-w64-mingw32': 'mingw64',
|
||||
|
||||
# Apple
|
||||
'x86_64-apple-darwin': 'darwin64-x86_64-cc',
|
||||
'aarch64-apple-darwin': 'darwin64-arm64-cc',
|
||||
}
|
||||
|
||||
openssl_arch = openssl_archs[toolchain.arch]
|
||||
configure = [
|
||||
'./Configure',
|
||||
'no-shared',
|
||||
'no-module',
|
||||
'no-engine',
|
||||
'no-static-engine',
|
||||
'no-async',
|
||||
'no-tests',
|
||||
'no-makedepend',
|
||||
'--libdir=lib', # no "lib64" on amd64, please
|
||||
'--prefix=' + toolchain.install_prefix,
|
||||
]
|
||||
|
||||
subprocess.check_call(['./Configure',
|
||||
'no-shared',
|
||||
'no-module', 'no-engine', 'no-static-engine',
|
||||
'no-async',
|
||||
'no-tests',
|
||||
'no-asm', # "asm" causes build failures on Windows
|
||||
openssl_arch,
|
||||
'--prefix=' + toolchain.install_prefix],
|
||||
cwd=src, env=toolchain.env)
|
||||
MakeProject.build(self, toolchain, src)
|
||||
if toolchain.is_windows:
|
||||
# workaround for build failures
|
||||
configure.append('no-asm')
|
||||
|
||||
if toolchain.host_triplet is not None:
|
||||
configure.append(openssl_archs[toolchain.host_triplet])
|
||||
configure.append(f'--cross-compile-prefix={toolchain.host_triplet}-')
|
||||
|
||||
subprocess.check_call(configure, cwd=src, env=toolchain.env)
|
||||
self.build_make(toolchain, src)
|
||||
|
@ -1,26 +1,30 @@
|
||||
import os, shutil
|
||||
import re
|
||||
from typing import cast, BinaryIO, Optional, Sequence, Union
|
||||
|
||||
from build.download import download_and_verify
|
||||
from build.download import download_basename, download_and_verify
|
||||
from build.tar import untar
|
||||
from build.quilt import push_all
|
||||
from .toolchain import AnyToolchain
|
||||
|
||||
class Project:
|
||||
def __init__(self, url, md5, installed, name=None, version=None,
|
||||
base=None,
|
||||
patches=None,
|
||||
def __init__(self, url: Union[str, Sequence[str]], md5: str, installed: str,
|
||||
name: Optional[str]=None, version: Optional[str]=None,
|
||||
base: Optional[str]=None,
|
||||
patches: Optional[str]=None,
|
||||
edits=None,
|
||||
use_cxx=False):
|
||||
use_cxx: bool=False):
|
||||
if base is None:
|
||||
basename = os.path.basename(url)
|
||||
basename = download_basename(url)
|
||||
m = re.match(r'^(.+)\.(tar(\.(gz|bz2|xz|lzma))?|zip)$', basename)
|
||||
if not m: raise
|
||||
if not m: raise RuntimeError('Could not identify tarball name: ' + basename)
|
||||
self.base = m.group(1)
|
||||
else:
|
||||
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 not m: raise RuntimeError('Could not identify tarball name: ' + self.base)
|
||||
if name is None: name = m.group(1)
|
||||
if version is None: version = m.group(2)
|
||||
|
||||
@ -38,10 +42,10 @@ class Project:
|
||||
self.edits = edits
|
||||
self.use_cxx = use_cxx
|
||||
|
||||
def download(self, toolchain):
|
||||
def download(self, toolchain: AnyToolchain) -> str:
|
||||
return download_and_verify(self.url, self.md5, toolchain.tarball_path)
|
||||
|
||||
def is_installed(self, toolchain):
|
||||
def is_installed(self, toolchain: AnyToolchain) -> bool:
|
||||
tarball = self.download(toolchain)
|
||||
installed = os.path.join(toolchain.install_prefix, self.installed)
|
||||
tarball_mtime = os.path.getmtime(tarball)
|
||||
@ -50,13 +54,13 @@ class Project:
|
||||
except FileNotFoundError:
|
||||
return False
|
||||
|
||||
def unpack(self, toolchain, out_of_tree=True):
|
||||
def unpack(self, toolchain: AnyToolchain, out_of_tree: bool=True) -> str:
|
||||
if out_of_tree:
|
||||
parent_path = toolchain.src_path
|
||||
else:
|
||||
parent_path = toolchain.build_path
|
||||
path = untar(self.download(toolchain), parent_path, self.base)
|
||||
|
||||
path = untar(self.download(toolchain), parent_path, self.base,
|
||||
lazy=out_of_tree and self.patches is None)
|
||||
if self.patches is not None:
|
||||
push_all(toolchain, path, self.patches)
|
||||
|
||||
@ -71,11 +75,16 @@ class Project:
|
||||
|
||||
return path
|
||||
|
||||
def make_build_path(self, toolchain):
|
||||
def make_build_path(self, toolchain: AnyToolchain, lazy: bool=False) -> str:
|
||||
path = os.path.join(toolchain.build_path, self.base)
|
||||
if lazy and os.path.isdir(path):
|
||||
return path
|
||||
try:
|
||||
shutil.rmtree(path)
|
||||
except FileNotFoundError:
|
||||
pass
|
||||
os.makedirs(path, exist_ok=True)
|
||||
return path
|
||||
|
||||
def build(self, toolchain: AnyToolchain) -> None:
|
||||
self._build(toolchain)
|
||||
|
@ -1,9 +1,12 @@
|
||||
import subprocess
|
||||
from typing import Union
|
||||
|
||||
def run_quilt(toolchain, cwd, patches_path, *args):
|
||||
from .toolchain import AnyToolchain
|
||||
|
||||
def run_quilt(toolchain: AnyToolchain, cwd: str, patches_path: str, *args: str) -> None:
|
||||
env = dict(toolchain.env)
|
||||
env['QUILT_PATCHES'] = patches_path
|
||||
subprocess.check_call(['quilt'] + list(args), cwd=cwd, env=env)
|
||||
|
||||
def push_all(toolchain, src_path, patches_path):
|
||||
def push_all(toolchain: AnyToolchain, src_path: str, patches_path: str) -> None:
|
||||
run_quilt(toolchain, src_path, patches_path, 'push', '-a')
|
||||
|
@ -1,14 +1,17 @@
|
||||
import os, shutil, subprocess
|
||||
|
||||
def untar(tarball_path, parent_path, base):
|
||||
def untar(tarball_path: str, parent_path: str, base: str,
|
||||
lazy: bool=False) -> str:
|
||||
path = os.path.join(parent_path, base)
|
||||
if lazy and os.path.isdir(path):
|
||||
return path
|
||||
try:
|
||||
shutil.rmtree(path)
|
||||
except FileNotFoundError:
|
||||
pass
|
||||
os.makedirs(parent_path, exist_ok=True)
|
||||
try:
|
||||
subprocess.check_call(['/bin/tar', 'xfC', tarball_path, parent_path])
|
||||
subprocess.check_call(['tar', 'xfC', tarball_path, parent_path])
|
||||
except FileNotFoundError:
|
||||
import tarfile
|
||||
tar = tarfile.open(tarball_path)
|
||||
|
175
python/build/toolchain.py
Normal file
175
python/build/toolchain.py
Normal file
@ -0,0 +1,175 @@
|
||||
import os.path
|
||||
import shutil
|
||||
from typing import Union
|
||||
|
||||
android_abis = {
|
||||
'armeabi-v7a': {
|
||||
'arch': 'armv7a-linux-androideabi',
|
||||
'ndk_arch': 'arm',
|
||||
'cflags': '-fpic -mfpu=neon -mfloat-abi=softfp',
|
||||
},
|
||||
|
||||
'arm64-v8a': {
|
||||
'arch': 'aarch64-linux-android',
|
||||
'ndk_arch': 'arm64',
|
||||
'cflags': '-fpic',
|
||||
},
|
||||
|
||||
'x86': {
|
||||
'arch': 'i686-linux-android',
|
||||
'ndk_arch': 'x86',
|
||||
'cflags': '-fPIC -march=i686 -mtune=intel -mssse3 -mfpmath=sse -m32',
|
||||
},
|
||||
|
||||
'x86_64': {
|
||||
'arch': 'x86_64-linux-android',
|
||||
'ndk_arch': 'x86_64',
|
||||
'cflags': '-fPIC -m64',
|
||||
},
|
||||
}
|
||||
|
||||
class AndroidNdkToolchain:
|
||||
def __init__(self, top_path: str, lib_path: str,
|
||||
tarball_path: str, src_path: str,
|
||||
ndk_path: str, android_abi: str,
|
||||
use_cxx):
|
||||
# build host configuration
|
||||
build_arch = 'linux-x86_64'
|
||||
|
||||
# select the NDK target
|
||||
abi_info = android_abis[android_abi]
|
||||
host_triplet = abi_info['arch']
|
||||
|
||||
arch_path = os.path.join(lib_path, host_triplet)
|
||||
|
||||
self.tarball_path = tarball_path
|
||||
self.src_path = src_path
|
||||
self.build_path = os.path.join(arch_path, 'build')
|
||||
|
||||
ndk_arch = abi_info['ndk_arch']
|
||||
android_api_level = '24'
|
||||
|
||||
install_prefix = os.path.join(arch_path, 'root')
|
||||
|
||||
self.host_triplet = host_triplet
|
||||
self.install_prefix = install_prefix
|
||||
|
||||
llvm_path = os.path.join(ndk_path, 'toolchains', 'llvm', 'prebuilt', build_arch)
|
||||
llvm_triple = host_triplet + android_api_level
|
||||
|
||||
common_flags = '-Os -g'
|
||||
common_flags += ' ' + abi_info['cflags']
|
||||
|
||||
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
|
||||
|
||||
common_flags += ' -fvisibility=hidden -fdata-sections -ffunction-sections'
|
||||
|
||||
self.ar = os.path.join(llvm_bin, 'llvm-ar')
|
||||
self.arflags = 'rcs'
|
||||
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
|
||||
self.cppflags = ' -isystem ' + os.path.join(install_prefix, 'include')
|
||||
self.ldflags = ' -L' + os.path.join(install_prefix, 'lib') + \
|
||||
' -Wl,--exclude-libs=ALL' + \
|
||||
' ' + common_flags
|
||||
self.ldflags = common_flags
|
||||
self.libs = ''
|
||||
|
||||
self.is_arm = ndk_arch == 'arm'
|
||||
self.is_armv7 = self.is_arm and 'armv7' in self.cflags
|
||||
self.is_aarch64 = ndk_arch == 'arm64'
|
||||
self.is_windows = False
|
||||
self.is_android = True
|
||||
self.is_darwin = False
|
||||
|
||||
libstdcxx_flags = ''
|
||||
libstdcxx_cxxflags = ''
|
||||
libstdcxx_ldflags = ''
|
||||
libstdcxx_libs = '-static-libstdc++'
|
||||
|
||||
if self.is_armv7:
|
||||
# On 32 bit ARM, clang generates no ".eh_frame" section;
|
||||
# instead, the LLVM unwinder library is used for unwinding
|
||||
# the stack after a C++ exception was thrown
|
||||
libstdcxx_libs += ' -lunwind'
|
||||
|
||||
if use_cxx:
|
||||
self.cxxflags += ' ' + libstdcxx_cxxflags
|
||||
self.ldflags += ' ' + libstdcxx_ldflags
|
||||
self.libs += ' ' + libstdcxx_libs
|
||||
|
||||
self.env = dict(os.environ)
|
||||
|
||||
# redirect pkg-config to use our root directory instead of the
|
||||
# default one on the build host
|
||||
bin_dir = os.path.join(install_prefix, 'bin')
|
||||
os.makedirs(bin_dir, exist_ok=True)
|
||||
self.pkg_config = shutil.copy(os.path.join(top_path, 'build', 'pkg-config.sh'),
|
||||
os.path.join(bin_dir, 'pkg-config'))
|
||||
self.env['PKG_CONFIG'] = self.pkg_config
|
||||
|
||||
class MingwToolchain:
|
||||
def __init__(self, top_path: str,
|
||||
toolchain_path, host_triplet, x64: bool,
|
||||
tarball_path, src_path, build_path, install_prefix):
|
||||
self.host_triplet = host_triplet
|
||||
self.tarball_path = tarball_path
|
||||
self.src_path = src_path
|
||||
self.build_path = build_path
|
||||
self.install_prefix = install_prefix
|
||||
|
||||
toolchain_bin = os.path.join(toolchain_path, 'bin')
|
||||
self.cc = os.path.join(toolchain_bin, host_triplet + '-gcc')
|
||||
self.cxx = os.path.join(toolchain_bin, host_triplet + '-g++')
|
||||
self.ar = os.path.join(toolchain_bin, host_triplet + '-ar')
|
||||
self.arflags = 'rcs'
|
||||
self.ranlib = os.path.join(toolchain_bin, host_triplet + '-ranlib')
|
||||
self.nm = os.path.join(toolchain_bin, host_triplet + '-nm')
|
||||
self.strip = os.path.join(toolchain_bin, host_triplet + '-strip')
|
||||
self.windres = os.path.join(toolchain_bin, host_triplet + '-windres')
|
||||
|
||||
common_flags = '-O2 -g'
|
||||
|
||||
if not x64:
|
||||
# enable SSE support which is required for LAME
|
||||
common_flags += ' -march=pentium3'
|
||||
|
||||
self.cflags = common_flags
|
||||
self.cxxflags = common_flags
|
||||
self.cppflags = '-isystem ' + os.path.join(install_prefix, 'include') + \
|
||||
' -DWINVER=0x0600 -D_WIN32_WINNT=0x0600'
|
||||
self.ldflags = '-L' + os.path.join(install_prefix, 'lib') + \
|
||||
' -static-libstdc++ -static-libgcc'
|
||||
self.libs = ''
|
||||
|
||||
# Explicitly disable _FORTIFY_SOURCE because it is broken with
|
||||
# mingw. This prevents some libraries such as libFLAC to
|
||||
# enable it.
|
||||
self.cppflags += ' -D_FORTIFY_SOURCE=0'
|
||||
|
||||
self.is_arm = host_triplet.startswith('arm')
|
||||
self.is_armv7 = self.is_arm and 'armv7' in self.cflags
|
||||
self.is_aarch64 = host_triplet == 'aarch64'
|
||||
self.is_windows = 'mingw32' in host_triplet
|
||||
self.is_android = False
|
||||
self.is_darwin = False
|
||||
|
||||
self.env = dict(os.environ)
|
||||
|
||||
# redirect pkg-config to use our root directory instead of the
|
||||
# default one on the build host
|
||||
import shutil
|
||||
bin_dir = os.path.join(install_prefix, 'bin')
|
||||
os.makedirs(bin_dir, exist_ok=True)
|
||||
self.pkg_config = shutil.copy(os.path.join(top_path, 'build', 'pkg-config.sh'),
|
||||
os.path.join(bin_dir, 'pkg-config'))
|
||||
self.env['PKG_CONFIG'] = self.pkg_config
|
||||
|
||||
AnyToolchain = Union[AndroidNdkToolchain, MingwToolchain]
|
@ -1,6 +1,7 @@
|
||||
import hashlib
|
||||
from typing import cast, Any, BinaryIO
|
||||
|
||||
def feed_file(h, f):
|
||||
def feed_file(h: Any, f: BinaryIO) -> None:
|
||||
"""Feed data read from an open file into the hashlib instance."""
|
||||
|
||||
while True:
|
||||
@ -10,20 +11,20 @@ def feed_file(h, f):
|
||||
break
|
||||
h.update(data)
|
||||
|
||||
def feed_file_path(h, path):
|
||||
def feed_file_path(h: Any, path: str) -> None:
|
||||
"""Feed data read from a file (to be opened by this function) into the hashlib instance."""
|
||||
|
||||
with open(path, 'rb') as f:
|
||||
feed_file(h, f)
|
||||
|
||||
def file_digest(algorithm, path):
|
||||
def file_digest(algorithm: Any, path: str) -> str:
|
||||
"""Calculate the digest of a file and return it in hexadecimal notation."""
|
||||
|
||||
h = algorithm()
|
||||
feed_file_path(h, path)
|
||||
return h.hexdigest()
|
||||
return cast(str, h.hexdigest())
|
||||
|
||||
def guess_digest_algorithm(digest):
|
||||
def guess_digest_algorithm(digest: str) -> Any:
|
||||
l = len(digest)
|
||||
if l == 32:
|
||||
return hashlib.md5
|
||||
@ -31,10 +32,12 @@ def guess_digest_algorithm(digest):
|
||||
return hashlib.sha1
|
||||
elif l == 64:
|
||||
return hashlib.sha256
|
||||
elif l == 128:
|
||||
return hashlib.sha512
|
||||
else:
|
||||
return None
|
||||
|
||||
def verify_file_digest(path, expected_digest):
|
||||
def verify_file_digest(path: str, expected_digest: str) -> bool:
|
||||
"""Verify the digest of a file, and return True if the digest matches with the given expected digest."""
|
||||
|
||||
algorithm = guess_digest_algorithm(expected_digest)
|
||||
|
@ -1,22 +1,34 @@
|
||||
import os.path, subprocess
|
||||
import subprocess
|
||||
from typing import Optional, Sequence, Union
|
||||
|
||||
from build.project import Project
|
||||
from build.makeproject import MakeProject
|
||||
from .toolchain import AnyToolchain
|
||||
|
||||
class ZlibProject(Project):
|
||||
def __init__(self, url, md5, installed,
|
||||
class ZlibProject(MakeProject):
|
||||
def __init__(self, url: Union[str, Sequence[str]], md5: str, installed: str,
|
||||
**kwargs):
|
||||
Project.__init__(self, url, md5, installed, **kwargs)
|
||||
MakeProject.__init__(self, url, md5, installed, **kwargs)
|
||||
|
||||
def build(self, toolchain):
|
||||
def get_make_args(self, toolchain: AnyToolchain) -> list[str]:
|
||||
return MakeProject.get_make_args(self, toolchain) + [
|
||||
'CC=' + toolchain.cc + ' ' + toolchain.cppflags + ' ' + toolchain.cflags,
|
||||
'CPP=' + toolchain.cc + ' -E ' + toolchain.cppflags,
|
||||
'AR=' + toolchain.ar,
|
||||
'ARFLAGS=' + toolchain.arflags,
|
||||
'RANLIB=' + toolchain.ranlib,
|
||||
'LDSHARED=' + toolchain.cc + ' -shared',
|
||||
'libz.a'
|
||||
]
|
||||
|
||||
def get_make_install_args(self, toolchain: AnyToolchain) -> list[str]:
|
||||
return [
|
||||
'RANLIB=' + toolchain.ranlib,
|
||||
self.install_target
|
||||
]
|
||||
|
||||
def _build(self, toolchain: AnyToolchain) -> None:
|
||||
src = self.unpack(toolchain, out_of_tree=False)
|
||||
|
||||
subprocess.check_call(['/usr/bin/make', '--quiet',
|
||||
'-f', 'win32/Makefile.gcc',
|
||||
'PREFIX=' + toolchain.arch + '-',
|
||||
'-j12',
|
||||
'install',
|
||||
'INCLUDE_PATH='+ os.path.join(toolchain.install_prefix, 'include'),
|
||||
'LIBRARY_PATH=' + os.path.join(toolchain.install_prefix, 'lib'),
|
||||
'BINARY_PATH=' + os.path.join(toolchain.install_prefix, 'bin'),
|
||||
],
|
||||
cwd=src, env=toolchain.env)
|
||||
subprocess.check_call(['./configure', '--prefix=' + toolchain.install_prefix, '--static'],
|
||||
cwd=src, env=toolchain.env)
|
||||
self.build_make(toolchain, src)
|
||||
|
@ -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"
|
||||
@ -85,6 +86,9 @@ enum Option {
|
||||
OPTION_KILL,
|
||||
OPTION_NO_CONFIG,
|
||||
OPTION_NO_DAEMON,
|
||||
#ifdef __linux__
|
||||
OPTION_SYSTEMD,
|
||||
#endif
|
||||
OPTION_STDOUT,
|
||||
OPTION_STDERR,
|
||||
OPTION_VERBOSE,
|
||||
@ -97,6 +101,9 @@ static constexpr OptionDef option_defs[] = {
|
||||
{"kill", "kill the currently running mpd session"},
|
||||
{"no-config", "don't read from config"},
|
||||
{"no-daemon", "don't detach from console"},
|
||||
#ifdef __linux__
|
||||
{"systemd", "systemd service mode"},
|
||||
#endif
|
||||
{"stdout", nullptr}, // hidden, compatibility with old versions
|
||||
{"stderr", "print messages to stderr"},
|
||||
{"verbose", 'v', "verbose logging"},
|
||||
@ -327,7 +334,7 @@ bool ConfigLoader::TryFile(const AllocatedPath &base_path, Path path)
|
||||
}
|
||||
|
||||
void
|
||||
ParseCommandLine(int argc, char **argv, struct options &options,
|
||||
ParseCommandLine(int argc, char **argv, CommandLineOptions &options,
|
||||
ConfigData &config)
|
||||
{
|
||||
bool use_config_file = true;
|
||||
@ -345,9 +352,20 @@ ParseCommandLine(int argc, char **argv, struct options &options,
|
||||
break;
|
||||
|
||||
case OPTION_NO_DAEMON:
|
||||
#ifdef ENABLE_DAEMON
|
||||
options.daemon = false;
|
||||
#endif
|
||||
break;
|
||||
|
||||
#ifdef __linux__
|
||||
case OPTION_SYSTEMD:
|
||||
#ifdef ENABLE_DAEMON
|
||||
options.daemon = false;
|
||||
#endif
|
||||
options.systemd = true;
|
||||
break;
|
||||
#endif
|
||||
|
||||
case OPTION_STDOUT:
|
||||
case OPTION_STDERR:
|
||||
options.log_stderr = true;
|
||||
|
@ -20,17 +20,29 @@
|
||||
#ifndef MPD_COMMAND_LINE_HXX
|
||||
#define MPD_COMMAND_LINE_HXX
|
||||
|
||||
#include "config.h" // for ENABLE_DAEMON
|
||||
|
||||
struct ConfigData;
|
||||
|
||||
struct options {
|
||||
struct CommandLineOptions {
|
||||
bool kill = false;
|
||||
|
||||
#ifdef ENABLE_DAEMON
|
||||
bool daemon = true;
|
||||
#else
|
||||
static constexpr bool daemon = false;
|
||||
#endif
|
||||
|
||||
#ifdef __linux__
|
||||
bool systemd = false;
|
||||
#endif
|
||||
|
||||
bool log_stderr = false;
|
||||
bool verbose = false;
|
||||
};
|
||||
|
||||
void
|
||||
ParseCommandLine(int argc, char **argv, struct options &options,
|
||||
ParseCommandLine(int argc, char **argv, CommandLineOptions &options,
|
||||
ConfigData &config);
|
||||
|
||||
#endif
|
||||
|
@ -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,17 @@
|
||||
#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/StandardDirectory.hxx"
|
||||
#include "fs/XDG.hxx"
|
||||
#include "util/Domain.hxx"
|
||||
#include "util/RuntimeError.hxx"
|
||||
|
||||
#include <sys/stat.h>
|
||||
|
||||
@ -41,6 +45,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
|
||||
@ -78,13 +86,10 @@ ListenXdgRuntimeDir(ClientListener &listener) noexcept
|
||||
use $XDG_RUNTIME_DIR */
|
||||
return false;
|
||||
|
||||
Path xdg_runtime_dir = Path::FromFS(getenv("XDG_RUNTIME_DIR"));
|
||||
if (xdg_runtime_dir.IsNull())
|
||||
const auto mpd_runtime_dir = GetAppRuntimeDir();
|
||||
if (mpd_runtime_dir.IsNull())
|
||||
return false;
|
||||
|
||||
const auto mpd_runtime_dir = xdg_runtime_dir / Path::FromFS("mpd");
|
||||
mkdir(mpd_runtime_dir.c_str(), 0700);
|
||||
|
||||
const auto socket_path = mpd_runtime_dir / Path::FromFS("socket");
|
||||
unlink(socket_path.c_str());
|
||||
|
||||
@ -98,9 +103,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"
|
||||
|
||||
@ -47,15 +48,14 @@ LocateFileUri(const char *uri, const Client *client
|
||||
/* this path was relative to the music
|
||||
directory */
|
||||
// TODO: don't use suffix.data() (ok for now because we know it's null-terminated)
|
||||
return LocatedUri(LocatedUri::Type::RELATIVE,
|
||||
suffix.data());
|
||||
return {LocatedUri::Type::RELATIVE, suffix.data()};
|
||||
}
|
||||
#endif
|
||||
|
||||
if (client != nullptr)
|
||||
client->AllowFile(path);
|
||||
|
||||
return LocatedUri(LocatedUri::Type::PATH, uri, std::move(path));
|
||||
return {LocatedUri::Type::PATH, uri, std::move(path)};
|
||||
}
|
||||
|
||||
static LocatedUri
|
||||
@ -67,9 +67,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:
|
||||
@ -85,12 +89,15 @@ LocateAbsoluteUri(UriPluginKind kind, const char *uri
|
||||
const auto suffix = storage->MapToRelativeUTF8(uri);
|
||||
if (suffix.data() != nullptr)
|
||||
// TODO: don't use suffix.data() (ok for now because we know it's null-terminated)
|
||||
return LocatedUri(LocatedUri::Type::RELATIVE,
|
||||
suffix.data());
|
||||
return {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);
|
||||
return {LocatedUri::Type::ABSOLUTE, uri};
|
||||
}
|
||||
|
||||
LocatedUri
|
||||
@ -105,7 +112,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);
|
||||
}
|
||||
|
142
src/Log.hxx
142
src/Log.hxx
@ -21,29 +21,83 @@
|
||||
#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 >= 90000
|
||||
return LogVFmt(level, domain, format_str,
|
||||
fmt::make_format_args(args...));
|
||||
#elif 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 +105,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 +147,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 */
|
||||
|
@ -19,6 +19,7 @@
|
||||
|
||||
#include "LogBackend.hxx"
|
||||
#include "Log.hxx"
|
||||
#include "util/Compiler.h"
|
||||
#include "util/Domain.hxx"
|
||||
#include "util/StringStrip.hxx"
|
||||
#include "Version.h"
|
||||
@ -103,15 +104,14 @@ 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
|
||||
|
||||
gcc_const
|
||||
[[gnu::const]]
|
||||
static int
|
||||
ToSysLogLevel(LogLevel log_level) noexcept
|
||||
{
|
||||
@ -137,11 +137,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 +161,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 +178,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)
|
||||
|
@ -158,12 +158,15 @@ log_init(const ConfigData &config, bool verbose, bool use_stdout)
|
||||
getenv("NOTIFY_SOCKET") != nullptr) {
|
||||
/* if MPD was started as a systemd
|
||||
service, default to journal (which
|
||||
is connected to fd=2) */
|
||||
is connected to stdout&stderr) */
|
||||
out_fd = STDOUT_FILENO;
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
#ifndef HAVE_SYSLOG
|
||||
#ifdef _WIN32
|
||||
/* default to stdout on Windows */
|
||||
out_fd = STDOUT_FILENO;
|
||||
#elif !defined(HAVE_SYSLOG)
|
||||
throw std::runtime_error("config parameter 'log_file' not found");
|
||||
#endif
|
||||
#ifdef HAVE_SYSLOG
|
||||
@ -234,22 +237,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
|
||||
}
|
||||
|
182
src/Main.cxx
182
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"
|
||||
@ -141,14 +142,24 @@ struct Config {
|
||||
#ifdef ENABLE_DAEMON
|
||||
|
||||
static void
|
||||
glue_daemonize_init(const struct options *options,
|
||||
glue_daemonize_init(const CommandLineOptions &options,
|
||||
const ConfigData &config)
|
||||
{
|
||||
auto pid_file = config.GetPath(ConfigOption::PID_FILE);
|
||||
|
||||
#ifdef __linux__
|
||||
if (options.systemd && pid_file != nullptr) {
|
||||
pid_file = nullptr;
|
||||
fprintf(stderr,
|
||||
"Ignoring the 'pid_file' setting in systemd mode\n");
|
||||
}
|
||||
#endif
|
||||
|
||||
daemonize_init(config.GetString(ConfigOption::USER),
|
||||
config.GetString(ConfigOption::GROUP),
|
||||
config.GetPath(ConfigOption::PID_FILE));
|
||||
std::move(pid_file));
|
||||
|
||||
if (options->kill)
|
||||
if (options.kill)
|
||||
daemonize_kill();
|
||||
}
|
||||
|
||||
@ -157,7 +168,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 +307,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 +354,7 @@ Instance::BeginShutdownUpdate() noexcept
|
||||
{
|
||||
#ifdef ENABLE_DATABASE
|
||||
#ifdef ENABLE_INOTIFY
|
||||
mpd_inotify_finish();
|
||||
inotify_update.reset();
|
||||
#endif
|
||||
|
||||
if (update != nullptr)
|
||||
@ -351,7 +371,8 @@ Instance::BeginShutdownPartitions() noexcept
|
||||
}
|
||||
|
||||
static inline void
|
||||
MainConfigured(const struct options &options, const ConfigData &raw_config)
|
||||
MainConfigured(const CommandLineOptions &options,
|
||||
const ConfigData &raw_config)
|
||||
{
|
||||
#ifdef ENABLE_DAEMON
|
||||
daemonize_close_stdin();
|
||||
@ -374,7 +395,7 @@ MainConfigured(const struct options &options, const ConfigData &raw_config)
|
||||
const Config config(raw_config);
|
||||
|
||||
#ifdef ENABLE_DAEMON
|
||||
glue_daemonize_init(&options, raw_config);
|
||||
glue_daemonize_init(options, raw_config);
|
||||
#endif
|
||||
|
||||
TagLoadConfig(raw_config);
|
||||
@ -441,7 +462,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();
|
||||
@ -460,7 +482,10 @@ MainConfigured(const struct options &options, const ConfigData &raw_config)
|
||||
#ifndef ANDROID
|
||||
setup_log_output();
|
||||
|
||||
const ScopeSignalHandlersInit signal_handlers_init(instance);
|
||||
const ScopeSignalHandlersInit signal_handlers_init{
|
||||
instance,
|
||||
options.daemon,
|
||||
};
|
||||
#endif
|
||||
|
||||
instance.io_thread.Start();
|
||||
@ -476,7 +501,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 +537,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,27 +588,51 @@ MainConfigured(const struct options &options, const ConfigData &raw_config)
|
||||
instance.state_file->Write();
|
||||
|
||||
instance.BeginShutdownUpdate();
|
||||
|
||||
ZeroconfDeinit();
|
||||
|
||||
instance.BeginShutdownPartitions();
|
||||
}
|
||||
|
||||
#ifdef ANDROID
|
||||
|
||||
static void
|
||||
AndroidMain()
|
||||
/**
|
||||
* Wrapper for ReadConfigFile() which returns false if the file was
|
||||
* not found.
|
||||
*/
|
||||
static bool
|
||||
TryReadConfigFile(ConfigData &config, Path path)
|
||||
{
|
||||
struct options options;
|
||||
if (!FileExists(path))
|
||||
return false;
|
||||
|
||||
ReadConfigFile(config, path);
|
||||
return true;
|
||||
}
|
||||
|
||||
static void
|
||||
LoadConfigFile(JNIEnv *env, ConfigData &config)
|
||||
{
|
||||
/* try loading mpd.conf from
|
||||
"Android/data/org.musicpd/files/mpd.conf" (the app specific
|
||||
data directory) first */
|
||||
if (const auto dir = context->GetExternalFilesDir(env);
|
||||
!dir.IsNull() &&
|
||||
TryReadConfigFile(config, dir / Path::FromFS("mpd.conf")))
|
||||
return;
|
||||
|
||||
/* if that fails, attempt to load "mpd.conf" from the root of
|
||||
the SD card (pre-0.23.9, ceases to work since Android
|
||||
12) */
|
||||
if (const auto dir = Environment::getExternalStorageDirectory(env);
|
||||
!dir.IsNull())
|
||||
TryReadConfigFile(config, dir / Path::FromFS("mpd.conf"));
|
||||
}
|
||||
|
||||
static void
|
||||
AndroidMain(JNIEnv *env)
|
||||
{
|
||||
CommandLineOptions options;
|
||||
ConfigData raw_config;
|
||||
|
||||
const auto sdcard = Environment::getExternalStorageDirectory();
|
||||
if (!sdcard.IsNull()) {
|
||||
const auto config_path =
|
||||
sdcard / Path::FromFS("mpd.conf");
|
||||
if (FileExists(config_path))
|
||||
ReadConfigFile(raw_config, config_path);
|
||||
}
|
||||
LoadConfigFile(env, raw_config);
|
||||
|
||||
MainConfigured(options, raw_config);
|
||||
}
|
||||
@ -569,9 +644,12 @@ Java_org_musicpd_Bridge_run(JNIEnv *env, jclass, jobject _context, jobject _logL
|
||||
Java::Init(env);
|
||||
Java::Object::Initialise(env);
|
||||
Java::File::Initialise(env);
|
||||
|
||||
Environment::Initialise(env);
|
||||
AtScopeExit(env) { Environment::Deinitialise(env); };
|
||||
|
||||
Context::Initialise(env);
|
||||
|
||||
context = new Context(env, _context);
|
||||
AtScopeExit() { delete context; };
|
||||
|
||||
@ -580,7 +658,7 @@ Java_org_musicpd_Bridge_run(JNIEnv *env, jclass, jobject _context, jobject _logL
|
||||
AtScopeExit() { delete logListener; };
|
||||
|
||||
try {
|
||||
AndroidMain();
|
||||
AndroidMain(env);
|
||||
} catch (...) {
|
||||
LogError(std::current_exception());
|
||||
}
|
||||
@ -594,12 +672,21 @@ 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
|
||||
MainOrThrow(int argc, char *argv[])
|
||||
{
|
||||
struct options options;
|
||||
CommandLineOptions options;
|
||||
ConfigData raw_config;
|
||||
|
||||
ParseCommandLine(argc, argv, options, raw_config);
|
||||
@ -607,27 +694,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
|
||||
|
||||
|
@ -85,15 +85,15 @@ map_fs_to_utf8(Path path_fs) noexcept
|
||||
{
|
||||
if (path_fs.IsAbsolute()) {
|
||||
if (global_instance->storage == nullptr)
|
||||
return std::string();
|
||||
return {};
|
||||
|
||||
const auto music_dir_fs = global_instance->storage->MapFS("");
|
||||
if (music_dir_fs.IsNull())
|
||||
return std::string();
|
||||
return {};
|
||||
|
||||
auto relative = music_dir_fs.Relative(path_fs);
|
||||
if (relative == nullptr || StringIsEmpty(relative))
|
||||
return std::string();
|
||||
return {};
|
||||
|
||||
path_fs = Path::FromFS(relative);
|
||||
}
|
||||
|
@ -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;
|
||||
|
||||
|
@ -29,8 +29,8 @@ MusicBuffer::MusicBuffer(unsigned num_chunks)
|
||||
MusicChunkPtr
|
||||
MusicBuffer::Allocate() noexcept
|
||||
{
|
||||
const std::lock_guard<Mutex> protect(mutex);
|
||||
return MusicChunkPtr(buffer.Allocate(), MusicChunkDeleter(*this));
|
||||
const std::scoped_lock<Mutex> protect(mutex);
|
||||
return {buffer.Allocate(), MusicChunkDeleter(*this)};
|
||||
}
|
||||
|
||||
void
|
||||
@ -44,7 +44,7 @@ MusicBuffer::Return(MusicChunk *chunk) noexcept
|
||||
chunk->next.reset();
|
||||
chunk->other.reset();
|
||||
|
||||
const std::lock_guard<Mutex> protect(mutex);
|
||||
const std::scoped_lock<Mutex> protect(mutex);
|
||||
|
||||
assert(!chunk->other || !chunk->other->other);
|
||||
|
||||
|
@ -54,7 +54,7 @@ public:
|
||||
#endif
|
||||
|
||||
bool IsFull() const noexcept {
|
||||
const std::lock_guard<Mutex> protect(mutex);
|
||||
const std::scoped_lock<Mutex> protect(mutex);
|
||||
return buffer.IsFull();
|
||||
}
|
||||
|
||||
@ -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
|
||||
};
|
||||
|
@ -27,7 +27,7 @@
|
||||
bool
|
||||
MusicPipe::Contains(const MusicChunk *chunk) const noexcept
|
||||
{
|
||||
const std::lock_guard<Mutex> protect(mutex);
|
||||
const std::scoped_lock<Mutex> protect(mutex);
|
||||
|
||||
for (const MusicChunk *i = head.get(); i != nullptr; i = i->next.get())
|
||||
if (i == chunk)
|
||||
@ -41,7 +41,7 @@ MusicPipe::Contains(const MusicChunk *chunk) const noexcept
|
||||
MusicChunkPtr
|
||||
MusicPipe::Shift() noexcept
|
||||
{
|
||||
const std::lock_guard<Mutex> protect(mutex);
|
||||
const std::scoped_lock<Mutex> protect(mutex);
|
||||
|
||||
auto chunk = std::move(head);
|
||||
if (chunk != nullptr) {
|
||||
@ -81,7 +81,7 @@ MusicPipe::Push(MusicChunkPtr chunk) noexcept
|
||||
assert(!chunk->IsEmpty());
|
||||
assert(chunk->length == 0 || chunk->audio_format.IsValid());
|
||||
|
||||
const std::lock_guard<Mutex> protect(mutex);
|
||||
const std::scoped_lock<Mutex> protect(mutex);
|
||||
|
||||
assert(size > 0 || !audio_format.IsDefined());
|
||||
assert(!audio_format.IsDefined() ||
|
||||
|
@ -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,9 +75,9 @@ 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);
|
||||
const std::scoped_lock<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);
|
||||
const std::scoped_lock<Mutex> protect(mutex);
|
||||
return size;
|
||||
}
|
||||
|
||||
gcc_pure
|
||||
[[gnu::pure]]
|
||||
bool IsEmpty() const noexcept {
|
||||
return GetSize() == 0;
|
||||
}
|
||||
|
@ -21,8 +21,8 @@
|
||||
#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"
|
||||
#include "client/Listener.hxx"
|
||||
#include "client/Client.hxx"
|
||||
@ -67,13 +67,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());
|
||||
}
|
||||
}
|
||||
|
||||
@ -204,7 +205,7 @@ Partition::OnBorderPause() noexcept
|
||||
void
|
||||
Partition::OnMixerVolumeChanged(Mixer &, int) noexcept
|
||||
{
|
||||
InvalidateHardwareVolume();
|
||||
mixer_memento.InvalidateHardwareVolume();
|
||||
|
||||
/* notify clients */
|
||||
EmitIdle(IDLE_MIXER);
|
||||
|
@ -25,8 +25,10 @@
|
||||
#include "queue/Listener.hxx"
|
||||
#include "output/MultipleOutputs.hxx"
|
||||
#include "mixer/Listener.hxx"
|
||||
#include "mixer/Memento.hxx"
|
||||
#include "player/Control.hxx"
|
||||
#include "player/Listener.hxx"
|
||||
#include "protocol/RangeArg.hxx"
|
||||
#include "ReplayGainMode.hxx"
|
||||
#include "SingleMode.hxx"
|
||||
#include "Chrono.hxx"
|
||||
@ -38,6 +40,7 @@
|
||||
#include <memory>
|
||||
|
||||
struct Instance;
|
||||
struct RangeArg;
|
||||
class MultipleOutputs;
|
||||
class SongLoader;
|
||||
class ClientListener;
|
||||
@ -74,6 +77,8 @@ struct Partition final : QueueListener, PlayerListener, MixerListener {
|
||||
|
||||
MultipleOutputs outputs;
|
||||
|
||||
MixerMemento mixer_memento;
|
||||
|
||||
PlayerControl pc;
|
||||
|
||||
ReplayGainMode replay_gain_mode = ReplayGainMode::OFF;
|
||||
@ -133,24 +138,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 +162,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,12 +22,14 @@
|
||||
#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"
|
||||
|
||||
#include <cassert>
|
||||
#include <cstring>
|
||||
#include <map>
|
||||
#include <string>
|
||||
#include <utility>
|
||||
@ -41,6 +43,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 +57,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 +72,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,24 +93,21 @@ 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)) {
|
||||
permission_default = 0;
|
||||
|
||||
param.With([](const char *value){
|
||||
const char *separator = std::strchr(value,
|
||||
PERMISSION_PASSWORD_CHAR);
|
||||
|
||||
if (separator == nullptr)
|
||||
param.With([](const StringView value){
|
||||
const auto [password, permissions] =
|
||||
value.Split(PERMISSION_PASSWORD_CHAR);
|
||||
if (permissions == nullptr)
|
||||
throw FormatRuntimeError("\"%c\" not found in password string",
|
||||
PERMISSION_PASSWORD_CHAR);
|
||||
|
||||
std::string password(value, separator);
|
||||
|
||||
unsigned permission = parsePermissions(separator + 1);
|
||||
permission_passwords.insert(std::make_pair(std::move(password),
|
||||
permission));
|
||||
permission_passwords.emplace(password,
|
||||
parsePermissions(permissions));
|
||||
});
|
||||
}
|
||||
|
||||
@ -115,17 +123,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);
|
||||
|
||||
|
@ -19,8 +19,8 @@
|
||||
|
||||
#include "PlaylistDatabase.hxx"
|
||||
#include "db/PlaylistVector.hxx"
|
||||
#include "fs/io/TextFile.hxx"
|
||||
#include "fs/io/BufferedOutputStream.hxx"
|
||||
#include "io/LineReader.hxx"
|
||||
#include "io/BufferedOutputStream.hxx"
|
||||
#include "time/ChronoUtil.hxx"
|
||||
#include "util/StringStrip.hxx"
|
||||
#include "util/RuntimeError.hxx"
|
||||
@ -42,7 +42,7 @@ playlist_vector_save(BufferedOutputStream &os, const PlaylistVector &pv)
|
||||
}
|
||||
|
||||
void
|
||||
playlist_metadata_load(TextFile &file, PlaylistVector &pv, const char *name)
|
||||
playlist_metadata_load(LineReader &file, PlaylistVector &pv, const char *name)
|
||||
{
|
||||
PlaylistInfo pm(name);
|
||||
|
||||
|
@ -24,7 +24,7 @@
|
||||
|
||||
class PlaylistVector;
|
||||
class BufferedOutputStream;
|
||||
class TextFile;
|
||||
class LineReader;
|
||||
|
||||
void
|
||||
playlist_vector_save(BufferedOutputStream &os, const PlaylistVector &pv);
|
||||
@ -33,6 +33,7 @@ playlist_vector_save(BufferedOutputStream &os, const PlaylistVector &pv);
|
||||
* Throws #std::runtime_error on error.
|
||||
*/
|
||||
void
|
||||
playlist_metadata_load(TextFile &file, PlaylistVector &pv, const char *name);
|
||||
playlist_metadata_load(LineReader &file, PlaylistVector &pv,
|
||||
const char *name);
|
||||
|
||||
#endif
|
||||
|
@ -26,15 +26,15 @@
|
||||
#include "song/DetachedSong.hxx"
|
||||
#include "SongLoader.hxx"
|
||||
#include "Mapper.hxx"
|
||||
#include "protocol/RangeArg.hxx"
|
||||
#include "fs/io/TextFile.hxx"
|
||||
#include "fs/io/FileOutputStream.hxx"
|
||||
#include "fs/io/BufferedOutputStream.hxx"
|
||||
#include "io/FileOutputStream.hxx"
|
||||
#include "io/BufferedOutputStream.hxx"
|
||||
#include "config/Data.hxx"
|
||||
#include "config/Option.hxx"
|
||||
#include "config/Defaults.hxx"
|
||||
#include "Idle.hxx"
|
||||
#include "fs/Limits.hxx"
|
||||
#include "fs/AllocatedPath.hxx"
|
||||
#include "fs/Traits.hxx"
|
||||
#include "fs/FileSystem.hxx"
|
||||
#include "fs/FileInfo.hxx"
|
||||
@ -81,6 +81,9 @@ spl_valid_name(const char *name_utf8)
|
||||
*/
|
||||
|
||||
return std::strchr(name_utf8, '/') == nullptr &&
|
||||
#ifdef _WIN32
|
||||
std::strchr(name_utf8, '\\') == nullptr &&
|
||||
#endif
|
||||
std::strchr(name_utf8, '\n') == nullptr &&
|
||||
std::strchr(name_utf8, '\r') == nullptr;
|
||||
}
|
||||
@ -173,11 +176,8 @@ ListPlaylistFiles()
|
||||
}
|
||||
|
||||
static void
|
||||
SavePlaylistFile(const PlaylistFileContents &contents, const char *utf8path)
|
||||
SavePlaylistFile(Path path_fs, const PlaylistFileContents &contents)
|
||||
{
|
||||
assert(utf8path != nullptr);
|
||||
|
||||
const auto path_fs = spl_map_to_fs(utf8path);
|
||||
assert(!path_fs.IsNull());
|
||||
|
||||
FileOutputStream fos(path_fs);
|
||||
@ -191,12 +191,11 @@ SavePlaylistFile(const PlaylistFileContents &contents, const char *utf8path)
|
||||
fos.Commit();
|
||||
}
|
||||
|
||||
PlaylistFileContents
|
||||
LoadPlaylistFile(const char *utf8path)
|
||||
static PlaylistFileContents
|
||||
LoadPlaylistFile(Path path_fs)
|
||||
try {
|
||||
PlaylistFileContents contents;
|
||||
|
||||
const auto path_fs = spl_map_to_fs(utf8path);
|
||||
assert(!path_fs.IsNull());
|
||||
|
||||
TextFile file(path_fs);
|
||||
@ -251,16 +250,54 @@ try {
|
||||
throw;
|
||||
}
|
||||
|
||||
void
|
||||
spl_move_index(const char *utf8path, unsigned src, unsigned dest)
|
||||
static PlaylistFileContents
|
||||
MaybeLoadPlaylistFile(Path path_fs, PlaylistFileEditor::LoadMode load_mode)
|
||||
try {
|
||||
if (load_mode == PlaylistFileEditor::LoadMode::NO)
|
||||
return {};
|
||||
|
||||
return LoadPlaylistFile(path_fs);
|
||||
} catch (const PlaylistError &error) {
|
||||
if (error.GetCode() == PlaylistResult::NO_SUCH_LIST &&
|
||||
load_mode == PlaylistFileEditor::LoadMode::TRY)
|
||||
return {};
|
||||
|
||||
throw;
|
||||
}
|
||||
|
||||
PlaylistFileEditor::PlaylistFileEditor(const char *name_utf8,
|
||||
LoadMode load_mode)
|
||||
:path(spl_map_to_fs(name_utf8)),
|
||||
contents(MaybeLoadPlaylistFile(path, load_mode))
|
||||
{
|
||||
if (src == dest)
|
||||
/* this doesn't check whether the playlist exists, but
|
||||
what the hell.. */
|
||||
return;
|
||||
}
|
||||
|
||||
auto contents = LoadPlaylistFile(utf8path);
|
||||
void
|
||||
PlaylistFileEditor::Insert(std::size_t i, const char *uri)
|
||||
{
|
||||
if (i > size())
|
||||
throw PlaylistError(PlaylistResult::BAD_RANGE, "Bad position");
|
||||
|
||||
if (size() >= playlist_max_length)
|
||||
throw PlaylistError(PlaylistResult::TOO_LARGE,
|
||||
"Stored playlist is too large");
|
||||
|
||||
contents.emplace(std::next(contents.begin(), i), uri);
|
||||
}
|
||||
|
||||
void
|
||||
PlaylistFileEditor::Insert(std::size_t i, const DetachedSong &song)
|
||||
{
|
||||
const char *uri = playlist_saveAbsolutePaths
|
||||
? song.GetRealURI()
|
||||
: song.GetURI();
|
||||
|
||||
Insert(i, uri);
|
||||
}
|
||||
|
||||
void
|
||||
PlaylistFileEditor::MoveIndex(unsigned src, unsigned dest)
|
||||
{
|
||||
if (src >= contents.size() || dest >= contents.size())
|
||||
throw PlaylistError(PlaylistResult::BAD_RANGE, "Bad range");
|
||||
|
||||
@ -270,9 +307,31 @@ spl_move_index(const char *utf8path, unsigned src, unsigned dest)
|
||||
|
||||
const auto dest_i = std::next(contents.begin(), dest);
|
||||
contents.insert(dest_i, std::move(value));
|
||||
}
|
||||
|
||||
SavePlaylistFile(contents, utf8path);
|
||||
void
|
||||
PlaylistFileEditor::RemoveIndex(unsigned i)
|
||||
{
|
||||
if (i >= contents.size())
|
||||
throw PlaylistError(PlaylistResult::BAD_RANGE, "Bad range");
|
||||
|
||||
contents.erase(std::next(contents.begin(), i));
|
||||
}
|
||||
|
||||
void
|
||||
PlaylistFileEditor::RemoveRange(RangeArg range)
|
||||
{
|
||||
if (!range.CheckClip(size()))
|
||||
throw PlaylistError::BadRange();
|
||||
|
||||
contents.erase(std::next(contents.begin(), range.start),
|
||||
std::next(contents.begin(), range.end));
|
||||
}
|
||||
|
||||
void
|
||||
PlaylistFileEditor::Save()
|
||||
{
|
||||
SavePlaylistFile(path, contents);
|
||||
idle_add(IDLE_STORED_PLAYLIST);
|
||||
}
|
||||
|
||||
@ -314,20 +373,6 @@ spl_delete(const char *name_utf8)
|
||||
idle_add(IDLE_STORED_PLAYLIST);
|
||||
}
|
||||
|
||||
void
|
||||
spl_remove_index(const char *utf8path, unsigned pos)
|
||||
{
|
||||
auto contents = LoadPlaylistFile(utf8path);
|
||||
|
||||
if (pos >= contents.size())
|
||||
throw PlaylistError(PlaylistResult::BAD_RANGE, "Bad range");
|
||||
|
||||
contents.erase(std::next(contents.begin(), pos));
|
||||
|
||||
SavePlaylistFile(contents, utf8path);
|
||||
idle_add(IDLE_STORED_PLAYLIST);
|
||||
}
|
||||
|
||||
void
|
||||
spl_append_song(const char *utf8path, const DetachedSong &song)
|
||||
try {
|
||||
|
@ -20,19 +20,55 @@
|
||||
#ifndef MPD_PLAYLIST_FILE_HXX
|
||||
#define MPD_PLAYLIST_FILE_HXX
|
||||
|
||||
#include "fs/AllocatedPath.hxx"
|
||||
|
||||
#include <vector>
|
||||
#include <string>
|
||||
|
||||
struct ConfigData;
|
||||
struct RangeArg;
|
||||
class DetachedSong;
|
||||
class SongLoader;
|
||||
class PlaylistVector;
|
||||
class AllocatedPath;
|
||||
|
||||
typedef std::vector<std::string> PlaylistFileContents;
|
||||
|
||||
extern bool playlist_saveAbsolutePaths;
|
||||
|
||||
class PlaylistFileEditor {
|
||||
const AllocatedPath path;
|
||||
|
||||
PlaylistFileContents contents;
|
||||
|
||||
public:
|
||||
enum class LoadMode {
|
||||
NO,
|
||||
YES,
|
||||
TRY,
|
||||
};
|
||||
|
||||
/**
|
||||
* Throws on error.
|
||||
*/
|
||||
explicit PlaylistFileEditor(const char *name_utf8, LoadMode load_mode);
|
||||
|
||||
auto size() const noexcept {
|
||||
return contents.size();
|
||||
}
|
||||
|
||||
void Insert(std::size_t i, const char *uri);
|
||||
void Insert(std::size_t i, const DetachedSong &song);
|
||||
|
||||
void MoveIndex(unsigned src, unsigned dest);
|
||||
void RemoveIndex(unsigned i);
|
||||
void RemoveRange(RangeArg range);
|
||||
|
||||
void Save();
|
||||
|
||||
private:
|
||||
void Load();
|
||||
};
|
||||
|
||||
/**
|
||||
* Perform some global initialization, e.g. load configuration values.
|
||||
*/
|
||||
@ -55,21 +91,12 @@ spl_map_to_fs(const char *name_utf8);
|
||||
PlaylistVector
|
||||
ListPlaylistFiles();
|
||||
|
||||
PlaylistFileContents
|
||||
LoadPlaylistFile(const char *utf8path);
|
||||
|
||||
void
|
||||
spl_move_index(const char *utf8path, unsigned src, unsigned dest);
|
||||
|
||||
void
|
||||
spl_clear(const char *utf8path);
|
||||
|
||||
void
|
||||
spl_delete(const char *name_utf8);
|
||||
|
||||
void
|
||||
spl_remove_index(const char *utf8path, unsigned pos);
|
||||
|
||||
void
|
||||
spl_append_song(const char *utf8path, const DetachedSong &song);
|
||||
|
||||
|
@ -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
|
||||
|
@ -28,8 +28,8 @@
|
||||
#include "fs/AllocatedPath.hxx"
|
||||
#include "fs/Traits.hxx"
|
||||
#include "fs/FileSystem.hxx"
|
||||
#include "fs/io/FileOutputStream.hxx"
|
||||
#include "fs/io/BufferedOutputStream.hxx"
|
||||
#include "io/FileOutputStream.hxx"
|
||||
#include "io/BufferedOutputStream.hxx"
|
||||
#include "util/UriExtract.hxx"
|
||||
|
||||
static void
|
||||
|
@ -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();
|
||||
|
||||
@ -94,7 +98,7 @@ RemoteTagCache::ItemResolved(Item &item) noexcept
|
||||
void
|
||||
RemoteTagCache::InvokeHandlers() noexcept
|
||||
{
|
||||
const std::lock_guard<Mutex> lock(mutex);
|
||||
const std::scoped_lock<Mutex> lock(mutex);
|
||||
|
||||
while (!invoke_list.empty()) {
|
||||
auto &item = invoke_list.front();
|
||||
@ -121,17 +125,18 @@ RemoteTagCache::Item::OnRemoteTag(Tag &&_tag) noexcept
|
||||
|
||||
scanner.reset();
|
||||
|
||||
const std::lock_guard<Mutex> lock(parent.mutex);
|
||||
const std::scoped_lock<Mutex> lock(parent.mutex);
|
||||
parent.ItemResolved(*this);
|
||||
}
|
||||
|
||||
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();
|
||||
|
||||
const std::lock_guard<Mutex> lock(parent.mutex);
|
||||
const std::scoped_lock<Mutex> lock(parent.mutex);
|
||||
parent.ItemResolved(*this);
|
||||
}
|
||||
|
@ -22,13 +22,17 @@
|
||||
|
||||
#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>
|
||||
#include <boost/intrusive/unordered_set.hpp>
|
||||
|
||||
#include <array>
|
||||
#include <functional>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <utility>
|
||||
|
||||
class RemoteTagCacheHandler;
|
||||
|
||||
@ -40,7 +44,7 @@ class RemoteTagCache final {
|
||||
|
||||
RemoteTagCacheHandler &handler;
|
||||
|
||||
DeferEvent defer_invoke_handler;
|
||||
InjectEvent defer_invoke_handler;
|
||||
|
||||
Mutex mutex;
|
||||
|
||||
@ -68,20 +72,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::const]]
|
||||
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;
|
||||
};
|
||||
|
||||
|
@ -24,9 +24,13 @@
|
||||
#include "TagPrint.hxx"
|
||||
#include "client/Response.hxx"
|
||||
#include "fs/Traits.hxx"
|
||||
#include "lib/fmt/AudioFormatFormatter.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 +46,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 +72,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 +94,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"), 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 +116,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"), 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());
|
||||
}
|
||||
|
@ -22,8 +22,8 @@
|
||||
#include "db/plugins/simple/Song.hxx"
|
||||
#include "song/DetachedSong.hxx"
|
||||
#include "TagSave.hxx"
|
||||
#include "fs/io/TextFile.hxx"
|
||||
#include "fs/io/BufferedOutputStream.hxx"
|
||||
#include "io/LineReader.hxx"
|
||||
#include "io/BufferedOutputStream.hxx"
|
||||
#include "tag/ParseName.hxx"
|
||||
#include "tag/Tag.hxx"
|
||||
#include "tag/Builder.hxx"
|
||||
@ -63,6 +63,9 @@ song_save(BufferedOutputStream &os, const Song &song)
|
||||
if (song.audio_format.IsDefined())
|
||||
os.Format("Format: %s\n", ToString(song.audio_format).c_str());
|
||||
|
||||
if (song.in_playlist)
|
||||
os.Write("InPlaylist: yes\n");
|
||||
|
||||
if (!IsNegative(song.mtime))
|
||||
os.Format(SONG_MTIME ": %li\n",
|
||||
(long)std::chrono::system_clock::to_time_t(song.mtime));
|
||||
@ -85,9 +88,8 @@ song_save(BufferedOutputStream &os, const DetachedSong &song)
|
||||
}
|
||||
|
||||
DetachedSong
|
||||
song_load(TextFile &file, const char *uri,
|
||||
std::string *target_r,
|
||||
AudioFormat *audio_format_r)
|
||||
song_load(LineReader &file, const char *uri,
|
||||
std::string *target_r, bool *in_playlist_r)
|
||||
{
|
||||
DetachedSong song(uri);
|
||||
|
||||
@ -113,13 +115,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"));
|
||||
@ -135,6 +135,9 @@ song_load(TextFile &file, const char *uri,
|
||||
|
||||
song.SetStartTime(SongTime::FromMS(start_ms));
|
||||
song.SetEndTime(SongTime::FromMS(end_ms));
|
||||
} else if (StringIsEqual(line, "InPlaylist")) {
|
||||
if (in_playlist_r != nullptr)
|
||||
*in_playlist_r = StringIsEqual(value, "yes");
|
||||
} else {
|
||||
throw FormatRuntimeError("unknown line in db: %s", line);
|
||||
}
|
||||
|
@ -28,7 +28,7 @@ struct Song;
|
||||
struct AudioFormat;
|
||||
class DetachedSong;
|
||||
class BufferedOutputStream;
|
||||
class TextFile;
|
||||
class LineReader;
|
||||
|
||||
void
|
||||
song_save(BufferedOutputStream &os, const Song &song);
|
||||
@ -43,8 +43,7 @@ song_save(BufferedOutputStream &os, const DetachedSong &song);
|
||||
* Throws on error.
|
||||
*/
|
||||
DetachedSong
|
||||
song_load(TextFile &file, const char *uri,
|
||||
std::string *target_r=nullptr,
|
||||
AudioFormat *audio_format_r=nullptr);
|
||||
song_load(LineReader &file, const char *uri,
|
||||
std::string *target_r=nullptr, bool *in_playlist_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
|
||||
|
@ -22,12 +22,11 @@
|
||||
#include "output/State.hxx"
|
||||
#include "queue/PlaylistState.hxx"
|
||||
#include "fs/io/TextFile.hxx"
|
||||
#include "fs/io/FileOutputStream.hxx"
|
||||
#include "fs/io/BufferedOutputStream.hxx"
|
||||
#include "io/FileOutputStream.hxx"
|
||||
#include "io/BufferedOutputStream.hxx"
|
||||
#include "storage/StorageState.hxx"
|
||||
#include "Partition.hxx"
|
||||
#include "Instance.hxx"
|
||||
#include "mixer/Volume.hxx"
|
||||
#include "SongLoader.hxx"
|
||||
#include "util/Domain.hxx"
|
||||
#include "Log.hxx"
|
||||
@ -47,7 +46,7 @@ StateFile::StateFile(StateFileConfig &&_config,
|
||||
void
|
||||
StateFile::RememberVersions() noexcept
|
||||
{
|
||||
prev_volume_version = sw_volume_state_get_hash();
|
||||
prev_volume_version = partition.mixer_memento.GetSoftwareVolumeStateHash();
|
||||
prev_output_version = audio_output_state_get_version();
|
||||
prev_playlist_version = playlist_state_get_hash(partition.playlist,
|
||||
partition.pc);
|
||||
@ -59,7 +58,7 @@ StateFile::RememberVersions() noexcept
|
||||
bool
|
||||
StateFile::IsModified() const noexcept
|
||||
{
|
||||
return prev_volume_version != sw_volume_state_get_hash() ||
|
||||
return prev_volume_version != partition.mixer_memento.GetSoftwareVolumeStateHash() ||
|
||||
prev_output_version != audio_output_state_get_version() ||
|
||||
prev_playlist_version != playlist_state_get_hash(partition.playlist,
|
||||
partition.pc)
|
||||
@ -72,7 +71,7 @@ StateFile::IsModified() const noexcept
|
||||
inline void
|
||||
StateFile::Write(BufferedOutputStream &os)
|
||||
{
|
||||
save_sw_volume_state(os);
|
||||
partition.mixer_memento.SaveSoftwareVolumeState(os);
|
||||
audio_output_state_save(os, partition.outputs);
|
||||
|
||||
#ifdef ENABLE_DATABASE
|
||||
@ -93,8 +92,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 +111,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);
|
||||
|
||||
@ -125,7 +124,7 @@ try {
|
||||
|
||||
const char *line;
|
||||
while ((line = file.ReadLine()) != nullptr) {
|
||||
success = read_sw_volume_state(line, partition.outputs) ||
|
||||
success = partition.mixer_memento.LoadSoftwareVolumeState(line, partition.outputs) ||
|
||||
audio_output_state_read(line, partition.outputs) ||
|
||||
playlist_state_restore(config, line, file, song_loader,
|
||||
partition.playlist,
|
||||
@ -135,9 +134,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 +147,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 */
|
||||
|
@ -32,7 +32,7 @@ StateFileConfig::StateFileConfig(const ConfigData &config)
|
||||
{
|
||||
#ifdef ANDROID
|
||||
if (path.IsNull()) {
|
||||
const auto cache_dir = GetUserCacheDir();
|
||||
const auto cache_dir = GetAppCacheDir();
|
||||
if (cache_dir.IsNull())
|
||||
return;
|
||||
|
||||
|
@ -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();
|
||||
|
@ -21,19 +21,29 @@
|
||||
#include "TagStream.hxx"
|
||||
#include "TagFile.hxx"
|
||||
#include "tag/Generic.hxx"
|
||||
#include "song/LightSong.hxx"
|
||||
#include "db/Interface.hxx"
|
||||
#include "storage/StorageInterface.hxx"
|
||||
#include "client/Client.hxx"
|
||||
#include "protocol/Ack.hxx"
|
||||
#include "fs/AllocatedPath.hxx"
|
||||
#include "input/InputStream.hxx"
|
||||
#include "util/Compiler.h"
|
||||
#include "util/ScopeExit.hxx"
|
||||
#include "util/StringCompare.hxx"
|
||||
#include "util/UriExtract.hxx"
|
||||
#include "LocateUri.hxx"
|
||||
|
||||
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
|
||||
@ -45,10 +55,67 @@ TagScanFile(const Path path_fs, TagHandler &handler)
|
||||
ScanGenericTags(path_fs, handler);
|
||||
}
|
||||
|
||||
#ifdef ENABLE_DATABASE
|
||||
|
||||
/**
|
||||
* Collapse "../" prefixes in a URI relative to the specified base
|
||||
* URI.
|
||||
*/
|
||||
static std::string
|
||||
ResolveUri(std::string_view base, const char *relative)
|
||||
{
|
||||
while (true) {
|
||||
const char *rest = StringAfterPrefix(relative, "../");
|
||||
if (rest == nullptr)
|
||||
break;
|
||||
|
||||
if (base == ".")
|
||||
throw ProtocolError(ACK_ERROR_NO_EXIST, "Bad real URI");
|
||||
|
||||
base = PathTraitsUTF8::GetParent(base);
|
||||
relative = rest;
|
||||
}
|
||||
|
||||
return PathTraitsUTF8::Build(base, relative);
|
||||
}
|
||||
|
||||
/**
|
||||
* Look up the specified song in the database and return its
|
||||
* (resolved) "real" URI.
|
||||
*/
|
||||
static std::string
|
||||
GetRealSongUri(Client &client, std::string_view uri)
|
||||
{
|
||||
const auto &db = client.GetDatabaseOrThrow();
|
||||
|
||||
const auto *song = db.GetSong(uri);
|
||||
if (song == nullptr)
|
||||
throw ProtocolError(ACK_ERROR_NO_EXIST, "No such song");
|
||||
|
||||
AtScopeExit(&db, song) { db.ReturnSong(song); };
|
||||
|
||||
if (song->real_uri == nullptr)
|
||||
return {};
|
||||
|
||||
return ResolveUri(PathTraitsUTF8::GetParent(uri), song->real_uri);
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
static void
|
||||
TagScanDatabase(Client &client, const char *uri, TagHandler &handler)
|
||||
{
|
||||
#ifdef ENABLE_DATABASE
|
||||
const auto real_uri = GetRealSongUri(client, uri);
|
||||
|
||||
if (!real_uri.empty()) {
|
||||
uri = real_uri.c_str();
|
||||
|
||||
// TODO: support absolute paths?
|
||||
if (uri_has_scheme(uri))
|
||||
return TagScanStream(uri, handler);
|
||||
}
|
||||
|
||||
const Storage *storage = client.GetStorage();
|
||||
if (storage == nullptr) {
|
||||
#else
|
||||
|
@ -23,26 +23,28 @@
|
||||
#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
|
||||
tag_print(Response &r, TagType type, StringView _value) noexcept
|
||||
{
|
||||
r.Format("%s: %.*s\n", tag_item_names[type],
|
||||
int(value.size), value.data);
|
||||
const std::string_view value{_value};
|
||||
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 +60,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);
|
||||
}
|
||||
|
@ -19,7 +19,7 @@
|
||||
|
||||
#include "TagSave.hxx"
|
||||
#include "tag/Tag.hxx"
|
||||
#include "fs/io/BufferedOutputStream.hxx"
|
||||
#include "io/BufferedOutputStream.hxx"
|
||||
|
||||
#define SONG_TIME "Time: "
|
||||
|
||||
|
@ -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.c_str());
|
||||
}
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user