Compare commits
1922 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ffb3a9f526 | ||
|
|
9761abf3b5 | ||
|
|
599a562170 | ||
|
|
d29a251547 | ||
|
|
31da4bc566 | ||
|
|
0f1a180e15 | ||
|
|
01a45a53aa | ||
|
|
a9a5907a0f | ||
|
|
8fb20fcdf8 | ||
|
|
72bf226608 | ||
|
|
d4b5699403 | ||
|
|
1dc27be015 | ||
|
|
230a3eb400 | ||
|
|
e39382dedd | ||
|
|
fd016f4507 | ||
|
|
9d728b365d | ||
|
|
ddc0283339 | ||
|
|
03a401e477 | ||
|
|
9994521b8c | ||
|
|
adbe8c409a | ||
|
|
58e600f408 | ||
|
|
d34e55c370 | ||
|
|
fbcbcdc001 | ||
|
|
4227a325a5 | ||
|
|
d115507502 | ||
|
|
43d8252050 | ||
|
|
674b4ab647 | ||
|
|
fe8fc1081a | ||
|
|
c7748fedab | ||
|
|
c392efb481 | ||
|
|
1ddd9dd52a | ||
|
|
f672e4016f | ||
|
|
76e3dec723 | ||
|
|
ba6ef53ef9 | ||
|
|
c93a28c641 | ||
|
|
7088a679a2 | ||
|
|
04c02a1eb8 | ||
|
|
41487426f5 | ||
|
|
0d24250aa7 | ||
|
|
2050e2f886 | ||
|
|
013e8479af | ||
|
|
27535a7f78 | ||
|
|
acaa725478 | ||
|
|
f351550534 | ||
|
|
66ecf39efe | ||
|
|
5ad21d7e98 | ||
|
|
ef5125f8f4 | ||
|
|
bf2e07074b | ||
|
|
20695ef369 | ||
|
|
9374e0f445 | ||
|
|
19ed233118 | ||
|
|
faa4fff4dd | ||
|
|
2276e7677b | ||
|
|
93f9c2ab6b | ||
|
|
4a993cd79e | ||
|
|
02325d2ede | ||
|
|
9c83464b95 | ||
|
|
b1bbd70f0f | ||
|
|
c31d11bfe0 | ||
|
|
c8ec85d649 | ||
|
|
e291f3d257 | ||
|
|
dc22846d58 | ||
|
|
c9aaabb5d4 | ||
|
|
335d5d5d72 | ||
|
|
51d793bec1 | ||
|
|
249dcd967e | ||
|
|
302972e9fc | ||
|
|
31b380b266 | ||
|
|
a869dfea85 | ||
|
|
12838c6294 | ||
|
|
49c7102547 | ||
|
|
1ae8972859 | ||
|
|
adcd2c8eac | ||
|
|
45ff355835 | ||
|
|
f8bf3afeae | ||
|
|
f703da1516 | ||
|
|
a582deee2c | ||
|
|
12be9e818f | ||
|
|
281cd7c057 | ||
|
|
63e8766091 | ||
|
|
18da582c87 | ||
|
|
0562cf99ba | ||
|
|
60ac702038 | ||
|
|
1dedb96478 | ||
|
|
7537722a44 | ||
|
|
4ebd69193e | ||
|
|
d4d92ac1a7 | ||
|
|
5385d1fa80 | ||
|
|
dbee2f1996 | ||
|
|
6dd70926fa | ||
|
|
5dfc0918c3 | ||
|
|
2eb14658d3 | ||
|
|
c1f196dbec | ||
|
|
ce108acebb | ||
|
|
0555e2c781 | ||
|
|
6bfd1f1727 | ||
|
|
baa77c8ae3 | ||
|
|
7235dbadfd | ||
|
|
ecec41025f | ||
|
|
799843cc97 | ||
|
|
16e91baa79 | ||
|
|
f2536445f7 | ||
|
|
ede70ee3a4 | ||
|
|
66ed427a57 | ||
|
|
c9e63e9fdb | ||
|
|
ddf7f5c131 | ||
|
|
660e40d07e | ||
|
|
9f3db5a70b | ||
|
|
1a59730782 | ||
|
|
20029e7ce8 | ||
|
|
fcb7950811 | ||
|
|
4eb57e1e9a | ||
|
|
1d52e2cc77 | ||
|
|
209aceeb14 | ||
|
|
ae70875f45 | ||
|
|
eda7410f4c | ||
|
|
e60141b4dd | ||
|
|
5b21742095 | ||
|
|
5665de5ee7 | ||
|
|
d5b9be0393 | ||
|
|
d2d9b45a81 | ||
|
|
9ebbdb9b0b | ||
|
|
48da345e79 | ||
|
|
edbfa46cbc | ||
|
|
90709a6de4 | ||
|
|
992c2fa2d4 | ||
|
|
ed915fed92 | ||
|
|
8aa29d5a66 | ||
|
|
9604e0aad2 | ||
|
|
25d053cbf2 | ||
|
|
055257a210 | ||
|
|
50cfb997cc | ||
|
|
d662c4c0cc | ||
|
|
457d98c860 | ||
|
|
6bb166aaaa | ||
|
|
cca9bc5176 | ||
|
|
8b6b25220d | ||
|
|
3c3f1b7ff2 | ||
|
|
57d89131e9 | ||
|
|
7c6d1896a4 | ||
|
|
ad83c7f704 | ||
|
|
6b52d040b1 | ||
|
|
404fa89937 | ||
|
|
89377556cd | ||
|
|
ec93114347 | ||
|
|
df1c5ce314 | ||
|
|
a0e4b6e266 | ||
|
|
0de39b64cb | ||
|
|
36a7b4e275 | ||
|
|
d3cc8e0ac0 | ||
|
|
db1ce4eeeb | ||
|
|
c22cbbf828 | ||
|
|
5acee73fc8 | ||
|
|
466c337bcb | ||
|
|
98a468a101 | ||
|
|
47c58c01d1 | ||
|
|
a9edf85a69 | ||
|
|
e7a1862517 | ||
|
|
f930f37d35 | ||
|
|
4feb57e895 | ||
|
|
712e3eb120 | ||
|
|
d8e423df1a | ||
|
|
09aa0dc676 | ||
|
|
83174de420 | ||
|
|
8ff0197a43 | ||
|
|
a2b5db0003 | ||
|
|
2803ec2e96 | ||
|
|
ddd4f675a2 | ||
|
|
d271dd2cce | ||
|
|
ebfdd37451 | ||
|
|
f6d6110aaa | ||
|
|
a0730cf264 | ||
|
|
ec61b3a5fc | ||
|
|
521f2294cb | ||
|
|
2f0674807c | ||
|
|
de0f46b947 | ||
|
|
1a57fa095f | ||
|
|
167242fec0 | ||
|
|
81208d78ac | ||
|
|
da8b01771f | ||
|
|
725fbe946b | ||
|
|
b99ecb4dc9 | ||
|
|
208a96b211 | ||
|
|
43774455cc | ||
|
|
5b61e077e0 | ||
|
|
921cc3e5db | ||
|
|
51dce3d161 | ||
|
|
c10f013fc2 | ||
|
|
2874d68bdb | ||
|
|
11e83eb7e7 | ||
|
|
9ec9a8705e | ||
|
|
170635e3a6 | ||
|
|
a5d1444ef4 | ||
|
|
55708b39c3 | ||
|
|
8c5ebdff36 | ||
|
|
1c84f324a1 | ||
|
|
3dba09f339 | ||
|
|
7ebf8e66c9 | ||
|
|
62218fe59d | ||
|
|
95d9bb6dfc | ||
|
|
33986075ef | ||
|
|
766905ba9f | ||
|
|
a29c64b6c7 | ||
|
|
777e1cabc5 | ||
|
|
396e97fc94 | ||
|
|
9c6a4505c8 | ||
|
|
238c3adad1 | ||
|
|
c1d0a8b5ce | ||
|
|
08ce24ec3f | ||
|
|
79eb7623ef | ||
|
|
b9e64d0472 | ||
|
|
4f500149af | ||
|
|
281b8714ef | ||
|
|
d5be3cce9c | ||
|
|
8dcefaf2e3 | ||
|
|
48e3432a63 | ||
|
|
69d3c611aa | ||
|
|
36827e1134 | ||
|
|
351ac4a2c0 | ||
|
|
36fff59a38 | ||
|
|
1e60a4386a | ||
|
|
e9f1b53ae6 | ||
|
|
5016839b90 | ||
|
|
faf35e6082 | ||
|
|
d07a6edd2f | ||
|
|
553d4e9283 | ||
|
|
7cef52478d | ||
|
|
e7ce362d22 | ||
|
|
c551c8b31b | ||
|
|
9c36e71081 | ||
|
|
c9c57af5f7 | ||
|
|
2516496993 | ||
|
|
3b565b5f97 | ||
|
|
0742976138 | ||
|
|
1a63663c85 | ||
|
|
00a20fc8a5 | ||
|
|
6ab4fb368c | ||
|
|
fa39bb0a50 | ||
|
|
645663cdfe | ||
|
|
ee2bcbb41d | ||
|
|
12b4ebf8d4 | ||
|
|
8da4750ee2 | ||
|
|
d22df2915c | ||
|
|
e77d96cf89 | ||
|
|
ae28ba84d2 | ||
|
|
b3f3b01958 | ||
|
|
52e9cab1c1 | ||
|
|
1baaaa40cc | ||
|
|
b8ed420058 | ||
|
|
ed16ee3029 | ||
|
|
7338b16c18 | ||
|
|
f529441400 | ||
|
|
03664d0426 | ||
|
|
652cfb7caf | ||
|
|
5540fbaec2 | ||
|
|
0a0b473765 | ||
|
|
df2d041483 | ||
|
|
ffc6e19548 | ||
|
|
d874d7661f | ||
|
|
1d66e714e6 | ||
|
|
d98a863b82 | ||
|
|
3b6790c7dc | ||
|
|
5ee3a9a9ca | ||
|
|
dd26fa67f2 | ||
|
|
89f5e60422 | ||
|
|
c616165f81 | ||
|
|
103832742d | ||
|
|
3e7e0bcb18 | ||
|
|
7d3d8f20ab | ||
|
|
e1e3ce980a | ||
|
|
7855a32579 | ||
|
|
9c92afa5fe | ||
|
|
edac498d03 | ||
|
|
d7d9dbd2c2 | ||
|
|
f8eece22c8 | ||
|
|
df563db294 | ||
|
|
01b0d9eb97 | ||
|
|
a7f13d841f | ||
|
|
686d8bbe69 | ||
|
|
4b36af4a34 | ||
|
|
4a23a4bfee | ||
|
|
ac3ad452c0 | ||
|
|
027c01511c | ||
|
|
4fdcc0496f | ||
|
|
0c4a2bea69 | ||
|
|
2ba3401238 | ||
|
|
1735284a2a | ||
|
|
8a3192ffc1 | ||
|
|
ffea273a28 | ||
|
|
1783aac438 | ||
|
|
29bf3d2c04 | ||
|
|
ee16fc958c | ||
|
|
5d73215a8d | ||
|
|
b7356bc526 | ||
|
|
8ec8282f38 | ||
|
|
43b7b98949 | ||
|
|
f791065a98 | ||
|
|
d95db28695 | ||
|
|
851fb16e7c | ||
|
|
a4cbaafd10 | ||
|
|
c5f2cdb822 | ||
|
|
1892d29be0 | ||
|
|
9e5a49b8cb | ||
|
|
767ade02f4 | ||
|
|
6e05071a47 | ||
|
|
c8a990b9d3 | ||
|
|
6868ceae9b | ||
|
|
cace646346 | ||
|
|
abcc225763 | ||
|
|
b9673fc521 | ||
|
|
b0ea3f4261 | ||
|
|
0103219f00 | ||
|
|
e15b4f40d6 | ||
|
|
66235fddff | ||
|
|
f9c5d026f4 | ||
|
|
c46239af22 | ||
|
|
48eb3ff8d9 | ||
|
|
5646dcc791 | ||
|
|
5d9876e338 | ||
|
|
083340a937 | ||
|
|
378fa5ee6a | ||
|
|
4764daf3c2 | ||
|
|
6357496d17 | ||
|
|
001e2a604b | ||
|
|
f370911c15 | ||
|
|
ef5cf40fa6 | ||
|
|
837bd79b20 | ||
|
|
3edd4a24af | ||
|
|
535a61b5a9 | ||
|
|
e87b7daab5 | ||
|
|
f20689519d | ||
|
|
ee9c460f74 | ||
|
|
84ba14fa29 | ||
|
|
3c75963352 | ||
|
|
5f9dd8287c | ||
|
|
d99f074eb7 | ||
|
|
a989140a07 | ||
|
|
420a4c163d | ||
|
|
1bab735580 | ||
|
|
a0fcd0cf22 | ||
|
|
5d5c753c74 | ||
|
|
7e7e3eb8d5 | ||
|
|
2e5f8aeba1 | ||
|
|
c2fe961db1 | ||
|
|
2a6acc04a1 | ||
|
|
a467128093 | ||
|
|
abf41eb85c | ||
|
|
d83dea4463 | ||
|
|
ec43721c3c | ||
|
|
39d52762d1 | ||
|
|
8d45d0d104 | ||
|
|
26ec62714c | ||
|
|
7873b5e78b | ||
|
|
1b889c527f | ||
|
|
9937ff8ac0 | ||
|
|
abd1949825 | ||
|
|
4e6bc77a70 | ||
|
|
531948358b | ||
|
|
0d3ec9c324 | ||
|
|
21caca4aea | ||
|
|
fbf3edf07d | ||
|
|
76fcf25898 | ||
|
|
56257f072b | ||
|
|
44401158e8 | ||
|
|
95b2df8261 | ||
|
|
1ebadf8620 | ||
|
|
9179d9592d | ||
|
|
f208217412 | ||
|
|
43c5058682 | ||
|
|
97b4a6b51f | ||
|
|
f405d27c56 | ||
|
|
99949c8f6f | ||
|
|
3a9697adf2 | ||
|
|
78c4351e04 | ||
|
|
0a427890fe | ||
|
|
e735abe334 | ||
|
|
c0070b2f13 | ||
|
|
cfedc6e9b4 | ||
|
|
b66d7f7e0b | ||
|
|
e01df06cd7 | ||
|
|
6584897b69 | ||
|
|
aa4f45b9a5 | ||
|
|
96ad5b8444 | ||
|
|
097e5dfbdc | ||
|
|
2ef7ee6ca7 | ||
|
|
2685b53b30 | ||
|
|
533e4fcdad | ||
|
|
006b8fa3f0 | ||
|
|
6a01153ce4 | ||
|
|
34aab116ae | ||
|
|
33232face9 | ||
|
|
b88b2b3d79 | ||
|
|
744d729dab | ||
|
|
71b5e43153 | ||
|
|
8459f27312 | ||
|
|
7dfbdef505 | ||
|
|
94386374ff | ||
|
|
f5d3859238 | ||
|
|
ef39da5973 | ||
|
|
81e8c4bbff | ||
|
|
8ca3642429 | ||
|
|
1dc000c06a | ||
|
|
4f093d5b97 | ||
|
|
e1b032cbad | ||
|
|
6f365c30eb | ||
|
|
718e180423 | ||
|
|
cead5e5bd7 | ||
|
|
cf15629aea | ||
|
|
a727d0bb0b | ||
|
|
0a218ee56a | ||
|
|
74beefcaf6 | ||
|
|
399a3abefc | ||
|
|
cee5036aca | ||
|
|
790823abb4 | ||
|
|
f546849352 | ||
|
|
a85af593f1 | ||
|
|
07067f8b95 | ||
|
|
a1e824ada0 | ||
|
|
f5f1bfbef1 | ||
|
|
cd108ba3aa | ||
|
|
2bb5bfa74e | ||
|
|
624e7a447d | ||
|
|
37420c342b | ||
|
|
ef40e362c9 | ||
|
|
ef369c2e2b | ||
|
|
6452461c39 | ||
|
|
1e89ca0994 | ||
|
|
92bb10eed8 | ||
|
|
8465c5fe0e | ||
|
|
42e248a8da | ||
|
|
e71a652985 | ||
|
|
bfef0fbff3 | ||
|
|
545685bc32 | ||
|
|
13ad2b4dc2 | ||
|
|
d019343017 | ||
|
|
e977ec924c | ||
|
|
b7d5652bf6 | ||
|
|
725e48fce4 | ||
|
|
43816c268b | ||
|
|
72e80db823 | ||
|
|
7b33c343f8 | ||
|
|
6d0601b99f | ||
|
|
b6e713711d | ||
|
|
c53edeeb6c | ||
|
|
dfb98417b3 | ||
|
|
9716c3a30e | ||
|
|
c4c44c4445 | ||
|
|
0debe9bd6f | ||
|
|
f1da118a6c | ||
|
|
c30c46cd5f | ||
|
|
d394017926 | ||
|
|
04525c0259 | ||
|
|
08a0bb756d | ||
|
|
20c6159c04 | ||
|
|
a47e9d1a4b | ||
|
|
e93dd374a4 | ||
|
|
0847ca4ec2 | ||
|
|
572d8d0cc4 | ||
|
|
c9a57d354d | ||
|
|
52b77f6e31 | ||
|
|
4c9782ee28 | ||
|
|
26225307d3 | ||
|
|
0129d1e158 | ||
|
|
ece6037a1e | ||
|
|
bea678a7cd | ||
|
|
1fb3fbb4de | ||
|
|
2c38d19af2 | ||
|
|
311e627463 | ||
|
|
8e1e36a0d5 | ||
|
|
db0bb425e7 | ||
|
|
e001dd2d45 | ||
|
|
c38772c97f | ||
|
|
3a31589fdd | ||
|
|
460522bb67 | ||
|
|
176debaf37 | ||
|
|
072f83b240 | ||
|
|
cbd6770f2f | ||
|
|
0c3a7bcf12 | ||
|
|
389fbaaca2 | ||
|
|
9652efd107 | ||
|
|
f3ea834322 | ||
|
|
02a6a46e86 | ||
|
|
75ad90abc7 | ||
|
|
4104593180 | ||
|
|
78e1704122 | ||
|
|
b9e99575ce | ||
|
|
fa5e06f95d | ||
|
|
3041409334 | ||
|
|
71536eb412 | ||
|
|
fe77230d84 | ||
|
|
accd262561 | ||
|
|
3057d19cdf | ||
|
|
8dd83a2cf3 | ||
|
|
49b84f9229 | ||
|
|
b43bf4dd74 | ||
|
|
5ed0eb51d1 | ||
|
|
72a1ca3b99 | ||
|
|
14424281a0 | ||
|
|
2b3fd0d4d3 | ||
|
|
894b9cfdb9 | ||
|
|
72ff9bd3e6 | ||
|
|
7ecbb0454f | ||
|
|
fa7fa2a55f | ||
|
|
5c0576ca55 | ||
|
|
039b354490 | ||
|
|
b2f03e76ff | ||
|
|
63b33b6ec5 | ||
|
|
23670795db | ||
|
|
8ea6c113b5 | ||
|
|
37f026a0a6 | ||
|
|
f67136df19 | ||
|
|
e07073ff28 | ||
|
|
64b0ba6da7 | ||
|
|
99d4ae0c1a | ||
|
|
f185b35088 | ||
|
|
4e909f9411 | ||
|
|
99f49e266c | ||
|
|
0458211ebc | ||
|
|
8a7b9d9f45 | ||
|
|
3fcf463f9e | ||
|
|
f77cd63286 | ||
|
|
2378c2d754 | ||
|
|
bbdb4a67e1 | ||
|
|
21851c0673 | ||
|
|
83f6498aac | ||
|
|
7e219c362c | ||
|
|
525a791987 | ||
|
|
fb19210cfd | ||
|
|
203f48d1fd | ||
|
|
371c82b2bf | ||
|
|
8176880173 | ||
|
|
b0b2c5b3e0 | ||
|
|
29742d23d3 | ||
|
|
533a6b0240 | ||
|
|
0c0400b6fc | ||
|
|
2e83af7c21 | ||
|
|
a3f7947ad2 | ||
|
|
7d9d459ac2 | ||
|
|
3ea1073809 | ||
|
|
76a959a578 | ||
|
|
f43873dc5f | ||
|
|
921553d7bb | ||
|
|
c476819cb1 | ||
|
|
77a56c7c5a | ||
|
|
46deb7ca82 | ||
|
|
b03f9ece05 | ||
|
|
27946a981f | ||
|
|
74617389c8 | ||
|
|
1d8840412f | ||
|
|
df1152ee0f | ||
|
|
b4a8b8c0d4 | ||
|
|
ffa9f4b47f | ||
|
|
667e22bbc7 | ||
|
|
d4d838c8bb | ||
|
|
c61a889c86 | ||
|
|
567fe89a77 | ||
|
|
950772ab8a | ||
|
|
0bd0e2a3ec | ||
|
|
47774ce882 | ||
|
|
3c9bcdd347 | ||
|
|
3934d2d30c | ||
|
|
423ce9557a | ||
|
|
947848ebf6 | ||
|
|
5e22fe488e | ||
|
|
c666cf1c44 | ||
|
|
79435dbdec | ||
|
|
27206368da | ||
|
|
2be245b75e | ||
|
|
17927f5c26 | ||
|
|
754f26a97c | ||
|
|
29241c4f83 | ||
|
|
6f655eb9b9 | ||
|
|
65dfd90141 | ||
|
|
59abdbd2dd | ||
|
|
a6d6873856 | ||
|
|
e99df3a3be | ||
|
|
8b657255cc | ||
|
|
0e1dc79321 | ||
|
|
0ef15e5fac | ||
|
|
9ea8a840a9 | ||
|
|
7b88089593 | ||
|
|
b513943893 | ||
|
|
edffc56600 | ||
|
|
856b0e6886 | ||
|
|
bf1eb46b87 | ||
|
|
7e27d660e2 | ||
|
|
53174ea45f | ||
|
|
87b624f5d5 | ||
|
|
443e96381a | ||
|
|
1cbba4fc59 | ||
|
|
344b6dd179 | ||
|
|
d8c829fa0c | ||
|
|
e1efc71ad3 | ||
|
|
f804a739b1 | ||
|
|
2ed870c854 | ||
|
|
ce35ba9ac9 | ||
|
|
724a59aaf7 | ||
|
|
42d8c2981f | ||
|
|
9aa91e0f17 | ||
|
|
5aabee8996 | ||
|
|
48a84ca23e | ||
|
|
8751783a1b | ||
|
|
3a2ec50d5f | ||
|
|
ce9aeed4cb | ||
|
|
f2be6432a9 | ||
|
|
e96f8c0444 | ||
|
|
2bf3bc3e10 | ||
|
|
28143f86f9 | ||
|
|
76ec3d3248 | ||
|
|
d0c85a5a96 | ||
|
|
46ed717af4 | ||
|
|
ad37c88f80 | ||
|
|
8c6e8a6eb8 | ||
|
|
dd33317f45 | ||
|
|
c344d63fb3 | ||
|
|
62557f4d6b | ||
|
|
9c46cc4ea0 | ||
|
|
a236a439cc | ||
|
|
c779e2674a | ||
|
|
a94d4be466 | ||
|
|
b7d2d4cfe8 | ||
|
|
a6c797ee4b | ||
|
|
3e40b2249f | ||
|
|
2fe1b5034d | ||
|
|
b4430839a3 | ||
|
|
412cf974a4 | ||
|
|
3fc6beeff0 | ||
|
|
2ff35c8bfa | ||
|
|
169db88c20 | ||
|
|
ca419c84b8 | ||
|
|
aede71b1dc | ||
|
|
14d573cbf1 | ||
|
|
7819aa6b20 | ||
|
|
7cc6b63aac | ||
|
|
4af6362751 | ||
|
|
d54075197f | ||
|
|
5cce56199b | ||
|
|
3cc12817f6 | ||
|
|
68875ba600 | ||
|
|
c345c5ebae | ||
|
|
2119a16e24 | ||
|
|
9f5c938ff3 | ||
|
|
b42a8d2364 | ||
|
|
61fc01e79e | ||
|
|
cceaec1d74 | ||
|
|
8e5f9c8160 | ||
|
|
c620fd42f4 | ||
|
|
2a859f870a | ||
|
|
b680753db8 | ||
|
|
35af940166 | ||
|
|
bc0fec0afe | ||
|
|
e3eca82cc3 | ||
|
|
e7651d0d0f | ||
|
|
5cf4ce9318 | ||
|
|
6a6f28668f | ||
|
|
dadf1339b5 | ||
|
|
c2c4228722 | ||
|
|
5469941f2b | ||
|
|
e888c9e827 | ||
|
|
3d3bca5338 | ||
|
|
6a3008d7ff | ||
|
|
2556449b36 | ||
|
|
446f9973cc | ||
|
|
596f36bb78 | ||
|
|
e7abdab58d | ||
|
|
13cdc9a9f8 | ||
|
|
a1b8806422 | ||
|
|
e635d47912 | ||
|
|
53ac72a878 | ||
|
|
2be6184c8d | ||
|
|
8b0b4ff086 | ||
|
|
60f7ff3de5 | ||
|
|
e76c752987 | ||
|
|
042c1abc6e | ||
|
|
1401621913 | ||
|
|
3c034b0a0c | ||
|
|
395191bd75 | ||
|
|
ec7d8fb6bd | ||
|
|
062948b110 | ||
|
|
7f3dc5f040 | ||
|
|
dd0798a317 | ||
|
|
9209ccfa40 | ||
|
|
2525d32e17 | ||
|
|
f3ac8a7cd9 | ||
|
|
f3d95f70e2 | ||
|
|
57526067f5 | ||
|
|
0545bab35d | ||
|
|
293836494d | ||
|
|
ae8bda190e | ||
|
|
34d9d8abd4 | ||
|
|
bd67e986f4 | ||
|
|
e587518d85 | ||
|
|
210b6c38bd | ||
|
|
9592c0b466 | ||
|
|
a33537b2b9 | ||
|
|
81d5c9757c | ||
|
|
f34124a50b | ||
|
|
74a39c715b | ||
|
|
195496333b | ||
|
|
9ccaa90439 | ||
|
|
4733c5fef0 | ||
|
|
a97ddc8cb9 | ||
|
|
59a5b000e0 | ||
|
|
d49a2ccb08 | ||
|
|
4a75acb46c | ||
|
|
453368078b | ||
|
|
6e3b643bdf | ||
|
|
a769352a74 | ||
|
|
8a63c27925 | ||
|
|
310895f060 | ||
|
|
4428894aba | ||
|
|
3d12d7de62 | ||
|
|
87593f95d4 | ||
|
|
11626e48bf | ||
|
|
4f021cbced | ||
|
|
ba31d176c8 | ||
|
|
68edbc3e4a | ||
|
|
5068227a46 | ||
|
|
f8f3bc89e7 | ||
|
|
635f7026b0 | ||
|
|
0f1e4f0326 | ||
|
|
89355edb8a | ||
|
|
37c8f5c1da | ||
|
|
5bba2df526 | ||
|
|
1935694440 | ||
|
|
12dd6ea8bb | ||
|
|
a1f922b040 | ||
|
|
017cf088a1 | ||
|
|
df627cc417 | ||
|
|
1b20300b73 | ||
|
|
3b9ffea36f | ||
|
|
e242f3999c | ||
|
|
523f89cc8c | ||
|
|
0575a6d652 | ||
|
|
08b88714e0 | ||
|
|
bcaff4b844 | ||
|
|
82f336a78f | ||
|
|
350aa33022 | ||
|
|
d6290a2f1a | ||
|
|
71e9d08863 | ||
|
|
9729dc7594 | ||
|
|
92c1b8f31e | ||
|
|
9ffa2604f8 | ||
|
|
03b1fad4d4 | ||
|
|
72eb4c534f | ||
|
|
47d936e9cc | ||
|
|
8e08407090 | ||
|
|
c0a4558c62 | ||
|
|
5ecb6fecc4 | ||
|
|
b3df4dc2c9 | ||
|
|
3db9ab82ea | ||
|
|
2dc3acc5f0 | ||
|
|
25686e5bce | ||
|
|
8d70f808d9 | ||
|
|
7c887af1ea | ||
|
|
b7f435b50e | ||
|
|
d3b15f8fda | ||
|
|
eea726740b | ||
|
|
0ea4c970d7 | ||
|
|
57936a1374 | ||
|
|
9242fde6b8 | ||
|
|
d1f653be65 | ||
|
|
0035dceb0a | ||
|
|
838f7cd210 | ||
|
|
13539961b2 | ||
|
|
a26f2ef17d | ||
|
|
d97c46bcdc | ||
|
|
2b6542467c | ||
|
|
8fa51faa38 | ||
|
|
b2175629fd | ||
|
|
2e28ed8f81 | ||
|
|
4c4f8bf02a | ||
|
|
e464be5f39 | ||
|
|
d7d717f2ce | ||
|
|
d1eeed6a5b | ||
|
|
b159bc0c5f | ||
|
|
a222c4879c | ||
|
|
eb2f413cf0 | ||
|
|
736fd0e293 | ||
|
|
6592ca9f88 | ||
|
|
762712c756 | ||
|
|
73f9e17951 | ||
|
|
7d6a605a85 | ||
|
|
a6a8bdffc3 | ||
|
|
296085ff23 | ||
|
|
36aa8ce3c9 | ||
|
|
c49c69d6ea | ||
|
|
d5684f7444 | ||
|
|
affb4bd923 | ||
|
|
65772a74e0 | ||
|
|
cca2c2f4ca | ||
|
|
52e2fa91c4 | ||
|
|
dca405a746 | ||
|
|
e54748d355 | ||
|
|
3680a6bbbb | ||
|
|
6aa6a9c272 | ||
|
|
8d1c7ca206 | ||
|
|
52b8e0f9ec | ||
|
|
08e2e2e791 | ||
|
|
3c4f4793b5 | ||
|
|
e2950a7e4d | ||
|
|
531c0067ec | ||
|
|
e228caaca6 | ||
|
|
4b4aa64261 | ||
|
|
9ac8f89b1d | ||
|
|
c6a72a14ac | ||
|
|
c74edd0e33 | ||
|
|
98acf3f281 | ||
|
|
26735390ff | ||
|
|
9402b23dd5 | ||
|
|
246db3d565 | ||
|
|
eaf414cbc8 | ||
|
|
327d41c00f | ||
|
|
05d8ce3bcd | ||
|
|
fe30db935b | ||
|
|
6303d54cbb | ||
|
|
9d91aa23e6 | ||
|
|
c2ada39fd3 | ||
|
|
def2fe8805 | ||
|
|
77b6e27500 | ||
|
|
99640171e2 | ||
|
|
38b3ab791f | ||
|
|
a86fc52eea | ||
|
|
6d3ed3f16f | ||
|
|
be798998bf | ||
|
|
c3b425d570 | ||
|
|
a25743875f | ||
|
|
124d1a5942 | ||
|
|
eec77b3ae7 | ||
|
|
f680b0a431 | ||
|
|
d4b00ff11c | ||
|
|
532f94a187 | ||
|
|
87ad2f8542 | ||
|
|
640bac1aa4 | ||
|
|
0b12fae84d | ||
|
|
a8f891efcd | ||
|
|
d9353c4fd3 | ||
|
|
0c9fc2f809 | ||
|
|
b5fc2419e8 | ||
|
|
fe588a255b | ||
|
|
1fc571088b | ||
|
|
8d83914f05 | ||
|
|
0fdcd381bc | ||
|
|
4f293ecd6f | ||
|
|
b6303313f0 | ||
|
|
a28449a123 | ||
|
|
6dcec36621 | ||
|
|
1a954748a0 | ||
|
|
84d0fd39a3 | ||
|
|
4d4b7e3de0 | ||
|
|
e2aea6bce5 | ||
|
|
5779146a7f | ||
|
|
a1d1c2beaa | ||
|
|
ee9c60fad4 | ||
|
|
2abad0f479 | ||
|
|
1674a4ec82 | ||
|
|
ce370bee60 | ||
|
|
2fb40fe728 | ||
|
|
b57330cf75 | ||
|
|
8cd91fabf9 | ||
|
|
e0580ee385 | ||
|
|
e257484870 | ||
|
|
2a1f4539f6 | ||
|
|
906efdd320 | ||
|
|
144d092637 | ||
|
|
948b8f35e6 | ||
|
|
e776c605ad | ||
|
|
8b2f4fc823 | ||
|
|
03018611f8 | ||
|
|
2bde9afdb9 | ||
|
|
f8b09c194f | ||
|
|
0e69ad32c1 | ||
|
|
9b4e14df71 | ||
|
|
03f7803f26 | ||
|
|
c6cbcc2c25 | ||
|
|
27aa34d4ae | ||
|
|
79e0db4ca0 | ||
|
|
8f99c954ad | ||
|
|
5735c9efc1 | ||
|
|
9ae3acf2e7 | ||
|
|
731ea9b489 | ||
|
|
18b30b5019 | ||
|
|
e6c3acaa6f | ||
|
|
24d51b9d14 | ||
|
|
a1edc199df | ||
|
|
db4aa81528 | ||
|
|
319ba94a52 | ||
|
|
1025f0be91 | ||
|
|
77d3b5e8f1 | ||
|
|
a0ad96a787 | ||
|
|
39c5af5dbc | ||
|
|
acca6a799b | ||
|
|
b77e62260a | ||
|
|
b6995ca011 | ||
|
|
715844fd08 | ||
|
|
2b29ca796e | ||
|
|
44b4b50949 | ||
|
|
9ad862d3a6 | ||
|
|
77d71c4ee6 | ||
|
|
5184008812 | ||
|
|
cf696ce443 | ||
|
|
1a2ea4c06c | ||
|
|
8c0afd8557 | ||
|
|
2a56300f7b | ||
|
|
5f06999686 | ||
|
|
4c09aeb5a1 | ||
|
|
af892e7e80 | ||
|
|
0022fb100b | ||
|
|
4f2d67dfb0 | ||
|
|
b5645ab29f | ||
|
|
3149d1abf9 | ||
|
|
59a417fc84 | ||
|
|
b75d53413d | ||
|
|
c44a744c0b | ||
|
|
a800afcbf8 | ||
|
|
ff4534613a | ||
|
|
b8fe2c74bc | ||
|
|
be3b5199b0 | ||
|
|
d849a40af6 | ||
|
|
4f48c10312 | ||
|
|
5274fee8a7 | ||
|
|
5462f34ed0 | ||
|
|
0958ed5844 | ||
|
|
e3cba18532 | ||
|
|
9bf7fde49f | ||
|
|
4783ebc918 | ||
|
|
8bda52783b | ||
|
|
07feec7ea6 | ||
|
|
e7471f589a | ||
|
|
24208be5cf | ||
|
|
8db5f66498 | ||
|
|
be670bfd1f | ||
|
|
19acf090ed | ||
|
|
2de7f6b457 | ||
|
|
a2e265e9a2 | ||
|
|
28d7b29d72 | ||
|
|
76cddfab90 | ||
|
|
60b4f6b3eb | ||
|
|
546232b1c0 | ||
|
|
42c5788de3 | ||
|
|
fb00e7fddc | ||
|
|
41fdcf328c | ||
|
|
9cb4aaf3c2 | ||
|
|
80dc602193 | ||
|
|
144ad7992e | ||
|
|
a0dd1a1b8b | ||
|
|
c360e69162 | ||
|
|
c7f5a87258 | ||
|
|
da01c6ef5b | ||
|
|
fcd2355f4f | ||
|
|
748a8a6f42 | ||
|
|
cb9965bab5 | ||
|
|
429ed24c99 | ||
|
|
1ab46472ab | ||
|
|
f6bbe1332f | ||
|
|
11613347be | ||
|
|
8f46f1520c | ||
|
|
f2893b0d0f | ||
|
|
c7265f9689 | ||
|
|
46ab8d18e2 | ||
|
|
f384f8da93 | ||
|
|
23cd8a74be | ||
|
|
cc1debc948 | ||
|
|
5a3aa1262a | ||
|
|
ad52eb236d | ||
|
|
d2c2cbd0ae | ||
|
|
af4a93dbcf | ||
|
|
4478b3ef74 | ||
|
|
462bba8e2f | ||
|
|
dec7090198 | ||
|
|
83ec0e5552 | ||
|
|
cc261872c2 | ||
|
|
5223261f19 | ||
|
|
c594afeee7 | ||
|
|
32d10eedbd | ||
|
|
dfd98eede7 | ||
|
|
a728d7a026 | ||
|
|
5a26320680 | ||
|
|
90dc880e67 | ||
|
|
e11ff967d0 | ||
|
|
2dc6ed7b3a | ||
|
|
ad430c6617 | ||
|
|
e8d8bd4c0d | ||
|
|
8d5fa754e8 | ||
|
|
2ee047a1dd | ||
|
|
9562f66741 | ||
|
|
21223154aa | ||
|
|
ec48b5ea3a | ||
|
|
754015544f | ||
|
|
3f89f77429 | ||
|
|
9dee419b7c | ||
|
|
7612bf1bfa | ||
|
|
ad56e10e5b | ||
|
|
75f4772ba2 | ||
|
|
fe1b626f76 | ||
|
|
4e94516912 | ||
|
|
dadb6747ad | ||
|
|
188e1b440e | ||
|
|
a57f9e712d | ||
|
|
a549d871f3 | ||
|
|
e6fc88a758 | ||
|
|
20004b7ee0 | ||
|
|
84e037631d | ||
|
|
18e3d0b504 | ||
|
|
04c4398bfc | ||
|
|
39e42394bd | ||
|
|
a39e6b43e8 | ||
|
|
e69df36e4a | ||
|
|
e10b872fc3 | ||
|
|
2b78358af5 | ||
|
|
a59ab3e2ee | ||
|
|
28bcb8bdf5 | ||
|
|
9af9fd1400 | ||
|
|
0c80bd5fc0 | ||
|
|
7563ece236 | ||
|
|
a14cd97f56 | ||
|
|
0955f33a86 | ||
|
|
a016fb4048 | ||
|
|
e8ebb1af91 | ||
|
|
9fa3d7c4fa | ||
|
|
9423b456a1 | ||
|
|
64209749fb | ||
|
|
586b7601c6 | ||
|
|
4425989898 | ||
|
|
5b996ab880 | ||
|
|
635cfbae13 | ||
|
|
922e51e8a9 | ||
|
|
6d9f1ad61f | ||
|
|
841b9b3d63 | ||
|
|
a3745ae7f3 | ||
|
|
27d7013ff8 | ||
|
|
27d3340af2 | ||
|
|
d953225531 | ||
|
|
663815ead8 | ||
|
|
bc87ec0059 | ||
|
|
917434269c | ||
|
|
a77506ae21 | ||
|
|
ed5d297301 | ||
|
|
92b6ba9eff | ||
|
|
68c02fc95a | ||
|
|
d18c1b1a0a | ||
|
|
c980fc653d | ||
|
|
36782a977a | ||
|
|
653c4792b5 | ||
|
|
5cb061ebdf | ||
|
|
12f4225d6b | ||
|
|
50862a1dd1 | ||
|
|
013ebb638a | ||
|
|
bedb82bf4d | ||
|
|
cbb1ab58cd | ||
|
|
b01235e330 | ||
|
|
8341a9f7b2 | ||
|
|
b233c145fa | ||
|
|
9de5bb9e23 | ||
|
|
0c5305c51f | ||
|
|
63c9a20f96 | ||
|
|
b40c0811f4 | ||
|
|
838790fc2d | ||
|
|
1ff2d5b689 | ||
|
|
e4b7a113fd | ||
|
|
7820ebb82e | ||
|
|
31ab0b3df1 | ||
|
|
e598922133 | ||
|
|
e21ad70f3f | ||
|
|
a81cb932c2 | ||
|
|
3d66a4fee8 | ||
|
|
dea5601e79 | ||
|
|
43a840552f | ||
|
|
2f16f8e9f7 | ||
|
|
4364b30c42 | ||
|
|
c0da938d4f | ||
|
|
fb19aa355e | ||
|
|
6135419ac3 | ||
|
|
2bc5161e95 | ||
|
|
e2e8d0d2f3 | ||
|
|
b2e3c0757b | ||
|
|
0fc0196dba | ||
|
|
1492324c76 | ||
|
|
002b283433 | ||
|
|
5a2820ca3d | ||
|
|
9eb292c1e5 | ||
|
|
5216cfb3c8 | ||
|
|
373d1843a8 | ||
|
|
82ee278f53 | ||
|
|
c27fc26ecd | ||
|
|
fba13bd5d2 | ||
|
|
68f75955d8 | ||
|
|
cd21cfc115 | ||
|
|
05703cf73b | ||
|
|
1ea10db953 | ||
|
|
eb6188f8c0 | ||
|
|
6bb7a7439d | ||
|
|
ca6110d92f | ||
|
|
a219d488d0 | ||
|
|
1746257492 | ||
|
|
07423c300e | ||
|
|
69fcdc6a07 | ||
|
|
dda5415def | ||
|
|
4347114455 | ||
|
|
9fb26b5617 | ||
|
|
442b327582 | ||
|
|
cff727644d | ||
|
|
7f80349494 | ||
|
|
a6ef696132 | ||
|
|
53f08a95eb | ||
|
|
d0d2d6ed1b | ||
|
|
4e83b79d2b | ||
|
|
37e69a89b9 | ||
|
|
6f28eb4c0a | ||
|
|
caf93d9a2c | ||
|
|
747e945d29 | ||
|
|
067d7212bf | ||
|
|
867b93a5b2 | ||
|
|
53647c8ba1 | ||
|
|
b571ba27a4 | ||
|
|
de8f2739c2 | ||
|
|
2c1c588868 | ||
|
|
e271f69a34 | ||
|
|
39105f2119 | ||
|
|
4e080a9b06 | ||
|
|
d55740808d | ||
|
|
6566038df9 | ||
|
|
f0faf00320 | ||
|
|
51c7577c8f | ||
|
|
be308c6657 | ||
|
|
1729a0b437 | ||
|
|
1b2fc2ae19 | ||
|
|
58a75f0b78 | ||
|
|
daf6e2b5da | ||
|
|
71da5b026c | ||
|
|
2f68ea635b | ||
|
|
ca08c4dbf4 | ||
|
|
8af52fa205 | ||
|
|
3b0bf856c4 | ||
|
|
28f5803d34 | ||
|
|
0b72299857 | ||
|
|
4a2302c2ed | ||
|
|
980201a665 | ||
|
|
d60bcd2869 | ||
|
|
017d61a1aa | ||
|
|
4028d03a6e | ||
|
|
44b771aca1 | ||
|
|
8c581eca4d | ||
|
|
8e815bec9f | ||
|
|
9dda53e1d2 | ||
|
|
e8310211e2 | ||
|
|
3709b9aa11 | ||
|
|
c5a291ad74 | ||
|
|
894573657d | ||
|
|
9cce1d749a | ||
|
|
ed0b48040c | ||
|
|
9aeed06964 | ||
|
|
eda46bc07f | ||
|
|
c727e86980 | ||
|
|
57b4013306 | ||
|
|
fc5a7a8774 | ||
|
|
8248f498b2 | ||
|
|
22f3ffb099 | ||
|
|
81bd8ef73c | ||
|
|
bedc172eab | ||
|
|
0a9b0dd070 | ||
|
|
6aa0f61e15 | ||
|
|
4461c3d5d1 | ||
|
|
fef46bcf49 | ||
|
|
fa2ff849c5 | ||
|
|
6b2b91ff01 | ||
|
|
733962db2f | ||
|
|
f824ecdfa1 | ||
|
|
6c831e04a2 | ||
|
|
67c48f66a6 | ||
|
|
5fa1c703de | ||
|
|
bead892e21 | ||
|
|
8341daca4a | ||
|
|
136759a298 | ||
|
|
0ac5b6e613 | ||
|
|
eb5208c4f9 | ||
|
|
d88c3c8462 | ||
|
|
5399a72ec1 | ||
|
|
d093fb2441 | ||
|
|
0140804ef9 | ||
|
|
ecb5f68ba8 | ||
|
|
55bf242635 | ||
|
|
7dba410c26 | ||
|
|
92e66e5ab2 | ||
|
|
54dedef9cf | ||
|
|
b8b2ef3de1 | ||
|
|
681128def4 | ||
|
|
f4f4a908bb | ||
|
|
7c37f2879b | ||
|
|
615ccd1070 | ||
|
|
6ed69e6395 | ||
|
|
f946a182a3 | ||
|
|
d3fcb81da0 | ||
|
|
7e42637b74 | ||
|
|
dbe93d4a6d | ||
|
|
20bc25cdcc | ||
|
|
13c0788334 | ||
|
|
672cf84a32 | ||
|
|
2834f7802f | ||
|
|
b135483abc | ||
|
|
ba36cbde06 | ||
|
|
f153681522 | ||
|
|
d73291959b | ||
|
|
01a675eb5f | ||
|
|
be5143c0f3 | ||
|
|
b7ee1f5c40 | ||
|
|
b619f5e73d | ||
|
|
e44f639002 | ||
|
|
1aa470e0bf | ||
|
|
b6a33691ff | ||
|
|
5e616b4ff3 | ||
|
|
d847593a8e | ||
|
|
1cd5700c37 | ||
|
|
957cf3c7b0 | ||
|
|
b6c8de6e01 | ||
|
|
21f5ed4a25 | ||
|
|
52fc168905 | ||
|
|
3bb03db097 | ||
|
|
ee88b8f8c4 | ||
|
|
7bfcb6151d | ||
|
|
3c549c5147 | ||
|
|
8b0fb1cc4d | ||
|
|
e3d62305f1 | ||
|
|
263a968c2b | ||
|
|
39b37d547c | ||
|
|
4cfcdd5e0f | ||
|
|
9d72a34109 | ||
|
|
18cdbfa70f | ||
|
|
7886fd711f | ||
|
|
3993ed6b81 | ||
|
|
abf974c5c5 | ||
|
|
97f80e04b0 | ||
|
|
944057c30b | ||
|
|
9c4d4bd6b0 | ||
|
|
a77fd83b15 | ||
|
|
ed78955f8d | ||
|
|
cab16f8e1e | ||
|
|
24db28072f | ||
|
|
e20d3d49b3 | ||
|
|
33641df09c | ||
|
|
294cce2710 | ||
|
|
9fb31aafb3 | ||
|
|
791ddc566a | ||
|
|
7d2b2e371c | ||
|
|
c3ecb5aa5e | ||
|
|
191e520999 | ||
|
|
dd5fdbc540 | ||
|
|
b39855731c | ||
|
|
e27dd7280f | ||
|
|
a9ff134e9f | ||
|
|
11fbb1c807 | ||
|
|
15998c0db9 | ||
|
|
cf3ada3d04 | ||
|
|
7df94b1718 | ||
|
|
c2d788f6de | ||
|
|
fa8992fc63 | ||
|
|
8d631b346a | ||
|
|
393bcd961a | ||
|
|
4d1eedbaa2 | ||
|
|
6a95898038 | ||
|
|
9d55b16998 | ||
|
|
c52f469c9c | ||
|
|
50c0c4b701 | ||
|
|
8446b70ddc | ||
|
|
635791d1cd | ||
|
|
e9beea072d | ||
|
|
e69bb3b337 | ||
|
|
06f86b4532 | ||
|
|
68ece2fef3 | ||
|
|
4953ea90c2 | ||
|
|
e7a515c8b1 | ||
|
|
e9b75d462c | ||
|
|
68017b1254 | ||
|
|
469c9b5def | ||
|
|
e686d19154 | ||
|
|
762565e9d1 | ||
|
|
41a4662c8c | ||
|
|
5842015b90 | ||
|
|
ae56901863 | ||
|
|
4e364854ab | ||
|
|
a3645984cd | ||
|
|
43cf4e97b9 | ||
|
|
795578ef95 | ||
|
|
96493e0333 | ||
|
|
48b49e2303 | ||
|
|
752dfb3d95 | ||
|
|
5e0117b444 | ||
|
|
c05e6a1275 | ||
|
|
b21e4d9a58 | ||
|
|
eeef501ed8 | ||
|
|
f4e9275f7c | ||
|
|
f672657388 | ||
|
|
32441175f4 | ||
|
|
059d1dc7f2 | ||
|
|
7fbb856eee | ||
|
|
bb2aa70ec6 | ||
|
|
8e34c59c82 | ||
|
|
766b9fd453 | ||
|
|
891dab7b91 | ||
|
|
7a24e496d5 | ||
|
|
7a62818ffd | ||
|
|
acb0ff1ea8 | ||
|
|
2acad9fe1e | ||
|
|
8914ebc964 | ||
|
|
5477c31160 | ||
|
|
acd3f8cd91 | ||
|
|
ca1fc13116 | ||
|
|
9cb7760c5e | ||
|
|
fb9bd53328 | ||
|
|
a0a26d3341 | ||
|
|
9d4b7ab113 | ||
|
|
8ac776c58b | ||
|
|
006f4be71c | ||
|
|
163e05f5a0 | ||
|
|
02526eda86 | ||
|
|
828a5f552f | ||
|
|
26841b6058 | ||
|
|
1abfcc56af | ||
|
|
da47afe7d1 | ||
|
|
96546c1a8a | ||
|
|
579a8a96ea | ||
|
|
79848e3414 | ||
|
|
87c861cae3 | ||
|
|
8f326a33ee | ||
|
|
a942384fbf | ||
|
|
de0cdee4aa | ||
|
|
550c9319e9 | ||
|
|
ae9c02b3a8 | ||
|
|
6f6d47dd20 | ||
|
|
030e61115c | ||
|
|
d6d5caae23 | ||
|
|
382691179f | ||
|
|
01c5cb985c | ||
|
|
8a5d2c3c83 | ||
|
|
2f8135ef8b | ||
|
|
9b9abff972 | ||
|
|
c69cc31de0 | ||
|
|
241e94936f | ||
|
|
1330274ffc | ||
|
|
0e183d3fa1 | ||
|
|
cd8f92c928 | ||
|
|
e58b4f773f | ||
|
|
604ca50b65 | ||
|
|
537e353546 | ||
|
|
f95d7b13da | ||
|
|
915182bcb8 | ||
|
|
d3b763a48c | ||
|
|
816b6ad4a7 | ||
|
|
3adfbfe36d | ||
|
|
9d3865cb95 | ||
|
|
05cde5810a | ||
|
|
6b96f5d566 | ||
|
|
7a2e07e124 | ||
|
|
aad05fd138 | ||
|
|
032c5376ad | ||
|
|
0cc3b98bd9 | ||
|
|
c157711eaf | ||
|
|
2632794578 | ||
|
|
3ddf7b620c | ||
|
|
131cb0598a | ||
|
|
9862521aec | ||
|
|
55fbb67cfb | ||
|
|
a0384aaead | ||
|
|
efc885a9dc | ||
|
|
333e11d0eb | ||
|
|
58da24b1cb | ||
|
|
bb4cef6a93 | ||
|
|
5787f73704 | ||
|
|
201316cd67 | ||
|
|
a038bca745 | ||
|
|
cf38505d8f | ||
|
|
af964e8929 | ||
|
|
554b2b0ed9 | ||
|
|
627975e897 | ||
|
|
6622d69fda | ||
|
|
216dff98d2 | ||
|
|
032354e65c | ||
|
|
115d26608b | ||
|
|
bad350bc18 | ||
|
|
870436a592 | ||
|
|
6a17233f78 | ||
|
|
b54bde6f2b | ||
|
|
d2051c7f50 | ||
|
|
bd29f7e3c8 | ||
|
|
c3bb81abec | ||
|
|
d469c8137f | ||
|
|
c374a7d3f4 | ||
|
|
3679d5bd7a | ||
|
|
c128f2dd7e | ||
|
|
843717d25c | ||
|
|
50ea6a4b5c | ||
|
|
5649f22322 | ||
|
|
b89281411f | ||
|
|
67c41033c1 | ||
|
|
59534b92d2 | ||
|
|
41a48b14e3 | ||
|
|
5821bd1a21 | ||
|
|
c5cdac9609 | ||
|
|
b7f55ad392 | ||
|
|
71fee09744 | ||
|
|
f9af1a445e | ||
|
|
0bc8c0c1da | ||
|
|
74156d5bed | ||
|
|
b04adde7ab | ||
|
|
3f64ac04b8 | ||
|
|
b009970af7 | ||
|
|
bd97586cc4 | ||
|
|
c3e0fbd9e4 | ||
|
|
b05ba0286e | ||
|
|
228b03edf8 | ||
|
|
d000d31355 | ||
|
|
971c9671f6 | ||
|
|
3d95226f2b | ||
|
|
b12072e6d9 | ||
|
|
03427d4eff | ||
|
|
a627a703ca | ||
|
|
440cfc8052 | ||
|
|
c959148ed1 | ||
|
|
530e480748 | ||
|
|
95c3f283ea | ||
|
|
aef6609f4f | ||
|
|
6b728e4756 | ||
|
|
f1ecd9eac8 | ||
|
|
243c96304b | ||
|
|
e3597e648c | ||
|
|
357037f7ab | ||
|
|
9715218d40 | ||
|
|
c0b8c2c73b | ||
|
|
51d0687377 | ||
|
|
849d7895dc | ||
|
|
850e213261 | ||
|
|
786c1f035f | ||
|
|
f70d2f58a1 | ||
|
|
67b0ab717e | ||
|
|
f7420dbfe1 | ||
|
|
400600ffff | ||
|
|
940e66bb89 | ||
|
|
69391dadda | ||
|
|
ff3393ebf1 | ||
|
|
0ac0bd26e7 | ||
|
|
bf6258f582 | ||
|
|
8587fcbb93 | ||
|
|
a4fbf772c1 | ||
|
|
e28c5a0beb | ||
|
|
c38b9490a8 | ||
|
|
c3085d7b61 | ||
|
|
179502fe93 | ||
|
|
b6b377edd1 | ||
|
|
a76097210f | ||
|
|
ac0bf1a445 | ||
|
|
7b80e73810 | ||
|
|
6d11711a01 | ||
|
|
bae03e173e | ||
|
|
07b388f8d4 | ||
|
|
4809213676 | ||
|
|
c77fa296bc | ||
|
|
c412d6251e | ||
|
|
68c2cfbb40 | ||
|
|
4076523198 | ||
|
|
3857bb9990 | ||
|
|
300f936228 | ||
|
|
1358428031 | ||
|
|
5a480137d2 | ||
|
|
943bafbbc8 | ||
|
|
b772f26213 | ||
|
|
c33bbd947b | ||
|
|
d37b4bb199 | ||
|
|
ea92dee1ae | ||
|
|
5420f9ae76 | ||
|
|
39404725f0 | ||
|
|
5d1e5f4ea0 | ||
|
|
5184476682 | ||
|
|
141cbc60b9 | ||
|
|
2c1fb48318 | ||
|
|
1dfadf4815 | ||
|
|
f5b9e3c064 | ||
|
|
ff70dbd316 | ||
|
|
dd4625ce13 | ||
|
|
1648c7aa5b | ||
|
|
edaf017908 | ||
|
|
2c7bf61e68 | ||
|
|
7efb548921 | ||
|
|
0c5329aedc | ||
|
|
2d236e281f | ||
|
|
a6fd5819f9 | ||
|
|
cef5dcc0a1 | ||
|
|
e5b119a324 | ||
|
|
719990b1c5 | ||
|
|
f47bb8c1db | ||
|
|
873025a495 | ||
|
|
409a3ed808 | ||
|
|
76283c25a5 | ||
|
|
b9866e43d3 | ||
|
|
9947b82cad | ||
|
|
5b82ffc291 | ||
|
|
8068fd5228 | ||
|
|
b9c610ac87 | ||
|
|
59189160e3 | ||
|
|
ee5d3337a7 | ||
|
|
4c6a8e3ca5 | ||
|
|
4f38cc9cae | ||
|
|
dfc09a37c9 | ||
|
|
8588c21689 | ||
|
|
2decc65b45 | ||
|
|
0fb877740b | ||
|
|
37181c9181 | ||
|
|
08b139f37c | ||
|
|
3973aeecd2 | ||
|
|
5b2d32b499 | ||
|
|
e0d5ee0045 | ||
|
|
d35efddd65 | ||
|
|
7b13776f2d | ||
|
|
f937ec9a7c | ||
|
|
a3f5284dc6 | ||
|
|
4a8cc87b4d | ||
|
|
183725733a | ||
|
|
3c1bacbdbc | ||
|
|
d5ed23438a | ||
|
|
5bbaf0c9f1 | ||
|
|
f2f8290242 | ||
|
|
c1186693b5 | ||
|
|
80b220a3a6 | ||
|
|
5cc3c4f503 | ||
|
|
96204ea3dc | ||
|
|
9d1a34e30b | ||
|
|
69d9716f8b | ||
|
|
707b9fea17 | ||
|
|
d605329f83 | ||
|
|
f6e7dffada | ||
|
|
43549db718 | ||
|
|
7c0c8ca8d7 | ||
|
|
884be8e2b3 | ||
|
|
305de100a7 | ||
|
|
79035d7ed9 | ||
|
|
c1a999c492 | ||
|
|
e51d9fc6a9 | ||
|
|
2f69831fb8 | ||
|
|
b6a2ffd3d7 | ||
|
|
6a5f4651a1 | ||
|
|
70106464d3 | ||
|
|
b722d3d7f3 | ||
|
|
de57c21a3b | ||
|
|
84917721c7 | ||
|
|
118495d372 | ||
|
|
a6bee71f1a | ||
|
|
e0e6813a1d | ||
|
|
3d2a9d3545 | ||
|
|
6975c087e0 | ||
|
|
54033c74e4 | ||
|
|
8420f1420f | ||
|
|
3359f8785e | ||
|
|
96b974bc45 | ||
|
|
1a4025420c | ||
|
|
223b0db5bd | ||
|
|
2f4144e1cd | ||
|
|
f66edccffd | ||
|
|
b043ade456 | ||
|
|
217b494cc5 | ||
|
|
5ef62312af | ||
|
|
5479ed7cfb | ||
|
|
cac63bfd21 | ||
|
|
1573ea1485 | ||
|
|
e3af0032b2 | ||
|
|
9b21152600 | ||
|
|
c440faa94d | ||
|
|
375fd5ed4c | ||
|
|
ec25cda68b | ||
|
|
c9f726048c | ||
|
|
c2251dc5a2 | ||
|
|
a505cbc6c9 | ||
|
|
c422344190 | ||
|
|
1a4cfc3d90 | ||
|
|
2f1bd39be8 | ||
|
|
4624dfcb30 | ||
|
|
3546d931a1 | ||
|
|
4dadb965a7 | ||
|
|
f2184db1cd | ||
|
|
41f3f12709 | ||
|
|
5d55b45654 | ||
|
|
e3da174fca | ||
|
|
bb862a8ceb | ||
|
|
9ba900486e | ||
|
|
587284bae6 | ||
|
|
4c7bfa514f | ||
|
|
f9218423b9 | ||
|
|
ba34d48cf0 | ||
|
|
2598dd5109 | ||
|
|
fac6e9ecdb | ||
|
|
dbbead6e72 | ||
|
|
8cb9f9b070 | ||
|
|
61cb5df842 | ||
|
|
7e66f34154 | ||
|
|
f3203b5de5 | ||
|
|
65e56ff829 | ||
|
|
a4970c66ef | ||
|
|
945287358b | ||
|
|
979cd5a768 | ||
|
|
a68da8a475 | ||
|
|
2a9685cb3a | ||
|
|
9449006ac3 | ||
|
|
88abfc0d0f | ||
|
|
4ec25b5d64 | ||
|
|
e96dc9a14c | ||
|
|
a434c35eb4 | ||
|
|
7af8c88e37 | ||
|
|
a027bdf118 | ||
|
|
c043bf0f63 | ||
|
|
5744634094 | ||
|
|
a22c93b659 | ||
|
|
507606bb78 | ||
|
|
f421c8a191 | ||
|
|
d06f8baf69 | ||
|
|
56b8081af6 | ||
|
|
98d30ac9cf | ||
|
|
98150f503a | ||
|
|
6c78c21fb8 | ||
|
|
7fd2097a44 | ||
|
|
89893faa19 | ||
|
|
bfa7da943c | ||
|
|
734676fcfb | ||
|
|
acb265d082 | ||
|
|
77a647fc26 | ||
|
|
59ffb5b7c1 | ||
|
|
4dc25d3908 | ||
|
|
0f9f82f227 | ||
|
|
55e3aa8179 | ||
|
|
408d52fe39 | ||
|
|
b9013944dc | ||
|
|
e814f8d5bd | ||
|
|
64a481d873 | ||
|
|
93d8f9f00e | ||
|
|
d1742a2330 | ||
|
|
5555d30bbd | ||
|
|
4b17aca747 | ||
|
|
63dda94a02 | ||
|
|
7f9ee00980 | ||
|
|
53a749780a | ||
|
|
c504004702 | ||
|
|
8bfe3497b0 | ||
|
|
9bcfd3a47d | ||
|
|
451f932d80 | ||
|
|
10b7608926 | ||
|
|
2cd8a9fecf | ||
|
|
22279127f9 | ||
|
|
ad01e1249b | ||
|
|
6ef428af2e | ||
|
|
806496dfc9 | ||
|
|
9eadb517da | ||
|
|
1ae4e4dcd3 | ||
|
|
d099a7e464 | ||
|
|
25a806a347 | ||
|
|
73cff374fd | ||
|
|
cec019efff | ||
|
|
4748decd8d | ||
|
|
975143ab47 | ||
|
|
bb5acc939f | ||
|
|
bde3d14339 | ||
|
|
1403172ef3 | ||
|
|
f74ee1a352 | ||
|
|
cf8d278b78 | ||
|
|
82af161210 | ||
|
|
9fed97b1f1 | ||
|
|
83a9cf74fb | ||
|
|
a13e9832e7 | ||
|
|
f67426871b | ||
|
|
2bfddd4310 | ||
|
|
7b343eaf50 | ||
|
|
ac830468bf | ||
|
|
7547b1170b | ||
|
|
e53ca368a5 | ||
|
|
c426a0bc5c | ||
|
|
acc99da73d | ||
|
|
c4f895daf4 | ||
|
|
ede828c910 | ||
|
|
7dd172efec | ||
|
|
80ac82c8fb | ||
|
|
fd182f6d1e | ||
|
|
a05d0d5d94 | ||
|
|
0107ef2aad | ||
|
|
a153f21315 | ||
|
|
294aaf7a90 | ||
|
|
2024763d2a | ||
|
|
9526fdbe73 | ||
|
|
a5c4566fa1 | ||
|
|
2c05430002 | ||
|
|
643650dba7 | ||
|
|
8cb6854da4 | ||
|
|
83844ec239 | ||
|
|
1bfd25be35 | ||
|
|
cee216f2dc | ||
|
|
27c246e8d9 | ||
|
|
1ff39476eb | ||
|
|
acfd9a73bc | ||
|
|
b479a264b6 | ||
|
|
ac32f36e4e | ||
|
|
b8ccc885c8 | ||
|
|
4e2fb3fb89 | ||
|
|
9cd2129eeb | ||
|
|
097e200a97 | ||
|
|
bd28caed3c | ||
|
|
bc4266bef8 | ||
|
|
bc629c8a3e | ||
|
|
2e9e34aa40 | ||
|
|
c953d6409d | ||
|
|
447e4d3583 | ||
|
|
33bf6c0978 | ||
|
|
d69e0ab53a | ||
|
|
e7c267db4f | ||
|
|
6a5e7c118b | ||
|
|
bddb6b4273 | ||
|
|
7ec32704f9 | ||
|
|
8d217567c6 | ||
|
|
a17d814381 | ||
|
|
5554633ab8 | ||
|
|
8cd845b79e | ||
|
|
06da91a73b | ||
|
|
792010ba32 | ||
|
|
b69246c646 | ||
|
|
f4ea9b7393 | ||
|
|
1ff101c568 | ||
|
|
a93ffdd1be | ||
|
|
319149254d | ||
|
|
f7ce4f6239 | ||
|
|
28442cce9f | ||
|
|
f122e6d456 | ||
|
|
8391ac4cc8 | ||
|
|
898d885ae2 | ||
|
|
a61d0c9567 | ||
|
|
eea4edd92c | ||
|
|
cb331ae436 | ||
|
|
767e27c8f0 | ||
|
|
e78370e050 | ||
|
|
ea616b3ed4 | ||
|
|
a9dc0e816c | ||
|
|
4390d72b14 | ||
|
|
2bf740fc71 | ||
|
|
dbb1e732b8 | ||
|
|
7ec503c4ec | ||
|
|
a07ab27dae | ||
|
|
727c301fbc | ||
|
|
71f881d5cb | ||
|
|
d4e3fb4330 | ||
|
|
57f69a2915 | ||
|
|
448aefaace | ||
|
|
47b5e73a15 | ||
|
|
d6a6f428b3 | ||
|
|
fd3934b849 | ||
|
|
e5857cb722 | ||
|
|
2ec89c6304 | ||
|
|
76953a9748 | ||
|
|
128a5fa4a5 | ||
|
|
a5960c20cc | ||
|
|
aa71ce4cd5 | ||
|
|
16c981d425 | ||
|
|
81e56705ad | ||
|
|
ecb118f1ed | ||
|
|
1e663b1869 | ||
|
|
7013f9fc31 | ||
|
|
31cabc751d | ||
|
|
0478a8e288 | ||
|
|
1039d57251 | ||
|
|
934a38f976 | ||
|
|
8f261af5c1 | ||
|
|
3e8bdb9384 | ||
|
|
7542ec4f20 | ||
|
|
89ba540e6d | ||
|
|
47ab2ad6f3 | ||
|
|
06d5d4b03e | ||
|
|
f3739a73af | ||
|
|
1b227e0145 | ||
|
|
308b3f2337 | ||
|
|
1e56107967 | ||
|
|
b0e1a3d34c | ||
|
|
1cc4914b24 | ||
|
|
aec18c74ec | ||
|
|
4729d10bb2 | ||
|
|
a8af3ce0dd | ||
|
|
d657be33ba | ||
|
|
a86f9b8035 | ||
|
|
32f212cb86 | ||
|
|
fdc479676f | ||
|
|
4cdf62000c | ||
|
|
761771ad24 | ||
|
|
f5f4a9da6b | ||
|
|
eefef369ea | ||
|
|
4ebf53ffdf | ||
|
|
8b6a5d19d0 | ||
|
|
129920e8f4 | ||
|
|
dc3b96a003 | ||
|
|
5a0d77bea3 | ||
|
|
430b5b0490 | ||
|
|
cde9408bd8 | ||
|
|
85ce9aa7de | ||
|
|
f2ff2409ad | ||
|
|
bff4c54ece | ||
|
|
df0c26a394 | ||
|
|
1e56c7b862 | ||
|
|
4a0d4a02a6 | ||
|
|
4231ec51c3 | ||
|
|
f401c1059c | ||
|
|
e28a0e97b5 | ||
|
|
499ed62dd7 | ||
|
|
c3e02bec3b | ||
|
|
4363c49443 | ||
|
|
09571fcc55 | ||
|
|
c7563a5783 | ||
|
|
b06b4f937d | ||
|
|
86c6bb618b | ||
|
|
bff72634ca | ||
|
|
6f060081be | ||
|
|
7f865f722c | ||
|
|
614a011845 | ||
|
|
0c66832b3b | ||
|
|
f78366910e | ||
|
|
c426bbcf95 | ||
|
|
6d71094ce5 | ||
|
|
9322f04529 | ||
|
|
884201b919 | ||
|
|
a0afd0369f | ||
|
|
caf48ee973 | ||
|
|
0749dbfadf | ||
|
|
54889c72e3 | ||
|
|
cba126ceb8 | ||
|
|
c95663312a | ||
|
|
00ee3de7b2 | ||
|
|
172a1dbdb9 | ||
|
|
c5a662f405 | ||
|
|
9277950441 | ||
|
|
44c97a8f6d | ||
|
|
6a071efa27 | ||
|
|
bfed1c04cc | ||
|
|
bdb1965b50 | ||
|
|
c9d43b4d71 | ||
|
|
49ede85827 | ||
|
|
05693e2d5d | ||
|
|
c5ec035fb4 | ||
|
|
5e2f98fdad | ||
|
|
7b92750622 | ||
|
|
37754559b8 | ||
|
|
09008cb0ec | ||
|
|
b58aa1f5ee | ||
|
|
38e9205d4e | ||
|
|
16ff44ad30 | ||
|
|
1745e68795 | ||
|
|
809c96b53f | ||
|
|
03e43356ce | ||
|
|
d718a8b59d | ||
|
|
4100035b19 | ||
|
|
9bef46c0da | ||
|
|
c134adbcbf | ||
|
|
7bc8c7518b | ||
|
|
739984f920 | ||
|
|
7261739526 | ||
|
|
62f9df98b4 | ||
|
|
c76f71e8d6 | ||
|
|
f7cc5b2efd | ||
|
|
df7d7732c6 | ||
|
|
889b1c1eae | ||
|
|
297d749fc8 | ||
|
|
6233de0546 | ||
|
|
b1afa40fc1 | ||
|
|
a620e936cc | ||
|
|
d897170455 | ||
|
|
5a886da93b | ||
|
|
9206f54979 | ||
|
|
75c0a33ec5 | ||
|
|
8ae9b45da0 | ||
|
|
1eebbc746f | ||
|
|
adb2f66ced | ||
|
|
7246d67263 | ||
|
|
971fbe5d8e | ||
|
|
45df3e5e54 | ||
|
|
468b7d3aea | ||
|
|
0275690b5c | ||
|
|
da8095db54 | ||
|
|
5d74b1efef | ||
|
|
d3b5574d7a | ||
|
|
90472526e0 | ||
|
|
206392ad1a | ||
|
|
171a9ee291 | ||
|
|
13e725ab09 | ||
|
|
f5c2acf1d4 | ||
|
|
17e3054399 | ||
|
|
09aadffe9b | ||
|
|
e47bdfe8e6 | ||
|
|
cd9c0a6b3e | ||
|
|
c372c3756b | ||
|
|
78fa3f06f9 | ||
|
|
1350cd0e42 | ||
|
|
89d4f438c0 | ||
|
|
8bd7b5b607 | ||
|
|
d78be1ab49 | ||
|
|
86e279f886 | ||
|
|
d4914fc9ef | ||
|
|
46c19b8249 | ||
|
|
077b24d62d | ||
|
|
111c4dac63 | ||
|
|
69ff9d757f | ||
|
|
0c2ab17e91 | ||
|
|
12e82b9e33 | ||
|
|
644fc48776 | ||
|
|
666b1fae79 | ||
|
|
e3c436f411 | ||
|
|
48f3e13bec | ||
|
|
a923080d9b | ||
|
|
706614b0d7 | ||
|
|
a641f562f3 | ||
|
|
fe96bdf7e6 | ||
|
|
5b78b46a30 | ||
|
|
eacd604518 | ||
|
|
637c6a1850 | ||
|
|
0cbc4012e8 | ||
|
|
8074b82653 | ||
|
|
5d583c9b2d | ||
|
|
093e900d44 | ||
|
|
8965b66ce4 | ||
|
|
146add67c2 | ||
|
|
34e9a0a960 | ||
|
|
b1e95b1fa8 | ||
|
|
ce072b89d2 | ||
|
|
4ffd9bce5a | ||
|
|
f16d05c633 |
21
.gitignore
vendored
21
.gitignore
vendored
@@ -24,6 +24,7 @@ config.sub
|
|||||||
config_detected.h
|
config_detected.h
|
||||||
config_detected.mk
|
config_detected.mk
|
||||||
configure
|
configure
|
||||||
|
configure.lineno
|
||||||
depcomp
|
depcomp
|
||||||
depmode
|
depmode
|
||||||
install-sh
|
install-sh
|
||||||
@@ -32,10 +33,15 @@ ltmain.sh
|
|||||||
missing
|
missing
|
||||||
mkinstalldirs
|
mkinstalldirs
|
||||||
mpd
|
mpd
|
||||||
|
mpd.exe
|
||||||
|
mpd.service
|
||||||
stamp-h1
|
stamp-h1
|
||||||
tags
|
tags
|
||||||
*~
|
*~
|
||||||
|
.#*
|
||||||
.stgit*
|
.stgit*
|
||||||
|
src/dsd2pcm/dsd2pcm
|
||||||
|
doc/doxygen.conf
|
||||||
doc/protocol.html
|
doc/protocol.html
|
||||||
doc/protocol
|
doc/protocol
|
||||||
doc/user
|
doc/user
|
||||||
@@ -43,10 +49,25 @@ doc/developer
|
|||||||
doc/sticker
|
doc/sticker
|
||||||
doc/api
|
doc/api
|
||||||
test/software_volume
|
test/software_volume
|
||||||
|
test/run_convert
|
||||||
test/run_decoder
|
test/run_decoder
|
||||||
test/read_tags
|
test/read_tags
|
||||||
|
test/run_filter
|
||||||
test/run_encoder
|
test/run_encoder
|
||||||
test/run_output
|
test/run_output
|
||||||
test/read_conf
|
test/read_conf
|
||||||
test/run_input
|
test/run_input
|
||||||
test/read_mixer
|
test/read_mixer
|
||||||
|
test/dump_playlist
|
||||||
|
test/run_normalize
|
||||||
|
test/tmp
|
||||||
|
test/run_inotify
|
||||||
|
test/test_queue_priority
|
||||||
|
test/run_ntp_server
|
||||||
|
test/run_resolver
|
||||||
|
test/run_tcp_connect
|
||||||
|
test/test_pcm
|
||||||
|
test/dump_rva2
|
||||||
|
test/dump_text_file
|
||||||
|
test/test_byte_reverse
|
||||||
|
test/test_vorbis_encoder
|
||||||
|
|||||||
3
AUTHORS
3
AUTHORS
@@ -19,9 +19,6 @@ Eric Wollesen <encoded@xmtp.net>
|
|||||||
Thomas Jansen <mithi@mithi.net>
|
Thomas Jansen <mithi@mithi.net>
|
||||||
multithreading tweaks, miscellaneous
|
multithreading tweaks, miscellaneous
|
||||||
|
|
||||||
Rasmus Steinke <rasi1979@googlemail.com>
|
|
||||||
documentation
|
|
||||||
|
|
||||||
Romain Bignon <romain@peerfuse.org>
|
Romain Bignon <romain@peerfuse.org>
|
||||||
playlist manipulation
|
playlist manipulation
|
||||||
|
|
||||||
|
|||||||
36
INSTALL
36
INSTALL
@@ -13,7 +13,7 @@ Dependencies
|
|||||||
gcc - http://gcc.gnu.org/
|
gcc - http://gcc.gnu.org/
|
||||||
Any other C99 compliant compiler should also work.
|
Any other C99 compliant compiler should also work.
|
||||||
|
|
||||||
glib - http://www.gtk.org/
|
GLib 2.16 - http://www.gtk.org/
|
||||||
General-purpose utility library.
|
General-purpose utility library.
|
||||||
|
|
||||||
|
|
||||||
@@ -56,6 +56,12 @@ libshout - http://www.icecast.org/
|
|||||||
For streaming to an Icecast or Shoutcast server.
|
For streaming to an Icecast or Shoutcast server.
|
||||||
You also need an encoder: either libvorbisenc (ogg), or liblame (mp3).
|
You also need an encoder: either libvorbisenc (ogg), or liblame (mp3).
|
||||||
|
|
||||||
|
OpenAL - http://kcat.strangesoft.net/openal.html
|
||||||
|
Open Audio Library
|
||||||
|
|
||||||
|
libffado - http://www.ffado.org/
|
||||||
|
For FireWire audio devices.
|
||||||
|
|
||||||
|
|
||||||
Optional Input Dependencies
|
Optional Input Dependencies
|
||||||
---------------------------
|
---------------------------
|
||||||
@@ -69,17 +75,15 @@ MAD - http://www.underbit.com/products/mad/
|
|||||||
For MP3 support. You will need libmad, and optionally libid3tag if you want
|
For MP3 support. You will need libmad, and optionally libid3tag if you want
|
||||||
ID3 tag support.
|
ID3 tag support.
|
||||||
|
|
||||||
|
libmpg123 - http://www.mpg123.de/
|
||||||
|
Alternative for MP3 support.
|
||||||
|
|
||||||
Ogg Vorbis - http://www.xiph.org/ogg/vorbis/
|
Ogg Vorbis - http://www.xiph.org/ogg/vorbis/
|
||||||
For Ogg Vorbis support. You will need libogg and libvorbis.
|
For Ogg Vorbis support. You will need libogg and libvorbis.
|
||||||
|
|
||||||
FLAC - http://flac.sourceforge.net/
|
FLAC - http://flac.sourceforge.net/
|
||||||
For FLAC support. You will need version 1.1.0 or higher of libflac.
|
For FLAC support. You will need version 1.1.0 or higher of libflac.
|
||||||
|
|
||||||
OggFLAC - http://www.xiph.org/ogg/vorbis/ and http://flac.sourceforge.net/
|
|
||||||
For OggFLAC support. You will need liboggflac, which can be built from the
|
|
||||||
FLAC sources if libogg is already installed. Versions of flac 1.1.3 and
|
|
||||||
greater will automatically detect and use OggFLAC if it's available.
|
|
||||||
|
|
||||||
Audio File - http://www.68k.org/~michael/audiofile/
|
Audio File - http://www.68k.org/~michael/audiofile/
|
||||||
For WAVE, AIFF, and AU support. You will need libaudiofile.
|
For WAVE, AIFF, and AU support. You will need libaudiofile.
|
||||||
|
|
||||||
@@ -92,18 +96,27 @@ For Musepack support.
|
|||||||
MikMod - http://mikmod.raphnet.net/
|
MikMod - http://mikmod.raphnet.net/
|
||||||
For MOD support. You will need libmikmod.
|
For MOD support. You will need libmikmod.
|
||||||
|
|
||||||
libavcodec, libavformat (ffmpeg) - http://ffmpeg.mplayerhq.hu/
|
libavcodec, libavformat (ffmpeg or libav) - http://ffmpeg.mplayerhq.hu/ http://libav.org/
|
||||||
Multi-codec library.
|
Multi-codec library.
|
||||||
|
|
||||||
libsidplay2 - http://sidplay2.sourceforge.net/
|
libsidplay2 - http://sidplay2.sourceforge.net/
|
||||||
For C64 SID support.
|
For C64 SID support.
|
||||||
|
|
||||||
libfluidsynth - http://fluidsynth.resonance.org/
|
libfluidsynth - http://fluidsynth.resonance.org/
|
||||||
For MIDI support (DO NOT USE - use libwildmidi instead)
|
For MIDI support.
|
||||||
|
|
||||||
libwildmidi - http://wildmidi.sourceforge.net/
|
libwildmidi - http://wildmidi.sourceforge.net/
|
||||||
For MIDI support.
|
For MIDI support.
|
||||||
|
|
||||||
|
libsndfile - http://www.mega-nerd.com/libsndfile/
|
||||||
|
WAVE, AIFF, and many others.
|
||||||
|
|
||||||
|
libwavpack - http://www.wavpack.com/
|
||||||
|
For WavPack playback.
|
||||||
|
|
||||||
|
despotify - https://github.com/SimonKagstrom/despotify
|
||||||
|
For Spotify playback.
|
||||||
|
|
||||||
|
|
||||||
Optional Miscellaneous Dependencies
|
Optional Miscellaneous Dependencies
|
||||||
-----------------------------------
|
-----------------------------------
|
||||||
@@ -123,8 +136,11 @@ For playing MMS streams.
|
|||||||
SQLite - http://www.sqlite.org/
|
SQLite - http://www.sqlite.org/
|
||||||
For the sticker database.
|
For the sticker database.
|
||||||
|
|
||||||
libcue - http://libcue.sourceforge.net/
|
libcdio - http://www.gnu.org/software/libcdio/
|
||||||
For CUE sheet support.
|
For playing audio CDs.
|
||||||
|
|
||||||
|
libsystemd-daemon - http://freedesktop.org/wiki/Software/systemd/
|
||||||
|
For systemd activation.
|
||||||
|
|
||||||
|
|
||||||
pkg-config
|
pkg-config
|
||||||
|
|||||||
1039
Makefile.am
1039
Makefile.am
File diff suppressed because it is too large
Load Diff
373
NEWS
373
NEWS
@@ -1,3 +1,376 @@
|
|||||||
|
ver 0.17.3 (2013/01/06)
|
||||||
|
* output:
|
||||||
|
- osx: fix pops during playback
|
||||||
|
- recorder: fix I/O error check
|
||||||
|
- shout: fix memory leak in error handler
|
||||||
|
- recorder, shout: support Ogg packets that span more than one page
|
||||||
|
* decoder:
|
||||||
|
- ffmpeg: ignore negative time stamps
|
||||||
|
- ffmpeg: support planar audio
|
||||||
|
* playlist:
|
||||||
|
- cue: fix memory leak
|
||||||
|
- cue: fix CUE files with only one track
|
||||||
|
|
||||||
|
ver 0.17.2 (2012/09/30)
|
||||||
|
* protocol:
|
||||||
|
- fix crash in local file check
|
||||||
|
* decoder:
|
||||||
|
- fluidsynth: remove throttle (requires libfluidsynth 1.1)
|
||||||
|
- fluidsynth: stop playback at end of file
|
||||||
|
- fluidsynth: check MIDI file format while scanning
|
||||||
|
- fluidsynth: add sample rate setting
|
||||||
|
- wavpack: support all APEv2 tags
|
||||||
|
* output:
|
||||||
|
- httpd: use monotonic clock, avoid hiccups after system clock adjustment
|
||||||
|
- httpd: fix throttling bug after resuming playback
|
||||||
|
* playlist:
|
||||||
|
- cue: map "PERFORMER" to "artist" or "album artist"
|
||||||
|
* mapper: fix non-UTF8 music directory name
|
||||||
|
* mapper: fix potential crash in file permission check
|
||||||
|
* playlist: fix use-after-free bug
|
||||||
|
* playlist: fix memory leak
|
||||||
|
* state_file: save song priorities
|
||||||
|
* player: disable cross-fading in "single" mode
|
||||||
|
* update: fix unsafe readlink() usage
|
||||||
|
* configure.ac:
|
||||||
|
- don't auto-detect the vorbis encoder when Tremor is enabled
|
||||||
|
|
||||||
|
ver 0.17.1 (2012/07/31)
|
||||||
|
* protocol:
|
||||||
|
- require appropriate permissions for searchadd{,pl}
|
||||||
|
* tags:
|
||||||
|
- aiff: support the AIFC format
|
||||||
|
- ape: check for ID3 if no usable APE tag was found
|
||||||
|
* playlist:
|
||||||
|
- cue: support file types "MP3", "AIFF"
|
||||||
|
* output:
|
||||||
|
- fix noisy playback with conversion and software volume
|
||||||
|
|
||||||
|
ver 0.17 (2012/06/27)
|
||||||
|
* protocol:
|
||||||
|
- support client-to-client communication
|
||||||
|
- "update" and "rescan" need only "CONTROL" permission
|
||||||
|
- new command "seekcur" for simpler seeking within current song
|
||||||
|
- new command "config" dumps location of music directory
|
||||||
|
- add range parameter to command "load"
|
||||||
|
- print extra "playlist" object for embedded CUE sheets
|
||||||
|
- new commands "searchadd", "searchaddpl"
|
||||||
|
* input:
|
||||||
|
- cdio_paranoia: new input plugin to play audio CDs
|
||||||
|
- curl: enable CURLOPT_NETRC
|
||||||
|
- curl: non-blocking I/O
|
||||||
|
- soup: new input plugin based on libsoup
|
||||||
|
* tags:
|
||||||
|
- RVA2: support separate album/track replay gain
|
||||||
|
* decoder:
|
||||||
|
- mpg123: implement seeking
|
||||||
|
- ffmpeg: drop support for pre-0.5 ffmpeg
|
||||||
|
- ffmpeg: support WebM
|
||||||
|
- oggflac: delete this obsolete plugin
|
||||||
|
- dsdiff: new decoder plugin
|
||||||
|
* output:
|
||||||
|
- alsa: support DSD-over-USB (dCS suggested standard)
|
||||||
|
- httpd: support for streaming to a DLNA client
|
||||||
|
- openal: improve buffer cancellation
|
||||||
|
- osx: allow user to specify other audio devices
|
||||||
|
- osx: implement 32 bit playback
|
||||||
|
- shout: add possibility to set url
|
||||||
|
- roar: new output plugin for RoarAudio
|
||||||
|
- winmm: fail if wrong device specified instead of using default device
|
||||||
|
* mixer:
|
||||||
|
- alsa: listen for external volume changes
|
||||||
|
* playlist:
|
||||||
|
- allow references to songs outside the music directory
|
||||||
|
- new CUE parser, without libcue
|
||||||
|
- soundcloud: new plugin for accessing soundcloud.com
|
||||||
|
* state_file: add option "restore_paused"
|
||||||
|
* cue: show CUE track numbers
|
||||||
|
* allow port specification in "bind_to_address" settings
|
||||||
|
* support floating point samples
|
||||||
|
* systemd socket activation
|
||||||
|
* improve --version output
|
||||||
|
* WIN32: fix renaming of stored playlists with non-ASCII names
|
||||||
|
|
||||||
|
|
||||||
|
ver 0.16.8 (2012/04/04)
|
||||||
|
* fix for libsamplerate assertion failure
|
||||||
|
* decoder:
|
||||||
|
- vorbis (and others): fix seeking at startup
|
||||||
|
- ffmpeg: read the "year" tag
|
||||||
|
* encoder:
|
||||||
|
- vorbis: generate end-of-stream packet before tag
|
||||||
|
- vorbis: generate end-of-stream packet when playback ends
|
||||||
|
* output:
|
||||||
|
- jack: check for connection failure before starting playback
|
||||||
|
- jack: workaround for libjack1 crash bug
|
||||||
|
- osx: fix stuttering due to buffering bug
|
||||||
|
* fix endless loop in text file reader
|
||||||
|
* update: skip symlinks in path that is to be updated
|
||||||
|
|
||||||
|
|
||||||
|
ver 0.16.7 (2012/02/04)
|
||||||
|
* input:
|
||||||
|
- ffmpeg: support libavformat 0.7
|
||||||
|
* decoder:
|
||||||
|
- ffmpeg: support libavformat 0.8, libavcodec 0.9
|
||||||
|
- ffmpeg: support all MPD tags
|
||||||
|
* output:
|
||||||
|
- httpd: fix excessive buffering
|
||||||
|
- openal: force 16 bit playback, as 8 bit doesn't work
|
||||||
|
- osx: remove sleep call from render callback
|
||||||
|
- osx: clear render buffer when there's not enough data
|
||||||
|
* fix moving after current song
|
||||||
|
|
||||||
|
|
||||||
|
ver 0.16.6 (2011/12/01)
|
||||||
|
* decoder:
|
||||||
|
- fix assertion failure when resuming streams
|
||||||
|
- ffmpeg: work around bogus channel count
|
||||||
|
* encoder:
|
||||||
|
- flac, null, wave: fix buffer corruption bug
|
||||||
|
- wave: support packed 24 bit samples
|
||||||
|
* mapper: fix the bogus "not a directory" error message
|
||||||
|
* mapper: check "x" and "r" permissions on music directory
|
||||||
|
* log: print reason for failure
|
||||||
|
* event_pipe: fix WIN32 regression
|
||||||
|
* define WINVER in ./configure
|
||||||
|
* WIN32: autodetect filesystem encoding
|
||||||
|
|
||||||
|
|
||||||
|
ver 0.16.5 (2011/10/09)
|
||||||
|
* configure.ac
|
||||||
|
- disable assertions in the non-debugging build
|
||||||
|
- show solaris plugin result correctly
|
||||||
|
- add option --enable-solaris-output
|
||||||
|
* pcm_format: fix 32-to-24 bit conversion (the "silence" bug)
|
||||||
|
* input:
|
||||||
|
- rewind: reduce heap usage
|
||||||
|
* decoder:
|
||||||
|
- ffmpeg: higher precision timestamps
|
||||||
|
- ffmpeg: don't require key frame for seeking
|
||||||
|
- fix CUE track seeking
|
||||||
|
* output:
|
||||||
|
- openal: auto-fallback to mono if channel count is unsupported
|
||||||
|
* player:
|
||||||
|
- make seeking to CUE track more reliable
|
||||||
|
- the "seek" command works when MPD is stopped
|
||||||
|
- restore song position from state file (bug fix)
|
||||||
|
- fix crash that sometimes occurred when audio device fails on startup
|
||||||
|
- fix absolute path support in playlists
|
||||||
|
* WIN32: close sockets properly
|
||||||
|
* install systemd service file if systemd is available
|
||||||
|
|
||||||
|
|
||||||
|
ver 0.16.4 (2011/09/01)
|
||||||
|
* don't abort configure when avahi is not found
|
||||||
|
* auto-detect libmad without pkg-config
|
||||||
|
* fix memory leaks
|
||||||
|
* don't resume playback when seeking to another song while paused
|
||||||
|
* apply follow_inside_symlinks to absolute symlinks
|
||||||
|
* fix playback discontinuation after seeking
|
||||||
|
* input:
|
||||||
|
- curl: limit the receive buffer size
|
||||||
|
- curl: implement a hard-coded timeout of 10 seconds
|
||||||
|
* decoder:
|
||||||
|
- ffmpeg: workaround for semantic API change in recent ffmpeg versions
|
||||||
|
- flac: validate the sample rate when scanning the tag
|
||||||
|
- wavpack: obey all decoder commands, stop at CUE track border
|
||||||
|
* encoder:
|
||||||
|
- vorbis: don't send end-of-stream on flush
|
||||||
|
* output:
|
||||||
|
- alsa: fix SIGFPE when alsa announces a period size of 0
|
||||||
|
- httpd: don't warn on client disconnect
|
||||||
|
- osx: don't drain the buffer when closing
|
||||||
|
- pulse: fix deadlock when resuming the stream
|
||||||
|
- pulse: fix deadlock when the stream was suspended
|
||||||
|
|
||||||
|
|
||||||
|
ver 0.16.3 (2011/06/04)
|
||||||
|
* fix assertion failure in audio format mask parser
|
||||||
|
* fix NULL pointer dereference in playlist parser
|
||||||
|
* fix playlist files in base music directory
|
||||||
|
* database: allow directories with just playlists
|
||||||
|
* decoder:
|
||||||
|
- ffmpeg: support libavcodec 0.7
|
||||||
|
|
||||||
|
|
||||||
|
ver 0.16.2 (2011/03/18)
|
||||||
|
* configure.ac:
|
||||||
|
- fix bashism in tremor test
|
||||||
|
* decoder:
|
||||||
|
- tremor: fix configure test
|
||||||
|
- gme: detect end of song
|
||||||
|
* encoder:
|
||||||
|
- vorbis: reset the Ogg stream after flush
|
||||||
|
* output:
|
||||||
|
- httpd: fix uninitialized variable
|
||||||
|
- httpd: include sys/socket.h
|
||||||
|
- oss: AFMT_S24_PACKED is little-endian
|
||||||
|
- oss: disable 24 bit playback on FreeBSD
|
||||||
|
|
||||||
|
|
||||||
|
ver 0.16.1 (2011/01/09)
|
||||||
|
* audio_check: fix parameter in prototype
|
||||||
|
* add void casts to suppress "result unused" warnings (clang)
|
||||||
|
* input:
|
||||||
|
- ffado: disable by default
|
||||||
|
* decoder:
|
||||||
|
- mad: work around build failure on Solaris
|
||||||
|
- resolve modplug vs. libsndfile cflags/headers conflict
|
||||||
|
* output:
|
||||||
|
- solaris: add missing parameter to open_cloexec() cal
|
||||||
|
- osx: fix up audio format first, then apply it to device
|
||||||
|
* player_thread: discard empty chunks while cross-fading
|
||||||
|
* player_thread: fix assertion failure due to early seek
|
||||||
|
* output_thread: fix double lock
|
||||||
|
|
||||||
|
|
||||||
|
ver 0.16 (2010/12/11)
|
||||||
|
* protocol:
|
||||||
|
- send song modification time to client
|
||||||
|
- added "update" idle event
|
||||||
|
- removed the deprecated "volume" command
|
||||||
|
- added the "findadd" command
|
||||||
|
- range support for "delete"
|
||||||
|
- "previous" really plays the previous song
|
||||||
|
- "addid" with negative position is deprecated
|
||||||
|
- "load" supports remote playlists (extm3u, pls, asx, xspf, lastfm://)
|
||||||
|
- allow changing replay gain mode on-the-fly
|
||||||
|
- omitting the range end is possible
|
||||||
|
- "update" checks if the path is malformed
|
||||||
|
* archive:
|
||||||
|
- iso: renamed plugin to "iso9660"
|
||||||
|
- zip: renamed plugin to "zzip"
|
||||||
|
* input:
|
||||||
|
- lastfm: obsolete plugin removed
|
||||||
|
- ffmpeg: new input plugin using libavformat's "avio" library
|
||||||
|
* tags:
|
||||||
|
- added tags "ArtistSort", "AlbumArtistSort"
|
||||||
|
- id3: revised "performer" tag support
|
||||||
|
- id3: support multiple values
|
||||||
|
- ape: MusicBrainz tags
|
||||||
|
- ape: support multiple values
|
||||||
|
* decoders:
|
||||||
|
- don't try a plugin twice (MIME type & suffix)
|
||||||
|
- don't fall back to "mad" unless no plugin matches
|
||||||
|
- ffmpeg: support multiple tags
|
||||||
|
- ffmpeg: convert metadata to generic format
|
||||||
|
- ffmpeg: implement the libavutil log callback
|
||||||
|
- sndfile: new decoder plugin based on libsndfile
|
||||||
|
- flac: moved CUE sheet support to a playlist plugin
|
||||||
|
- flac: support streams without STREAMINFO block
|
||||||
|
- mikmod: sample rate is configurable
|
||||||
|
- mpg123: new decoder plugin based on libmpg123
|
||||||
|
- sidplay: support sub-tunes
|
||||||
|
- sidplay: implemented songlength database
|
||||||
|
- sidplay: support seeking
|
||||||
|
- sidplay: play monaural SID tunes in mono
|
||||||
|
- sidplay: play mus, str, prg, x00 files
|
||||||
|
- wavpack: activate 32 bit support
|
||||||
|
- wavpack: allow more than 2 channels
|
||||||
|
- mp4ff: rename plugin "mp4" to "mp4ff"
|
||||||
|
* encoders:
|
||||||
|
- twolame: new encoder plugin based on libtwolame
|
||||||
|
- flac: new encoder plugin based on libFLAC
|
||||||
|
- wave: new encoder plugin for PCM WAV format
|
||||||
|
* output:
|
||||||
|
- recorder: new output plugin for recording radio streams
|
||||||
|
- alsa: don't recover on CANCEL
|
||||||
|
- alsa: fill period buffer with silence before draining
|
||||||
|
- openal: new output plugin
|
||||||
|
- pulse: announce "media.role=music"
|
||||||
|
- pulse: renamed context to "Music Player Daemon"
|
||||||
|
- pulse: connect to server on MPD startup, implement pause
|
||||||
|
- jack: require libjack 0.100
|
||||||
|
- jack: don't disconnect during pause
|
||||||
|
- jack: connect to server on MPD startup
|
||||||
|
- jack: added options "client_name", "server_name"
|
||||||
|
- jack: clear ring buffers before activating
|
||||||
|
- jack: renamed option "ports" to "destination_ports"
|
||||||
|
- jack: support more than two audio channels
|
||||||
|
- httpd: bind port when output is enabled
|
||||||
|
- httpd: added name/genre/website configuration
|
||||||
|
- httpd: implement "pause"
|
||||||
|
- httpd: bind_to_address support (including IPv6)
|
||||||
|
- oss: 24 bit support via OSS4
|
||||||
|
- win32: new output plugin for Windows Wave
|
||||||
|
- shout, httpd: more responsive to control commands
|
||||||
|
- wildcards allowed in audio_format configuration
|
||||||
|
- consistently lock audio output objects
|
||||||
|
* player:
|
||||||
|
- drain audio outputs at the end of the playlist
|
||||||
|
* mixers:
|
||||||
|
- removed support for legacy mixer configuration
|
||||||
|
- reimplemented software volume as mixer+filter plugin
|
||||||
|
- per-device software/hardware mixer setting
|
||||||
|
* commands:
|
||||||
|
- added new "status" line with more precise "elapsed time"
|
||||||
|
* update:
|
||||||
|
- automatically update the database with Linux inotify
|
||||||
|
- support .mpdignore files in the music directory
|
||||||
|
- sort songs by album name first, then disc/track number
|
||||||
|
- rescan after metadata_to_use change
|
||||||
|
* normalize: upgraded to AudioCompress 2.0
|
||||||
|
- automatically convert to 16 bit samples
|
||||||
|
* replay gain:
|
||||||
|
- reimplemented as a filter plugin
|
||||||
|
- fall back to track gain if album gain is unavailable
|
||||||
|
- optionally use hardware mixer to apply replay gain
|
||||||
|
- added mode "auto"
|
||||||
|
- parse replay gain from APE tags
|
||||||
|
* log unused/unknown block parameters
|
||||||
|
* removed the deprecated "error_file" option
|
||||||
|
* save state when stopped
|
||||||
|
* renamed option "--stdout" to "--stderr"
|
||||||
|
* removed options --create-db and --no-create-db
|
||||||
|
* state_file: save only if something has changed
|
||||||
|
* database: eliminated maximum line length
|
||||||
|
* log: redirect stdout/stderr to /dev/null if syslog is used
|
||||||
|
* set the close-on-exec flag on all file descriptors
|
||||||
|
* pcm_volume, pcm_mix: implemented 32 bit support
|
||||||
|
* support packed 24 bit samples
|
||||||
|
* CUE sheet support
|
||||||
|
* support for MixRamp tags
|
||||||
|
* obey $(sysconfdir) for default mpd.conf location
|
||||||
|
* build with large file support by default
|
||||||
|
* added test suite ("make check")
|
||||||
|
* require GLib 2.12
|
||||||
|
* added libwrap support
|
||||||
|
* make single mode 'sticky'
|
||||||
|
|
||||||
|
|
||||||
|
ver 0.15.17 (2011/??/??)
|
||||||
|
* encoder:
|
||||||
|
- vorbis: reset the Ogg stream after flush
|
||||||
|
* decoders:
|
||||||
|
- vorbis: fix tremor support
|
||||||
|
|
||||||
|
|
||||||
|
ver 0.15.16 (2011/03/13)
|
||||||
|
* output:
|
||||||
|
- ao: initialize the ao_sample_format struct
|
||||||
|
- jack: fix crash with mono playback
|
||||||
|
* encoders:
|
||||||
|
- lame: explicitly configure the output sample rate
|
||||||
|
* update: log all file permission problems
|
||||||
|
|
||||||
|
|
||||||
|
ver 0.15.15 (2010/11/08)
|
||||||
|
* input:
|
||||||
|
- rewind: fix assertion failure
|
||||||
|
* output:
|
||||||
|
- shout: artist comes first in stream title
|
||||||
|
|
||||||
|
|
||||||
|
ver 0.15.14 (2010/11/06)
|
||||||
|
* player_thread: fix assertion failure due to wrong music pipe on seek
|
||||||
|
* output_thread: fix assertion failure due to race condition in OPEN
|
||||||
|
* input:
|
||||||
|
- rewind: fix double free bug
|
||||||
|
* decoders:
|
||||||
|
- mp4ff, ffmpeg: add extension ".m4b" (audio book)
|
||||||
|
|
||||||
|
|
||||||
ver 0.15.13 (2010/10/10)
|
ver 0.15.13 (2010/10/10)
|
||||||
* output_thread: fix race condition after CANCEL command
|
* output_thread: fix race condition after CANCEL command
|
||||||
* output:
|
* output:
|
||||||
|
|||||||
@@ -9,20 +9,20 @@ srcdir="`dirname $0`"
|
|||||||
test -z "$srcdir" && srcdir=.
|
test -z "$srcdir" && srcdir=.
|
||||||
cd "$srcdir"
|
cd "$srcdir"
|
||||||
DIE=
|
DIE=
|
||||||
AM_VERSIONGREP="sed -e s/.*[^0-9\.]\([0-9]\.[0-9]\).*/\1/"
|
AM_VERSIONGREP="sed -e s/.*[^0-9\.]\([0-9]\.[0-9][0-9]*\).*/\1/"
|
||||||
AC_VERSIONGREP="sed -e s/.*[^0-9\.]\([0-9]\.[0-9][0-9]\).*/\1/"
|
AC_VERSIONGREP="sed -e s/.*[^0-9\.]\([0-9]\.[0-9][0-9]\).*/\1/"
|
||||||
VERSIONMKINT="sed -e s/[^0-9]//"
|
VERSIONMKINT="sed -e s/[^0-9]//"
|
||||||
if test -n "$AM_FORCE_VERSION"
|
if test -n "$AM_FORCE_VERSION"
|
||||||
then
|
then
|
||||||
AM_VERSIONS="$AM_FORCE_VERSION"
|
AM_VERSIONS="$AM_FORCE_VERSION"
|
||||||
else
|
else
|
||||||
AM_VERSIONS='1.9 1.10'
|
AM_VERSIONS='1.11'
|
||||||
fi
|
fi
|
||||||
if test -n "$AC_FORCE_VERSION"
|
if test -n "$AC_FORCE_VERSION"
|
||||||
then
|
then
|
||||||
AC_VERSIONS="$AC_FORCE_VERSION"
|
AC_VERSIONS="$AC_FORCE_VERSION"
|
||||||
else
|
else
|
||||||
AC_VERSIONS='2.58 2.59 2.60 2.61'
|
AC_VERSIONS='2.60 2.61'
|
||||||
fi
|
fi
|
||||||
|
|
||||||
versioned_bins ()
|
versioned_bins ()
|
||||||
|
|||||||
2305
configure.ac
2305
configure.ac
File diff suppressed because it is too large
Load Diff
@@ -57,7 +57,7 @@
|
|||||||
Some example code:
|
Some example code:
|
||||||
</para>
|
</para>
|
||||||
|
|
||||||
<programlisting lang="C">static inline bool
|
<programlisting lang="C">static inline int
|
||||||
foo(const char *abc, int xyz)
|
foo(const char *abc, int xyz)
|
||||||
{
|
{
|
||||||
if (abc == NULL) {
|
if (abc == NULL) {
|
||||||
|
|||||||
@@ -31,7 +31,7 @@ PROJECT_NAME = MPD
|
|||||||
# This could be handy for archiving the generated documentation or
|
# This could be handy for archiving the generated documentation or
|
||||||
# if some version control system is used.
|
# if some version control system is used.
|
||||||
|
|
||||||
PROJECT_NUMBER =
|
PROJECT_NUMBER = @VERSION@
|
||||||
|
|
||||||
# The OUTPUT_DIRECTORY tag is used to specify the (relative or absolute)
|
# The OUTPUT_DIRECTORY tag is used to specify the (relative or absolute)
|
||||||
# base path where the generated documentation will be put.
|
# base path where the generated documentation will be put.
|
||||||
@@ -481,7 +481,7 @@ FILE_VERSION_FILTER =
|
|||||||
# The QUIET tag can be used to turn on/off the messages that are generated
|
# The QUIET tag can be used to turn on/off the messages that are generated
|
||||||
# by doxygen. Possible values are YES and NO. If left blank NO is used.
|
# by doxygen. Possible values are YES and NO. If left blank NO is used.
|
||||||
|
|
||||||
QUIET = NO
|
QUIET = YES
|
||||||
|
|
||||||
# The WARNINGS tag can be used to turn on/off the warning messages that are
|
# The WARNINGS tag can be used to turn on/off the warning messages that are
|
||||||
# generated by doxygen. Possible values are YES and NO. If left blank
|
# generated by doxygen. Possible values are YES and NO. If left blank
|
||||||
@@ -534,7 +534,7 @@ WARN_LOGFILE =
|
|||||||
# directories like "/usr/src/myproject". Separate the files or directories
|
# directories like "/usr/src/myproject". Separate the files or directories
|
||||||
# with spaces.
|
# with spaces.
|
||||||
|
|
||||||
INPUT = src
|
INPUT = @abs_top_srcdir@/src/
|
||||||
|
|
||||||
# This tag can be used to specify the character encoding of the source files
|
# This tag can be used to specify the character encoding of the source files
|
||||||
# that doxygen parses. Internally doxygen uses the UTF-8 encoding, which is
|
# that doxygen parses. Internally doxygen uses the UTF-8 encoding, which is
|
||||||
10
doc/mpd.1
10
doc/mpd.1
@@ -25,17 +25,11 @@ Output a brief help message.
|
|||||||
Kill the currently running mpd session. The pid_file parameter must be
|
Kill the currently running mpd session. The pid_file parameter must be
|
||||||
specified in the config file for this to work.
|
specified in the config file for this to work.
|
||||||
.TP
|
.TP
|
||||||
.BI --create-db
|
|
||||||
Force (re)creation of database.
|
|
||||||
.TP
|
|
||||||
.BI --no-create-db
|
|
||||||
Do not create database, even if it doesn't exist.
|
|
||||||
.TP
|
|
||||||
.BI --no-daemon
|
.BI --no-daemon
|
||||||
Don't detach from console.
|
Don't detach from console.
|
||||||
.TP
|
.TP
|
||||||
.BI --stdout
|
.BI --stderr
|
||||||
Print messages to stdout and stderr.
|
Print messages stderr.
|
||||||
.TP
|
.TP
|
||||||
.BI --verbose
|
.BI --verbose
|
||||||
Verbose logging.
|
Verbose logging.
|
||||||
|
|||||||
106
doc/mpd.conf.5
106
doc/mpd.conf.5
@@ -69,6 +69,9 @@ mpd will be saved to this file when mpd is terminated by a TERM signal or by
|
|||||||
the "kill" command. When mpd is restarted, it will read the state file and
|
the "kill" command. When mpd is restarted, it will read the state file and
|
||||||
restore the state of mpd (including the playlist).
|
restore the state of mpd (including the playlist).
|
||||||
.TP
|
.TP
|
||||||
|
.B restore_paused <yes or no>
|
||||||
|
Put MPD into pause mode instead of starting playback after startup.
|
||||||
|
.TP
|
||||||
.B user <username>
|
.B user <username>
|
||||||
This specifies the user that MPD will run as, if set. MPD should
|
This specifies the user that MPD will run as, if set. MPD should
|
||||||
never run as root, and you may use this option to make MPD change its
|
never run as root, and you may use this option to make MPD change its
|
||||||
@@ -80,6 +83,10 @@ This specifies which address mpd binds to and listens on. Multiple
|
|||||||
bind_to_address parameters may be specified. The default is "any", which binds
|
bind_to_address parameters may be specified. The default is "any", which binds
|
||||||
to all available addresses.
|
to all available addresses.
|
||||||
|
|
||||||
|
You can set a port that is different from the global port setting,
|
||||||
|
e.g. "localhost:6602". IPv6 addresses must be enclosed in square
|
||||||
|
brackets if you want to configure a port, e.g. "[::1]:6602".
|
||||||
|
|
||||||
To bind to a Unix domain socket, specify an absolute path. For a
|
To bind to a Unix domain socket, specify an absolute path. For a
|
||||||
system-wide MPD, we suggest the path "\fB/var/run/mpd/socket\fP".
|
system-wide MPD, we suggest the path "\fB/var/run/mpd/socket\fP".
|
||||||
.TP
|
.TP
|
||||||
@@ -129,6 +136,8 @@ audio that is sent to each audio output. Note that audio outputs may specify
|
|||||||
their own audio format which will be used for actual output to the audio
|
their own audio format which will be used for actual output to the audio
|
||||||
device. An example is "44100:16:2" for 44100Hz, 16 bits, and 2 channels. The
|
device. An example is "44100:16:2" for 44100Hz, 16 bits, and 2 channels. The
|
||||||
default is to use the audio format of the input file.
|
default is to use the audio format of the input file.
|
||||||
|
Any of the three attributes may be an asterisk to specify that this
|
||||||
|
attribute should not be enforced
|
||||||
.TP
|
.TP
|
||||||
.B samplerate_converter <integer or prefix>
|
.B samplerate_converter <integer or prefix>
|
||||||
This specifies the libsamplerate converter to use. The supplied value should
|
This specifies the libsamplerate converter to use. The supplied value should
|
||||||
@@ -168,31 +177,14 @@ only choice) if MPD was compiled without libsamplerate.
|
|||||||
For an up-to-date list of available converters, please see the libsamplerate
|
For an up-to-date list of available converters, please see the libsamplerate
|
||||||
documentation (available online at <\fBhttp://www.mega-nerd.com/SRC/\fP>).
|
documentation (available online at <\fBhttp://www.mega-nerd.com/SRC/\fP>).
|
||||||
.TP
|
.TP
|
||||||
.B mixer_type <alsa, oss, software, hardware or disabled>
|
.B replaygain <off or album or track or auto>
|
||||||
This specifies which mixer to use. The default is hardware and depends on
|
|
||||||
what audio output support mpd was built with. Options alsa and oss are
|
|
||||||
legacy and should not be used in new configs, but when set mixer_device
|
|
||||||
and mixer_control will apply.
|
|
||||||
.TP
|
|
||||||
.B mixer_device <mixer dev>
|
|
||||||
This specifies which mixer to use. The default for oss is
|
|
||||||
"/dev/mixer"; the default for alsa is "default". This global option is
|
|
||||||
deprecated and should not be used. Look at the mixer_device option of
|
|
||||||
corresponding output device instead.
|
|
||||||
.TP
|
|
||||||
.B mixer_control <mixer ctrl>
|
|
||||||
This specifies which mixer control to use (sometimes referred to as
|
|
||||||
the "device"). Examples of mixer controls are PCM, Line1, Master,
|
|
||||||
etc. An example for OSS is "Pcm", and an example for alsa is
|
|
||||||
"PCM". This global option is deprecated and should not be used. Look
|
|
||||||
at the mixer_control option of corresponding output device instead.
|
|
||||||
.TP
|
|
||||||
.B replaygain <album or track>
|
|
||||||
If specified, mpd will adjust the volume of songs played using ReplayGain tags
|
If specified, mpd will adjust the volume of songs played using ReplayGain tags
|
||||||
(see <\fBhttp://www.replaygain.org/\fP>). Setting this to "album" will adjust
|
(see <\fBhttp://www.replaygain.org/\fP>). Setting this to "album" will adjust
|
||||||
volume using the album's ReplayGain tags, while setting it to "track" will
|
volume using the album's ReplayGain tags, while setting it to "track" will
|
||||||
adjust it using the track ReplayGain tags. Currently only FLAC, Ogg Vorbis,
|
adjust it using the track ReplayGain tags. "auto" uses the track ReplayGain
|
||||||
Musepack, and MP3 (through ID3v2 ReplayGain tags, not APEv2) are supported.
|
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.
|
||||||
.TP
|
.TP
|
||||||
.B replaygain_preamp <-15 to 15>
|
.B replaygain_preamp <-15 to 15>
|
||||||
This is the gain (in dB) applied to songs with ReplayGain tags.
|
This is the gain (in dB) applied to songs with ReplayGain tags.
|
||||||
@@ -224,7 +216,7 @@ default is 5.
|
|||||||
.TP
|
.TP
|
||||||
.B max_playlist_length <number>
|
.B max_playlist_length <number>
|
||||||
This specifies the maximum number of songs that can be in the playlist. The
|
This specifies the maximum number of songs that can be in the playlist. The
|
||||||
default is 4096.
|
default is 16384.
|
||||||
.TP
|
.TP
|
||||||
.B max_command_list_size <size in KiB>
|
.B max_command_list_size <size in KiB>
|
||||||
This specifies the maximum size a command list can be. The default is 2048.
|
This specifies the maximum size a command list can be. The default is 2048.
|
||||||
@@ -260,11 +252,32 @@ when saving playlists. The default is "no".
|
|||||||
This specifies the tag types that will be scanned for and made available to
|
This specifies the tag types that will be scanned for and made available to
|
||||||
clients. Note that you must recreate (not update) your database for changes to
|
clients. Note that you must recreate (not update) your database for changes to
|
||||||
this parameter to take effect. Possible values are artist, album, title,
|
this parameter to take effect. Possible values are artist, album, title,
|
||||||
track, name, genre, date, composer, performer, comment, and disc. Multiple
|
track, name, genre, date, composer, performer, comment, disc,
|
||||||
tags may be specified as a comma separated list. An example value is
|
musicbrainz_artistid, musicbrainz_albumid, musicbrainz_albumartistid,
|
||||||
"artist,album,title,track". The special value "none" may be used alone to
|
musicbrainz_trackid. Multiple tags may be specified as a comma separated list.
|
||||||
disable all metadata. The default is to use all known tag types except for
|
An example value is "artist,album,title,track". The special value "none" may
|
||||||
comments.
|
be used alone to disable all metadata. The default is to use all known tag
|
||||||
|
types except for comments and those starting with "musicbrainz".
|
||||||
|
.TP
|
||||||
|
.B auto_update <yes or no>
|
||||||
|
This specifies the wheter to support automatic update of music database when
|
||||||
|
files are changed in music_directory. The default is to disable autoupdate
|
||||||
|
of database.
|
||||||
|
.TP
|
||||||
|
.B auto_update_depth <N>
|
||||||
|
Limit the depth of the directories being watched, 0 means only watch
|
||||||
|
the music directory itself. There is no limit by default.
|
||||||
|
.TP
|
||||||
|
.B despotify_user <name>
|
||||||
|
This specifies the user to use when logging in to Spotify using the despotify plugins.
|
||||||
|
.TP
|
||||||
|
.B despotify_password <name>
|
||||||
|
This specifies the password to use when logging in to Spotify using the despotify plugins.
|
||||||
|
.TP
|
||||||
|
.B despotify_high_bitrate <yes or no>
|
||||||
|
This specifies if the requested bitrate for Spotify should be high or not. Higher sounds
|
||||||
|
better but requires more processing and higher bandwidth. Default is yes.
|
||||||
|
.TP
|
||||||
.SH REQUIRED AUDIO OUTPUT PARAMETERS
|
.SH REQUIRED AUDIO OUTPUT PARAMETERS
|
||||||
.TP
|
.TP
|
||||||
.B type <type>
|
.B type <type>
|
||||||
@@ -280,11 +293,25 @@ This specifies the sample rate, bits per sample, and number of channels of
|
|||||||
audio that is sent to the audio output device. See documentation for the
|
audio that is sent to the audio output device. See documentation for the
|
||||||
\fBaudio_output_format\fP parameter for more details. The default is to use
|
\fBaudio_output_format\fP parameter for more details. The default is to use
|
||||||
whatever audio format is passed to the audio output.
|
whatever audio format is passed to the audio output.
|
||||||
|
Any of the three attributes may be an asterisk to specify that this
|
||||||
|
attribute should not be enforced
|
||||||
|
.TP
|
||||||
|
.B replay_gain_handler <software, mixer or none>
|
||||||
|
Specifies how 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.
|
||||||
.SH OPTIONAL ALSA OUTPUT PARAMETERS
|
.SH OPTIONAL ALSA OUTPUT PARAMETERS
|
||||||
.TP
|
.TP
|
||||||
.B device <dev>
|
.B device <dev>
|
||||||
This specifies the device to use for audio output. The default is "default".
|
This specifies the device to use for audio output. The default is "default".
|
||||||
.TP
|
.TP
|
||||||
|
.B mixer_type <hardware, software or none>
|
||||||
|
Specifies which mixer should be used for this audio output: the
|
||||||
|
hardware mixer (available for ALSA, OSS and PulseAudio), the software
|
||||||
|
mixer or no mixer ("none"). By default, the hardware mixer is used
|
||||||
|
for devices which support it, and none for the others.
|
||||||
|
.TP
|
||||||
.B mixer_device <mixer dev>
|
.B mixer_device <mixer dev>
|
||||||
This specifies which mixer to use. The default is "default". To use
|
This specifies which mixer to use. The default is "default". To use
|
||||||
the second sound card in a system, use "hw:1".
|
the second sound card in a system, use "hw:1".
|
||||||
@@ -352,13 +379,12 @@ after another until it successfully establishes a connection.
|
|||||||
.TP
|
.TP
|
||||||
.B sink <sink>
|
.B sink <sink>
|
||||||
The sink to output to. The default is to let PulseAudio choose a sink.
|
The sink to output to. The default is to let PulseAudio choose a sink.
|
||||||
.SH REQUIRED JACK OUTPUT PARAMETERS
|
.SH OPTIONAL JACK OUTPUT PARAMETERS
|
||||||
.TP
|
.TP
|
||||||
.B name <name>
|
.B client_name <name>
|
||||||
The client name to use when connecting to JACK. The output ports <name>:left
|
The client name to use when connecting to JACK. The output ports <name>:left
|
||||||
and <name>:right will also be created for the left and right channels,
|
and <name>:right will also be created for the left and right channels,
|
||||||
respectively.
|
respectively.
|
||||||
.SH OPTIONAL JACK OUTPUT PARAMETERS
|
|
||||||
.TP
|
.TP
|
||||||
.B ports <left_port,right_port>
|
.B ports <left_port,right_port>
|
||||||
This specifies the left and right ports to connect to for the left and right
|
This specifies the left and right ports to connect to for the left and right
|
||||||
@@ -457,8 +483,26 @@ connect to the icecast server. The default is 2 seconds.
|
|||||||
.B description <description>
|
.B description <description>
|
||||||
This specifies a description of the stream.
|
This specifies a description of the stream.
|
||||||
.TP
|
.TP
|
||||||
|
.B url <url>
|
||||||
|
This specifies a URL associated with the stream.
|
||||||
|
.TP
|
||||||
.B genre <genre>
|
.B genre <genre>
|
||||||
This specifies the genre(s) of the stream.
|
This specifies the genre(s) of the stream.
|
||||||
|
.SH OPTIONAL TCP KEEPALIVE PARAMETERS
|
||||||
|
.TP
|
||||||
|
.B tcp_keep_alive <yes or no>
|
||||||
|
Enable tcp keepalive on new client connections. (default is "no")
|
||||||
|
.TP
|
||||||
|
.B tcp_keep_alive_idle <seconds>
|
||||||
|
Time in seconds since the last communication on the connection and before
|
||||||
|
the keepalive probing is started. (default is 7200 seconds)
|
||||||
|
.TP
|
||||||
|
.B tcp_keep_alive_interval <seconds>
|
||||||
|
Interval in seconds between keepalive probes, once a probe started. (default is 75 seconds)
|
||||||
|
.TP
|
||||||
|
.B tcp_keep_alive_count <number of times>
|
||||||
|
Number of failed probes before the connection is pronounced dead and
|
||||||
|
the connection is closed. (default is 9 times)
|
||||||
.SH FILES
|
.SH FILES
|
||||||
.TP
|
.TP
|
||||||
.BI ~/.mpdconf
|
.BI ~/.mpdconf
|
||||||
|
|||||||
@@ -49,6 +49,11 @@
|
|||||||
#
|
#
|
||||||
#state_file "~/.mpd/state"
|
#state_file "~/.mpd/state"
|
||||||
#
|
#
|
||||||
|
# The location of the sticker database. This is a database which
|
||||||
|
# manages dynamic information attached to songs.
|
||||||
|
#
|
||||||
|
#sticker_file "~/.mpd/sticker.sql"
|
||||||
|
#
|
||||||
###############################################################################
|
###############################################################################
|
||||||
|
|
||||||
|
|
||||||
@@ -61,6 +66,13 @@
|
|||||||
#
|
#
|
||||||
#user "nobody"
|
#user "nobody"
|
||||||
#
|
#
|
||||||
|
# This setting specifies the group that MPD will run as. If not specified
|
||||||
|
# primary group of user specified with "user" setting will be used (if set).
|
||||||
|
# This is useful if MPD needs to be a member of group such as "audio" to
|
||||||
|
# have permission to use sound card.
|
||||||
|
#
|
||||||
|
#group "nogroup"
|
||||||
|
#
|
||||||
# This setting sets the address for the daemon to listen on. Careful attention
|
# This setting sets the address for the daemon to listen on. Careful attention
|
||||||
# should be paid if this is assigned to anything other then the default, any.
|
# should be paid if this is assigned to anything other then the default, any.
|
||||||
# This setting can deny access to control of the daemon.
|
# This setting can deny access to control of the daemon.
|
||||||
@@ -91,17 +103,31 @@
|
|||||||
#
|
#
|
||||||
#gapless_mp3_playback "yes"
|
#gapless_mp3_playback "yes"
|
||||||
#
|
#
|
||||||
|
# Setting "restore_paused" to "yes" puts MPD into pause mode instead
|
||||||
|
# of starting playback after startup.
|
||||||
|
#
|
||||||
|
#restore_paused "no"
|
||||||
|
#
|
||||||
# This setting enables MPD to create playlists in a format usable by other
|
# This setting enables MPD to create playlists in a format usable by other
|
||||||
# music players.
|
# music players.
|
||||||
#
|
#
|
||||||
#save_absolute_paths_in_playlists "no"
|
#save_absolute_paths_in_playlists "no"
|
||||||
#
|
#
|
||||||
# This setting defines a list of tag types that will be extracted during the
|
# This setting defines a list of tag types that will be extracted during the
|
||||||
# audio file discovery process. Optionally, 'comment' can be added to this
|
# audio file discovery process. The complete list of possible values can be
|
||||||
# list.
|
# found in the mpd.conf man page.
|
||||||
#
|
|
||||||
#metadata_to_use "artist,album,title,track,name,genre,date,composer,performer,disc"
|
#metadata_to_use "artist,album,title,track,name,genre,date,composer,performer,disc"
|
||||||
#
|
#
|
||||||
|
# This setting enables automatic update of MPD's database when files in
|
||||||
|
# music_directory are changed.
|
||||||
|
#
|
||||||
|
#auto_update "yes"
|
||||||
|
#
|
||||||
|
# Limit the depth of the directories being watched, 0 means only watch
|
||||||
|
# the music directory itself. There is no limit by default.
|
||||||
|
#
|
||||||
|
#auto_update_depth "3"
|
||||||
|
#
|
||||||
###############################################################################
|
###############################################################################
|
||||||
|
|
||||||
|
|
||||||
@@ -179,6 +205,7 @@ input {
|
|||||||
# name "My ALSA Device"
|
# name "My ALSA Device"
|
||||||
## device "hw:0,0" # optional
|
## device "hw:0,0" # optional
|
||||||
## format "44100:16:2" # optional
|
## format "44100:16:2" # optional
|
||||||
|
## mixer_type "hardware" # optional
|
||||||
## mixer_device "default" # optional
|
## mixer_device "default" # optional
|
||||||
## mixer_control "PCM" # optional
|
## mixer_control "PCM" # optional
|
||||||
## mixer_index "0" # optional
|
## mixer_index "0" # optional
|
||||||
@@ -191,6 +218,7 @@ input {
|
|||||||
# name "My OSS Device"
|
# name "My OSS Device"
|
||||||
## device "/dev/dsp" # optional
|
## device "/dev/dsp" # optional
|
||||||
## format "44100:16:2" # optional
|
## format "44100:16:2" # optional
|
||||||
|
## mixer_type "hardware" # optional
|
||||||
## mixer_device "/dev/mixer" # optional
|
## mixer_device "/dev/mixer" # optional
|
||||||
## mixer_control "PCM" # optional
|
## mixer_control "PCM" # optional
|
||||||
#}
|
#}
|
||||||
@@ -211,9 +239,23 @@ input {
|
|||||||
## protocol "icecast2" # optional
|
## protocol "icecast2" # optional
|
||||||
## user "source" # optional
|
## user "source" # optional
|
||||||
## description "My Stream Description" # optional
|
## description "My Stream Description" # optional
|
||||||
|
## url "http://example.com" # optional
|
||||||
## genre "jazz" # optional
|
## genre "jazz" # optional
|
||||||
## public "no" # optional
|
## public "no" # optional
|
||||||
## timeout "2" # optional
|
## timeout "2" # optional
|
||||||
|
## mixer_type "software" # optional
|
||||||
|
#}
|
||||||
|
#
|
||||||
|
# An example of a recorder output:
|
||||||
|
#
|
||||||
|
#audio_output {
|
||||||
|
# type "recorder"
|
||||||
|
# name "My recorder"
|
||||||
|
# encoder "vorbis" # optional, vorbis or lame
|
||||||
|
# path "/var/lib/mpd/recorder/mpd.ogg"
|
||||||
|
## quality "5.0" # do not define if bitrate is defined
|
||||||
|
# bitrate "128" # do not define if quality is defined
|
||||||
|
# format "44100:16:1"
|
||||||
#}
|
#}
|
||||||
#
|
#
|
||||||
# An example of a httpd output (built-in HTTP streaming server):
|
# An example of a httpd output (built-in HTTP streaming server):
|
||||||
@@ -223,9 +265,11 @@ input {
|
|||||||
# name "My HTTP Stream"
|
# name "My HTTP Stream"
|
||||||
# encoder "vorbis" # optional, vorbis or lame
|
# encoder "vorbis" # optional, vorbis or lame
|
||||||
# port "8000"
|
# port "8000"
|
||||||
|
# bind_to_address "0.0.0.0" # optional, IPv4 or IPv6
|
||||||
## quality "5.0" # do not define if bitrate is defined
|
## quality "5.0" # do not define if bitrate is defined
|
||||||
# bitrate "128" # do not define if quality is defined
|
# bitrate "128" # do not define if quality is defined
|
||||||
# format "44100:16:1"
|
# format "44100:16:1"
|
||||||
|
# max_clients "0" # optional 0=no limit
|
||||||
#}
|
#}
|
||||||
#
|
#
|
||||||
# An example of a pulseaudio output (streaming to a remote pulseaudio server)
|
# An example of a pulseaudio output (streaming to a remote pulseaudio server)
|
||||||
@@ -255,6 +299,7 @@ input {
|
|||||||
#audio_output {
|
#audio_output {
|
||||||
# type "null"
|
# type "null"
|
||||||
# name "My Null Output"
|
# name "My Null Output"
|
||||||
|
# mixer_type "none" # optional
|
||||||
#}
|
#}
|
||||||
#
|
#
|
||||||
# This setting will change all decoded audio to be converted to the specified
|
# This setting will change all decoded audio to be converted to the specified
|
||||||
@@ -273,38 +318,11 @@ input {
|
|||||||
###############################################################################
|
###############################################################################
|
||||||
|
|
||||||
|
|
||||||
# Volume control mixer ########################################################
|
|
||||||
#
|
|
||||||
# These are the global volume control settings. By default, this setting will
|
|
||||||
# be detected to the available audio output device, with preference going to
|
|
||||||
# hardware mixing. Hardware and software mixers for individual audio_output
|
|
||||||
# sections cannot yet be mixed.
|
|
||||||
#
|
|
||||||
# An example for controlling an ALSA, OSS or Pulseaudio mixer; If this
|
|
||||||
# setting is used other sound applications will be affected by the volume
|
|
||||||
# being controlled by MPD.
|
|
||||||
#
|
|
||||||
#mixer_type "hardware"
|
|
||||||
#
|
|
||||||
# An example for controlling all mixers through software. This will control
|
|
||||||
# all controls, even if the mixer is not supported by the device and will not
|
|
||||||
# affect any other sound producing applications.
|
|
||||||
#
|
|
||||||
#mixer_type "software"
|
|
||||||
#
|
|
||||||
# This example will not allow MPD to touch the mixer at all and will disable
|
|
||||||
# all volume controls.
|
|
||||||
#
|
|
||||||
#mixer_type "disabled"
|
|
||||||
#
|
|
||||||
###############################################################################
|
|
||||||
|
|
||||||
|
|
||||||
# Normalization automatic volume adjustments ##################################
|
# Normalization automatic volume adjustments ##################################
|
||||||
#
|
#
|
||||||
# This setting specifies the type of ReplayGain to use. This setting can have
|
# This setting specifies the type of ReplayGain to use. This setting can have
|
||||||
# the argument "album" or "track". See <http://www.replaygain.org> for more
|
# the argument "off", "album" or "track". See <http://www.replaygain.org>
|
||||||
# details. This setting is disabled by default.
|
# for more details. This setting is off by default.
|
||||||
#
|
#
|
||||||
#replaygain "album"
|
#replaygain "album"
|
||||||
#
|
#
|
||||||
@@ -353,12 +371,40 @@ input {
|
|||||||
#
|
#
|
||||||
###############################################################################
|
###############################################################################
|
||||||
|
|
||||||
|
# Client TCP keep alive #######################################################
|
||||||
|
#
|
||||||
|
# For clients connected by TCP on supported platforms.
|
||||||
|
# Allows detection of dangling connections due to clients disappearing from
|
||||||
|
# the network without closing their connections.
|
||||||
|
#
|
||||||
|
# This is not usually necessary but can be useful in cases such as wifi connectected
|
||||||
|
# clients that go in and out of network range or turn off wifi without closing their
|
||||||
|
# connections. Combined with low max_connections this can soon cause clients to not
|
||||||
|
# be able to connect.
|
||||||
|
#
|
||||||
|
#
|
||||||
|
# Enable tcp keepalive on new client connections (default is "no")
|
||||||
|
#
|
||||||
|
#tcp_keep_alive "no"
|
||||||
|
#
|
||||||
|
# Time in seconds since the last communication on the connection and before
|
||||||
|
# the keepalive probing is started. (default is 7200 seconds)
|
||||||
|
#tcp_keep_alive_idle "7200"
|
||||||
|
#
|
||||||
|
# Interval in seconds between keepalive probes, once a probe started.
|
||||||
|
# (default is 75 seconds)
|
||||||
|
#tcp_keep_alive_interval "75"
|
||||||
|
#
|
||||||
|
# Number of failed probes before the connection is pronounced dead and
|
||||||
|
# the connection is closed. (default is 9 times)
|
||||||
|
#tcp_keep_alive_count "9"
|
||||||
|
#
|
||||||
|
###############################################################################
|
||||||
|
|
||||||
# Character Encoding ##########################################################
|
# Character Encoding ##########################################################
|
||||||
#
|
#
|
||||||
# If file or directory names do not display correctly for your locale then you
|
# If file or directory names do not display correctly for your locale then you
|
||||||
# may need to modify this setting. After modification of this setting mpd
|
# may need to modify this setting.
|
||||||
# --create-db must be run to change the database.
|
|
||||||
#
|
#
|
||||||
#filesystem_charset "UTF-8"
|
#filesystem_charset "UTF-8"
|
||||||
#
|
#
|
||||||
@@ -367,3 +413,29 @@ input {
|
|||||||
#id3v1_encoding "ISO-8859-1"
|
#id3v1_encoding "ISO-8859-1"
|
||||||
#
|
#
|
||||||
###############################################################################
|
###############################################################################
|
||||||
|
|
||||||
|
|
||||||
|
# SIDPlay decoder #############################################################
|
||||||
|
#
|
||||||
|
# songlength_database:
|
||||||
|
# Location of your songlengths file, as distributed with the HVSC.
|
||||||
|
# The sidplay plugin checks this for matching MD5 fingerprints.
|
||||||
|
# See http://www.c64.org/HVSC/DOCUMENTS/Songlengths.faq
|
||||||
|
#
|
||||||
|
# default_songlength:
|
||||||
|
# This is the default playing time in seconds for songs not in the
|
||||||
|
# songlength database, or in case you're not using a database.
|
||||||
|
# A value of 0 means play indefinitely.
|
||||||
|
#
|
||||||
|
# filter:
|
||||||
|
# Turns the SID filter emulation on or off.
|
||||||
|
#
|
||||||
|
#decoder {
|
||||||
|
# plugin "sidplay"
|
||||||
|
# songlength_database "/media/C64Music/DOCUMENTS/Songlengths.txt"
|
||||||
|
# default_songlength "120"
|
||||||
|
# filter "true"
|
||||||
|
#}
|
||||||
|
#
|
||||||
|
###############################################################################
|
||||||
|
|
||||||
|
|||||||
690
doc/protocol.xml
690
doc/protocol.xml
@@ -8,22 +8,58 @@
|
|||||||
<title>General protocol syntax</title>
|
<title>General protocol syntax</title>
|
||||||
|
|
||||||
<section>
|
<section>
|
||||||
<title>Requests</title>
|
<title>Protocol overview</title>
|
||||||
|
|
||||||
<para>
|
<para>
|
||||||
If arguments contain spaces, they should be surrounded by double quotation
|
The MPD command protocol exchanges line-based text records
|
||||||
marks.
|
between client and server over TCP. Once the client is
|
||||||
|
connected to the server, they conduct a conversation until the
|
||||||
|
client closes the connection. The conversation flow is always
|
||||||
|
initiated by the client.
|
||||||
</para>
|
</para>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
The client transmits a command sequence, terminated by the
|
||||||
|
newline character <constant>\n</constant>. The server will
|
||||||
|
respond with one or more lines, the last of which will be a
|
||||||
|
completion code.
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
When the client connects to the server, the server will answer
|
||||||
|
with the following line:
|
||||||
|
|
||||||
|
<synopsis>OK MPD version</synopsis>
|
||||||
|
|
||||||
|
where <varname>version</varname> is a version identifier such as
|
||||||
|
0.12.2. This version identifier is the version of the protocol
|
||||||
|
spoken, not the real version of the daemon. (There is no way to
|
||||||
|
retrieve this real version identifier from the connection.)
|
||||||
|
</para>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section>
|
||||||
|
<title>Requests</title>
|
||||||
|
|
||||||
<cmdsynopsis>
|
<cmdsynopsis>
|
||||||
<command>COMMAND</command>
|
<command>COMMAND</command>
|
||||||
<arg rep="repeat"><replaceable>ARG</replaceable></arg>
|
<arg rep="repeat"><replaceable>ARG</replaceable></arg>
|
||||||
</cmdsynopsis>
|
</cmdsynopsis>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
If arguments contain spaces, they should be surrounded by double
|
||||||
|
quotation marks.
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
Argument strings are separated from the command and any other
|
||||||
|
arguments by linear white-space (' ' or '\t').
|
||||||
|
</para>
|
||||||
|
|
||||||
<para>
|
<para>
|
||||||
All data between the client and the server is encoded in
|
All data between the client and the server is encoded in
|
||||||
UTF-8. (Note: In UTF-8 all standard ansi characters, 0-127 are
|
UTF-8. (Note: In UTF-8 all standard ansi characters, 0-127 are
|
||||||
the same as a standard ansi encoding. Also, no ansi character
|
the same as in standard ansi encoding. Also, no ansi character
|
||||||
appears in any multi-byte characters. So, you can use
|
appears in any multi-byte characters. So, you can use
|
||||||
standard C functions like <function>strlen</function>, and
|
standard C functions like <function>strlen</function>, and
|
||||||
<function>strcpy</function> just fine with UTF-8 encoded
|
<function>strcpy</function> just fine with UTF-8 encoded
|
||||||
@@ -38,13 +74,97 @@
|
|||||||
<title>Responses</title>
|
<title>Responses</title>
|
||||||
|
|
||||||
<para>
|
<para>
|
||||||
A command returns <returnvalue>OK</returnvalue> on completion
|
A command returns <returnvalue>OK</returnvalue> on completion or
|
||||||
or <returnvalue>ACK some error</returnvalue> on failure.
|
<returnvalue>ACK some error</returnvalue> on failure. These
|
||||||
These denote the end of command execution.
|
denote the end of command execution.
|
||||||
</para>
|
</para>
|
||||||
</section>
|
|
||||||
|
|
||||||
<section>
|
<section>
|
||||||
|
<title>Failure responses</title>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
The nature of the error can be gleaned from the information
|
||||||
|
that follows the <returnvalue>ACK</returnvalue>.
|
||||||
|
<returnvalue>ACK</returnvalue> lines are of the form:
|
||||||
|
|
||||||
|
<synopsis>ACK [error@command_listNum] {current_command} message_text\n</synopsis>
|
||||||
|
|
||||||
|
These responses are generated by a call to
|
||||||
|
<function>commandError</function>. They contain four separate
|
||||||
|
terms. Let's look at each of them:
|
||||||
|
|
||||||
|
<itemizedlist>
|
||||||
|
<listitem>
|
||||||
|
<para>
|
||||||
|
<returnvalue>error</returnvalue>: numeric value of one
|
||||||
|
of the <constant>ACK_ERROR</constant> constants defined
|
||||||
|
in <filename>ack.h</filename>.
|
||||||
|
</para>
|
||||||
|
</listitem>
|
||||||
|
<listitem>
|
||||||
|
<para>
|
||||||
|
<returnvalue>command_listNum</returnvalue>:
|
||||||
|
offset of the command that caused the error in a <link
|
||||||
|
linkend="command_lists">Command List</link>.
|
||||||
|
An error will always cause a command list to terminate
|
||||||
|
at the command that causes the error.
|
||||||
|
</para>
|
||||||
|
</listitem>
|
||||||
|
<listitem>
|
||||||
|
<para>
|
||||||
|
<returnvalue>current_command</returnvalue>:
|
||||||
|
name of the command, in a <link
|
||||||
|
linkend="command_lists">Command List</link>,
|
||||||
|
that was executing when the error occurred.
|
||||||
|
</para>
|
||||||
|
</listitem>
|
||||||
|
<listitem>
|
||||||
|
<para>
|
||||||
|
<returnvalue>message_text</returnvalue>:
|
||||||
|
some (hopefully) informative text that describes the
|
||||||
|
nature of the error.
|
||||||
|
</para>
|
||||||
|
</listitem>
|
||||||
|
</itemizedlist>
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<example>
|
||||||
|
<title>foo</title>
|
||||||
|
<para>
|
||||||
|
An example might help. Consider the following sequence
|
||||||
|
sent from the client to the server.
|
||||||
|
|
||||||
|
<synopsis>
|
||||||
|
command_list_begin
|
||||||
|
volume 86
|
||||||
|
play 10240
|
||||||
|
status
|
||||||
|
command_list_end
|
||||||
|
</synopsis>
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
The server responds with:
|
||||||
|
|
||||||
|
<returnvalue>
|
||||||
|
ACK [50@1] {play} song doesn't exist: "10240"
|
||||||
|
</returnvalue>
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
This tells us that the play command, which was the
|
||||||
|
second in the list (the first or only command is
|
||||||
|
numbered 0), failed with error 50. The number 50
|
||||||
|
translates to <constant>ACK_ERROR_NO_EXIST</constant>--the
|
||||||
|
song doesn't exist. This is reiterated by the message text
|
||||||
|
which also tells us which song doesn't exist.
|
||||||
|
</para>
|
||||||
|
|
||||||
|
</example>
|
||||||
|
</section>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section id="command_lists">
|
||||||
<title>Command lists</title>
|
<title>Command lists</title>
|
||||||
|
|
||||||
<para>
|
<para>
|
||||||
@@ -67,6 +187,61 @@
|
|||||||
successful command executed in the command list.
|
successful command executed in the command list.
|
||||||
</para>
|
</para>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
|
<section>
|
||||||
|
<title>Ranges</title>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
Some commands (e.g. <link
|
||||||
|
linkend="command_delete"><command>delete</command></link>)
|
||||||
|
allow specifying a range in the form
|
||||||
|
<parameter>START:END</parameter> (the <varname>END</varname>
|
||||||
|
item is not included in the range, similar to ranges in the
|
||||||
|
Python programming language). If <varname>END</varname> is
|
||||||
|
omitted, then the maximum possible value is assumed.
|
||||||
|
</para>
|
||||||
|
</section>
|
||||||
|
</chapter>
|
||||||
|
|
||||||
|
<chapter>
|
||||||
|
<title>Recipes</title>
|
||||||
|
|
||||||
|
<section>
|
||||||
|
<title>Queuing</title>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
Often, users run MPD with "<link
|
||||||
|
linkend="command_random">random</link>" enabled, but want to
|
||||||
|
be able to insert songs "before" the rest of the playlist.
|
||||||
|
That is commonly called "queuing".
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
MPD implements this by allowing the client to specify a
|
||||||
|
"priority" for each song in the playlist (commands <link
|
||||||
|
linkend="command_prio"><command>prio</command></link> and
|
||||||
|
<link
|
||||||
|
linkend="command_prioid"><command>prioid</command></link>). A
|
||||||
|
higher priority means that the song is going to be played
|
||||||
|
before the other songs.
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
In "random" mode, MPD maintains an internal randomized
|
||||||
|
sequence of songs. In this sequence, songs with a higher
|
||||||
|
priority come first, and all songs with the same priority are
|
||||||
|
shuffled (by default, all songs are shuffled, because all have
|
||||||
|
the same priority "0"). When you increase the priority of a
|
||||||
|
song, it is moved to the front of the sequence according to
|
||||||
|
its new priority, but always after the current one. A song
|
||||||
|
that has been played already (it's "before" the current song
|
||||||
|
in that sequence) will only be scheduled for repeated playback
|
||||||
|
if its priority has become bigger than the priority of the
|
||||||
|
current song. Decreasing the priority of a song will moved it
|
||||||
|
farther to the end of the sequence. Changing the priority of
|
||||||
|
the current song has no effect on the sequence.
|
||||||
|
</para>
|
||||||
|
</section>
|
||||||
</chapter>
|
</chapter>
|
||||||
|
|
||||||
<chapter>
|
<chapter>
|
||||||
@@ -123,7 +298,7 @@
|
|||||||
</term>
|
</term>
|
||||||
<listitem>
|
<listitem>
|
||||||
<para>
|
<para>
|
||||||
<footnote id="since_0_14"><simpara>Since MPD 0.14</simpara></footnote>
|
<footnote id="since_0_14"><simpara>Introduced with MPD 0.14</simpara></footnote>
|
||||||
Waits until there is a noteworthy change in one or more
|
Waits until there is a noteworthy change in one or more
|
||||||
of MPD's subsystems. As soon as there is one, it lists
|
of MPD's subsystems. As soon as there is one, it lists
|
||||||
all changed systems in a line in the format
|
all changed systems in a line in the format
|
||||||
@@ -137,6 +312,15 @@
|
|||||||
has been modified after <command>update</command>.
|
has been modified after <command>update</command>.
|
||||||
</para>
|
</para>
|
||||||
</listitem>
|
</listitem>
|
||||||
|
<listitem>
|
||||||
|
<para>
|
||||||
|
<returnvalue>update</returnvalue>: a database update
|
||||||
|
has started or finished. If the database was
|
||||||
|
modified during the update, the
|
||||||
|
<returnvalue>database</returnvalue> event is also
|
||||||
|
emitted.
|
||||||
|
</para>
|
||||||
|
</listitem>
|
||||||
<listitem>
|
<listitem>
|
||||||
<para>
|
<para>
|
||||||
<returnvalue>stored_playlist</returnvalue>: a stored
|
<returnvalue>stored_playlist</returnvalue>: a stored
|
||||||
@@ -172,7 +356,26 @@
|
|||||||
<para>
|
<para>
|
||||||
<returnvalue>options</returnvalue>: options like
|
<returnvalue>options</returnvalue>: options like
|
||||||
<option>repeat</option>, <option>random</option>,
|
<option>repeat</option>, <option>random</option>,
|
||||||
<option>crossfade</option>
|
<option>crossfade</option>, replay gain
|
||||||
|
</para>
|
||||||
|
</listitem>
|
||||||
|
<listitem>
|
||||||
|
<para>
|
||||||
|
<returnvalue>sticker</returnvalue>: the sticker database
|
||||||
|
has been modified.
|
||||||
|
</para>
|
||||||
|
</listitem>
|
||||||
|
<listitem>
|
||||||
|
<para>
|
||||||
|
<returnvalue>subscription</returnvalue>: a client
|
||||||
|
has subscribed or unsubscribed to a channel
|
||||||
|
</para>
|
||||||
|
</listitem>
|
||||||
|
<listitem>
|
||||||
|
<para>
|
||||||
|
<returnvalue>message</returnvalue>: a message was
|
||||||
|
received on a channel this client is subscribed to;
|
||||||
|
this event is only emitted when the queue is empty
|
||||||
</para>
|
</para>
|
||||||
</listitem>
|
</listitem>
|
||||||
</itemizedlist>
|
</itemizedlist>
|
||||||
@@ -191,9 +394,6 @@
|
|||||||
MPD will only send notifications when something changed in
|
MPD will only send notifications when something changed in
|
||||||
one of the specified subsytems.
|
one of the specified subsytems.
|
||||||
</para>
|
</para>
|
||||||
<simpara>
|
|
||||||
Since <application>MPD</application> 0.14
|
|
||||||
</simpara>
|
|
||||||
</listitem>
|
</listitem>
|
||||||
</varlistentry>
|
</varlistentry>
|
||||||
<varlistentry id="command_status">
|
<varlistentry id="command_status">
|
||||||
@@ -222,16 +422,22 @@
|
|||||||
</listitem>
|
</listitem>
|
||||||
<listitem>
|
<listitem>
|
||||||
<para>
|
<para>
|
||||||
<varname>single</varname>:
|
<varname>random</varname>:
|
||||||
|
<returnvalue>0 or 1</returnvalue>
|
||||||
|
</para>
|
||||||
|
</listitem>
|
||||||
|
<listitem>
|
||||||
|
<para>
|
||||||
|
<varname>single</varname>:
|
||||||
|
<footnote id="since_0_15"><simpara>Introduced with MPD 0.15</simpara></footnote>
|
||||||
<returnvalue>0 or 1</returnvalue>
|
<returnvalue>0 or 1</returnvalue>
|
||||||
<footnote id="since_0_15"><simpara>Since MPD 0.15</simpara></footnote>
|
|
||||||
</para>
|
</para>
|
||||||
</listitem>
|
</listitem>
|
||||||
<listitem>
|
<listitem>
|
||||||
<para>
|
<para>
|
||||||
<varname>consume</varname>:
|
<varname>consume</varname>:
|
||||||
<returnvalue>0 or 1</returnvalue>
|
|
||||||
<footnoteref linkend="since_0_15"/>
|
<footnoteref linkend="since_0_15"/>
|
||||||
|
<returnvalue>0 or 1</returnvalue>
|
||||||
</para>
|
</para>
|
||||||
</listitem>
|
</listitem>
|
||||||
<listitem>
|
<listitem>
|
||||||
@@ -295,6 +501,16 @@
|
|||||||
playing/paused song)</returnvalue>
|
playing/paused song)</returnvalue>
|
||||||
</para>
|
</para>
|
||||||
</listitem>
|
</listitem>
|
||||||
|
<listitem>
|
||||||
|
<para>
|
||||||
|
<varname>elapsed</varname>:
|
||||||
|
<footnote id="since_0_16"><simpara>Introduced with MPD 0.16</simpara></footnote>
|
||||||
|
<returnvalue>
|
||||||
|
Total time elapsed within the current song, but
|
||||||
|
with higher resolution.
|
||||||
|
</returnvalue>
|
||||||
|
</para>
|
||||||
|
</listitem>
|
||||||
<listitem>
|
<listitem>
|
||||||
<para>
|
<para>
|
||||||
<varname>bitrate</varname>:
|
<varname>bitrate</varname>:
|
||||||
@@ -308,6 +524,18 @@
|
|||||||
<returnvalue>crossfade in seconds</returnvalue>
|
<returnvalue>crossfade in seconds</returnvalue>
|
||||||
</para>
|
</para>
|
||||||
</listitem>
|
</listitem>
|
||||||
|
<listitem>
|
||||||
|
<para>
|
||||||
|
<varname>mixrampdb</varname>:
|
||||||
|
<returnvalue>mixramp threshold in dB</returnvalue>
|
||||||
|
</para>
|
||||||
|
</listitem>
|
||||||
|
<listitem>
|
||||||
|
<para>
|
||||||
|
<varname>mixrampdelay</varname>:
|
||||||
|
<returnvalue>mixrampdelay in seconds</returnvalue>
|
||||||
|
</para>
|
||||||
|
</listitem>
|
||||||
<listitem>
|
<listitem>
|
||||||
<para>
|
<para>
|
||||||
<varname>audio</varname>:
|
<varname>audio</varname>:
|
||||||
@@ -316,7 +544,7 @@
|
|||||||
</listitem>
|
</listitem>
|
||||||
<listitem>
|
<listitem>
|
||||||
<para>
|
<para>
|
||||||
<varname>updatings_db</varname>:
|
<varname>updating_db</varname>:
|
||||||
<returnvalue>job id</returnvalue>
|
<returnvalue>job id</returnvalue>
|
||||||
</para>
|
</para>
|
||||||
</listitem>
|
</listitem>
|
||||||
@@ -412,6 +640,32 @@
|
|||||||
</para>
|
</para>
|
||||||
</listitem>
|
</listitem>
|
||||||
</varlistentry>
|
</varlistentry>
|
||||||
|
<varlistentry id="command_mixrampdb">
|
||||||
|
<term>
|
||||||
|
<cmdsynopsis>
|
||||||
|
<command>mixrampdb</command>
|
||||||
|
<arg choice="req"><replaceable>deciBels</replaceable></arg>
|
||||||
|
</cmdsynopsis>
|
||||||
|
</term>
|
||||||
|
<listitem>
|
||||||
|
<para>
|
||||||
|
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
|
||||||
|
</para>
|
||||||
|
</listitem>
|
||||||
|
</varlistentry>
|
||||||
|
<varlistentry id="command_mixrampdelay">
|
||||||
|
<term>
|
||||||
|
<cmdsynopsis>
|
||||||
|
<command>mixrampdelay</command>
|
||||||
|
<arg choice="req"><replaceable>SECONDS</replaceable></arg>
|
||||||
|
</cmdsynopsis>
|
||||||
|
</term>
|
||||||
|
<listitem>
|
||||||
|
<para>
|
||||||
|
Additional time subtracted from the overlap calculated by mixrampdb. A value of "nan" disables MixRamp overlapping and falls back to crossfading.
|
||||||
|
</para>
|
||||||
|
</listitem>
|
||||||
|
</varlistentry>
|
||||||
<varlistentry id="command_random">
|
<varlistentry id="command_random">
|
||||||
<term>
|
<term>
|
||||||
<cmdsynopsis>
|
<cmdsynopsis>
|
||||||
@@ -471,23 +725,47 @@
|
|||||||
</para>
|
</para>
|
||||||
</listitem>
|
</listitem>
|
||||||
</varlistentry>
|
</varlistentry>
|
||||||
<varlistentry id="command_volume">
|
<varlistentry id="command_replay_gain_mode">
|
||||||
<term>
|
<term>
|
||||||
<cmdsynopsis>
|
<cmdsynopsis>
|
||||||
<command>volume</command>
|
<command>replay_gain_mode</command>
|
||||||
<arg choice="req"><replaceable>CHANGE</replaceable></arg>
|
<arg choice="req"><replaceable>MODE</replaceable></arg>
|
||||||
</cmdsynopsis>
|
</cmdsynopsis>
|
||||||
</term>
|
</term>
|
||||||
<listitem>
|
<listitem>
|
||||||
<para>
|
<para>
|
||||||
Changes volume by amount <varname>CHANGE</varname>.
|
Sets the replay gain mode. One of
|
||||||
|
<parameter>off</parameter>,
|
||||||
|
<parameter>track</parameter>,
|
||||||
|
<parameter>album</parameter>,
|
||||||
|
<parameter>auto</parameter><footnote
|
||||||
|
id="replay_gain_auto_since_0_16">
|
||||||
|
<simpara>added in MPD 0.16</simpara>
|
||||||
|
</footnote>.
|
||||||
</para>
|
</para>
|
||||||
<note>
|
|
||||||
<para>
|
<para>
|
||||||
<command>volume</command> is deprecated, use
|
Changing the mode during playback may take several
|
||||||
<command>setvol</command> instead.
|
seconds, because the new settings does not affect the
|
||||||
|
buffered data.
|
||||||
|
</para>
|
||||||
|
<para>
|
||||||
|
This command triggers the
|
||||||
|
<returnvalue>options</returnvalue> idle event.
|
||||||
|
</para>
|
||||||
|
</listitem>
|
||||||
|
</varlistentry>
|
||||||
|
<varlistentry id="command_replay_gain_status">
|
||||||
|
<term>
|
||||||
|
<cmdsynopsis>
|
||||||
|
<command>replay_gain_status</command>
|
||||||
|
</cmdsynopsis>
|
||||||
|
</term>
|
||||||
|
<listitem>
|
||||||
|
<para>
|
||||||
|
Prints replay gain options. Currently, only the
|
||||||
|
variable <varname>replay_gain_mode</varname> is
|
||||||
|
returned.
|
||||||
</para>
|
</para>
|
||||||
</note>
|
|
||||||
</listitem>
|
</listitem>
|
||||||
</varlistentry>
|
</varlistentry>
|
||||||
</variablelist>
|
</variablelist>
|
||||||
@@ -600,6 +878,23 @@
|
|||||||
</para>
|
</para>
|
||||||
</listitem>
|
</listitem>
|
||||||
</varlistentry>
|
</varlistentry>
|
||||||
|
|
||||||
|
<varlistentry id="command_seekcur">
|
||||||
|
<term>
|
||||||
|
<cmdsynopsis>
|
||||||
|
<command>seekcur</command>
|
||||||
|
<arg choice="req"><replaceable>TIME</replaceable></arg>
|
||||||
|
</cmdsynopsis>
|
||||||
|
</term>
|
||||||
|
<listitem>
|
||||||
|
<para>
|
||||||
|
Seeks to the position <varname>TIME</varname> within the
|
||||||
|
current song. If prefixed by '+' or '-', then the time
|
||||||
|
is relative to the current playing position.
|
||||||
|
</para>
|
||||||
|
</listitem>
|
||||||
|
</varlistentry>
|
||||||
|
|
||||||
<varlistentry id="command_stop">
|
<varlistentry id="command_stop">
|
||||||
<term>
|
<term>
|
||||||
<cmdsynopsis>
|
<cmdsynopsis>
|
||||||
@@ -648,10 +943,7 @@
|
|||||||
</para>
|
</para>
|
||||||
<para>
|
<para>
|
||||||
<varname>URI</varname> is always a single file or
|
<varname>URI</varname> is always a single file or
|
||||||
URL. <varname>POSITION</varname> is optional, a
|
URL. For example:
|
||||||
negative number means it is relative to the currently
|
|
||||||
playing song in the playlist (if there is one).
|
|
||||||
For example:
|
|
||||||
</para>
|
</para>
|
||||||
<screen>
|
<screen>
|
||||||
addid "foo.mp3"
|
addid "foo.mp3"
|
||||||
@@ -676,7 +968,10 @@ OK
|
|||||||
<term>
|
<term>
|
||||||
<cmdsynopsis>
|
<cmdsynopsis>
|
||||||
<command>delete</command>
|
<command>delete</command>
|
||||||
<arg choice="req"><replaceable>SONGPOS</replaceable></arg>
|
<group>
|
||||||
|
<arg choice="req"><replaceable>POS</replaceable></arg>
|
||||||
|
<arg choice="req"><replaceable>START:END</replaceable></arg>
|
||||||
|
</group>
|
||||||
</cmdsynopsis>
|
</cmdsynopsis>
|
||||||
</term>
|
</term>
|
||||||
<listitem>
|
<listitem>
|
||||||
@@ -859,6 +1154,46 @@ OK
|
|||||||
</para>
|
</para>
|
||||||
</listitem>
|
</listitem>
|
||||||
</varlistentry>
|
</varlistentry>
|
||||||
|
|
||||||
|
<varlistentry id="command_prio">
|
||||||
|
<term>
|
||||||
|
<cmdsynopsis>
|
||||||
|
<command>prio</command>
|
||||||
|
<arg choice="req"><replaceable>PRIORITY</replaceable></arg>
|
||||||
|
<arg choice="req" rep="repeat"><replaceable>START:END</replaceable></arg>
|
||||||
|
</cmdsynopsis>
|
||||||
|
</term>
|
||||||
|
<listitem>
|
||||||
|
<para>
|
||||||
|
Set the priority of the specified songs. A higher
|
||||||
|
priority means that it will be played first when
|
||||||
|
"random" mode is enabled.
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
A priority is an integer between 0 and 255. The default
|
||||||
|
priority of new songs is 0.
|
||||||
|
</para>
|
||||||
|
</listitem>
|
||||||
|
</varlistentry>
|
||||||
|
|
||||||
|
<varlistentry id="command_prioid">
|
||||||
|
<term>
|
||||||
|
<cmdsynopsis>
|
||||||
|
<command>prioid</command>
|
||||||
|
<arg choice="req"><replaceable>PRIORITY</replaceable></arg>
|
||||||
|
<arg choice="req" rep="repeat"><replaceable>ID</replaceable></arg>
|
||||||
|
</cmdsynopsis>
|
||||||
|
</term>
|
||||||
|
<listitem>
|
||||||
|
<para>
|
||||||
|
Same as <link
|
||||||
|
linkend="command_prio"><command>prio</command></link>,
|
||||||
|
but address the songs with their id.
|
||||||
|
</para>
|
||||||
|
</listitem>
|
||||||
|
</varlistentry>
|
||||||
|
|
||||||
<varlistentry id="command_shuffle">
|
<varlistentry id="command_shuffle">
|
||||||
<term>
|
<term>
|
||||||
<cmdsynopsis>
|
<cmdsynopsis>
|
||||||
@@ -910,6 +1245,20 @@ OK
|
|||||||
<section>
|
<section>
|
||||||
<title>Stored playlists</title>
|
<title>Stored playlists</title>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
Playlists are stored inside the configured playlist directory.
|
||||||
|
They are addressed with their file name (without the directory
|
||||||
|
and without the <filename>.m3u</filename> suffix).
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
Some of the commands described in this section can be used to
|
||||||
|
run playlist plugins instead of the hard-coded simple
|
||||||
|
<filename>m3u</filename> parser. They can access playlists in
|
||||||
|
the music directory (relative path including the suffix) or
|
||||||
|
remote playlists (absolute URI with a supported scheme).
|
||||||
|
</para>
|
||||||
|
|
||||||
<variablelist>
|
<variablelist>
|
||||||
<varlistentry id="command_listplaylist">
|
<varlistentry id="command_listplaylist">
|
||||||
<term>
|
<term>
|
||||||
@@ -920,8 +1269,8 @@ OK
|
|||||||
</term>
|
</term>
|
||||||
<listitem>
|
<listitem>
|
||||||
<para>
|
<para>
|
||||||
Lists the files in the playlist
|
Lists the songs in the playlist. Playlist plugins are
|
||||||
<filename>NAME.m3u</filename>.
|
supported.
|
||||||
</para>
|
</para>
|
||||||
</listitem>
|
</listitem>
|
||||||
</varlistentry>
|
</varlistentry>
|
||||||
@@ -934,7 +1283,8 @@ OK
|
|||||||
</term>
|
</term>
|
||||||
<listitem>
|
<listitem>
|
||||||
<para>
|
<para>
|
||||||
Lists songs in the playlist <filename>NAME.m3u</filename>.
|
Lists the songs with metadata in the playlist. Playlist
|
||||||
|
plugins are supported.
|
||||||
</para>
|
</para>
|
||||||
</listitem>
|
</listitem>
|
||||||
</varlistentry>
|
</varlistentry>
|
||||||
@@ -962,12 +1312,14 @@ OK
|
|||||||
<cmdsynopsis>
|
<cmdsynopsis>
|
||||||
<command>load</command>
|
<command>load</command>
|
||||||
<arg choice="req"><replaceable>NAME</replaceable></arg>
|
<arg choice="req"><replaceable>NAME</replaceable></arg>
|
||||||
|
<arg choice="opt"><replaceable>START:END</replaceable></arg>
|
||||||
</cmdsynopsis>
|
</cmdsynopsis>
|
||||||
</term>
|
</term>
|
||||||
<listitem>
|
<listitem>
|
||||||
<para>
|
<para>
|
||||||
Loads the playlist <filename>NAME.m3u</filename> from
|
Loads the playlist into the current queue. Playlist
|
||||||
the playlist directory.
|
plugins are supported. A range may be specified to load
|
||||||
|
only a part of the playlist.
|
||||||
</para>
|
</para>
|
||||||
</listitem>
|
</listitem>
|
||||||
</varlistentry>
|
</varlistentry>
|
||||||
@@ -1105,16 +1457,35 @@ OK
|
|||||||
<command>find</command>
|
<command>find</command>
|
||||||
<arg choice="req"><replaceable>TYPE</replaceable></arg>
|
<arg choice="req"><replaceable>TYPE</replaceable></arg>
|
||||||
<arg choice="req"><replaceable>WHAT</replaceable></arg>
|
<arg choice="req"><replaceable>WHAT</replaceable></arg>
|
||||||
|
<arg choice="opt"><replaceable>...</replaceable></arg>
|
||||||
</cmdsynopsis>
|
</cmdsynopsis>
|
||||||
</term>
|
</term>
|
||||||
<listitem>
|
<listitem>
|
||||||
<para>
|
<para>
|
||||||
Finds songs in the db that are exactly
|
Finds songs in the db that are exactly
|
||||||
<varname>WHAT</varname>. <varname>TYPE</varname> should
|
<varname>WHAT</varname>. <varname>TYPE</varname> can
|
||||||
be <parameter>album</parameter>,
|
be any tag supported by MPD, or one of the two special
|
||||||
<parameter>artist</parameter>, or
|
parameters — <parameter>file</parameter> to search by
|
||||||
<parameter>title</parameter>. <varname>WHAT</varname>
|
full path (relative to database root), and
|
||||||
is what to find.
|
<parameter>any</parameter> to match against all
|
||||||
|
available tags. <varname>WHAT</varname> is what to find.
|
||||||
|
</para>
|
||||||
|
</listitem>
|
||||||
|
</varlistentry>
|
||||||
|
<varlistentry id="command_findadd">
|
||||||
|
<term>
|
||||||
|
<cmdsynopsis>
|
||||||
|
<command>findadd</command>
|
||||||
|
<arg choice="req"><replaceable>TYPE</replaceable></arg>
|
||||||
|
<arg choice="req"><replaceable>WHAT</replaceable></arg>
|
||||||
|
<arg choice="opt"><replaceable>...</replaceable></arg>
|
||||||
|
</cmdsynopsis>
|
||||||
|
</term>
|
||||||
|
<listitem>
|
||||||
|
<para>
|
||||||
|
Finds songs in the db that are exactly
|
||||||
|
<varname>WHAT</varname> and adds them to current playlist.
|
||||||
|
Parameters have the same meaning as for <command>find</command>.
|
||||||
</para>
|
</para>
|
||||||
</listitem>
|
</listitem>
|
||||||
</varlistentry>
|
</varlistentry>
|
||||||
@@ -1129,7 +1500,8 @@ OK
|
|||||||
<listitem>
|
<listitem>
|
||||||
<para>
|
<para>
|
||||||
Lists all tags of the specified type.
|
Lists all tags of the specified type.
|
||||||
<varname>TYPE</varname> should be album or artist.
|
<varname>TYPE</varname> can be any tag supported by MPD or
|
||||||
|
<parameter>file</parameter>.
|
||||||
</para>
|
</para>
|
||||||
<para>
|
<para>
|
||||||
<varname>ARTIST</varname> is an optional parameter when
|
<varname>ARTIST</varname> is an optional parameter when
|
||||||
@@ -1184,6 +1556,11 @@ OK
|
|||||||
the list of stored playlists. This behavior is
|
the list of stored playlists. This behavior is
|
||||||
deprecated; use "listplaylists" instead.
|
deprecated; use "listplaylists" instead.
|
||||||
</para>
|
</para>
|
||||||
|
<para>
|
||||||
|
Clients that are connected via UNIX domain socket may
|
||||||
|
use this command to read the tags of an arbitrary local
|
||||||
|
file (URI beginning with "file:///").
|
||||||
|
</para>
|
||||||
</listitem>
|
</listitem>
|
||||||
</varlistentry>
|
</varlistentry>
|
||||||
<varlistentry id="command_search">
|
<varlistentry id="command_search">
|
||||||
@@ -1192,17 +1569,60 @@ OK
|
|||||||
<command>search</command>
|
<command>search</command>
|
||||||
<arg choice="req"><replaceable>TYPE</replaceable></arg>
|
<arg choice="req"><replaceable>TYPE</replaceable></arg>
|
||||||
<arg choice="req"><replaceable>WHAT</replaceable></arg>
|
<arg choice="req"><replaceable>WHAT</replaceable></arg>
|
||||||
|
<arg choice="opt"><replaceable>...</replaceable></arg>
|
||||||
</cmdsynopsis>
|
</cmdsynopsis>
|
||||||
</term>
|
</term>
|
||||||
<listitem>
|
<listitem>
|
||||||
<para>
|
<para>
|
||||||
Searches for any song that contains
|
Searches for any song that contains
|
||||||
<varname>WHAT</varname>. <varname>TYPE</varname> can be
|
<varname>WHAT</varname>. Parameters have the same meaning
|
||||||
<parameter>title</parameter>,
|
as for <command>find</command>, except that search is not
|
||||||
<parameter>artist</parameter>,
|
case sensitive.
|
||||||
<parameter>album</parameter> or
|
</para>
|
||||||
<parameter>filename</parameter>. Search is not case
|
</listitem>
|
||||||
sensitive.
|
</varlistentry>
|
||||||
|
<varlistentry id="command_searchadd">
|
||||||
|
<term>
|
||||||
|
<cmdsynopsis>
|
||||||
|
<command>searchadd</command>
|
||||||
|
<arg choice="req"><replaceable>TYPE</replaceable></arg>
|
||||||
|
<arg choice="req"><replaceable>WHAT</replaceable></arg>
|
||||||
|
<arg choice="opt"><replaceable>...</replaceable></arg>
|
||||||
|
</cmdsynopsis>
|
||||||
|
</term>
|
||||||
|
<listitem>
|
||||||
|
<para>
|
||||||
|
Searches for any song that contains <varname>WHAT</varname>
|
||||||
|
in tag <varname>TYPE</varname> and adds them to current playlist.
|
||||||
|
</para>
|
||||||
|
<para>
|
||||||
|
Parameters have the same meaning as for <command>find</command>,
|
||||||
|
except that search is not case sensitive.
|
||||||
|
</para>
|
||||||
|
</listitem>
|
||||||
|
</varlistentry>
|
||||||
|
<varlistentry id="command_searchaddpl">
|
||||||
|
<term>
|
||||||
|
<cmdsynopsis>
|
||||||
|
<command>searchaddpl</command>
|
||||||
|
<arg choice="req"><replaceable>NAME</replaceable></arg>
|
||||||
|
<arg choice="req"><replaceable>TYPE</replaceable></arg>
|
||||||
|
<arg choice="req"><replaceable>WHAT</replaceable></arg>
|
||||||
|
<arg choice="opt"><replaceable>...</replaceable></arg>
|
||||||
|
</cmdsynopsis>
|
||||||
|
</term>
|
||||||
|
<listitem>
|
||||||
|
<para>
|
||||||
|
Searches for any song that contains <varname>WHAT</varname>
|
||||||
|
in tag <varname>TYPE</varname> and adds them to the playlist
|
||||||
|
named <varname>NAME</varname>.
|
||||||
|
</para>
|
||||||
|
<para>
|
||||||
|
If a playlist by that name doesn't exist it is created.
|
||||||
|
</para>
|
||||||
|
<para>
|
||||||
|
Parameters have the same meaning as for <command>find</command>,
|
||||||
|
except that search is not case sensitive.
|
||||||
</para>
|
</para>
|
||||||
</listitem>
|
</listitem>
|
||||||
</varlistentry>
|
</varlistentry>
|
||||||
@@ -1231,6 +1651,20 @@ OK
|
|||||||
</para>
|
</para>
|
||||||
</listitem>
|
</listitem>
|
||||||
</varlistentry>
|
</varlistentry>
|
||||||
|
<varlistentry id="command_rescan">
|
||||||
|
<term>
|
||||||
|
<cmdsynopsis>
|
||||||
|
<command>rescan</command>
|
||||||
|
<arg choice="opt"><replaceable>URI</replaceable></arg>
|
||||||
|
</cmdsynopsis>
|
||||||
|
</term>
|
||||||
|
<listitem>
|
||||||
|
<para>
|
||||||
|
Same as <command>update</command>, but also rescans
|
||||||
|
unmodified files.
|
||||||
|
</para>
|
||||||
|
</listitem>
|
||||||
|
</varlistentry>
|
||||||
</variablelist>
|
</variablelist>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
@@ -1420,6 +1854,7 @@ OK
|
|||||||
<term>
|
<term>
|
||||||
<cmdsynopsis>
|
<cmdsynopsis>
|
||||||
<command>disableoutput</command>
|
<command>disableoutput</command>
|
||||||
|
<arg choice="req"><replaceable>ID</replaceable></arg>
|
||||||
</cmdsynopsis>
|
</cmdsynopsis>
|
||||||
</term>
|
</term>
|
||||||
<listitem>
|
<listitem>
|
||||||
@@ -1432,6 +1867,7 @@ OK
|
|||||||
<term>
|
<term>
|
||||||
<cmdsynopsis>
|
<cmdsynopsis>
|
||||||
<command>enableoutput</command>
|
<command>enableoutput</command>
|
||||||
|
<arg choice="req"><replaceable>ID</replaceable></arg>
|
||||||
</cmdsynopsis>
|
</cmdsynopsis>
|
||||||
</term>
|
</term>
|
||||||
<listitem>
|
<listitem>
|
||||||
@@ -1459,6 +1895,47 @@ OK
|
|||||||
<title>Reflection</title>
|
<title>Reflection</title>
|
||||||
|
|
||||||
<variablelist>
|
<variablelist>
|
||||||
|
<varlistentry id="command_config">
|
||||||
|
<term>
|
||||||
|
<cmdsynopsis>
|
||||||
|
<command>config</command>
|
||||||
|
</cmdsynopsis>
|
||||||
|
</term>
|
||||||
|
<listitem>
|
||||||
|
<para>
|
||||||
|
Dumps configuration values that may be interesting for
|
||||||
|
the client. This command is only permitted to "local"
|
||||||
|
clients (connected via UNIX domain socket).
|
||||||
|
</para>
|
||||||
|
<para>
|
||||||
|
The following response attributes are available:
|
||||||
|
</para>
|
||||||
|
<informaltable>
|
||||||
|
<tgroup cols="2">
|
||||||
|
<thead>
|
||||||
|
<row>
|
||||||
|
<entry>
|
||||||
|
Name
|
||||||
|
</entry>
|
||||||
|
<entry>
|
||||||
|
Description
|
||||||
|
</entry>
|
||||||
|
</row>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<row>
|
||||||
|
<entry>
|
||||||
|
<varname>music_directory</varname>
|
||||||
|
</entry>
|
||||||
|
<entry>
|
||||||
|
The absolute path of the music directory.
|
||||||
|
</entry>
|
||||||
|
</row>
|
||||||
|
</tbody>
|
||||||
|
</tgroup>
|
||||||
|
</informaltable>
|
||||||
|
</listitem>
|
||||||
|
</varlistentry>
|
||||||
<varlistentry id="command_commands">
|
<varlistentry id="command_commands">
|
||||||
<term>
|
<term>
|
||||||
<cmdsynopsis>
|
<cmdsynopsis>
|
||||||
@@ -1508,6 +1985,125 @@ OK
|
|||||||
</para>
|
</para>
|
||||||
</listitem>
|
</listitem>
|
||||||
</varlistentry>
|
</varlistentry>
|
||||||
|
<varlistentry id="command_decoders">
|
||||||
|
<term>
|
||||||
|
<cmdsynopsis>
|
||||||
|
<command>decoders</command>
|
||||||
|
</cmdsynopsis>
|
||||||
|
</term>
|
||||||
|
<listitem>
|
||||||
|
<para>
|
||||||
|
Print a list of decoder plugins, followed by their
|
||||||
|
supported suffixes and MIME types. Example response:
|
||||||
|
</para>
|
||||||
|
<programlisting>plugin: mad
|
||||||
|
suffix: mp3
|
||||||
|
suffix: mp2
|
||||||
|
mime_type: audio/mpeg
|
||||||
|
plugin: mpcdec
|
||||||
|
suffix: mpc</programlisting>
|
||||||
|
</listitem>
|
||||||
|
</varlistentry>
|
||||||
|
</variablelist>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section>
|
||||||
|
<title>Client to client</title>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
Clients can communicate with each others over "channels". A
|
||||||
|
channel is created by a client subscribing to it. More than
|
||||||
|
one client can be subscribed to a channel at a time; all of
|
||||||
|
them will receive the messages which get sent to it.
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
Each time a client subscribes or unsubscribes, the global idle
|
||||||
|
event <varname>subscription</varname> is generated. In
|
||||||
|
conjunction with the <command>channels</command> command, this
|
||||||
|
may be used to auto-detect clients providing additional
|
||||||
|
services.
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
New messages are indicated by the <varname>message</varname>
|
||||||
|
idle event.
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<variablelist>
|
||||||
|
<varlistentry id="command_subscribe">
|
||||||
|
<term>
|
||||||
|
<cmdsynopsis>
|
||||||
|
<command>subscribe</command>
|
||||||
|
<arg choice="req"><replaceable>NAME</replaceable></arg>
|
||||||
|
</cmdsynopsis>
|
||||||
|
</term>
|
||||||
|
<listitem>
|
||||||
|
<para>
|
||||||
|
Subscribe to a channel. The channel is created if it
|
||||||
|
does not exist already. The name may consist of
|
||||||
|
alphanumeric ASCII characters plus underscore, dash, dot
|
||||||
|
and colon.
|
||||||
|
</para>
|
||||||
|
</listitem>
|
||||||
|
</varlistentry>
|
||||||
|
|
||||||
|
<varlistentry id="command_unsubscribe">
|
||||||
|
<term>
|
||||||
|
<cmdsynopsis>
|
||||||
|
<command>unsubscribe</command>
|
||||||
|
<arg choice="req"><replaceable>NAME</replaceable></arg>
|
||||||
|
</cmdsynopsis>
|
||||||
|
</term>
|
||||||
|
<listitem>
|
||||||
|
<para>
|
||||||
|
Unsubscribe from a channel.
|
||||||
|
</para>
|
||||||
|
</listitem>
|
||||||
|
</varlistentry>
|
||||||
|
|
||||||
|
<varlistentry id="command_channels">
|
||||||
|
<term>
|
||||||
|
<cmdsynopsis>
|
||||||
|
<command>channels</command>
|
||||||
|
</cmdsynopsis>
|
||||||
|
</term>
|
||||||
|
<listitem>
|
||||||
|
<para>
|
||||||
|
Obtain a list of all channels. The response is a list
|
||||||
|
of "channel:" lines.
|
||||||
|
</para>
|
||||||
|
</listitem>
|
||||||
|
</varlistentry>
|
||||||
|
|
||||||
|
<varlistentry id="command_readmessages">
|
||||||
|
<term>
|
||||||
|
<cmdsynopsis>
|
||||||
|
<command>readmessages</command>
|
||||||
|
</cmdsynopsis>
|
||||||
|
</term>
|
||||||
|
<listitem>
|
||||||
|
<para>
|
||||||
|
Reads messages for this client. The response is a list
|
||||||
|
of "channel:" and "message:" lines.
|
||||||
|
</para>
|
||||||
|
</listitem>
|
||||||
|
</varlistentry>
|
||||||
|
|
||||||
|
<varlistentry id="command_sendmessage">
|
||||||
|
<term>
|
||||||
|
<cmdsynopsis>
|
||||||
|
<command>sendmessage</command>
|
||||||
|
<arg choice="req"><replaceable>CHANNEL</replaceable></arg>
|
||||||
|
<arg choice="req"><replaceable>TEXT</replaceable></arg>
|
||||||
|
</cmdsynopsis>
|
||||||
|
</term>
|
||||||
|
<listitem>
|
||||||
|
<para>
|
||||||
|
Send a message to the specified channel.
|
||||||
|
</para>
|
||||||
|
</listitem>
|
||||||
|
</varlistentry>
|
||||||
</variablelist>
|
</variablelist>
|
||||||
</section>
|
</section>
|
||||||
</chapter>
|
</chapter>
|
||||||
|
|||||||
@@ -1,91 +0,0 @@
|
|||||||
<?xml version='1.0' encoding="utf-8"?>
|
|
||||||
<!DOCTYPE book PUBLIC "-//OASIS//DTD DocBook XML V4.2//EN"
|
|
||||||
"docbook/dtd/xml/4.2/docbookx.dtd">
|
|
||||||
<book>
|
|
||||||
<title>The Music Player Daemon Sticker Database</title>
|
|
||||||
|
|
||||||
<chapter>
|
|
||||||
<title>Introduction to MPD's Sticker Database</title>
|
|
||||||
<para>
|
|
||||||
This document shell give a short guideline for recommended tags
|
|
||||||
for use in MPD's Sticker Database.
|
|
||||||
MPD's Sticker Database is a subsystem that enables users to add
|
|
||||||
custom tags. MPD does not alter the media files.
|
|
||||||
</para>
|
|
||||||
</chapter>
|
|
||||||
|
|
||||||
<chapter>
|
|
||||||
<title>Guideline for recommended tags</title>
|
|
||||||
<para>
|
|
||||||
Since there is no standard for tags in media files, this
|
|
||||||
document is trying to give you some help deciding what tags to
|
|
||||||
use. The selection of these tags tries to cover the most
|
|
||||||
widely used tags. This way the tags might still work in other
|
|
||||||
players, if you sync the database with your original media
|
|
||||||
files.
|
|
||||||
Keep in mind that we stick with lower case tags with underscores
|
|
||||||
instead of spaces. If there will be a Sync tool in future
|
|
||||||
its easy to change this on the fly, if needed.
|
|
||||||
</para>
|
|
||||||
|
|
||||||
<variablelist>
|
|
||||||
<varlistentry>
|
|
||||||
<term><varname>rating</varname></term>
|
|
||||||
<listitem>
|
|
||||||
<para>
|
|
||||||
Will store a rating value from 1 (worst) to 5 (best) for a
|
|
||||||
given song.
|
|
||||||
</para>
|
|
||||||
</listitem>
|
|
||||||
</varlistentry>
|
|
||||||
|
|
||||||
<varlistentry>
|
|
||||||
<term><varname>album_rating</varname></term>
|
|
||||||
<listitem>
|
|
||||||
<para>
|
|
||||||
Will store a rating value from 1 (worst) to 5 (best) for a
|
|
||||||
given album.
|
|
||||||
</para>
|
|
||||||
</listitem>
|
|
||||||
</varlistentry>
|
|
||||||
|
|
||||||
<varlistentry>
|
|
||||||
<term><varname>style</varname></term>
|
|
||||||
<listitem>
|
|
||||||
<para>
|
|
||||||
This tag is used to keep the Genre tag clean, by now
|
|
||||||
having 1000's of genres. Instead you define a Main Genre
|
|
||||||
for each file and can make a more specific
|
|
||||||
description. This should be one Keyword like "Post Punk"
|
|
||||||
or "Progressive Death Metal" An Alternative name for this
|
|
||||||
tag is "Subgenre", time will tell which one gets more
|
|
||||||
support.
|
|
||||||
</para>
|
|
||||||
</listitem>
|
|
||||||
</varlistentry>
|
|
||||||
|
|
||||||
<varlistentry>
|
|
||||||
<term><varname>lyrics</varname></term>
|
|
||||||
<listitem>
|
|
||||||
<para>
|
|
||||||
This one is self explaining. This gives the option to
|
|
||||||
store lyrics of a song where they belong to: mapped to the
|
|
||||||
song
|
|
||||||
</para>
|
|
||||||
</listitem>
|
|
||||||
</varlistentry>
|
|
||||||
|
|
||||||
<varlistentry>
|
|
||||||
<term><varname>similar_artists</varname> (Comma seperated list of artists)</term>
|
|
||||||
<listitem>
|
|
||||||
<para>
|
|
||||||
This tag enables a last.fm alike aproach which will still
|
|
||||||
work when being offline Keep in mind, that this tag is
|
|
||||||
absolutely non-standard! I am not aware of any other
|
|
||||||
player that uses a comparable tag.
|
|
||||||
</para>
|
|
||||||
</listitem>
|
|
||||||
</varlistentry>
|
|
||||||
</variablelist>
|
|
||||||
</chapter>
|
|
||||||
</book>
|
|
||||||
1080
doc/user.xml
1080
doc/user.xml
File diff suppressed because it is too large
Load Diff
63
m4/ax_append_compile_flags.m4
Normal file
63
m4/ax_append_compile_flags.m4
Normal file
@@ -0,0 +1,63 @@
|
|||||||
|
# ===========================================================================
|
||||||
|
# http://www.gnu.org/software/autoconf-archive/ax_append_compile_flags.html
|
||||||
|
# ===========================================================================
|
||||||
|
#
|
||||||
|
# SYNOPSIS
|
||||||
|
#
|
||||||
|
# AX_APPEND_COMPILE_FLAGS([FLAG1 FLAG2 ...], [FLAGS-VARIABLE], [EXTRA-FLAGS])
|
||||||
|
#
|
||||||
|
# DESCRIPTION
|
||||||
|
#
|
||||||
|
# For every FLAG1, FLAG2 it is checked whether the compiler works with the
|
||||||
|
# flag. If it does, the flag is added FLAGS-VARIABLE
|
||||||
|
#
|
||||||
|
# If FLAGS-VARIABLE is not specified, the current language's flags (e.g.
|
||||||
|
# CFLAGS) is used. During the check the flag is always added to the
|
||||||
|
# current language's flags.
|
||||||
|
#
|
||||||
|
# If EXTRA-FLAGS is defined, it is added to the current language's default
|
||||||
|
# flags (e.g. CFLAGS) when the check is done. The check is thus made with
|
||||||
|
# the flags: "CFLAGS EXTRA-FLAGS FLAG". This can for example be used to
|
||||||
|
# force the compiler to issue an error when a bad flag is given.
|
||||||
|
#
|
||||||
|
# NOTE: This macro depends on the AX_APPEND_FLAG and
|
||||||
|
# AX_CHECK_COMPILE_FLAG. Please keep this macro in sync with
|
||||||
|
# AX_APPEND_LINK_FLAGS.
|
||||||
|
#
|
||||||
|
# LICENSE
|
||||||
|
#
|
||||||
|
# Copyright (c) 2011 Maarten Bosmans <mkbosmans@gmail.com>
|
||||||
|
#
|
||||||
|
# This program is free software: you can redistribute it and/or modify it
|
||||||
|
# under the terms of the GNU General Public License as published by the
|
||||||
|
# Free Software Foundation, either version 3 of the License, or (at your
|
||||||
|
# option) any later version.
|
||||||
|
#
|
||||||
|
# This program is distributed in the hope that it will be useful, but
|
||||||
|
# WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
|
||||||
|
# Public License for more details.
|
||||||
|
#
|
||||||
|
# You should have received a copy of the GNU General Public License along
|
||||||
|
# with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
#
|
||||||
|
# As a special exception, the respective Autoconf Macro's copyright owner
|
||||||
|
# gives unlimited permission to copy, distribute and modify the configure
|
||||||
|
# scripts that are the output of Autoconf when processing the Macro. You
|
||||||
|
# need not follow the terms of the GNU General Public License when using
|
||||||
|
# or distributing such scripts, even though portions of the text of the
|
||||||
|
# Macro appear in them. The GNU General Public License (GPL) does govern
|
||||||
|
# all other use of the material that constitutes the Autoconf Macro.
|
||||||
|
#
|
||||||
|
# This special exception to the GPL applies to versions of the Autoconf
|
||||||
|
# Macro released by the Autoconf Archive. When you make and distribute a
|
||||||
|
# modified version of the Autoconf Macro, you may extend this special
|
||||||
|
# exception to the GPL to apply to your modified version as well.
|
||||||
|
|
||||||
|
#serial 2
|
||||||
|
|
||||||
|
AC_DEFUN([AX_APPEND_COMPILE_FLAGS],
|
||||||
|
[for flag in $1; do
|
||||||
|
AX_CHECK_COMPILE_FLAG([$flag], [AX_APPEND_FLAG([$flag], [$2])], [], [$3])
|
||||||
|
done
|
||||||
|
])dnl AX_APPEND_COMPILE_FLAGS
|
||||||
69
m4/ax_append_flag.m4
Normal file
69
m4/ax_append_flag.m4
Normal file
@@ -0,0 +1,69 @@
|
|||||||
|
# ===========================================================================
|
||||||
|
# http://www.gnu.org/software/autoconf-archive/ax_append_flag.html
|
||||||
|
# ===========================================================================
|
||||||
|
#
|
||||||
|
# SYNOPSIS
|
||||||
|
#
|
||||||
|
# AX_APPEND_FLAG(FLAG, [FLAGS-VARIABLE])
|
||||||
|
#
|
||||||
|
# DESCRIPTION
|
||||||
|
#
|
||||||
|
# FLAG is appended to the FLAGS-VARIABLE shell variable, with a space
|
||||||
|
# added in between.
|
||||||
|
#
|
||||||
|
# If FLAGS-VARIABLE is not specified, the current language's flags (e.g.
|
||||||
|
# CFLAGS) is used. FLAGS-VARIABLE is not changed if it already contains
|
||||||
|
# FLAG. If FLAGS-VARIABLE is unset in the shell, it is set to exactly
|
||||||
|
# FLAG.
|
||||||
|
#
|
||||||
|
# NOTE: Implementation based on AX_CFLAGS_GCC_OPTION.
|
||||||
|
#
|
||||||
|
# LICENSE
|
||||||
|
#
|
||||||
|
# Copyright (c) 2008 Guido U. Draheim <guidod@gmx.de>
|
||||||
|
# Copyright (c) 2011 Maarten Bosmans <mkbosmans@gmail.com>
|
||||||
|
#
|
||||||
|
# This program is free software: you can redistribute it and/or modify it
|
||||||
|
# under the terms of the GNU General Public License as published by the
|
||||||
|
# Free Software Foundation, either version 3 of the License, or (at your
|
||||||
|
# option) any later version.
|
||||||
|
#
|
||||||
|
# This program is distributed in the hope that it will be useful, but
|
||||||
|
# WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
|
||||||
|
# Public License for more details.
|
||||||
|
#
|
||||||
|
# You should have received a copy of the GNU General Public License along
|
||||||
|
# with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
#
|
||||||
|
# As a special exception, the respective Autoconf Macro's copyright owner
|
||||||
|
# gives unlimited permission to copy, distribute and modify the configure
|
||||||
|
# scripts that are the output of Autoconf when processing the Macro. You
|
||||||
|
# need not follow the terms of the GNU General Public License when using
|
||||||
|
# or distributing such scripts, even though portions of the text of the
|
||||||
|
# Macro appear in them. The GNU General Public License (GPL) does govern
|
||||||
|
# all other use of the material that constitutes the Autoconf Macro.
|
||||||
|
#
|
||||||
|
# This special exception to the GPL applies to versions of the Autoconf
|
||||||
|
# Macro released by the Autoconf Archive. When you make and distribute a
|
||||||
|
# modified version of the Autoconf Macro, you may extend this special
|
||||||
|
# exception to the GPL to apply to your modified version as well.
|
||||||
|
|
||||||
|
#serial 2
|
||||||
|
|
||||||
|
AC_DEFUN([AX_APPEND_FLAG],
|
||||||
|
[AC_PREREQ(2.59)dnl for _AC_LANG_PREFIX
|
||||||
|
AS_VAR_PUSHDEF([FLAGS], [m4_default($2,_AC_LANG_PREFIX[FLAGS])])dnl
|
||||||
|
AS_VAR_SET_IF(FLAGS,
|
||||||
|
[case " AS_VAR_GET(FLAGS) " in
|
||||||
|
*" $1 "*)
|
||||||
|
AC_RUN_LOG([: FLAGS already contains $1])
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
AC_RUN_LOG([: FLAGS="$FLAGS $1"])
|
||||||
|
AS_VAR_SET(FLAGS, ["AS_VAR_GET(FLAGS) $1"])
|
||||||
|
;;
|
||||||
|
esac],
|
||||||
|
[AS_VAR_SET(FLAGS,["$1"])])
|
||||||
|
AS_VAR_POPDEF([FLAGS])dnl
|
||||||
|
])dnl AX_APPEND_FLAG
|
||||||
72
m4/ax_check_compile_flag.m4
Normal file
72
m4/ax_check_compile_flag.m4
Normal file
@@ -0,0 +1,72 @@
|
|||||||
|
# ===========================================================================
|
||||||
|
# http://www.gnu.org/software/autoconf-archive/ax_check_compile_flag.html
|
||||||
|
# ===========================================================================
|
||||||
|
#
|
||||||
|
# SYNOPSIS
|
||||||
|
#
|
||||||
|
# AX_CHECK_COMPILE_FLAG(FLAG, [ACTION-SUCCESS], [ACTION-FAILURE], [EXTRA-FLAGS])
|
||||||
|
#
|
||||||
|
# DESCRIPTION
|
||||||
|
#
|
||||||
|
# Check whether the given FLAG works with the current language's compiler
|
||||||
|
# or gives an error. (Warnings, however, are ignored)
|
||||||
|
#
|
||||||
|
# ACTION-SUCCESS/ACTION-FAILURE are shell commands to execute on
|
||||||
|
# success/failure.
|
||||||
|
#
|
||||||
|
# If EXTRA-FLAGS is defined, it is added to the current language's default
|
||||||
|
# flags (e.g. CFLAGS) when the check is done. The check is thus made with
|
||||||
|
# the flags: "CFLAGS EXTRA-FLAGS FLAG". This can for example be used to
|
||||||
|
# force the compiler to issue an error when a bad flag is given.
|
||||||
|
#
|
||||||
|
# NOTE: Implementation based on AX_CFLAGS_GCC_OPTION. Please keep this
|
||||||
|
# macro in sync with AX_CHECK_{PREPROC,LINK}_FLAG.
|
||||||
|
#
|
||||||
|
# LICENSE
|
||||||
|
#
|
||||||
|
# Copyright (c) 2008 Guido U. Draheim <guidod@gmx.de>
|
||||||
|
# Copyright (c) 2011 Maarten Bosmans <mkbosmans@gmail.com>
|
||||||
|
#
|
||||||
|
# This program is free software: you can redistribute it and/or modify it
|
||||||
|
# under the terms of the GNU General Public License as published by the
|
||||||
|
# Free Software Foundation, either version 3 of the License, or (at your
|
||||||
|
# option) any later version.
|
||||||
|
#
|
||||||
|
# This program is distributed in the hope that it will be useful, but
|
||||||
|
# WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
|
||||||
|
# Public License for more details.
|
||||||
|
#
|
||||||
|
# You should have received a copy of the GNU General Public License along
|
||||||
|
# with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
#
|
||||||
|
# As a special exception, the respective Autoconf Macro's copyright owner
|
||||||
|
# gives unlimited permission to copy, distribute and modify the configure
|
||||||
|
# scripts that are the output of Autoconf when processing the Macro. You
|
||||||
|
# need not follow the terms of the GNU General Public License when using
|
||||||
|
# or distributing such scripts, even though portions of the text of the
|
||||||
|
# Macro appear in them. The GNU General Public License (GPL) does govern
|
||||||
|
# all other use of the material that constitutes the Autoconf Macro.
|
||||||
|
#
|
||||||
|
# This special exception to the GPL applies to versions of the Autoconf
|
||||||
|
# Macro released by the Autoconf Archive. When you make and distribute a
|
||||||
|
# modified version of the Autoconf Macro, you may extend this special
|
||||||
|
# exception to the GPL to apply to your modified version as well.
|
||||||
|
|
||||||
|
#serial 2
|
||||||
|
|
||||||
|
AC_DEFUN([AX_CHECK_COMPILE_FLAG],
|
||||||
|
[AC_PREREQ(2.59)dnl for _AC_LANG_PREFIX
|
||||||
|
AS_VAR_PUSHDEF([CACHEVAR],[ax_cv_check_[]_AC_LANG_ABBREV[]flags_$4_$1])dnl
|
||||||
|
AC_CACHE_CHECK([whether _AC_LANG compiler accepts $1], CACHEVAR, [
|
||||||
|
ax_check_save_flags=$[]_AC_LANG_PREFIX[]FLAGS
|
||||||
|
_AC_LANG_PREFIX[]FLAGS="$[]_AC_LANG_PREFIX[]FLAGS $4 $1"
|
||||||
|
AC_COMPILE_IFELSE([AC_LANG_PROGRAM()],
|
||||||
|
[AS_VAR_SET(CACHEVAR,[yes])],
|
||||||
|
[AS_VAR_SET(CACHEVAR,[no])])
|
||||||
|
_AC_LANG_PREFIX[]FLAGS=$ax_check_save_flags])
|
||||||
|
AS_IF([test x"AS_VAR_GET(CACHEVAR)" = xyes],
|
||||||
|
[m4_default([$2], :)],
|
||||||
|
[m4_default([$3], :)])
|
||||||
|
AS_VAR_POPDEF([CACHEVAR])dnl
|
||||||
|
])dnl AX_CHECK_COMPILE_FLAGS
|
||||||
30
m4/faad.m4
30
m4/faad.m4
@@ -39,8 +39,8 @@ if test x$enable_aac = xyes; then
|
|||||||
oldcflags=$CFLAGS
|
oldcflags=$CFLAGS
|
||||||
oldlibs=$LIBS
|
oldlibs=$LIBS
|
||||||
oldcppflags=$CPPFLAGS
|
oldcppflags=$CPPFLAGS
|
||||||
CFLAGS="$CFLAGS $MPD_CFLAGS $FAAD_CFLAGS -I."
|
CFLAGS="$CFLAGS $FAAD_CFLAGS -I."
|
||||||
LIBS="$LIBS $MPD_LIBS $FAAD_LIBS"
|
LIBS="$LIBS $FAAD_LIBS"
|
||||||
CPPFLAGS=$CFLAGS
|
CPPFLAGS=$CFLAGS
|
||||||
AC_CHECK_HEADER(faad.h,,enable_aac=no)
|
AC_CHECK_HEADER(faad.h,,enable_aac=no)
|
||||||
if test x$enable_aac = xyes; then
|
if test x$enable_aac = xyes; then
|
||||||
@@ -50,15 +50,15 @@ if test x$enable_aac = xyes; then
|
|||||||
AC_CHECK_DECL(faacDecInit2,,enable_aac=no,[#include <faad.h>])
|
AC_CHECK_DECL(faacDecInit2,,enable_aac=no,[#include <faad.h>])
|
||||||
fi
|
fi
|
||||||
if test x$enable_aac = xyes; then
|
if test x$enable_aac = xyes; then
|
||||||
AC_CHECK_LIB(faad,faacDecInit2,[MPD_LIBS="$MPD_LIBS $FAAD_LIBS";MPD_CFLAGS="$MPD_CFLAGS $FAAD_CFLAGS"],enable_aac=no)
|
AC_CHECK_LIB(faad,faacDecInit2,,enable_aac=no)
|
||||||
if test x$enable_aac = xno; then
|
if test x$enable_aac = xno; then
|
||||||
enable_aac=yes
|
enable_aac=yes
|
||||||
AC_CHECK_LIB(faad,NeAACDecInit2,[MPD_LIBS="$MPD_LIBS $FAAD_LIBS";MPD_CFLAGS="$MPD_CFLAGS $FAAD_CFLAGS"],enable_aac=no)
|
AC_CHECK_LIB(faad,NeAACDecInit2,,enable_aac=no)
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
if test x$enable_aac = xyes; then
|
if test x$enable_aac = xyes; then
|
||||||
AC_MSG_CHECKING(that FAAD2 uses buffer and bufferlen)
|
AC_MSG_CHECKING(that FAAD2 uses buffer and bufferlen)
|
||||||
AC_COMPILE_IFELSE([
|
AC_COMPILE_IFELSE([AC_LANG_SOURCE([
|
||||||
#include <faad.h>
|
#include <faad.h>
|
||||||
|
|
||||||
int main() {
|
int main() {
|
||||||
@@ -82,9 +82,9 @@ int main() {
|
|||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
],[AC_MSG_RESULT(yes);AC_DEFINE(HAVE_FAAD_BUFLEN_FUNCS,1,[Define if FAAD2 uses buflen in function calls])],[AC_MSG_RESULT(no);
|
])],[AC_MSG_RESULT(yes);AC_DEFINE(HAVE_FAAD_BUFLEN_FUNCS,1,[Define if FAAD2 uses buflen in function calls])],[AC_MSG_RESULT(no);
|
||||||
AC_MSG_CHECKING(that FAAD2 can even be used)
|
AC_MSG_CHECKING(that FAAD2 can even be used)
|
||||||
AC_COMPILE_IFELSE([
|
AC_COMPILE_IFELSE([AC_LANG_SOURCE([
|
||||||
#include <faad.h>
|
#include <faad.h>
|
||||||
|
|
||||||
int main() {
|
int main() {
|
||||||
@@ -113,7 +113,7 @@ int main() {
|
|||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
],AC_MSG_RESULT(yes),[AC_MSG_RESULT(no);enable_aac=no])
|
])],AC_MSG_RESULT(yes),[AC_MSG_RESULT(no);enable_aac=no])
|
||||||
])
|
])
|
||||||
fi
|
fi
|
||||||
if test x$enable_aac = xyes; then
|
if test x$enable_aac = xyes; then
|
||||||
@@ -131,12 +131,12 @@ if test x$enable_aac = xyes; then
|
|||||||
oldcflags=$CFLAGS
|
oldcflags=$CFLAGS
|
||||||
oldlibs=$LIBS
|
oldlibs=$LIBS
|
||||||
oldcppflags=$CPPFLAGS
|
oldcppflags=$CPPFLAGS
|
||||||
CFLAGS="$CFLAGS $MPD_CFLAGS $FAAD_CFLAGS -Werror"
|
CFLAGS="$CFLAGS $FAAD_CFLAGS -Werror"
|
||||||
LIBS="$LIBS $MPD_LIBS $FAAD_LIBS"
|
LIBS="$LIBS $FAAD_LIBS"
|
||||||
CPPFLAGS=$CFLAGS
|
CPPFLAGS=$CFLAGS
|
||||||
|
|
||||||
AC_MSG_CHECKING(for broken libfaad headers)
|
AC_MSG_CHECKING(for broken libfaad headers)
|
||||||
AC_COMPILE_IFELSE([
|
AC_COMPILE_IFELSE([AC_LANG_SOURCE([
|
||||||
#include <faad.h>
|
#include <faad.h>
|
||||||
#include <stddef.h>
|
#include <stddef.h>
|
||||||
#include <stdint.h>
|
#include <stdint.h>
|
||||||
@@ -148,7 +148,7 @@ int main() {
|
|||||||
faacDecInit2(NULL, NULL, 0, &sample_rate, &channels);
|
faacDecInit2(NULL, NULL, 0, &sample_rate, &channels);
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
],
|
])],
|
||||||
[AC_MSG_RESULT(correct)],
|
[AC_MSG_RESULT(correct)],
|
||||||
[AC_MSG_RESULT(broken);
|
[AC_MSG_RESULT(broken);
|
||||||
AC_DEFINE(HAVE_FAAD_LONG, 1, [Define if faad.h uses the broken "unsigned long" pointers])])
|
AC_DEFINE(HAVE_FAAD_LONG, 1, [Define if faad.h uses the broken "unsigned long" pointers])])
|
||||||
@@ -188,5 +188,11 @@ if test x$enable_aac = xyes; then
|
|||||||
CPPFLAGS=$oldcppflags
|
CPPFLAGS=$oldcppflags
|
||||||
else
|
else
|
||||||
enable_mp4=no
|
enable_mp4=no
|
||||||
|
FAAD_CFLAGS=""
|
||||||
|
FAAD_LIBS=""
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
AC_SUBST(FAAD_CFLAGS)
|
||||||
|
AC_SUBST(FAAD_LIBS)
|
||||||
|
|
||||||
])
|
])
|
||||||
|
|||||||
111
m4/lame.m4
111
m4/lame.m4
@@ -1,111 +0,0 @@
|
|||||||
dnl borrowed from oddsock.org
|
|
||||||
dnl AM_PATH_LAME([ACTION-IF-FOUND [, ACTION-IF-NOT-FOUND]])
|
|
||||||
dnl Test for liblame, and define LAME_CFLAGS and LAME_LIBS
|
|
||||||
dnl
|
|
||||||
AC_DEFUN([AM_PATH_LAME],
|
|
||||||
[dnl
|
|
||||||
dnl Get the cflags and libraries
|
|
||||||
dnl
|
|
||||||
AC_ARG_WITH(lame,
|
|
||||||
AS_HELP_STRING([--with-lame=PFX],
|
|
||||||
[prefix where liblame is installed (optional)]),,
|
|
||||||
lame_prefix="")
|
|
||||||
AC_ARG_WITH(lame-libraries,
|
|
||||||
AS_HELP_STRING([--with-lame-libraries=DIR],
|
|
||||||
[directory where liblame library is installed (optional)]),,
|
|
||||||
lame_libraries="")
|
|
||||||
AC_ARG_WITH(lame-includes,
|
|
||||||
AS_HELP_STRING([--with-lame-includes=DIR],
|
|
||||||
[directory where liblame header files are installed (optional)]),,
|
|
||||||
lame_includes="")
|
|
||||||
|
|
||||||
if test "x$lame_prefix" != "xno" ; then
|
|
||||||
|
|
||||||
if test "x$lame_libraries" != "x" ; then
|
|
||||||
LAME_LIBS="-L$lame_libraries"
|
|
||||||
elif test "x$lame_prefix" != "x" ; then
|
|
||||||
LAME_LIBS="-L$lame_prefix/lib"
|
|
||||||
elif test "x$prefix" != "xNONE" ; then
|
|
||||||
LAME_LIBS="-L$prefix/lib"
|
|
||||||
fi
|
|
||||||
|
|
||||||
LAME_LIBS="$LAME_LIBS -lmp3lame -lm"
|
|
||||||
|
|
||||||
if test "x$lame_includes" != "x" ; then
|
|
||||||
LAME_CFLAGS="-I$lame_includes"
|
|
||||||
elif test "x$lame_prefix" != "x" ; then
|
|
||||||
LAME_CFLAGS="-I$lame_prefix/include"
|
|
||||||
elif test "x$prefix" != "xNONE"; then
|
|
||||||
LAME_CFLAGS="-I$prefix/include"
|
|
||||||
fi
|
|
||||||
|
|
||||||
AC_MSG_CHECKING(for liblame)
|
|
||||||
no_lame=""
|
|
||||||
|
|
||||||
|
|
||||||
ac_save_CFLAGS="$CFLAGS"
|
|
||||||
ac_save_LIBS="$LIBS"
|
|
||||||
CFLAGS="$CFLAGS $LAME_CFLAGS"
|
|
||||||
LIBS="$LIBS $LAME_LIBS"
|
|
||||||
dnl
|
|
||||||
dnl Now check if the installed liblame is sufficiently new.
|
|
||||||
dnl
|
|
||||||
rm -f conf.lametest
|
|
||||||
AC_TRY_RUN([
|
|
||||||
#include <stdio.h>
|
|
||||||
#include <stdlib.h>
|
|
||||||
#include <string.h>
|
|
||||||
#include <lame/lame.h>
|
|
||||||
|
|
||||||
int main ()
|
|
||||||
{
|
|
||||||
system("touch conf.lametest");
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
],, no_lame=yes,[echo $ac_n "cross compiling; assumed OK... $ac_c"])
|
|
||||||
CFLAGS="$ac_save_CFLAGS"
|
|
||||||
LIBS="$ac_save_LIBS"
|
|
||||||
fi
|
|
||||||
|
|
||||||
if test "x$no_lame" = "x" ; then
|
|
||||||
AC_MSG_RESULT(yes)
|
|
||||||
ifelse([$1], , :, [$1])
|
|
||||||
else
|
|
||||||
AC_MSG_RESULT(no)
|
|
||||||
if test -f conf.lametest ; then
|
|
||||||
:
|
|
||||||
else
|
|
||||||
echo "*** Could not run liblame test program, checking why..."
|
|
||||||
CFLAGS="$CFLAGS $LAME_CFLAGS"
|
|
||||||
LIBS="$LIBS $LAME_LIBS"
|
|
||||||
AC_TRY_LINK([
|
|
||||||
#include <stdio.h>
|
|
||||||
#include <lame/lame.h>
|
|
||||||
], [ return 0; ],
|
|
||||||
[ echo "*** The test program compiled, but did not run. This usually means"
|
|
||||||
echo "*** that the run-time linker is not finding liblame or finding the wrong"
|
|
||||||
echo "*** version of liblame. If it is not finding liblame, you'll need to set your"
|
|
||||||
echo "*** LD_LIBRARY_PATH environment variable, or edit /etc/ld.so.conf to point"
|
|
||||||
echo "*** to the installed location Also, make sure you have run ldconfig if that"
|
|
||||||
echo "*** is required on your system"
|
|
||||||
echo "***"
|
|
||||||
echo "*** If you have an old version installed, it is best to remove it, although"
|
|
||||||
echo "*** you may also be able to get things to work by modifying LD_LIBRARY_PATH"],
|
|
||||||
[ echo "*** The test program failed to compile or link. See the file config.log for the"
|
|
||||||
echo "*** exact error that occured. This usually means liblame was incorrectly installed"
|
|
||||||
echo "*** or that you have moved liblame since it was installed." ])
|
|
||||||
CFLAGS="$ac_save_CFLAGS"
|
|
||||||
LIBS="$ac_save_LIBS"
|
|
||||||
fi
|
|
||||||
LAME_CFLAGS=""
|
|
||||||
LAME_LIBS=""
|
|
||||||
ifelse([$2], , :, [$2])
|
|
||||||
fi
|
|
||||||
AC_DEFINE(HAVE_LAME, 1, [Define if you have liblame.])
|
|
||||||
use_lame="1"
|
|
||||||
AC_SUBST(LAME_CFLAGS)
|
|
||||||
AC_SUBST(LAME_LIBS)
|
|
||||||
rm -f conf.lametest
|
|
||||||
])
|
|
||||||
|
|
||||||
116
m4/libOggFLAC.m4
116
m4/libOggFLAC.m4
@@ -1,116 +0,0 @@
|
|||||||
# Configure paths for libOggFLAC
|
|
||||||
# "Inspired" by ogg.m4
|
|
||||||
|
|
||||||
dnl AM_PATH_LIBOGGFLAC([ACTION-IF-FOUND [, ACTION-IF-NOT-FOUND]])
|
|
||||||
dnl Test for libOggFLAC, and define LIBOGGFLAC_CFLAGS and LIBOGGFLAC_LIBS
|
|
||||||
dnl
|
|
||||||
AC_DEFUN([AM_PATH_LIBOGGFLAC],
|
|
||||||
[dnl
|
|
||||||
dnl Get the cflags and libraries
|
|
||||||
dnl
|
|
||||||
AC_ARG_WITH(libOggFLAC,
|
|
||||||
AS_HELP_STRING([--with-libOggFLAC=PFX],
|
|
||||||
[prefix where libOggFLAC is installed (optional)]),,
|
|
||||||
libOggFLAC_prefix="")
|
|
||||||
AC_ARG_WITH(libOggFLAC-libraries,
|
|
||||||
AS_HELP_STRING([--with-libOggFLAC-libraries=DIR],
|
|
||||||
[directory where libOggFLAC library is installed (optional)]),,
|
|
||||||
libOggFLAC_libraries="")
|
|
||||||
AC_ARG_WITH(libOggFLAC-includes,
|
|
||||||
AS_HELP_STRING([--with-libOggFLAC-includes=DIR],
|
|
||||||
[directory where libOggFLAC header files are installed (optional)]),,
|
|
||||||
libOggFLAC_includes="")
|
|
||||||
AC_ARG_ENABLE(libOggFLACtest,
|
|
||||||
AS_HELP_STRING([--disable-libOggFLACtest],
|
|
||||||
[do not try to compile and run a test libOggFLAC program]),,
|
|
||||||
enable_libOggFLACtest=yes)
|
|
||||||
|
|
||||||
if test "x$libOggFLAC_libraries" != "x" ; then
|
|
||||||
LIBOGGFLAC_LIBS="-L$libOggFLAC_libraries"
|
|
||||||
elif test "x$libOggFLAC_prefix" != "x" ; then
|
|
||||||
LIBOGGFLAC_LIBS="-L$libOggFLAC_prefix/lib"
|
|
||||||
elif test "x$prefix" != "xNONE" ; then
|
|
||||||
LIBOGGFLAC_LIBS="-L$libdir"
|
|
||||||
fi
|
|
||||||
|
|
||||||
LIBOGGFLAC_LIBS="$LIBOGGFLAC_LIBS -lOggFLAC -lFLAC -lm"
|
|
||||||
|
|
||||||
if test "x$libOggFLAC_includes" != "x" ; then
|
|
||||||
LIBOGGFLAC_CFLAGS="-I$libOggFLAC_includes"
|
|
||||||
elif test "x$libOggFLAC_prefix" != "x" ; then
|
|
||||||
LIBOGGFLAC_CFLAGS="-I$libOggFLAC_prefix/include"
|
|
||||||
elif test "x$prefix" != "xNONE"; then
|
|
||||||
LIBOGGFLAC_CFLAGS="-I$prefix/include"
|
|
||||||
fi
|
|
||||||
|
|
||||||
AC_MSG_CHECKING(for libOggFLAC)
|
|
||||||
no_libOggFLAC=""
|
|
||||||
|
|
||||||
|
|
||||||
if test "x$enable_libOggFLACtest" = "xyes" ; then
|
|
||||||
ac_save_CFLAGS="$CFLAGS"
|
|
||||||
ac_save_CXXFLAGS="$CXXFLAGS"
|
|
||||||
ac_save_LIBS="$LIBS"
|
|
||||||
CFLAGS="$CFLAGS $LIBOGGFLAC_CFLAGS"
|
|
||||||
CXXFLAGS="$CXXFLAGS $LIBOGGFLAC_CFLAGS"
|
|
||||||
LIBS="$LIBS $LIBOGGFLAC_LIBS"
|
|
||||||
dnl
|
|
||||||
dnl Now check if the installed libOggFLAC is sufficiently new.
|
|
||||||
dnl
|
|
||||||
rm -f conf.libOggFLACtest
|
|
||||||
AC_TRY_RUN([
|
|
||||||
#include <stdio.h>
|
|
||||||
#include <stdlib.h>
|
|
||||||
#include <string.h>
|
|
||||||
#include <OggFLAC/stream_decoder.h>
|
|
||||||
|
|
||||||
int main ()
|
|
||||||
{
|
|
||||||
system("touch conf.libOggFLACtest");
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
],, no_libOggFLAC=yes,[echo $ac_n "cross compiling; assumed OK... $ac_c"])
|
|
||||||
CFLAGS="$ac_save_CFLAGS"
|
|
||||||
LIBS="$ac_save_LIBS"
|
|
||||||
fi
|
|
||||||
|
|
||||||
if test "x$no_libOggFLAC" = "x" ; then
|
|
||||||
AC_MSG_RESULT(yes)
|
|
||||||
ifelse([$1], , :, [$1])
|
|
||||||
else
|
|
||||||
AC_MSG_RESULT(no)
|
|
||||||
if test -f conf.libOggFLACtest ; then
|
|
||||||
:
|
|
||||||
else
|
|
||||||
echo "*** Could not run libOggFLAC test program, checking why..."
|
|
||||||
CFLAGS="$CFLAGS $LIBOGGFLAC_CFLAGS"
|
|
||||||
LIBS="$LIBS $LIBOGGFLAC_LIBS"
|
|
||||||
AC_TRY_LINK([
|
|
||||||
#include <stdio.h>
|
|
||||||
#include <OggFLAC/stream_decoder.h>
|
|
||||||
], [ return 0; ],
|
|
||||||
[ echo "*** The test program compiled, but did not run. This usually means"
|
|
||||||
echo "*** that the run-time linker is not finding libOggFLAC or finding the wrong"
|
|
||||||
echo "*** version of libOggFLAC. If it is not finding libOggFLAC, you'll need to set your"
|
|
||||||
echo "*** LD_LIBRARY_PATH environment variable, or edit /etc/ld.so.conf to point"
|
|
||||||
echo "*** to the installed location Also, make sure you have run ldconfig if that"
|
|
||||||
echo "*** is required on your system"
|
|
||||||
echo "***"
|
|
||||||
echo "*** If you have an old version installed, it is best to remove it, although"
|
|
||||||
echo "*** you may also be able to get things to work by modifying LD_LIBRARY_PATH"],
|
|
||||||
[ echo "*** The test program failed to compile or link. See the file config.log for the"
|
|
||||||
echo "*** exact error that occured. This usually means libOggFLAC was incorrectly installed"
|
|
||||||
echo "*** or that you have moved libOggFLAC since it was installed. In the latter case, you"
|
|
||||||
echo "*** may want to edit the libOggFLAC-config script: $LIBOGGFLAC_CONFIG" ])
|
|
||||||
CFLAGS="$ac_save_CFLAGS"
|
|
||||||
LIBS="$ac_save_LIBS"
|
|
||||||
fi
|
|
||||||
LIBOGGFLAC_CFLAGS=""
|
|
||||||
LIBOGGFLAC_LIBS=""
|
|
||||||
ifelse([$2], , :, [$2])
|
|
||||||
fi
|
|
||||||
AC_SUBST(LIBOGGFLAC_CFLAGS)
|
|
||||||
AC_SUBST(LIBOGGFLAC_LIBS)
|
|
||||||
rm -f conf.libOggFLACtest
|
|
||||||
])
|
|
||||||
14
m4/libwrap.m4
Normal file
14
m4/libwrap.m4
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
dnl
|
||||||
|
dnl Usage:
|
||||||
|
dnl AC_CHECK_LIBWRAP([ACTION-IF-FOUND], [ACTION-IF-NOT-FOUND])
|
||||||
|
dnl
|
||||||
|
|
||||||
|
AC_DEFUN([AC_CHECK_LIBWRAP],[
|
||||||
|
AC_CHECK_HEADERS([tcpd.h],
|
||||||
|
AC_CHECK_LIB([wrap], [request_init],
|
||||||
|
[LIBWRAP_CFLAGS=""
|
||||||
|
LIBWRAP_LDFLAGS="-lwrap"
|
||||||
|
$1],
|
||||||
|
$2),
|
||||||
|
$2)
|
||||||
|
])
|
||||||
@@ -63,3 +63,19 @@ AC_DEFUN([MPD_AUTO_PKG], [
|
|||||||
|
|
||||||
MPD_AUTO_RESULT([$1], [$4], [$5])
|
MPD_AUTO_RESULT([$1], [$4], [$5])
|
||||||
])
|
])
|
||||||
|
|
||||||
|
dnl Check with pkg-config first, fall back to AC_CHECK_LIB.
|
||||||
|
dnl
|
||||||
|
dnl Parameters: varname1, varname2, pkgname, libname, symname, libs, cflags, description, errmsg
|
||||||
|
AC_DEFUN([MPD_AUTO_PKG_LIB], [
|
||||||
|
if eval "test x`echo '$'enable_$1` != xno"; then
|
||||||
|
PKG_CHECK_MODULES([$2], [$3],
|
||||||
|
[eval "found_$1=yes"],
|
||||||
|
AC_CHECK_LIB($4, $5,
|
||||||
|
[eval "found_$1=yes $2_LIBS='$6' $2_CFLAGS='$7'"],
|
||||||
|
[eval "found_$1=no"],
|
||||||
|
[$6]))
|
||||||
|
fi
|
||||||
|
|
||||||
|
MPD_AUTO_RESULT([$1], [$8], [$9])
|
||||||
|
])
|
||||||
|
|||||||
@@ -1,18 +0,0 @@
|
|||||||
AC_DEFUN([MPD_CHECK_FLAG],[
|
|
||||||
var=`echo "$1" | tr "=-" "__"`
|
|
||||||
AC_CACHE_CHECK([whether the C compiler accepts $1],
|
|
||||||
[mpd_check_cflag_$var],[
|
|
||||||
save_CFLAGS="$CFLAGS"
|
|
||||||
CFLAGS="$CFLAGS $1"
|
|
||||||
AC_COMPILE_IFELSE([
|
|
||||||
int main(void) { return 0; }
|
|
||||||
], [ eval "mpd_check_cflag_$var=yes"
|
|
||||||
], [ eval "mpd_check_cflag_$var=no" ])
|
|
||||||
CFLAGS="$save_CFLAGS"
|
|
||||||
])
|
|
||||||
if eval "test x`echo '$mpd_check_cflag_'$var` = xyes"
|
|
||||||
then
|
|
||||||
AM_CFLAGS="$AM_CFLAGS $1"
|
|
||||||
fi
|
|
||||||
])
|
|
||||||
])
|
|
||||||
19
m4/pretty_print.m4
Normal file
19
m4/pretty_print.m4
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
AC_DEFUN([results], [
|
||||||
|
dnl This is a hack to allow "with" names, otherwise "enable".
|
||||||
|
num=`expr $1 : 'with'`
|
||||||
|
if test "$num" != "0"; then
|
||||||
|
var="`echo '$'$1`"
|
||||||
|
else
|
||||||
|
var="`echo '$'enable_$1`"
|
||||||
|
fi
|
||||||
|
|
||||||
|
printf '('
|
||||||
|
if eval "test x$var = xyes"; then
|
||||||
|
printf '+'
|
||||||
|
elif test -n "$3" && eval "test x$var = x$3"; then
|
||||||
|
printf '+'
|
||||||
|
else
|
||||||
|
printf '-'
|
||||||
|
fi
|
||||||
|
printf '%s) ' "$2"
|
||||||
|
])
|
||||||
@@ -20,7 +20,7 @@ AC_DEFUN([STRUCT_UCRED],[
|
|||||||
mpd_cv_have_struct_ucred=no)
|
mpd_cv_have_struct_ucred=no)
|
||||||
if test x$mpd_cv_have_struct_ucred = xyes; then
|
if test x$mpd_cv_have_struct_ucred = xyes; then
|
||||||
# :(
|
# :(
|
||||||
MPD_CFLAGS="$MPD_CFLAGS -D_GNU_SOURCE"
|
CFLAGS="$CFLAGS -D_GNU_SOURCE"
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
])
|
])
|
||||||
|
|||||||
9
mpd.service.in
Normal file
9
mpd.service.in
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
[Unit]
|
||||||
|
Description=Music Player Daemon
|
||||||
|
After=network.target sound.target
|
||||||
|
|
||||||
|
[Service]
|
||||||
|
ExecStart=@prefix@/bin/mpd --no-daemon
|
||||||
|
|
||||||
|
[Install]
|
||||||
|
WantedBy=multi-user.target
|
||||||
47
scripts/check_config_h.rb
Executable file
47
scripts/check_config_h.rb
Executable file
@@ -0,0 +1,47 @@
|
|||||||
|
#!/usr/bin/env ruby
|
||||||
|
#
|
||||||
|
# This script verifies that every source includes config.h first.
|
||||||
|
# This is very important for consistent Large File Support.
|
||||||
|
#
|
||||||
|
|
||||||
|
def check_file(file)
|
||||||
|
first = true
|
||||||
|
file.each_line do |line|
|
||||||
|
if line =~ /^\#include\s+(\S+)/ then
|
||||||
|
if $1 == '"config.h"'
|
||||||
|
unless first
|
||||||
|
puts "#{file.path}: config.h included too late"
|
||||||
|
end
|
||||||
|
else
|
||||||
|
if first
|
||||||
|
puts "#{file.path}: config.h missing"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
first = false
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def check_path(path)
|
||||||
|
File.open(path) do |file|
|
||||||
|
check_file(file)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
if ARGV.empty?
|
||||||
|
Dir["src/*.c"].each do |path|
|
||||||
|
check_path(path)
|
||||||
|
end
|
||||||
|
|
||||||
|
Dir["src/*/*.c"].each do |path|
|
||||||
|
check_path(path)
|
||||||
|
end
|
||||||
|
|
||||||
|
Dir["test/*.c"].each do |path|
|
||||||
|
check_path(path)
|
||||||
|
end
|
||||||
|
else
|
||||||
|
ARGV.each do |path|
|
||||||
|
check_path(path)
|
||||||
|
end
|
||||||
|
end
|
||||||
@@ -3,7 +3,7 @@ PWD=`pwd`
|
|||||||
|
|
||||||
## If we're not in the scripts directory
|
## If we're not in the scripts directory
|
||||||
## assume the base directory.
|
## assume the base directory.
|
||||||
if test "`basename $PWD`" == "scripts"; then
|
if test "`basename $PWD`" = "scripts"; then
|
||||||
cd ../
|
cd ../
|
||||||
else
|
else
|
||||||
MYOLDPWD=`pwd`
|
MYOLDPWD=`pwd`
|
||||||
@@ -18,7 +18,7 @@ fi
|
|||||||
make
|
make
|
||||||
make dist
|
make dist
|
||||||
|
|
||||||
if test "`basename $PWD`" == "scripts"; then
|
if test "`basename $PWD`" = "scripts"; then
|
||||||
cd contrib/
|
cd contrib/
|
||||||
else
|
else
|
||||||
cd $MYOLDPWD
|
cd $MYOLDPWD
|
||||||
|
|||||||
@@ -18,6 +18,7 @@ test -x configure || NOCONFIGURE=1 ./autogen.sh
|
|||||||
./configure --prefix=$PREFIX/full \
|
./configure --prefix=$PREFIX/full \
|
||||||
--disable-dependency-tracking --enable-debug --enable-werror \
|
--disable-dependency-tracking --enable-debug --enable-werror \
|
||||||
--enable-un \
|
--enable-un \
|
||||||
|
--enable-modplug \
|
||||||
--enable-ao --enable-mikmod --enable-mvp
|
--enable-ao --enable-mikmod --enable-mvp
|
||||||
$MAKE install
|
$MAKE install
|
||||||
$MAKE distclean
|
$MAKE distclean
|
||||||
@@ -47,6 +48,7 @@ $MAKE install
|
|||||||
$MAKE distclean
|
$MAKE distclean
|
||||||
|
|
||||||
# shout: ogg without mp3
|
# shout: ogg without mp3
|
||||||
|
# sndfile instead of modplug
|
||||||
./configure --prefix=$PREFIX/shout_ogg \
|
./configure --prefix=$PREFIX/shout_ogg \
|
||||||
--disable-dependency-tracking --disable-debug --enable-werror \
|
--disable-dependency-tracking --disable-debug --enable-werror \
|
||||||
--disable-tcp \
|
--disable-tcp \
|
||||||
@@ -56,6 +58,7 @@ $MAKE distclean
|
|||||||
--enable-shout-ogg --disable-shout-mp3 --disable-lame-encoder \
|
--enable-shout-ogg --disable-shout-mp3 --disable-lame-encoder \
|
||||||
--disable-ffmpeg --disable-wavpack --disable-mpc --disable-aac \
|
--disable-ffmpeg --disable-wavpack --disable-mpc --disable-aac \
|
||||||
--disable-flac --enable-vorbis --disable-oggflac --disable-audiofile \
|
--disable-flac --enable-vorbis --disable-oggflac --disable-audiofile \
|
||||||
|
--disable-modplug --enable-sndfile \
|
||||||
--with-zeroconf=no
|
--with-zeroconf=no
|
||||||
$MAKE install
|
$MAKE install
|
||||||
$MAKE distclean
|
$MAKE distclean
|
||||||
|
|||||||
192
src/AudioCompress/compress.c
Normal file
192
src/AudioCompress/compress.c
Normal file
@@ -0,0 +1,192 @@
|
|||||||
|
/* compress.c
|
||||||
|
* Compressor logic
|
||||||
|
*
|
||||||
|
* (c)2007 busybee (http://beesbuzz.biz/
|
||||||
|
* Licensed under the terms of the LGPL. See the file COPYING for details.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <stdint.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
|
#include "config.h"
|
||||||
|
#include "compress.h"
|
||||||
|
|
||||||
|
struct Compressor {
|
||||||
|
//! The compressor's preferences
|
||||||
|
struct CompressorConfig prefs;
|
||||||
|
|
||||||
|
//! History of the peak values
|
||||||
|
int *peaks;
|
||||||
|
|
||||||
|
//! History of the gain values
|
||||||
|
int *gain;
|
||||||
|
|
||||||
|
//! History of clip amounts
|
||||||
|
int *clipped;
|
||||||
|
|
||||||
|
unsigned int pos;
|
||||||
|
unsigned int bufsz;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct Compressor *Compressor_new(unsigned int history)
|
||||||
|
{
|
||||||
|
struct Compressor *obj = malloc(sizeof(struct Compressor));
|
||||||
|
if (obj == NULL)
|
||||||
|
/* out of memory, not much we can do */
|
||||||
|
abort();
|
||||||
|
|
||||||
|
obj->prefs.target = TARGET;
|
||||||
|
obj->prefs.maxgain = GAINMAX;
|
||||||
|
obj->prefs.smooth = GAINSMOOTH;
|
||||||
|
|
||||||
|
obj->peaks = obj->gain = obj->clipped = NULL;
|
||||||
|
obj->bufsz = 0;
|
||||||
|
obj->pos = 0;
|
||||||
|
|
||||||
|
Compressor_setHistory(obj, history);
|
||||||
|
|
||||||
|
return obj;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Compressor_delete(struct Compressor *obj)
|
||||||
|
{
|
||||||
|
if (obj->peaks)
|
||||||
|
free(obj->peaks);
|
||||||
|
if (obj->gain)
|
||||||
|
free(obj->gain);
|
||||||
|
if (obj->clipped)
|
||||||
|
free(obj->clipped);
|
||||||
|
free(obj);
|
||||||
|
}
|
||||||
|
|
||||||
|
static int *resizeArray(int *data, int newsz, int oldsz)
|
||||||
|
{
|
||||||
|
data = realloc(data, newsz*sizeof(int));
|
||||||
|
if (data == NULL)
|
||||||
|
/* out of memory, not much we can do */
|
||||||
|
abort();
|
||||||
|
|
||||||
|
if (newsz > oldsz)
|
||||||
|
memset(data + oldsz, 0, sizeof(int)*(newsz - oldsz));
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Compressor_setHistory(struct Compressor *obj, unsigned int history)
|
||||||
|
{
|
||||||
|
if (!history)
|
||||||
|
history = BUCKETS;
|
||||||
|
|
||||||
|
obj->peaks = resizeArray(obj->peaks, history, obj->bufsz);
|
||||||
|
obj->gain = resizeArray(obj->gain, history, obj->bufsz);
|
||||||
|
obj->clipped = resizeArray(obj->clipped, history, obj->bufsz);
|
||||||
|
obj->bufsz = history;
|
||||||
|
}
|
||||||
|
|
||||||
|
struct CompressorConfig *Compressor_getConfig(struct Compressor *obj)
|
||||||
|
{
|
||||||
|
return &obj->prefs;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Compressor_Process_int16(struct Compressor *obj, int16_t *audio,
|
||||||
|
unsigned int count)
|
||||||
|
{
|
||||||
|
struct CompressorConfig *prefs = Compressor_getConfig(obj);
|
||||||
|
int16_t *ap;
|
||||||
|
unsigned int i;
|
||||||
|
int *peaks = obj->peaks;
|
||||||
|
int curGain = obj->gain[obj->pos];
|
||||||
|
int newGain;
|
||||||
|
int peakVal = 1;
|
||||||
|
int peakPos = 0;
|
||||||
|
int slot = (obj->pos + 1) % obj->bufsz;
|
||||||
|
int *clipped = obj->clipped + slot;
|
||||||
|
unsigned int ramp = count;
|
||||||
|
int delta;
|
||||||
|
|
||||||
|
ap = audio;
|
||||||
|
for (i = 0; i < count; i++)
|
||||||
|
{
|
||||||
|
int val = *ap++;
|
||||||
|
if (val < 0)
|
||||||
|
val = -val;
|
||||||
|
if (val > peakVal)
|
||||||
|
{
|
||||||
|
peakVal = val;
|
||||||
|
peakPos = i;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
peaks[slot] = peakVal;
|
||||||
|
|
||||||
|
|
||||||
|
for (i = 0; i < obj->bufsz; i++)
|
||||||
|
{
|
||||||
|
if (peaks[i] > peakVal)
|
||||||
|
{
|
||||||
|
peakVal = peaks[i];
|
||||||
|
peakPos = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//! Determine target gain
|
||||||
|
newGain = (1 << 10)*prefs->target/peakVal;
|
||||||
|
|
||||||
|
//! Adjust the gain with inertia from the previous gain value
|
||||||
|
newGain = (curGain*((1 << prefs->smooth) - 1) + newGain)
|
||||||
|
>> prefs->smooth;
|
||||||
|
|
||||||
|
//! Make sure it's no more than the maximum gain value
|
||||||
|
if (newGain > (prefs->maxgain << 10))
|
||||||
|
newGain = prefs->maxgain << 10;
|
||||||
|
|
||||||
|
//! Make sure it's no less than 1:1
|
||||||
|
if (newGain < (1 << 10))
|
||||||
|
newGain = 1 << 10;
|
||||||
|
|
||||||
|
//! Make sure the adjusted gain won't cause clipping
|
||||||
|
if ((peakVal*newGain >> 10) > 32767)
|
||||||
|
{
|
||||||
|
newGain = (32767 << 10)/peakVal;
|
||||||
|
//! Truncate the ramp time
|
||||||
|
ramp = peakPos;
|
||||||
|
}
|
||||||
|
|
||||||
|
//! Record the new gain
|
||||||
|
obj->gain[slot] = newGain;
|
||||||
|
|
||||||
|
if (!ramp)
|
||||||
|
ramp = 1;
|
||||||
|
if (!curGain)
|
||||||
|
curGain = 1 << 10;
|
||||||
|
delta = (newGain - curGain) / (int)ramp;
|
||||||
|
|
||||||
|
ap = audio;
|
||||||
|
*clipped = 0;
|
||||||
|
for (i = 0; i < count; i++)
|
||||||
|
{
|
||||||
|
int sample;
|
||||||
|
|
||||||
|
//! Amplify the sample
|
||||||
|
sample = *ap*curGain >> 10;
|
||||||
|
if (sample < -32768)
|
||||||
|
{
|
||||||
|
*clipped += -32768 - sample;
|
||||||
|
sample = -32768;
|
||||||
|
} else if (sample > 32767)
|
||||||
|
{
|
||||||
|
*clipped += sample - 32767;
|
||||||
|
sample = 32767;
|
||||||
|
}
|
||||||
|
*ap++ = sample;
|
||||||
|
|
||||||
|
//! Adjust the gain
|
||||||
|
if (i < ramp)
|
||||||
|
curGain += delta;
|
||||||
|
else
|
||||||
|
curGain = newGain;
|
||||||
|
}
|
||||||
|
|
||||||
|
obj->pos = slot;
|
||||||
|
}
|
||||||
|
|
||||||
40
src/AudioCompress/compress.h
Normal file
40
src/AudioCompress/compress.h
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
/*! compress.h
|
||||||
|
* interface to audio compression
|
||||||
|
*
|
||||||
|
* (c)2007 busybee (http://beesbuzz.biz/)
|
||||||
|
* Licensed under the terms of the LGPL. See the file COPYING for details.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef COMPRESS_H
|
||||||
|
#define COMPRESS_H
|
||||||
|
|
||||||
|
#include <stdint.h>
|
||||||
|
|
||||||
|
//! Configuration values for the compressor object
|
||||||
|
struct CompressorConfig {
|
||||||
|
int target;
|
||||||
|
int maxgain;
|
||||||
|
int smooth;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct Compressor;
|
||||||
|
|
||||||
|
//! Create a new compressor (use history value of 0 for default)
|
||||||
|
struct Compressor *Compressor_new(unsigned int history);
|
||||||
|
|
||||||
|
//! Delete a compressor
|
||||||
|
void Compressor_delete(struct Compressor *);
|
||||||
|
|
||||||
|
//! Set the history length
|
||||||
|
void Compressor_setHistory(struct Compressor *, unsigned int history);
|
||||||
|
|
||||||
|
//! Get the configuration for a compressor
|
||||||
|
struct CompressorConfig *Compressor_getConfig(struct Compressor *);
|
||||||
|
|
||||||
|
//! Process 16-bit signed data
|
||||||
|
void Compressor_Process_int16(struct Compressor *, int16_t *data, unsigned int count);
|
||||||
|
|
||||||
|
//! TODO: Compressor_Process_int32, Compressor_Process_float, others as needed
|
||||||
|
|
||||||
|
//! TODO: functions for getting at the peak/gain/clip history buffers (for monitoring)
|
||||||
|
#endif
|
||||||
19
src/AudioCompress/config.h
Normal file
19
src/AudioCompress/config.h
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
/* config.h
|
||||||
|
** Default values for the configuration, and also a few random debug things
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef AC_CONFIG_H
|
||||||
|
#define AC_CONFIG_H
|
||||||
|
|
||||||
|
/*** Version information ***/
|
||||||
|
#define ACVERSION "2.0"
|
||||||
|
|
||||||
|
/*** Default configuration stuff ***/
|
||||||
|
#define TARGET 16384 /*!< Target level (on a scale of 0-32767) */
|
||||||
|
|
||||||
|
#define GAINMAX 32 /*!< The maximum amount to amplify by */
|
||||||
|
#define GAINSMOOTH 8 /*!< How much inertia ramping has*/
|
||||||
|
#define BUCKETS 400 /*!< How long of a history to use by default */
|
||||||
|
|
||||||
|
#endif
|
||||||
|
|
||||||
14
src/ack.h
14
src/ack.h
@@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright (C) 2003-2009 The Music Player Daemon Project
|
* Copyright (C) 2003-2011 The Music Player Daemon Project
|
||||||
* http://www.musicpd.org
|
* http://www.musicpd.org
|
||||||
*
|
*
|
||||||
* This program is free software; you can redistribute it and/or modify
|
* This program is free software; you can redistribute it and/or modify
|
||||||
@@ -20,6 +20,8 @@
|
|||||||
#ifndef MPD_ACK_H
|
#ifndef MPD_ACK_H
|
||||||
#define MPD_ACK_H
|
#define MPD_ACK_H
|
||||||
|
|
||||||
|
#include <glib.h>
|
||||||
|
|
||||||
enum ack {
|
enum ack {
|
||||||
ACK_ERROR_NOT_LIST = 1,
|
ACK_ERROR_NOT_LIST = 1,
|
||||||
ACK_ERROR_ARG = 2,
|
ACK_ERROR_ARG = 2,
|
||||||
@@ -36,4 +38,14 @@ enum ack {
|
|||||||
ACK_ERROR_EXIST = 56,
|
ACK_ERROR_EXIST = 56,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Quark for GError.domain; the code is an enum #ack.
|
||||||
|
*/
|
||||||
|
G_GNUC_CONST
|
||||||
|
static inline GQuark
|
||||||
|
ack_quark(void)
|
||||||
|
{
|
||||||
|
return g_quark_from_static_string("ack");
|
||||||
|
}
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright (C) 2003-2009 The Music Player Daemon Project
|
* Copyright (C) 2003-2011 The Music Player Daemon Project
|
||||||
* http://www.musicpd.org
|
* http://www.musicpd.org
|
||||||
*
|
*
|
||||||
* This program is free software; you can redistribute it and/or modify
|
* This program is free software; you can redistribute it and/or modify
|
||||||
@@ -17,6 +17,7 @@
|
|||||||
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
#include "config.h" /* must be first for large file support */
|
||||||
#include "aiff.h"
|
#include "aiff.h"
|
||||||
|
|
||||||
#include <glib.h>
|
#include <glib.h>
|
||||||
@@ -56,7 +57,7 @@ aiff_seek_id3(FILE *file)
|
|||||||
ret = fstat(fileno(file), &st);
|
ret = fstat(fileno(file), &st);
|
||||||
if (ret < 0) {
|
if (ret < 0) {
|
||||||
g_warning("Failed to stat file descriptor: %s",
|
g_warning("Failed to stat file descriptor: %s",
|
||||||
strerror(errno));
|
g_strerror(errno));
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -72,7 +73,8 @@ aiff_seek_id3(FILE *file)
|
|||||||
if (size != 1 ||
|
if (size != 1 ||
|
||||||
memcmp(header.id, "FORM", 4) != 0 ||
|
memcmp(header.id, "FORM", 4) != 0 ||
|
||||||
GUINT32_FROM_BE(header.size) > (uint32_t)st.st_size ||
|
GUINT32_FROM_BE(header.size) > (uint32_t)st.st_size ||
|
||||||
memcmp(header.format, "AIFF", 4) != 0)
|
(memcmp(header.format, "AIFF", 4) != 0 &&
|
||||||
|
memcmp(header.format, "AIFC", 4) != 0))
|
||||||
/* not a AIFF file */
|
/* not a AIFF file */
|
||||||
return 0;
|
return 0;
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright (C) 2003-2009 The Music Player Daemon Project
|
* Copyright (C) 2003-2011 The Music Player Daemon Project
|
||||||
* http://www.musicpd.org
|
* http://www.musicpd.org
|
||||||
*
|
*
|
||||||
* This program is free software; you can redistribute it and/or modify
|
* This program is free software; you can redistribute it and/or modify
|
||||||
|
|||||||
115
src/ape.c
Normal file
115
src/ape.c
Normal file
@@ -0,0 +1,115 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2003-2011 The Music Player Daemon Project
|
||||||
|
* http://www.musicpd.org
|
||||||
|
*
|
||||||
|
* This program is free software; you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation; either version 2 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License along
|
||||||
|
* with this program; if not, write to the Free Software Foundation, Inc.,
|
||||||
|
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "config.h"
|
||||||
|
#include "ape.h"
|
||||||
|
|
||||||
|
#include <glib.h>
|
||||||
|
|
||||||
|
#include <stdint.h>
|
||||||
|
#include <assert.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
|
struct ape_footer {
|
||||||
|
unsigned char id[8];
|
||||||
|
uint32_t version;
|
||||||
|
uint32_t length;
|
||||||
|
uint32_t count;
|
||||||
|
unsigned char flags[4];
|
||||||
|
unsigned char reserved[8];
|
||||||
|
};
|
||||||
|
|
||||||
|
static bool
|
||||||
|
ape_scan_internal(FILE *fp, tag_ape_callback_t callback, void *ctx)
|
||||||
|
{
|
||||||
|
/* determine if file has an apeV2 tag */
|
||||||
|
struct ape_footer footer;
|
||||||
|
if (fseek(fp, -(long)sizeof(footer), SEEK_END) ||
|
||||||
|
fread(&footer, 1, sizeof(footer), fp) != sizeof(footer) ||
|
||||||
|
memcmp(footer.id, "APETAGEX", sizeof(footer.id)) != 0 ||
|
||||||
|
GUINT32_FROM_LE(footer.version) != 2000)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
/* find beginning of ape tag */
|
||||||
|
size_t remaining = GUINT32_FROM_LE(footer.length);
|
||||||
|
if (remaining <= sizeof(footer) + 10 ||
|
||||||
|
/* refuse to load more than one megabyte of tag data */
|
||||||
|
remaining > 1024 * 1024 ||
|
||||||
|
fseek(fp, -(long)remaining, SEEK_END))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
/* read tag into buffer */
|
||||||
|
remaining -= sizeof(footer);
|
||||||
|
assert(remaining > 10);
|
||||||
|
|
||||||
|
char *buffer = g_malloc(remaining);
|
||||||
|
if (fread(buffer, 1, remaining, fp) != remaining) {
|
||||||
|
g_free(buffer);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* read tags */
|
||||||
|
unsigned n = GUINT32_FROM_LE(footer.count);
|
||||||
|
const char *p = buffer;
|
||||||
|
while (n-- && remaining > 10) {
|
||||||
|
size_t size = GUINT32_FROM_LE(*(const uint32_t *)p);
|
||||||
|
p += 4;
|
||||||
|
remaining -= 4;
|
||||||
|
unsigned long flags = GUINT32_FROM_LE(*(const uint32_t *)p);
|
||||||
|
p += 4;
|
||||||
|
remaining -= 4;
|
||||||
|
|
||||||
|
/* get the key */
|
||||||
|
const char *key = p;
|
||||||
|
while (remaining > size && *p != '\0') {
|
||||||
|
p++;
|
||||||
|
remaining--;
|
||||||
|
}
|
||||||
|
p++;
|
||||||
|
remaining--;
|
||||||
|
|
||||||
|
/* get the value */
|
||||||
|
if (remaining < size)
|
||||||
|
break;
|
||||||
|
|
||||||
|
if (!callback(flags, key, p, size, ctx))
|
||||||
|
break;
|
||||||
|
|
||||||
|
p += size;
|
||||||
|
remaining -= size;
|
||||||
|
}
|
||||||
|
|
||||||
|
g_free(buffer);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool
|
||||||
|
tag_ape_scan(const char *path_fs, tag_ape_callback_t callback, void *ctx)
|
||||||
|
{
|
||||||
|
FILE *fp;
|
||||||
|
|
||||||
|
fp = fopen(path_fs, "rb");
|
||||||
|
if (fp == NULL)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
bool success = ape_scan_internal(fp, callback, ctx);
|
||||||
|
fclose(fp);
|
||||||
|
return success;
|
||||||
|
}
|
||||||
42
src/ape.h
Normal file
42
src/ape.h
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2003-2011 The Music Player Daemon Project
|
||||||
|
* http://www.musicpd.org
|
||||||
|
*
|
||||||
|
* This program is free software; you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation; either version 2 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License along
|
||||||
|
* with this program; if not, write to the Free Software Foundation, Inc.,
|
||||||
|
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef MPD_APE_H
|
||||||
|
#define MPD_APE_H
|
||||||
|
|
||||||
|
#include "check.h"
|
||||||
|
|
||||||
|
#include <stdbool.h>
|
||||||
|
#include <stddef.h>
|
||||||
|
|
||||||
|
typedef bool (*tag_ape_callback_t)(unsigned long flags, const char *key,
|
||||||
|
const char *value, size_t value_length,
|
||||||
|
void *ctx);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Scans the APE tag values from a file.
|
||||||
|
*
|
||||||
|
* @param path_fs the path of the file in filesystem encoding
|
||||||
|
* @return false if the file could not be opened or if no APE tag is
|
||||||
|
* present
|
||||||
|
*/
|
||||||
|
bool
|
||||||
|
tag_ape_scan(const char *path_fs, tag_ape_callback_t callback, void *ctx);
|
||||||
|
|
||||||
|
#endif
|
||||||
314
src/archive/bz2_archive_plugin.c
Normal file
314
src/archive/bz2_archive_plugin.c
Normal file
@@ -0,0 +1,314 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2003-2011 The Music Player Daemon Project
|
||||||
|
* http://www.musicpd.org
|
||||||
|
*
|
||||||
|
* This program is free software; you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation; either version 2 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License along
|
||||||
|
* with this program; if not, write to the Free Software Foundation, Inc.,
|
||||||
|
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* single bz2 archive handling (requires libbz2)
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "config.h"
|
||||||
|
#include "archive/bz2_archive_plugin.h"
|
||||||
|
#include "archive_api.h"
|
||||||
|
#include "input_internal.h"
|
||||||
|
#include "input_plugin.h"
|
||||||
|
#include "refcount.h"
|
||||||
|
|
||||||
|
#include <stdint.h>
|
||||||
|
#include <stddef.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <glib.h>
|
||||||
|
#include <bzlib.h>
|
||||||
|
|
||||||
|
#ifdef HAVE_OLDER_BZIP2
|
||||||
|
#define BZ2_bzDecompressInit bzDecompressInit
|
||||||
|
#define BZ2_bzDecompress bzDecompress
|
||||||
|
#endif
|
||||||
|
|
||||||
|
struct bz2_archive_file {
|
||||||
|
struct archive_file base;
|
||||||
|
|
||||||
|
struct refcount ref;
|
||||||
|
|
||||||
|
char *name;
|
||||||
|
bool reset;
|
||||||
|
struct input_stream *istream;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct bz2_input_stream {
|
||||||
|
struct input_stream base;
|
||||||
|
|
||||||
|
struct bz2_archive_file *archive;
|
||||||
|
|
||||||
|
bool eof;
|
||||||
|
|
||||||
|
bz_stream bzstream;
|
||||||
|
|
||||||
|
char buffer[5000];
|
||||||
|
};
|
||||||
|
|
||||||
|
static const struct input_plugin bz2_inputplugin;
|
||||||
|
|
||||||
|
static inline GQuark
|
||||||
|
bz2_quark(void)
|
||||||
|
{
|
||||||
|
return g_quark_from_static_string("bz2");
|
||||||
|
}
|
||||||
|
|
||||||
|
/* single archive handling allocation helpers */
|
||||||
|
|
||||||
|
static bool
|
||||||
|
bz2_alloc(struct bz2_input_stream *data, GError **error_r)
|
||||||
|
{
|
||||||
|
int ret;
|
||||||
|
|
||||||
|
data->bzstream.bzalloc = NULL;
|
||||||
|
data->bzstream.bzfree = NULL;
|
||||||
|
data->bzstream.opaque = NULL;
|
||||||
|
|
||||||
|
data->bzstream.next_in = (void *) data->buffer;
|
||||||
|
data->bzstream.avail_in = 0;
|
||||||
|
|
||||||
|
ret = BZ2_bzDecompressInit(&data->bzstream, 0, 0);
|
||||||
|
if (ret != BZ_OK) {
|
||||||
|
g_free(data);
|
||||||
|
|
||||||
|
g_set_error(error_r, bz2_quark(), ret,
|
||||||
|
"BZ2_bzDecompressInit() has failed");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
bz2_destroy(struct bz2_input_stream *data)
|
||||||
|
{
|
||||||
|
BZ2_bzDecompressEnd(&data->bzstream);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* archive open && listing routine */
|
||||||
|
|
||||||
|
#if GCC_CHECK_VERSION(4, 2)
|
||||||
|
/* workaround for a warning caused by G_STATIC_MUTEX_INIT */
|
||||||
|
#pragma GCC diagnostic ignored "-Wmissing-field-initializers"
|
||||||
|
#endif
|
||||||
|
|
||||||
|
static struct archive_file *
|
||||||
|
bz2_open(const char *pathname, GError **error_r)
|
||||||
|
{
|
||||||
|
struct bz2_archive_file *context;
|
||||||
|
int len;
|
||||||
|
|
||||||
|
context = g_malloc(sizeof(*context));
|
||||||
|
archive_file_init(&context->base, &bz2_archive_plugin);
|
||||||
|
refcount_init(&context->ref);
|
||||||
|
|
||||||
|
//open archive
|
||||||
|
static GStaticMutex mutex = G_STATIC_MUTEX_INIT;
|
||||||
|
context->istream = input_stream_open(pathname,
|
||||||
|
g_static_mutex_get_mutex(&mutex),
|
||||||
|
NULL,
|
||||||
|
error_r);
|
||||||
|
if (context->istream == NULL) {
|
||||||
|
g_free(context);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
context->name = g_path_get_basename(pathname);
|
||||||
|
|
||||||
|
//remove suffix
|
||||||
|
len = strlen(context->name);
|
||||||
|
if (len > 4) {
|
||||||
|
context->name[len - 4] = 0; //remove .bz2 suffix
|
||||||
|
}
|
||||||
|
|
||||||
|
return &context->base;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
bz2_scan_reset(struct archive_file *file)
|
||||||
|
{
|
||||||
|
struct bz2_archive_file *context = (struct bz2_archive_file *) file;
|
||||||
|
context->reset = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
static char *
|
||||||
|
bz2_scan_next(struct archive_file *file)
|
||||||
|
{
|
||||||
|
struct bz2_archive_file *context = (struct bz2_archive_file *) file;
|
||||||
|
char *name = NULL;
|
||||||
|
|
||||||
|
if (context->reset) {
|
||||||
|
name = context->name;
|
||||||
|
context->reset = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return name;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
bz2_close(struct archive_file *file)
|
||||||
|
{
|
||||||
|
struct bz2_archive_file *context = (struct bz2_archive_file *) file;
|
||||||
|
|
||||||
|
if (!refcount_dec(&context->ref))
|
||||||
|
return;
|
||||||
|
|
||||||
|
g_free(context->name);
|
||||||
|
|
||||||
|
input_stream_close(context->istream);
|
||||||
|
g_free(context);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* single archive handling */
|
||||||
|
|
||||||
|
static struct input_stream *
|
||||||
|
bz2_open_stream(struct archive_file *file, const char *path,
|
||||||
|
GMutex *mutex, GCond *cond,
|
||||||
|
GError **error_r)
|
||||||
|
{
|
||||||
|
struct bz2_archive_file *context = (struct bz2_archive_file *) file;
|
||||||
|
struct bz2_input_stream *bis = g_new(struct bz2_input_stream, 1);
|
||||||
|
|
||||||
|
input_stream_init(&bis->base, &bz2_inputplugin, path,
|
||||||
|
mutex, cond);
|
||||||
|
|
||||||
|
bis->archive = context;
|
||||||
|
|
||||||
|
bis->base.ready = true;
|
||||||
|
bis->base.seekable = false;
|
||||||
|
|
||||||
|
if (!bz2_alloc(bis, error_r)) {
|
||||||
|
input_stream_deinit(&bis->base);
|
||||||
|
g_free(bis);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
bis->eof = false;
|
||||||
|
|
||||||
|
refcount_inc(&context->ref);
|
||||||
|
|
||||||
|
return &bis->base;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
bz2_is_close(struct input_stream *is)
|
||||||
|
{
|
||||||
|
struct bz2_input_stream *bis = (struct bz2_input_stream *)is;
|
||||||
|
|
||||||
|
bz2_destroy(bis);
|
||||||
|
|
||||||
|
bz2_close(&bis->archive->base);
|
||||||
|
|
||||||
|
input_stream_deinit(&bis->base);
|
||||||
|
g_free(bis);
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool
|
||||||
|
bz2_fillbuffer(struct bz2_input_stream *bis, GError **error_r)
|
||||||
|
{
|
||||||
|
size_t count;
|
||||||
|
bz_stream *bzstream;
|
||||||
|
|
||||||
|
bzstream = &bis->bzstream;
|
||||||
|
|
||||||
|
if (bzstream->avail_in > 0)
|
||||||
|
return true;
|
||||||
|
|
||||||
|
count = input_stream_read(bis->archive->istream,
|
||||||
|
bis->buffer, sizeof(bis->buffer),
|
||||||
|
error_r);
|
||||||
|
if (count == 0)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
bzstream->next_in = bis->buffer;
|
||||||
|
bzstream->avail_in = count;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
static size_t
|
||||||
|
bz2_is_read(struct input_stream *is, void *ptr, size_t length,
|
||||||
|
GError **error_r)
|
||||||
|
{
|
||||||
|
struct bz2_input_stream *bis = (struct bz2_input_stream *)is;
|
||||||
|
bz_stream *bzstream;
|
||||||
|
int bz_result;
|
||||||
|
size_t nbytes = 0;
|
||||||
|
|
||||||
|
if (bis->eof)
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
bzstream = &bis->bzstream;
|
||||||
|
bzstream->next_out = ptr;
|
||||||
|
bzstream->avail_out = length;
|
||||||
|
|
||||||
|
do {
|
||||||
|
if (!bz2_fillbuffer(bis, error_r))
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
bz_result = BZ2_bzDecompress(bzstream);
|
||||||
|
|
||||||
|
if (bz_result == BZ_STREAM_END) {
|
||||||
|
bis->eof = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (bz_result != BZ_OK) {
|
||||||
|
g_set_error(error_r, bz2_quark(), bz_result,
|
||||||
|
"BZ2_bzDecompress() has failed");
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
} while (bzstream->avail_out == length);
|
||||||
|
|
||||||
|
nbytes = length - bzstream->avail_out;
|
||||||
|
is->offset += nbytes;
|
||||||
|
|
||||||
|
return nbytes;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool
|
||||||
|
bz2_is_eof(struct input_stream *is)
|
||||||
|
{
|
||||||
|
struct bz2_input_stream *bis = (struct bz2_input_stream *)is;
|
||||||
|
|
||||||
|
return bis->eof;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* exported structures */
|
||||||
|
|
||||||
|
static const char *const bz2_extensions[] = {
|
||||||
|
"bz2",
|
||||||
|
NULL
|
||||||
|
};
|
||||||
|
|
||||||
|
static const struct input_plugin bz2_inputplugin = {
|
||||||
|
.close = bz2_is_close,
|
||||||
|
.read = bz2_is_read,
|
||||||
|
.eof = bz2_is_eof,
|
||||||
|
};
|
||||||
|
|
||||||
|
const struct archive_plugin bz2_archive_plugin = {
|
||||||
|
.name = "bz2",
|
||||||
|
.open = bz2_open,
|
||||||
|
.scan_reset = bz2_scan_reset,
|
||||||
|
.scan_next = bz2_scan_next,
|
||||||
|
.open_stream = bz2_open_stream,
|
||||||
|
.close = bz2_close,
|
||||||
|
.suffixes = bz2_extensions
|
||||||
|
};
|
||||||
|
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright (C) 2003-2009 The Music Player Daemon Project
|
* Copyright (C) 2003-2011 The Music Player Daemon Project
|
||||||
* http://www.musicpd.org
|
* http://www.musicpd.org
|
||||||
*
|
*
|
||||||
* This program is free software; you can redistribute it and/or modify
|
* This program is free software; you can redistribute it and/or modify
|
||||||
@@ -17,9 +17,9 @@
|
|||||||
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#ifndef LASTFM_INPUT_PLUGIN_H
|
#ifndef MPD_ARCHIVE_BZ2_H
|
||||||
#define LASTFM_INPUT_PLUGIN_H
|
#define MPD_ARCHIVE_BZ2_H
|
||||||
|
|
||||||
extern const struct input_plugin lastfm_input_plugin;
|
extern const struct archive_plugin bz2_archive_plugin;
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
@@ -1,284 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (C) 2003-2009 The Music Player Daemon Project
|
|
||||||
* http://www.musicpd.org
|
|
||||||
*
|
|
||||||
* This program is free software; you can redistribute it and/or modify
|
|
||||||
* it under the terms of the GNU General Public License as published by
|
|
||||||
* the Free Software Foundation; either version 2 of the License, or
|
|
||||||
* (at your option) any later version.
|
|
||||||
*
|
|
||||||
* This program is distributed in the hope that it will be useful,
|
|
||||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
* GNU General Public License for more details.
|
|
||||||
*
|
|
||||||
* You should have received a copy of the GNU General Public License along
|
|
||||||
* with this program; if not, write to the Free Software Foundation, Inc.,
|
|
||||||
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
|
||||||
* single bz2 archive handling (requires libbz2)
|
|
||||||
*/
|
|
||||||
|
|
||||||
#include "archive_api.h"
|
|
||||||
#include "input_plugin.h"
|
|
||||||
#include "config.h"
|
|
||||||
|
|
||||||
#include <stdint.h>
|
|
||||||
#include <stddef.h>
|
|
||||||
#include <string.h>
|
|
||||||
#include <glib.h>
|
|
||||||
#include <bzlib.h>
|
|
||||||
|
|
||||||
#ifdef HAVE_OLDER_BZIP2
|
|
||||||
#define BZ2_bzDecompressInit bzDecompressInit
|
|
||||||
#define BZ2_bzDecompress bzDecompress
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#define BZ_BUFSIZE 5000
|
|
||||||
|
|
||||||
typedef struct {
|
|
||||||
char *name;
|
|
||||||
bool reset;
|
|
||||||
struct input_stream istream;
|
|
||||||
int last_bz_result;
|
|
||||||
int last_parent_result;
|
|
||||||
bz_stream bzstream;
|
|
||||||
char *buffer;
|
|
||||||
} bz2_context;
|
|
||||||
|
|
||||||
|
|
||||||
static const struct input_plugin bz2_inputplugin;
|
|
||||||
|
|
||||||
/* single archive handling allocation helpers */
|
|
||||||
|
|
||||||
static bool
|
|
||||||
bz2_alloc(bz2_context *data)
|
|
||||||
{
|
|
||||||
data->bzstream.bzalloc = NULL;
|
|
||||||
data->bzstream.bzfree = NULL;
|
|
||||||
data->bzstream.opaque = NULL;
|
|
||||||
|
|
||||||
data->buffer = g_malloc(BZ_BUFSIZE);
|
|
||||||
data->bzstream.next_in = (void *) data->buffer;
|
|
||||||
data->bzstream.avail_in = 0;
|
|
||||||
|
|
||||||
if (BZ2_bzDecompressInit(&data->bzstream, 0, 0) != BZ_OK) {
|
|
||||||
g_free(data->buffer);
|
|
||||||
g_free(data);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
data->last_bz_result = BZ_OK;
|
|
||||||
data->last_parent_result = 0;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
static void
|
|
||||||
bz2_destroy(bz2_context *data)
|
|
||||||
{
|
|
||||||
BZ2_bzDecompressEnd(&data->bzstream);
|
|
||||||
g_free(data->buffer);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* archive open && listing routine */
|
|
||||||
|
|
||||||
static struct archive_file *
|
|
||||||
bz2_open(char * pathname)
|
|
||||||
{
|
|
||||||
bz2_context *context;
|
|
||||||
char *name;
|
|
||||||
int len;
|
|
||||||
|
|
||||||
context = g_malloc(sizeof(bz2_context));
|
|
||||||
if (!context) {
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
//open archive
|
|
||||||
if (!input_stream_open(&context->istream, pathname)) {
|
|
||||||
g_warning("failed to open an bzip2 archive %s\n",pathname);
|
|
||||||
g_free(context);
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
//capture filename
|
|
||||||
name = strrchr(pathname, '/');
|
|
||||||
if (name == NULL) {
|
|
||||||
g_warning("failed to get bzip2 name from %s\n",pathname);
|
|
||||||
g_free(context);
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
context->name = g_strdup(name+1);
|
|
||||||
//remove suffix
|
|
||||||
len = strlen(context->name);
|
|
||||||
if (len > 4) {
|
|
||||||
context->name[len-4] = 0; //remove .bz2 suffix
|
|
||||||
}
|
|
||||||
return (struct archive_file *) context;
|
|
||||||
}
|
|
||||||
|
|
||||||
static void
|
|
||||||
bz2_scan_reset(struct archive_file *file)
|
|
||||||
{
|
|
||||||
bz2_context *context = (bz2_context *) file;
|
|
||||||
context->reset = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
static char *
|
|
||||||
bz2_scan_next(struct archive_file *file)
|
|
||||||
{
|
|
||||||
bz2_context *context = (bz2_context *) file;
|
|
||||||
char *name = NULL;
|
|
||||||
if (context->reset) {
|
|
||||||
name = context->name;
|
|
||||||
context->reset = false;
|
|
||||||
}
|
|
||||||
return name;
|
|
||||||
}
|
|
||||||
|
|
||||||
static void
|
|
||||||
bz2_close(struct archive_file *file)
|
|
||||||
{
|
|
||||||
bz2_context *context = (bz2_context *) file;
|
|
||||||
|
|
||||||
g_free(context->name);
|
|
||||||
|
|
||||||
input_stream_close(&context->istream);
|
|
||||||
g_free(context);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* single archive handling */
|
|
||||||
|
|
||||||
static bool
|
|
||||||
bz2_open_stream(struct archive_file *file, struct input_stream *is,
|
|
||||||
G_GNUC_UNUSED const char *path)
|
|
||||||
{
|
|
||||||
bz2_context *context = (bz2_context *) file;
|
|
||||||
//setup file ops
|
|
||||||
is->plugin = &bz2_inputplugin;
|
|
||||||
//insert back reference
|
|
||||||
is->data = context;
|
|
||||||
is->seekable = false;
|
|
||||||
|
|
||||||
if (!bz2_alloc(context)) {
|
|
||||||
g_warning("alloc bz2 failed\n");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
static void
|
|
||||||
bz2_is_close(struct input_stream *is)
|
|
||||||
{
|
|
||||||
bz2_context *context = (bz2_context *) is->data;
|
|
||||||
bz2_destroy(context);
|
|
||||||
is->data = NULL;
|
|
||||||
|
|
||||||
bz2_close((struct archive_file *)context);
|
|
||||||
}
|
|
||||||
|
|
||||||
static int
|
|
||||||
bz2_fillbuffer(bz2_context *context,
|
|
||||||
size_t numBytes)
|
|
||||||
{
|
|
||||||
size_t count;
|
|
||||||
bz_stream *bzstream;
|
|
||||||
|
|
||||||
bzstream = &context->bzstream;
|
|
||||||
|
|
||||||
if (bzstream->avail_in > 0)
|
|
||||||
return 0;
|
|
||||||
|
|
||||||
count = input_stream_read(&context->istream,
|
|
||||||
context->buffer, BZ_BUFSIZE);
|
|
||||||
|
|
||||||
if (count == 0) {
|
|
||||||
if (bzstream->avail_out == numBytes)
|
|
||||||
return -1;
|
|
||||||
if (!input_stream_eof(&context->istream))
|
|
||||||
context->last_parent_result = 1;
|
|
||||||
} else {
|
|
||||||
bzstream->next_in = context->buffer;
|
|
||||||
bzstream->avail_in = count;
|
|
||||||
}
|
|
||||||
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
static size_t
|
|
||||||
bz2_is_read(struct input_stream *is, void *ptr, size_t size)
|
|
||||||
{
|
|
||||||
bz2_context *context = (bz2_context *) is->data;
|
|
||||||
bz_stream *bzstream;
|
|
||||||
int bz_result;
|
|
||||||
size_t numBytes = size;
|
|
||||||
size_t bytesRead = 0;
|
|
||||||
|
|
||||||
if (context->last_bz_result != BZ_OK)
|
|
||||||
return 0;
|
|
||||||
if (context->last_parent_result != 0)
|
|
||||||
return 0;
|
|
||||||
|
|
||||||
bzstream = &context->bzstream;
|
|
||||||
bzstream->next_out = ptr;
|
|
||||||
bzstream->avail_out = numBytes;
|
|
||||||
|
|
||||||
while (bzstream->avail_out != 0) {
|
|
||||||
if (bz2_fillbuffer(context, numBytes) != 0)
|
|
||||||
break;
|
|
||||||
|
|
||||||
bz_result = BZ2_bzDecompress(bzstream);
|
|
||||||
|
|
||||||
if (context->last_bz_result != BZ_OK
|
|
||||||
&& bzstream->avail_out == numBytes) {
|
|
||||||
context->last_bz_result = bz_result;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (bz_result == BZ_STREAM_END) {
|
|
||||||
context->last_bz_result = bz_result;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
bytesRead = numBytes - bzstream->avail_out;
|
|
||||||
is->offset += bytesRead;
|
|
||||||
|
|
||||||
return bytesRead;
|
|
||||||
}
|
|
||||||
|
|
||||||
static bool
|
|
||||||
bz2_is_eof(struct input_stream *is)
|
|
||||||
{
|
|
||||||
bz2_context *context = (bz2_context *) is->data;
|
|
||||||
|
|
||||||
if (context->last_bz_result == BZ_STREAM_END) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* exported structures */
|
|
||||||
|
|
||||||
static const char *const bz2_extensions[] = {
|
|
||||||
"bz2",
|
|
||||||
NULL
|
|
||||||
};
|
|
||||||
|
|
||||||
static const struct input_plugin bz2_inputplugin = {
|
|
||||||
.close = bz2_is_close,
|
|
||||||
.read = bz2_is_read,
|
|
||||||
.eof = bz2_is_eof,
|
|
||||||
};
|
|
||||||
|
|
||||||
const struct archive_plugin bz2_plugin = {
|
|
||||||
.name = "bz2",
|
|
||||||
.open = bz2_open,
|
|
||||||
.scan_reset = bz2_scan_reset,
|
|
||||||
.scan_next = bz2_scan_next,
|
|
||||||
.open_stream = bz2_open_stream,
|
|
||||||
.close = bz2_close,
|
|
||||||
.suffixes = bz2_extensions
|
|
||||||
};
|
|
||||||
|
|
||||||
290
src/archive/iso9660_archive_plugin.c
Normal file
290
src/archive/iso9660_archive_plugin.c
Normal file
@@ -0,0 +1,290 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2003-2011 The Music Player Daemon Project
|
||||||
|
* http://www.musicpd.org
|
||||||
|
*
|
||||||
|
* This program is free software; you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation; either version 2 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License along
|
||||||
|
* with this program; if not, write to the Free Software Foundation, Inc.,
|
||||||
|
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* iso archive handling (requires cdio, and iso9660)
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "config.h"
|
||||||
|
#include "archive/iso9660_archive_plugin.h"
|
||||||
|
#include "archive_api.h"
|
||||||
|
#include "input_internal.h"
|
||||||
|
#include "input_plugin.h"
|
||||||
|
#include "refcount.h"
|
||||||
|
|
||||||
|
#include <cdio/cdio.h>
|
||||||
|
#include <cdio/iso9660.h>
|
||||||
|
|
||||||
|
#include <glib.h>
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
|
#define CEILING(x, y) ((x+(y-1))/y)
|
||||||
|
|
||||||
|
struct iso9660_archive_file {
|
||||||
|
struct archive_file base;
|
||||||
|
|
||||||
|
struct refcount ref;
|
||||||
|
|
||||||
|
iso9660_t *iso;
|
||||||
|
GSList *list;
|
||||||
|
GSList *iter;
|
||||||
|
};
|
||||||
|
|
||||||
|
static const struct input_plugin iso9660_input_plugin;
|
||||||
|
|
||||||
|
static inline GQuark
|
||||||
|
iso9660_quark(void)
|
||||||
|
{
|
||||||
|
return g_quark_from_static_string("iso9660");
|
||||||
|
}
|
||||||
|
|
||||||
|
/* archive open && listing routine */
|
||||||
|
|
||||||
|
static void
|
||||||
|
listdir_recur(const char *psz_path, struct iso9660_archive_file *context)
|
||||||
|
{
|
||||||
|
iso9660_t *iso = context->iso;
|
||||||
|
CdioList_t *entlist;
|
||||||
|
CdioListNode_t *entnode;
|
||||||
|
iso9660_stat_t *statbuf;
|
||||||
|
char pathname[4096];
|
||||||
|
|
||||||
|
entlist = iso9660_ifs_readdir (iso, psz_path);
|
||||||
|
if (!entlist) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
/* Iterate over the list of nodes that iso9660_ifs_readdir gives */
|
||||||
|
_CDIO_LIST_FOREACH (entnode, entlist) {
|
||||||
|
statbuf = (iso9660_stat_t *) _cdio_list_node_data (entnode);
|
||||||
|
|
||||||
|
strcpy(pathname, psz_path);
|
||||||
|
strcat(pathname, statbuf->filename);
|
||||||
|
|
||||||
|
if (_STAT_DIR == statbuf->type ) {
|
||||||
|
if (strcmp(statbuf->filename, ".") && strcmp(statbuf->filename, "..")) {
|
||||||
|
strcat(pathname, "/");
|
||||||
|
listdir_recur(pathname, context);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
//remove leading /
|
||||||
|
context->list = g_slist_prepend( context->list,
|
||||||
|
g_strdup(pathname + 1));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_cdio_list_free (entlist, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
static struct archive_file *
|
||||||
|
iso9660_archive_open(const char *pathname, GError **error_r)
|
||||||
|
{
|
||||||
|
struct iso9660_archive_file *context =
|
||||||
|
g_new(struct iso9660_archive_file, 1);
|
||||||
|
|
||||||
|
archive_file_init(&context->base, &iso9660_archive_plugin);
|
||||||
|
refcount_init(&context->ref);
|
||||||
|
|
||||||
|
context->list = NULL;
|
||||||
|
|
||||||
|
/* open archive */
|
||||||
|
context->iso = iso9660_open (pathname);
|
||||||
|
if (context->iso == NULL) {
|
||||||
|
g_set_error(error_r, iso9660_quark(), 0,
|
||||||
|
"Failed to open ISO9660 file %s", pathname);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
listdir_recur("/", context);
|
||||||
|
|
||||||
|
return &context->base;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
iso9660_archive_scan_reset(struct archive_file *file)
|
||||||
|
{
|
||||||
|
struct iso9660_archive_file *context =
|
||||||
|
(struct iso9660_archive_file *)file;
|
||||||
|
|
||||||
|
//reset iterator
|
||||||
|
context->iter = context->list;
|
||||||
|
}
|
||||||
|
|
||||||
|
static char *
|
||||||
|
iso9660_archive_scan_next(struct archive_file *file)
|
||||||
|
{
|
||||||
|
struct iso9660_archive_file *context =
|
||||||
|
(struct iso9660_archive_file *)file;
|
||||||
|
|
||||||
|
char *data = NULL;
|
||||||
|
if (context->iter != NULL) {
|
||||||
|
///fetch data and goto next
|
||||||
|
data = context->iter->data;
|
||||||
|
context->iter = g_slist_next(context->iter);
|
||||||
|
}
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
iso9660_archive_close(struct archive_file *file)
|
||||||
|
{
|
||||||
|
struct iso9660_archive_file *context =
|
||||||
|
(struct iso9660_archive_file *)file;
|
||||||
|
GSList *tmp;
|
||||||
|
|
||||||
|
if (!refcount_dec(&context->ref))
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (context->list) {
|
||||||
|
//free list
|
||||||
|
for (tmp = context->list; tmp != NULL; tmp = g_slist_next(tmp))
|
||||||
|
g_free(tmp->data);
|
||||||
|
g_slist_free(context->list);
|
||||||
|
}
|
||||||
|
//close archive
|
||||||
|
iso9660_close(context->iso);
|
||||||
|
|
||||||
|
g_free(context);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* single archive handling */
|
||||||
|
|
||||||
|
struct iso9660_input_stream {
|
||||||
|
struct input_stream base;
|
||||||
|
|
||||||
|
struct iso9660_archive_file *archive;
|
||||||
|
|
||||||
|
iso9660_stat_t *statbuf;
|
||||||
|
size_t max_blocks;
|
||||||
|
};
|
||||||
|
|
||||||
|
static struct input_stream *
|
||||||
|
iso9660_archive_open_stream(struct archive_file *file, const char *pathname,
|
||||||
|
GMutex *mutex, GCond *cond,
|
||||||
|
GError **error_r)
|
||||||
|
{
|
||||||
|
struct iso9660_archive_file *context =
|
||||||
|
(struct iso9660_archive_file *)file;
|
||||||
|
struct iso9660_input_stream *iis;
|
||||||
|
|
||||||
|
iis = g_new(struct iso9660_input_stream, 1);
|
||||||
|
input_stream_init(&iis->base, &iso9660_input_plugin, pathname,
|
||||||
|
mutex, cond);
|
||||||
|
|
||||||
|
iis->archive = context;
|
||||||
|
iis->statbuf = iso9660_ifs_stat_translate(context->iso, pathname);
|
||||||
|
if (iis->statbuf == NULL) {
|
||||||
|
g_free(iis);
|
||||||
|
g_set_error(error_r, iso9660_quark(), 0,
|
||||||
|
"not found in the ISO file: %s", pathname);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
iis->base.ready = true;
|
||||||
|
//we are not seekable
|
||||||
|
iis->base.seekable = false;
|
||||||
|
|
||||||
|
iis->base.size = iis->statbuf->size;
|
||||||
|
|
||||||
|
iis->max_blocks = CEILING(iis->statbuf->size, ISO_BLOCKSIZE);
|
||||||
|
|
||||||
|
refcount_inc(&context->ref);
|
||||||
|
|
||||||
|
return &iis->base;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
iso9660_input_close(struct input_stream *is)
|
||||||
|
{
|
||||||
|
struct iso9660_input_stream *iis = (struct iso9660_input_stream *)is;
|
||||||
|
|
||||||
|
g_free(iis->statbuf);
|
||||||
|
|
||||||
|
iso9660_archive_close(&iis->archive->base);
|
||||||
|
|
||||||
|
input_stream_deinit(&iis->base);
|
||||||
|
g_free(iis);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
static size_t
|
||||||
|
iso9660_input_read(struct input_stream *is, void *ptr, size_t size, GError **error_r)
|
||||||
|
{
|
||||||
|
struct iso9660_input_stream *iis = (struct iso9660_input_stream *)is;
|
||||||
|
int toread, readed = 0;
|
||||||
|
int no_blocks, cur_block;
|
||||||
|
size_t left_bytes = iis->statbuf->size - is->offset;
|
||||||
|
|
||||||
|
size = (size * ISO_BLOCKSIZE) / ISO_BLOCKSIZE;
|
||||||
|
|
||||||
|
if (left_bytes < size) {
|
||||||
|
toread = left_bytes;
|
||||||
|
no_blocks = CEILING(left_bytes,ISO_BLOCKSIZE);
|
||||||
|
} else {
|
||||||
|
toread = size;
|
||||||
|
no_blocks = toread / ISO_BLOCKSIZE;
|
||||||
|
}
|
||||||
|
if (no_blocks > 0) {
|
||||||
|
|
||||||
|
cur_block = is->offset / ISO_BLOCKSIZE;
|
||||||
|
|
||||||
|
readed = iso9660_iso_seek_read (iis->archive->iso, ptr,
|
||||||
|
iis->statbuf->lsn + cur_block, no_blocks);
|
||||||
|
|
||||||
|
if (readed != no_blocks * ISO_BLOCKSIZE) {
|
||||||
|
g_set_error(error_r, iso9660_quark(), 0,
|
||||||
|
"error reading ISO file at lsn %lu",
|
||||||
|
(long unsigned int) cur_block);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
if (left_bytes < size) {
|
||||||
|
readed = left_bytes;
|
||||||
|
}
|
||||||
|
|
||||||
|
is->offset += readed;
|
||||||
|
}
|
||||||
|
return readed;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool
|
||||||
|
iso9660_input_eof(struct input_stream *is)
|
||||||
|
{
|
||||||
|
return is->offset == is->size;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* exported structures */
|
||||||
|
|
||||||
|
static const char *const iso9660_archive_extensions[] = {
|
||||||
|
"iso",
|
||||||
|
NULL
|
||||||
|
};
|
||||||
|
|
||||||
|
static const struct input_plugin iso9660_input_plugin = {
|
||||||
|
.close = iso9660_input_close,
|
||||||
|
.read = iso9660_input_read,
|
||||||
|
.eof = iso9660_input_eof,
|
||||||
|
};
|
||||||
|
|
||||||
|
const struct archive_plugin iso9660_archive_plugin = {
|
||||||
|
.name = "iso",
|
||||||
|
.open = iso9660_archive_open,
|
||||||
|
.scan_reset = iso9660_archive_scan_reset,
|
||||||
|
.scan_next = iso9660_archive_scan_next,
|
||||||
|
.open_stream = iso9660_archive_open_stream,
|
||||||
|
.close = iso9660_archive_close,
|
||||||
|
.suffixes = iso9660_archive_extensions
|
||||||
|
};
|
||||||
25
src/archive/iso9660_archive_plugin.h
Normal file
25
src/archive/iso9660_archive_plugin.h
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2003-2011 The Music Player Daemon Project
|
||||||
|
* http://www.musicpd.org
|
||||||
|
*
|
||||||
|
* This program is free software; you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation; either version 2 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License along
|
||||||
|
* with this program; if not, write to the Free Software Foundation, Inc.,
|
||||||
|
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef MPD_ARCHIVE_ISO9660_H
|
||||||
|
#define MPD_ARCHIVE_ISO9660_H
|
||||||
|
|
||||||
|
extern const struct archive_plugin iso9660_archive_plugin;
|
||||||
|
|
||||||
|
#endif
|
||||||
@@ -1,239 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (C) 2003-2009 The Music Player Daemon Project
|
|
||||||
* http://www.musicpd.org
|
|
||||||
*
|
|
||||||
* This program is free software; you can redistribute it and/or modify
|
|
||||||
* it under the terms of the GNU General Public License as published by
|
|
||||||
* the Free Software Foundation; either version 2 of the License, or
|
|
||||||
* (at your option) any later version.
|
|
||||||
*
|
|
||||||
* This program is distributed in the hope that it will be useful,
|
|
||||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
* GNU General Public License for more details.
|
|
||||||
*
|
|
||||||
* You should have received a copy of the GNU General Public License along
|
|
||||||
* with this program; if not, write to the Free Software Foundation, Inc.,
|
|
||||||
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
|
||||||
* iso archive handling (requires cdio, and iso9660)
|
|
||||||
*/
|
|
||||||
|
|
||||||
#include "archive_api.h"
|
|
||||||
#include "input_plugin.h"
|
|
||||||
|
|
||||||
#include <cdio/cdio.h>
|
|
||||||
#include <cdio/iso9660.h>
|
|
||||||
|
|
||||||
#include <glib.h>
|
|
||||||
#include <string.h>
|
|
||||||
|
|
||||||
#define CEILING(x, y) ((x+(y-1))/y)
|
|
||||||
|
|
||||||
typedef struct {
|
|
||||||
iso9660_t *iso;
|
|
||||||
iso9660_stat_t *statbuf;
|
|
||||||
size_t cur_ofs;
|
|
||||||
size_t max_blocks;
|
|
||||||
GSList *list;
|
|
||||||
GSList *iter;
|
|
||||||
} iso_context;
|
|
||||||
|
|
||||||
static const struct input_plugin iso_inputplugin;
|
|
||||||
|
|
||||||
/* archive open && listing routine */
|
|
||||||
|
|
||||||
static void
|
|
||||||
listdir_recur(const char *psz_path, iso_context *context)
|
|
||||||
{
|
|
||||||
iso9660_t *iso = context->iso;
|
|
||||||
CdioList_t *entlist;
|
|
||||||
CdioListNode_t *entnode;
|
|
||||||
iso9660_stat_t *statbuf;
|
|
||||||
char pathname[4096];
|
|
||||||
|
|
||||||
entlist = iso9660_ifs_readdir (iso, psz_path);
|
|
||||||
if (!entlist) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
/* Iterate over the list of nodes that iso9660_ifs_readdir gives */
|
|
||||||
_CDIO_LIST_FOREACH (entnode, entlist) {
|
|
||||||
statbuf = (iso9660_stat_t *) _cdio_list_node_data (entnode);
|
|
||||||
|
|
||||||
strcpy(pathname, psz_path);
|
|
||||||
strcat(pathname, statbuf->filename);
|
|
||||||
|
|
||||||
if (_STAT_DIR == statbuf->type ) {
|
|
||||||
if (strcmp(statbuf->filename, ".") && strcmp(statbuf->filename, "..")) {
|
|
||||||
strcat(pathname, "/");
|
|
||||||
listdir_recur(pathname, context);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
//remove leading /
|
|
||||||
context->list = g_slist_prepend( context->list,
|
|
||||||
g_strdup(pathname + 1));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
_cdio_list_free (entlist, true);
|
|
||||||
}
|
|
||||||
|
|
||||||
static struct archive_file *
|
|
||||||
iso_open(char * pathname)
|
|
||||||
{
|
|
||||||
iso_context *context = g_malloc(sizeof(iso_context));
|
|
||||||
|
|
||||||
context->list = NULL;
|
|
||||||
|
|
||||||
/* open archive */
|
|
||||||
context->iso = iso9660_open (pathname);
|
|
||||||
if (context->iso == NULL) {
|
|
||||||
g_warning("iso %s open failed\n", pathname);
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
|
|
||||||
listdir_recur("/", context);
|
|
||||||
|
|
||||||
return (struct archive_file *)context;
|
|
||||||
}
|
|
||||||
|
|
||||||
static void
|
|
||||||
iso_scan_reset(struct archive_file *file)
|
|
||||||
{
|
|
||||||
iso_context *context = (iso_context *) file;
|
|
||||||
//reset iterator
|
|
||||||
context->iter = context->list;
|
|
||||||
}
|
|
||||||
|
|
||||||
static char *
|
|
||||||
iso_scan_next(struct archive_file *file)
|
|
||||||
{
|
|
||||||
iso_context *context = (iso_context *) file;
|
|
||||||
char *data = NULL;
|
|
||||||
if (context->iter != NULL) {
|
|
||||||
///fetch data and goto next
|
|
||||||
data = context->iter->data;
|
|
||||||
context->iter = g_slist_next(context->iter);
|
|
||||||
}
|
|
||||||
return data;
|
|
||||||
}
|
|
||||||
|
|
||||||
static void
|
|
||||||
iso_close(struct archive_file *file)
|
|
||||||
{
|
|
||||||
iso_context *context = (iso_context *) file;
|
|
||||||
GSList *tmp;
|
|
||||||
if (context->list) {
|
|
||||||
//free list
|
|
||||||
for (tmp = context->list; tmp != NULL; tmp = g_slist_next(tmp))
|
|
||||||
g_free(tmp->data);
|
|
||||||
g_slist_free(context->list);
|
|
||||||
}
|
|
||||||
//close archive
|
|
||||||
iso9660_close(context->iso);
|
|
||||||
|
|
||||||
g_free(context);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* single archive handling */
|
|
||||||
|
|
||||||
static bool
|
|
||||||
iso_open_stream(struct archive_file *file, struct input_stream *is,
|
|
||||||
const char *pathname)
|
|
||||||
{
|
|
||||||
iso_context *context = (iso_context *) file;
|
|
||||||
//setup file ops
|
|
||||||
is->plugin = &iso_inputplugin;
|
|
||||||
//insert back reference
|
|
||||||
is->data = context;
|
|
||||||
//we are not seekable
|
|
||||||
is->seekable = false;
|
|
||||||
|
|
||||||
context->statbuf = iso9660_ifs_stat_translate (context->iso, pathname);
|
|
||||||
|
|
||||||
if (context->statbuf == NULL) {
|
|
||||||
g_warning("file %s not found in iso\n", pathname);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
context->cur_ofs = 0;
|
|
||||||
context->max_blocks = CEILING(context->statbuf->size, ISO_BLOCKSIZE);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
static void
|
|
||||||
iso_is_close(struct input_stream *is)
|
|
||||||
{
|
|
||||||
iso_context *context = (iso_context *) is->data;
|
|
||||||
g_free(context->statbuf);
|
|
||||||
|
|
||||||
iso_close((struct archive_file *)context);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
static size_t
|
|
||||||
iso_is_read(struct input_stream *is, void *ptr, size_t size)
|
|
||||||
{
|
|
||||||
iso_context *context = (iso_context *) is->data;
|
|
||||||
int toread, readed = 0;
|
|
||||||
int no_blocks, cur_block;
|
|
||||||
size_t left_bytes = context->statbuf->size - context->cur_ofs;
|
|
||||||
|
|
||||||
size = (size * ISO_BLOCKSIZE) / ISO_BLOCKSIZE;
|
|
||||||
|
|
||||||
if (left_bytes < size) {
|
|
||||||
toread = left_bytes;
|
|
||||||
no_blocks = CEILING(left_bytes,ISO_BLOCKSIZE);
|
|
||||||
} else {
|
|
||||||
toread = size;
|
|
||||||
no_blocks = toread / ISO_BLOCKSIZE;
|
|
||||||
}
|
|
||||||
if (no_blocks > 0) {
|
|
||||||
|
|
||||||
cur_block = context->cur_ofs / ISO_BLOCKSIZE;
|
|
||||||
|
|
||||||
readed = iso9660_iso_seek_read (context->iso, ptr,
|
|
||||||
context->statbuf->lsn + cur_block, no_blocks);
|
|
||||||
|
|
||||||
if (readed != no_blocks * ISO_BLOCKSIZE) {
|
|
||||||
g_warning("error reading ISO file at lsn %lu\n",
|
|
||||||
(long unsigned int) cur_block );
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
if (left_bytes < size) {
|
|
||||||
readed = left_bytes;
|
|
||||||
}
|
|
||||||
context->cur_ofs += readed;
|
|
||||||
}
|
|
||||||
return readed;
|
|
||||||
}
|
|
||||||
|
|
||||||
static bool
|
|
||||||
iso_is_eof(struct input_stream *is)
|
|
||||||
{
|
|
||||||
iso_context *context = (iso_context *) is->data;
|
|
||||||
return (context->cur_ofs == context->statbuf->size);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* exported structures */
|
|
||||||
|
|
||||||
static const char *const iso_extensions[] = {
|
|
||||||
"iso",
|
|
||||||
NULL
|
|
||||||
};
|
|
||||||
|
|
||||||
static const struct input_plugin iso_inputplugin = {
|
|
||||||
.close = iso_is_close,
|
|
||||||
.read = iso_is_read,
|
|
||||||
.eof = iso_is_eof,
|
|
||||||
};
|
|
||||||
|
|
||||||
const struct archive_plugin iso_plugin = {
|
|
||||||
.name = "iso",
|
|
||||||
.open = iso_open,
|
|
||||||
.scan_reset = iso_scan_reset,
|
|
||||||
.scan_next = iso_scan_next,
|
|
||||||
.open_stream = iso_open_stream,
|
|
||||||
.close = iso_close,
|
|
||||||
.suffixes = iso_extensions
|
|
||||||
};
|
|
||||||
@@ -1,196 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (C) 2003-2009 The Music Player Daemon Project
|
|
||||||
* http://www.musicpd.org
|
|
||||||
*
|
|
||||||
* This program is free software; you can redistribute it and/or modify
|
|
||||||
* it under the terms of the GNU General Public License as published by
|
|
||||||
* the Free Software Foundation; either version 2 of the License, or
|
|
||||||
* (at your option) any later version.
|
|
||||||
*
|
|
||||||
* This program is distributed in the hope that it will be useful,
|
|
||||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
* GNU General Public License for more details.
|
|
||||||
*
|
|
||||||
* You should have received a copy of the GNU General Public License along
|
|
||||||
* with this program; if not, write to the Free Software Foundation, Inc.,
|
|
||||||
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
|
||||||
* zip archive handling (requires zziplib)
|
|
||||||
*/
|
|
||||||
|
|
||||||
#include "archive_api.h"
|
|
||||||
#include "archive_api.h"
|
|
||||||
#include "input_plugin.h"
|
|
||||||
|
|
||||||
#include <zzip/zzip.h>
|
|
||||||
#include <glib.h>
|
|
||||||
#include <string.h>
|
|
||||||
|
|
||||||
typedef struct {
|
|
||||||
ZZIP_DIR *dir;
|
|
||||||
ZZIP_FILE *file;
|
|
||||||
size_t length;
|
|
||||||
GSList *list;
|
|
||||||
GSList *iter;
|
|
||||||
} zip_context;
|
|
||||||
|
|
||||||
static const struct input_plugin zip_inputplugin;
|
|
||||||
|
|
||||||
/* archive open && listing routine */
|
|
||||||
|
|
||||||
static struct archive_file *
|
|
||||||
zip_open(char * pathname)
|
|
||||||
{
|
|
||||||
zip_context *context = g_malloc(sizeof(zip_context));
|
|
||||||
ZZIP_DIRENT dirent;
|
|
||||||
|
|
||||||
// open archive
|
|
||||||
context->list = NULL;
|
|
||||||
context->dir = zzip_dir_open(pathname, NULL);
|
|
||||||
if (context->dir == NULL) {
|
|
||||||
g_warning("zipfile %s open failed\n", pathname);
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
|
|
||||||
while (zzip_dir_read(context->dir, &dirent)) {
|
|
||||||
//add only files
|
|
||||||
if (dirent.st_size > 0) {
|
|
||||||
context->list = g_slist_prepend(context->list,
|
|
||||||
g_strdup(dirent.d_name));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return (struct archive_file *)context;
|
|
||||||
}
|
|
||||||
|
|
||||||
static void
|
|
||||||
zip_scan_reset(struct archive_file *file)
|
|
||||||
{
|
|
||||||
zip_context *context = (zip_context *) file;
|
|
||||||
//reset iterator
|
|
||||||
context->iter = context->list;
|
|
||||||
}
|
|
||||||
|
|
||||||
static char *
|
|
||||||
zip_scan_next(struct archive_file *file)
|
|
||||||
{
|
|
||||||
zip_context *context = (zip_context *) file;
|
|
||||||
char *data = NULL;
|
|
||||||
if (context->iter != NULL) {
|
|
||||||
///fetch data and goto next
|
|
||||||
data = context->iter->data;
|
|
||||||
context->iter = g_slist_next(context->iter);
|
|
||||||
}
|
|
||||||
return data;
|
|
||||||
}
|
|
||||||
|
|
||||||
static void
|
|
||||||
zip_close(struct archive_file *file)
|
|
||||||
{
|
|
||||||
zip_context *context = (zip_context *) file;
|
|
||||||
if (context->list) {
|
|
||||||
//free list
|
|
||||||
for (GSList *tmp = context->list; tmp != NULL; tmp = g_slist_next(tmp))
|
|
||||||
g_free(tmp->data);
|
|
||||||
g_slist_free(context->list);
|
|
||||||
}
|
|
||||||
//close archive
|
|
||||||
zzip_dir_close (context->dir);
|
|
||||||
|
|
||||||
g_free(context);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* single archive handling */
|
|
||||||
|
|
||||||
static bool
|
|
||||||
zip_open_stream(struct archive_file *file, struct input_stream *is,
|
|
||||||
const char *pathname)
|
|
||||||
{
|
|
||||||
zip_context *context = (zip_context *) file;
|
|
||||||
ZZIP_STAT z_stat;
|
|
||||||
|
|
||||||
//setup file ops
|
|
||||||
is->plugin = &zip_inputplugin;
|
|
||||||
//insert back reference
|
|
||||||
is->data = context;
|
|
||||||
//we are seekable (but its not recommendent to do so)
|
|
||||||
is->seekable = true;
|
|
||||||
|
|
||||||
context->file = zzip_file_open(context->dir, pathname, 0);
|
|
||||||
if (!context->file) {
|
|
||||||
g_warning("file %s not found in the zipfile\n", pathname);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
zzip_file_stat(context->file, &z_stat);
|
|
||||||
context->length = z_stat.st_size;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
static void
|
|
||||||
zip_is_close(struct input_stream *is)
|
|
||||||
{
|
|
||||||
zip_context *context = (zip_context *) is->data;
|
|
||||||
zzip_file_close (context->file);
|
|
||||||
|
|
||||||
zip_close((struct archive_file *)context);
|
|
||||||
}
|
|
||||||
|
|
||||||
static size_t
|
|
||||||
zip_is_read(struct input_stream *is, void *ptr, size_t size)
|
|
||||||
{
|
|
||||||
zip_context *context = (zip_context *) is->data;
|
|
||||||
int ret;
|
|
||||||
ret = zzip_file_read(context->file, ptr, size);
|
|
||||||
if (ret < 0) {
|
|
||||||
g_warning("error %d reading zipfile\n", ret);
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
return ret;
|
|
||||||
}
|
|
||||||
|
|
||||||
static bool
|
|
||||||
zip_is_eof(struct input_stream *is)
|
|
||||||
{
|
|
||||||
zip_context *context = (zip_context *) is->data;
|
|
||||||
return ((size_t) zzip_tell(context->file) == context->length);
|
|
||||||
}
|
|
||||||
|
|
||||||
static bool
|
|
||||||
zip_is_seek(G_GNUC_UNUSED struct input_stream *is,
|
|
||||||
G_GNUC_UNUSED off_t offset, G_GNUC_UNUSED int whence)
|
|
||||||
{
|
|
||||||
zip_context *context = (zip_context *) is->data;
|
|
||||||
zzip_off_t ofs = zzip_seek(context->file, offset, whence);
|
|
||||||
if (ofs != -1) {
|
|
||||||
is->offset = ofs;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* exported structures */
|
|
||||||
|
|
||||||
static const char *const zip_extensions[] = {
|
|
||||||
"zip",
|
|
||||||
NULL
|
|
||||||
};
|
|
||||||
|
|
||||||
static const struct input_plugin zip_inputplugin = {
|
|
||||||
.close = zip_is_close,
|
|
||||||
.read = zip_is_read,
|
|
||||||
.eof = zip_is_eof,
|
|
||||||
.seek = zip_is_seek,
|
|
||||||
};
|
|
||||||
|
|
||||||
const struct archive_plugin zip_plugin = {
|
|
||||||
.name = "zip",
|
|
||||||
.open = zip_open,
|
|
||||||
.scan_reset = zip_scan_reset,
|
|
||||||
.scan_next = zip_scan_next,
|
|
||||||
.open_stream = zip_open_stream,
|
|
||||||
.close = zip_close,
|
|
||||||
.suffixes = zip_extensions
|
|
||||||
};
|
|
||||||
246
src/archive/zzip_archive_plugin.c
Normal file
246
src/archive/zzip_archive_plugin.c
Normal file
@@ -0,0 +1,246 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2003-2011 The Music Player Daemon Project
|
||||||
|
* http://www.musicpd.org
|
||||||
|
*
|
||||||
|
* This program is free software; you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation; either version 2 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License along
|
||||||
|
* with this program; if not, write to the Free Software Foundation, Inc.,
|
||||||
|
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* zip archive handling (requires zziplib)
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "config.h"
|
||||||
|
#include "archive/zzip_archive_plugin.h"
|
||||||
|
#include "archive_api.h"
|
||||||
|
#include "archive_api.h"
|
||||||
|
#include "input_internal.h"
|
||||||
|
#include "input_plugin.h"
|
||||||
|
#include "refcount.h"
|
||||||
|
|
||||||
|
#include <zzip/zzip.h>
|
||||||
|
#include <glib.h>
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
|
struct zzip_archive {
|
||||||
|
struct archive_file base;
|
||||||
|
|
||||||
|
struct refcount ref;
|
||||||
|
|
||||||
|
ZZIP_DIR *dir;
|
||||||
|
GSList *list;
|
||||||
|
GSList *iter;
|
||||||
|
};
|
||||||
|
|
||||||
|
static const struct input_plugin zzip_input_plugin;
|
||||||
|
|
||||||
|
static inline GQuark
|
||||||
|
zzip_quark(void)
|
||||||
|
{
|
||||||
|
return g_quark_from_static_string("zzip");
|
||||||
|
}
|
||||||
|
|
||||||
|
/* archive open && listing routine */
|
||||||
|
|
||||||
|
static struct archive_file *
|
||||||
|
zzip_archive_open(const char *pathname, GError **error_r)
|
||||||
|
{
|
||||||
|
struct zzip_archive *context = g_malloc(sizeof(*context));
|
||||||
|
ZZIP_DIRENT dirent;
|
||||||
|
|
||||||
|
archive_file_init(&context->base, &zzip_archive_plugin);
|
||||||
|
refcount_init(&context->ref);
|
||||||
|
|
||||||
|
// open archive
|
||||||
|
context->list = NULL;
|
||||||
|
context->dir = zzip_dir_open(pathname, NULL);
|
||||||
|
if (context->dir == NULL) {
|
||||||
|
g_set_error(error_r, zzip_quark(), 0,
|
||||||
|
"Failed to open ZIP file %s", pathname);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
while (zzip_dir_read(context->dir, &dirent)) {
|
||||||
|
//add only files
|
||||||
|
if (dirent.st_size > 0) {
|
||||||
|
context->list = g_slist_prepend(context->list,
|
||||||
|
g_strdup(dirent.d_name));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return &context->base;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
zzip_archive_scan_reset(struct archive_file *file)
|
||||||
|
{
|
||||||
|
struct zzip_archive *context = (struct zzip_archive *) file;
|
||||||
|
//reset iterator
|
||||||
|
context->iter = context->list;
|
||||||
|
}
|
||||||
|
|
||||||
|
static char *
|
||||||
|
zzip_archive_scan_next(struct archive_file *file)
|
||||||
|
{
|
||||||
|
struct zzip_archive *context = (struct zzip_archive *) file;
|
||||||
|
char *data = NULL;
|
||||||
|
if (context->iter != NULL) {
|
||||||
|
///fetch data and goto next
|
||||||
|
data = context->iter->data;
|
||||||
|
context->iter = g_slist_next(context->iter);
|
||||||
|
}
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
zzip_archive_close(struct archive_file *file)
|
||||||
|
{
|
||||||
|
struct zzip_archive *context = (struct zzip_archive *) file;
|
||||||
|
|
||||||
|
if (!refcount_dec(&context->ref))
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (context->list) {
|
||||||
|
//free list
|
||||||
|
for (GSList *tmp = context->list; tmp != NULL; tmp = g_slist_next(tmp))
|
||||||
|
g_free(tmp->data);
|
||||||
|
g_slist_free(context->list);
|
||||||
|
}
|
||||||
|
//close archive
|
||||||
|
zzip_dir_close (context->dir);
|
||||||
|
|
||||||
|
g_free(context);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* single archive handling */
|
||||||
|
|
||||||
|
struct zzip_input_stream {
|
||||||
|
struct input_stream base;
|
||||||
|
|
||||||
|
struct zzip_archive *archive;
|
||||||
|
|
||||||
|
ZZIP_FILE *file;
|
||||||
|
};
|
||||||
|
|
||||||
|
static struct input_stream *
|
||||||
|
zzip_archive_open_stream(struct archive_file *file,
|
||||||
|
const char *pathname,
|
||||||
|
GMutex *mutex, GCond *cond,
|
||||||
|
GError **error_r)
|
||||||
|
{
|
||||||
|
struct zzip_archive *context = (struct zzip_archive *) file;
|
||||||
|
struct zzip_input_stream *zis;
|
||||||
|
ZZIP_STAT z_stat;
|
||||||
|
|
||||||
|
zis = g_new(struct zzip_input_stream, 1);
|
||||||
|
input_stream_init(&zis->base, &zzip_input_plugin, pathname,
|
||||||
|
mutex, cond);
|
||||||
|
|
||||||
|
zis->archive = context;
|
||||||
|
zis->file = zzip_file_open(context->dir, pathname, 0);
|
||||||
|
if (zis->file == NULL) {
|
||||||
|
g_free(zis);
|
||||||
|
g_set_error(error_r, zzip_quark(), 0,
|
||||||
|
"not found in the ZIP file: %s", pathname);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
zis->base.ready = true;
|
||||||
|
//we are seekable (but its not recommendent to do so)
|
||||||
|
zis->base.seekable = true;
|
||||||
|
|
||||||
|
zzip_file_stat(zis->file, &z_stat);
|
||||||
|
zis->base.size = z_stat.st_size;
|
||||||
|
|
||||||
|
refcount_inc(&context->ref);
|
||||||
|
|
||||||
|
return &zis->base;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
zzip_input_close(struct input_stream *is)
|
||||||
|
{
|
||||||
|
struct zzip_input_stream *zis = (struct zzip_input_stream *)is;
|
||||||
|
|
||||||
|
zzip_file_close(zis->file);
|
||||||
|
zzip_archive_close(&zis->archive->base);
|
||||||
|
input_stream_deinit(&zis->base);
|
||||||
|
g_free(zis);
|
||||||
|
}
|
||||||
|
|
||||||
|
static size_t
|
||||||
|
zzip_input_read(struct input_stream *is, void *ptr, size_t size,
|
||||||
|
GError **error_r)
|
||||||
|
{
|
||||||
|
struct zzip_input_stream *zis = (struct zzip_input_stream *)is;
|
||||||
|
int ret;
|
||||||
|
|
||||||
|
ret = zzip_file_read(zis->file, ptr, size);
|
||||||
|
if (ret < 0) {
|
||||||
|
g_set_error(error_r, zzip_quark(), ret,
|
||||||
|
"zzip_file_read() has failed");
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
is->offset = zzip_tell(zis->file);
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool
|
||||||
|
zzip_input_eof(struct input_stream *is)
|
||||||
|
{
|
||||||
|
struct zzip_input_stream *zis = (struct zzip_input_stream *)is;
|
||||||
|
|
||||||
|
return (goffset)zzip_tell(zis->file) == is->size;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool
|
||||||
|
zzip_input_seek(struct input_stream *is,
|
||||||
|
goffset offset, int whence, GError **error_r)
|
||||||
|
{
|
||||||
|
struct zzip_input_stream *zis = (struct zzip_input_stream *)is;
|
||||||
|
zzip_off_t ofs = zzip_seek(zis->file, offset, whence);
|
||||||
|
if (ofs != -1) {
|
||||||
|
g_set_error(error_r, zzip_quark(), ofs,
|
||||||
|
"zzip_seek() has failed");
|
||||||
|
is->offset = ofs;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* exported structures */
|
||||||
|
|
||||||
|
static const char *const zzip_archive_extensions[] = {
|
||||||
|
"zip",
|
||||||
|
NULL
|
||||||
|
};
|
||||||
|
|
||||||
|
static const struct input_plugin zzip_input_plugin = {
|
||||||
|
.close = zzip_input_close,
|
||||||
|
.read = zzip_input_read,
|
||||||
|
.eof = zzip_input_eof,
|
||||||
|
.seek = zzip_input_seek,
|
||||||
|
};
|
||||||
|
|
||||||
|
const struct archive_plugin zzip_archive_plugin = {
|
||||||
|
.name = "zzip",
|
||||||
|
.open = zzip_archive_open,
|
||||||
|
.scan_reset = zzip_archive_scan_reset,
|
||||||
|
.scan_next = zzip_archive_scan_next,
|
||||||
|
.open_stream = zzip_archive_open_stream,
|
||||||
|
.close = zzip_archive_close,
|
||||||
|
.suffixes = zzip_archive_extensions
|
||||||
|
};
|
||||||
25
src/archive/zzip_archive_plugin.h
Normal file
25
src/archive/zzip_archive_plugin.h
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2003-2011 The Music Player Daemon Project
|
||||||
|
* http://www.musicpd.org
|
||||||
|
*
|
||||||
|
* This program is free software; you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation; either version 2 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License along
|
||||||
|
* with this program; if not, write to the Free Software Foundation, Inc.,
|
||||||
|
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef MPD_ARCHIVE_ZZIP_H
|
||||||
|
#define MPD_ARCHIVE_ZZIP_H
|
||||||
|
|
||||||
|
extern const struct archive_plugin zzip_archive_plugin;
|
||||||
|
|
||||||
|
#endif
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright (C) 2003-2009 The Music Player Daemon Project
|
* Copyright (C) 2003-2011 The Music Player Daemon Project
|
||||||
* http://www.musicpd.org
|
* http://www.musicpd.org
|
||||||
*
|
*
|
||||||
* This program is free software; you can redistribute it and/or modify
|
* This program is free software; you can redistribute it and/or modify
|
||||||
@@ -17,6 +17,9 @@
|
|||||||
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
#include "config.h" /* must be first for large file support */
|
||||||
|
#include "archive_api.h"
|
||||||
|
|
||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
|
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
@@ -26,8 +29,6 @@
|
|||||||
#include <errno.h>
|
#include <errno.h>
|
||||||
#include <glib.h>
|
#include <glib.h>
|
||||||
|
|
||||||
#include "archive_api.h"
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
* archive_lookup is used to determine if part of pathname refers to an regular
|
* archive_lookup is used to determine if part of pathname refers to an regular
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright (C) 2003-2009 The Music Player Daemon Project
|
* Copyright (C) 2003-2011 The Music Player Daemon Project
|
||||||
* http://www.musicpd.org
|
* http://www.musicpd.org
|
||||||
*
|
*
|
||||||
* This program is free software; you can redistribute it and/or modify
|
* This program is free software; you can redistribute it and/or modify
|
||||||
@@ -27,72 +27,11 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
#include "archive_internal.h"
|
#include "archive_internal.h"
|
||||||
|
#include "archive_plugin.h"
|
||||||
#include "input_stream.h"
|
#include "input_stream.h"
|
||||||
|
|
||||||
#include <stdbool.h>
|
#include <stdbool.h>
|
||||||
|
|
||||||
struct archive_file;
|
|
||||||
|
|
||||||
struct archive_plugin {
|
|
||||||
const char *name;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* optional, set this to NULL if the archive plugin doesn't
|
|
||||||
* have/need one this must false if there is an error and
|
|
||||||
* true otherwise
|
|
||||||
*/
|
|
||||||
bool (*init)(void);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* optional, set this to NULL if the archive plugin doesn't
|
|
||||||
* have/need one
|
|
||||||
*/
|
|
||||||
void (*finish)(void);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* tryes to open archive file and associates handle with archive
|
|
||||||
* returns pointer to handle used is all operations with this archive
|
|
||||||
* or NULL when opening fails
|
|
||||||
*/
|
|
||||||
struct archive_file *(*open)(char * pathname);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* reset routine will move current read index in archive to default
|
|
||||||
* position and then the filenames from archives can be read
|
|
||||||
* via scan_next routine
|
|
||||||
*/
|
|
||||||
void (*scan_reset)(struct archive_file *);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* the read method will return corresponding files from archive
|
|
||||||
* (as pathnames) and move read index to next file. When there is no
|
|
||||||
* next file it return NULL.
|
|
||||||
*/
|
|
||||||
char *(*scan_next)(struct archive_file *);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Opens an input_stream of a file within the archive.
|
|
||||||
*
|
|
||||||
* If this function succeeds, then the #input_stream "owns"
|
|
||||||
* the archive file and will automatically close it.
|
|
||||||
*
|
|
||||||
* @param path the path within the archive
|
|
||||||
*/
|
|
||||||
bool (*open_stream)(struct archive_file *, struct input_stream *is,
|
|
||||||
const char *path);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* closes archive file.
|
|
||||||
*/
|
|
||||||
void (*close)(struct archive_file *);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* suffixes handled by this plugin.
|
|
||||||
* last element in these arrays must always be a NULL
|
|
||||||
*/
|
|
||||||
const char *const*suffixes;
|
|
||||||
};
|
|
||||||
|
|
||||||
bool archive_lookup(char *pathname, char **archive, char **inpath, char **suffix);
|
bool archive_lookup(char *pathname, char **archive, char **inpath, char **suffix);
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright (C) 2003-2009 The Music Player Daemon Project
|
* Copyright (C) 2003-2011 The Music Player Daemon Project
|
||||||
* http://www.musicpd.org
|
* http://www.musicpd.org
|
||||||
*
|
*
|
||||||
* This program is free software; you can redistribute it and/or modify
|
* This program is free software; you can redistribute it and/or modify
|
||||||
@@ -21,7 +21,14 @@
|
|||||||
#define MPD_ARCHIVE_INTERNAL_H
|
#define MPD_ARCHIVE_INTERNAL_H
|
||||||
|
|
||||||
struct archive_file {
|
struct archive_file {
|
||||||
int placeholder;
|
const struct archive_plugin *plugin;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
static inline void
|
||||||
|
archive_file_init(struct archive_file *archive_file,
|
||||||
|
const struct archive_plugin *plugin)
|
||||||
|
{
|
||||||
|
archive_file->plugin = plugin;
|
||||||
|
}
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright (C) 2003-2009 The Music Player Daemon Project
|
* Copyright (C) 2003-2011 The Music Player Daemon Project
|
||||||
* http://www.musicpd.org
|
* http://www.musicpd.org
|
||||||
*
|
*
|
||||||
* This program is free software; you can redistribute it and/or modify
|
* This program is free software; you can redistribute it and/or modify
|
||||||
@@ -17,91 +17,64 @@
|
|||||||
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#include "archive_list.h"
|
|
||||||
#include "archive_api.h"
|
|
||||||
#include "utils.h"
|
|
||||||
#include "config.h"
|
#include "config.h"
|
||||||
|
#include "archive_list.h"
|
||||||
|
#include "archive_plugin.h"
|
||||||
|
#include "string_util.h"
|
||||||
|
#include "archive/bz2_archive_plugin.h"
|
||||||
|
#include "archive/iso9660_archive_plugin.h"
|
||||||
|
#include "archive/zzip_archive_plugin.h"
|
||||||
|
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
#include <glib.h>
|
#include <glib.h>
|
||||||
|
|
||||||
extern const struct archive_plugin bz2_plugin;
|
const struct archive_plugin *const archive_plugins[] = {
|
||||||
extern const struct archive_plugin zip_plugin;
|
|
||||||
extern const struct archive_plugin iso_plugin;
|
|
||||||
|
|
||||||
static const struct archive_plugin *const archive_plugins[] = {
|
|
||||||
#ifdef HAVE_BZ2
|
#ifdef HAVE_BZ2
|
||||||
&bz2_plugin,
|
&bz2_archive_plugin,
|
||||||
#endif
|
#endif
|
||||||
#ifdef HAVE_ZIP
|
#ifdef HAVE_ZZIP
|
||||||
&zip_plugin,
|
&zzip_archive_plugin,
|
||||||
#endif
|
#endif
|
||||||
#ifdef HAVE_ISO
|
#ifdef HAVE_ISO9660
|
||||||
&iso_plugin,
|
&iso9660_archive_plugin,
|
||||||
#endif
|
#endif
|
||||||
NULL
|
NULL
|
||||||
};
|
};
|
||||||
|
|
||||||
enum {
|
|
||||||
num_archive_plugins = G_N_ELEMENTS(archive_plugins)-1,
|
|
||||||
};
|
|
||||||
|
|
||||||
/** which plugins have been initialized successfully? */
|
/** which plugins have been initialized successfully? */
|
||||||
static bool archive_plugins_enabled[num_archive_plugins+1];
|
static bool archive_plugins_enabled[G_N_ELEMENTS(archive_plugins) - 1];
|
||||||
|
|
||||||
|
#define archive_plugins_for_each_enabled(plugin) \
|
||||||
|
archive_plugins_for_each(plugin) \
|
||||||
|
if (archive_plugins_enabled[archive_plugin_iterator - archive_plugins])
|
||||||
|
|
||||||
const struct archive_plugin *
|
const struct archive_plugin *
|
||||||
archive_plugin_from_suffix(const char *suffix)
|
archive_plugin_from_suffix(const char *suffix)
|
||||||
{
|
{
|
||||||
unsigned i;
|
|
||||||
|
|
||||||
if (suffix == NULL)
|
if (suffix == NULL)
|
||||||
return NULL;
|
return NULL;
|
||||||
|
|
||||||
for (i=0; i < num_archive_plugins; ++i) {
|
archive_plugins_for_each_enabled(plugin)
|
||||||
const struct archive_plugin *plugin = archive_plugins[i];
|
if (plugin->suffixes != NULL &&
|
||||||
if (archive_plugins_enabled[i] &&
|
string_array_contains(plugin->suffixes, suffix))
|
||||||
stringFoundInStringArray(plugin->suffixes, suffix)) {
|
|
||||||
++i;
|
|
||||||
return plugin;
|
return plugin;
|
||||||
}
|
|
||||||
}
|
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
const struct archive_plugin *
|
const struct archive_plugin *
|
||||||
archive_plugin_from_name(const char *name)
|
archive_plugin_from_name(const char *name)
|
||||||
{
|
{
|
||||||
for (unsigned i = 0; i < num_archive_plugins; ++i) {
|
archive_plugins_for_each_enabled(plugin)
|
||||||
const struct archive_plugin *plugin = archive_plugins[i];
|
if (strcmp(plugin->name, name) == 0)
|
||||||
if (archive_plugins_enabled[i] &&
|
|
||||||
strcmp(plugin->name, name) == 0)
|
|
||||||
return plugin;
|
return plugin;
|
||||||
}
|
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
void archive_plugin_print_all_suffixes(FILE * fp)
|
|
||||||
{
|
|
||||||
const char *const*suffixes;
|
|
||||||
|
|
||||||
for (unsigned i = 0; i < num_archive_plugins; ++i) {
|
|
||||||
const struct archive_plugin *plugin = archive_plugins[i];
|
|
||||||
if (!archive_plugins_enabled[i])
|
|
||||||
continue;
|
|
||||||
|
|
||||||
suffixes = plugin->suffixes;
|
|
||||||
while (suffixes && *suffixes) {
|
|
||||||
fprintf(fp, "%s ", *suffixes);
|
|
||||||
suffixes++;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
fprintf(fp, "\n");
|
|
||||||
fflush(fp);
|
|
||||||
}
|
|
||||||
|
|
||||||
void archive_plugin_init_all(void)
|
void archive_plugin_init_all(void)
|
||||||
{
|
{
|
||||||
for (unsigned i = 0; i < num_archive_plugins; ++i) {
|
for (unsigned i = 0; archive_plugins[i] != NULL; ++i) {
|
||||||
const struct archive_plugin *plugin = archive_plugins[i];
|
const struct archive_plugin *plugin = archive_plugins[i];
|
||||||
if (plugin->init == NULL || archive_plugins[i]->init())
|
if (plugin->init == NULL || archive_plugins[i]->init())
|
||||||
archive_plugins_enabled[i] = true;
|
archive_plugins_enabled[i] = true;
|
||||||
@@ -110,10 +83,8 @@ void archive_plugin_init_all(void)
|
|||||||
|
|
||||||
void archive_plugin_deinit_all(void)
|
void archive_plugin_deinit_all(void)
|
||||||
{
|
{
|
||||||
for (unsigned i = 0; i < num_archive_plugins; ++i) {
|
archive_plugins_for_each_enabled(plugin)
|
||||||
const struct archive_plugin *plugin = archive_plugins[i];
|
if (plugin->finish != NULL)
|
||||||
if (archive_plugins_enabled[i] && plugin->finish != NULL)
|
plugin->finish();
|
||||||
archive_plugins[i]->finish();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright (C) 2003-2009 The Music Player Daemon Project
|
* Copyright (C) 2003-2011 The Music Player Daemon Project
|
||||||
* http://www.musicpd.org
|
* http://www.musicpd.org
|
||||||
*
|
*
|
||||||
* This program is free software; you can redistribute it and/or modify
|
* This program is free software; you can redistribute it and/or modify
|
||||||
@@ -20,12 +20,16 @@
|
|||||||
#ifndef MPD_ARCHIVE_LIST_H
|
#ifndef MPD_ARCHIVE_LIST_H
|
||||||
#define MPD_ARCHIVE_LIST_H
|
#define MPD_ARCHIVE_LIST_H
|
||||||
|
|
||||||
#include "archive_api.h"
|
|
||||||
|
|
||||||
#include <stdio.h>
|
|
||||||
|
|
||||||
struct archive_plugin;
|
struct archive_plugin;
|
||||||
|
|
||||||
|
extern const struct archive_plugin *const archive_plugins[];
|
||||||
|
|
||||||
|
#define archive_plugins_for_each(plugin) \
|
||||||
|
for (const struct archive_plugin *plugin, \
|
||||||
|
*const*archive_plugin_iterator = &archive_plugins[0]; \
|
||||||
|
(plugin = *archive_plugin_iterator) != NULL; \
|
||||||
|
++archive_plugin_iterator)
|
||||||
|
|
||||||
/* interface for using plugins */
|
/* interface for using plugins */
|
||||||
|
|
||||||
const struct archive_plugin *
|
const struct archive_plugin *
|
||||||
@@ -34,8 +38,6 @@ archive_plugin_from_suffix(const char *suffix);
|
|||||||
const struct archive_plugin *
|
const struct archive_plugin *
|
||||||
archive_plugin_from_name(const char *name);
|
archive_plugin_from_name(const char *name);
|
||||||
|
|
||||||
void archive_plugin_print_all_suffixes(FILE * fp);
|
|
||||||
|
|
||||||
/* this is where we "load" all the "plugins" ;-) */
|
/* this is where we "load" all the "plugins" ;-) */
|
||||||
void archive_plugin_init_all(void);
|
void archive_plugin_init_all(void);
|
||||||
|
|
||||||
|
|||||||
94
src/archive_plugin.c
Normal file
94
src/archive_plugin.c
Normal file
@@ -0,0 +1,94 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2003-2011 The Music Player Daemon Project
|
||||||
|
* http://www.musicpd.org
|
||||||
|
*
|
||||||
|
* This program is free software; you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation; either version 2 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License along
|
||||||
|
* with this program; if not, write to the Free Software Foundation, Inc.,
|
||||||
|
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "archive_plugin.h"
|
||||||
|
#include "archive_internal.h"
|
||||||
|
|
||||||
|
#include <assert.h>
|
||||||
|
|
||||||
|
struct archive_file *
|
||||||
|
archive_file_open(const struct archive_plugin *plugin, const char *path,
|
||||||
|
GError **error_r)
|
||||||
|
{
|
||||||
|
struct archive_file *file;
|
||||||
|
|
||||||
|
assert(plugin != NULL);
|
||||||
|
assert(plugin->open != NULL);
|
||||||
|
assert(path != NULL);
|
||||||
|
assert(error_r == NULL || *error_r == NULL);
|
||||||
|
|
||||||
|
file = plugin->open(path, error_r);
|
||||||
|
|
||||||
|
if (file != NULL) {
|
||||||
|
assert(file->plugin != NULL);
|
||||||
|
assert(file->plugin->close != NULL);
|
||||||
|
assert(file->plugin->scan_reset != NULL);
|
||||||
|
assert(file->plugin->scan_next != NULL);
|
||||||
|
assert(file->plugin->open_stream != NULL);
|
||||||
|
assert(error_r == NULL || *error_r == NULL);
|
||||||
|
} else {
|
||||||
|
assert(error_r == NULL || *error_r != NULL);
|
||||||
|
}
|
||||||
|
|
||||||
|
return file;
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
archive_file_close(struct archive_file *file)
|
||||||
|
{
|
||||||
|
assert(file != NULL);
|
||||||
|
assert(file->plugin != NULL);
|
||||||
|
assert(file->plugin->close != NULL);
|
||||||
|
|
||||||
|
file->plugin->close(file);
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
archive_file_scan_reset(struct archive_file *file)
|
||||||
|
{
|
||||||
|
assert(file != NULL);
|
||||||
|
assert(file->plugin != NULL);
|
||||||
|
assert(file->plugin->scan_reset != NULL);
|
||||||
|
assert(file->plugin->scan_next != NULL);
|
||||||
|
|
||||||
|
file->plugin->scan_reset(file);
|
||||||
|
}
|
||||||
|
|
||||||
|
char *
|
||||||
|
archive_file_scan_next(struct archive_file *file)
|
||||||
|
{
|
||||||
|
assert(file != NULL);
|
||||||
|
assert(file->plugin != NULL);
|
||||||
|
assert(file->plugin->scan_next != NULL);
|
||||||
|
|
||||||
|
return file->plugin->scan_next(file);
|
||||||
|
}
|
||||||
|
|
||||||
|
struct input_stream *
|
||||||
|
archive_file_open_stream(struct archive_file *file, const char *path,
|
||||||
|
GMutex *mutex, GCond *cond,
|
||||||
|
GError **error_r)
|
||||||
|
{
|
||||||
|
assert(file != NULL);
|
||||||
|
assert(file->plugin != NULL);
|
||||||
|
assert(file->plugin->open_stream != NULL);
|
||||||
|
|
||||||
|
return file->plugin->open_stream(file, path, mutex, cond,
|
||||||
|
error_r);
|
||||||
|
}
|
||||||
109
src/archive_plugin.h
Normal file
109
src/archive_plugin.h
Normal file
@@ -0,0 +1,109 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2003-2011 The Music Player Daemon Project
|
||||||
|
* http://www.musicpd.org
|
||||||
|
*
|
||||||
|
* This program is free software; you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation; either version 2 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License along
|
||||||
|
* with this program; if not, write to the Free Software Foundation, Inc.,
|
||||||
|
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef MPD_ARCHIVE_PLUGIN_H
|
||||||
|
#define MPD_ARCHIVE_PLUGIN_H
|
||||||
|
|
||||||
|
#include <glib.h>
|
||||||
|
|
||||||
|
#include <stdbool.h>
|
||||||
|
|
||||||
|
struct input_stream;
|
||||||
|
struct archive_file;
|
||||||
|
|
||||||
|
struct archive_plugin {
|
||||||
|
const char *name;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* optional, set this to NULL if the archive plugin doesn't
|
||||||
|
* have/need one this must false if there is an error and
|
||||||
|
* true otherwise
|
||||||
|
*/
|
||||||
|
bool (*init)(void);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* optional, set this to NULL if the archive plugin doesn't
|
||||||
|
* have/need one
|
||||||
|
*/
|
||||||
|
void (*finish)(void);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* tryes to open archive file and associates handle with archive
|
||||||
|
* returns pointer to handle used is all operations with this archive
|
||||||
|
* or NULL when opening fails
|
||||||
|
*/
|
||||||
|
struct archive_file *(*open)(const char *path_fs, GError **error_r);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* reset routine will move current read index in archive to default
|
||||||
|
* position and then the filenames from archives can be read
|
||||||
|
* via scan_next routine
|
||||||
|
*/
|
||||||
|
void (*scan_reset)(struct archive_file *);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* the read method will return corresponding files from archive
|
||||||
|
* (as pathnames) and move read index to next file. When there is no
|
||||||
|
* next file it return NULL.
|
||||||
|
*/
|
||||||
|
char *(*scan_next)(struct archive_file *);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Opens an input_stream of a file within the archive.
|
||||||
|
*
|
||||||
|
* @param path the path within the archive
|
||||||
|
* @param error_r location to store the error occurring, or
|
||||||
|
* NULL to ignore errors
|
||||||
|
*/
|
||||||
|
struct input_stream *(*open_stream)(struct archive_file *af,
|
||||||
|
const char *path,
|
||||||
|
GMutex *mutex, GCond *cond,
|
||||||
|
GError **error_r);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* closes archive file.
|
||||||
|
*/
|
||||||
|
void (*close)(struct archive_file *);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* suffixes handled by this plugin.
|
||||||
|
* last element in these arrays must always be a NULL
|
||||||
|
*/
|
||||||
|
const char *const*suffixes;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct archive_file *
|
||||||
|
archive_file_open(const struct archive_plugin *plugin, const char *path,
|
||||||
|
GError **error_r);
|
||||||
|
|
||||||
|
void
|
||||||
|
archive_file_close(struct archive_file *file);
|
||||||
|
|
||||||
|
void
|
||||||
|
archive_file_scan_reset(struct archive_file *file);
|
||||||
|
|
||||||
|
char *
|
||||||
|
archive_file_scan_next(struct archive_file *file);
|
||||||
|
|
||||||
|
struct input_stream *
|
||||||
|
archive_file_open_stream(struct archive_file *file, const char *path,
|
||||||
|
GMutex *mutex, GCond *cond,
|
||||||
|
GError **error_r);
|
||||||
|
|
||||||
|
#endif
|
||||||
74
src/audio_check.c
Normal file
74
src/audio_check.c
Normal file
@@ -0,0 +1,74 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2003-2011 The Music Player Daemon Project
|
||||||
|
* http://www.musicpd.org
|
||||||
|
*
|
||||||
|
* This program is free software; you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation; either version 2 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License along
|
||||||
|
* with this program; if not, write to the Free Software Foundation, Inc.,
|
||||||
|
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "audio_check.h"
|
||||||
|
#include "audio_format.h"
|
||||||
|
|
||||||
|
#include <assert.h>
|
||||||
|
|
||||||
|
bool
|
||||||
|
audio_check_sample_rate(unsigned long sample_rate, GError **error_r)
|
||||||
|
{
|
||||||
|
if (!audio_valid_sample_rate(sample_rate)) {
|
||||||
|
g_set_error(error_r, audio_format_quark(), 0,
|
||||||
|
"Invalid sample rate: %lu", sample_rate);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool
|
||||||
|
audio_check_sample_format(enum sample_format sample_format, GError **error_r)
|
||||||
|
{
|
||||||
|
if (!audio_valid_sample_format(sample_format)) {
|
||||||
|
g_set_error(error_r, audio_format_quark(), 0,
|
||||||
|
"Invalid sample format: %u", sample_format);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool
|
||||||
|
audio_check_channel_count(unsigned channels, GError **error_r)
|
||||||
|
{
|
||||||
|
if (!audio_valid_channel_count(channels)) {
|
||||||
|
g_set_error(error_r, audio_format_quark(), 0,
|
||||||
|
"Invalid channel count: %u", channels);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool
|
||||||
|
audio_format_init_checked(struct audio_format *af, unsigned long sample_rate,
|
||||||
|
enum sample_format sample_format, unsigned channels,
|
||||||
|
GError **error_r)
|
||||||
|
{
|
||||||
|
if (audio_check_sample_rate(sample_rate, error_r) &&
|
||||||
|
audio_check_sample_format(sample_format, error_r) &&
|
||||||
|
audio_check_channel_count(channels, error_r)) {
|
||||||
|
audio_format_init(af, sample_rate, sample_format, channels);
|
||||||
|
assert(audio_format_valid(af));
|
||||||
|
return true;
|
||||||
|
} else
|
||||||
|
return false;
|
||||||
|
}
|
||||||
55
src/audio_check.h
Normal file
55
src/audio_check.h
Normal file
@@ -0,0 +1,55 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2003-2011 The Music Player Daemon Project
|
||||||
|
* http://www.musicpd.org
|
||||||
|
*
|
||||||
|
* This program is free software; you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation; either version 2 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License along
|
||||||
|
* with this program; if not, write to the Free Software Foundation, Inc.,
|
||||||
|
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef MPD_AUDIO_CHECK_H
|
||||||
|
#define MPD_AUDIO_CHECK_H
|
||||||
|
|
||||||
|
#include "audio_format.h"
|
||||||
|
|
||||||
|
#include <glib.h>
|
||||||
|
#include <stdbool.h>
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The GLib quark used for errors reported by this library.
|
||||||
|
*/
|
||||||
|
G_GNUC_CONST
|
||||||
|
static inline GQuark
|
||||||
|
audio_format_quark(void)
|
||||||
|
{
|
||||||
|
return g_quark_from_static_string("audio_format");
|
||||||
|
}
|
||||||
|
|
||||||
|
bool
|
||||||
|
audio_check_sample_rate(unsigned long sample_rate, GError **error_r);
|
||||||
|
|
||||||
|
bool
|
||||||
|
audio_check_sample_format(enum sample_format, GError **error_r);
|
||||||
|
|
||||||
|
bool
|
||||||
|
audio_check_channel_count(unsigned sample_format, GError **error_r);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Wrapper for audio_format_init(), which checks all attributes.
|
||||||
|
*/
|
||||||
|
bool
|
||||||
|
audio_format_init_checked(struct audio_format *af, unsigned long sample_rate,
|
||||||
|
enum sample_format sample_format, unsigned channels,
|
||||||
|
GError **error_r);
|
||||||
|
|
||||||
|
#endif
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright (C) 2003-2009 The Music Player Daemon Project
|
* Copyright (C) 2003-2011 The Music Player Daemon Project
|
||||||
* http://www.musicpd.org
|
* http://www.musicpd.org
|
||||||
*
|
*
|
||||||
* This program is free software; you can redistribute it and/or modify
|
* This program is free software; you can redistribute it and/or modify
|
||||||
@@ -17,13 +17,15 @@
|
|||||||
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#include "audio.h"
|
#include "config.h"
|
||||||
|
#include "audio_config.h"
|
||||||
#include "audio_format.h"
|
#include "audio_format.h"
|
||||||
#include "audio_parser.h"
|
#include "audio_parser.h"
|
||||||
#include "output_internal.h"
|
#include "output_internal.h"
|
||||||
#include "output_plugin.h"
|
#include "output_plugin.h"
|
||||||
#include "output_all.h"
|
#include "output_all.h"
|
||||||
#include "conf.h"
|
#include "conf.h"
|
||||||
|
#include "mpd_error.h"
|
||||||
|
|
||||||
#include <glib.h>
|
#include <glib.h>
|
||||||
|
|
||||||
@@ -35,9 +37,8 @@ static struct audio_format configured_audio_format;
|
|||||||
void getOutputAudioFormat(const struct audio_format *inAudioFormat,
|
void getOutputAudioFormat(const struct audio_format *inAudioFormat,
|
||||||
struct audio_format *outAudioFormat)
|
struct audio_format *outAudioFormat)
|
||||||
{
|
{
|
||||||
*outAudioFormat = audio_format_defined(&configured_audio_format)
|
*outAudioFormat = *inAudioFormat;
|
||||||
? configured_audio_format
|
audio_format_mask_apply(outAudioFormat, &configured_audio_format);
|
||||||
: *inAudioFormat;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void initAudioConfig(void)
|
void initAudioConfig(void)
|
||||||
@@ -46,17 +47,13 @@ void initAudioConfig(void)
|
|||||||
GError *error = NULL;
|
GError *error = NULL;
|
||||||
bool ret;
|
bool ret;
|
||||||
|
|
||||||
if (NULL == param || NULL == param->value)
|
if (param == NULL)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
ret = audio_format_parse(&configured_audio_format, param->value,
|
ret = audio_format_parse(&configured_audio_format, param->value,
|
||||||
&error);
|
true, &error);
|
||||||
if (!ret)
|
if (!ret)
|
||||||
g_error("error parsing \"%s\" at line %i: %s",
|
MPD_ERROR("error parsing \"%s\" at line %i: %s",
|
||||||
CONF_AUDIO_OUTPUT_FORMAT, param->line, error->message);
|
CONF_AUDIO_OUTPUT_FORMAT, param->line,
|
||||||
}
|
error->message);
|
||||||
|
|
||||||
void finishAudioConfig(void)
|
|
||||||
{
|
|
||||||
audio_format_clear(&configured_audio_format);
|
|
||||||
}
|
}
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright (C) 2003-2009 The Music Player Daemon Project
|
* Copyright (C) 2003-2011 The Music Player Daemon Project
|
||||||
* http://www.musicpd.org
|
* http://www.musicpd.org
|
||||||
*
|
*
|
||||||
* This program is free software; you can redistribute it and/or modify
|
* This program is free software; you can redistribute it and/or modify
|
||||||
@@ -17,8 +17,8 @@
|
|||||||
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#ifndef MPD_AUDIO_H
|
#ifndef MPD_AUDIO_CONFIG_H
|
||||||
#define MPD_AUDIO_H
|
#define MPD_AUDIO_CONFIG_H
|
||||||
|
|
||||||
#include <stdbool.h>
|
#include <stdbool.h>
|
||||||
|
|
||||||
@@ -30,6 +30,4 @@ void getOutputAudioFormat(const struct audio_format *inFormat,
|
|||||||
/* make sure initPlayerData is called before this function!! */
|
/* make sure initPlayerData is called before this function!! */
|
||||||
void initAudioConfig(void);
|
void initAudioConfig(void);
|
||||||
|
|
||||||
void finishAudioConfig(void);
|
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
87
src/audio_format.c
Normal file
87
src/audio_format.c
Normal file
@@ -0,0 +1,87 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2003-2011 The Music Player Daemon Project
|
||||||
|
* http://www.musicpd.org
|
||||||
|
*
|
||||||
|
* This program is free software; you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation; either version 2 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License along
|
||||||
|
* with this program; if not, write to the Free Software Foundation, Inc.,
|
||||||
|
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "audio_format.h"
|
||||||
|
|
||||||
|
#include <assert.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
|
||||||
|
void
|
||||||
|
audio_format_mask_apply(struct audio_format *af,
|
||||||
|
const struct audio_format *mask)
|
||||||
|
{
|
||||||
|
assert(audio_format_valid(af));
|
||||||
|
assert(audio_format_mask_valid(mask));
|
||||||
|
|
||||||
|
if (mask->sample_rate != 0)
|
||||||
|
af->sample_rate = mask->sample_rate;
|
||||||
|
|
||||||
|
if (mask->format != SAMPLE_FORMAT_UNDEFINED)
|
||||||
|
af->format = mask->format;
|
||||||
|
|
||||||
|
if (mask->channels != 0)
|
||||||
|
af->channels = mask->channels;
|
||||||
|
|
||||||
|
assert(audio_format_valid(af));
|
||||||
|
}
|
||||||
|
|
||||||
|
const char *
|
||||||
|
sample_format_to_string(enum sample_format format)
|
||||||
|
{
|
||||||
|
switch (format) {
|
||||||
|
case SAMPLE_FORMAT_UNDEFINED:
|
||||||
|
return "?";
|
||||||
|
|
||||||
|
case SAMPLE_FORMAT_S8:
|
||||||
|
return "8";
|
||||||
|
|
||||||
|
case SAMPLE_FORMAT_S16:
|
||||||
|
return "16";
|
||||||
|
|
||||||
|
case SAMPLE_FORMAT_S24_P32:
|
||||||
|
return "24";
|
||||||
|
|
||||||
|
case SAMPLE_FORMAT_S32:
|
||||||
|
return "32";
|
||||||
|
|
||||||
|
case SAMPLE_FORMAT_FLOAT:
|
||||||
|
return "f";
|
||||||
|
|
||||||
|
case SAMPLE_FORMAT_DSD:
|
||||||
|
return "dsd";
|
||||||
|
}
|
||||||
|
|
||||||
|
/* unreachable */
|
||||||
|
assert(false);
|
||||||
|
return "?";
|
||||||
|
}
|
||||||
|
|
||||||
|
const char *
|
||||||
|
audio_format_to_string(const struct audio_format *af,
|
||||||
|
struct audio_format_string *s)
|
||||||
|
{
|
||||||
|
assert(af != NULL);
|
||||||
|
assert(s != NULL);
|
||||||
|
|
||||||
|
snprintf(s->buffer, sizeof(s->buffer), "%u:%s:%u",
|
||||||
|
af->sample_rate, sample_format_to_string(af->format),
|
||||||
|
af->channels);
|
||||||
|
|
||||||
|
return s->buffer;
|
||||||
|
}
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright (C) 2003-2009 The Music Player Daemon Project
|
* Copyright (C) 2003-2011 The Music Player Daemon Project
|
||||||
* http://www.musicpd.org
|
* http://www.musicpd.org
|
||||||
*
|
*
|
||||||
* This program is free software; you can redistribute it and/or modify
|
* This program is free software; you can redistribute it and/or modify
|
||||||
@@ -20,27 +20,127 @@
|
|||||||
#ifndef MPD_AUDIO_FORMAT_H
|
#ifndef MPD_AUDIO_FORMAT_H
|
||||||
#define MPD_AUDIO_FORMAT_H
|
#define MPD_AUDIO_FORMAT_H
|
||||||
|
|
||||||
|
#include <glib.h>
|
||||||
#include <stdint.h>
|
#include <stdint.h>
|
||||||
#include <stdbool.h>
|
#include <stdbool.h>
|
||||||
|
#include <assert.h>
|
||||||
|
|
||||||
|
enum sample_format {
|
||||||
|
SAMPLE_FORMAT_UNDEFINED = 0,
|
||||||
|
|
||||||
|
SAMPLE_FORMAT_S8,
|
||||||
|
SAMPLE_FORMAT_S16,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Signed 24 bit integer samples, packed in 32 bit integers
|
||||||
|
* (the most significant byte is filled with the sign bit).
|
||||||
|
*/
|
||||||
|
SAMPLE_FORMAT_S24_P32,
|
||||||
|
|
||||||
|
SAMPLE_FORMAT_S32,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 32 bit floating point samples in the host's format. The
|
||||||
|
* range is -1.0f to +1.0f.
|
||||||
|
*/
|
||||||
|
SAMPLE_FORMAT_FLOAT,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Direct Stream Digital. 1-bit samples; each frame has one
|
||||||
|
* byte (8 samples) per channel.
|
||||||
|
*/
|
||||||
|
SAMPLE_FORMAT_DSD,
|
||||||
|
};
|
||||||
|
|
||||||
|
static const unsigned MAX_CHANNELS = 8;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This structure describes the format of a raw PCM stream.
|
||||||
|
*/
|
||||||
struct audio_format {
|
struct audio_format {
|
||||||
|
/**
|
||||||
|
* The sample rate in Hz. A better name for this attribute is
|
||||||
|
* "frame rate", because technically, you have two samples per
|
||||||
|
* frame in stereo sound.
|
||||||
|
*/
|
||||||
uint32_t sample_rate;
|
uint32_t sample_rate;
|
||||||
uint8_t bits;
|
|
||||||
|
/**
|
||||||
|
* The format samples are stored in. See the #sample_format
|
||||||
|
* enum for valid values.
|
||||||
|
*/
|
||||||
|
uint8_t format;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The number of channels. Only mono (1) and stereo (2) are
|
||||||
|
* fully supported currently.
|
||||||
|
*/
|
||||||
uint8_t channels;
|
uint8_t channels;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Buffer for audio_format_string().
|
||||||
|
*/
|
||||||
|
struct audio_format_string {
|
||||||
|
char buffer[24];
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Clears the #audio_format object, i.e. sets all attributes to an
|
||||||
|
* undefined (invalid) value.
|
||||||
|
*/
|
||||||
static inline void audio_format_clear(struct audio_format *af)
|
static inline void audio_format_clear(struct audio_format *af)
|
||||||
{
|
{
|
||||||
af->sample_rate = 0;
|
af->sample_rate = 0;
|
||||||
af->bits = 0;
|
af->format = SAMPLE_FORMAT_UNDEFINED;
|
||||||
af->channels = 0;
|
af->channels = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initializes an #audio_format object, i.e. sets all
|
||||||
|
* attributes to valid values.
|
||||||
|
*/
|
||||||
|
static inline void audio_format_init(struct audio_format *af,
|
||||||
|
uint32_t sample_rate,
|
||||||
|
enum sample_format format, uint8_t channels)
|
||||||
|
{
|
||||||
|
af->sample_rate = sample_rate;
|
||||||
|
af->format = (uint8_t)format;
|
||||||
|
af->channels = channels;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks whether the specified #audio_format object has a defined
|
||||||
|
* value.
|
||||||
|
*/
|
||||||
static inline bool audio_format_defined(const struct audio_format *af)
|
static inline bool audio_format_defined(const struct audio_format *af)
|
||||||
{
|
{
|
||||||
return af->sample_rate != 0;
|
return af->sample_rate != 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks whether the specified #audio_format object is full, i.e. all
|
||||||
|
* attributes are defined. This is more complete than
|
||||||
|
* audio_format_defined(), but slower.
|
||||||
|
*/
|
||||||
|
static inline bool
|
||||||
|
audio_format_fully_defined(const struct audio_format *af)
|
||||||
|
{
|
||||||
|
return af->sample_rate != 0 && af->format != SAMPLE_FORMAT_UNDEFINED &&
|
||||||
|
af->channels != 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks whether the specified #audio_format object has at least one
|
||||||
|
* defined value.
|
||||||
|
*/
|
||||||
|
static inline bool
|
||||||
|
audio_format_mask_defined(const struct audio_format *af)
|
||||||
|
{
|
||||||
|
return af->sample_rate != 0 || af->format != SAMPLE_FORMAT_UNDEFINED ||
|
||||||
|
af->channels != 0;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Checks whether the sample rate is valid.
|
* Checks whether the sample rate is valid.
|
||||||
*
|
*
|
||||||
@@ -58,9 +158,22 @@ audio_valid_sample_rate(unsigned sample_rate)
|
|||||||
* @param bits the number of significant bits per sample
|
* @param bits the number of significant bits per sample
|
||||||
*/
|
*/
|
||||||
static inline bool
|
static inline bool
|
||||||
audio_valid_sample_format(unsigned bits)
|
audio_valid_sample_format(enum sample_format format)
|
||||||
{
|
{
|
||||||
return bits == 16 || bits == 24 || bits == 32 || bits == 8;
|
switch (format) {
|
||||||
|
case SAMPLE_FORMAT_S8:
|
||||||
|
case SAMPLE_FORMAT_S16:
|
||||||
|
case SAMPLE_FORMAT_S24_P32:
|
||||||
|
case SAMPLE_FORMAT_S32:
|
||||||
|
case SAMPLE_FORMAT_FLOAT:
|
||||||
|
case SAMPLE_FORMAT_DSD:
|
||||||
|
return true;
|
||||||
|
|
||||||
|
case SAMPLE_FORMAT_UNDEFINED:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -69,55 +182,126 @@ audio_valid_sample_format(unsigned bits)
|
|||||||
static inline bool
|
static inline bool
|
||||||
audio_valid_channel_count(unsigned channels)
|
audio_valid_channel_count(unsigned channels)
|
||||||
{
|
{
|
||||||
return channels >= 1 && channels <= 8;
|
return channels >= 1 && channels <= MAX_CHANNELS;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns false if the format is not valid for playback with MPD.
|
* Returns false if the format is not valid for playback with MPD.
|
||||||
* This function performs some basic validity checks.
|
* This function performs some basic validity checks.
|
||||||
*/
|
*/
|
||||||
|
G_GNUC_PURE
|
||||||
static inline bool audio_format_valid(const struct audio_format *af)
|
static inline bool audio_format_valid(const struct audio_format *af)
|
||||||
{
|
{
|
||||||
return audio_valid_sample_rate(af->sample_rate) &&
|
return audio_valid_sample_rate(af->sample_rate) &&
|
||||||
audio_valid_sample_format(af->bits) &&
|
audio_valid_sample_format((enum sample_format)af->format) &&
|
||||||
audio_valid_channel_count(af->channels);
|
audio_valid_channel_count(af->channels);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns false if the format mask is not valid for playback with
|
||||||
|
* MPD. This function performs some basic validity checks.
|
||||||
|
*/
|
||||||
|
G_GNUC_PURE
|
||||||
|
static inline bool audio_format_mask_valid(const struct audio_format *af)
|
||||||
|
{
|
||||||
|
return (af->sample_rate == 0 ||
|
||||||
|
audio_valid_sample_rate(af->sample_rate)) &&
|
||||||
|
(af->format == SAMPLE_FORMAT_UNDEFINED ||
|
||||||
|
audio_valid_sample_format((enum sample_format)af->format)) &&
|
||||||
|
(af->channels == 0 || audio_valid_channel_count(af->channels));
|
||||||
|
}
|
||||||
|
|
||||||
static inline bool audio_format_equals(const struct audio_format *a,
|
static inline bool audio_format_equals(const struct audio_format *a,
|
||||||
const struct audio_format *b)
|
const struct audio_format *b)
|
||||||
{
|
{
|
||||||
return a->sample_rate == b->sample_rate &&
|
return a->sample_rate == b->sample_rate &&
|
||||||
a->bits == b->bits &&
|
a->format == b->format &&
|
||||||
a->channels == b->channels;
|
a->channels == b->channels;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
audio_format_mask_apply(struct audio_format *af,
|
||||||
|
const struct audio_format *mask);
|
||||||
|
|
||||||
|
G_GNUC_CONST
|
||||||
|
static inline unsigned
|
||||||
|
sample_format_size(enum sample_format format)
|
||||||
|
{
|
||||||
|
switch (format) {
|
||||||
|
case SAMPLE_FORMAT_S8:
|
||||||
|
return 1;
|
||||||
|
|
||||||
|
case SAMPLE_FORMAT_S16:
|
||||||
|
return 2;
|
||||||
|
|
||||||
|
case SAMPLE_FORMAT_S24_P32:
|
||||||
|
case SAMPLE_FORMAT_S32:
|
||||||
|
case SAMPLE_FORMAT_FLOAT:
|
||||||
|
return 4;
|
||||||
|
|
||||||
|
case SAMPLE_FORMAT_DSD:
|
||||||
|
/* each frame has 8 samples per channel */
|
||||||
|
return 1;
|
||||||
|
|
||||||
|
case SAMPLE_FORMAT_UNDEFINED:
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
assert(false);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the size of each (mono) sample in bytes.
|
* Returns the size of each (mono) sample in bytes.
|
||||||
*/
|
*/
|
||||||
|
G_GNUC_PURE
|
||||||
static inline unsigned audio_format_sample_size(const struct audio_format *af)
|
static inline unsigned audio_format_sample_size(const struct audio_format *af)
|
||||||
{
|
{
|
||||||
if (af->bits <= 8)
|
return sample_format_size((enum sample_format)af->format);
|
||||||
return 1;
|
|
||||||
else if (af->bits <= 16)
|
|
||||||
return 2;
|
|
||||||
else
|
|
||||||
return 4;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the size of each full frame in bytes.
|
||||||
|
*/
|
||||||
|
G_GNUC_PURE
|
||||||
static inline unsigned
|
static inline unsigned
|
||||||
audio_format_frame_size(const struct audio_format *af)
|
audio_format_frame_size(const struct audio_format *af)
|
||||||
{
|
{
|
||||||
return audio_format_sample_size(af) * af->channels;
|
return audio_format_sample_size(af) * af->channels;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the floating point factor which converts a time span to a
|
||||||
|
* storage size in bytes.
|
||||||
|
*/
|
||||||
|
G_GNUC_PURE
|
||||||
static inline double audio_format_time_to_size(const struct audio_format *af)
|
static inline double audio_format_time_to_size(const struct audio_format *af)
|
||||||
{
|
{
|
||||||
return af->sample_rate * audio_format_frame_size(af);
|
return af->sample_rate * audio_format_frame_size(af);
|
||||||
}
|
}
|
||||||
|
|
||||||
static inline double audioFormatSizeToTime(const struct audio_format *af)
|
/**
|
||||||
{
|
* Renders a #sample_format enum into a string, e.g. for printing it
|
||||||
return 1.0 / audio_format_time_to_size(af);
|
* in a log file.
|
||||||
}
|
*
|
||||||
|
* @param format a #sample_format enum value
|
||||||
|
* @return the string
|
||||||
|
*/
|
||||||
|
G_GNUC_PURE G_GNUC_MALLOC
|
||||||
|
const char *
|
||||||
|
sample_format_to_string(enum sample_format format);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Renders the #audio_format object into a string, e.g. for printing
|
||||||
|
* it in a log file.
|
||||||
|
*
|
||||||
|
* @param af the #audio_format object
|
||||||
|
* @param s a buffer to print into
|
||||||
|
* @return the string, or NULL if the #audio_format object is invalid
|
||||||
|
*/
|
||||||
|
G_GNUC_PURE G_GNUC_MALLOC
|
||||||
|
const char *
|
||||||
|
audio_format_to_string(const struct audio_format *af,
|
||||||
|
struct audio_format_string *s);
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright (C) 2003-2009 The Music Player Daemon Project
|
* Copyright (C) 2003-2011 The Music Player Daemon Project
|
||||||
* http://www.musicpd.org
|
* http://www.musicpd.org
|
||||||
*
|
*
|
||||||
* This program is free software; you can redistribute it and/or modify
|
* This program is free software; you can redistribute it and/or modify
|
||||||
@@ -22,9 +22,14 @@
|
|||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
#include "config.h"
|
||||||
#include "audio_parser.h"
|
#include "audio_parser.h"
|
||||||
#include "audio_format.h"
|
#include "audio_format.h"
|
||||||
|
#include "audio_check.h"
|
||||||
|
#include "gcc.h"
|
||||||
|
|
||||||
|
#include <assert.h>
|
||||||
|
#include <string.h>
|
||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -36,64 +41,182 @@ audio_parser_quark(void)
|
|||||||
return g_quark_from_static_string("audio_parser");
|
return g_quark_from_static_string("audio_parser");
|
||||||
}
|
}
|
||||||
|
|
||||||
bool
|
static bool
|
||||||
audio_format_parse(struct audio_format *dest, const char *src, GError **error)
|
parse_sample_rate(const char *src, bool mask, uint32_t *sample_rate_r,
|
||||||
|
const char **endptr_r, GError **error_r)
|
||||||
{
|
{
|
||||||
char *endptr;
|
|
||||||
unsigned long value;
|
unsigned long value;
|
||||||
|
char *endptr;
|
||||||
|
|
||||||
|
if (mask && *src == '*') {
|
||||||
|
*sample_rate_r = 0;
|
||||||
|
*endptr_r = src + 1;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
value = strtoul(src, &endptr, 10);
|
||||||
|
if (endptr == src) {
|
||||||
|
g_set_error(error_r, audio_parser_quark(), 0,
|
||||||
|
"Failed to parse the sample rate");
|
||||||
|
return false;
|
||||||
|
} else if (!audio_check_sample_rate(value, error_r))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
*sample_rate_r = value;
|
||||||
|
*endptr_r = endptr;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool
|
||||||
|
parse_sample_format(const char *src, bool mask,
|
||||||
|
enum sample_format *sample_format_r,
|
||||||
|
const char **endptr_r, GError **error_r)
|
||||||
|
{
|
||||||
|
unsigned long value;
|
||||||
|
char *endptr;
|
||||||
|
enum sample_format sample_format;
|
||||||
|
|
||||||
|
if (mask && *src == '*') {
|
||||||
|
*sample_format_r = SAMPLE_FORMAT_UNDEFINED;
|
||||||
|
*endptr_r = src + 1;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (*src == 'f') {
|
||||||
|
*sample_format_r = SAMPLE_FORMAT_FLOAT;
|
||||||
|
*endptr_r = src + 1;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (memcmp(src, "dsd", 3) == 0) {
|
||||||
|
*sample_format_r = SAMPLE_FORMAT_DSD;
|
||||||
|
*endptr_r = src + 3;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
value = strtoul(src, &endptr, 10);
|
||||||
|
if (endptr == src) {
|
||||||
|
g_set_error(error_r, audio_parser_quark(), 0,
|
||||||
|
"Failed to parse the sample format");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (value) {
|
||||||
|
case 8:
|
||||||
|
sample_format = SAMPLE_FORMAT_S8;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 16:
|
||||||
|
sample_format = SAMPLE_FORMAT_S16;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 24:
|
||||||
|
if (memcmp(endptr, "_3", 2) == 0)
|
||||||
|
/* for backwards compatibility */
|
||||||
|
endptr += 2;
|
||||||
|
|
||||||
|
sample_format = SAMPLE_FORMAT_S24_P32;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 32:
|
||||||
|
sample_format = SAMPLE_FORMAT_S32;
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
g_set_error(error_r, audio_parser_quark(), 0,
|
||||||
|
"Invalid sample format: %lu", value);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
assert(audio_valid_sample_format(sample_format));
|
||||||
|
|
||||||
|
*sample_format_r = sample_format;
|
||||||
|
*endptr_r = endptr;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool
|
||||||
|
parse_channel_count(const char *src, bool mask, uint8_t *channels_r,
|
||||||
|
const char **endptr_r, GError **error_r)
|
||||||
|
{
|
||||||
|
unsigned long value;
|
||||||
|
char *endptr;
|
||||||
|
|
||||||
|
if (mask && *src == '*') {
|
||||||
|
*channels_r = 0;
|
||||||
|
*endptr_r = src + 1;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
value = strtoul(src, &endptr, 10);
|
||||||
|
if (endptr == src) {
|
||||||
|
g_set_error(error_r, audio_parser_quark(), 0,
|
||||||
|
"Failed to parse the channel count");
|
||||||
|
return false;
|
||||||
|
} else if (!audio_check_channel_count(value, error_r))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
*channels_r = value;
|
||||||
|
*endptr_r = endptr;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool
|
||||||
|
audio_format_parse(struct audio_format *dest, const char *src,
|
||||||
|
bool mask, GError **error_r)
|
||||||
|
{
|
||||||
|
uint32_t rate;
|
||||||
|
enum sample_format sample_format;
|
||||||
|
uint8_t channels;
|
||||||
|
|
||||||
audio_format_clear(dest);
|
audio_format_clear(dest);
|
||||||
|
|
||||||
/* parse sample rate */
|
/* parse sample rate */
|
||||||
|
|
||||||
value = strtoul(src, &endptr, 10);
|
#if GCC_CHECK_VERSION(4,7)
|
||||||
if (endptr == src) {
|
/* workaround -Wmaybe-uninitialized false positive */
|
||||||
g_set_error(error, audio_parser_quark(), 0,
|
rate = 0;
|
||||||
"Sample rate missing");
|
#endif
|
||||||
|
|
||||||
|
if (!parse_sample_rate(src, mask, &rate, &src, error_r))
|
||||||
return false;
|
return false;
|
||||||
} else if (*endptr != ':') {
|
|
||||||
g_set_error(error, audio_parser_quark(), 0,
|
if (*src++ != ':') {
|
||||||
|
g_set_error(error_r, audio_parser_quark(), 0,
|
||||||
"Sample format missing");
|
"Sample format missing");
|
||||||
return false;
|
return false;
|
||||||
} else if (!audio_valid_sample_rate(value)) {
|
|
||||||
g_set_error(error, audio_parser_quark(), 0,
|
|
||||||
"Invalid sample rate: %lu", value);
|
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
dest->sample_rate = value;
|
|
||||||
|
|
||||||
/* parse sample format */
|
/* parse sample format */
|
||||||
|
|
||||||
src = endptr + 1;
|
#if GCC_CHECK_VERSION(4,7)
|
||||||
value = strtoul(src, &endptr, 10);
|
/* workaround -Wmaybe-uninitialized false positive */
|
||||||
if (endptr == src) {
|
sample_format = SAMPLE_FORMAT_UNDEFINED;
|
||||||
g_set_error(error, audio_parser_quark(), 0,
|
#endif
|
||||||
"Sample format missing");
|
|
||||||
|
if (!parse_sample_format(src, mask, &sample_format, &src, error_r))
|
||||||
return false;
|
return false;
|
||||||
} else if (*endptr != ':') {
|
|
||||||
g_set_error(error, audio_parser_quark(), 0,
|
if (*src++ != ':') {
|
||||||
|
g_set_error(error_r, audio_parser_quark(), 0,
|
||||||
"Channel count missing");
|
"Channel count missing");
|
||||||
return false;
|
return false;
|
||||||
} else if (!audio_valid_sample_format(value)) {
|
|
||||||
g_set_error(error, audio_parser_quark(), 0,
|
|
||||||
"Invalid sample format: %lu", value);
|
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
dest->bits = value;
|
|
||||||
|
|
||||||
/* parse channel count */
|
/* parse channel count */
|
||||||
|
|
||||||
src = endptr + 1;
|
if (!parse_channel_count(src, mask, &channels, &src, error_r))
|
||||||
value = strtoul(src, &endptr, 10);
|
return false;
|
||||||
if (*endptr != 0 || !audio_valid_channel_count(value)) {
|
|
||||||
g_set_error(error, audio_parser_quark(), 0,
|
if (*src != 0) {
|
||||||
"Invalid channel count: %s", src);
|
g_set_error(error_r, audio_parser_quark(), 0,
|
||||||
|
"Extra data after channel count: %s", src);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
dest->channels = value;
|
audio_format_init(dest, rate, sample_format, channels);
|
||||||
|
assert(mask ? audio_format_mask_valid(dest)
|
||||||
|
: audio_format_valid(dest));
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright (C) 2003-2009 The Music Player Daemon Project
|
* Copyright (C) 2003-2011 The Music Player Daemon Project
|
||||||
* http://www.musicpd.org
|
* http://www.musicpd.org
|
||||||
*
|
*
|
||||||
* This program is free software; you can redistribute it and/or modify
|
* This program is free software; you can redistribute it and/or modify
|
||||||
@@ -37,11 +37,13 @@ struct audio_format;
|
|||||||
*
|
*
|
||||||
* @param dest the destination #audio_format struct
|
* @param dest the destination #audio_format struct
|
||||||
* @param src the input string
|
* @param src the input string
|
||||||
* @param error location to store the error occuring, or NULL to
|
* @param mask if true, then "*" is allowed for any number of items
|
||||||
|
* @param error_r location to store the error occurring, or NULL to
|
||||||
* ignore errors
|
* ignore errors
|
||||||
* @return true on success
|
* @return true on success
|
||||||
*/
|
*/
|
||||||
bool
|
bool
|
||||||
audio_format_parse(struct audio_format *dest, const char *src, GError **error);
|
audio_format_parse(struct audio_format *dest, const char *src,
|
||||||
|
bool mask, GError **error_r);
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright (C) 2003-2009 The Music Player Daemon Project
|
* Copyright (C) 2003-2011 The Music Player Daemon Project
|
||||||
* http://www.musicpd.org
|
* http://www.musicpd.org
|
||||||
*
|
*
|
||||||
* This program is free software; you can redistribute it and/or modify
|
* This program is free software; you can redistribute it and/or modify
|
||||||
@@ -17,6 +17,7 @@
|
|||||||
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
#include "config.h"
|
||||||
#include "buffer.h"
|
#include "buffer.h"
|
||||||
#include "chunk.h"
|
#include "chunk.h"
|
||||||
#include "poison.h"
|
#include "poison.h"
|
||||||
@@ -117,6 +118,9 @@ music_buffer_return(struct music_buffer *buffer, struct music_chunk *chunk)
|
|||||||
assert(buffer != NULL);
|
assert(buffer != NULL);
|
||||||
assert(chunk != NULL);
|
assert(chunk != NULL);
|
||||||
|
|
||||||
|
if (chunk->other != NULL)
|
||||||
|
music_buffer_return(buffer, chunk->other);
|
||||||
|
|
||||||
g_mutex_lock(buffer->mutex);
|
g_mutex_lock(buffer->mutex);
|
||||||
|
|
||||||
music_chunk_free(chunk);
|
music_chunk_free(chunk);
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright (C) 2003-2009 The Music Player Daemon Project
|
* Copyright (C) 2003-2011 The Music Player Daemon Project
|
||||||
* http://www.musicpd.org
|
* http://www.musicpd.org
|
||||||
*
|
*
|
||||||
* This program is free software; you can redistribute it and/or modify
|
* This program is free software; you can redistribute it and/or modify
|
||||||
|
|||||||
@@ -1,133 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (C) 2003-2009 The Music Player Daemon Project
|
|
||||||
* http://www.musicpd.org
|
|
||||||
*
|
|
||||||
* This program is free software; you can redistribute it and/or modify
|
|
||||||
* it under the terms of the GNU General Public License as published by
|
|
||||||
* the Free Software Foundation; either version 2 of the License, or
|
|
||||||
* (at your option) any later version.
|
|
||||||
*
|
|
||||||
* This program is distributed in the hope that it will be useful,
|
|
||||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
* GNU General Public License for more details.
|
|
||||||
*
|
|
||||||
* You should have received a copy of the GNU General Public License along
|
|
||||||
* with this program; if not, write to the Free Software Foundation, Inc.,
|
|
||||||
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
|
||||||
*/
|
|
||||||
|
|
||||||
#include "buffer2array.h"
|
|
||||||
|
|
||||||
#include <glib.h>
|
|
||||||
|
|
||||||
#include <string.h>
|
|
||||||
|
|
||||||
int buffer2array(char *buffer, char *array[], const int max)
|
|
||||||
{
|
|
||||||
int i = 0;
|
|
||||||
char *c = buffer;
|
|
||||||
|
|
||||||
while (*c != '\0' && i < max) {
|
|
||||||
if (*c == '\"') {
|
|
||||||
array[i++] = ++c;
|
|
||||||
while (*c != '\0') {
|
|
||||||
if (*c == '\"') {
|
|
||||||
*(c++) = '\0';
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
else if (*(c++) == '\\' && *c != '\0') {
|
|
||||||
memmove(c - 1, c, strlen(c) + 1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
c = g_strchug(c);
|
|
||||||
if (*c == '\0')
|
|
||||||
return i;
|
|
||||||
|
|
||||||
array[i++] = c++;
|
|
||||||
|
|
||||||
while (!g_ascii_isspace(*c) && *c != '\0')
|
|
||||||
++c;
|
|
||||||
}
|
|
||||||
if (*c == '\0')
|
|
||||||
return i;
|
|
||||||
*(c++) = '\0';
|
|
||||||
|
|
||||||
c = g_strchug(c);
|
|
||||||
}
|
|
||||||
return i;
|
|
||||||
}
|
|
||||||
|
|
||||||
#ifdef UNIT_TEST
|
|
||||||
|
|
||||||
#include <stdio.h>
|
|
||||||
#include <string.h>
|
|
||||||
#include <assert.h>
|
|
||||||
|
|
||||||
int main()
|
|
||||||
{
|
|
||||||
char *a[4] = { NULL };
|
|
||||||
char *b;
|
|
||||||
int max;
|
|
||||||
|
|
||||||
b = strdup("lsinfo \"/some/dir/name \\\"test\\\"\"");
|
|
||||||
assert(b);
|
|
||||||
max = buffer2array(b, a, 4);
|
|
||||||
assert( !strcmp("lsinfo", a[0]) );
|
|
||||||
assert( !strcmp("/some/dir/name \"test\"", a[1]) );
|
|
||||||
assert( !a[2] );
|
|
||||||
|
|
||||||
b = strdup("lsinfo \"/some/dir/name \\\"test\\\" something else\"");
|
|
||||||
assert(b);
|
|
||||||
max = buffer2array(b, a, 4);
|
|
||||||
assert( !strcmp("lsinfo", a[0]) );
|
|
||||||
assert( !strcmp("/some/dir/name \"test\" something else", a[1]) );
|
|
||||||
assert( !a[2] );
|
|
||||||
|
|
||||||
b = strdup("lsinfo \"/some/dir\\\\name\"");
|
|
||||||
assert(b);
|
|
||||||
max = buffer2array(b, a, 4);
|
|
||||||
assert( !strcmp("lsinfo", a[0]) );
|
|
||||||
assert( !strcmp("/some/dir\\name", a[1]) );
|
|
||||||
assert( !a[2] );
|
|
||||||
|
|
||||||
b = strdup("lsinfo \"/some/dir name\"");
|
|
||||||
assert(b);
|
|
||||||
max = buffer2array(b, a, 4);
|
|
||||||
assert( !strcmp("lsinfo", a[0]) );
|
|
||||||
assert( !strcmp("/some/dir name", a[1]) );
|
|
||||||
assert( !a[2] );
|
|
||||||
|
|
||||||
b = strdup("lsinfo \"\\\"/some/dir\\\"\"");
|
|
||||||
assert(b);
|
|
||||||
max = buffer2array(b, a, 4);
|
|
||||||
assert( !strcmp("lsinfo", a[0]) );
|
|
||||||
assert( !strcmp("\"/some/dir\"", a[1]) );
|
|
||||||
assert( !a[2] );
|
|
||||||
|
|
||||||
b = strdup("lsinfo \"\\\"/some/dir\\\" x\"");
|
|
||||||
assert(b);
|
|
||||||
max = buffer2array(b, a, 4);
|
|
||||||
assert( !strcmp("lsinfo", a[0]) );
|
|
||||||
assert( !strcmp("\"/some/dir\" x", a[1]) );
|
|
||||||
assert( !a[2] );
|
|
||||||
|
|
||||||
b = strdup("lsinfo \"single quote\\'d from php magicquotes\"");
|
|
||||||
assert(b);
|
|
||||||
max = buffer2array(b, a, 4);
|
|
||||||
assert( !strcmp("lsinfo", a[0]) );
|
|
||||||
assert( !strcmp("single quote\'d from php magicquotes", a[1]) );
|
|
||||||
assert( !a[2] );
|
|
||||||
|
|
||||||
b = strdup("lsinfo \"double quote\\\"d from php magicquotes\"");
|
|
||||||
assert(b);
|
|
||||||
max = buffer2array(b, a, 4);
|
|
||||||
assert( !strcmp("lsinfo", a[0]) );
|
|
||||||
assert( !strcmp("double quote\"d from php magicquotes", a[1]) );
|
|
||||||
assert( !a[2] );
|
|
||||||
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
#endif
|
|
||||||
47
src/check.h
Normal file
47
src/check.h
Normal file
@@ -0,0 +1,47 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2003-2011 The Music Player Daemon Project
|
||||||
|
* http://www.musicpd.org
|
||||||
|
*
|
||||||
|
* This program is free software; you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation; either version 2 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License along
|
||||||
|
* with this program; if not, write to the Free Software Foundation, Inc.,
|
||||||
|
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef MPD_CHECK_H
|
||||||
|
#define MPD_CHECK_H
|
||||||
|
|
||||||
|
/*
|
||||||
|
* All sources must include config.h on the first line to ensure that
|
||||||
|
* Large File Support is configured properly. This header checks
|
||||||
|
* whether this has happened.
|
||||||
|
*
|
||||||
|
* Usage: include this header before you use any of the above types.
|
||||||
|
* It will stop the compiler if something went wrong.
|
||||||
|
*
|
||||||
|
* This is Linux/glibc specific, and only enabled in the debug build,
|
||||||
|
* so bugs in this headers don't affect users with production builds.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef PACKAGE_VERSION
|
||||||
|
#error config.h missing
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#if defined(__linux__) && !defined(NDEBUG) && defined(ENABLE_LARGEFILE) && \
|
||||||
|
defined(_FEATURES_H) && defined(__i386__) && \
|
||||||
|
!defined(__USE_FILE_OFFSET64)
|
||||||
|
/* on i386, check if LFS is enabled */
|
||||||
|
#error config.h was included too late
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#endif
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright (C) 2003-2009 The Music Player Daemon Project
|
* Copyright (C) 2003-2011 The Music Player Daemon Project
|
||||||
* http://www.musicpd.org
|
* http://www.musicpd.org
|
||||||
*
|
*
|
||||||
* This program is free software; you can redistribute it and/or modify
|
* This program is free software; you can redistribute it and/or modify
|
||||||
@@ -17,6 +17,7 @@
|
|||||||
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
#include "config.h"
|
||||||
#include "chunk.h"
|
#include "chunk.h"
|
||||||
#include "audio_format.h"
|
#include "audio_format.h"
|
||||||
#include "tag.h"
|
#include "tag.h"
|
||||||
@@ -26,8 +27,10 @@
|
|||||||
void
|
void
|
||||||
music_chunk_init(struct music_chunk *chunk)
|
music_chunk_init(struct music_chunk *chunk)
|
||||||
{
|
{
|
||||||
|
chunk->other = NULL;
|
||||||
chunk->length = 0;
|
chunk->length = 0;
|
||||||
chunk->tag = NULL;
|
chunk->tag = NULL;
|
||||||
|
chunk->replay_gain_serial = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
|
|||||||
29
src/chunk.h
29
src/chunk.h
@@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright (C) 2003-2009 The Music Player Daemon Project
|
* Copyright (C) 2003-2011 The Music Player Daemon Project
|
||||||
* http://www.musicpd.org
|
* http://www.musicpd.org
|
||||||
*
|
*
|
||||||
* This program is free software; you can redistribute it and/or modify
|
* This program is free software; you can redistribute it and/or modify
|
||||||
@@ -20,6 +20,8 @@
|
|||||||
#ifndef MPD_CHUNK_H
|
#ifndef MPD_CHUNK_H
|
||||||
#define MPD_CHUNK_H
|
#define MPD_CHUNK_H
|
||||||
|
|
||||||
|
#include "replay_gain_info.h"
|
||||||
|
|
||||||
#ifndef NDEBUG
|
#ifndef NDEBUG
|
||||||
#include "audio_format.h"
|
#include "audio_format.h"
|
||||||
#endif
|
#endif
|
||||||
@@ -42,6 +44,18 @@ struct music_chunk {
|
|||||||
/** the next chunk in a linked list */
|
/** the next chunk in a linked list */
|
||||||
struct music_chunk *next;
|
struct music_chunk *next;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An optional chunk which should be mixed into this chunk.
|
||||||
|
* This is used for cross-fading.
|
||||||
|
*/
|
||||||
|
struct music_chunk *other;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The current mix ratio for cross-fading: 1.0 means play 100%
|
||||||
|
* of this chunk, 0.0 means play 100% of the "other" chunk.
|
||||||
|
*/
|
||||||
|
float mix_ratio;
|
||||||
|
|
||||||
/** number of bytes stored in this chunk */
|
/** number of bytes stored in this chunk */
|
||||||
uint16_t length;
|
uint16_t length;
|
||||||
|
|
||||||
@@ -59,6 +73,19 @@ struct music_chunk {
|
|||||||
*/
|
*/
|
||||||
struct tag *tag;
|
struct tag *tag;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Replay gain information associated with this chunk.
|
||||||
|
* Only valid if the serial is not 0.
|
||||||
|
*/
|
||||||
|
struct replay_gain_info replay_gain_info;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A serial number for checking if replay gain info has
|
||||||
|
* changed since the last chunk. The magic value 0 indicates
|
||||||
|
* that there is no replay gain info available.
|
||||||
|
*/
|
||||||
|
unsigned replay_gain_serial;
|
||||||
|
|
||||||
/** the data (probably PCM) */
|
/** the data (probably PCM) */
|
||||||
char data[CHUNK_SIZE];
|
char data[CHUNK_SIZE];
|
||||||
|
|
||||||
|
|||||||
885
src/client.c
885
src/client.c
@@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright (C) 2003-2009 The Music Player Daemon Project
|
* Copyright (C) 2003-2011 The Music Player Daemon Project
|
||||||
* http://www.musicpd.org
|
* http://www.musicpd.org
|
||||||
*
|
*
|
||||||
* This program is free software; you can redistribute it and/or modify
|
* This program is free software; you can redistribute it and/or modify
|
||||||
@@ -17,110 +17,8 @@
|
|||||||
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#include "client.h"
|
|
||||||
#include "fifo_buffer.h"
|
|
||||||
#include "command.h"
|
|
||||||
#include "conf.h"
|
|
||||||
#include "listen.h"
|
|
||||||
#include "socket_util.h"
|
|
||||||
#include "permission.h"
|
|
||||||
#include "event_pipe.h"
|
|
||||||
#include "idle.h"
|
|
||||||
#include "main.h"
|
|
||||||
#include "config.h"
|
#include "config.h"
|
||||||
|
#include "client_internal.h"
|
||||||
#include <glib.h>
|
|
||||||
#include <assert.h>
|
|
||||||
#include <unistd.h>
|
|
||||||
#include <string.h>
|
|
||||||
#include <stdlib.h>
|
|
||||||
#include <stdio.h>
|
|
||||||
#include <errno.h>
|
|
||||||
|
|
||||||
#ifdef WIN32
|
|
||||||
#include <ws2tcpip.h>
|
|
||||||
#include <winsock.h>
|
|
||||||
#else
|
|
||||||
#include <sys/socket.h>
|
|
||||||
#include <netinet/in.h>
|
|
||||||
#include <arpa/inet.h>
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#undef G_LOG_DOMAIN
|
|
||||||
#define G_LOG_DOMAIN "client"
|
|
||||||
#define LOG_LEVEL_SECURE G_LOG_LEVEL_INFO
|
|
||||||
|
|
||||||
static const char GREETING[] = "OK MPD " PROTOCOL_VERSION "\n";
|
|
||||||
|
|
||||||
#define CLIENT_LIST_MODE_BEGIN "command_list_begin"
|
|
||||||
#define CLIENT_LIST_OK_MODE_BEGIN "command_list_ok_begin"
|
|
||||||
#define CLIENT_LIST_MODE_END "command_list_end"
|
|
||||||
#define CLIENT_TIMEOUT_DEFAULT (60)
|
|
||||||
#define CLIENT_MAX_CONNECTIONS_DEFAULT (10)
|
|
||||||
#define CLIENT_MAX_COMMAND_LIST_DEFAULT (2048*1024)
|
|
||||||
#define CLIENT_MAX_OUTPUT_BUFFER_SIZE_DEFAULT (8192*1024)
|
|
||||||
|
|
||||||
/* set this to zero to indicate we have no possible clients */
|
|
||||||
static unsigned int client_max_connections; /*CLIENT_MAX_CONNECTIONS_DEFAULT; */
|
|
||||||
static int client_timeout;
|
|
||||||
static size_t client_max_command_list_size;
|
|
||||||
static size_t client_max_output_buffer_size;
|
|
||||||
|
|
||||||
struct deferred_buffer {
|
|
||||||
size_t size;
|
|
||||||
char data[sizeof(long)];
|
|
||||||
};
|
|
||||||
|
|
||||||
struct client {
|
|
||||||
GIOChannel *channel;
|
|
||||||
guint source_id;
|
|
||||||
|
|
||||||
/** the buffer for reading lines from the #channel */
|
|
||||||
struct fifo_buffer *input;
|
|
||||||
|
|
||||||
unsigned permission;
|
|
||||||
|
|
||||||
/** the uid of the client process, or -1 if unknown */
|
|
||||||
int uid;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* How long since the last activity from this client?
|
|
||||||
*/
|
|
||||||
GTimer *last_activity;
|
|
||||||
|
|
||||||
GSList *cmd_list; /* for when in list mode */
|
|
||||||
int cmd_list_OK; /* print OK after each command execution */
|
|
||||||
size_t cmd_list_size; /* mem cmd_list consumes */
|
|
||||||
GQueue *deferred_send; /* for output if client is slow */
|
|
||||||
size_t deferred_bytes; /* mem deferred_send consumes */
|
|
||||||
unsigned int num; /* client number */
|
|
||||||
|
|
||||||
char send_buf[4096];
|
|
||||||
size_t send_buf_used; /* bytes used this instance */
|
|
||||||
|
|
||||||
/** is this client waiting for an "idle" response? */
|
|
||||||
bool idle_waiting;
|
|
||||||
|
|
||||||
/** idle flags pending on this client, to be sent as soon as
|
|
||||||
the client enters "idle" */
|
|
||||||
unsigned idle_flags;
|
|
||||||
|
|
||||||
/** idle flags that the client wants to receive */
|
|
||||||
unsigned idle_subscriptions;
|
|
||||||
};
|
|
||||||
|
|
||||||
static GList *clients;
|
|
||||||
static unsigned num_clients;
|
|
||||||
static guint expire_source_id;
|
|
||||||
|
|
||||||
static void client_write_deferred(struct client *client);
|
|
||||||
|
|
||||||
static void client_write_output(struct client *client);
|
|
||||||
|
|
||||||
static void client_manager_expire(void);
|
|
||||||
|
|
||||||
static gboolean
|
|
||||||
client_in_event(GIOChannel *source, GIOCondition condition, gpointer data);
|
|
||||||
|
|
||||||
bool client_is_expired(const struct client *client)
|
bool client_is_expired(const struct client *client)
|
||||||
{
|
{
|
||||||
@@ -141,782 +39,3 @@ void client_set_permission(struct client *client, unsigned permission)
|
|||||||
{
|
{
|
||||||
client->permission = permission;
|
client->permission = permission;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* An idle event which calls client_manager_expire().
|
|
||||||
*/
|
|
||||||
static gboolean
|
|
||||||
client_manager_expire_event(G_GNUC_UNUSED gpointer data)
|
|
||||||
{
|
|
||||||
expire_source_id = 0;
|
|
||||||
client_manager_expire();
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
static inline void client_set_expired(struct client *client)
|
|
||||||
{
|
|
||||||
if (expire_source_id == 0 && !client_is_expired(client))
|
|
||||||
/* delayed deletion */
|
|
||||||
expire_source_id = g_idle_add(client_manager_expire_event,
|
|
||||||
NULL);
|
|
||||||
|
|
||||||
if (client->source_id != 0) {
|
|
||||||
g_source_remove(client->source_id);
|
|
||||||
client->source_id = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (client->channel != NULL) {
|
|
||||||
g_io_channel_unref(client->channel);
|
|
||||||
client->channel = NULL;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static void client_init(struct client *client, int fd)
|
|
||||||
{
|
|
||||||
static unsigned int next_client_num;
|
|
||||||
|
|
||||||
assert(fd >= 0);
|
|
||||||
|
|
||||||
client->cmd_list_size = 0;
|
|
||||||
client->cmd_list_OK = -1;
|
|
||||||
|
|
||||||
#ifndef G_OS_WIN32
|
|
||||||
client->channel = g_io_channel_unix_new(fd);
|
|
||||||
#else
|
|
||||||
client->channel = g_io_channel_win32_new_socket(fd);
|
|
||||||
#endif
|
|
||||||
/* GLib is responsible for closing the file descriptor */
|
|
||||||
g_io_channel_set_close_on_unref(client->channel, true);
|
|
||||||
/* NULL encoding means the stream is binary safe; the MPD
|
|
||||||
protocol is UTF-8 only, but we are doing this call anyway
|
|
||||||
to prevent GLib from messing around with the stream */
|
|
||||||
g_io_channel_set_encoding(client->channel, NULL, NULL);
|
|
||||||
/* we prefer to do buffering */
|
|
||||||
g_io_channel_set_buffered(client->channel, false);
|
|
||||||
|
|
||||||
client->source_id = g_io_add_watch(client->channel,
|
|
||||||
G_IO_IN|G_IO_ERR|G_IO_HUP,
|
|
||||||
client_in_event, client);
|
|
||||||
|
|
||||||
client->input = fifo_buffer_new(4096);
|
|
||||||
|
|
||||||
client->cmd_list = NULL;
|
|
||||||
client->deferred_send = g_queue_new();
|
|
||||||
client->deferred_bytes = 0;
|
|
||||||
client->num = next_client_num++;
|
|
||||||
client->send_buf_used = 0;
|
|
||||||
|
|
||||||
client->permission = getDefaultPermissions();
|
|
||||||
|
|
||||||
(void)write(fd, GREETING, sizeof(GREETING) - 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
static void free_cmd_list(GSList *list)
|
|
||||||
{
|
|
||||||
for (GSList *tmp = list; tmp != NULL; tmp = g_slist_next(tmp))
|
|
||||||
g_free(tmp->data);
|
|
||||||
|
|
||||||
g_slist_free(list);
|
|
||||||
}
|
|
||||||
|
|
||||||
static void new_cmd_list_ptr(struct client *client, char *s)
|
|
||||||
{
|
|
||||||
client->cmd_list = g_slist_prepend(client->cmd_list, g_strdup(s));
|
|
||||||
}
|
|
||||||
|
|
||||||
static void
|
|
||||||
deferred_buffer_free(gpointer data, G_GNUC_UNUSED gpointer user_data)
|
|
||||||
{
|
|
||||||
struct deferred_buffer *buffer = data;
|
|
||||||
g_free(buffer);
|
|
||||||
}
|
|
||||||
|
|
||||||
static void client_close(struct client *client)
|
|
||||||
{
|
|
||||||
assert(num_clients > 0);
|
|
||||||
assert(clients != NULL);
|
|
||||||
|
|
||||||
clients = g_list_remove(clients, client);
|
|
||||||
--num_clients;
|
|
||||||
|
|
||||||
client_set_expired(client);
|
|
||||||
|
|
||||||
g_timer_destroy(client->last_activity);
|
|
||||||
|
|
||||||
if (client->cmd_list) {
|
|
||||||
free_cmd_list(client->cmd_list);
|
|
||||||
client->cmd_list = NULL;
|
|
||||||
}
|
|
||||||
|
|
||||||
g_queue_foreach(client->deferred_send, deferred_buffer_free, NULL);
|
|
||||||
g_queue_free(client->deferred_send);
|
|
||||||
|
|
||||||
fifo_buffer_free(client->input);
|
|
||||||
|
|
||||||
g_log(G_LOG_DOMAIN, LOG_LEVEL_SECURE,
|
|
||||||
"[%u] closed", client->num);
|
|
||||||
g_free(client);
|
|
||||||
}
|
|
||||||
|
|
||||||
void client_new(int fd, const struct sockaddr *sa, size_t sa_length, int uid)
|
|
||||||
{
|
|
||||||
struct client *client;
|
|
||||||
char *remote;
|
|
||||||
|
|
||||||
if (num_clients >= client_max_connections) {
|
|
||||||
g_warning("Max Connections Reached!");
|
|
||||||
close(fd);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
client = g_new0(struct client, 1);
|
|
||||||
clients = g_list_prepend(clients, client);
|
|
||||||
++num_clients;
|
|
||||||
|
|
||||||
client_init(client, fd);
|
|
||||||
client->uid = uid;
|
|
||||||
|
|
||||||
client->last_activity = g_timer_new();
|
|
||||||
|
|
||||||
remote = sockaddr_to_string(sa, sa_length, NULL);
|
|
||||||
g_log(G_LOG_DOMAIN, LOG_LEVEL_SECURE,
|
|
||||||
"[%u] opened from %s", client->num, remote);
|
|
||||||
g_free(remote);
|
|
||||||
}
|
|
||||||
|
|
||||||
static int client_process_line(struct client *client, char *line)
|
|
||||||
{
|
|
||||||
int ret = 1;
|
|
||||||
|
|
||||||
if (strcmp(line, "noidle") == 0) {
|
|
||||||
if (client->idle_waiting) {
|
|
||||||
/* send empty idle response and leave idle mode */
|
|
||||||
client->idle_waiting = false;
|
|
||||||
command_success(client);
|
|
||||||
client_write_output(client);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* do nothing if the client wasn't idling: the client
|
|
||||||
has already received the full idle response from
|
|
||||||
client_idle_notify(), which he can now evaluate */
|
|
||||||
|
|
||||||
return 0;
|
|
||||||
} else if (client->idle_waiting) {
|
|
||||||
/* during idle mode, clients must not send anything
|
|
||||||
except "noidle" */
|
|
||||||
g_warning("[%u] command \"%s\" during idle",
|
|
||||||
client->num, line);
|
|
||||||
return COMMAND_RETURN_CLOSE;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (client->cmd_list_OK >= 0) {
|
|
||||||
if (strcmp(line, CLIENT_LIST_MODE_END) == 0) {
|
|
||||||
g_debug("[%u] process command list",
|
|
||||||
client->num);
|
|
||||||
|
|
||||||
/* for scalability reasons, we have prepended
|
|
||||||
each new command; now we have to reverse it
|
|
||||||
to restore the correct order */
|
|
||||||
client->cmd_list = g_slist_reverse(client->cmd_list);
|
|
||||||
|
|
||||||
ret = command_process_list(client,
|
|
||||||
client->cmd_list_OK,
|
|
||||||
client->cmd_list);
|
|
||||||
g_debug("[%u] process command "
|
|
||||||
"list returned %i", client->num, ret);
|
|
||||||
|
|
||||||
if (ret == COMMAND_RETURN_CLOSE ||
|
|
||||||
client_is_expired(client))
|
|
||||||
return COMMAND_RETURN_CLOSE;
|
|
||||||
|
|
||||||
if (ret == 0)
|
|
||||||
command_success(client);
|
|
||||||
|
|
||||||
client_write_output(client);
|
|
||||||
free_cmd_list(client->cmd_list);
|
|
||||||
client->cmd_list = NULL;
|
|
||||||
client->cmd_list_OK = -1;
|
|
||||||
} else {
|
|
||||||
size_t len = strlen(line) + 1;
|
|
||||||
client->cmd_list_size += len;
|
|
||||||
if (client->cmd_list_size >
|
|
||||||
client_max_command_list_size) {
|
|
||||||
g_warning("[%u] command list size (%lu) "
|
|
||||||
"is larger than the max (%lu)",
|
|
||||||
client->num,
|
|
||||||
(unsigned long)client->cmd_list_size,
|
|
||||||
(unsigned long)client_max_command_list_size);
|
|
||||||
return COMMAND_RETURN_CLOSE;
|
|
||||||
} else
|
|
||||||
new_cmd_list_ptr(client, line);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if (strcmp(line, CLIENT_LIST_MODE_BEGIN) == 0) {
|
|
||||||
client->cmd_list_OK = 0;
|
|
||||||
ret = 1;
|
|
||||||
} else if (strcmp(line, CLIENT_LIST_OK_MODE_BEGIN) == 0) {
|
|
||||||
client->cmd_list_OK = 1;
|
|
||||||
ret = 1;
|
|
||||||
} else {
|
|
||||||
g_debug("[%u] process command \"%s\"",
|
|
||||||
client->num, line);
|
|
||||||
ret = command_process(client, line);
|
|
||||||
g_debug("[%u] command returned %i",
|
|
||||||
client->num, ret);
|
|
||||||
|
|
||||||
if (ret == COMMAND_RETURN_CLOSE ||
|
|
||||||
client_is_expired(client))
|
|
||||||
return COMMAND_RETURN_CLOSE;
|
|
||||||
|
|
||||||
if (ret == 0)
|
|
||||||
command_success(client);
|
|
||||||
|
|
||||||
client_write_output(client);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return ret;
|
|
||||||
}
|
|
||||||
|
|
||||||
static char *
|
|
||||||
client_read_line(struct client *client)
|
|
||||||
{
|
|
||||||
const char *p, *newline;
|
|
||||||
size_t length;
|
|
||||||
char *line;
|
|
||||||
|
|
||||||
p = fifo_buffer_read(client->input, &length);
|
|
||||||
if (p == NULL)
|
|
||||||
return NULL;
|
|
||||||
|
|
||||||
newline = memchr(p, '\n', length);
|
|
||||||
if (newline == NULL)
|
|
||||||
return NULL;
|
|
||||||
|
|
||||||
line = g_strndup(p, newline - p);
|
|
||||||
fifo_buffer_consume(client->input, newline - p + 1);
|
|
||||||
|
|
||||||
return g_strchomp(line);
|
|
||||||
}
|
|
||||||
|
|
||||||
static int client_input_received(struct client *client, size_t bytesRead)
|
|
||||||
{
|
|
||||||
char *line;
|
|
||||||
int ret;
|
|
||||||
|
|
||||||
fifo_buffer_append(client->input, bytesRead);
|
|
||||||
|
|
||||||
/* process all lines */
|
|
||||||
|
|
||||||
while ((line = client_read_line(client)) != NULL) {
|
|
||||||
ret = client_process_line(client, line);
|
|
||||||
g_free(line);
|
|
||||||
|
|
||||||
if (ret == COMMAND_RETURN_KILL ||
|
|
||||||
ret == COMMAND_RETURN_CLOSE)
|
|
||||||
return ret;
|
|
||||||
if (client_is_expired(client))
|
|
||||||
return COMMAND_RETURN_CLOSE;
|
|
||||||
}
|
|
||||||
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
static int client_read(struct client *client)
|
|
||||||
{
|
|
||||||
char *p;
|
|
||||||
size_t max_length;
|
|
||||||
GError *error = NULL;
|
|
||||||
GIOStatus status;
|
|
||||||
gsize bytes_read;
|
|
||||||
|
|
||||||
assert(client != NULL);
|
|
||||||
assert(client->channel != NULL);
|
|
||||||
|
|
||||||
p = fifo_buffer_write(client->input, &max_length);
|
|
||||||
if (p == NULL) {
|
|
||||||
g_warning("[%u] buffer overflow", client->num);
|
|
||||||
return COMMAND_RETURN_CLOSE;
|
|
||||||
}
|
|
||||||
|
|
||||||
status = g_io_channel_read_chars(client->channel, p, max_length,
|
|
||||||
&bytes_read, &error);
|
|
||||||
switch (status) {
|
|
||||||
case G_IO_STATUS_NORMAL:
|
|
||||||
return client_input_received(client, bytes_read);
|
|
||||||
|
|
||||||
case G_IO_STATUS_AGAIN:
|
|
||||||
/* try again later, after select() */
|
|
||||||
return 0;
|
|
||||||
|
|
||||||
case G_IO_STATUS_EOF:
|
|
||||||
/* peer disconnected */
|
|
||||||
return COMMAND_RETURN_CLOSE;
|
|
||||||
|
|
||||||
case G_IO_STATUS_ERROR:
|
|
||||||
/* I/O error */
|
|
||||||
g_warning("failed to read from client %d: %s",
|
|
||||||
client->num, error->message);
|
|
||||||
g_error_free(error);
|
|
||||||
return COMMAND_RETURN_CLOSE;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* unreachable */
|
|
||||||
return COMMAND_RETURN_CLOSE;
|
|
||||||
}
|
|
||||||
|
|
||||||
static gboolean
|
|
||||||
client_out_event(G_GNUC_UNUSED GIOChannel *source, GIOCondition condition,
|
|
||||||
gpointer data);
|
|
||||||
|
|
||||||
static gboolean
|
|
||||||
client_in_event(G_GNUC_UNUSED GIOChannel *source,
|
|
||||||
GIOCondition condition,
|
|
||||||
gpointer data)
|
|
||||||
{
|
|
||||||
struct client *client = data;
|
|
||||||
int ret;
|
|
||||||
|
|
||||||
assert(!client_is_expired(client));
|
|
||||||
|
|
||||||
if (condition != G_IO_IN) {
|
|
||||||
client_set_expired(client);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
g_timer_start(client->last_activity);
|
|
||||||
|
|
||||||
ret = client_read(client);
|
|
||||||
switch (ret) {
|
|
||||||
case COMMAND_RETURN_KILL:
|
|
||||||
client_close(client);
|
|
||||||
g_main_loop_quit(main_loop);
|
|
||||||
return false;
|
|
||||||
|
|
||||||
case COMMAND_RETURN_CLOSE:
|
|
||||||
client_close(client);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (client_is_expired(client)) {
|
|
||||||
client_close(client);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!g_queue_is_empty(client->deferred_send)) {
|
|
||||||
/* deferred buffers exist: schedule write */
|
|
||||||
client->source_id = g_io_add_watch(client->channel,
|
|
||||||
G_IO_OUT|G_IO_ERR|G_IO_HUP,
|
|
||||||
client_out_event, client);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* read more */
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
static gboolean
|
|
||||||
client_out_event(G_GNUC_UNUSED GIOChannel *source, GIOCondition condition,
|
|
||||||
gpointer data)
|
|
||||||
{
|
|
||||||
struct client *client = data;
|
|
||||||
|
|
||||||
assert(!client_is_expired(client));
|
|
||||||
|
|
||||||
if (condition != G_IO_OUT) {
|
|
||||||
client_set_expired(client);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
client_write_deferred(client);
|
|
||||||
|
|
||||||
if (client_is_expired(client)) {
|
|
||||||
client_close(client);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
g_timer_start(client->last_activity);
|
|
||||||
|
|
||||||
if (g_queue_is_empty(client->deferred_send)) {
|
|
||||||
/* done sending deferred buffers exist: schedule
|
|
||||||
read */
|
|
||||||
client->source_id = g_io_add_watch(client->channel,
|
|
||||||
G_IO_IN|G_IO_ERR|G_IO_HUP,
|
|
||||||
client_in_event, client);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* write more */
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
void client_manager_init(void)
|
|
||||||
{
|
|
||||||
client_timeout = config_get_positive(CONF_CONN_TIMEOUT,
|
|
||||||
CLIENT_TIMEOUT_DEFAULT);
|
|
||||||
client_max_connections =
|
|
||||||
config_get_positive(CONF_MAX_CONN,
|
|
||||||
CLIENT_MAX_CONNECTIONS_DEFAULT);
|
|
||||||
client_max_command_list_size =
|
|
||||||
config_get_positive(CONF_MAX_COMMAND_LIST_SIZE,
|
|
||||||
CLIENT_MAX_COMMAND_LIST_DEFAULT / 1024)
|
|
||||||
* 1024;
|
|
||||||
|
|
||||||
client_max_output_buffer_size =
|
|
||||||
config_get_positive(CONF_MAX_OUTPUT_BUFFER_SIZE,
|
|
||||||
CLIENT_MAX_OUTPUT_BUFFER_SIZE_DEFAULT / 1024)
|
|
||||||
* 1024;
|
|
||||||
}
|
|
||||||
|
|
||||||
static void client_close_all(void)
|
|
||||||
{
|
|
||||||
while (clients != NULL) {
|
|
||||||
struct client *client = clients->data;
|
|
||||||
|
|
||||||
client_close(client);
|
|
||||||
}
|
|
||||||
|
|
||||||
assert(num_clients == 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
void client_manager_deinit(void)
|
|
||||||
{
|
|
||||||
client_close_all();
|
|
||||||
|
|
||||||
client_max_connections = 0;
|
|
||||||
|
|
||||||
if (expire_source_id != 0)
|
|
||||||
g_source_remove(expire_source_id);
|
|
||||||
}
|
|
||||||
|
|
||||||
static void
|
|
||||||
client_check_expired_callback(gpointer data, G_GNUC_UNUSED gpointer user_data)
|
|
||||||
{
|
|
||||||
struct client *client = data;
|
|
||||||
|
|
||||||
if (client_is_expired(client)) {
|
|
||||||
g_debug("[%u] expired", client->num);
|
|
||||||
client_close(client);
|
|
||||||
} else if (!client->idle_waiting && /* idle clients
|
|
||||||
never expire */
|
|
||||||
(int)g_timer_elapsed(client->last_activity, NULL) >
|
|
||||||
client_timeout) {
|
|
||||||
g_debug("[%u] timeout", client->num);
|
|
||||||
client_close(client);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static void
|
|
||||||
client_manager_expire(void)
|
|
||||||
{
|
|
||||||
g_list_foreach(clients, client_check_expired_callback, NULL);
|
|
||||||
}
|
|
||||||
|
|
||||||
static size_t
|
|
||||||
client_write_deferred_buffer(struct client *client,
|
|
||||||
const struct deferred_buffer *buffer)
|
|
||||||
{
|
|
||||||
GError *error = NULL;
|
|
||||||
GIOStatus status;
|
|
||||||
gsize bytes_written;
|
|
||||||
|
|
||||||
assert(client != NULL);
|
|
||||||
assert(client->channel != NULL);
|
|
||||||
assert(buffer != NULL);
|
|
||||||
|
|
||||||
status = g_io_channel_write_chars
|
|
||||||
(client->channel, buffer->data, buffer->size,
|
|
||||||
&bytes_written, &error);
|
|
||||||
switch (status) {
|
|
||||||
case G_IO_STATUS_NORMAL:
|
|
||||||
return bytes_written;
|
|
||||||
|
|
||||||
case G_IO_STATUS_AGAIN:
|
|
||||||
return 0;
|
|
||||||
|
|
||||||
case G_IO_STATUS_EOF:
|
|
||||||
/* client has disconnected */
|
|
||||||
|
|
||||||
client_set_expired(client);
|
|
||||||
return 0;
|
|
||||||
|
|
||||||
case G_IO_STATUS_ERROR:
|
|
||||||
/* I/O error */
|
|
||||||
|
|
||||||
client_set_expired(client);
|
|
||||||
g_warning("failed to flush buffer for %i: %s",
|
|
||||||
client->num, error->message);
|
|
||||||
g_error_free(error);
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* unreachable */
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
static void client_write_deferred(struct client *client)
|
|
||||||
{
|
|
||||||
size_t ret;
|
|
||||||
|
|
||||||
while (!g_queue_is_empty(client->deferred_send)) {
|
|
||||||
struct deferred_buffer *buf =
|
|
||||||
g_queue_peek_head(client->deferred_send);
|
|
||||||
|
|
||||||
assert(buf->size > 0);
|
|
||||||
assert(buf->size <= client->deferred_bytes);
|
|
||||||
|
|
||||||
ret = client_write_deferred_buffer(client, buf);
|
|
||||||
if (ret == 0)
|
|
||||||
break;
|
|
||||||
|
|
||||||
if (ret < buf->size) {
|
|
||||||
assert(client->deferred_bytes >= (size_t)ret);
|
|
||||||
client->deferred_bytes -= ret;
|
|
||||||
buf->size -= ret;
|
|
||||||
memmove(buf->data, buf->data + ret, buf->size);
|
|
||||||
break;
|
|
||||||
} else {
|
|
||||||
size_t decr = sizeof(*buf) -
|
|
||||||
sizeof(buf->data) + buf->size;
|
|
||||||
|
|
||||||
assert(client->deferred_bytes >= decr);
|
|
||||||
client->deferred_bytes -= decr;
|
|
||||||
g_free(buf);
|
|
||||||
g_queue_pop_head(client->deferred_send);
|
|
||||||
}
|
|
||||||
|
|
||||||
g_timer_start(client->last_activity);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (g_queue_is_empty(client->deferred_send)) {
|
|
||||||
g_debug("[%u] buffer empty %lu", client->num,
|
|
||||||
(unsigned long)client->deferred_bytes);
|
|
||||||
assert(client->deferred_bytes == 0);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static void client_defer_output(struct client *client,
|
|
||||||
const void *data, size_t length)
|
|
||||||
{
|
|
||||||
size_t alloc;
|
|
||||||
struct deferred_buffer *buf;
|
|
||||||
|
|
||||||
assert(length > 0);
|
|
||||||
|
|
||||||
alloc = sizeof(*buf) - sizeof(buf->data) + length;
|
|
||||||
client->deferred_bytes += alloc;
|
|
||||||
if (client->deferred_bytes > client_max_output_buffer_size) {
|
|
||||||
g_warning("[%u] output buffer size (%lu) is "
|
|
||||||
"larger than the max (%lu)",
|
|
||||||
client->num,
|
|
||||||
(unsigned long)client->deferred_bytes,
|
|
||||||
(unsigned long)client_max_output_buffer_size);
|
|
||||||
/* cause client to close */
|
|
||||||
client_set_expired(client);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
buf = g_malloc(alloc);
|
|
||||||
buf->size = length;
|
|
||||||
memcpy(buf->data, data, length);
|
|
||||||
|
|
||||||
g_queue_push_tail(client->deferred_send, buf);
|
|
||||||
}
|
|
||||||
|
|
||||||
static void client_write_direct(struct client *client,
|
|
||||||
const char *data, size_t length)
|
|
||||||
{
|
|
||||||
GError *error = NULL;
|
|
||||||
GIOStatus status;
|
|
||||||
gsize bytes_written;
|
|
||||||
|
|
||||||
assert(client != NULL);
|
|
||||||
assert(client->channel != NULL);
|
|
||||||
assert(data != NULL);
|
|
||||||
assert(length > 0);
|
|
||||||
assert(g_queue_is_empty(client->deferred_send));
|
|
||||||
|
|
||||||
status = g_io_channel_write_chars(client->channel, data, length,
|
|
||||||
&bytes_written, &error);
|
|
||||||
switch (status) {
|
|
||||||
case G_IO_STATUS_NORMAL:
|
|
||||||
case G_IO_STATUS_AGAIN:
|
|
||||||
break;
|
|
||||||
|
|
||||||
case G_IO_STATUS_EOF:
|
|
||||||
/* client has disconnected */
|
|
||||||
|
|
||||||
client_set_expired(client);
|
|
||||||
return;
|
|
||||||
|
|
||||||
case G_IO_STATUS_ERROR:
|
|
||||||
/* I/O error */
|
|
||||||
|
|
||||||
client_set_expired(client);
|
|
||||||
g_warning("failed to write to %i: %s",
|
|
||||||
client->num, error->message);
|
|
||||||
g_error_free(error);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (bytes_written < length)
|
|
||||||
client_defer_output(client, data + bytes_written,
|
|
||||||
length - bytes_written);
|
|
||||||
|
|
||||||
if (!g_queue_is_empty(client->deferred_send))
|
|
||||||
g_debug("[%u] buffer created", client->num);
|
|
||||||
}
|
|
||||||
|
|
||||||
static void client_write_output(struct client *client)
|
|
||||||
{
|
|
||||||
if (client_is_expired(client) || !client->send_buf_used)
|
|
||||||
return;
|
|
||||||
|
|
||||||
if (!g_queue_is_empty(client->deferred_send)) {
|
|
||||||
client_defer_output(client, client->send_buf,
|
|
||||||
client->send_buf_used);
|
|
||||||
|
|
||||||
if (client_is_expired(client))
|
|
||||||
return;
|
|
||||||
|
|
||||||
/* try to flush the deferred buffers now; the current
|
|
||||||
server command may take too long to finish, and
|
|
||||||
meanwhile try to feed output to the client,
|
|
||||||
otherwise it will time out. One reason why
|
|
||||||
deferring is slow might be that currently each
|
|
||||||
client_write() allocates a new deferred buffer.
|
|
||||||
This should be optimized after MPD 0.14. */
|
|
||||||
client_write_deferred(client);
|
|
||||||
} else
|
|
||||||
client_write_direct(client, client->send_buf,
|
|
||||||
client->send_buf_used);
|
|
||||||
|
|
||||||
client->send_buf_used = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Write a block of data to the client.
|
|
||||||
*/
|
|
||||||
static void client_write(struct client *client, const char *buffer, size_t buflen)
|
|
||||||
{
|
|
||||||
/* if the client is going to be closed, do nothing */
|
|
||||||
if (client_is_expired(client))
|
|
||||||
return;
|
|
||||||
|
|
||||||
while (buflen > 0 && !client_is_expired(client)) {
|
|
||||||
size_t copylen;
|
|
||||||
|
|
||||||
assert(client->send_buf_used < sizeof(client->send_buf));
|
|
||||||
|
|
||||||
copylen = sizeof(client->send_buf) - client->send_buf_used;
|
|
||||||
if (copylen > buflen)
|
|
||||||
copylen = buflen;
|
|
||||||
|
|
||||||
memcpy(client->send_buf + client->send_buf_used, buffer,
|
|
||||||
copylen);
|
|
||||||
buflen -= copylen;
|
|
||||||
client->send_buf_used += copylen;
|
|
||||||
buffer += copylen;
|
|
||||||
if (client->send_buf_used >= sizeof(client->send_buf))
|
|
||||||
client_write_output(client);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void client_puts(struct client *client, const char *s)
|
|
||||||
{
|
|
||||||
client_write(client, s, strlen(s));
|
|
||||||
}
|
|
||||||
|
|
||||||
void client_vprintf(struct client *client, const char *fmt, va_list args)
|
|
||||||
{
|
|
||||||
va_list tmp;
|
|
||||||
int length;
|
|
||||||
char *buffer;
|
|
||||||
|
|
||||||
va_copy(tmp, args);
|
|
||||||
length = vsnprintf(NULL, 0, fmt, tmp);
|
|
||||||
va_end(tmp);
|
|
||||||
|
|
||||||
if (length <= 0)
|
|
||||||
/* wtf.. */
|
|
||||||
return;
|
|
||||||
|
|
||||||
buffer = g_malloc(length + 1);
|
|
||||||
vsnprintf(buffer, length + 1, fmt, args);
|
|
||||||
client_write(client, buffer, length);
|
|
||||||
g_free(buffer);
|
|
||||||
}
|
|
||||||
|
|
||||||
G_GNUC_PRINTF(2, 3) void client_printf(struct client *client, const char *fmt, ...)
|
|
||||||
{
|
|
||||||
va_list args;
|
|
||||||
|
|
||||||
va_start(args, fmt);
|
|
||||||
client_vprintf(client, fmt, args);
|
|
||||||
va_end(args);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Send "idle" response to this client.
|
|
||||||
*/
|
|
||||||
static void
|
|
||||||
client_idle_notify(struct client *client)
|
|
||||||
{
|
|
||||||
unsigned flags, i;
|
|
||||||
const char *const* idle_names;
|
|
||||||
|
|
||||||
assert(client->idle_waiting);
|
|
||||||
assert(client->idle_flags != 0);
|
|
||||||
|
|
||||||
flags = client->idle_flags;
|
|
||||||
client->idle_flags = 0;
|
|
||||||
client->idle_waiting = false;
|
|
||||||
|
|
||||||
idle_names = idle_get_names();
|
|
||||||
for (i = 0; idle_names[i]; ++i) {
|
|
||||||
if (flags & (1 << i) & client->idle_subscriptions)
|
|
||||||
client_printf(client, "changed: %s\n",
|
|
||||||
idle_names[i]);
|
|
||||||
}
|
|
||||||
|
|
||||||
client_puts(client, "OK\n");
|
|
||||||
g_timer_start(client->last_activity);
|
|
||||||
}
|
|
||||||
|
|
||||||
static void
|
|
||||||
client_idle_callback(gpointer data, gpointer user_data)
|
|
||||||
{
|
|
||||||
struct client *client = data;
|
|
||||||
unsigned flags = GPOINTER_TO_UINT(user_data);
|
|
||||||
|
|
||||||
if (client_is_expired(client))
|
|
||||||
return;
|
|
||||||
|
|
||||||
client->idle_flags |= flags;
|
|
||||||
if (client->idle_waiting
|
|
||||||
&& (client->idle_flags & client->idle_subscriptions)) {
|
|
||||||
client_idle_notify(client);
|
|
||||||
client_write_output(client);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void client_manager_idle_add(unsigned flags)
|
|
||||||
{
|
|
||||||
assert(flags != 0);
|
|
||||||
|
|
||||||
g_list_foreach(clients, client_idle_callback, GUINT_TO_POINTER(flags));
|
|
||||||
}
|
|
||||||
|
|
||||||
bool client_idle_wait(struct client *client, unsigned flags)
|
|
||||||
{
|
|
||||||
assert(!client->idle_waiting);
|
|
||||||
|
|
||||||
client->idle_waiting = true;
|
|
||||||
client->idle_subscriptions = flags;
|
|
||||||
|
|
||||||
if (client->idle_flags & client->idle_subscriptions) {
|
|
||||||
client_idle_notify(client);
|
|
||||||
return true;
|
|
||||||
} else
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|||||||
33
src/client.h
33
src/client.h
@@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright (C) 2003-2009 The Music Player Daemon Project
|
* Copyright (C) 2003-2011 The Music Player Daemon Project
|
||||||
* http://www.musicpd.org
|
* http://www.musicpd.org
|
||||||
*
|
*
|
||||||
* This program is free software; you can redistribute it and/or modify
|
* This program is free software; you can redistribute it and/or modify
|
||||||
@@ -27,20 +27,36 @@
|
|||||||
|
|
||||||
struct client;
|
struct client;
|
||||||
struct sockaddr;
|
struct sockaddr;
|
||||||
|
struct player_control;
|
||||||
|
|
||||||
void client_manager_init(void);
|
void client_manager_init(void);
|
||||||
void client_manager_deinit(void);
|
void client_manager_deinit(void);
|
||||||
|
|
||||||
void client_new(int fd, const struct sockaddr *sa, size_t sa_length, int uid);
|
void client_new(struct player_control *player_control,
|
||||||
|
int fd, const struct sockaddr *sa, size_t sa_length, int uid);
|
||||||
|
|
||||||
|
G_GNUC_PURE
|
||||||
bool client_is_expired(const struct client *client);
|
bool client_is_expired(const struct client *client);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* returns the uid of the client process, or a negative value if the
|
* returns the uid of the client process, or a negative value if the
|
||||||
* uid is unknown
|
* uid is unknown
|
||||||
*/
|
*/
|
||||||
|
G_GNUC_PURE
|
||||||
int client_get_uid(const struct client *client);
|
int client_get_uid(const struct client *client);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Is this client running on the same machine, connected with a local
|
||||||
|
* (UNIX domain) socket?
|
||||||
|
*/
|
||||||
|
G_GNUC_PURE
|
||||||
|
static inline bool
|
||||||
|
client_is_local(const struct client *client)
|
||||||
|
{
|
||||||
|
return client_get_uid(client) > 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
G_GNUC_PURE
|
||||||
unsigned client_get_permission(const struct client *client);
|
unsigned client_get_permission(const struct client *client);
|
||||||
|
|
||||||
void client_set_permission(struct client *client, unsigned permission);
|
void client_set_permission(struct client *client, unsigned permission);
|
||||||
@@ -60,17 +76,4 @@ void client_vprintf(struct client *client, const char *fmt, va_list args);
|
|||||||
*/
|
*/
|
||||||
G_GNUC_PRINTF(2, 3) void client_printf(struct client *client, const char *fmt, ...);
|
G_GNUC_PRINTF(2, 3) void client_printf(struct client *client, const char *fmt, ...);
|
||||||
|
|
||||||
/**
|
|
||||||
* Adds the specified idle flags to all clients and immediately sends
|
|
||||||
* notifications to all waiting clients.
|
|
||||||
*/
|
|
||||||
void client_manager_idle_add(unsigned flags);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Checks whether the client has pending idle flags. If yes, they are
|
|
||||||
* sent immediately and "true" is returned". If no, it puts the
|
|
||||||
* client into waiting mode and returns false.
|
|
||||||
*/
|
|
||||||
bool client_idle_wait(struct client *client, unsigned flags);
|
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
108
src/client_event.c
Normal file
108
src/client_event.c
Normal file
@@ -0,0 +1,108 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2003-2011 The Music Player Daemon Project
|
||||||
|
* http://www.musicpd.org
|
||||||
|
*
|
||||||
|
* This program is free software; you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation; either version 2 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License along
|
||||||
|
* with this program; if not, write to the Free Software Foundation, Inc.,
|
||||||
|
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "config.h"
|
||||||
|
#include "client_internal.h"
|
||||||
|
#include "main.h"
|
||||||
|
|
||||||
|
#include <assert.h>
|
||||||
|
|
||||||
|
static gboolean
|
||||||
|
client_out_event(G_GNUC_UNUSED GIOChannel *source, GIOCondition condition,
|
||||||
|
gpointer data)
|
||||||
|
{
|
||||||
|
struct client *client = data;
|
||||||
|
|
||||||
|
assert(!client_is_expired(client));
|
||||||
|
|
||||||
|
if (condition != G_IO_OUT) {
|
||||||
|
client_set_expired(client);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
client_write_deferred(client);
|
||||||
|
|
||||||
|
if (client_is_expired(client)) {
|
||||||
|
client_close(client);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
g_timer_start(client->last_activity);
|
||||||
|
|
||||||
|
if (g_queue_is_empty(client->deferred_send)) {
|
||||||
|
/* done sending deferred buffers exist: schedule
|
||||||
|
read */
|
||||||
|
client->source_id = g_io_add_watch(client->channel,
|
||||||
|
G_IO_IN|G_IO_ERR|G_IO_HUP,
|
||||||
|
client_in_event, client);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* write more */
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
gboolean
|
||||||
|
client_in_event(G_GNUC_UNUSED GIOChannel *source, GIOCondition condition,
|
||||||
|
gpointer data)
|
||||||
|
{
|
||||||
|
struct client *client = data;
|
||||||
|
enum command_return ret;
|
||||||
|
|
||||||
|
assert(!client_is_expired(client));
|
||||||
|
|
||||||
|
if (condition != G_IO_IN) {
|
||||||
|
client_set_expired(client);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
g_timer_start(client->last_activity);
|
||||||
|
|
||||||
|
ret = client_read(client);
|
||||||
|
switch (ret) {
|
||||||
|
case COMMAND_RETURN_OK:
|
||||||
|
case COMMAND_RETURN_ERROR:
|
||||||
|
break;
|
||||||
|
|
||||||
|
case COMMAND_RETURN_KILL:
|
||||||
|
client_close(client);
|
||||||
|
g_main_loop_quit(main_loop);
|
||||||
|
return false;
|
||||||
|
|
||||||
|
case COMMAND_RETURN_CLOSE:
|
||||||
|
client_close(client);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (client_is_expired(client)) {
|
||||||
|
client_close(client);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!g_queue_is_empty(client->deferred_send)) {
|
||||||
|
/* deferred buffers exist: schedule write */
|
||||||
|
client->source_id = g_io_add_watch(client->channel,
|
||||||
|
G_IO_OUT|G_IO_ERR|G_IO_HUP,
|
||||||
|
client_out_event, client);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* read more */
|
||||||
|
return true;
|
||||||
|
}
|
||||||
90
src/client_expire.c
Normal file
90
src/client_expire.c
Normal file
@@ -0,0 +1,90 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2003-2011 The Music Player Daemon Project
|
||||||
|
* http://www.musicpd.org
|
||||||
|
*
|
||||||
|
* This program is free software; you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation; either version 2 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License along
|
||||||
|
* with this program; if not, write to the Free Software Foundation, Inc.,
|
||||||
|
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "config.h"
|
||||||
|
#include "client_internal.h"
|
||||||
|
|
||||||
|
static guint expire_source_id;
|
||||||
|
|
||||||
|
void
|
||||||
|
client_set_expired(struct client *client)
|
||||||
|
{
|
||||||
|
if (!client_is_expired(client))
|
||||||
|
client_schedule_expire();
|
||||||
|
|
||||||
|
if (client->source_id != 0) {
|
||||||
|
g_source_remove(client->source_id);
|
||||||
|
client->source_id = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (client->channel != NULL) {
|
||||||
|
g_io_channel_unref(client->channel);
|
||||||
|
client->channel = NULL;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
client_check_expired_callback(gpointer data, G_GNUC_UNUSED gpointer user_data)
|
||||||
|
{
|
||||||
|
struct client *client = data;
|
||||||
|
|
||||||
|
if (client_is_expired(client)) {
|
||||||
|
g_debug("[%u] expired", client->num);
|
||||||
|
client_close(client);
|
||||||
|
} else if (!client->idle_waiting && /* idle clients
|
||||||
|
never expire */
|
||||||
|
(int)g_timer_elapsed(client->last_activity, NULL) >
|
||||||
|
client_timeout) {
|
||||||
|
g_debug("[%u] timeout", client->num);
|
||||||
|
client_close(client);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
client_manager_expire(void)
|
||||||
|
{
|
||||||
|
client_list_foreach(client_check_expired_callback, NULL);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An idle event which calls client_manager_expire().
|
||||||
|
*/
|
||||||
|
static gboolean
|
||||||
|
client_manager_expire_event(G_GNUC_UNUSED gpointer data)
|
||||||
|
{
|
||||||
|
expire_source_id = 0;
|
||||||
|
client_manager_expire();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
client_schedule_expire(void)
|
||||||
|
{
|
||||||
|
if (expire_source_id == 0)
|
||||||
|
/* delayed deletion */
|
||||||
|
expire_source_id = g_idle_add(client_manager_expire_event,
|
||||||
|
NULL);
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
client_deinit_expire(void)
|
||||||
|
{
|
||||||
|
if (expire_source_id != 0)
|
||||||
|
g_source_remove(expire_source_id);
|
||||||
|
}
|
||||||
70
src/client_file.c
Normal file
70
src/client_file.c
Normal file
@@ -0,0 +1,70 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2003-2012 The Music Player Daemon Project
|
||||||
|
* http://www.musicpd.org
|
||||||
|
*
|
||||||
|
* This program is free software; you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation; either version 2 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License along
|
||||||
|
* with this program; if not, write to the Free Software Foundation, Inc.,
|
||||||
|
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "client_file.h"
|
||||||
|
#include "client.h"
|
||||||
|
#include "ack.h"
|
||||||
|
|
||||||
|
#include <sys/stat.h>
|
||||||
|
#include <sys/types.h>
|
||||||
|
#include <errno.h>
|
||||||
|
#include <unistd.h>
|
||||||
|
|
||||||
|
bool
|
||||||
|
client_allow_file(const struct client *client, const char *path_fs,
|
||||||
|
GError **error_r)
|
||||||
|
{
|
||||||
|
#ifdef WIN32
|
||||||
|
(void)client;
|
||||||
|
(void)path_fs;
|
||||||
|
|
||||||
|
g_set_error(error_r, ack_quark(), ACK_ERROR_PERMISSION,
|
||||||
|
"Access denied");
|
||||||
|
return false;
|
||||||
|
#else
|
||||||
|
const int uid = client_get_uid(client);
|
||||||
|
if (uid >= 0 && (uid_t)uid == geteuid())
|
||||||
|
/* always allow access if user runs his own MPD
|
||||||
|
instance */
|
||||||
|
return true;
|
||||||
|
|
||||||
|
if (uid <= 0) {
|
||||||
|
/* unauthenticated client */
|
||||||
|
g_set_error(error_r, ack_quark(), ACK_ERROR_PERMISSION,
|
||||||
|
"Access denied");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
struct stat st;
|
||||||
|
if (stat(path_fs, &st) < 0) {
|
||||||
|
g_set_error(error_r, g_file_error_quark(), errno,
|
||||||
|
"%s", g_strerror(errno));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (st.st_uid != (uid_t)uid && (st.st_mode & 0444) != 0444) {
|
||||||
|
/* client is not owner */
|
||||||
|
g_set_error(error_r, ack_quark(), ACK_ERROR_PERMISSION,
|
||||||
|
"Access denied");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
#endif
|
||||||
|
}
|
||||||
42
src/client_file.h
Normal file
42
src/client_file.h
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2003-2012 The Music Player Daemon Project
|
||||||
|
* http://www.musicpd.org
|
||||||
|
*
|
||||||
|
* This program is free software; you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation; either version 2 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License along
|
||||||
|
* with this program; if not, write to the Free Software Foundation, Inc.,
|
||||||
|
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef MPD_CLIENT_FILE_H
|
||||||
|
#define MPD_CLIENT_FILE_H
|
||||||
|
|
||||||
|
#include <glib.h>
|
||||||
|
#include <stdbool.h>
|
||||||
|
|
||||||
|
struct client;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Is this client allowed to use the specified local file?
|
||||||
|
*
|
||||||
|
* Note that this function is vulnerable to timing/symlink attacks.
|
||||||
|
* We cannot fix this as long as there are plugins that open a file by
|
||||||
|
* its name, and not by file descriptor / callbacks.
|
||||||
|
*
|
||||||
|
* @param path_fs the absolute path name in filesystem encoding
|
||||||
|
* @return true if access is allowed
|
||||||
|
*/
|
||||||
|
bool
|
||||||
|
client_allow_file(const struct client *client, const char *path_fs,
|
||||||
|
GError **error_r);
|
||||||
|
|
||||||
|
#endif
|
||||||
73
src/client_global.c
Normal file
73
src/client_global.c
Normal file
@@ -0,0 +1,73 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2003-2011 The Music Player Daemon Project
|
||||||
|
* http://www.musicpd.org
|
||||||
|
*
|
||||||
|
* This program is free software; you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation; either version 2 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License along
|
||||||
|
* with this program; if not, write to the Free Software Foundation, Inc.,
|
||||||
|
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "config.h"
|
||||||
|
#include "client_internal.h"
|
||||||
|
#include "conf.h"
|
||||||
|
|
||||||
|
#include <assert.h>
|
||||||
|
|
||||||
|
#define CLIENT_TIMEOUT_DEFAULT (60)
|
||||||
|
#define CLIENT_MAX_CONNECTIONS_DEFAULT (10)
|
||||||
|
#define CLIENT_MAX_COMMAND_LIST_DEFAULT (2048*1024)
|
||||||
|
#define CLIENT_MAX_OUTPUT_BUFFER_SIZE_DEFAULT (8192*1024)
|
||||||
|
|
||||||
|
/* set this to zero to indicate we have no possible clients */
|
||||||
|
unsigned int client_max_connections;
|
||||||
|
int client_timeout;
|
||||||
|
size_t client_max_command_list_size;
|
||||||
|
size_t client_max_output_buffer_size;
|
||||||
|
|
||||||
|
void client_manager_init(void)
|
||||||
|
{
|
||||||
|
client_timeout = config_get_positive(CONF_CONN_TIMEOUT,
|
||||||
|
CLIENT_TIMEOUT_DEFAULT);
|
||||||
|
client_max_connections =
|
||||||
|
config_get_positive(CONF_MAX_CONN,
|
||||||
|
CLIENT_MAX_CONNECTIONS_DEFAULT);
|
||||||
|
client_max_command_list_size =
|
||||||
|
config_get_positive(CONF_MAX_COMMAND_LIST_SIZE,
|
||||||
|
CLIENT_MAX_COMMAND_LIST_DEFAULT / 1024)
|
||||||
|
* 1024;
|
||||||
|
|
||||||
|
client_max_output_buffer_size =
|
||||||
|
config_get_positive(CONF_MAX_OUTPUT_BUFFER_SIZE,
|
||||||
|
CLIENT_MAX_OUTPUT_BUFFER_SIZE_DEFAULT / 1024)
|
||||||
|
* 1024;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void client_close_all(void)
|
||||||
|
{
|
||||||
|
while (!client_list_is_empty()) {
|
||||||
|
struct client *client = client_list_get_first();
|
||||||
|
|
||||||
|
client_close(client);
|
||||||
|
}
|
||||||
|
|
||||||
|
assert(client_list_is_empty());
|
||||||
|
}
|
||||||
|
|
||||||
|
void client_manager_deinit(void)
|
||||||
|
{
|
||||||
|
client_close_all();
|
||||||
|
|
||||||
|
client_max_connections = 0;
|
||||||
|
|
||||||
|
client_deinit_expire();
|
||||||
|
}
|
||||||
96
src/client_idle.c
Normal file
96
src/client_idle.c
Normal file
@@ -0,0 +1,96 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2003-2011 The Music Player Daemon Project
|
||||||
|
* http://www.musicpd.org
|
||||||
|
*
|
||||||
|
* This program is free software; you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation; either version 2 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License along
|
||||||
|
* with this program; if not, write to the Free Software Foundation, Inc.,
|
||||||
|
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "config.h"
|
||||||
|
#include "client_idle.h"
|
||||||
|
#include "client_internal.h"
|
||||||
|
#include "idle.h"
|
||||||
|
|
||||||
|
#include <assert.h>
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Send "idle" response to this client.
|
||||||
|
*/
|
||||||
|
static void
|
||||||
|
client_idle_notify(struct client *client)
|
||||||
|
{
|
||||||
|
unsigned flags, i;
|
||||||
|
const char *const* idle_names;
|
||||||
|
|
||||||
|
assert(client->idle_waiting);
|
||||||
|
assert(client->idle_flags != 0);
|
||||||
|
|
||||||
|
flags = client->idle_flags;
|
||||||
|
client->idle_flags = 0;
|
||||||
|
client->idle_waiting = false;
|
||||||
|
|
||||||
|
idle_names = idle_get_names();
|
||||||
|
for (i = 0; idle_names[i]; ++i) {
|
||||||
|
if (flags & (1 << i) & client->idle_subscriptions)
|
||||||
|
client_printf(client, "changed: %s\n",
|
||||||
|
idle_names[i]);
|
||||||
|
}
|
||||||
|
|
||||||
|
client_puts(client, "OK\n");
|
||||||
|
g_timer_start(client->last_activity);
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
client_idle_add(struct client *client, unsigned flags)
|
||||||
|
{
|
||||||
|
if (client_is_expired(client))
|
||||||
|
return;
|
||||||
|
|
||||||
|
client->idle_flags |= flags;
|
||||||
|
if (client->idle_waiting
|
||||||
|
&& (client->idle_flags & client->idle_subscriptions)) {
|
||||||
|
client_idle_notify(client);
|
||||||
|
client_write_output(client);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
client_idle_callback(gpointer data, gpointer user_data)
|
||||||
|
{
|
||||||
|
struct client *client = data;
|
||||||
|
unsigned flags = GPOINTER_TO_UINT(user_data);
|
||||||
|
|
||||||
|
client_idle_add(client, flags);
|
||||||
|
}
|
||||||
|
|
||||||
|
void client_manager_idle_add(unsigned flags)
|
||||||
|
{
|
||||||
|
assert(flags != 0);
|
||||||
|
|
||||||
|
client_list_foreach(client_idle_callback, GUINT_TO_POINTER(flags));
|
||||||
|
}
|
||||||
|
|
||||||
|
bool client_idle_wait(struct client *client, unsigned flags)
|
||||||
|
{
|
||||||
|
assert(!client->idle_waiting);
|
||||||
|
|
||||||
|
client->idle_waiting = true;
|
||||||
|
client->idle_subscriptions = flags;
|
||||||
|
|
||||||
|
if (client->idle_flags & client->idle_subscriptions) {
|
||||||
|
client_idle_notify(client);
|
||||||
|
return true;
|
||||||
|
} else
|
||||||
|
return false;
|
||||||
|
}
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright (C) 2003-2009 The Music Player Daemon Project
|
* Copyright (C) 2003-2011 The Music Player Daemon Project
|
||||||
* http://www.musicpd.org
|
* http://www.musicpd.org
|
||||||
*
|
*
|
||||||
* This program is free software; you can redistribute it and/or modify
|
* This program is free software; you can redistribute it and/or modify
|
||||||
@@ -17,35 +17,29 @@
|
|||||||
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#ifndef MPD_SONGVEC_H
|
#ifndef MPD_CLIENT_IDLE_H
|
||||||
#define MPD_SONGVEC_H
|
#define MPD_CLIENT_IDLE_H
|
||||||
|
|
||||||
#include <stddef.h>
|
#include <stdbool.h>
|
||||||
|
|
||||||
struct songvec {
|
struct client;
|
||||||
struct song **base;
|
|
||||||
size_t nr;
|
|
||||||
};
|
|
||||||
|
|
||||||
void songvec_init(void);
|
|
||||||
|
|
||||||
void songvec_deinit(void);
|
|
||||||
|
|
||||||
void songvec_sort(struct songvec *sv);
|
|
||||||
|
|
||||||
struct song *
|
|
||||||
songvec_find(const struct songvec *sv, const char *url);
|
|
||||||
|
|
||||||
int
|
|
||||||
songvec_delete(struct songvec *sv, const struct song *del);
|
|
||||||
|
|
||||||
void
|
void
|
||||||
songvec_add(struct songvec *sv, struct song *add);
|
client_idle_add(struct client *client, unsigned flags);
|
||||||
|
|
||||||
void songvec_destroy(struct songvec *sv);
|
/**
|
||||||
|
* Adds the specified idle flags to all clients and immediately sends
|
||||||
|
* notifications to all waiting clients.
|
||||||
|
*/
|
||||||
|
void
|
||||||
|
client_manager_idle_add(unsigned flags);
|
||||||
|
|
||||||
int
|
/**
|
||||||
songvec_for_each(const struct songvec *sv,
|
* Checks whether the client has pending idle flags. If yes, they are
|
||||||
int (*fn)(struct song *, void *), void *arg);
|
* sent immediately and "true" is returned". If no, it puts the
|
||||||
|
* client into waiting mode and returns false.
|
||||||
|
*/
|
||||||
|
bool
|
||||||
|
client_idle_wait(struct client *client, unsigned flags);
|
||||||
|
|
||||||
#endif /* SONGVEC_H */
|
#endif
|
||||||
175
src/client_internal.h
Normal file
175
src/client_internal.h
Normal file
@@ -0,0 +1,175 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2003-2011 The Music Player Daemon Project
|
||||||
|
* http://www.musicpd.org
|
||||||
|
*
|
||||||
|
* This program is free software; you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation; either version 2 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License along
|
||||||
|
* with this program; if not, write to the Free Software Foundation, Inc.,
|
||||||
|
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef MPD_CLIENT_INTERNAL_H
|
||||||
|
#define MPD_CLIENT_INTERNAL_H
|
||||||
|
|
||||||
|
#include "client.h"
|
||||||
|
#include "client_message.h"
|
||||||
|
#include "command.h"
|
||||||
|
|
||||||
|
#undef G_LOG_DOMAIN
|
||||||
|
#define G_LOG_DOMAIN "client"
|
||||||
|
|
||||||
|
enum {
|
||||||
|
CLIENT_MAX_SUBSCRIPTIONS = 16,
|
||||||
|
CLIENT_MAX_MESSAGES = 64,
|
||||||
|
};
|
||||||
|
|
||||||
|
struct deferred_buffer {
|
||||||
|
size_t size;
|
||||||
|
char data[sizeof(long)];
|
||||||
|
};
|
||||||
|
|
||||||
|
struct client {
|
||||||
|
struct player_control *player_control;
|
||||||
|
|
||||||
|
GIOChannel *channel;
|
||||||
|
guint source_id;
|
||||||
|
|
||||||
|
/** the buffer for reading lines from the #channel */
|
||||||
|
struct fifo_buffer *input;
|
||||||
|
|
||||||
|
unsigned permission;
|
||||||
|
|
||||||
|
/** the uid of the client process, or -1 if unknown */
|
||||||
|
int uid;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* How long since the last activity from this client?
|
||||||
|
*/
|
||||||
|
GTimer *last_activity;
|
||||||
|
|
||||||
|
GSList *cmd_list; /* for when in list mode */
|
||||||
|
int cmd_list_OK; /* print OK after each command execution */
|
||||||
|
size_t cmd_list_size; /* mem cmd_list consumes */
|
||||||
|
GQueue *deferred_send; /* for output if client is slow */
|
||||||
|
size_t deferred_bytes; /* mem deferred_send consumes */
|
||||||
|
unsigned int num; /* client number */
|
||||||
|
|
||||||
|
char send_buf[16384];
|
||||||
|
size_t send_buf_used; /* bytes used this instance */
|
||||||
|
|
||||||
|
/** is this client waiting for an "idle" response? */
|
||||||
|
bool idle_waiting;
|
||||||
|
|
||||||
|
/** idle flags pending on this client, to be sent as soon as
|
||||||
|
the client enters "idle" */
|
||||||
|
unsigned idle_flags;
|
||||||
|
|
||||||
|
/** idle flags that the client wants to receive */
|
||||||
|
unsigned idle_subscriptions;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A list of channel names this client is subscribed to.
|
||||||
|
*/
|
||||||
|
GSList *subscriptions;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The number of subscriptions in #subscriptions. Used to
|
||||||
|
* limit the number of subscriptions.
|
||||||
|
*/
|
||||||
|
unsigned num_subscriptions;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A list of messages this client has received in reverse
|
||||||
|
* order (latest first).
|
||||||
|
*/
|
||||||
|
GSList *messages;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The number of messages in #messages.
|
||||||
|
*/
|
||||||
|
unsigned num_messages;
|
||||||
|
};
|
||||||
|
|
||||||
|
extern unsigned int client_max_connections;
|
||||||
|
extern int client_timeout;
|
||||||
|
extern size_t client_max_command_list_size;
|
||||||
|
extern size_t client_max_output_buffer_size;
|
||||||
|
|
||||||
|
bool
|
||||||
|
client_list_is_empty(void);
|
||||||
|
|
||||||
|
bool
|
||||||
|
client_list_is_full(void);
|
||||||
|
|
||||||
|
struct client *
|
||||||
|
client_list_get_first(void);
|
||||||
|
|
||||||
|
void
|
||||||
|
client_list_add(struct client *client);
|
||||||
|
|
||||||
|
void
|
||||||
|
client_list_foreach(GFunc func, gpointer user_data);
|
||||||
|
|
||||||
|
void
|
||||||
|
client_list_remove(struct client *client);
|
||||||
|
|
||||||
|
void
|
||||||
|
client_close(struct client *client);
|
||||||
|
|
||||||
|
static inline void
|
||||||
|
new_cmd_list_ptr(struct client *client, const char *s)
|
||||||
|
{
|
||||||
|
client->cmd_list = g_slist_prepend(client->cmd_list, g_strdup(s));
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline void
|
||||||
|
free_cmd_list(GSList *list)
|
||||||
|
{
|
||||||
|
for (GSList *tmp = list; tmp != NULL; tmp = g_slist_next(tmp))
|
||||||
|
g_free(tmp->data);
|
||||||
|
|
||||||
|
g_slist_free(list);
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
client_set_expired(struct client *client);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Schedule an "expired" check for all clients: permanently delete
|
||||||
|
* clients which have been set "expired" with client_set_expired().
|
||||||
|
*/
|
||||||
|
void
|
||||||
|
client_schedule_expire(void);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Removes a scheduled "expired" check.
|
||||||
|
*/
|
||||||
|
void
|
||||||
|
client_deinit_expire(void);
|
||||||
|
|
||||||
|
enum command_return
|
||||||
|
client_read(struct client *client);
|
||||||
|
|
||||||
|
enum command_return
|
||||||
|
client_process_line(struct client *client, char *line);
|
||||||
|
|
||||||
|
void
|
||||||
|
client_write_deferred(struct client *client);
|
||||||
|
|
||||||
|
void
|
||||||
|
client_write_output(struct client *client);
|
||||||
|
|
||||||
|
gboolean
|
||||||
|
client_in_event(GIOChannel *source, GIOCondition condition,
|
||||||
|
gpointer data);
|
||||||
|
|
||||||
|
#endif
|
||||||
69
src/client_list.c
Normal file
69
src/client_list.c
Normal file
@@ -0,0 +1,69 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2003-2011 The Music Player Daemon Project
|
||||||
|
* http://www.musicpd.org
|
||||||
|
*
|
||||||
|
* This program is free software; you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation; either version 2 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License along
|
||||||
|
* with this program; if not, write to the Free Software Foundation, Inc.,
|
||||||
|
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "config.h"
|
||||||
|
#include "client_internal.h"
|
||||||
|
|
||||||
|
#include <assert.h>
|
||||||
|
|
||||||
|
static GList *clients;
|
||||||
|
static unsigned num_clients;
|
||||||
|
|
||||||
|
bool
|
||||||
|
client_list_is_empty(void)
|
||||||
|
{
|
||||||
|
return num_clients == 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool
|
||||||
|
client_list_is_full(void)
|
||||||
|
{
|
||||||
|
return num_clients >= client_max_connections;
|
||||||
|
}
|
||||||
|
|
||||||
|
struct client *
|
||||||
|
client_list_get_first(void)
|
||||||
|
{
|
||||||
|
assert(clients != NULL);
|
||||||
|
|
||||||
|
return clients->data;
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
client_list_add(struct client *client)
|
||||||
|
{
|
||||||
|
clients = g_list_prepend(clients, client);
|
||||||
|
++num_clients;
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
client_list_foreach(GFunc func, gpointer user_data)
|
||||||
|
{
|
||||||
|
g_list_foreach(clients, func, user_data);
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
client_list_remove(struct client *client)
|
||||||
|
{
|
||||||
|
assert(num_clients > 0);
|
||||||
|
assert(clients != NULL);
|
||||||
|
|
||||||
|
clients = g_list_remove(clients, client);
|
||||||
|
--num_clients;
|
||||||
|
}
|
||||||
96
src/client_message.c
Normal file
96
src/client_message.c
Normal file
@@ -0,0 +1,96 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2003-2011 The Music Player Daemon Project
|
||||||
|
* http://www.musicpd.org
|
||||||
|
*
|
||||||
|
* This program is free software; you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation; either version 2 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License along
|
||||||
|
* with this program; if not, write to the Free Software Foundation, Inc.,
|
||||||
|
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "client_message.h"
|
||||||
|
|
||||||
|
#include <assert.h>
|
||||||
|
#include <glib.h>
|
||||||
|
|
||||||
|
G_GNUC_PURE
|
||||||
|
static bool
|
||||||
|
valid_channel_char(const char ch)
|
||||||
|
{
|
||||||
|
return g_ascii_isalnum(ch) ||
|
||||||
|
ch == '_' || ch == '-' || ch == '.' || ch == ':';
|
||||||
|
}
|
||||||
|
|
||||||
|
bool
|
||||||
|
client_message_valid_channel_name(const char *name)
|
||||||
|
{
|
||||||
|
do {
|
||||||
|
if (!valid_channel_char(*name))
|
||||||
|
return false;
|
||||||
|
} while (*++name != 0);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
client_message_init_null(struct client_message *msg)
|
||||||
|
{
|
||||||
|
assert(msg != NULL);
|
||||||
|
|
||||||
|
msg->channel = NULL;
|
||||||
|
msg->message = NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
client_message_init(struct client_message *msg,
|
||||||
|
const char *channel, const char *message)
|
||||||
|
{
|
||||||
|
assert(msg != NULL);
|
||||||
|
|
||||||
|
msg->channel = g_strdup(channel);
|
||||||
|
msg->message = g_strdup(message);
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
client_message_copy(struct client_message *dest,
|
||||||
|
const struct client_message *src)
|
||||||
|
{
|
||||||
|
assert(dest != NULL);
|
||||||
|
assert(src != NULL);
|
||||||
|
assert(client_message_defined(src));
|
||||||
|
|
||||||
|
client_message_init(dest, src->channel, src->message);
|
||||||
|
}
|
||||||
|
|
||||||
|
struct client_message *
|
||||||
|
client_message_dup(const struct client_message *src)
|
||||||
|
{
|
||||||
|
struct client_message *dest = g_slice_new(struct client_message);
|
||||||
|
client_message_copy(dest, src);
|
||||||
|
return dest;
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
client_message_deinit(struct client_message *msg)
|
||||||
|
{
|
||||||
|
assert(msg != NULL);
|
||||||
|
|
||||||
|
g_free(msg->channel);
|
||||||
|
g_free(msg->message);
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
client_message_free(struct client_message *msg)
|
||||||
|
{
|
||||||
|
client_message_deinit(msg);
|
||||||
|
g_slice_free(struct client_message, msg);
|
||||||
|
}
|
||||||
72
src/client_message.h
Normal file
72
src/client_message.h
Normal file
@@ -0,0 +1,72 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2003-2011 The Music Player Daemon Project
|
||||||
|
* http://www.musicpd.org
|
||||||
|
*
|
||||||
|
* This program is free software; you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation; either version 2 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License along
|
||||||
|
* with this program; if not, write to the Free Software Foundation, Inc.,
|
||||||
|
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef MPD_CLIENT_MESSAGE_H
|
||||||
|
#define MPD_CLIENT_MESSAGE_H
|
||||||
|
|
||||||
|
#include <assert.h>
|
||||||
|
#include <stdbool.h>
|
||||||
|
#include <stddef.h>
|
||||||
|
#include <glib.h>
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A client-to-client message.
|
||||||
|
*/
|
||||||
|
struct client_message {
|
||||||
|
char *channel;
|
||||||
|
|
||||||
|
char *message;
|
||||||
|
};
|
||||||
|
|
||||||
|
G_GNUC_PURE
|
||||||
|
bool
|
||||||
|
client_message_valid_channel_name(const char *name);
|
||||||
|
|
||||||
|
G_GNUC_PURE
|
||||||
|
static inline bool
|
||||||
|
client_message_defined(const struct client_message *msg)
|
||||||
|
{
|
||||||
|
assert(msg != NULL);
|
||||||
|
assert((msg->channel == NULL) == (msg->message == NULL));
|
||||||
|
|
||||||
|
return msg->channel != NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
client_message_init_null(struct client_message *msg);
|
||||||
|
|
||||||
|
void
|
||||||
|
client_message_init(struct client_message *msg,
|
||||||
|
const char *channel, const char *message);
|
||||||
|
|
||||||
|
void
|
||||||
|
client_message_copy(struct client_message *dest,
|
||||||
|
const struct client_message *src);
|
||||||
|
|
||||||
|
G_GNUC_MALLOC
|
||||||
|
struct client_message *
|
||||||
|
client_message_dup(const struct client_message *src);
|
||||||
|
|
||||||
|
void
|
||||||
|
client_message_deinit(struct client_message *msg);
|
||||||
|
|
||||||
|
void
|
||||||
|
client_message_free(struct client_message *msg);
|
||||||
|
|
||||||
|
#endif
|
||||||
165
src/client_new.c
Normal file
165
src/client_new.c
Normal file
@@ -0,0 +1,165 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2003-2011 The Music Player Daemon Project
|
||||||
|
* http://www.musicpd.org
|
||||||
|
*
|
||||||
|
* This program is free software; you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation; either version 2 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License along
|
||||||
|
* with this program; if not, write to the Free Software Foundation, Inc.,
|
||||||
|
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "config.h"
|
||||||
|
#include "client_internal.h"
|
||||||
|
#include "fd_util.h"
|
||||||
|
#include "fifo_buffer.h"
|
||||||
|
#include "resolver.h"
|
||||||
|
#include "permission.h"
|
||||||
|
#include "glib_socket.h"
|
||||||
|
|
||||||
|
#include <assert.h>
|
||||||
|
#include <sys/types.h>
|
||||||
|
#ifdef WIN32
|
||||||
|
#include <winsock2.h>
|
||||||
|
#else
|
||||||
|
#include <sys/socket.h>
|
||||||
|
#endif
|
||||||
|
#include <unistd.h>
|
||||||
|
|
||||||
|
#ifdef HAVE_LIBWRAP
|
||||||
|
#include <tcpd.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
|
|
||||||
|
#define LOG_LEVEL_SECURE G_LOG_LEVEL_INFO
|
||||||
|
|
||||||
|
static const char GREETING[] = "OK MPD " PROTOCOL_VERSION "\n";
|
||||||
|
|
||||||
|
void
|
||||||
|
client_new(struct player_control *player_control,
|
||||||
|
int fd, const struct sockaddr *sa, size_t sa_length, int uid)
|
||||||
|
{
|
||||||
|
static unsigned int next_client_num;
|
||||||
|
struct client *client;
|
||||||
|
char *remote;
|
||||||
|
|
||||||
|
assert(player_control != NULL);
|
||||||
|
assert(fd >= 0);
|
||||||
|
|
||||||
|
#ifdef HAVE_LIBWRAP
|
||||||
|
if (sa->sa_family != AF_UNIX) {
|
||||||
|
char *hostaddr = sockaddr_to_string(sa, sa_length, NULL);
|
||||||
|
const char *progname = g_get_prgname();
|
||||||
|
|
||||||
|
struct request_info req;
|
||||||
|
request_init(&req, RQ_FILE, fd, RQ_DAEMON, progname, 0);
|
||||||
|
|
||||||
|
fromhost(&req);
|
||||||
|
|
||||||
|
if (!hosts_access(&req)) {
|
||||||
|
/* tcp wrappers says no */
|
||||||
|
g_log(G_LOG_DOMAIN, LOG_LEVEL_SECURE,
|
||||||
|
"libwrap refused connection (libwrap=%s) from %s",
|
||||||
|
progname, hostaddr);
|
||||||
|
|
||||||
|
g_free(hostaddr);
|
||||||
|
close_socket(fd);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
g_free(hostaddr);
|
||||||
|
}
|
||||||
|
#endif /* HAVE_WRAP */
|
||||||
|
|
||||||
|
if (client_list_is_full()) {
|
||||||
|
g_warning("Max Connections Reached!");
|
||||||
|
close_socket(fd);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
client = g_new0(struct client, 1);
|
||||||
|
client->player_control = player_control;
|
||||||
|
|
||||||
|
client->channel = g_io_channel_new_socket(fd);
|
||||||
|
/* GLib is responsible for closing the file descriptor */
|
||||||
|
g_io_channel_set_close_on_unref(client->channel, true);
|
||||||
|
/* NULL encoding means the stream is binary safe; the MPD
|
||||||
|
protocol is UTF-8 only, but we are doing this call anyway
|
||||||
|
to prevent GLib from messing around with the stream */
|
||||||
|
g_io_channel_set_encoding(client->channel, NULL, NULL);
|
||||||
|
/* we prefer to do buffering */
|
||||||
|
g_io_channel_set_buffered(client->channel, false);
|
||||||
|
|
||||||
|
client->source_id = g_io_add_watch(client->channel,
|
||||||
|
G_IO_IN|G_IO_ERR|G_IO_HUP,
|
||||||
|
client_in_event, client);
|
||||||
|
|
||||||
|
client->input = fifo_buffer_new(4096);
|
||||||
|
|
||||||
|
client->permission = getDefaultPermissions();
|
||||||
|
client->uid = uid;
|
||||||
|
|
||||||
|
client->last_activity = g_timer_new();
|
||||||
|
|
||||||
|
client->cmd_list = NULL;
|
||||||
|
client->cmd_list_OK = -1;
|
||||||
|
client->cmd_list_size = 0;
|
||||||
|
|
||||||
|
client->deferred_send = g_queue_new();
|
||||||
|
client->deferred_bytes = 0;
|
||||||
|
client->num = next_client_num++;
|
||||||
|
|
||||||
|
client->send_buf_used = 0;
|
||||||
|
|
||||||
|
client->subscriptions = NULL;
|
||||||
|
client->messages = NULL;
|
||||||
|
client->num_messages = 0;
|
||||||
|
|
||||||
|
(void)send(fd, GREETING, sizeof(GREETING) - 1, 0);
|
||||||
|
|
||||||
|
client_list_add(client);
|
||||||
|
|
||||||
|
remote = sockaddr_to_string(sa, sa_length, NULL);
|
||||||
|
g_log(G_LOG_DOMAIN, LOG_LEVEL_SECURE,
|
||||||
|
"[%u] opened from %s", client->num, remote);
|
||||||
|
g_free(remote);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
deferred_buffer_free(gpointer data, G_GNUC_UNUSED gpointer user_data)
|
||||||
|
{
|
||||||
|
struct deferred_buffer *buffer = data;
|
||||||
|
g_free(buffer);
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
client_close(struct client *client)
|
||||||
|
{
|
||||||
|
client_list_remove(client);
|
||||||
|
|
||||||
|
client_set_expired(client);
|
||||||
|
|
||||||
|
g_timer_destroy(client->last_activity);
|
||||||
|
|
||||||
|
if (client->cmd_list) {
|
||||||
|
free_cmd_list(client->cmd_list);
|
||||||
|
client->cmd_list = NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
g_queue_foreach(client->deferred_send, deferred_buffer_free, NULL);
|
||||||
|
g_queue_free(client->deferred_send);
|
||||||
|
|
||||||
|
fifo_buffer_free(client->input);
|
||||||
|
|
||||||
|
g_log(G_LOG_DOMAIN, LOG_LEVEL_SECURE,
|
||||||
|
"[%u] closed", client->num);
|
||||||
|
g_free(client);
|
||||||
|
}
|
||||||
146
src/client_process.c
Normal file
146
src/client_process.c
Normal file
@@ -0,0 +1,146 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2003-2011 The Music Player Daemon Project
|
||||||
|
* http://www.musicpd.org
|
||||||
|
*
|
||||||
|
* This program is free software; you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation; either version 2 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License along
|
||||||
|
* with this program; if not, write to the Free Software Foundation, Inc.,
|
||||||
|
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "config.h"
|
||||||
|
#include "client_internal.h"
|
||||||
|
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
|
#define CLIENT_LIST_MODE_BEGIN "command_list_begin"
|
||||||
|
#define CLIENT_LIST_OK_MODE_BEGIN "command_list_ok_begin"
|
||||||
|
#define CLIENT_LIST_MODE_END "command_list_end"
|
||||||
|
|
||||||
|
static enum command_return
|
||||||
|
client_process_command_list(struct client *client, bool list_ok, GSList *list)
|
||||||
|
{
|
||||||
|
enum command_return ret = COMMAND_RETURN_OK;
|
||||||
|
unsigned num = 0;
|
||||||
|
|
||||||
|
for (GSList *cur = list; cur != NULL; cur = g_slist_next(cur)) {
|
||||||
|
char *cmd = cur->data;
|
||||||
|
|
||||||
|
g_debug("command_process_list: process command \"%s\"",
|
||||||
|
cmd);
|
||||||
|
ret = command_process(client, num++, cmd);
|
||||||
|
g_debug("command_process_list: command returned %i", ret);
|
||||||
|
if (ret != COMMAND_RETURN_OK || client_is_expired(client))
|
||||||
|
break;
|
||||||
|
else if (list_ok)
|
||||||
|
client_puts(client, "list_OK\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
enum command_return
|
||||||
|
client_process_line(struct client *client, char *line)
|
||||||
|
{
|
||||||
|
enum command_return ret;
|
||||||
|
|
||||||
|
if (strcmp(line, "noidle") == 0) {
|
||||||
|
if (client->idle_waiting) {
|
||||||
|
/* send empty idle response and leave idle mode */
|
||||||
|
client->idle_waiting = false;
|
||||||
|
command_success(client);
|
||||||
|
client_write_output(client);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* do nothing if the client wasn't idling: the client
|
||||||
|
has already received the full idle response from
|
||||||
|
client_idle_notify(), which he can now evaluate */
|
||||||
|
|
||||||
|
return COMMAND_RETURN_OK;
|
||||||
|
} else if (client->idle_waiting) {
|
||||||
|
/* during idle mode, clients must not send anything
|
||||||
|
except "noidle" */
|
||||||
|
g_warning("[%u] command \"%s\" during idle",
|
||||||
|
client->num, line);
|
||||||
|
return COMMAND_RETURN_CLOSE;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (client->cmd_list_OK >= 0) {
|
||||||
|
if (strcmp(line, CLIENT_LIST_MODE_END) == 0) {
|
||||||
|
g_debug("[%u] process command list",
|
||||||
|
client->num);
|
||||||
|
|
||||||
|
/* for scalability reasons, we have prepended
|
||||||
|
each new command; now we have to reverse it
|
||||||
|
to restore the correct order */
|
||||||
|
client->cmd_list = g_slist_reverse(client->cmd_list);
|
||||||
|
|
||||||
|
ret = client_process_command_list(client,
|
||||||
|
client->cmd_list_OK,
|
||||||
|
client->cmd_list);
|
||||||
|
g_debug("[%u] process command "
|
||||||
|
"list returned %i", client->num, ret);
|
||||||
|
|
||||||
|
if (ret == COMMAND_RETURN_CLOSE ||
|
||||||
|
client_is_expired(client))
|
||||||
|
return COMMAND_RETURN_CLOSE;
|
||||||
|
|
||||||
|
if (ret == COMMAND_RETURN_OK)
|
||||||
|
command_success(client);
|
||||||
|
|
||||||
|
client_write_output(client);
|
||||||
|
free_cmd_list(client->cmd_list);
|
||||||
|
client->cmd_list = NULL;
|
||||||
|
client->cmd_list_OK = -1;
|
||||||
|
} else {
|
||||||
|
size_t len = strlen(line) + 1;
|
||||||
|
client->cmd_list_size += len;
|
||||||
|
if (client->cmd_list_size >
|
||||||
|
client_max_command_list_size) {
|
||||||
|
g_warning("[%u] command list size (%lu) "
|
||||||
|
"is larger than the max (%lu)",
|
||||||
|
client->num,
|
||||||
|
(unsigned long)client->cmd_list_size,
|
||||||
|
(unsigned long)client_max_command_list_size);
|
||||||
|
return COMMAND_RETURN_CLOSE;
|
||||||
|
}
|
||||||
|
|
||||||
|
new_cmd_list_ptr(client, line);
|
||||||
|
ret = COMMAND_RETURN_OK;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (strcmp(line, CLIENT_LIST_MODE_BEGIN) == 0) {
|
||||||
|
client->cmd_list_OK = 0;
|
||||||
|
ret = COMMAND_RETURN_OK;
|
||||||
|
} else if (strcmp(line, CLIENT_LIST_OK_MODE_BEGIN) == 0) {
|
||||||
|
client->cmd_list_OK = 1;
|
||||||
|
ret = COMMAND_RETURN_OK;
|
||||||
|
} else {
|
||||||
|
g_debug("[%u] process command \"%s\"",
|
||||||
|
client->num, line);
|
||||||
|
ret = command_process(client, 0, line);
|
||||||
|
g_debug("[%u] command returned %i",
|
||||||
|
client->num, ret);
|
||||||
|
|
||||||
|
if (ret == COMMAND_RETURN_CLOSE ||
|
||||||
|
client_is_expired(client))
|
||||||
|
return COMMAND_RETURN_CLOSE;
|
||||||
|
|
||||||
|
if (ret == COMMAND_RETURN_OK)
|
||||||
|
command_success(client);
|
||||||
|
|
||||||
|
client_write_output(client);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
113
src/client_read.c
Normal file
113
src/client_read.c
Normal file
@@ -0,0 +1,113 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2003-2011 The Music Player Daemon Project
|
||||||
|
* http://www.musicpd.org
|
||||||
|
*
|
||||||
|
* This program is free software; you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation; either version 2 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License along
|
||||||
|
* with this program; if not, write to the Free Software Foundation, Inc.,
|
||||||
|
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "config.h"
|
||||||
|
#include "client_internal.h"
|
||||||
|
#include "fifo_buffer.h"
|
||||||
|
|
||||||
|
#include <assert.h>
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
|
static char *
|
||||||
|
client_read_line(struct client *client)
|
||||||
|
{
|
||||||
|
const char *p, *newline;
|
||||||
|
size_t length;
|
||||||
|
char *line;
|
||||||
|
|
||||||
|
p = fifo_buffer_read(client->input, &length);
|
||||||
|
if (p == NULL)
|
||||||
|
return NULL;
|
||||||
|
|
||||||
|
newline = memchr(p, '\n', length);
|
||||||
|
if (newline == NULL)
|
||||||
|
return NULL;
|
||||||
|
|
||||||
|
line = g_strndup(p, newline - p);
|
||||||
|
fifo_buffer_consume(client->input, newline - p + 1);
|
||||||
|
|
||||||
|
return g_strchomp(line);
|
||||||
|
}
|
||||||
|
|
||||||
|
static enum command_return
|
||||||
|
client_input_received(struct client *client, size_t bytesRead)
|
||||||
|
{
|
||||||
|
char *line;
|
||||||
|
|
||||||
|
fifo_buffer_append(client->input, bytesRead);
|
||||||
|
|
||||||
|
/* process all lines */
|
||||||
|
|
||||||
|
while ((line = client_read_line(client)) != NULL) {
|
||||||
|
enum command_return ret = client_process_line(client, line);
|
||||||
|
g_free(line);
|
||||||
|
|
||||||
|
if (ret == COMMAND_RETURN_KILL ||
|
||||||
|
ret == COMMAND_RETURN_CLOSE)
|
||||||
|
return ret;
|
||||||
|
if (client_is_expired(client))
|
||||||
|
return COMMAND_RETURN_CLOSE;
|
||||||
|
}
|
||||||
|
|
||||||
|
return COMMAND_RETURN_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
enum command_return
|
||||||
|
client_read(struct client *client)
|
||||||
|
{
|
||||||
|
char *p;
|
||||||
|
size_t max_length;
|
||||||
|
GError *error = NULL;
|
||||||
|
GIOStatus status;
|
||||||
|
gsize bytes_read;
|
||||||
|
|
||||||
|
assert(client != NULL);
|
||||||
|
assert(client->channel != NULL);
|
||||||
|
|
||||||
|
p = fifo_buffer_write(client->input, &max_length);
|
||||||
|
if (p == NULL) {
|
||||||
|
g_warning("[%u] buffer overflow", client->num);
|
||||||
|
return COMMAND_RETURN_CLOSE;
|
||||||
|
}
|
||||||
|
|
||||||
|
status = g_io_channel_read_chars(client->channel, p, max_length,
|
||||||
|
&bytes_read, &error);
|
||||||
|
switch (status) {
|
||||||
|
case G_IO_STATUS_NORMAL:
|
||||||
|
return client_input_received(client, bytes_read);
|
||||||
|
|
||||||
|
case G_IO_STATUS_AGAIN:
|
||||||
|
/* try again later, after select() */
|
||||||
|
return COMMAND_RETURN_OK;
|
||||||
|
|
||||||
|
case G_IO_STATUS_EOF:
|
||||||
|
/* peer disconnected */
|
||||||
|
return COMMAND_RETURN_CLOSE;
|
||||||
|
|
||||||
|
case G_IO_STATUS_ERROR:
|
||||||
|
/* I/O error */
|
||||||
|
g_warning("failed to read from client %d: %s",
|
||||||
|
client->num, error->message);
|
||||||
|
g_error_free(error);
|
||||||
|
return COMMAND_RETURN_CLOSE;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* unreachable */
|
||||||
|
return COMMAND_RETURN_CLOSE;
|
||||||
|
}
|
||||||
123
src/client_subscribe.c
Normal file
123
src/client_subscribe.c
Normal file
@@ -0,0 +1,123 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2003-2011 The Music Player Daemon Project
|
||||||
|
* http://www.musicpd.org
|
||||||
|
*
|
||||||
|
* This program is free software; you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation; either version 2 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License along
|
||||||
|
* with this program; if not, write to the Free Software Foundation, Inc.,
|
||||||
|
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "client_subscribe.h"
|
||||||
|
#include "client_internal.h"
|
||||||
|
#include "client_idle.h"
|
||||||
|
#include "idle.h"
|
||||||
|
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
|
G_GNUC_PURE
|
||||||
|
static GSList *
|
||||||
|
client_find_subscription(const struct client *client, const char *channel)
|
||||||
|
{
|
||||||
|
for (GSList *i = client->subscriptions; i != NULL; i = g_slist_next(i))
|
||||||
|
if (strcmp((const char *)i->data, channel) == 0)
|
||||||
|
return i;
|
||||||
|
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
enum client_subscribe_result
|
||||||
|
client_subscribe(struct client *client, const char *channel)
|
||||||
|
{
|
||||||
|
assert(client != NULL);
|
||||||
|
assert(channel != NULL);
|
||||||
|
|
||||||
|
if (!client_message_valid_channel_name(channel))
|
||||||
|
return CLIENT_SUBSCRIBE_INVALID;
|
||||||
|
|
||||||
|
if (client_find_subscription(client, channel) != NULL)
|
||||||
|
return CLIENT_SUBSCRIBE_ALREADY;
|
||||||
|
|
||||||
|
if (client->num_subscriptions >= CLIENT_MAX_SUBSCRIPTIONS)
|
||||||
|
return CLIENT_SUBSCRIBE_FULL;
|
||||||
|
|
||||||
|
client->subscriptions = g_slist_prepend(client->subscriptions,
|
||||||
|
g_strdup(channel));
|
||||||
|
++client->num_subscriptions;
|
||||||
|
|
||||||
|
idle_add(IDLE_SUBSCRIPTION);
|
||||||
|
|
||||||
|
return CLIENT_SUBSCRIBE_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool
|
||||||
|
client_unsubscribe(struct client *client, const char *channel)
|
||||||
|
{
|
||||||
|
GSList *i = client_find_subscription(client, channel);
|
||||||
|
if (i == NULL)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
assert(client->num_subscriptions > 0);
|
||||||
|
|
||||||
|
client->subscriptions = g_slist_remove(client->subscriptions, i->data);
|
||||||
|
--client->num_subscriptions;
|
||||||
|
|
||||||
|
idle_add(IDLE_SUBSCRIPTION);
|
||||||
|
|
||||||
|
assert((client->num_subscriptions == 0) ==
|
||||||
|
(client->subscriptions == NULL));
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
client_unsubscribe_all(struct client *client)
|
||||||
|
{
|
||||||
|
for (GSList *i = client->subscriptions; i != NULL; i = g_slist_next(i))
|
||||||
|
g_free(i->data);
|
||||||
|
|
||||||
|
g_slist_free(client->subscriptions);
|
||||||
|
client->subscriptions = NULL;
|
||||||
|
client->num_subscriptions = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool
|
||||||
|
client_push_message(struct client *client, const struct client_message *msg)
|
||||||
|
{
|
||||||
|
assert(client != NULL);
|
||||||
|
assert(msg != NULL);
|
||||||
|
assert(client_message_defined(msg));
|
||||||
|
|
||||||
|
if (client->num_messages >= CLIENT_MAX_MESSAGES ||
|
||||||
|
client_find_subscription(client, msg->channel) == NULL)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
if (client->messages == NULL)
|
||||||
|
client_idle_add(client, IDLE_MESSAGE);
|
||||||
|
|
||||||
|
client->messages = g_slist_prepend(client->messages,
|
||||||
|
client_message_dup(msg));
|
||||||
|
++client->num_messages;
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
GSList *
|
||||||
|
client_read_messages(struct client *client)
|
||||||
|
{
|
||||||
|
GSList *messages = g_slist_reverse(client->messages);
|
||||||
|
|
||||||
|
client->messages = NULL;
|
||||||
|
client->num_messages = 0;
|
||||||
|
|
||||||
|
return messages;
|
||||||
|
}
|
||||||
59
src/client_subscribe.h
Normal file
59
src/client_subscribe.h
Normal file
@@ -0,0 +1,59 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2003-2011 The Music Player Daemon Project
|
||||||
|
* http://www.musicpd.org
|
||||||
|
*
|
||||||
|
* This program is free software; you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation; either version 2 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License along
|
||||||
|
* with this program; if not, write to the Free Software Foundation, Inc.,
|
||||||
|
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef MPD_CLIENT_SUBSCRIBE_H
|
||||||
|
#define MPD_CLIENT_SUBSCRIBE_H
|
||||||
|
|
||||||
|
#include <stdbool.h>
|
||||||
|
#include <glib.h>
|
||||||
|
|
||||||
|
struct client;
|
||||||
|
struct client_message;
|
||||||
|
|
||||||
|
enum client_subscribe_result {
|
||||||
|
/** success */
|
||||||
|
CLIENT_SUBSCRIBE_OK,
|
||||||
|
|
||||||
|
/** invalid channel name */
|
||||||
|
CLIENT_SUBSCRIBE_INVALID,
|
||||||
|
|
||||||
|
/** already subscribed to this channel */
|
||||||
|
CLIENT_SUBSCRIBE_ALREADY,
|
||||||
|
|
||||||
|
/** too many subscriptions */
|
||||||
|
CLIENT_SUBSCRIBE_FULL,
|
||||||
|
};
|
||||||
|
|
||||||
|
enum client_subscribe_result
|
||||||
|
client_subscribe(struct client *client, const char *channel);
|
||||||
|
|
||||||
|
bool
|
||||||
|
client_unsubscribe(struct client *client, const char *channel);
|
||||||
|
|
||||||
|
void
|
||||||
|
client_unsubscribe_all(struct client *client);
|
||||||
|
|
||||||
|
bool
|
||||||
|
client_push_message(struct client *client, const struct client_message *msg);
|
||||||
|
|
||||||
|
G_GNUC_MALLOC
|
||||||
|
GSList *
|
||||||
|
client_read_messages(struct client *client);
|
||||||
|
|
||||||
|
#endif
|
||||||
284
src/client_write.c
Normal file
284
src/client_write.c
Normal file
@@ -0,0 +1,284 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2003-2011 The Music Player Daemon Project
|
||||||
|
* http://www.musicpd.org
|
||||||
|
*
|
||||||
|
* This program is free software; you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation; either version 2 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License along
|
||||||
|
* with this program; if not, write to the Free Software Foundation, Inc.,
|
||||||
|
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "config.h"
|
||||||
|
#include "client_internal.h"
|
||||||
|
|
||||||
|
#include <assert.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
|
||||||
|
static size_t
|
||||||
|
client_write_deferred_buffer(struct client *client,
|
||||||
|
const struct deferred_buffer *buffer)
|
||||||
|
{
|
||||||
|
GError *error = NULL;
|
||||||
|
GIOStatus status;
|
||||||
|
gsize bytes_written;
|
||||||
|
|
||||||
|
assert(client != NULL);
|
||||||
|
assert(client->channel != NULL);
|
||||||
|
assert(buffer != NULL);
|
||||||
|
|
||||||
|
status = g_io_channel_write_chars
|
||||||
|
(client->channel, buffer->data, buffer->size,
|
||||||
|
&bytes_written, &error);
|
||||||
|
switch (status) {
|
||||||
|
case G_IO_STATUS_NORMAL:
|
||||||
|
return bytes_written;
|
||||||
|
|
||||||
|
case G_IO_STATUS_AGAIN:
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
case G_IO_STATUS_EOF:
|
||||||
|
/* client has disconnected */
|
||||||
|
|
||||||
|
client_set_expired(client);
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
case G_IO_STATUS_ERROR:
|
||||||
|
/* I/O error */
|
||||||
|
|
||||||
|
client_set_expired(client);
|
||||||
|
g_warning("failed to flush buffer for %i: %s",
|
||||||
|
client->num, error->message);
|
||||||
|
g_error_free(error);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* unreachable */
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
client_write_deferred(struct client *client)
|
||||||
|
{
|
||||||
|
size_t ret;
|
||||||
|
|
||||||
|
while (!g_queue_is_empty(client->deferred_send)) {
|
||||||
|
struct deferred_buffer *buf =
|
||||||
|
g_queue_peek_head(client->deferred_send);
|
||||||
|
|
||||||
|
assert(buf->size > 0);
|
||||||
|
assert(buf->size <= client->deferred_bytes);
|
||||||
|
|
||||||
|
ret = client_write_deferred_buffer(client, buf);
|
||||||
|
if (ret == 0)
|
||||||
|
break;
|
||||||
|
|
||||||
|
if (ret < buf->size) {
|
||||||
|
assert(client->deferred_bytes >= (size_t)ret);
|
||||||
|
client->deferred_bytes -= ret;
|
||||||
|
buf->size -= ret;
|
||||||
|
memmove(buf->data, buf->data + ret, buf->size);
|
||||||
|
break;
|
||||||
|
} else {
|
||||||
|
size_t decr = sizeof(*buf) -
|
||||||
|
sizeof(buf->data) + buf->size;
|
||||||
|
|
||||||
|
assert(client->deferred_bytes >= decr);
|
||||||
|
client->deferred_bytes -= decr;
|
||||||
|
g_free(buf);
|
||||||
|
g_queue_pop_head(client->deferred_send);
|
||||||
|
}
|
||||||
|
|
||||||
|
g_timer_start(client->last_activity);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (g_queue_is_empty(client->deferred_send)) {
|
||||||
|
g_debug("[%u] buffer empty %lu", client->num,
|
||||||
|
(unsigned long)client->deferred_bytes);
|
||||||
|
assert(client->deferred_bytes == 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void client_defer_output(struct client *client,
|
||||||
|
const void *data, size_t length)
|
||||||
|
{
|
||||||
|
size_t alloc;
|
||||||
|
struct deferred_buffer *buf;
|
||||||
|
|
||||||
|
assert(length > 0);
|
||||||
|
|
||||||
|
alloc = sizeof(*buf) - sizeof(buf->data) + length;
|
||||||
|
client->deferred_bytes += alloc;
|
||||||
|
if (client->deferred_bytes > client_max_output_buffer_size) {
|
||||||
|
g_warning("[%u] output buffer size (%lu) is "
|
||||||
|
"larger than the max (%lu)",
|
||||||
|
client->num,
|
||||||
|
(unsigned long)client->deferred_bytes,
|
||||||
|
(unsigned long)client_max_output_buffer_size);
|
||||||
|
/* cause client to close */
|
||||||
|
client_set_expired(client);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
buf = g_malloc(alloc);
|
||||||
|
buf->size = length;
|
||||||
|
memcpy(buf->data, data, length);
|
||||||
|
|
||||||
|
g_queue_push_tail(client->deferred_send, buf);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void client_write_direct(struct client *client,
|
||||||
|
const char *data, size_t length)
|
||||||
|
{
|
||||||
|
GError *error = NULL;
|
||||||
|
GIOStatus status;
|
||||||
|
gsize bytes_written;
|
||||||
|
|
||||||
|
assert(client != NULL);
|
||||||
|
assert(client->channel != NULL);
|
||||||
|
assert(data != NULL);
|
||||||
|
assert(length > 0);
|
||||||
|
assert(g_queue_is_empty(client->deferred_send));
|
||||||
|
|
||||||
|
status = g_io_channel_write_chars(client->channel, data, length,
|
||||||
|
&bytes_written, &error);
|
||||||
|
switch (status) {
|
||||||
|
case G_IO_STATUS_NORMAL:
|
||||||
|
case G_IO_STATUS_AGAIN:
|
||||||
|
break;
|
||||||
|
|
||||||
|
case G_IO_STATUS_EOF:
|
||||||
|
/* client has disconnected */
|
||||||
|
|
||||||
|
client_set_expired(client);
|
||||||
|
return;
|
||||||
|
|
||||||
|
case G_IO_STATUS_ERROR:
|
||||||
|
/* I/O error */
|
||||||
|
|
||||||
|
client_set_expired(client);
|
||||||
|
g_warning("failed to write to %i: %s",
|
||||||
|
client->num, error->message);
|
||||||
|
g_error_free(error);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (bytes_written < length)
|
||||||
|
client_defer_output(client, data + bytes_written,
|
||||||
|
length - bytes_written);
|
||||||
|
|
||||||
|
if (!g_queue_is_empty(client->deferred_send))
|
||||||
|
g_debug("[%u] buffer created", client->num);
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
client_write_output(struct client *client)
|
||||||
|
{
|
||||||
|
if (client_is_expired(client) || !client->send_buf_used)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (!g_queue_is_empty(client->deferred_send)) {
|
||||||
|
client_defer_output(client, client->send_buf,
|
||||||
|
client->send_buf_used);
|
||||||
|
|
||||||
|
if (client_is_expired(client))
|
||||||
|
return;
|
||||||
|
|
||||||
|
/* try to flush the deferred buffers now; the current
|
||||||
|
server command may take too long to finish, and
|
||||||
|
meanwhile try to feed output to the client,
|
||||||
|
otherwise it will time out. One reason why
|
||||||
|
deferring is slow might be that currently each
|
||||||
|
client_write() allocates a new deferred buffer.
|
||||||
|
This should be optimized after MPD 0.14. */
|
||||||
|
client_write_deferred(client);
|
||||||
|
} else
|
||||||
|
client_write_direct(client, client->send_buf,
|
||||||
|
client->send_buf_used);
|
||||||
|
|
||||||
|
client->send_buf_used = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Write a block of data to the client.
|
||||||
|
*/
|
||||||
|
static void client_write(struct client *client, const char *buffer, size_t buflen)
|
||||||
|
{
|
||||||
|
/* if the client is going to be closed, do nothing */
|
||||||
|
if (client_is_expired(client))
|
||||||
|
return;
|
||||||
|
|
||||||
|
while (buflen > 0 && !client_is_expired(client)) {
|
||||||
|
size_t copylen;
|
||||||
|
|
||||||
|
assert(client->send_buf_used < sizeof(client->send_buf));
|
||||||
|
|
||||||
|
copylen = sizeof(client->send_buf) - client->send_buf_used;
|
||||||
|
if (copylen > buflen)
|
||||||
|
copylen = buflen;
|
||||||
|
|
||||||
|
memcpy(client->send_buf + client->send_buf_used, buffer,
|
||||||
|
copylen);
|
||||||
|
buflen -= copylen;
|
||||||
|
client->send_buf_used += copylen;
|
||||||
|
buffer += copylen;
|
||||||
|
if (client->send_buf_used >= sizeof(client->send_buf))
|
||||||
|
client_write_output(client);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void client_puts(struct client *client, const char *s)
|
||||||
|
{
|
||||||
|
client_write(client, s, strlen(s));
|
||||||
|
}
|
||||||
|
|
||||||
|
void client_vprintf(struct client *client, const char *fmt, va_list args)
|
||||||
|
{
|
||||||
|
#ifndef G_OS_WIN32
|
||||||
|
va_list tmp;
|
||||||
|
int length;
|
||||||
|
char *buffer;
|
||||||
|
|
||||||
|
va_copy(tmp, args);
|
||||||
|
length = vsnprintf(NULL, 0, fmt, tmp);
|
||||||
|
va_end(tmp);
|
||||||
|
|
||||||
|
if (length <= 0)
|
||||||
|
/* wtf.. */
|
||||||
|
return;
|
||||||
|
|
||||||
|
buffer = g_malloc(length + 1);
|
||||||
|
vsnprintf(buffer, length + 1, fmt, args);
|
||||||
|
client_write(client, buffer, length);
|
||||||
|
g_free(buffer);
|
||||||
|
#else
|
||||||
|
/* On mingw32, snprintf() expects a 64 bit integer instead of
|
||||||
|
a "long int" for "%li". This is not consistent with our
|
||||||
|
expectation, so we're using plain sprintf() here, hoping
|
||||||
|
the static buffer is large enough. Sorry for this hack,
|
||||||
|
but WIN32 development is so painful, I'm not in the mood to
|
||||||
|
do it properly now. */
|
||||||
|
|
||||||
|
static char buffer[4096];
|
||||||
|
vsprintf(buffer, fmt, args);
|
||||||
|
client_write(client, buffer, strlen(buffer));
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
G_GNUC_PRINTF(2, 3) void client_printf(struct client *client, const char *fmt, ...)
|
||||||
|
{
|
||||||
|
va_list args;
|
||||||
|
|
||||||
|
va_start(args, fmt);
|
||||||
|
client_vprintf(client, fmt, args);
|
||||||
|
va_end(args);
|
||||||
|
}
|
||||||
95
src/clock.c
Normal file
95
src/clock.c
Normal file
@@ -0,0 +1,95 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2003-2012 The Music Player Daemon Project
|
||||||
|
* http://www.musicpd.org
|
||||||
|
*
|
||||||
|
* This program is free software; you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation; either version 2 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License along
|
||||||
|
* with this program; if not, write to the Free Software Foundation, Inc.,
|
||||||
|
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "clock.h"
|
||||||
|
|
||||||
|
#ifdef WIN32
|
||||||
|
#include <windows.h>
|
||||||
|
#elif defined(__APPLE__)
|
||||||
|
#include <mach/mach_time.h>
|
||||||
|
#else
|
||||||
|
#include <time.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
|
unsigned
|
||||||
|
monotonic_clock_ms(void)
|
||||||
|
{
|
||||||
|
#ifdef WIN32
|
||||||
|
return GetTickCount();
|
||||||
|
#elif defined(__APPLE__) /* OS X does not define CLOCK_MONOTONIC */
|
||||||
|
static mach_timebase_info_data_t base;
|
||||||
|
if (base.denom == 0)
|
||||||
|
(void)mach_timebase_info(&base);
|
||||||
|
|
||||||
|
return (unsigned)((mach_absolute_time() * base.numer)
|
||||||
|
/ (1000000 * base.denom));
|
||||||
|
#elif defined(CLOCK_MONOTONIC)
|
||||||
|
struct timespec ts;
|
||||||
|
clock_gettime(CLOCK_MONOTONIC, &ts);
|
||||||
|
return ts.tv_sec * 1000 + ts.tv_nsec / 1000000;
|
||||||
|
#else
|
||||||
|
/* we have no monotonic clock, fall back to gettimeofday() */
|
||||||
|
struct timeval tv;
|
||||||
|
gettimeofday(&tv, 0);
|
||||||
|
return tv.tv_sec * 1000 + tv.tv_usec / 1000;
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
uint64_t
|
||||||
|
monotonic_clock_us(void)
|
||||||
|
{
|
||||||
|
#ifdef WIN32
|
||||||
|
LARGE_INTEGER l_value, l_frequency;
|
||||||
|
|
||||||
|
if (!QueryPerformanceCounter(&l_value) ||
|
||||||
|
!QueryPerformanceFrequency(&l_frequency))
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
uint64_t value = l_value.QuadPart;
|
||||||
|
uint64_t frequency = l_frequency.QuadPart;
|
||||||
|
|
||||||
|
if (frequency > 1000000) {
|
||||||
|
value *= 10000;
|
||||||
|
value /= frequency / 100;
|
||||||
|
} else if (frequency < 1000000) {
|
||||||
|
value *= 10000;
|
||||||
|
value /= frequency;
|
||||||
|
value *= 100;
|
||||||
|
}
|
||||||
|
|
||||||
|
return value;
|
||||||
|
#elif defined(__APPLE__) /* OS X does not define CLOCK_MONOTONIC */
|
||||||
|
static mach_timebase_info_data_t base;
|
||||||
|
if (base.denom == 0)
|
||||||
|
(void)mach_timebase_info(&base);
|
||||||
|
|
||||||
|
return ((uint64_t)mach_absolute_time() * (uint64_t)base.numer)
|
||||||
|
/ (1000 * (uint64_t)base.denom);
|
||||||
|
#elif defined(CLOCK_MONOTONIC)
|
||||||
|
struct timespec ts;
|
||||||
|
clock_gettime(CLOCK_MONOTONIC, &ts);
|
||||||
|
return (uint64_t)ts.tv_sec * 1000000 + (uint64_t)(ts.tv_nsec / 1000);
|
||||||
|
#else
|
||||||
|
/* we have no monotonic clock, fall back to gettimeofday() */
|
||||||
|
struct timeval tv;
|
||||||
|
gettimeofday(&tv, 0);
|
||||||
|
return (uint64_t)tv.tv_sec * 1000 + (uint64_t)(tv.tv_usec) / 1000(;
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
41
src/clock.h
Normal file
41
src/clock.h
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2003-2012 The Music Player Daemon Project
|
||||||
|
* http://www.musicpd.org
|
||||||
|
*
|
||||||
|
* This program is free software; you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation; either version 2 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License along
|
||||||
|
* with this program; if not, write to the Free Software Foundation, Inc.,
|
||||||
|
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef MPD_CLOCK_H
|
||||||
|
#define MPD_CLOCK_H
|
||||||
|
|
||||||
|
#include <glib.h>
|
||||||
|
|
||||||
|
#include <stdint.h>
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the value of a monotonic clock in milliseconds.
|
||||||
|
*/
|
||||||
|
G_GNUC_PURE
|
||||||
|
unsigned
|
||||||
|
monotonic_clock_ms(void);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the value of a monotonic clock in microseconds.
|
||||||
|
*/
|
||||||
|
G_GNUC_PURE
|
||||||
|
uint64_t
|
||||||
|
monotonic_clock_us(void);
|
||||||
|
|
||||||
|
#endif
|
||||||
172
src/cmdline.c
172
src/cmdline.c
@@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright (C) 2003-2009 The Music Player Daemon Project
|
* Copyright (C) 2003-2011 The Music Player Daemon Project
|
||||||
* http://www.musicpd.org
|
* http://www.musicpd.org
|
||||||
*
|
*
|
||||||
* This program is free software; you can redistribute it and/or modify
|
* This program is free software; you can redistribute it and/or modify
|
||||||
@@ -17,17 +17,31 @@
|
|||||||
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
#include "config.h"
|
||||||
#include "cmdline.h"
|
#include "cmdline.h"
|
||||||
#include "path.h"
|
#include "path.h"
|
||||||
#include "log.h"
|
#include "log.h"
|
||||||
#include "conf.h"
|
#include "conf.h"
|
||||||
#include "decoder_list.h"
|
#include "decoder_list.h"
|
||||||
#include "config.h"
|
#include "decoder_plugin.h"
|
||||||
#include "output_list.h"
|
#include "output_list.h"
|
||||||
|
#include "output_plugin.h"
|
||||||
|
#include "input_registry.h"
|
||||||
|
#include "input_plugin.h"
|
||||||
|
#include "playlist_list.h"
|
||||||
|
#include "playlist_plugin.h"
|
||||||
#include "ls.h"
|
#include "ls.h"
|
||||||
|
#include "mpd_error.h"
|
||||||
|
#include "glib_compat.h"
|
||||||
|
|
||||||
|
#ifdef ENABLE_ENCODER
|
||||||
|
#include "encoder_list.h"
|
||||||
|
#include "encoder_plugin.h"
|
||||||
|
#endif
|
||||||
|
|
||||||
#ifdef ENABLE_ARCHIVE
|
#ifdef ENABLE_ARCHIVE
|
||||||
#include "archive_list.h"
|
#include "archive_list.h"
|
||||||
|
#include "archive_plugin.h"
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#include <glib.h>
|
#include <glib.h>
|
||||||
@@ -35,9 +49,18 @@
|
|||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
|
|
||||||
#define SYSTEM_CONFIG_FILE_LOCATION "/etc/mpd.conf"
|
#ifdef G_OS_WIN32
|
||||||
|
#define CONFIG_FILE_LOCATION "\\mpd\\mpd.conf"
|
||||||
|
#else /* G_OS_WIN32 */
|
||||||
#define USER_CONFIG_FILE_LOCATION1 ".mpdconf"
|
#define USER_CONFIG_FILE_LOCATION1 ".mpdconf"
|
||||||
#define USER_CONFIG_FILE_LOCATION2 ".mpd/mpd.conf"
|
#define USER_CONFIG_FILE_LOCATION2 ".mpd/mpd.conf"
|
||||||
|
#endif
|
||||||
|
|
||||||
|
static GQuark
|
||||||
|
cmdline_quark(void)
|
||||||
|
{
|
||||||
|
return g_quark_from_static_string("cmdline");
|
||||||
|
}
|
||||||
|
|
||||||
G_GNUC_NORETURN
|
G_GNUC_NORETURN
|
||||||
static void version(void)
|
static void version(void)
|
||||||
@@ -45,58 +68,92 @@ static void version(void)
|
|||||||
puts(PACKAGE " (MPD: Music Player Daemon) " VERSION " \n"
|
puts(PACKAGE " (MPD: Music Player Daemon) " VERSION " \n"
|
||||||
"\n"
|
"\n"
|
||||||
"Copyright (C) 2003-2007 Warren Dukes <warren.dukes@gmail.com>\n"
|
"Copyright (C) 2003-2007 Warren Dukes <warren.dukes@gmail.com>\n"
|
||||||
"Copyright (C) 2008 Max Kellermann <max@duempel.org>\n"
|
"Copyright (C) 2008-2012 Max Kellermann <max@duempel.org>\n"
|
||||||
"This is free software; see the source for copying conditions. There is NO\n"
|
"This is free software; see the source for copying conditions. There is NO\n"
|
||||||
"warranty; not even MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n"
|
"warranty; not even MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n"
|
||||||
"\n"
|
"\n"
|
||||||
"Supported decoders:\n");
|
"Decoders plugins:");
|
||||||
|
|
||||||
decoder_plugin_init_all();
|
decoder_plugins_for_each(plugin) {
|
||||||
decoder_plugin_print_all_decoders(stdout);
|
printf(" [%s]", plugin->name);
|
||||||
|
|
||||||
|
const char *const*suffixes = plugin->suffixes;
|
||||||
|
if (suffixes != NULL)
|
||||||
|
for (; *suffixes != NULL; ++suffixes)
|
||||||
|
printf(" %s", *suffixes);
|
||||||
|
|
||||||
|
puts("");
|
||||||
|
}
|
||||||
|
|
||||||
puts("\n"
|
puts("\n"
|
||||||
"Supported outputs:\n");
|
"Output plugins:");
|
||||||
audio_output_plugin_print_all_types(stdout);
|
audio_output_plugins_for_each(plugin)
|
||||||
|
printf(" %s", plugin->name);
|
||||||
|
puts("");
|
||||||
|
|
||||||
|
#ifdef ENABLE_ENCODER
|
||||||
|
puts("\n"
|
||||||
|
"Encoder plugins:");
|
||||||
|
encoder_plugins_for_each(plugin)
|
||||||
|
printf(" %s", plugin->name);
|
||||||
|
puts("");
|
||||||
|
#endif
|
||||||
|
|
||||||
#ifdef ENABLE_ARCHIVE
|
#ifdef ENABLE_ARCHIVE
|
||||||
puts("\n"
|
puts("\n"
|
||||||
"Supported archives:\n");
|
"Archive plugins:");
|
||||||
archive_plugin_init_all();
|
archive_plugins_for_each(plugin) {
|
||||||
archive_plugin_print_all_suffixes(stdout);
|
printf(" [%s]", plugin->name);
|
||||||
|
|
||||||
|
const char *const*suffixes = plugin->suffixes;
|
||||||
|
if (suffixes != NULL)
|
||||||
|
for (; *suffixes != NULL; ++suffixes)
|
||||||
|
printf(" %s", *suffixes);
|
||||||
|
|
||||||
|
puts("");
|
||||||
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
puts("\n"
|
puts("\n"
|
||||||
"Supported protocols:\n");
|
"Input plugins:");
|
||||||
|
input_plugins_for_each(plugin)
|
||||||
|
printf(" %s", plugin->name);
|
||||||
|
|
||||||
|
puts("\n\n"
|
||||||
|
"Playlist plugins:");
|
||||||
|
playlist_plugins_for_each(plugin)
|
||||||
|
printf(" %s", plugin->name);
|
||||||
|
|
||||||
|
puts("\n\n"
|
||||||
|
"Protocols:");
|
||||||
print_supported_uri_schemes_to_fp(stdout);
|
print_supported_uri_schemes_to_fp(stdout);
|
||||||
|
|
||||||
exit(EXIT_SUCCESS);
|
exit(EXIT_SUCCESS);
|
||||||
}
|
}
|
||||||
|
|
||||||
#if GLIB_CHECK_VERSION(2,12,0)
|
|
||||||
static const char *summary =
|
static const char *summary =
|
||||||
"Music Player Daemon - a daemon for playing music.";
|
"Music Player Daemon - a daemon for playing music.";
|
||||||
#endif
|
|
||||||
|
|
||||||
void parseOptions(int argc, char **argv, Options *options)
|
bool
|
||||||
|
parse_cmdline(int argc, char **argv, struct options *options,
|
||||||
|
GError **error_r)
|
||||||
{
|
{
|
||||||
GError *error = NULL;
|
GError *error = NULL;
|
||||||
GOptionContext *context;
|
GOptionContext *context;
|
||||||
bool ret;
|
bool ret;
|
||||||
static gboolean option_version,
|
static gboolean option_version,
|
||||||
option_create_db, option_no_create_db, option_no_daemon,
|
option_no_daemon,
|
||||||
option_no_config;
|
option_no_config;
|
||||||
const GOptionEntry entries[] = {
|
const GOptionEntry entries[] = {
|
||||||
{ "create-db", 0, 0, G_OPTION_ARG_NONE, &option_create_db,
|
|
||||||
"force (re)creation of database", NULL },
|
|
||||||
{ "kill", 0, 0, G_OPTION_ARG_NONE, &options->kill,
|
{ "kill", 0, 0, G_OPTION_ARG_NONE, &options->kill,
|
||||||
"kill the currently running mpd session", NULL },
|
"kill the currently running mpd session", NULL },
|
||||||
{ "no-config", 0, 0, G_OPTION_ARG_NONE, &option_no_config,
|
{ "no-config", 0, 0, G_OPTION_ARG_NONE, &option_no_config,
|
||||||
"don't read from config", NULL },
|
"don't read from config", NULL },
|
||||||
{ "no-create-db", 0, 0, G_OPTION_ARG_NONE, &option_no_create_db,
|
|
||||||
"don't create database, even if it doesn't exist", NULL },
|
|
||||||
{ "no-daemon", 0, 0, G_OPTION_ARG_NONE, &option_no_daemon,
|
{ "no-daemon", 0, 0, G_OPTION_ARG_NONE, &option_no_daemon,
|
||||||
"don't detach from console", NULL },
|
"don't detach from console", NULL },
|
||||||
{ "stdout", 0, 0, G_OPTION_ARG_NONE, &options->stdOutput,
|
{ "stdout", 0, 0, G_OPTION_ARG_NONE, &options->log_stderr,
|
||||||
|
NULL, NULL },
|
||||||
|
{ "stderr", 0, 0, G_OPTION_ARG_NONE, &options->log_stderr,
|
||||||
"print messages to stderr", NULL },
|
"print messages to stderr", NULL },
|
||||||
{ "verbose", 'v', 0, G_OPTION_ARG_NONE, &options->verbose,
|
{ "verbose", 'v', 0, G_OPTION_ARG_NONE, &options->verbose,
|
||||||
"verbose logging", NULL },
|
"verbose logging", NULL },
|
||||||
@@ -107,24 +164,19 @@ void parseOptions(int argc, char **argv, Options *options)
|
|||||||
|
|
||||||
options->kill = false;
|
options->kill = false;
|
||||||
options->daemon = true;
|
options->daemon = true;
|
||||||
options->stdOutput = false;
|
options->log_stderr = false;
|
||||||
options->verbose = false;
|
options->verbose = false;
|
||||||
options->createDB = 0;
|
|
||||||
|
|
||||||
context = g_option_context_new("[path/to/mpd.conf]");
|
context = g_option_context_new("[path/to/mpd.conf]");
|
||||||
g_option_context_add_main_entries(context, entries, NULL);
|
g_option_context_add_main_entries(context, entries, NULL);
|
||||||
|
|
||||||
#if GLIB_CHECK_VERSION(2,12,0)
|
|
||||||
g_option_context_set_summary(context, summary);
|
g_option_context_set_summary(context, summary);
|
||||||
#endif
|
|
||||||
|
|
||||||
ret = g_option_context_parse(context, &argc, &argv, &error);
|
ret = g_option_context_parse(context, &argc, &argv, &error);
|
||||||
g_option_context_free(context);
|
g_option_context_free(context);
|
||||||
|
|
||||||
if (!ret) {
|
if (!ret)
|
||||||
g_error("option parsing failed: %s\n", error->message);
|
MPD_ERROR("option parsing failed: %s\n", error->message);
|
||||||
exit(1);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (option_version)
|
if (option_version)
|
||||||
version();
|
version();
|
||||||
@@ -133,39 +185,69 @@ void parseOptions(int argc, char **argv, Options *options)
|
|||||||
parser can use it already */
|
parser can use it already */
|
||||||
log_early_init(options->verbose);
|
log_early_init(options->verbose);
|
||||||
|
|
||||||
if (option_create_db && option_no_create_db)
|
|
||||||
g_error("Cannot use both --create-db and --no-create-db\n");
|
|
||||||
|
|
||||||
if (option_no_create_db)
|
|
||||||
options->createDB = -1;
|
|
||||||
else if (option_create_db)
|
|
||||||
options->createDB = 1;
|
|
||||||
|
|
||||||
options->daemon = !option_no_daemon;
|
options->daemon = !option_no_daemon;
|
||||||
|
|
||||||
if (option_no_config) {
|
if (option_no_config) {
|
||||||
g_debug("Ignoring config, using daemon defaults\n");
|
g_debug("Ignoring config, using daemon defaults\n");
|
||||||
|
return true;
|
||||||
} else if (argc <= 1) {
|
} else if (argc <= 1) {
|
||||||
/* default configuration file path */
|
/* default configuration file path */
|
||||||
char *path1;
|
char *path1;
|
||||||
char *path2;
|
|
||||||
|
|
||||||
|
#ifdef G_OS_WIN32
|
||||||
|
path1 = g_build_filename(g_get_user_config_dir(),
|
||||||
|
CONFIG_FILE_LOCATION, NULL);
|
||||||
|
if (g_file_test(path1, G_FILE_TEST_IS_REGULAR))
|
||||||
|
ret = config_read_file(path1, error_r);
|
||||||
|
else {
|
||||||
|
int i = 0;
|
||||||
|
char *system_path = NULL;
|
||||||
|
const char * const *system_config_dirs;
|
||||||
|
|
||||||
|
system_config_dirs = g_get_system_config_dirs();
|
||||||
|
|
||||||
|
while(system_config_dirs[i] != NULL) {
|
||||||
|
system_path = g_build_filename(system_config_dirs[i],
|
||||||
|
CONFIG_FILE_LOCATION,
|
||||||
|
NULL);
|
||||||
|
if(g_file_test(system_path,
|
||||||
|
G_FILE_TEST_IS_REGULAR)) {
|
||||||
|
ret = config_read_file(system_path,error_r);
|
||||||
|
g_free(system_path);
|
||||||
|
break;
|
||||||
|
} else
|
||||||
|
g_free(system_path);
|
||||||
|
++i;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#else /* G_OS_WIN32 */
|
||||||
|
char *path2;
|
||||||
path1 = g_build_filename(g_get_home_dir(),
|
path1 = g_build_filename(g_get_home_dir(),
|
||||||
USER_CONFIG_FILE_LOCATION1, NULL);
|
USER_CONFIG_FILE_LOCATION1, NULL);
|
||||||
path2 = g_build_filename(g_get_home_dir(),
|
path2 = g_build_filename(g_get_home_dir(),
|
||||||
USER_CONFIG_FILE_LOCATION2, NULL);
|
USER_CONFIG_FILE_LOCATION2, NULL);
|
||||||
if (g_file_test(path1, G_FILE_TEST_IS_REGULAR))
|
if (g_file_test(path1, G_FILE_TEST_IS_REGULAR))
|
||||||
config_read_file(path1);
|
ret = config_read_file(path1, error_r);
|
||||||
else if (g_file_test(path2, G_FILE_TEST_IS_REGULAR))
|
else if (g_file_test(path2, G_FILE_TEST_IS_REGULAR))
|
||||||
config_read_file(path2);
|
ret = config_read_file(path2, error_r);
|
||||||
else if (g_file_test(SYSTEM_CONFIG_FILE_LOCATION,
|
else if (g_file_test(SYSTEM_CONFIG_FILE_LOCATION,
|
||||||
G_FILE_TEST_IS_REGULAR))
|
G_FILE_TEST_IS_REGULAR))
|
||||||
config_read_file(SYSTEM_CONFIG_FILE_LOCATION);
|
ret = config_read_file(SYSTEM_CONFIG_FILE_LOCATION,
|
||||||
|
error_r);
|
||||||
|
#endif
|
||||||
|
|
||||||
g_free(path1);
|
g_free(path1);
|
||||||
|
#ifndef G_OS_WIN32
|
||||||
g_free(path2);
|
g_free(path2);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
return ret;
|
||||||
} else if (argc == 2) {
|
} else if (argc == 2) {
|
||||||
/* specified configuration file */
|
/* specified configuration file */
|
||||||
config_read_file(argv[1]);
|
return config_read_file(argv[1], error_r);
|
||||||
} else
|
} else {
|
||||||
g_error("too many arguments");
|
g_set_error(error_r, cmdline_quark(), 0,
|
||||||
|
"too many arguments");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright (C) 2003-2009 The Music Player Daemon Project
|
* Copyright (C) 2003-2011 The Music Player Daemon Project
|
||||||
* http://www.musicpd.org
|
* http://www.musicpd.org
|
||||||
*
|
*
|
||||||
* This program is free software; you can redistribute it and/or modify
|
* This program is free software; you can redistribute it and/or modify
|
||||||
@@ -22,14 +22,17 @@
|
|||||||
|
|
||||||
#include <glib.h>
|
#include <glib.h>
|
||||||
|
|
||||||
typedef struct _Options {
|
#include <stdbool.h>
|
||||||
|
|
||||||
|
struct options {
|
||||||
gboolean kill;
|
gboolean kill;
|
||||||
gboolean daemon;
|
gboolean daemon;
|
||||||
gboolean stdOutput;
|
gboolean log_stderr;
|
||||||
gboolean verbose;
|
gboolean verbose;
|
||||||
int createDB;
|
};
|
||||||
} Options;
|
|
||||||
|
|
||||||
void parseOptions(int argc, char **argv, Options *options);
|
bool
|
||||||
|
parse_cmdline(int argc, char **argv, struct options *options,
|
||||||
|
GError **error_r);
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
1393
src/command.c
1393
src/command.c
File diff suppressed because it is too large
Load Diff
@@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright (C) 2003-2009 The Music Player Daemon Project
|
* Copyright (C) 2003-2011 The Music Player Daemon Project
|
||||||
* http://www.musicpd.org
|
* http://www.musicpd.org
|
||||||
*
|
*
|
||||||
* This program is free software; you can redistribute it and/or modify
|
* This program is free software; you can redistribute it and/or modify
|
||||||
@@ -39,11 +39,7 @@ void command_init(void);
|
|||||||
void command_finish(void);
|
void command_finish(void);
|
||||||
|
|
||||||
enum command_return
|
enum command_return
|
||||||
command_process_list(struct client *client,
|
command_process(struct client *client, unsigned num, char *line);
|
||||||
bool list_ok, GSList *list);
|
|
||||||
|
|
||||||
enum command_return
|
|
||||||
command_process(struct client *client, char *commandString);
|
|
||||||
|
|
||||||
void command_success(struct client *client);
|
void command_success(struct client *client);
|
||||||
|
|
||||||
|
|||||||
410
src/compress.c
410
src/compress.c
@@ -1,410 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (C) 2003-2009 The Music Player Daemon Project
|
|
||||||
* http://www.musicpd.org
|
|
||||||
*
|
|
||||||
* This program is free software; you can redistribute it and/or modify
|
|
||||||
* it under the terms of the GNU General Public License as published by
|
|
||||||
* the Free Software Foundation; either version 2 of the License, or
|
|
||||||
* (at your option) any later version.
|
|
||||||
*
|
|
||||||
* This program is distributed in the hope that it will be useful,
|
|
||||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
* GNU General Public License for more details.
|
|
||||||
*
|
|
||||||
* You should have received a copy of the GNU General Public License along
|
|
||||||
* with this program; if not, write to the Free Software Foundation, Inc.,
|
|
||||||
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
|
||||||
*/
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Imported from AudioCompress by J. Shagam <fluffy@beesbuzz.biz>
|
|
||||||
*/
|
|
||||||
|
|
||||||
#include "compress.h"
|
|
||||||
|
|
||||||
#include <glib.h>
|
|
||||||
|
|
||||||
#include <stdint.h>
|
|
||||||
#include <string.h>
|
|
||||||
|
|
||||||
#ifdef USE_X
|
|
||||||
#include <X11/Xlib.h>
|
|
||||||
#include <X11/Xutil.h>
|
|
||||||
|
|
||||||
static Display *display;
|
|
||||||
static Window window;
|
|
||||||
static Visual *visual;
|
|
||||||
static int screen;
|
|
||||||
static GC blackGC, whiteGC, blueGC, yellowGC, dkyellowGC, redGC;
|
|
||||||
#endif
|
|
||||||
|
|
||||||
static int *peaks;
|
|
||||||
static int gainCurrent, gainTarget;
|
|
||||||
|
|
||||||
static struct {
|
|
||||||
int show_mon;
|
|
||||||
int anticlip;
|
|
||||||
int target;
|
|
||||||
int gainmax;
|
|
||||||
int gainsmooth;
|
|
||||||
unsigned buckets;
|
|
||||||
} prefs;
|
|
||||||
|
|
||||||
#ifdef USE_X
|
|
||||||
static int mon_init;
|
|
||||||
#endif
|
|
||||||
|
|
||||||
void CompressCfg(int show_mon, int anticlip, int target, int gainmax,
|
|
||||||
int gainsmooth, unsigned buckets)
|
|
||||||
{
|
|
||||||
static unsigned lastsize;
|
|
||||||
|
|
||||||
prefs.show_mon = show_mon;
|
|
||||||
prefs.anticlip = anticlip;
|
|
||||||
prefs.target = target;
|
|
||||||
prefs.gainmax = gainmax;
|
|
||||||
prefs.gainsmooth = gainsmooth;
|
|
||||||
prefs.buckets = buckets;
|
|
||||||
|
|
||||||
/* Allocate the peak structure */
|
|
||||||
peaks = g_realloc(peaks, sizeof(int)*prefs.buckets);
|
|
||||||
|
|
||||||
if (prefs.buckets > lastsize)
|
|
||||||
memset(peaks + lastsize, 0, sizeof(int)*(prefs.buckets
|
|
||||||
- lastsize));
|
|
||||||
lastsize = prefs.buckets;
|
|
||||||
|
|
||||||
#ifdef USE_X
|
|
||||||
/* Configure the monitor window if needed */
|
|
||||||
if (show_mon && !mon_init)
|
|
||||||
{
|
|
||||||
display = XOpenDisplay(getenv("DISPLAY"));
|
|
||||||
|
|
||||||
/* We really shouldn't try to init X if there's no X */
|
|
||||||
if (!display)
|
|
||||||
{
|
|
||||||
fprintf(stderr,
|
|
||||||
"X not detected; disabling monitor window\n");
|
|
||||||
show_mon = prefs.show_mon = 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (show_mon && !mon_init)
|
|
||||||
{
|
|
||||||
XGCValues gcv;
|
|
||||||
XColor col;
|
|
||||||
|
|
||||||
gainCurrent = gainTarget = (1 << GAINSHIFT);
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
screen = DefaultScreen(display);
|
|
||||||
visual = DefaultVisual(display, screen);
|
|
||||||
window = XCreateSimpleWindow(display,
|
|
||||||
RootWindow(display, screen),
|
|
||||||
0, 0, prefs.buckets, 128 + 8, 0,
|
|
||||||
BlackPixel(display, screen),
|
|
||||||
WhitePixel(display, screen));
|
|
||||||
XStoreName(display, window, "AudioCompress monitor");
|
|
||||||
|
|
||||||
gcv.foreground = BlackPixel(display, screen);
|
|
||||||
blackGC = XCreateGC(display, window, GCForeground, &gcv);
|
|
||||||
gcv.foreground = WhitePixel(display, screen);
|
|
||||||
whiteGC = XCreateGC(display, window, GCForeground, &gcv);
|
|
||||||
col.red = 0;
|
|
||||||
col.green = 0;
|
|
||||||
col.blue = 65535;
|
|
||||||
XAllocColor(display, DefaultColormap(display, screen), &col);
|
|
||||||
gcv.foreground = col.pixel;
|
|
||||||
blueGC = XCreateGC(display, window, GCForeground, &gcv);
|
|
||||||
col.red = 65535;
|
|
||||||
col.green = 65535;
|
|
||||||
col.blue = 0;
|
|
||||||
XAllocColor(display, DefaultColormap(display, screen), &col);
|
|
||||||
gcv.foreground = col.pixel;
|
|
||||||
yellowGC = XCreateGC(display, window, GCForeground, &gcv);
|
|
||||||
col.red = 32767;
|
|
||||||
col.green = 32767;
|
|
||||||
col.blue = 0;
|
|
||||||
XAllocColor(display, DefaultColormap(display, screen), &col);
|
|
||||||
gcv.foreground = col.pixel;
|
|
||||||
dkyellowGC = XCreateGC(display, window, GCForeground, &gcv);
|
|
||||||
col.red = 65535;
|
|
||||||
col.green = 0;
|
|
||||||
col.blue = 0;
|
|
||||||
XAllocColor(display, DefaultColormap(display, screen), &col);
|
|
||||||
gcv.foreground = col.pixel;
|
|
||||||
redGC = XCreateGC(display, window, GCForeground, &gcv);
|
|
||||||
mon_init = 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (mon_init)
|
|
||||||
{
|
|
||||||
if (show_mon)
|
|
||||||
XMapWindow(display, window);
|
|
||||||
else
|
|
||||||
XUnmapWindow(display, window);
|
|
||||||
XResizeWindow(display, window, prefs.buckets, 128 + 8);
|
|
||||||
XFlush(display);
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
|
|
||||||
void CompressFree(void)
|
|
||||||
{
|
|
||||||
#ifdef USE_X
|
|
||||||
if (mon_init)
|
|
||||||
{
|
|
||||||
XFreeGC(display, blackGC);
|
|
||||||
XFreeGC(display, whiteGC);
|
|
||||||
XFreeGC(display, blueGC);
|
|
||||||
XFreeGC(display, yellowGC);
|
|
||||||
XFreeGC(display, dkyellowGC);
|
|
||||||
XFreeGC(display, redGC);
|
|
||||||
XDestroyWindow(display, window);
|
|
||||||
XCloseDisplay(display);
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
|
|
||||||
g_free(peaks);
|
|
||||||
}
|
|
||||||
|
|
||||||
void CompressDo(void *data, unsigned int length)
|
|
||||||
{
|
|
||||||
int16_t *audio = (int16_t *)data, *ap;
|
|
||||||
int peak;
|
|
||||||
unsigned int i, pos;
|
|
||||||
int gr, gf, gn;
|
|
||||||
static int pn = -1;
|
|
||||||
#ifdef STATS
|
|
||||||
static int clip;
|
|
||||||
#endif
|
|
||||||
static int clipped;
|
|
||||||
|
|
||||||
if (!peaks)
|
|
||||||
return;
|
|
||||||
|
|
||||||
if (pn == -1)
|
|
||||||
{
|
|
||||||
for (i = 0; i < prefs.buckets; i++)
|
|
||||||
peaks[i] = 0;
|
|
||||||
}
|
|
||||||
pn = (pn + 1)%prefs.buckets;
|
|
||||||
|
|
||||||
#ifdef DEBUG
|
|
||||||
fprintf(stderr, "modifyNative16(0x%08x, %d)\n",(unsigned int)data,
|
|
||||||
length);
|
|
||||||
#endif
|
|
||||||
|
|
||||||
/* Determine peak's value and position */
|
|
||||||
peak = 1;
|
|
||||||
pos = 0;
|
|
||||||
|
|
||||||
#ifdef DEBUG
|
|
||||||
fprintf(stderr, "finding peak(b=%d)\n", pn);
|
|
||||||
#endif
|
|
||||||
|
|
||||||
ap = audio;
|
|
||||||
for (i = 0; i < length/2; i++)
|
|
||||||
{
|
|
||||||
int val = *ap;
|
|
||||||
if (val > peak)
|
|
||||||
{
|
|
||||||
peak = val;
|
|
||||||
pos = i;
|
|
||||||
} else if (-val > peak)
|
|
||||||
{
|
|
||||||
peak = -val;
|
|
||||||
pos = i;
|
|
||||||
}
|
|
||||||
ap++;
|
|
||||||
}
|
|
||||||
peaks[pn] = peak;
|
|
||||||
|
|
||||||
/* Only draw if needed, of course */
|
|
||||||
#ifdef USE_X
|
|
||||||
if (prefs.show_mon)
|
|
||||||
{
|
|
||||||
/* current amplitude */
|
|
||||||
XDrawLine(display, window, whiteGC,
|
|
||||||
pn, 0,
|
|
||||||
pn,
|
|
||||||
127 -
|
|
||||||
(peaks[pn]*gainCurrent >> (GAINSHIFT + 8)));
|
|
||||||
|
|
||||||
/* amplification */
|
|
||||||
XDrawLine(display, window, yellowGC,
|
|
||||||
pn,
|
|
||||||
127 - (peaks[pn]*gainCurrent
|
|
||||||
>> (GAINSHIFT + 8)),
|
|
||||||
pn, 127);
|
|
||||||
|
|
||||||
/* peak */
|
|
||||||
XDrawLine(display, window, blackGC,
|
|
||||||
pn, 127 - (peaks[pn] >> 8), pn, 127);
|
|
||||||
|
|
||||||
/* clip indicator */
|
|
||||||
if (clipped)
|
|
||||||
XDrawLine(display, window, redGC,
|
|
||||||
(pn + prefs.buckets - 1)%prefs.buckets,
|
|
||||||
126 - clipped/(length*512),
|
|
||||||
(pn + prefs.buckets - 1)%prefs.buckets,
|
|
||||||
127);
|
|
||||||
clipped = 0;
|
|
||||||
|
|
||||||
/* target line */
|
|
||||||
/* XDrawPoint(display, window, redGC, */
|
|
||||||
/* pn, 127 - TARGET/256); */
|
|
||||||
/* amplification edge */
|
|
||||||
XDrawLine(display, window, dkyellowGC,
|
|
||||||
pn,
|
|
||||||
127 - (peaks[pn]*gainCurrent
|
|
||||||
>> (GAINSHIFT + 8)),
|
|
||||||
pn - 1,
|
|
||||||
127 -
|
|
||||||
(peaks[(pn + prefs.buckets
|
|
||||||
- 1)%prefs.buckets]*gainCurrent
|
|
||||||
>> (GAINSHIFT + 8)));
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
|
|
||||||
for (i = 0; i < prefs.buckets; i++)
|
|
||||||
{
|
|
||||||
if (peaks[i] > peak)
|
|
||||||
{
|
|
||||||
peak = peaks[i];
|
|
||||||
pos = 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Determine target gain */
|
|
||||||
gn = (1 << GAINSHIFT)*prefs.target/peak;
|
|
||||||
|
|
||||||
if (gn <(1 << GAINSHIFT))
|
|
||||||
gn = 1 << GAINSHIFT;
|
|
||||||
|
|
||||||
gainTarget = (gainTarget *((1 << prefs.gainsmooth) - 1) + gn)
|
|
||||||
>> prefs.gainsmooth;
|
|
||||||
|
|
||||||
/* Give it an extra insignifigant nudge to counteract possible
|
|
||||||
** rounding error
|
|
||||||
*/
|
|
||||||
|
|
||||||
if (gn < gainTarget)
|
|
||||||
gainTarget--;
|
|
||||||
else if (gn > gainTarget)
|
|
||||||
gainTarget++;
|
|
||||||
|
|
||||||
if (gainTarget > prefs.gainmax << GAINSHIFT)
|
|
||||||
gainTarget = prefs.gainmax << GAINSHIFT;
|
|
||||||
|
|
||||||
|
|
||||||
#ifdef USE_X
|
|
||||||
if (prefs.show_mon)
|
|
||||||
{
|
|
||||||
int x;
|
|
||||||
|
|
||||||
/* peak*gain */
|
|
||||||
XDrawPoint(display, window, redGC,
|
|
||||||
pn,
|
|
||||||
127 - (peak*gainCurrent
|
|
||||||
>> (GAINSHIFT + 8)));
|
|
||||||
|
|
||||||
/* gain indicator */
|
|
||||||
XFillRectangle(display, window, whiteGC, 0, 128,
|
|
||||||
prefs.buckets, 8);
|
|
||||||
x = (gainTarget - (1 << GAINSHIFT))*prefs.buckets
|
|
||||||
/ ((prefs.gainmax - 1) << GAINSHIFT);
|
|
||||||
XDrawLine(display, window, redGC, x,
|
|
||||||
128, x, 128 + 8);
|
|
||||||
|
|
||||||
x = (gn - (1 << GAINSHIFT))*prefs.buckets
|
|
||||||
/ ((prefs.gainmax - 1) << GAINSHIFT);
|
|
||||||
|
|
||||||
XDrawLine(display, window, blackGC,
|
|
||||||
x, 132 - 1,
|
|
||||||
x, 132 + 1);
|
|
||||||
|
|
||||||
/* blue peak line */
|
|
||||||
XDrawLine(display, window, blueGC,
|
|
||||||
0, 127 - (peak >> 8), prefs.buckets,
|
|
||||||
127 - (peak >> 8));
|
|
||||||
XFlush(display);
|
|
||||||
XDrawLine(display, window, whiteGC,
|
|
||||||
0, 127 - (peak >> 8), prefs.buckets,
|
|
||||||
127 - (peak >> 8));
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
|
|
||||||
/* See if a peak is going to clip */
|
|
||||||
gn = (1 << GAINSHIFT)*32768/peak;
|
|
||||||
|
|
||||||
if (gn < gainTarget)
|
|
||||||
{
|
|
||||||
gainTarget = gn;
|
|
||||||
|
|
||||||
if (prefs.anticlip)
|
|
||||||
pos = 0;
|
|
||||||
|
|
||||||
} else
|
|
||||||
{
|
|
||||||
/* We're ramping up, so draw it out over the whole frame */
|
|
||||||
pos = length;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Determine gain rate necessary to make target */
|
|
||||||
if (!pos)
|
|
||||||
pos = 1;
|
|
||||||
|
|
||||||
gr = ((gainTarget - gainCurrent) << 16)/(int)pos;
|
|
||||||
|
|
||||||
/* Do the shiznit */
|
|
||||||
gf = gainCurrent << 16;
|
|
||||||
|
|
||||||
#ifdef STATS
|
|
||||||
fprintf(stderr, "\rgain = %2.2f%+.2e ",
|
|
||||||
gainCurrent*1.0/(1 << GAINSHIFT),
|
|
||||||
(gainTarget - gainCurrent)*1.0/(1 << GAINSHIFT));
|
|
||||||
#endif
|
|
||||||
|
|
||||||
ap = audio;
|
|
||||||
for (i = 0; i < length/2; i++)
|
|
||||||
{
|
|
||||||
int sample;
|
|
||||||
|
|
||||||
/* Interpolate the gain */
|
|
||||||
gainCurrent = gf >> 16;
|
|
||||||
if (i < pos)
|
|
||||||
gf += gr;
|
|
||||||
else if (i == pos)
|
|
||||||
gf = gainTarget << 16;
|
|
||||||
|
|
||||||
/* Amplify */
|
|
||||||
sample = (*ap)*gainCurrent >> GAINSHIFT;
|
|
||||||
if (sample < -32768)
|
|
||||||
{
|
|
||||||
#ifdef STATS
|
|
||||||
clip++;
|
|
||||||
#endif
|
|
||||||
clipped += -32768 - sample;
|
|
||||||
sample = -32768;
|
|
||||||
} else if (sample > 32767)
|
|
||||||
{
|
|
||||||
#ifdef STATS
|
|
||||||
clip++;
|
|
||||||
#endif
|
|
||||||
clipped += sample - 32767;
|
|
||||||
sample = 32767;
|
|
||||||
}
|
|
||||||
*ap++ = sample;
|
|
||||||
}
|
|
||||||
#ifdef STATS
|
|
||||||
fprintf(stderr, "clip %d b%-3d ", clip, pn);
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#ifdef DEBUG
|
|
||||||
fprintf(stderr, "\ndone\n");
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
|
|
||||||
@@ -1,48 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (C) 2003-2009 The Music Player Daemon Project
|
|
||||||
* http://www.musicpd.org
|
|
||||||
*
|
|
||||||
* This program is free software; you can redistribute it and/or modify
|
|
||||||
* it under the terms of the GNU General Public License as published by
|
|
||||||
* the Free Software Foundation; either version 2 of the License, or
|
|
||||||
* (at your option) any later version.
|
|
||||||
*
|
|
||||||
* This program is distributed in the hope that it will be useful,
|
|
||||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
* GNU General Public License for more details.
|
|
||||||
*
|
|
||||||
* You should have received a copy of the GNU General Public License along
|
|
||||||
* with this program; if not, write to the Free Software Foundation, Inc.,
|
|
||||||
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
|
||||||
*/
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Imported from AudioCompress by J. Shagam <fluffy@beesbuzz.biz>
|
|
||||||
*/
|
|
||||||
|
|
||||||
#ifndef MPD_COMPRESS_H
|
|
||||||
#define MPD_COMPRESS_H
|
|
||||||
|
|
||||||
/* These are copied from the AudioCompress config.h, mainly because CompressDo
|
|
||||||
* needs GAINSHIFT defined. The rest are here so they can be used as defaults
|
|
||||||
* to pass to CompressCfg(). -- jat */
|
|
||||||
#define ANTICLIP 0 /* Strict clipping protection */
|
|
||||||
#define TARGET 25000 /* Target level */
|
|
||||||
#define GAINMAX 32 /* The maximum amount to amplify by */
|
|
||||||
#define GAINSHIFT 10 /* How fine-grained the gain is */
|
|
||||||
#define GAINSMOOTH 8 /* How much inertia ramping has*/
|
|
||||||
#define BUCKETS 400 /* How long of a history to store */
|
|
||||||
|
|
||||||
void CompressCfg(int monitor,
|
|
||||||
int anticlip,
|
|
||||||
int target,
|
|
||||||
int maxgain,
|
|
||||||
int smooth,
|
|
||||||
unsigned buckets);
|
|
||||||
|
|
||||||
void CompressDo(void *data, unsigned int numSamples);
|
|
||||||
|
|
||||||
void CompressFree(void);
|
|
||||||
|
|
||||||
#endif
|
|
||||||
604
src/conf.c
604
src/conf.c
@@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright (C) 2003-2009 The Music Player Daemon Project
|
* Copyright (C) 2003-2011 The Music Player Daemon Project
|
||||||
* http://www.musicpd.org
|
* http://www.musicpd.org
|
||||||
*
|
*
|
||||||
* This program is free software; you can redistribute it and/or modify
|
* This program is free software; you can redistribute it and/or modify
|
||||||
@@ -17,10 +17,13 @@
|
|||||||
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
#include "config.h"
|
||||||
#include "conf.h"
|
#include "conf.h"
|
||||||
#include "utils.h"
|
#include "utils.h"
|
||||||
#include "buffer2array.h"
|
#include "string_util.h"
|
||||||
|
#include "tokenizer.h"
|
||||||
#include "path.h"
|
#include "path.h"
|
||||||
|
#include "mpd_error.h"
|
||||||
|
|
||||||
#include <glib.h>
|
#include <glib.h>
|
||||||
|
|
||||||
@@ -36,37 +39,88 @@
|
|||||||
#define MAX_STRING_SIZE MPD_PATH_MAX+80
|
#define MAX_STRING_SIZE MPD_PATH_MAX+80
|
||||||
|
|
||||||
#define CONF_COMMENT '#'
|
#define CONF_COMMENT '#'
|
||||||
#define CONF_BLOCK_BEGIN "{"
|
|
||||||
#define CONF_BLOCK_END "}"
|
|
||||||
|
|
||||||
#define CONF_REPEATABLE_MASK 0x01
|
|
||||||
#define CONF_BLOCK_MASK 0x02
|
|
||||||
#define CONF_LINE_TOKEN_MAX 3
|
|
||||||
|
|
||||||
struct config_entry {
|
struct config_entry {
|
||||||
const char *name;
|
const char *const name;
|
||||||
unsigned char mask;
|
const bool repeatable;
|
||||||
|
const bool block;
|
||||||
|
|
||||||
GSList *params;
|
GSList *params;
|
||||||
};
|
};
|
||||||
|
|
||||||
static GSList *config_entries;
|
static struct config_entry config_entries[] = {
|
||||||
|
{ .name = CONF_MUSIC_DIR, false, false },
|
||||||
|
{ .name = CONF_PLAYLIST_DIR, false, false },
|
||||||
|
{ .name = CONF_FOLLOW_INSIDE_SYMLINKS, false, false },
|
||||||
|
{ .name = CONF_FOLLOW_OUTSIDE_SYMLINKS, false, false },
|
||||||
|
{ .name = CONF_DB_FILE, false, false },
|
||||||
|
{ .name = CONF_STICKER_FILE, false, false },
|
||||||
|
{ .name = CONF_LOG_FILE, false, false },
|
||||||
|
{ .name = CONF_PID_FILE, false, false },
|
||||||
|
{ .name = CONF_STATE_FILE, false, false },
|
||||||
|
{ .name = "restore_paused", false, false },
|
||||||
|
{ .name = CONF_USER, false, false },
|
||||||
|
{ .name = CONF_GROUP, false, false },
|
||||||
|
{ .name = CONF_BIND_TO_ADDRESS, true, false },
|
||||||
|
{ .name = CONF_PORT, false, false },
|
||||||
|
{ .name = CONF_LOG_LEVEL, false, false },
|
||||||
|
{ .name = CONF_ZEROCONF_NAME, false, false },
|
||||||
|
{ .name = CONF_ZEROCONF_ENABLED, false, false },
|
||||||
|
{ .name = CONF_PASSWORD, true, false },
|
||||||
|
{ .name = CONF_DEFAULT_PERMS, false, false },
|
||||||
|
{ .name = CONF_AUDIO_OUTPUT, true, true },
|
||||||
|
{ .name = CONF_AUDIO_OUTPUT_FORMAT, false, false },
|
||||||
|
{ .name = CONF_MIXER_TYPE, false, false },
|
||||||
|
{ .name = CONF_REPLAYGAIN, false, false },
|
||||||
|
{ .name = CONF_REPLAYGAIN_PREAMP, false, false },
|
||||||
|
{ .name = CONF_REPLAYGAIN_MISSING_PREAMP, false, false },
|
||||||
|
{ .name = CONF_REPLAYGAIN_LIMIT, false, false },
|
||||||
|
{ .name = CONF_VOLUME_NORMALIZATION, false, false },
|
||||||
|
{ .name = CONF_SAMPLERATE_CONVERTER, false, false },
|
||||||
|
{ .name = CONF_AUDIO_BUFFER_SIZE, false, false },
|
||||||
|
{ .name = CONF_BUFFER_BEFORE_PLAY, false, false },
|
||||||
|
{ .name = CONF_HTTP_PROXY_HOST, false, false },
|
||||||
|
{ .name = CONF_HTTP_PROXY_PORT, false, false },
|
||||||
|
{ .name = CONF_HTTP_PROXY_USER, false, false },
|
||||||
|
{ .name = CONF_HTTP_PROXY_PASSWORD, false, false },
|
||||||
|
{ .name = CONF_CONN_TIMEOUT, false, false },
|
||||||
|
{ .name = CONF_MAX_CONN, false, false },
|
||||||
|
{ .name = CONF_MAX_PLAYLIST_LENGTH, false, false },
|
||||||
|
{ .name = CONF_MAX_COMMAND_LIST_SIZE, false, false },
|
||||||
|
{ .name = CONF_MAX_OUTPUT_BUFFER_SIZE, false, false },
|
||||||
|
{ .name = CONF_FS_CHARSET, false, false },
|
||||||
|
{ .name = CONF_ID3V1_ENCODING, false, false },
|
||||||
|
{ .name = CONF_METADATA_TO_USE, false, false },
|
||||||
|
{ .name = CONF_SAVE_ABSOLUTE_PATHS, false, false },
|
||||||
|
{ .name = CONF_DECODER, true, true },
|
||||||
|
{ .name = CONF_INPUT, true, true },
|
||||||
|
{ .name = CONF_GAPLESS_MP3_PLAYBACK, false, false },
|
||||||
|
{ .name = CONF_PLAYLIST_PLUGIN, true, true },
|
||||||
|
{ .name = CONF_AUTO_UPDATE, false, false },
|
||||||
|
{ .name = CONF_AUTO_UPDATE_DEPTH, false, false },
|
||||||
|
{ .name = CONF_DESPOTIFY_USER, false, false },
|
||||||
|
{ .name = CONF_DESPOTIFY_PASSWORD, false, false},
|
||||||
|
{ .name = CONF_DESPOTIFY_HIGH_BITRATE, false, false },
|
||||||
|
{ .name = "filter", true, true },
|
||||||
|
};
|
||||||
|
|
||||||
static int get_bool(const char *value)
|
static bool
|
||||||
|
get_bool(const char *value, bool *value_r)
|
||||||
{
|
{
|
||||||
const char **x;
|
|
||||||
static const char *t[] = { "yes", "true", "1", NULL };
|
static const char *t[] = { "yes", "true", "1", NULL };
|
||||||
static const char *f[] = { "no", "false", "0", NULL };
|
static const char *f[] = { "no", "false", "0", NULL };
|
||||||
|
|
||||||
for (x = t; *x; x++) {
|
if (string_array_contains(t, value)) {
|
||||||
if (!g_ascii_strcasecmp(*x, value))
|
*value_r = true;
|
||||||
return 1;
|
return true;
|
||||||
}
|
}
|
||||||
for (x = f; *x; x++) {
|
|
||||||
if (!g_ascii_strcasecmp(*x, value))
|
if (string_array_contains(f, value)) {
|
||||||
return 0;
|
*value_r = false;
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
return CONF_BOOL_INVALID;
|
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
struct config_param *
|
struct config_param *
|
||||||
@@ -83,15 +137,14 @@ config_new_param(const char *value, int line)
|
|||||||
|
|
||||||
ret->num_block_params = 0;
|
ret->num_block_params = 0;
|
||||||
ret->block_params = NULL;
|
ret->block_params = NULL;
|
||||||
|
ret->used = false;
|
||||||
|
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
static void
|
void
|
||||||
config_param_free(gpointer data, G_GNUC_UNUSED gpointer user_data)
|
config_param_free(struct config_param *param)
|
||||||
{
|
{
|
||||||
struct config_param *param = data;
|
|
||||||
|
|
||||||
g_free(param->value);
|
g_free(param->value);
|
||||||
|
|
||||||
for (unsigned i = 0; i < param->num_block_params; i++) {
|
for (unsigned i = 0; i < param->num_block_params; i++) {
|
||||||
@@ -105,42 +158,19 @@ config_param_free(gpointer data, G_GNUC_UNUSED gpointer user_data)
|
|||||||
g_free(param);
|
g_free(param);
|
||||||
}
|
}
|
||||||
|
|
||||||
static struct config_entry *
|
|
||||||
newConfigEntry(const char *name, int repeatable, int block)
|
|
||||||
{
|
|
||||||
struct config_entry *ret = g_new(struct config_entry, 1);
|
|
||||||
|
|
||||||
ret->name = name;
|
|
||||||
ret->mask = 0;
|
|
||||||
ret->params = NULL;
|
|
||||||
|
|
||||||
if (repeatable)
|
|
||||||
ret->mask |= CONF_REPEATABLE_MASK;
|
|
||||||
if (block)
|
|
||||||
ret->mask |= CONF_BLOCK_MASK;
|
|
||||||
|
|
||||||
return ret;
|
|
||||||
}
|
|
||||||
|
|
||||||
static void
|
static void
|
||||||
config_entry_free(gpointer data, G_GNUC_UNUSED gpointer user_data)
|
config_param_free_callback(gpointer data, G_GNUC_UNUSED gpointer user_data)
|
||||||
{
|
{
|
||||||
struct config_entry *entry = data;
|
struct config_param *param = data;
|
||||||
|
|
||||||
g_slist_foreach(entry->params, config_param_free, NULL);
|
config_param_free(param);
|
||||||
g_slist_free(entry->params);
|
|
||||||
|
|
||||||
g_free(entry);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static struct config_entry *
|
static struct config_entry *
|
||||||
config_entry_get(const char *name)
|
config_entry_get(const char *name)
|
||||||
{
|
{
|
||||||
GSList *list;
|
for (unsigned i = 0; i < G_N_ELEMENTS(config_entries); ++i) {
|
||||||
|
struct config_entry *entry = &config_entries[i];
|
||||||
for (list = config_entries; list != NULL;
|
|
||||||
list = g_slist_next(list)) {
|
|
||||||
struct config_entry *entry = list->data;
|
|
||||||
if (strcmp(entry->name, name) == 0)
|
if (strcmp(entry->name, name) == 0)
|
||||||
return entry;
|
return entry;
|
||||||
}
|
}
|
||||||
@@ -148,74 +178,48 @@ config_entry_get(const char *name)
|
|||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
static void registerConfigParam(const char *name, int repeatable, int block)
|
|
||||||
{
|
|
||||||
struct config_entry *entry;
|
|
||||||
|
|
||||||
entry = config_entry_get(name);
|
|
||||||
if (entry != NULL)
|
|
||||||
g_error("config parameter \"%s\" already registered\n", name);
|
|
||||||
|
|
||||||
entry = newConfigEntry(name, repeatable, block);
|
|
||||||
config_entries = g_slist_prepend(config_entries, entry);
|
|
||||||
}
|
|
||||||
|
|
||||||
void config_global_finish(void)
|
void config_global_finish(void)
|
||||||
{
|
{
|
||||||
g_slist_foreach(config_entries, config_entry_free, NULL);
|
for (unsigned i = 0; i < G_N_ELEMENTS(config_entries); ++i) {
|
||||||
g_slist_free(config_entries);
|
struct config_entry *entry = &config_entries[i];
|
||||||
|
|
||||||
|
g_slist_foreach(entry->params,
|
||||||
|
config_param_free_callback, NULL);
|
||||||
|
g_slist_free(entry->params);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void config_global_init(void)
|
void config_global_init(void)
|
||||||
{
|
{
|
||||||
config_entries = NULL;
|
}
|
||||||
|
|
||||||
/* registerConfigParam(name, repeatable, block); */
|
static void
|
||||||
registerConfigParam(CONF_MUSIC_DIR, 0, 0);
|
config_param_check(gpointer data, G_GNUC_UNUSED gpointer user_data)
|
||||||
registerConfigParam(CONF_PLAYLIST_DIR, 0, 0);
|
{
|
||||||
registerConfigParam(CONF_FOLLOW_INSIDE_SYMLINKS, 0, 0);
|
struct config_param *param = data;
|
||||||
registerConfigParam(CONF_FOLLOW_OUTSIDE_SYMLINKS, 0, 0);
|
|
||||||
registerConfigParam(CONF_DB_FILE, 0, 0);
|
if (!param->used)
|
||||||
registerConfigParam(CONF_STICKER_FILE, false, false);
|
/* this whole config_param was not queried at all -
|
||||||
registerConfigParam(CONF_LOG_FILE, 0, 0);
|
the feature might be disabled at compile time?
|
||||||
registerConfigParam(CONF_ERROR_FILE, 0, 0);
|
Silently ignore it here. */
|
||||||
registerConfigParam(CONF_PID_FILE, 0, 0);
|
return;
|
||||||
registerConfigParam(CONF_STATE_FILE, 0, 0);
|
|
||||||
registerConfigParam(CONF_USER, 0, 0);
|
for (unsigned i = 0; i < param->num_block_params; i++) {
|
||||||
registerConfigParam(CONF_BIND_TO_ADDRESS, 1, 0);
|
struct block_param *bp = ¶m->block_params[i];
|
||||||
registerConfigParam(CONF_PORT, 0, 0);
|
|
||||||
registerConfigParam(CONF_LOG_LEVEL, 0, 0);
|
if (!bp->used)
|
||||||
registerConfigParam(CONF_ZEROCONF_NAME, 0, 0);
|
g_warning("option '%s' on line %i was not recognized",
|
||||||
registerConfigParam(CONF_ZEROCONF_ENABLED, 0, 0);
|
bp->name, bp->line);
|
||||||
registerConfigParam(CONF_PASSWORD, 1, 0);
|
}
|
||||||
registerConfigParam(CONF_DEFAULT_PERMS, 0, 0);
|
}
|
||||||
registerConfigParam(CONF_AUDIO_OUTPUT, 1, 1);
|
|
||||||
registerConfigParam(CONF_AUDIO_OUTPUT_FORMAT, 0, 0);
|
void config_global_check(void)
|
||||||
registerConfigParam(CONF_MIXER_TYPE, 0, 0);
|
{
|
||||||
registerConfigParam(CONF_MIXER_DEVICE, 0, 0);
|
for (unsigned i = 0; i < G_N_ELEMENTS(config_entries); ++i) {
|
||||||
registerConfigParam(CONF_MIXER_CONTROL, 0, 0);
|
struct config_entry *entry = &config_entries[i];
|
||||||
registerConfigParam(CONF_REPLAYGAIN, 0, 0);
|
|
||||||
registerConfigParam(CONF_REPLAYGAIN_PREAMP, 0, 0);
|
g_slist_foreach(entry->params, config_param_check, NULL);
|
||||||
registerConfigParam(CONF_VOLUME_NORMALIZATION, 0, 0);
|
}
|
||||||
registerConfigParam(CONF_SAMPLERATE_CONVERTER, 0, 0);
|
|
||||||
registerConfigParam(CONF_AUDIO_BUFFER_SIZE, 0, 0);
|
|
||||||
registerConfigParam(CONF_BUFFER_BEFORE_PLAY, 0, 0);
|
|
||||||
registerConfigParam(CONF_HTTP_PROXY_HOST, 0, 0);
|
|
||||||
registerConfigParam(CONF_HTTP_PROXY_PORT, 0, 0);
|
|
||||||
registerConfigParam(CONF_HTTP_PROXY_USER, 0, 0);
|
|
||||||
registerConfigParam(CONF_HTTP_PROXY_PASSWORD, 0, 0);
|
|
||||||
registerConfigParam(CONF_CONN_TIMEOUT, 0, 0);
|
|
||||||
registerConfigParam(CONF_MAX_CONN, 0, 0);
|
|
||||||
registerConfigParam(CONF_MAX_PLAYLIST_LENGTH, 0, 0);
|
|
||||||
registerConfigParam(CONF_MAX_COMMAND_LIST_SIZE, 0, 0);
|
|
||||||
registerConfigParam(CONF_MAX_OUTPUT_BUFFER_SIZE, 0, 0);
|
|
||||||
registerConfigParam(CONF_FS_CHARSET, 0, 0);
|
|
||||||
registerConfigParam(CONF_ID3V1_ENCODING, 0, 0);
|
|
||||||
registerConfigParam(CONF_METADATA_TO_USE, 0, 0);
|
|
||||||
registerConfigParam(CONF_SAVE_ABSOLUTE_PATHS, 0, 0);
|
|
||||||
registerConfigParam(CONF_DECODER, true, true);
|
|
||||||
registerConfigParam(CONF_INPUT, true, true);
|
|
||||||
registerConfigParam(CONF_GAPLESS_MP3_PLAYBACK, 0, 0);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
@@ -224,6 +228,8 @@ config_add_block_param(struct config_param * param, const char *name,
|
|||||||
{
|
{
|
||||||
struct block_param *bp;
|
struct block_param *bp;
|
||||||
|
|
||||||
|
assert(config_get_block_param(param, name) == NULL);
|
||||||
|
|
||||||
param->num_block_params++;
|
param->num_block_params++;
|
||||||
|
|
||||||
param->block_params = g_realloc(param->block_params,
|
param->block_params = g_realloc(param->block_params,
|
||||||
@@ -235,67 +241,105 @@ config_add_block_param(struct config_param * param, const char *name,
|
|||||||
bp->name = g_strdup(name);
|
bp->name = g_strdup(name);
|
||||||
bp->value = g_strdup(value);
|
bp->value = g_strdup(value);
|
||||||
bp->line = line;
|
bp->line = line;
|
||||||
|
bp->used = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool
|
||||||
|
config_read_name_value(struct config_param *param, char *input, unsigned line,
|
||||||
|
GError **error_r)
|
||||||
|
{
|
||||||
|
const char *name = tokenizer_next_word(&input, error_r);
|
||||||
|
if (name == NULL) {
|
||||||
|
assert(*input != 0);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const char *value = tokenizer_next_string(&input, error_r);
|
||||||
|
if (value == NULL) {
|
||||||
|
if (*input == 0) {
|
||||||
|
assert(error_r == NULL || *error_r == NULL);
|
||||||
|
g_set_error(error_r, config_quark(), 0,
|
||||||
|
"Value missing");
|
||||||
|
} else {
|
||||||
|
assert(error_r == NULL || *error_r != NULL);
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (*input != 0 && *input != CONF_COMMENT) {
|
||||||
|
g_set_error(error_r, config_quark(), 0,
|
||||||
|
"Unknown tokens after value");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const struct block_param *bp = config_get_block_param(param, name);
|
||||||
|
if (bp != NULL) {
|
||||||
|
g_set_error(error_r, config_quark(), 0,
|
||||||
|
"\"%s\" is duplicate, first defined on line %i",
|
||||||
|
name, bp->line);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
config_add_block_param(param, name, value, line);
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
static struct config_param *
|
static struct config_param *
|
||||||
config_read_block(FILE *fp, int *count, char *string)
|
config_read_block(FILE *fp, int *count, char *string, GError **error_r)
|
||||||
{
|
{
|
||||||
struct config_param *ret = config_new_param(NULL, *count);
|
struct config_param *ret = config_new_param(NULL, *count);
|
||||||
|
GError *error = NULL;
|
||||||
|
|
||||||
int i;
|
while (true) {
|
||||||
int numberOfArgs;
|
char *line;
|
||||||
int argsMinusComment;
|
|
||||||
|
|
||||||
while (fgets(string, MAX_STRING_SIZE, fp)) {
|
line = fgets(string, MAX_STRING_SIZE, fp);
|
||||||
char *array[CONF_LINE_TOKEN_MAX] = { NULL };
|
if (line == NULL) {
|
||||||
|
config_param_free(ret);
|
||||||
|
g_set_error(error_r, config_quark(), 0,
|
||||||
|
"Expected '}' before end-of-file");
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
(*count)++;
|
(*count)++;
|
||||||
|
line = strchug_fast(line);
|
||||||
numberOfArgs = buffer2array(string, array, CONF_LINE_TOKEN_MAX);
|
if (*line == 0 || *line == CONF_COMMENT)
|
||||||
|
|
||||||
for (i = 0; i < numberOfArgs; i++) {
|
|
||||||
if (array[i][0] == CONF_COMMENT)
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
argsMinusComment = i;
|
|
||||||
|
|
||||||
if (0 == argsMinusComment) {
|
|
||||||
continue;
|
continue;
|
||||||
}
|
|
||||||
|
|
||||||
if (1 == argsMinusComment &&
|
if (*line == '}') {
|
||||||
0 == strcmp(array[0], CONF_BLOCK_END)) {
|
/* end of this block; return from the function
|
||||||
break;
|
(and from this "while" loop) */
|
||||||
}
|
|
||||||
|
|
||||||
if (2 != argsMinusComment) {
|
line = strchug_fast(line + 1);
|
||||||
g_error("improperly formatted config file at line %i:"
|
if (*line != 0 && *line != CONF_COMMENT) {
|
||||||
" %s\n", *count, string);
|
config_param_free(ret);
|
||||||
}
|
g_set_error(error_r, config_quark(), 0,
|
||||||
|
"line %i: Unknown tokens after '}'",
|
||||||
if (0 == strcmp(array[0], CONF_BLOCK_BEGIN) ||
|
*count);
|
||||||
0 == strcmp(array[1], CONF_BLOCK_BEGIN) ||
|
return false;
|
||||||
0 == strcmp(array[0], CONF_BLOCK_END) ||
|
|
||||||
0 == strcmp(array[1], CONF_BLOCK_END)) {
|
|
||||||
g_error("improperly formatted config file at line %i: %s "
|
|
||||||
"in block beginning at line %i\n",
|
|
||||||
*count, string, ret->line);
|
|
||||||
}
|
|
||||||
|
|
||||||
config_add_block_param(ret, array[0], array[1], *count);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
void config_read_file(const char *file)
|
/* parse name and value */
|
||||||
|
|
||||||
|
if (!config_read_name_value(ret, line, *count, &error)) {
|
||||||
|
assert(*line != 0);
|
||||||
|
config_param_free(ret);
|
||||||
|
g_propagate_prefixed_error(error_r, error,
|
||||||
|
"line %i: ", *count);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool
|
||||||
|
config_read_file(const char *file, GError **error_r)
|
||||||
{
|
{
|
||||||
FILE *fp;
|
FILE *fp;
|
||||||
char string[MAX_STRING_SIZE + 1];
|
char string[MAX_STRING_SIZE + 1];
|
||||||
int i;
|
|
||||||
int numberOfArgs;
|
|
||||||
int argsMinusComment;
|
|
||||||
int count = 0;
|
int count = 0;
|
||||||
struct config_entry *entry;
|
struct config_entry *entry;
|
||||||
struct config_param *param;
|
struct config_param *param;
|
||||||
@@ -303,70 +347,122 @@ void config_read_file(const char *file)
|
|||||||
g_debug("loading file %s", file);
|
g_debug("loading file %s", file);
|
||||||
|
|
||||||
if (!(fp = fopen(file, "r"))) {
|
if (!(fp = fopen(file, "r"))) {
|
||||||
g_error("problems opening file %s for reading: %s\n",
|
g_set_error(error_r, config_quark(), errno,
|
||||||
file, strerror(errno));
|
"Failed to open %s: %s",
|
||||||
|
file, g_strerror(errno));
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
while (fgets(string, MAX_STRING_SIZE, fp)) {
|
while (fgets(string, MAX_STRING_SIZE, fp)) {
|
||||||
char *array[CONF_LINE_TOKEN_MAX] = { NULL };
|
char *line;
|
||||||
|
const char *name, *value;
|
||||||
|
GError *error = NULL;
|
||||||
|
|
||||||
count++;
|
count++;
|
||||||
|
|
||||||
numberOfArgs = buffer2array(string, array, CONF_LINE_TOKEN_MAX);
|
line = strchug_fast(string);
|
||||||
|
if (*line == 0 || *line == CONF_COMMENT)
|
||||||
for (i = 0; i < numberOfArgs; i++) {
|
|
||||||
if (array[i][0] == CONF_COMMENT)
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
argsMinusComment = i;
|
|
||||||
|
|
||||||
if (0 == argsMinusComment) {
|
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
|
/* the first token in each line is the name, followed
|
||||||
|
by either the value or '{' */
|
||||||
|
|
||||||
|
name = tokenizer_next_word(&line, &error);
|
||||||
|
if (name == NULL) {
|
||||||
|
assert(*line != 0);
|
||||||
|
g_propagate_prefixed_error(error_r, error,
|
||||||
|
"line %i: ", count);
|
||||||
|
fclose(fp);
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (2 != argsMinusComment) {
|
/* get the definition of that option, and check the
|
||||||
g_error("improperly formatted config file at line %i:"
|
"repeatable" flag */
|
||||||
" %s\n", count, string);
|
|
||||||
|
entry = config_entry_get(name);
|
||||||
|
if (entry == NULL) {
|
||||||
|
g_set_error(error_r, config_quark(), 0,
|
||||||
|
"unrecognized parameter in config file at "
|
||||||
|
"line %i: %s\n", count, name);
|
||||||
|
fclose(fp);
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
entry = config_entry_get(array[0]);
|
if (entry->params != NULL && !entry->repeatable) {
|
||||||
if (entry == NULL)
|
|
||||||
g_error("unrecognized parameter in config file at "
|
|
||||||
"line %i: %s\n", count, string);
|
|
||||||
|
|
||||||
if (!(entry->mask & CONF_REPEATABLE_MASK) &&
|
|
||||||
entry->params != NULL) {
|
|
||||||
param = entry->params->data;
|
param = entry->params->data;
|
||||||
g_error("config parameter \"%s\" is first defined on "
|
g_set_error(error_r, config_quark(), 0,
|
||||||
"line %i and redefined on line %i\n",
|
"config parameter \"%s\" is first defined "
|
||||||
array[0], param->line, count);
|
"on line %i and redefined on line %i\n",
|
||||||
|
name, param->line, count);
|
||||||
|
fclose(fp);
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (entry->mask & CONF_BLOCK_MASK) {
|
/* now parse the block or the value */
|
||||||
if (0 != strcmp(array[1], CONF_BLOCK_BEGIN)) {
|
|
||||||
g_error("improperly formatted config file at "
|
if (entry->block) {
|
||||||
"line %i: %s\n", count, string);
|
/* it's a block, call config_read_block() */
|
||||||
|
|
||||||
|
if (*line != '{') {
|
||||||
|
g_set_error(error_r, config_quark(), 0,
|
||||||
|
"line %i: '{' expected", count);
|
||||||
|
fclose(fp);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
line = strchug_fast(line + 1);
|
||||||
|
if (*line != 0 && *line != CONF_COMMENT) {
|
||||||
|
g_set_error(error_r, config_quark(), 0,
|
||||||
|
"line %i: Unknown tokens after '{'",
|
||||||
|
count);
|
||||||
|
fclose(fp);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
param = config_read_block(fp, &count, string, error_r);
|
||||||
|
if (param == NULL) {
|
||||||
|
fclose(fp);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
/* a string value */
|
||||||
|
|
||||||
|
value = tokenizer_next_string(&line, &error);
|
||||||
|
if (value == NULL) {
|
||||||
|
if (*line == 0)
|
||||||
|
g_set_error(error_r, config_quark(), 0,
|
||||||
|
"line %i: Value missing",
|
||||||
|
count);
|
||||||
|
else {
|
||||||
|
g_set_error(error_r, config_quark(), 0,
|
||||||
|
"line %i: %s", count,
|
||||||
|
error->message);
|
||||||
|
g_error_free(error);
|
||||||
|
}
|
||||||
|
|
||||||
|
fclose(fp);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (*line != 0 && *line != CONF_COMMENT) {
|
||||||
|
g_set_error(error_r, config_quark(), 0,
|
||||||
|
"line %i: Unknown tokens after value",
|
||||||
|
count);
|
||||||
|
fclose(fp);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
param = config_new_param(value, count);
|
||||||
}
|
}
|
||||||
param = config_read_block(fp, &count, string);
|
|
||||||
} else
|
|
||||||
param = config_new_param(array[1], count);
|
|
||||||
|
|
||||||
entry->params = g_slist_append(entry->params, param);
|
entry->params = g_slist_append(entry->params, param);
|
||||||
}
|
}
|
||||||
fclose(fp);
|
fclose(fp);
|
||||||
|
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
const struct config_param *
|
||||||
config_add_param(const char *name, struct config_param *param)
|
|
||||||
{
|
|
||||||
struct config_entry *entry = config_entry_get(name);
|
|
||||||
assert(entry != NULL);
|
|
||||||
|
|
||||||
entry->params = g_slist_append(entry->params, param);
|
|
||||||
}
|
|
||||||
|
|
||||||
struct config_param *
|
|
||||||
config_get_next_param(const char *name, const struct config_param * last)
|
config_get_next_param(const char *name, const struct config_param * last)
|
||||||
{
|
{
|
||||||
struct config_entry *entry;
|
struct config_entry *entry;
|
||||||
@@ -391,7 +487,7 @@ config_get_next_param(const char *name, const struct config_param * last)
|
|||||||
return NULL;
|
return NULL;
|
||||||
|
|
||||||
param = node->data;
|
param = node->data;
|
||||||
|
param->used = true;
|
||||||
return param;
|
return param;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -406,22 +502,41 @@ config_get_string(const char *name, const char *default_value)
|
|||||||
return param->value;
|
return param->value;
|
||||||
}
|
}
|
||||||
|
|
||||||
const char *
|
char *
|
||||||
config_get_path(const char *name)
|
config_dup_path(const char *name, GError **error_r)
|
||||||
{
|
{
|
||||||
struct config_param *param = config_get_param(name);
|
assert(error_r != NULL);
|
||||||
char *path;
|
assert(*error_r == NULL);
|
||||||
|
|
||||||
|
const struct config_param *param = config_get_param(name);
|
||||||
if (param == NULL)
|
if (param == NULL)
|
||||||
return NULL;
|
return NULL;
|
||||||
|
|
||||||
path = parsePath(param->value);
|
char *path = parsePath(param->value, error_r);
|
||||||
if (path == NULL)
|
if (G_UNLIKELY(path == NULL))
|
||||||
g_error("error parsing \"%s\" at line %i\n",
|
g_prefix_error(error_r,
|
||||||
|
"Invalid path in \"%s\" at line %i: ",
|
||||||
name, param->line);
|
name, param->line);
|
||||||
|
|
||||||
g_free(param->value);
|
return path;
|
||||||
return param->value = path;
|
}
|
||||||
|
|
||||||
|
unsigned
|
||||||
|
config_get_unsigned(const char *name, unsigned default_value)
|
||||||
|
{
|
||||||
|
const struct config_param *param = config_get_param(name);
|
||||||
|
long value;
|
||||||
|
char *endptr;
|
||||||
|
|
||||||
|
if (param == NULL)
|
||||||
|
return default_value;
|
||||||
|
|
||||||
|
value = strtol(param->value, &endptr, 0);
|
||||||
|
if (*endptr != 0 || value < 0)
|
||||||
|
MPD_ERROR("Not a valid non-negative number in line %i",
|
||||||
|
param->line);
|
||||||
|
|
||||||
|
return (unsigned)value;
|
||||||
}
|
}
|
||||||
|
|
||||||
unsigned
|
unsigned
|
||||||
@@ -436,61 +551,53 @@ config_get_positive(const char *name, unsigned default_value)
|
|||||||
|
|
||||||
value = strtol(param->value, &endptr, 0);
|
value = strtol(param->value, &endptr, 0);
|
||||||
if (*endptr != 0)
|
if (*endptr != 0)
|
||||||
g_error("Not a valid number in line %i", param->line);
|
MPD_ERROR("Not a valid number in line %i", param->line);
|
||||||
|
|
||||||
if (value <= 0)
|
if (value <= 0)
|
||||||
g_error("Not a positive number in line %i", param->line);
|
MPD_ERROR("Not a positive number in line %i", param->line);
|
||||||
|
|
||||||
return (unsigned)value;
|
return (unsigned)value;
|
||||||
}
|
}
|
||||||
|
|
||||||
struct block_param *
|
const struct block_param *
|
||||||
config_get_block_param(const struct config_param * param, const char *name)
|
config_get_block_param(const struct config_param * param, const char *name)
|
||||||
{
|
{
|
||||||
struct block_param *ret = NULL;
|
|
||||||
|
|
||||||
if (param == NULL)
|
if (param == NULL)
|
||||||
return NULL;
|
return NULL;
|
||||||
|
|
||||||
for (unsigned i = 0; i < param->num_block_params; i++) {
|
for (unsigned i = 0; i < param->num_block_params; i++) {
|
||||||
if (0 == strcmp(name, param->block_params[i].name)) {
|
if (0 == strcmp(name, param->block_params[i].name)) {
|
||||||
if (ret) {
|
struct block_param *bp = ¶m->block_params[i];
|
||||||
g_warning("\"%s\" first defined on line %i, and "
|
bp->used = true;
|
||||||
"redefined on line %i\n", name,
|
return bp;
|
||||||
ret->line, param->block_params[i].line);
|
|
||||||
}
|
|
||||||
ret = param->block_params + i;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return ret;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool config_get_bool(const char *name, bool default_value)
|
bool config_get_bool(const char *name, bool default_value)
|
||||||
{
|
{
|
||||||
const struct config_param *param = config_get_param(name);
|
const struct config_param *param = config_get_param(name);
|
||||||
int value;
|
bool success, value;
|
||||||
|
|
||||||
if (param == NULL)
|
if (param == NULL)
|
||||||
return default_value;
|
return default_value;
|
||||||
|
|
||||||
value = get_bool(param->value);
|
success = get_bool(param->value, &value);
|
||||||
if (value == CONF_BOOL_INVALID)
|
if (!success)
|
||||||
g_error("%s is not a boolean value (yes, true, 1) or "
|
MPD_ERROR("%s is not a boolean value (yes, true, 1) or "
|
||||||
"(no, false, 0) on line %i\n",
|
"(no, false, 0) on line %i\n",
|
||||||
name, param->line);
|
name, param->line);
|
||||||
|
|
||||||
if (value == CONF_BOOL_UNSET)
|
return value;
|
||||||
return default_value;
|
|
||||||
|
|
||||||
return !!value;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const char *
|
const char *
|
||||||
config_get_block_string(const struct config_param *param, const char *name,
|
config_get_block_string(const struct config_param *param, const char *name,
|
||||||
const char *default_value)
|
const char *default_value)
|
||||||
{
|
{
|
||||||
struct block_param *bp = config_get_block_param(param, name);
|
const struct block_param *bp = config_get_block_param(param, name);
|
||||||
|
|
||||||
if (bp == NULL)
|
if (bp == NULL)
|
||||||
return default_value;
|
return default_value;
|
||||||
@@ -498,11 +605,31 @@ config_get_block_string(const struct config_param *param, const char *name,
|
|||||||
return bp->value;
|
return bp->value;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
char *
|
||||||
|
config_dup_block_path(const struct config_param *param, const char *name,
|
||||||
|
GError **error_r)
|
||||||
|
{
|
||||||
|
assert(error_r != NULL);
|
||||||
|
assert(*error_r == NULL);
|
||||||
|
|
||||||
|
const struct block_param *bp = config_get_block_param(param, name);
|
||||||
|
if (bp == NULL)
|
||||||
|
return NULL;
|
||||||
|
|
||||||
|
char *path = parsePath(bp->value, error_r);
|
||||||
|
if (G_UNLIKELY(path == NULL))
|
||||||
|
g_prefix_error(error_r,
|
||||||
|
"Invalid path in \"%s\" at line %i: ",
|
||||||
|
name, bp->line);
|
||||||
|
|
||||||
|
return path;
|
||||||
|
}
|
||||||
|
|
||||||
unsigned
|
unsigned
|
||||||
config_get_block_unsigned(const struct config_param *param, const char *name,
|
config_get_block_unsigned(const struct config_param *param, const char *name,
|
||||||
unsigned default_value)
|
unsigned default_value)
|
||||||
{
|
{
|
||||||
struct block_param *bp = config_get_block_param(param, name);
|
const struct block_param *bp = config_get_block_param(param, name);
|
||||||
long value;
|
long value;
|
||||||
char *endptr;
|
char *endptr;
|
||||||
|
|
||||||
@@ -511,10 +638,10 @@ config_get_block_unsigned(const struct config_param *param, const char *name,
|
|||||||
|
|
||||||
value = strtol(bp->value, &endptr, 0);
|
value = strtol(bp->value, &endptr, 0);
|
||||||
if (*endptr != 0)
|
if (*endptr != 0)
|
||||||
g_error("Not a valid number in line %i", bp->line);
|
MPD_ERROR("Not a valid number in line %i", bp->line);
|
||||||
|
|
||||||
if (value < 0)
|
if (value < 0)
|
||||||
g_error("Not a positive number in line %i", bp->line);
|
MPD_ERROR("Not a positive number in line %i", bp->line);
|
||||||
|
|
||||||
return (unsigned)value;
|
return (unsigned)value;
|
||||||
}
|
}
|
||||||
@@ -523,20 +650,17 @@ bool
|
|||||||
config_get_block_bool(const struct config_param *param, const char *name,
|
config_get_block_bool(const struct config_param *param, const char *name,
|
||||||
bool default_value)
|
bool default_value)
|
||||||
{
|
{
|
||||||
struct block_param *bp = config_get_block_param(param, name);
|
const struct block_param *bp = config_get_block_param(param, name);
|
||||||
int value;
|
bool success, value;
|
||||||
|
|
||||||
if (bp == NULL)
|
if (bp == NULL)
|
||||||
return default_value;
|
return default_value;
|
||||||
|
|
||||||
value = get_bool(bp->value);
|
success = get_bool(bp->value, &value);
|
||||||
if (value == CONF_BOOL_INVALID)
|
if (!success)
|
||||||
g_error("%s is not a boolean value (yes, true, 1) or "
|
MPD_ERROR("%s is not a boolean value (yes, true, 1) or "
|
||||||
"(no, false, 0) on line %i\n",
|
"(no, false, 0) on line %i\n",
|
||||||
name, bp->line);
|
name, bp->line);
|
||||||
|
|
||||||
if (value == CONF_BOOL_UNSET)
|
return value;
|
||||||
return default_value;
|
|
||||||
|
|
||||||
return !!value;
|
|
||||||
}
|
}
|
||||||
|
|||||||
107
src/conf.h
107
src/conf.h
@@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright (C) 2003-2009 The Music Player Daemon Project
|
* Copyright (C) 2003-2011 The Music Player Daemon Project
|
||||||
* http://www.musicpd.org
|
* http://www.musicpd.org
|
||||||
*
|
*
|
||||||
* This program is free software; you can redistribute it and/or modify
|
* This program is free software; you can redistribute it and/or modify
|
||||||
@@ -30,10 +30,10 @@
|
|||||||
#define CONF_DB_FILE "db_file"
|
#define CONF_DB_FILE "db_file"
|
||||||
#define CONF_STICKER_FILE "sticker_file"
|
#define CONF_STICKER_FILE "sticker_file"
|
||||||
#define CONF_LOG_FILE "log_file"
|
#define CONF_LOG_FILE "log_file"
|
||||||
#define CONF_ERROR_FILE "error_file"
|
|
||||||
#define CONF_PID_FILE "pid_file"
|
#define CONF_PID_FILE "pid_file"
|
||||||
#define CONF_STATE_FILE "state_file"
|
#define CONF_STATE_FILE "state_file"
|
||||||
#define CONF_USER "user"
|
#define CONF_USER "user"
|
||||||
|
#define CONF_GROUP "group"
|
||||||
#define CONF_BIND_TO_ADDRESS "bind_to_address"
|
#define CONF_BIND_TO_ADDRESS "bind_to_address"
|
||||||
#define CONF_PORT "port"
|
#define CONF_PORT "port"
|
||||||
#define CONF_LOG_LEVEL "log_level"
|
#define CONF_LOG_LEVEL "log_level"
|
||||||
@@ -42,12 +42,13 @@
|
|||||||
#define CONF_PASSWORD "password"
|
#define CONF_PASSWORD "password"
|
||||||
#define CONF_DEFAULT_PERMS "default_permissions"
|
#define CONF_DEFAULT_PERMS "default_permissions"
|
||||||
#define CONF_AUDIO_OUTPUT "audio_output"
|
#define CONF_AUDIO_OUTPUT "audio_output"
|
||||||
|
#define CONF_AUDIO_FILTER "filter"
|
||||||
#define CONF_AUDIO_OUTPUT_FORMAT "audio_output_format"
|
#define CONF_AUDIO_OUTPUT_FORMAT "audio_output_format"
|
||||||
#define CONF_MIXER_TYPE "mixer_type"
|
#define CONF_MIXER_TYPE "mixer_type"
|
||||||
#define CONF_MIXER_DEVICE "mixer_device"
|
|
||||||
#define CONF_MIXER_CONTROL "mixer_control"
|
|
||||||
#define CONF_REPLAYGAIN "replaygain"
|
#define CONF_REPLAYGAIN "replaygain"
|
||||||
#define CONF_REPLAYGAIN_PREAMP "replaygain_preamp"
|
#define CONF_REPLAYGAIN_PREAMP "replaygain_preamp"
|
||||||
|
#define CONF_REPLAYGAIN_MISSING_PREAMP "replaygain_missing_preamp"
|
||||||
|
#define CONF_REPLAYGAIN_LIMIT "replaygain_limit"
|
||||||
#define CONF_VOLUME_NORMALIZATION "volume_normalization"
|
#define CONF_VOLUME_NORMALIZATION "volume_normalization"
|
||||||
#define CONF_SAMPLERATE_CONVERTER "samplerate_converter"
|
#define CONF_SAMPLERATE_CONVERTER "samplerate_converter"
|
||||||
#define CONF_AUDIO_BUFFER_SIZE "audio_buffer_size"
|
#define CONF_AUDIO_BUFFER_SIZE "audio_buffer_size"
|
||||||
@@ -68,17 +69,28 @@
|
|||||||
#define CONF_DECODER "decoder"
|
#define CONF_DECODER "decoder"
|
||||||
#define CONF_INPUT "input"
|
#define CONF_INPUT "input"
|
||||||
#define CONF_GAPLESS_MP3_PLAYBACK "gapless_mp3_playback"
|
#define CONF_GAPLESS_MP3_PLAYBACK "gapless_mp3_playback"
|
||||||
|
#define CONF_PLAYLIST_PLUGIN "playlist_plugin"
|
||||||
#define CONF_BOOL_UNSET -1
|
#define CONF_AUTO_UPDATE "auto_update"
|
||||||
#define CONF_BOOL_INVALID -2
|
#define CONF_AUTO_UPDATE_DEPTH "auto_update_depth"
|
||||||
|
#define CONF_DESPOTIFY_USER "despotify_user"
|
||||||
|
#define CONF_DESPOTIFY_PASSWORD "despotify_password"
|
||||||
|
#define CONF_DESPOTIFY_HIGH_BITRATE "despotify_high_bitrate"
|
||||||
|
|
||||||
#define DEFAULT_PLAYLIST_MAX_LENGTH (1024*16)
|
#define DEFAULT_PLAYLIST_MAX_LENGTH (1024*16)
|
||||||
#define DEFAULT_PLAYLIST_SAVE_ABSOLUTE_PATHS false
|
#define DEFAULT_PLAYLIST_SAVE_ABSOLUTE_PATHS false
|
||||||
|
|
||||||
|
#define MAX_FILTER_CHAIN_LENGTH 255
|
||||||
|
|
||||||
struct block_param {
|
struct block_param {
|
||||||
char *name;
|
char *name;
|
||||||
char *value;
|
char *value;
|
||||||
int line;
|
int line;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This flag is false when nobody has queried the value of
|
||||||
|
* this option yet.
|
||||||
|
*/
|
||||||
|
bool used;
|
||||||
};
|
};
|
||||||
|
|
||||||
struct config_param {
|
struct config_param {
|
||||||
@@ -87,54 +99,94 @@ struct config_param {
|
|||||||
|
|
||||||
struct block_param *block_params;
|
struct block_param *block_params;
|
||||||
unsigned num_block_params;
|
unsigned num_block_params;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This flag is false when nobody has queried the value of
|
||||||
|
* this option yet.
|
||||||
|
*/
|
||||||
|
bool used;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A GQuark for GError instances, resulting from malformed
|
||||||
|
* configuration.
|
||||||
|
*/
|
||||||
|
G_GNUC_CONST
|
||||||
|
static inline GQuark
|
||||||
|
config_quark(void)
|
||||||
|
{
|
||||||
|
return g_quark_from_static_string("config");
|
||||||
|
}
|
||||||
|
|
||||||
void config_global_init(void);
|
void config_global_init(void);
|
||||||
void config_global_finish(void);
|
void config_global_finish(void);
|
||||||
|
|
||||||
void config_read_file(const char *file);
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Adds a new configuration parameter. The name must be registered
|
* Call this function after all configuration has been evaluated. It
|
||||||
* with registerConfigParam().
|
* checks for unused parameters, and logs warnings.
|
||||||
*/
|
*/
|
||||||
void
|
void config_global_check(void);
|
||||||
config_add_param(const char *name, struct config_param *param);
|
|
||||||
|
bool
|
||||||
|
config_read_file(const char *file, GError **error_r);
|
||||||
|
|
||||||
/* don't free the returned value
|
/* don't free the returned value
|
||||||
set _last_ to NULL to get first entry */
|
set _last_ to NULL to get first entry */
|
||||||
struct config_param *
|
G_GNUC_PURE
|
||||||
|
const struct config_param *
|
||||||
config_get_next_param(const char *name, const struct config_param *last);
|
config_get_next_param(const char *name, const struct config_param *last);
|
||||||
|
|
||||||
static inline struct config_param *
|
G_GNUC_PURE
|
||||||
|
static inline const struct config_param *
|
||||||
config_get_param(const char *name)
|
config_get_param(const char *name)
|
||||||
{
|
{
|
||||||
return config_get_next_param(name, NULL);
|
return config_get_next_param(name, NULL);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Note on G_GNUC_PURE: Some of the functions declared pure are not
|
||||||
|
really pure in strict sense. They have side effect such that they
|
||||||
|
validate parameter's value and signal an error if it's invalid.
|
||||||
|
However, if the argument was already validated or we don't care
|
||||||
|
about the argument at all, this may be ignored so in the end, we
|
||||||
|
should be fine with calling those functions pure. */
|
||||||
|
|
||||||
|
G_GNUC_PURE
|
||||||
const char *
|
const char *
|
||||||
config_get_string(const char *name, const char *default_value);
|
config_get_string(const char *name, const char *default_value);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns an optional configuration variable which contains an
|
* Returns an optional configuration variable which contains an
|
||||||
* absolute path. If there is a tilde prefix, it is expanded. Aborts
|
* absolute path. If there is a tilde prefix, it is expanded.
|
||||||
* MPD if the path is not a valid absolute path.
|
* Returns NULL if the value is not present. If the path could not be
|
||||||
|
* parsed, returns NULL and sets the error.
|
||||||
|
*
|
||||||
|
* The return value must be freed with g_free().
|
||||||
*/
|
*/
|
||||||
const char *
|
G_GNUC_MALLOC
|
||||||
config_get_path(const char *name);
|
char *
|
||||||
|
config_dup_path(const char *name, GError **error_r);
|
||||||
|
|
||||||
|
G_GNUC_PURE
|
||||||
|
unsigned
|
||||||
|
config_get_unsigned(const char *name, unsigned default_value);
|
||||||
|
|
||||||
|
G_GNUC_PURE
|
||||||
unsigned
|
unsigned
|
||||||
config_get_positive(const char *name, unsigned default_value);
|
config_get_positive(const char *name, unsigned default_value);
|
||||||
|
|
||||||
struct block_param *
|
G_GNUC_PURE
|
||||||
|
const struct block_param *
|
||||||
config_get_block_param(const struct config_param *param, const char *name);
|
config_get_block_param(const struct config_param *param, const char *name);
|
||||||
|
|
||||||
|
G_GNUC_PURE
|
||||||
bool config_get_bool(const char *name, bool default_value);
|
bool config_get_bool(const char *name, bool default_value);
|
||||||
|
|
||||||
|
G_GNUC_PURE
|
||||||
const char *
|
const char *
|
||||||
config_get_block_string(const struct config_param *param, const char *name,
|
config_get_block_string(const struct config_param *param, const char *name,
|
||||||
const char *default_value);
|
const char *default_value);
|
||||||
|
|
||||||
|
G_GNUC_MALLOC
|
||||||
static inline char *
|
static inline char *
|
||||||
config_dup_block_string(const struct config_param *param, const char *name,
|
config_dup_block_string(const struct config_param *param, const char *name,
|
||||||
const char *default_value)
|
const char *default_value)
|
||||||
@@ -142,17 +194,32 @@ config_dup_block_string(const struct config_param *param, const char *name,
|
|||||||
return g_strdup(config_get_block_string(param, name, default_value));
|
return g_strdup(config_get_block_string(param, name, default_value));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Same as config_dup_path(), but looks up the setting in the
|
||||||
|
* specified block.
|
||||||
|
*/
|
||||||
|
G_GNUC_MALLOC
|
||||||
|
char *
|
||||||
|
config_dup_block_path(const struct config_param *param, const char *name,
|
||||||
|
GError **error_r);
|
||||||
|
|
||||||
|
G_GNUC_PURE
|
||||||
unsigned
|
unsigned
|
||||||
config_get_block_unsigned(const struct config_param *param, const char *name,
|
config_get_block_unsigned(const struct config_param *param, const char *name,
|
||||||
unsigned default_value);
|
unsigned default_value);
|
||||||
|
|
||||||
|
G_GNUC_PURE
|
||||||
bool
|
bool
|
||||||
config_get_block_bool(const struct config_param *param, const char *name,
|
config_get_block_bool(const struct config_param *param, const char *name,
|
||||||
bool default_value);
|
bool default_value);
|
||||||
|
|
||||||
|
G_GNUC_MALLOC
|
||||||
struct config_param *
|
struct config_param *
|
||||||
config_new_param(const char *value, int line);
|
config_new_param(const char *value, int line);
|
||||||
|
|
||||||
|
void
|
||||||
|
config_param_free(struct config_param *param);
|
||||||
|
|
||||||
void
|
void
|
||||||
config_add_block_param(struct config_param * param, const char *name,
|
config_add_block_param(struct config_param * param, const char *name,
|
||||||
const char *value, int line);
|
const char *value, int line);
|
||||||
|
|||||||
149
src/crossfade.c
149
src/crossfade.c
@@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright (C) 2003-2009 The Music Player Daemon Project
|
* Copyright (C) 2003-2011 The Music Player Daemon Project
|
||||||
* http://www.musicpd.org
|
* http://www.musicpd.org
|
||||||
*
|
*
|
||||||
* This program is free software; you can redistribute it and/or modify
|
* This program is free software; you can redistribute it and/or modify
|
||||||
@@ -17,81 +17,120 @@
|
|||||||
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
#include "config.h"
|
||||||
#include "crossfade.h"
|
#include "crossfade.h"
|
||||||
#include "pcm_mix.h"
|
|
||||||
#include "chunk.h"
|
#include "chunk.h"
|
||||||
#include "audio_format.h"
|
#include "audio_format.h"
|
||||||
#include "tag.h"
|
#include "tag.h"
|
||||||
|
|
||||||
#include <assert.h>
|
#include <assert.h>
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <glib.h>
|
||||||
|
|
||||||
|
#undef G_LOG_DOMAIN
|
||||||
|
#define G_LOG_DOMAIN "crossfade"
|
||||||
|
|
||||||
|
#ifdef G_OS_WIN32
|
||||||
|
static char *
|
||||||
|
strtok_r(char *str, const char *delim, G_GNUC_UNUSED char **saveptr)
|
||||||
|
{
|
||||||
|
return strtok(str, delim);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
static float mixramp_interpolate(char *ramp_list, float required_db)
|
||||||
|
{
|
||||||
|
float db, secs, last_db = nan(""), last_secs = 0;
|
||||||
|
char *ramp_str, *save_str = NULL;
|
||||||
|
|
||||||
|
/* ramp_list is a string of pairs of dBs and seconds that describe the
|
||||||
|
* volume profile. Delimiters are semi-colons between pairs and spaces
|
||||||
|
* between the dB and seconds of a pair.
|
||||||
|
* The dB values must be monotonically increasing for this to work. */
|
||||||
|
|
||||||
|
while (1) {
|
||||||
|
/* Parse the dB tokens out of the input string. */
|
||||||
|
ramp_str = strtok_r(ramp_list, " ", &save_str);
|
||||||
|
|
||||||
|
/* Tell strtok to continue next time round. */
|
||||||
|
ramp_list = NULL;
|
||||||
|
|
||||||
|
/* Parse the dB value. */
|
||||||
|
if (NULL == ramp_str) {
|
||||||
|
return nan("");
|
||||||
|
}
|
||||||
|
db = (float)atof(ramp_str);
|
||||||
|
|
||||||
|
/* Parse the time. */
|
||||||
|
ramp_str = strtok_r(NULL, ";", &save_str);
|
||||||
|
if (NULL == ramp_str) {
|
||||||
|
return nan("");
|
||||||
|
}
|
||||||
|
secs = (float)atof(ramp_str);
|
||||||
|
|
||||||
|
/* Check for exact match. */
|
||||||
|
if (db == required_db) {
|
||||||
|
return secs;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Save if too quiet. */
|
||||||
|
if (db < required_db) {
|
||||||
|
last_db = db;
|
||||||
|
last_secs = secs;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* If required db < any stored value, use the least. */
|
||||||
|
if (isnan(last_db)) {
|
||||||
|
return secs;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Finally, interpolate linearly. */
|
||||||
|
secs = last_secs + (required_db - last_db) * (secs - last_secs) / (db - last_db);
|
||||||
|
return secs;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
unsigned cross_fade_calc(float duration, float total_time,
|
unsigned cross_fade_calc(float duration, float total_time,
|
||||||
|
float mixramp_db, float mixramp_delay,
|
||||||
|
float replay_gain_db, float replay_gain_prev_db,
|
||||||
|
char *mixramp_start, char *mixramp_prev_end,
|
||||||
const struct audio_format *af,
|
const struct audio_format *af,
|
||||||
const struct audio_format *old_format,
|
const struct audio_format *old_format,
|
||||||
unsigned max_chunks)
|
unsigned max_chunks)
|
||||||
{
|
{
|
||||||
unsigned int chunks;
|
unsigned int chunks = 0;
|
||||||
|
float chunks_f;
|
||||||
|
float mixramp_overlap;
|
||||||
|
|
||||||
if (duration <= 0 || duration >= total_time ||
|
if (duration < 0 || duration >= total_time ||
|
||||||
/* we can't crossfade when the audio formats are different */
|
/* we can't crossfade when the audio formats are different */
|
||||||
!audio_format_equals(af, old_format))
|
!audio_format_equals(af, old_format))
|
||||||
return 0;
|
return 0;
|
||||||
|
|
||||||
assert(duration > 0);
|
assert(duration >= 0);
|
||||||
assert(af->bits > 0);
|
assert(audio_format_valid(af));
|
||||||
assert(af->channels > 0);
|
|
||||||
assert(af->sample_rate > 0);
|
|
||||||
|
|
||||||
chunks = audio_format_time_to_size(af) / CHUNK_SIZE;
|
chunks_f = (float)audio_format_time_to_size(af) / (float)CHUNK_SIZE;
|
||||||
chunks = (chunks * duration + 0.5);
|
|
||||||
|
|
||||||
if (chunks > max_chunks)
|
if (isnan(mixramp_delay) || !(mixramp_start) || !(mixramp_prev_end)) {
|
||||||
|
chunks = (chunks_f * duration + 0.5);
|
||||||
|
} else {
|
||||||
|
/* Calculate mixramp overlap. */
|
||||||
|
mixramp_overlap = mixramp_interpolate(mixramp_start, mixramp_db - replay_gain_db)
|
||||||
|
+ mixramp_interpolate(mixramp_prev_end, mixramp_db - replay_gain_prev_db);
|
||||||
|
if (!isnan(mixramp_overlap) && (mixramp_delay <= mixramp_overlap)) {
|
||||||
|
chunks = (chunks_f * (mixramp_overlap - mixramp_delay));
|
||||||
|
g_debug("will overlap %d chunks, %fs", chunks,
|
||||||
|
mixramp_overlap - mixramp_delay);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (chunks > max_chunks) {
|
||||||
chunks = max_chunks;
|
chunks = max_chunks;
|
||||||
|
g_warning("audio_buffer_size too small for computed MixRamp overlap");
|
||||||
|
}
|
||||||
|
|
||||||
return chunks;
|
return chunks;
|
||||||
}
|
}
|
||||||
|
|
||||||
void cross_fade_apply(struct music_chunk *a, const struct music_chunk *b,
|
|
||||||
const struct audio_format *format,
|
|
||||||
unsigned int current_chunk, unsigned int num_chunks)
|
|
||||||
{
|
|
||||||
size_t size;
|
|
||||||
|
|
||||||
assert(a != NULL);
|
|
||||||
assert(b != NULL);
|
|
||||||
assert(a->length == 0 || b->length == 0 ||
|
|
||||||
audio_format_equals(&a->audio_format, &b->audio_format));
|
|
||||||
assert(current_chunk <= num_chunks);
|
|
||||||
|
|
||||||
if (a->tag == NULL && b->tag != NULL)
|
|
||||||
/* merge the tag into the destination chunk */
|
|
||||||
a->tag = tag_dup(b->tag);
|
|
||||||
|
|
||||||
size = b->length > a->length
|
|
||||||
? a->length
|
|
||||||
: b->length;
|
|
||||||
|
|
||||||
pcm_mix(a->data,
|
|
||||||
b->data,
|
|
||||||
size,
|
|
||||||
format,
|
|
||||||
((float)current_chunk) / num_chunks);
|
|
||||||
|
|
||||||
if (b->length > a->length) {
|
|
||||||
/* the second buffer is larger than the first one:
|
|
||||||
there is unmixed rest at the end. Copy it over.
|
|
||||||
The output buffer API guarantees that there is
|
|
||||||
enough room in a->data. */
|
|
||||||
|
|
||||||
#ifndef NDEBUG
|
|
||||||
if (a->length == 0)
|
|
||||||
a->audio_format = b->audio_format;
|
|
||||||
#endif
|
|
||||||
|
|
||||||
memcpy(a->data + a->length,
|
|
||||||
b->data + a->length,
|
|
||||||
b->length - a->length);
|
|
||||||
a->length = b->length;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright (C) 2003-2009 The Music Player Daemon Project
|
* Copyright (C) 2003-2011 The Music Player Daemon Project
|
||||||
* http://www.musicpd.org
|
* http://www.musicpd.org
|
||||||
*
|
*
|
||||||
* This program is free software; you can redistribute it and/or modify
|
* This program is free software; you can redistribute it and/or modify
|
||||||
@@ -28,6 +28,12 @@ struct music_chunk;
|
|||||||
*
|
*
|
||||||
* @param duration the requested crossfade duration
|
* @param duration the requested crossfade duration
|
||||||
* @param total_time total_time the duration of the new song
|
* @param total_time total_time the duration of the new song
|
||||||
|
* @param mixramp_db the current mixramp_db setting
|
||||||
|
* @param mixramp_delay the current mixramp_delay setting
|
||||||
|
* @param replay_gain_db the ReplayGain adjustment used for this song
|
||||||
|
* @param replay_gain_prev_db the ReplayGain adjustment used on the last song
|
||||||
|
* @param mixramp_start the next songs mixramp_start tag
|
||||||
|
* @param mixramp_prev_end the last songs mixramp_end setting
|
||||||
* @param af the audio format of the new song
|
* @param af the audio format of the new song
|
||||||
* @param old_format the audio format of the current song
|
* @param old_format the audio format of the current song
|
||||||
* @param max_chunks the maximum number of chunks
|
* @param max_chunks the maximum number of chunks
|
||||||
@@ -35,22 +41,11 @@ struct music_chunk;
|
|||||||
* should be disabled for this song change
|
* should be disabled for this song change
|
||||||
*/
|
*/
|
||||||
unsigned cross_fade_calc(float duration, float total_time,
|
unsigned cross_fade_calc(float duration, float total_time,
|
||||||
|
float mixramp_db, float mixramp_delay,
|
||||||
|
float replay_gain_db, float replay_gain_prev_db,
|
||||||
|
char *mixramp_start, char *mixramp_prev_end,
|
||||||
const struct audio_format *af,
|
const struct audio_format *af,
|
||||||
const struct audio_format *old_format,
|
const struct audio_format *old_format,
|
||||||
unsigned max_chunks);
|
unsigned max_chunks);
|
||||||
|
|
||||||
/**
|
|
||||||
* Applies cross fading to two chunks, i.e. mixes these chunks.
|
|
||||||
* Internally, this calls pcm_mix().
|
|
||||||
*
|
|
||||||
* @param a the chunk in the current song (and the destination chunk)
|
|
||||||
* @param b the according chunk in the new song
|
|
||||||
* @param format the audio format of both chunks (must be the same)
|
|
||||||
* @param current_chunk the relative index of the current chunk
|
|
||||||
* @param num_chunks the number of chunks used for cross fading
|
|
||||||
*/
|
|
||||||
void cross_fade_apply(struct music_chunk *a, const struct music_chunk *b,
|
|
||||||
const struct audio_format *format,
|
|
||||||
unsigned int current_chunk, unsigned int num_chunks);
|
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
401
src/cue/cue_parser.c
Normal file
401
src/cue/cue_parser.c
Normal file
@@ -0,0 +1,401 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2003-2011 The Music Player Daemon Project
|
||||||
|
* http://www.musicpd.org
|
||||||
|
*
|
||||||
|
* This program is free software; you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation; either version 2 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License along
|
||||||
|
* with this program; if not, write to the Free Software Foundation, Inc.,
|
||||||
|
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "config.h"
|
||||||
|
#include "cue_parser.h"
|
||||||
|
#include "string_util.h"
|
||||||
|
#include "song.h"
|
||||||
|
#include "tag.h"
|
||||||
|
|
||||||
|
#include <assert.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
|
||||||
|
struct cue_parser {
|
||||||
|
enum {
|
||||||
|
/**
|
||||||
|
* Parsing the CUE header.
|
||||||
|
*/
|
||||||
|
HEADER,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parsing a "FILE ... WAVE".
|
||||||
|
*/
|
||||||
|
WAVE,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Ignore everything until the next "FILE".
|
||||||
|
*/
|
||||||
|
IGNORE_FILE,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parsing a "TRACK ... AUDIO".
|
||||||
|
*/
|
||||||
|
TRACK,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Ignore everything until the next "TRACK".
|
||||||
|
*/
|
||||||
|
IGNORE_TRACK,
|
||||||
|
} state;
|
||||||
|
|
||||||
|
struct tag *tag;
|
||||||
|
|
||||||
|
char *filename;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The song currently being edited.
|
||||||
|
*/
|
||||||
|
struct song *current;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The previous song. It is remembered because its end_time
|
||||||
|
* will be set to the current song's start time.
|
||||||
|
*/
|
||||||
|
struct song *previous;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A song that is completely finished and can be returned to
|
||||||
|
* the caller via cue_parser_get().
|
||||||
|
*/
|
||||||
|
struct song *finished;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set to true after previous.end_time has been updated to the
|
||||||
|
* start time of the current song.
|
||||||
|
*/
|
||||||
|
bool last_updated;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tracks whether cue_parser_finish() has been called. If
|
||||||
|
* true, then all remaining (partial) results will be
|
||||||
|
* delivered by cue_parser_get().
|
||||||
|
*/
|
||||||
|
bool end;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct cue_parser *
|
||||||
|
cue_parser_new(void)
|
||||||
|
{
|
||||||
|
struct cue_parser *parser = g_new(struct cue_parser, 1);
|
||||||
|
parser->state = HEADER;
|
||||||
|
parser->tag = tag_new();
|
||||||
|
parser->filename = NULL;
|
||||||
|
parser->current = NULL;
|
||||||
|
parser->previous = NULL;
|
||||||
|
parser->finished = NULL;
|
||||||
|
parser->end = false;
|
||||||
|
return parser;
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
cue_parser_free(struct cue_parser *parser)
|
||||||
|
{
|
||||||
|
tag_free(parser->tag);
|
||||||
|
g_free(parser->filename);
|
||||||
|
|
||||||
|
if (parser->current != NULL)
|
||||||
|
song_free(parser->current);
|
||||||
|
|
||||||
|
if (parser->previous != NULL)
|
||||||
|
song_free(parser->previous);
|
||||||
|
|
||||||
|
if (parser->finished != NULL)
|
||||||
|
song_free(parser->finished);
|
||||||
|
|
||||||
|
g_free(parser);
|
||||||
|
}
|
||||||
|
|
||||||
|
static const char *
|
||||||
|
cue_next_word(char *p, char **pp)
|
||||||
|
{
|
||||||
|
assert(p >= *pp);
|
||||||
|
assert(!g_ascii_isspace(*p));
|
||||||
|
|
||||||
|
const char *word = p;
|
||||||
|
while (*p != 0 && !g_ascii_isspace(*p))
|
||||||
|
++p;
|
||||||
|
|
||||||
|
*p = 0;
|
||||||
|
*pp = p + 1;
|
||||||
|
return word;
|
||||||
|
}
|
||||||
|
|
||||||
|
static const char *
|
||||||
|
cue_next_quoted(char *p, char **pp)
|
||||||
|
{
|
||||||
|
assert(p >= *pp);
|
||||||
|
assert(p[-1] == '"');
|
||||||
|
|
||||||
|
char *end = strchr(p, '"');
|
||||||
|
if (end == NULL) {
|
||||||
|
/* syntax error - ignore it silently */
|
||||||
|
*pp = p + strlen(p);
|
||||||
|
return p;
|
||||||
|
}
|
||||||
|
|
||||||
|
*end = 0;
|
||||||
|
*pp = end + 1;
|
||||||
|
|
||||||
|
return p;
|
||||||
|
}
|
||||||
|
|
||||||
|
static const char *
|
||||||
|
cue_next_token(char **pp)
|
||||||
|
{
|
||||||
|
char *p = strchug_fast(*pp);
|
||||||
|
if (*p == 0)
|
||||||
|
return NULL;
|
||||||
|
|
||||||
|
return cue_next_word(p, pp);
|
||||||
|
}
|
||||||
|
|
||||||
|
static const char *
|
||||||
|
cue_next_value(char **pp)
|
||||||
|
{
|
||||||
|
char *p = strchug_fast(*pp);
|
||||||
|
if (*p == 0)
|
||||||
|
return NULL;
|
||||||
|
|
||||||
|
if (*p == '"')
|
||||||
|
return cue_next_quoted(p + 1, pp);
|
||||||
|
else
|
||||||
|
return cue_next_word(p, pp);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
cue_add_tag(struct tag *tag, enum tag_type type, char *p)
|
||||||
|
{
|
||||||
|
const char *value = cue_next_value(&p);
|
||||||
|
if (value != NULL)
|
||||||
|
tag_add_item(tag, type, value);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
cue_parse_rem(char *p, struct tag *tag)
|
||||||
|
{
|
||||||
|
const char *type = cue_next_token(&p);
|
||||||
|
if (type == NULL)
|
||||||
|
return;
|
||||||
|
|
||||||
|
enum tag_type type2 = tag_name_parse_i(type);
|
||||||
|
if (type2 != TAG_NUM_OF_ITEM_TYPES)
|
||||||
|
cue_add_tag(tag, type2, p);
|
||||||
|
}
|
||||||
|
|
||||||
|
static struct tag *
|
||||||
|
cue_current_tag(struct cue_parser *parser)
|
||||||
|
{
|
||||||
|
if (parser->state == HEADER)
|
||||||
|
return parser->tag;
|
||||||
|
else if (parser->state == TRACK)
|
||||||
|
return parser->current->tag;
|
||||||
|
else
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int
|
||||||
|
cue_parse_position(const char *p)
|
||||||
|
{
|
||||||
|
char *endptr;
|
||||||
|
unsigned long minutes = strtoul(p, &endptr, 10);
|
||||||
|
if (endptr == p || *endptr != ':')
|
||||||
|
return -1;
|
||||||
|
|
||||||
|
p = endptr + 1;
|
||||||
|
unsigned long seconds = strtoul(p, &endptr, 10);
|
||||||
|
if (endptr == p || *endptr != ':')
|
||||||
|
return -1;
|
||||||
|
|
||||||
|
p = endptr + 1;
|
||||||
|
unsigned long frames = strtoul(p, &endptr, 10);
|
||||||
|
if (endptr == p || *endptr != 0)
|
||||||
|
return -1;
|
||||||
|
|
||||||
|
return minutes * 60000 + seconds * 1000 + frames * 1000 / 75;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Commit the current song. It will be moved to "previous", so the
|
||||||
|
* next song may soon edit its end time (using the next song's start
|
||||||
|
* time).
|
||||||
|
*/
|
||||||
|
static void
|
||||||
|
cue_parser_commit(struct cue_parser *parser)
|
||||||
|
{
|
||||||
|
/* the caller of this library must call cue_parser_get() often
|
||||||
|
enough */
|
||||||
|
assert(parser->finished == NULL);
|
||||||
|
assert(!parser->end);
|
||||||
|
|
||||||
|
if (parser->current == NULL)
|
||||||
|
return;
|
||||||
|
|
||||||
|
parser->finished = parser->previous;
|
||||||
|
parser->previous = parser->current;
|
||||||
|
parser->current = NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
cue_parser_feed2(struct cue_parser *parser, char *p)
|
||||||
|
{
|
||||||
|
assert(parser != NULL);
|
||||||
|
assert(!parser->end);
|
||||||
|
assert(p != NULL);
|
||||||
|
|
||||||
|
const char *command = cue_next_token(&p);
|
||||||
|
if (command == NULL)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (strcmp(command, "REM") == 0) {
|
||||||
|
struct tag *tag = cue_current_tag(parser);
|
||||||
|
if (tag != NULL)
|
||||||
|
cue_parse_rem(p, tag);
|
||||||
|
} else if (strcmp(command, "PERFORMER") == 0) {
|
||||||
|
/* MPD knows a "performer" tag, but it is not a good
|
||||||
|
match for this CUE tag; from the Hydrogenaudio
|
||||||
|
Knowledgebase: "At top-level this will specify the
|
||||||
|
CD artist, while at track-level it specifies the
|
||||||
|
track artist." */
|
||||||
|
|
||||||
|
enum tag_type type = parser->state == TRACK
|
||||||
|
? TAG_ARTIST
|
||||||
|
: TAG_ALBUM_ARTIST;
|
||||||
|
|
||||||
|
struct tag *tag = cue_current_tag(parser);
|
||||||
|
if (tag != NULL)
|
||||||
|
cue_add_tag(tag, type, p);
|
||||||
|
} else if (strcmp(command, "TITLE") == 0) {
|
||||||
|
if (parser->state == HEADER)
|
||||||
|
cue_add_tag(parser->tag, TAG_ALBUM, p);
|
||||||
|
else if (parser->state == TRACK)
|
||||||
|
cue_add_tag(parser->current->tag, TAG_TITLE, p);
|
||||||
|
} else if (strcmp(command, "FILE") == 0) {
|
||||||
|
cue_parser_commit(parser);
|
||||||
|
|
||||||
|
const char *filename = cue_next_value(&p);
|
||||||
|
if (filename == NULL)
|
||||||
|
return;
|
||||||
|
|
||||||
|
const char *type = cue_next_token(&p);
|
||||||
|
if (type == NULL)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (strcmp(type, "WAVE") != 0 &&
|
||||||
|
strcmp(type, "MP3") != 0 &&
|
||||||
|
strcmp(type, "AIFF") != 0) {
|
||||||
|
parser->state = IGNORE_FILE;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
parser->state = WAVE;
|
||||||
|
g_free(parser->filename);
|
||||||
|
parser->filename = g_strdup(filename);
|
||||||
|
} else if (parser->state == IGNORE_FILE) {
|
||||||
|
return;
|
||||||
|
} else if (strcmp(command, "TRACK") == 0) {
|
||||||
|
cue_parser_commit(parser);
|
||||||
|
|
||||||
|
const char *nr = cue_next_token(&p);
|
||||||
|
if (nr == NULL)
|
||||||
|
return;
|
||||||
|
|
||||||
|
const char *type = cue_next_token(&p);
|
||||||
|
if (type == NULL)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (strcmp(type, "AUDIO") != 0) {
|
||||||
|
parser->state = IGNORE_TRACK;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
parser->state = TRACK;
|
||||||
|
parser->current = song_remote_new(parser->filename);
|
||||||
|
assert(parser->current->tag == NULL);
|
||||||
|
parser->current->tag = tag_dup(parser->tag);
|
||||||
|
tag_add_item(parser->current->tag, TAG_TRACK, nr);
|
||||||
|
parser->last_updated = false;
|
||||||
|
} else if (parser->state == IGNORE_TRACK) {
|
||||||
|
return;
|
||||||
|
} else if (parser->state == TRACK && strcmp(command, "INDEX") == 0) {
|
||||||
|
const char *nr = cue_next_token(&p);
|
||||||
|
if (nr == NULL)
|
||||||
|
return;
|
||||||
|
|
||||||
|
const char *position = cue_next_token(&p);
|
||||||
|
if (position == NULL)
|
||||||
|
return;
|
||||||
|
|
||||||
|
int position_ms = cue_parse_position(position);
|
||||||
|
if (position_ms < 0)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (!parser->last_updated && parser->previous != NULL &&
|
||||||
|
parser->previous->start_ms < (unsigned)position_ms) {
|
||||||
|
parser->last_updated = true;
|
||||||
|
parser->previous->end_ms = position_ms;
|
||||||
|
parser->previous->tag->time =
|
||||||
|
(parser->previous->end_ms - parser->previous->start_ms + 500) / 1000;
|
||||||
|
}
|
||||||
|
|
||||||
|
parser->current->start_ms = position_ms;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
cue_parser_feed(struct cue_parser *parser, const char *line)
|
||||||
|
{
|
||||||
|
assert(parser != NULL);
|
||||||
|
assert(!parser->end);
|
||||||
|
assert(line != NULL);
|
||||||
|
|
||||||
|
char *allocated = g_strdup(line);
|
||||||
|
cue_parser_feed2(parser, allocated);
|
||||||
|
g_free(allocated);
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
cue_parser_finish(struct cue_parser *parser)
|
||||||
|
{
|
||||||
|
if (parser->end)
|
||||||
|
/* has already been called, ignore */
|
||||||
|
return;
|
||||||
|
|
||||||
|
cue_parser_commit(parser);
|
||||||
|
parser->end = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
struct song *
|
||||||
|
cue_parser_get(struct cue_parser *parser)
|
||||||
|
{
|
||||||
|
assert(parser != NULL);
|
||||||
|
|
||||||
|
if (parser->finished == NULL && parser->end) {
|
||||||
|
/* cue_parser_finish() has been called already:
|
||||||
|
deliver all remaining (partial) results */
|
||||||
|
assert(parser->current == NULL);
|
||||||
|
|
||||||
|
parser->finished = parser->previous;
|
||||||
|
parser->previous = NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
struct song *song = parser->finished;
|
||||||
|
parser->finished = NULL;
|
||||||
|
return song;
|
||||||
|
}
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user