Compare commits
745 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0468f850c2 | ||
|
|
b9e7d4e30c | ||
|
|
0e7734b555 | ||
|
|
e81dcce5b3 | ||
|
|
38bd1f7e1c | ||
|
|
36ad9704a2 | ||
|
|
a6c05df362 | ||
|
|
f6b70fda71 | ||
|
|
8cb4e75f70 | ||
|
|
da1815ac2b | ||
|
|
f7357f2d10 | ||
|
|
d9c92c5ddd | ||
|
|
c82bdb6405 | ||
|
|
eec8ee5830 | ||
|
|
e67eb3c0d8 | ||
|
|
9c101759f6 | ||
|
|
a55ee92590 | ||
|
|
5ccfc97dab | ||
|
|
d6dd88ec3d | ||
|
|
8557d83599 | ||
|
|
94cf1f8698 | ||
|
|
9ada35c3a3 | ||
|
|
79b0c906fa | ||
|
|
a0688f1dba | ||
|
|
f91e8fecee | ||
|
|
1b363babe2 | ||
|
|
73843cb54d | ||
|
|
a2ccfc2116 | ||
|
|
fe104e2bad | ||
|
|
35e3e9f4ff | ||
|
|
b2a1cd9716 | ||
|
|
14f58eae4b | ||
|
|
5cec000618 | ||
|
|
4805a5287d | ||
|
|
4789dc5b30 | ||
|
|
5a35099043 | ||
|
|
4cd0128919 | ||
|
|
61e9b80f67 | ||
|
|
dcd881ba6c | ||
|
|
d40a67ce68 | ||
|
|
1c1e7ca62a | ||
|
|
d3831d511b | ||
|
|
0942e93144 | ||
|
|
98d3eba8e8 | ||
|
|
ff3db03696 | ||
|
|
3608c31d22 | ||
|
|
8d504d9cee | ||
|
|
782921f6e0 | ||
|
|
0694aabb45 | ||
|
|
604b35dfde | ||
|
|
4c308bf5d0 | ||
|
|
7a53ca7f8e | ||
|
|
7c51f04140 | ||
|
|
488b326e0f | ||
|
|
1e0c0a35ba | ||
|
|
bf044c1ccc | ||
|
|
c9caa7f034 | ||
|
|
1b8966b3a4 | ||
|
|
01d4d404c0 | ||
|
|
354bbf820f | ||
|
|
7620f9ebbe | ||
|
|
9c2c668a04 | ||
|
|
b6f5a5712b | ||
|
|
9e9df6eb16 | ||
|
|
f510c993a6 | ||
|
|
8ddcbaac27 | ||
|
|
3fcab72900 | ||
|
|
70aa2233e2 | ||
|
|
8123a2924d | ||
|
|
dd3c308b09 | ||
|
|
e56ca2ffd6 | ||
|
|
57c98c363c | ||
|
|
2211508061 | ||
|
|
848e41919d | ||
|
|
dac613a8bb | ||
|
|
a1735278df | ||
|
|
3730bd04a4 | ||
|
|
eee3445d2f | ||
|
|
8bf473dc3a | ||
|
|
e106dda794 | ||
|
|
fc35d2a35c | ||
|
|
aef9ff0d38 | ||
|
|
192fb46d1d | ||
|
|
1dd4eb05f2 | ||
|
|
79f0c494fa | ||
|
|
7caeb47637 | ||
|
|
ffef339ebd | ||
|
|
817153b20b | ||
|
|
686eb2e786 | ||
|
|
53d5192477 | ||
|
|
a172f5fe85 | ||
|
|
288408795b | ||
|
|
4b5a811b08 | ||
|
|
c1259d8b6e | ||
|
|
3acbe431f7 | ||
|
|
40f381257d | ||
|
|
beec983f21 | ||
|
|
d20750012e | ||
|
|
e31c9d74fa | ||
|
|
78adc388df | ||
|
|
609413cda4 | ||
|
|
63e5d6a94a | ||
|
|
fd5970b647 | ||
|
|
af38b8f92b | ||
|
|
e676e65f9e | ||
|
|
4c479301ff | ||
|
|
98178947ae | ||
|
|
0dbf3b462b | ||
|
|
cd9f8b569b | ||
|
|
6b23728efa | ||
|
|
ff0f7ee483 | ||
|
|
74498c3ac9 | ||
|
|
7a61e740e8 | ||
|
|
81e6b55c39 | ||
|
|
e439ac0e0e | ||
|
|
7b9d784a64 | ||
|
|
56b2630a1c | ||
|
|
6d5597a732 | ||
|
|
c0a9345358 | ||
|
|
2e4ad81fff | ||
|
|
1aad85a4f9 | ||
|
|
fbd2993239 | ||
|
|
203228bd05 | ||
|
|
da7edebe90 | ||
|
|
c7444a189e | ||
|
|
29235c5fa6 | ||
|
|
bf8374ff9f | ||
|
|
ba05e85e48 | ||
|
|
a9aab0702c | ||
|
|
b5cf83d501 | ||
|
|
1cc2e6303a | ||
|
|
4509a07fff | ||
|
|
ea419f6d40 | ||
|
|
c9b6707468 | ||
|
|
cf512a35a7 | ||
|
|
bdd3e5343d | ||
|
|
80fd8cd338 | ||
|
|
eb48592083 | ||
|
|
a45cb25002 | ||
|
|
21ec116769 | ||
|
|
1d4775a94b | ||
|
|
4cde9a7e9e | ||
|
|
709ba72777 | ||
|
|
0e56b8c575 | ||
|
|
558a087e37 | ||
|
|
7d96fe8b65 | ||
|
|
2f3e8986ab | ||
|
|
df359ba0fa | ||
|
|
31fa60884f | ||
|
|
15be227920 | ||
|
|
fdedfd54c7 | ||
|
|
7d55eb4b44 | ||
|
|
61ac7ae31e | ||
|
|
353e141036 | ||
|
|
cb4332842f | ||
|
|
004d219c97 | ||
|
|
94561e6815 | ||
|
|
ce3af4961b | ||
|
|
bbd81e7d9c | ||
|
|
8b1d198efd | ||
|
|
7a3bafc961 | ||
|
|
512eed9b04 | ||
|
|
38adf640e9 | ||
|
|
f4d8d73970 | ||
|
|
5a50285dee | ||
|
|
ca4ea0719f | ||
|
|
4c6251bf28 | ||
|
|
22c12c7366 | ||
|
|
8f49d1591f | ||
|
|
a97dbab2ae | ||
|
|
4cb8261d3b | ||
|
|
059e2d740f | ||
|
|
587d1c9d74 | ||
|
|
ab56c96170 | ||
|
|
efa4270429 | ||
|
|
0fbe5d888a | ||
|
|
b68f81179e | ||
|
|
6a8c1af5f9 | ||
|
|
98f287559b | ||
|
|
1a53d85f6a | ||
|
|
05b168aa04 | ||
|
|
a68d856074 | ||
|
|
d5dac9c6cc | ||
|
|
e9f7f42c03 | ||
|
|
7b8265d4a3 | ||
|
|
5715f4c2cb | ||
|
|
350debb66c | ||
|
|
267cd3660b | ||
|
|
bc1c0c3c6d | ||
|
|
c420f69da8 | ||
|
|
2151d96303 | ||
|
|
c48c65d0ce | ||
|
|
1c0b706b94 | ||
|
|
2dca3d6f5a | ||
|
|
0992636f8c | ||
|
|
86d3f2b4ed | ||
|
|
b78b4038f6 | ||
|
|
84c14349dc | ||
|
|
9916e34006 | ||
|
|
a729b42e5d | ||
|
|
01f8129ed0 | ||
|
|
fd85763fb4 | ||
|
|
c278ffe2cb | ||
|
|
902c76a781 | ||
|
|
4daed0070a | ||
|
|
a4d055b3ac | ||
|
|
e6ae6c6f04 | ||
|
|
de54ceeb40 | ||
|
|
77fe00df30 | ||
|
|
4ae3e63dea | ||
|
|
3329839dbe | ||
|
|
ca10920bb5 | ||
|
|
b41957ce5c | ||
|
|
3db6699018 | ||
|
|
5284ca90ef | ||
|
|
628cdff4fa | ||
|
|
af0c6f9233 | ||
|
|
7697bbfa4e | ||
|
|
642a455a9c | ||
|
|
6abfa2859b | ||
|
|
20c2225d8f | ||
|
|
5e7759b697 | ||
|
|
1ce8d2b31d | ||
|
|
9fbecb5af6 | ||
|
|
1223469be9 | ||
|
|
5eae3ddd8a | ||
|
|
3d0b6e6ea1 | ||
|
|
c6c53548ac | ||
|
|
3efe2d1e05 | ||
|
|
7c87b53517 | ||
|
|
a461c97bcf | ||
|
|
67a6d6c1e3 | ||
|
|
8b8e427a2b | ||
|
|
7d5c263ab2 | ||
|
|
79ac53b2d9 | ||
|
|
a704412dee | ||
|
|
24e2338769 | ||
|
|
bc240f82ef | ||
|
|
a2ad68406d | ||
|
|
3d807d2331 | ||
|
|
e6a7b484ba | ||
|
|
a62371829f | ||
|
|
fb98336713 | ||
|
|
ebadb4db0a | ||
|
|
9a480eb710 | ||
|
|
03ecde8b83 | ||
|
|
0291b78bfa | ||
|
|
377e6fad25 | ||
|
|
e75c955a68 | ||
|
|
6df356327d | ||
|
|
7a57586f90 | ||
|
|
1e2bad270d | ||
|
|
688d983b25 | ||
|
|
fa834a76ef | ||
|
|
5cb88efc38 | ||
|
|
6b64c43851 | ||
|
|
704e6c5448 | ||
|
|
072d7379df | ||
|
|
df1b756a43 | ||
|
|
30269d9d95 | ||
|
|
d29a1de980 | ||
|
|
57f5ccff81 | ||
|
|
69374bfa11 | ||
|
|
d9fd330216 | ||
|
|
312f62d98f | ||
|
|
0d408055b2 | ||
|
|
496ae42d72 | ||
|
|
3ab86543ad | ||
|
|
ce7926cfa4 | ||
|
|
48c81b188c | ||
|
|
21b241bbbe | ||
|
|
934d6fc267 | ||
|
|
a0ce2daa2e | ||
|
|
006d77239b | ||
|
|
303d31bde7 | ||
|
|
737fbeccde | ||
|
|
ec17dc9830 | ||
|
|
0f710ea3be | ||
|
|
bd4eec4527 | ||
|
|
c78252e0d5 | ||
|
|
b1f70982bf | ||
|
|
6128fb4f19 | ||
|
|
ee915254e7 | ||
|
|
d835d4aae6 | ||
|
|
5945d0ebee | ||
|
|
3d3aacdcb1 | ||
|
|
c3ce6cff72 | ||
|
|
6d7a01fb4e | ||
|
|
8582e09e73 | ||
|
|
90744f2965 | ||
|
|
03d0776fdc | ||
|
|
9d18f40c5f | ||
|
|
47e771ee37 | ||
|
|
20760dcf0e | ||
|
|
2edf12e4cd | ||
|
|
c946c1d1c4 | ||
|
|
bb5fe5a6d1 | ||
|
|
814d2fa9fc | ||
|
|
10d89cb6a6 | ||
|
|
a6569d09ac | ||
|
|
b38ad81928 | ||
|
|
637772f8f0 | ||
|
|
0093eb6c2c | ||
|
|
f3b70d1cb2 | ||
|
|
5152f57f6e | ||
|
|
e6112a311d | ||
|
|
e069d069d2 | ||
|
|
fd74bbc868 | ||
|
|
3b37aea3da | ||
|
|
2fd4bbbd49 | ||
|
|
17226b524b | ||
|
|
f38b33ee50 | ||
|
|
0da0e0a6db | ||
|
|
de2b6d1dee | ||
|
|
c9a501ddc8 | ||
|
|
c5babbbb22 | ||
|
|
1fd0d1a330 | ||
|
|
84d9b30fec | ||
|
|
d0f1522f0d | ||
|
|
6b2e25122d | ||
|
|
ce3026f91c | ||
|
|
4b384b3b59 | ||
|
|
da7605a234 | ||
|
|
3abdec1b91 | ||
|
|
ef34199887 | ||
|
|
08eca88e2f | ||
|
|
4636cc74dc | ||
|
|
4cc54a2f05 | ||
|
|
9bd728889b | ||
|
|
d870115467 | ||
|
|
2b445fb563 | ||
|
|
44a3bad278 | ||
|
|
8d2615547d | ||
|
|
3292db8b77 | ||
|
|
8c6ad52437 | ||
|
|
62e53b53f0 | ||
|
|
35db157617 | ||
|
|
cfb137a94b | ||
|
|
9b4a33a612 | ||
|
|
918b7c1e03 | ||
|
|
8fb354705c | ||
|
|
b3826064b7 | ||
|
|
acba68dab9 | ||
|
|
32ea709350 | ||
|
|
c15dfc7c5d | ||
|
|
92f6a0c6c8 | ||
|
|
0235be538b | ||
|
|
abf19e7a27 | ||
|
|
1d3540dca6 | ||
|
|
ddfc9d911b | ||
|
|
b952256988 | ||
|
|
71868936d1 | ||
|
|
23ef4fb132 | ||
|
|
f74f2ca3ef | ||
|
|
f4f005cdd6 | ||
|
|
3137652280 | ||
|
|
88c6f0340a | ||
|
|
86146c7292 | ||
|
|
fad39b6d67 | ||
|
|
09a9330f3e | ||
|
|
d2d7f32c45 | ||
|
|
120b18b399 | ||
|
|
32b9e6b73d | ||
|
|
8d6e5272b9 | ||
|
|
b6f90a9715 | ||
|
|
e3d0b777fc | ||
|
|
79a8450462 | ||
|
|
1a967597e8 | ||
|
|
d5b0794b00 | ||
|
|
d78ee3d62b | ||
|
|
03037a9a6b | ||
|
|
0637b65846 | ||
|
|
e77e914f44 | ||
|
|
68c44daef2 | ||
|
|
8ca0b54b18 | ||
|
|
b77d01baca | ||
|
|
cd509bbbdc | ||
|
|
24a3ac9811 | ||
|
|
d35d3aabc3 | ||
|
|
9e624a6c0d | ||
|
|
9823cd25d2 | ||
|
|
416a8c8f5d | ||
|
|
c55bdd423e | ||
|
|
e45e202c5e | ||
|
|
2280a30ba9 | ||
|
|
9ac7518156 | ||
|
|
0e6dbaf71a | ||
|
|
fe018ff8f7 | ||
|
|
9b01e09302 | ||
|
|
3127474fd7 | ||
|
|
b849edfcca | ||
|
|
20a23c2868 | ||
|
|
49d9ded684 | ||
|
|
fc57b437c2 | ||
|
|
1ba20561ed | ||
|
|
2c8b26e091 | ||
|
|
62d2a97f38 | ||
|
|
44aa292bb5 | ||
|
|
5273b52c31 | ||
|
|
f143efb810 | ||
|
|
978fb06349 | ||
|
|
e274e8070d | ||
|
|
46bd5b42fa | ||
|
|
0b15e29324 | ||
|
|
75b6669371 | ||
|
|
c1c34017e4 | ||
|
|
ed08818b6f | ||
|
|
71adfc0a74 | ||
|
|
49ccbddb17 | ||
|
|
be3c7eef60 | ||
|
|
336c6cdd9d | ||
|
|
709a706853 | ||
|
|
dad62faf88 | ||
|
|
6e0a5fb5c6 | ||
|
|
4b9551d27f | ||
|
|
2a67bc9926 | ||
|
|
d02241d32c | ||
|
|
55e038d345 | ||
|
|
ea2bfbda44 | ||
|
|
ed7794f396 | ||
|
|
086646f311 | ||
|
|
ad169ca5a5 | ||
|
|
671e636aef | ||
|
|
72d381e9ed | ||
|
|
c076933b52 | ||
|
|
fdb5c813ad | ||
|
|
1bda6633b1 | ||
|
|
249a5bf3b7 | ||
|
|
679b468618 | ||
|
|
cf2a0af3d9 | ||
|
|
82be53224d | ||
|
|
9cc995cb52 | ||
|
|
42d414797a | ||
|
|
b071a4df70 | ||
|
|
46d927291c | ||
|
|
50ab01d9c9 | ||
|
|
dc8fe63acf | ||
|
|
4fc5863888 | ||
|
|
e2d8149dcf | ||
|
|
5f156d6bab | ||
|
|
816cdcf4bf | ||
|
|
75c94ae092 | ||
|
|
ecf2c50a26 | ||
|
|
cecb9293f6 | ||
|
|
1ebfa0ad7e | ||
|
|
35c7b57308 | ||
|
|
36bfeffbcc | ||
|
|
e6858719c9 | ||
|
|
f2d52f83fe | ||
|
|
f1d3cadb3b | ||
|
|
7d61d8e646 | ||
|
|
6ede400f3a | ||
|
|
78de45fee2 | ||
|
|
4559e33331 | ||
|
|
43875dd3fe | ||
|
|
fa10384a92 | ||
|
|
7e144da6b9 | ||
|
|
54af17e7bf | ||
|
|
d4a9f5bb2e | ||
|
|
8040813da8 | ||
|
|
871e40c5c0 | ||
|
|
7ce922b084 | ||
|
|
0c9989695a | ||
|
|
88d7cb3ed5 | ||
|
|
5b7fc80f26 | ||
|
|
553e8baa8b | ||
|
|
62a5031ccf | ||
|
|
4abac65316 | ||
|
|
c6e42e1032 | ||
|
|
a353631892 | ||
|
|
f144c982e3 | ||
|
|
16625b1dc7 | ||
|
|
78ccce7d1a | ||
|
|
ffba5f7d31 | ||
|
|
2cb8fa62df | ||
|
|
853b936cdd | ||
|
|
4f0fdbab62 | ||
|
|
190b23b702 | ||
|
|
e0bb79b2c4 | ||
|
|
2eab763d74 | ||
|
|
f64c1dd9e5 | ||
|
|
fab1d94d34 | ||
|
|
facf49b2b7 | ||
|
|
da86b86776 | ||
|
|
a79d9d4e77 | ||
|
|
d9e378211a | ||
|
|
99fbbf70de | ||
|
|
50a616457d | ||
|
|
2ad30ebe88 | ||
|
|
68dbc29f2c | ||
|
|
a87863229f | ||
|
|
f39ffcb997 | ||
|
|
f20bb388be | ||
|
|
fc45015b13 | ||
|
|
7a5f047f8e | ||
|
|
4251bee3ca | ||
|
|
f02741284c | ||
|
|
fec7419fcc | ||
|
|
e8694531f6 | ||
|
|
461491f742 | ||
|
|
af29acf462 | ||
|
|
b47f29e87c | ||
|
|
fc8ec6d7fa | ||
|
|
1d4c736cfa | ||
|
|
600450082a | ||
|
|
c6da0864f2 | ||
|
|
10fc6b4562 | ||
|
|
e48b7d83a3 | ||
|
|
a6742d401c | ||
|
|
87b9a8c4c8 | ||
|
|
134dc55891 | ||
|
|
295cf98e70 | ||
|
|
7333155b8c | ||
|
|
3960c7d8e6 | ||
|
|
b053f99690 | ||
|
|
5700c3f72e | ||
|
|
cdb3729a88 | ||
|
|
c33f2a1d27 | ||
|
|
9b9a32b053 | ||
|
|
95eec369b5 | ||
|
|
5d5860683b | ||
|
|
434b31b932 | ||
|
|
f69e42e520 | ||
|
|
c5cadfe0c6 | ||
|
|
f21be30004 | ||
|
|
b8b21d53e1 | ||
|
|
09ec39c87a | ||
|
|
dbf18db3a3 | ||
|
|
d8f0ae0980 | ||
|
|
09bbf1f4d7 | ||
|
|
d07aff9872 | ||
|
|
4cb3f9d177 | ||
|
|
c1a815778b | ||
|
|
5b003b09ac | ||
|
|
22d0697c77 | ||
|
|
432b0f3e54 | ||
|
|
2bee41e90e | ||
|
|
2a7312f2b4 | ||
|
|
f5091339ad | ||
|
|
3d06d68196 | ||
|
|
1a643bfa8c | ||
|
|
cd82b0a669 | ||
|
|
57312e29e2 | ||
|
|
592df4819b | ||
|
|
f36997c7c5 | ||
|
|
b1640d3626 | ||
|
|
825c62c62b | ||
|
|
a4b0c6f37d | ||
|
|
239b88aa3b | ||
|
|
88819611f4 | ||
|
|
e6ff8368a9 | ||
|
|
8315f572ea | ||
|
|
89e2070419 | ||
|
|
b5f4df0912 | ||
|
|
b9f3f80d50 | ||
|
|
0e8ae1a206 | ||
|
|
32729174bb | ||
|
|
2cf6fe8da7 | ||
|
|
929b031f09 | ||
|
|
57a36491ee | ||
|
|
f3a8dde5f0 | ||
|
|
ffa2489998 | ||
|
|
6cd7bcdae6 | ||
|
|
e4f684f411 | ||
|
|
fdc9a71f9e | ||
|
|
bb134ee7ac | ||
|
|
18dc6f3c88 | ||
|
|
9327fd3aa1 | ||
|
|
aa859b9002 | ||
|
|
20a15ecd35 | ||
|
|
062c59b321 | ||
|
|
9ace66edb7 | ||
|
|
a97d784f26 | ||
|
|
a2e7173983 | ||
|
|
0869651dc3 | ||
|
|
ca42286354 | ||
|
|
174bfcc597 | ||
|
|
244d25ce53 | ||
|
|
8287efd51f | ||
|
|
df31b5d59f | ||
|
|
53cc47a8b1 | ||
|
|
55f8294a38 | ||
|
|
67a5e3f37e | ||
|
|
06cd2f3a57 | ||
|
|
d614a94203 | ||
|
|
f66aca2164 | ||
|
|
b071ecb45e | ||
|
|
83a70fecca | ||
|
|
dbc7c224c1 | ||
|
|
7876f9a8a5 | ||
|
|
215057ce6f | ||
|
|
6d8b0b3ab6 | ||
|
|
e5c85ddd32 | ||
|
|
6bf3c34fe5 | ||
|
|
584f5e5935 | ||
|
|
58a5367015 | ||
|
|
8c2b907ff5 | ||
|
|
e3ab0c0192 | ||
|
|
8572e3eabb | ||
|
|
d4f10c61ef | ||
|
|
59e11d6caa | ||
|
|
67778198de | ||
|
|
ebfd8cd6f7 | ||
|
|
83b54f2ae6 | ||
|
|
7ea60ed178 | ||
|
|
c61d1ce6b4 | ||
|
|
6a7959547e | ||
|
|
af3834672e | ||
|
|
f95ff6cb89 | ||
|
|
e85c13b3fe | ||
|
|
ed09627fdb | ||
|
|
99ba2c2e8b | ||
|
|
589bdf5dcd | ||
|
|
1abbd5ecbc | ||
|
|
7619e8427a | ||
|
|
069e24d713 | ||
|
|
8e9b45f80f | ||
|
|
2d604a80c9 | ||
|
|
1f59a1b952 | ||
|
|
6e5ff01db5 | ||
|
|
50be44adf8 | ||
|
|
e1bf4347ab | ||
|
|
3a3305c020 | ||
|
|
a83f174e1a | ||
|
|
1295033fae | ||
|
|
755abec636 | ||
|
|
310995fb87 | ||
|
|
bee30e572f | ||
|
|
cd37a40bab | ||
|
|
8699790e78 | ||
|
|
d2d3f58a14 | ||
|
|
081df59ed7 | ||
|
|
ec3bcdcb26 | ||
|
|
8265cf8a6a | ||
|
|
dfb53fb3dd | ||
|
|
705fc920e5 | ||
|
|
99764741e4 | ||
|
|
35b3bc4522 | ||
|
|
91eee99bab | ||
|
|
e56defdc50 | ||
|
|
fbe4d3ce9f | ||
|
|
8cf5707575 | ||
|
|
f786a17014 | ||
|
|
427b9c1ebc | ||
|
|
141957e4b5 | ||
|
|
b911f4f34e | ||
|
|
0caf76dfae | ||
|
|
00f09168d7 | ||
|
|
d636359c10 | ||
|
|
8fd32aba4f | ||
|
|
9db59d5deb | ||
|
|
d98321c703 | ||
|
|
a6424c005f | ||
|
|
80d127c277 | ||
|
|
3ca0c828c6 | ||
|
|
d3a8e03b2c | ||
|
|
6e75de0dcb | ||
|
|
a7bed9741a | ||
|
|
f70e52a7a5 | ||
|
|
95d7de0654 | ||
|
|
3398d25b35 | ||
|
|
80a4a43680 | ||
|
|
01ac61f38c | ||
|
|
a98be36684 | ||
|
|
7f085c4012 | ||
|
|
5f008d1560 | ||
|
|
0782b579d5 | ||
|
|
f8ed2afef1 | ||
|
|
a883508eca | ||
|
|
a4a20ec220 | ||
|
|
faed63712f | ||
|
|
0032ec5036 | ||
|
|
9df24a841f | ||
|
|
d2aa4ff2bd | ||
|
|
171db01401 | ||
|
|
0112f1e11d | ||
|
|
8f7b09b58e | ||
|
|
479c2b6cec | ||
|
|
e6f50e6637 | ||
|
|
548f8bf23e | ||
|
|
b6bfe81f9a | ||
|
|
ab95bcc028 | ||
|
|
8b7b3df0a3 | ||
|
|
69a6416146 | ||
|
|
1ced4e277b | ||
|
|
0c5236ebcb | ||
|
|
66c55c5007 | ||
|
|
7f5a0f1b0c | ||
|
|
0c766938f1 | ||
|
|
1455e87aef | ||
|
|
7c50eef8ad | ||
|
|
aa9f972ccf | ||
|
|
45df99bb56 | ||
|
|
a2968e57cf | ||
|
|
67f831beba | ||
|
|
264d47caf4 | ||
|
|
ef72b3273e | ||
|
|
7d3ade7ae1 | ||
|
|
59897fdf55 | ||
|
|
0b4ea04c91 | ||
|
|
094b3b4636 | ||
|
|
0298beeb06 | ||
|
|
971542d6bc | ||
|
|
5bdbc9f40d | ||
|
|
efcd35d4a1 | ||
|
|
a01541d7ca | ||
|
|
7d96d7c066 | ||
|
|
639ea35921 | ||
|
|
dee1905e16 | ||
|
|
1803705749 | ||
|
|
5d96ee5492 | ||
|
|
4a0a1a32a4 | ||
|
|
c2108a35d0 | ||
|
|
31ecdbae18 | ||
|
|
e9c733e78f | ||
|
|
2320c5e04c | ||
|
|
fb7b3b0295 | ||
|
|
316a3d51ee | ||
|
|
240dcf2e5c | ||
|
|
5fbdbffa23 | ||
|
|
932c8b65fb | ||
|
|
e18e174947 | ||
|
|
f2c7df3a3b | ||
|
|
2b15dae806 | ||
|
|
2625d72818 | ||
|
|
a8b4d72421 | ||
|
|
88562890ae | ||
|
|
86c267eb89 | ||
|
|
3d1928cca9 | ||
|
|
8c64d3b55c | ||
|
|
41b238e87b | ||
|
|
da83025fb0 | ||
|
|
84d3b190c8 | ||
|
|
272fc5a72d | ||
|
|
0f709059e3 | ||
|
|
0f91bb28a9 | ||
|
|
63d75a8e17 | ||
|
|
be95d8409b | ||
|
|
9d3000498b | ||
|
|
48f8468e65 | ||
|
|
9b60559855 | ||
|
|
e97acf7938 | ||
|
|
1cfb92e75c | ||
|
|
4c617ccd49 | ||
|
|
494517e767 |
@@ -1,417 +0,0 @@
|
|||||||
version: 2.1
|
|
||||||
commands:
|
|
||||||
|
|
||||||
cmake:
|
|
||||||
description: Configure build
|
|
||||||
steps:
|
|
||||||
- run:
|
|
||||||
name: Configure build
|
|
||||||
command: cmake ..
|
|
||||||
working_directory: build
|
|
||||||
|
|
||||||
|
|
||||||
build_source:
|
|
||||||
description: Create source tarball
|
|
||||||
steps:
|
|
||||||
- run:
|
|
||||||
name: Create source tarball
|
|
||||||
command: ../dist/scripts/maketarball.sh
|
|
||||||
working_directory: build
|
|
||||||
|
|
||||||
|
|
||||||
build_rpm:
|
|
||||||
description: Build RPM
|
|
||||||
steps:
|
|
||||||
- run:
|
|
||||||
name: Create RPM build sources directories
|
|
||||||
command: mkdir -p ~/rpmbuild/SOURCES /usr/src/packages/SOURCES
|
|
||||||
- run:
|
|
||||||
name: Copy source tarball 1
|
|
||||||
command: cp strawberry-*.tar.xz ~/rpmbuild/SOURCES/
|
|
||||||
working_directory: build
|
|
||||||
- run:
|
|
||||||
name: Copy source tarball 2
|
|
||||||
command: cp strawberry-*.tar.xz /usr/src/packages/SOURCES/
|
|
||||||
working_directory: build
|
|
||||||
- run:
|
|
||||||
name: Build RPM
|
|
||||||
command: rpmbuild -ba ../dist/unix/strawberry.spec
|
|
||||||
working_directory: build
|
|
||||||
|
|
||||||
|
|
||||||
build_deb:
|
|
||||||
description: Build Deb
|
|
||||||
steps:
|
|
||||||
- run:
|
|
||||||
name: make deb
|
|
||||||
command: dpkg-buildpackage -b -d -uc -us -nc -j2
|
|
||||||
|
|
||||||
|
|
||||||
install_opensuse_dependencies:
|
|
||||||
description: Install openSUSE dependencies
|
|
||||||
steps:
|
|
||||||
- run:
|
|
||||||
name: Update packages
|
|
||||||
command: zypper --non-interactive --gpg-auto-import-keys ref
|
|
||||||
- run:
|
|
||||||
name: Install openSUSE dependencies
|
|
||||||
command: >
|
|
||||||
zypper --non-interactive --gpg-auto-import-keys install
|
|
||||||
lsb-release
|
|
||||||
rpm-build
|
|
||||||
git
|
|
||||||
tar
|
|
||||||
make
|
|
||||||
cmake
|
|
||||||
gcc
|
|
||||||
gcc-c++
|
|
||||||
gettext-tools
|
|
||||||
glibc-devel
|
|
||||||
libboost_headers-devel
|
|
||||||
boost-devel
|
|
||||||
glib2-devel
|
|
||||||
glib2-tools
|
|
||||||
dbus-1-devel
|
|
||||||
alsa-devel
|
|
||||||
libnotify-devel
|
|
||||||
libgnutls-devel
|
|
||||||
protobuf-devel
|
|
||||||
sqlite3-devel
|
|
||||||
libpulse-devel
|
|
||||||
gstreamer-devel
|
|
||||||
gstreamer-plugins-base-devel
|
|
||||||
taglib-devel
|
|
||||||
vlc-devel
|
|
||||||
libQt5Core-devel
|
|
||||||
libQt5Gui-devel
|
|
||||||
libQt5Widgets-devel
|
|
||||||
libQt5Concurrent-devel
|
|
||||||
libQt5Network-devel
|
|
||||||
libQt5Sql-devel
|
|
||||||
libQt5DBus-devel
|
|
||||||
libQt5Test-devel
|
|
||||||
libqt5-qtx11extras-devel
|
|
||||||
libqt5-qtbase-common-devel
|
|
||||||
libQt5Sql5-sqlite
|
|
||||||
libqt5-linguist-devel
|
|
||||||
libcdio-devel
|
|
||||||
libgpod-devel
|
|
||||||
libmtp-devel
|
|
||||||
libchromaprint-devel
|
|
||||||
desktop-file-utils
|
|
||||||
update-desktop-files
|
|
||||||
appstream-glib
|
|
||||||
hicolor-icon-theme
|
|
||||||
|
|
||||||
|
|
||||||
install_fedora_dependencies:
|
|
||||||
description: Install Fedora dependencies
|
|
||||||
steps:
|
|
||||||
- run:
|
|
||||||
name: Update packages
|
|
||||||
command: yum update --assumeyes
|
|
||||||
- run:
|
|
||||||
name: Upgrade packages
|
|
||||||
command: yum upgrade --assumeyes
|
|
||||||
- run:
|
|
||||||
name: Install Fedora dependencies
|
|
||||||
command: >
|
|
||||||
dnf install --assumeyes
|
|
||||||
@development-tools
|
|
||||||
redhat-lsb-core
|
|
||||||
git
|
|
||||||
glibc
|
|
||||||
gcc-c++
|
|
||||||
rpmdevtools
|
|
||||||
make
|
|
||||||
cmake
|
|
||||||
pkgconfig
|
|
||||||
glib
|
|
||||||
man
|
|
||||||
tar
|
|
||||||
gettext
|
|
||||||
openssh
|
|
||||||
boost-devel
|
|
||||||
dbus-devel
|
|
||||||
protobuf-devel
|
|
||||||
protobuf-compiler
|
|
||||||
sqlite-devel
|
|
||||||
alsa-lib-devel
|
|
||||||
pulseaudio-libs-devel
|
|
||||||
libnotify-devel
|
|
||||||
gnutls-devel
|
|
||||||
qt5-qtbase-devel
|
|
||||||
qt5-qtx11extras-devel
|
|
||||||
qt5-qttools-devel
|
|
||||||
gstreamer1-devel
|
|
||||||
gstreamer1-plugins-base-devel
|
|
||||||
taglib-devel
|
|
||||||
libcdio-devel
|
|
||||||
libgpod-devel
|
|
||||||
libmtp-devel
|
|
||||||
libchromaprint-devel
|
|
||||||
fftw-devel
|
|
||||||
desktop-file-utils
|
|
||||||
libappstream-glib
|
|
||||||
hicolor-icon-theme
|
|
||||||
|
|
||||||
|
|
||||||
install_debian_dependencies:
|
|
||||||
description: Install Debian dependencies
|
|
||||||
steps:
|
|
||||||
- run:
|
|
||||||
name: Install Debian dependencies
|
|
||||||
command: >
|
|
||||||
apt-get update && apt-get install -y
|
|
||||||
build-essential
|
|
||||||
dh-make
|
|
||||||
ssh
|
|
||||||
git
|
|
||||||
make
|
|
||||||
cmake
|
|
||||||
gcc
|
|
||||||
pkg-config
|
|
||||||
fakeroot
|
|
||||||
gettext
|
|
||||||
lsb-release
|
|
||||||
libglib2.0-dev
|
|
||||||
dpkg-dev
|
|
||||||
libdbus-1-dev
|
|
||||||
libboost-dev
|
|
||||||
libprotobuf-dev
|
|
||||||
protobuf-compiler
|
|
||||||
libsqlite3-dev
|
|
||||||
libgnutls28-dev
|
|
||||||
libasound2-dev
|
|
||||||
libpulse-dev
|
|
||||||
libtag1-dev
|
|
||||||
qtbase5-dev
|
|
||||||
qtbase5-dev-tools
|
|
||||||
qtbase5-private-dev
|
|
||||||
libqt5x11extras5-dev
|
|
||||||
qttools5-dev
|
|
||||||
libgstreamer1.0-dev
|
|
||||||
libgstreamer-plugins-base1.0-dev
|
|
||||||
gstreamer1.0-alsa
|
|
||||||
gstreamer1.0-pulseaudio
|
|
||||||
libchromaprint-dev
|
|
||||||
libfftw3-dev
|
|
||||||
libcdio-dev
|
|
||||||
libmtp-dev
|
|
||||||
libgpod-dev
|
|
||||||
|
|
||||||
|
|
||||||
install_ubuntu_dependencies:
|
|
||||||
description: Install Ubuntu dependencies
|
|
||||||
steps:
|
|
||||||
- run:
|
|
||||||
name: Setup environment
|
|
||||||
command: |
|
|
||||||
echo 'export DEBIAN_FRONTEND=noninteractive' >> $BASH_ENV
|
|
||||||
source $BASH_ENV
|
|
||||||
- run:
|
|
||||||
name: Install Ubuntu dependencies
|
|
||||||
command: >
|
|
||||||
apt-get update && apt-get install -y
|
|
||||||
build-essential
|
|
||||||
dh-make
|
|
||||||
ssh
|
|
||||||
git
|
|
||||||
make
|
|
||||||
cmake
|
|
||||||
pkg-config
|
|
||||||
gcc
|
|
||||||
fakeroot
|
|
||||||
wget
|
|
||||||
curl
|
|
||||||
gettext
|
|
||||||
lsb-release
|
|
||||||
dpkg-dev
|
|
||||||
libglib2.0-dev
|
|
||||||
libboost-dev
|
|
||||||
libdbus-1-dev
|
|
||||||
libprotobuf-dev
|
|
||||||
protobuf-compiler
|
|
||||||
libsqlite3-dev
|
|
||||||
libgnutls28-dev
|
|
||||||
libasound2-dev
|
|
||||||
libpulse-dev
|
|
||||||
libtag1-dev
|
|
||||||
qtbase5-dev
|
|
||||||
qtbase5-dev-tools
|
|
||||||
qtbase5-private-dev
|
|
||||||
libqt5x11extras5-dev
|
|
||||||
qttools5-dev
|
|
||||||
libgstreamer1.0-dev
|
|
||||||
libgstreamer-plugins-base1.0-dev
|
|
||||||
libgstreamer-plugins-good1.0-dev
|
|
||||||
gstreamer1.0-alsa
|
|
||||||
gstreamer1.0-pulseaudio
|
|
||||||
libchromaprint-dev
|
|
||||||
libfftw3-dev
|
|
||||||
libcdio-dev
|
|
||||||
libmtp-dev
|
|
||||||
libgpod-dev
|
|
||||||
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
|
|
||||||
build_source:
|
|
||||||
docker:
|
|
||||||
- image: opensuse/leap:15.2
|
|
||||||
steps:
|
|
||||||
- install_opensuse_dependencies
|
|
||||||
- checkout
|
|
||||||
- cmake
|
|
||||||
- build_source
|
|
||||||
|
|
||||||
|
|
||||||
build_opensuse_lp151:
|
|
||||||
docker:
|
|
||||||
- image: opensuse/leap:15.1
|
|
||||||
environment:
|
|
||||||
RPM_BUILD_NCPUS: "2"
|
|
||||||
steps:
|
|
||||||
- install_opensuse_dependencies
|
|
||||||
- checkout
|
|
||||||
- cmake
|
|
||||||
- build_source
|
|
||||||
- build_rpm
|
|
||||||
|
|
||||||
build_opensuse_lp152:
|
|
||||||
docker:
|
|
||||||
- image: opensuse/leap:15.2
|
|
||||||
environment:
|
|
||||||
RPM_BUILD_NCPUS: "2"
|
|
||||||
steps:
|
|
||||||
- install_opensuse_dependencies
|
|
||||||
- checkout
|
|
||||||
- cmake
|
|
||||||
- build_source
|
|
||||||
- build_rpm
|
|
||||||
|
|
||||||
|
|
||||||
build_fedora_32:
|
|
||||||
docker:
|
|
||||||
- image: fedora:32
|
|
||||||
environment:
|
|
||||||
RPM_BUILD_NCPUS: "2"
|
|
||||||
steps:
|
|
||||||
- install_fedora_dependencies
|
|
||||||
- checkout
|
|
||||||
- cmake
|
|
||||||
- build_source
|
|
||||||
- build_rpm
|
|
||||||
|
|
||||||
build_fedora_33:
|
|
||||||
docker:
|
|
||||||
- image: fedora:33
|
|
||||||
environment:
|
|
||||||
RPM_BUILD_NCPUS: "2"
|
|
||||||
steps:
|
|
||||||
- install_fedora_dependencies
|
|
||||||
- checkout
|
|
||||||
- cmake
|
|
||||||
- build_source
|
|
||||||
- build_rpm
|
|
||||||
|
|
||||||
|
|
||||||
build_debian_buster:
|
|
||||||
docker:
|
|
||||||
- image: debian:buster
|
|
||||||
steps:
|
|
||||||
- install_debian_dependencies
|
|
||||||
- checkout
|
|
||||||
- cmake
|
|
||||||
- build_deb
|
|
||||||
|
|
||||||
build_debian_bullseye:
|
|
||||||
docker:
|
|
||||||
- image: debian:bullseye
|
|
||||||
steps:
|
|
||||||
- install_debian_dependencies
|
|
||||||
- checkout
|
|
||||||
- cmake
|
|
||||||
- build_deb
|
|
||||||
|
|
||||||
|
|
||||||
build_ubuntu_bionic:
|
|
||||||
docker:
|
|
||||||
- image: ubuntu:bionic
|
|
||||||
steps:
|
|
||||||
- install_ubuntu_dependencies
|
|
||||||
- checkout
|
|
||||||
- cmake
|
|
||||||
- build_deb
|
|
||||||
|
|
||||||
build_ubuntu_focal:
|
|
||||||
docker:
|
|
||||||
- image: ubuntu:focal
|
|
||||||
steps:
|
|
||||||
- install_ubuntu_dependencies
|
|
||||||
- checkout
|
|
||||||
- cmake
|
|
||||||
- build_deb
|
|
||||||
|
|
||||||
build_ubuntu_groovy:
|
|
||||||
docker:
|
|
||||||
- image: ubuntu:groovy
|
|
||||||
steps:
|
|
||||||
- install_ubuntu_dependencies
|
|
||||||
- checkout
|
|
||||||
- cmake
|
|
||||||
- build_deb
|
|
||||||
|
|
||||||
workflows:
|
|
||||||
version: 2
|
|
||||||
build_all:
|
|
||||||
jobs:
|
|
||||||
|
|
||||||
- build_source:
|
|
||||||
filters:
|
|
||||||
tags:
|
|
||||||
only: /.*/
|
|
||||||
|
|
||||||
|
|
||||||
- build_opensuse_lp151:
|
|
||||||
filters:
|
|
||||||
tags:
|
|
||||||
only: /.*/
|
|
||||||
- build_opensuse_lp152:
|
|
||||||
filters:
|
|
||||||
tags:
|
|
||||||
only: /.*/
|
|
||||||
|
|
||||||
|
|
||||||
- build_fedora_32:
|
|
||||||
filters:
|
|
||||||
tags:
|
|
||||||
only: /.*/
|
|
||||||
- build_fedora_33:
|
|
||||||
filters:
|
|
||||||
tags:
|
|
||||||
only: /.*/
|
|
||||||
|
|
||||||
|
|
||||||
- build_debian_buster:
|
|
||||||
filters:
|
|
||||||
tags:
|
|
||||||
only: /.*/
|
|
||||||
- build_debian_bullseye:
|
|
||||||
filters:
|
|
||||||
tags:
|
|
||||||
only: /.*/
|
|
||||||
|
|
||||||
|
|
||||||
- build_ubuntu_bionic:
|
|
||||||
filters:
|
|
||||||
tags:
|
|
||||||
only: /.*/
|
|
||||||
- build_ubuntu_focal:
|
|
||||||
filters:
|
|
||||||
tags:
|
|
||||||
only: /.*/
|
|
||||||
- build_ubuntu_groovy:
|
|
||||||
filters:
|
|
||||||
tags:
|
|
||||||
only: /.*/
|
|
||||||
2069
.github/workflows/ccpp.yml
vendored
5
.gitignore
vendored
@@ -119,3 +119,8 @@ stage/
|
|||||||
*.snap
|
*.snap
|
||||||
/snap/.snapcraft/
|
/snap/.snapcraft/
|
||||||
/*_source.tar.bz2
|
/*_source.tar.bz2
|
||||||
|
|
||||||
|
# MSVC
|
||||||
|
CMakeSettings.json
|
||||||
|
/.vs/
|
||||||
|
/out/
|
||||||
|
|||||||
46
.travis.yml
@@ -1,46 +0,0 @@
|
|||||||
sudo: required
|
|
||||||
language: C++
|
|
||||||
os: osx
|
|
||||||
osx_image: xcode11.3
|
|
||||||
compiler: clang
|
|
||||||
|
|
||||||
before_install:
|
|
||||||
- if ! [ "$DEPLOY_KEY_ENC" == "" ]; then
|
|
||||||
echo $DEPLOY_KEY_ENC | base64 --decode | openssl aes-256-cbc -K $encrypted_83a41ac424a6_key -iv $encrypted_83a41ac424a6_iv -out ~/.ssh/id_rsa -d ;
|
|
||||||
chmod 600 ~/.ssh/id_rsa ;
|
|
||||||
fi
|
|
||||||
- git fetch --unshallow
|
|
||||||
- git pull
|
|
||||||
- brew update
|
|
||||||
- travis_wait 400 brew upgrade || echo "Failed"
|
|
||||||
- travis_wait 400 brew upgrade || echo "Failed"
|
|
||||||
- brew install glib pkgconfig libffi protobuf protobuf-c qt gettext gnutls fftw sqlite chromaprint zlib taglib
|
|
||||||
- brew install gstreamer gst-plugins-base gst-plugins-good gst-plugins-bad gst-plugins-ugly gst-libav
|
|
||||||
- brew install libcdio libmtp
|
|
||||||
- brew install create-dmg
|
|
||||||
- brew install --cask sparkle
|
|
||||||
- sudo ln -s /usr/local/Caskroom/sparkle/$(ls /usr/local/Caskroom/sparkle | head -n1) /usr/local/opt/sparkle
|
|
||||||
- export Qt6_DIR=/usr/local/opt/qt6/lib/cmake
|
|
||||||
- export Qt6LinguistTools_DIR=/usr/local/opt/qt6/lib/cmake/Qt6LinguistTools
|
|
||||||
- ls /usr/local/lib/gstreamer-1.0
|
|
||||||
before_script:
|
|
||||||
- mkdir build
|
|
||||||
- cd build
|
|
||||||
- cmake .. -DCMAKE_BUILD_TYPE=Release -DBUILD_WITH_QT6=ON -DBUILD_WERROR=ON -DUSE_BUNDLE=ON
|
|
||||||
script:
|
|
||||||
- make -j8
|
|
||||||
- make install
|
|
||||||
- make dmg2
|
|
||||||
after_success:
|
|
||||||
- ls -lh strawberry*.dmg
|
|
||||||
- if [[ "$TRAVIS_OS_NAME" == "osx" ]] && [ -f ~/.ssh/id_rsa ]; then
|
|
||||||
if [[ "$TRAVIS_BRANCH" == "master" ]] || [[ "$TRAVIS_BRANCH" == "travis" ]]; then
|
|
||||||
rsync -e "ssh -o StrictHostKeyChecking=no" -va strawberry*.dmg travis@echoes.jkvinge.net:/home/travis/builds/macos/mojave/;
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
|
|
||||||
branches:
|
|
||||||
except:
|
|
||||||
- # Do not build tags that we create when we upload to GitHub Releases
|
|
||||||
- /^(?i:continuous)$/
|
|
||||||
|
|
||||||
5
3rdparty/README.md
vendored
@@ -23,3 +23,8 @@ macdeployqt
|
|||||||
A modified version of Qt's official macdeployqt utility that fixes some issues,
|
A modified version of Qt's official macdeployqt utility that fixes some issues,
|
||||||
this version also deploys gstreamer plugins.
|
this version also deploys gstreamer plugins.
|
||||||
Can safely be deleted on other platforms.
|
Can safely be deleted on other platforms.
|
||||||
|
|
||||||
|
|
||||||
|
getopt
|
||||||
|
------
|
||||||
|
getopt included only when compiling with MSVC on Windows.
|
||||||
|
|||||||
2
3rdparty/getopt/CMakeLists.txt
vendored
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
add_library(getopt STATIC getopt.c)
|
||||||
|
target_include_directories(getopt PRIVATE ${CMAKE_CURRENT_SOURCE_DIR})
|
||||||
562
3rdparty/getopt/getopt.c
vendored
Normal file
@@ -0,0 +1,562 @@
|
|||||||
|
/* $OpenBSD: getopt_long.c,v 1.23 2007/10/31 12:34:57 chl Exp $ */
|
||||||
|
/* $NetBSD: getopt_long.c,v 1.15 2002/01/31 22:43:40 tv Exp $ */
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2002 Todd C. Miller <Todd.Miller@courtesan.com>
|
||||||
|
*
|
||||||
|
* Permission to use, copy, modify, and distribute this software for any
|
||||||
|
* purpose with or without fee is hereby granted, provided that the above
|
||||||
|
* copyright notice and this permission notice appear in all copies.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||||
|
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||||
|
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||||||
|
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||||
|
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||||
|
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
||||||
|
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||||
|
*
|
||||||
|
* Sponsored in part by the Defense Advanced Research Projects
|
||||||
|
* Agency (DARPA) and Air Force Research Laboratory, Air Force
|
||||||
|
* Materiel Command, USAF, under agreement number F39502-99-1-0512.
|
||||||
|
*/
|
||||||
|
/*-
|
||||||
|
* Copyright (c) 2000 The NetBSD Foundation, Inc.
|
||||||
|
* All rights reserved.
|
||||||
|
*
|
||||||
|
* This code is derived from software contributed to The NetBSD Foundation
|
||||||
|
* by Dieter Baron and Thomas Klausner.
|
||||||
|
*
|
||||||
|
* Redistribution and use in source and binary forms, with or without
|
||||||
|
* modification, are permitted provided that the following conditions
|
||||||
|
* are met:
|
||||||
|
* 1. Redistributions of source code must retain the above copyright
|
||||||
|
* notice, this list of conditions and the following disclaimer.
|
||||||
|
* 2. Redistributions in binary form must reproduce the above copyright
|
||||||
|
* notice, this list of conditions and the following disclaimer in the
|
||||||
|
* documentation and/or other materials provided with the distribution.
|
||||||
|
*
|
||||||
|
* THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
|
||||||
|
* ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
|
||||||
|
* TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
|
||||||
|
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
|
||||||
|
* BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
||||||
|
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
||||||
|
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
||||||
|
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
||||||
|
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
||||||
|
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||||
|
* POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <errno.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <getopt.h>
|
||||||
|
#include <stdarg.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <windows.h>
|
||||||
|
|
||||||
|
#define REPLACE_GETOPT /* use this getopt as the system getopt(3) */
|
||||||
|
|
||||||
|
#ifdef REPLACE_GETOPT
|
||||||
|
int opterr = 1; /* if error message should be printed */
|
||||||
|
int optind = 1; /* index into parent argv vector */
|
||||||
|
int optopt = '?'; /* character checked for validity */
|
||||||
|
#undef optreset /* see getopt.h */
|
||||||
|
#define optreset __mingw_optreset
|
||||||
|
int optreset; /* reset getopt */
|
||||||
|
char *optarg; /* argument associated with option */
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#define PRINT_ERROR ((opterr) && (*options != ':'))
|
||||||
|
|
||||||
|
#define FLAG_PERMUTE 0x01 /* permute non-options to the end of argv */
|
||||||
|
#define FLAG_ALLARGS 0x02 /* treat non-options as args to option "-1" */
|
||||||
|
#define FLAG_LONGONLY 0x04 /* operate as getopt_long_only */
|
||||||
|
|
||||||
|
/* return values */
|
||||||
|
#define BADCH (int)'?'
|
||||||
|
#define BADARG ((*options == ':') ? (int)':' : (int)'?')
|
||||||
|
#define INORDER (int)1
|
||||||
|
|
||||||
|
#ifndef __CYGWIN__
|
||||||
|
#define __progname __argv[0]
|
||||||
|
#else
|
||||||
|
extern char __declspec(dllimport) *__progname;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifdef __CYGWIN__
|
||||||
|
static char EMSG[] = "";
|
||||||
|
#else
|
||||||
|
#define EMSG ""
|
||||||
|
#endif
|
||||||
|
|
||||||
|
static int getopt_internal(int, char * const *, const char *,
|
||||||
|
const struct option *, int *, int);
|
||||||
|
static int parse_long_options(char * const *, const char *,
|
||||||
|
const struct option *, int *, int);
|
||||||
|
static int gcd(int, int);
|
||||||
|
static void permute_args(int, int, int, char * const *);
|
||||||
|
|
||||||
|
static char *place = EMSG; /* option letter processing */
|
||||||
|
|
||||||
|
/* XXX: set optreset to 1 rather than these two */
|
||||||
|
static int nonopt_start = -1; /* first non option argument (for permute) */
|
||||||
|
static int nonopt_end = -1; /* first option after non options (for permute) */
|
||||||
|
|
||||||
|
/* Error messages */
|
||||||
|
static const char recargchar[] = "option requires an argument -- %c";
|
||||||
|
static const char recargstring[] = "option requires an argument -- %s";
|
||||||
|
static const char ambig[] = "ambiguous option -- %.*s";
|
||||||
|
static const char noarg[] = "option doesn't take an argument -- %.*s";
|
||||||
|
static const char illoptchar[] = "unknown option -- %c";
|
||||||
|
static const char illoptstring[] = "unknown option -- %s";
|
||||||
|
|
||||||
|
static void
|
||||||
|
_vwarnx(const char *fmt,va_list ap)
|
||||||
|
{
|
||||||
|
(void)fprintf(stderr,"%s: ",__progname);
|
||||||
|
if (fmt != NULL)
|
||||||
|
(void)vfprintf(stderr,fmt,ap);
|
||||||
|
(void)fprintf(stderr,"\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
warnx(const char *fmt,...)
|
||||||
|
{
|
||||||
|
va_list ap;
|
||||||
|
va_start(ap,fmt);
|
||||||
|
_vwarnx(fmt,ap);
|
||||||
|
va_end(ap);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Compute the greatest common divisor of a and b.
|
||||||
|
*/
|
||||||
|
static int
|
||||||
|
gcd(int a, int b)
|
||||||
|
{
|
||||||
|
int c;
|
||||||
|
|
||||||
|
c = a % b;
|
||||||
|
while (c != 0) {
|
||||||
|
a = b;
|
||||||
|
b = c;
|
||||||
|
c = a % b;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (b);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Exchange the block from nonopt_start to nonopt_end with the block
|
||||||
|
* from nonopt_end to opt_end (keeping the same order of arguments
|
||||||
|
* in each block).
|
||||||
|
*/
|
||||||
|
static void
|
||||||
|
permute_args(int panonopt_start, int panonopt_end, int opt_end,
|
||||||
|
char * const *nargv)
|
||||||
|
{
|
||||||
|
int cstart, cyclelen, i, j, ncycle, nnonopts, nopts, pos;
|
||||||
|
char *swap;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* compute lengths of blocks and number and size of cycles
|
||||||
|
*/
|
||||||
|
nnonopts = panonopt_end - panonopt_start;
|
||||||
|
nopts = opt_end - panonopt_end;
|
||||||
|
ncycle = gcd(nnonopts, nopts);
|
||||||
|
cyclelen = (opt_end - panonopt_start) / ncycle;
|
||||||
|
|
||||||
|
for (i = 0; i < ncycle; i++) {
|
||||||
|
cstart = panonopt_end+i;
|
||||||
|
pos = cstart;
|
||||||
|
for (j = 0; j < cyclelen; j++) {
|
||||||
|
if (pos >= panonopt_end)
|
||||||
|
pos -= nnonopts;
|
||||||
|
else
|
||||||
|
pos += nopts;
|
||||||
|
swap = nargv[pos];
|
||||||
|
/* LINTED const cast */
|
||||||
|
((char **) nargv)[pos] = nargv[cstart];
|
||||||
|
/* LINTED const cast */
|
||||||
|
((char **)nargv)[cstart] = swap;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* parse_long_options --
|
||||||
|
* Parse long options in argc/argv argument vector.
|
||||||
|
* Returns -1 if short_too is set and the option does not match long_options.
|
||||||
|
*/
|
||||||
|
static int
|
||||||
|
parse_long_options(char * const *nargv, const char *options,
|
||||||
|
const struct option *long_options, int *idx, int short_too)
|
||||||
|
{
|
||||||
|
char *current_argv, *has_equal;
|
||||||
|
size_t current_argv_len;
|
||||||
|
int i, ambiguous, match;
|
||||||
|
|
||||||
|
#define IDENTICAL_INTERPRETATION(_x, _y) \
|
||||||
|
(long_options[(_x)].has_arg == long_options[(_y)].has_arg && \
|
||||||
|
long_options[(_x)].flag == long_options[(_y)].flag && \
|
||||||
|
long_options[(_x)].val == long_options[(_y)].val)
|
||||||
|
|
||||||
|
current_argv = place;
|
||||||
|
match = -1;
|
||||||
|
ambiguous = 0;
|
||||||
|
|
||||||
|
optind++;
|
||||||
|
|
||||||
|
if ((has_equal = strchr(current_argv, '=')) != NULL) {
|
||||||
|
/* argument found (--option=arg) */
|
||||||
|
current_argv_len = has_equal - current_argv;
|
||||||
|
has_equal++;
|
||||||
|
} else
|
||||||
|
current_argv_len = strlen(current_argv);
|
||||||
|
|
||||||
|
for (i = 0; long_options[i].name; i++) {
|
||||||
|
/* find matching long option */
|
||||||
|
if (strncmp(current_argv, long_options[i].name,
|
||||||
|
current_argv_len))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
if (strlen(long_options[i].name) == current_argv_len) {
|
||||||
|
/* exact match */
|
||||||
|
match = i;
|
||||||
|
ambiguous = 0;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
/*
|
||||||
|
* If this is a known short option, don't allow
|
||||||
|
* a partial match of a single character.
|
||||||
|
*/
|
||||||
|
if (short_too && current_argv_len == 1)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
if (match == -1) /* partial match */
|
||||||
|
match = i;
|
||||||
|
else if (!IDENTICAL_INTERPRETATION(i, match))
|
||||||
|
ambiguous = 1;
|
||||||
|
}
|
||||||
|
if (ambiguous) {
|
||||||
|
/* ambiguous abbreviation */
|
||||||
|
if (PRINT_ERROR)
|
||||||
|
warnx(ambig, (int)current_argv_len,
|
||||||
|
current_argv);
|
||||||
|
optopt = 0;
|
||||||
|
return (BADCH);
|
||||||
|
}
|
||||||
|
if (match != -1) { /* option found */
|
||||||
|
if (long_options[match].has_arg == no_argument
|
||||||
|
&& has_equal) {
|
||||||
|
if (PRINT_ERROR)
|
||||||
|
warnx(noarg, (int)current_argv_len,
|
||||||
|
current_argv);
|
||||||
|
/*
|
||||||
|
* XXX: GNU sets optopt to val regardless of flag
|
||||||
|
*/
|
||||||
|
if (long_options[match].flag == NULL)
|
||||||
|
optopt = long_options[match].val;
|
||||||
|
else
|
||||||
|
optopt = 0;
|
||||||
|
return (BADARG);
|
||||||
|
}
|
||||||
|
if (long_options[match].has_arg == required_argument ||
|
||||||
|
long_options[match].has_arg == optional_argument) {
|
||||||
|
if (has_equal)
|
||||||
|
optarg = has_equal;
|
||||||
|
else if (long_options[match].has_arg ==
|
||||||
|
required_argument) {
|
||||||
|
/*
|
||||||
|
* optional argument doesn't use next nargv
|
||||||
|
*/
|
||||||
|
optarg = nargv[optind++];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if ((long_options[match].has_arg == required_argument)
|
||||||
|
&& (optarg == NULL)) {
|
||||||
|
/*
|
||||||
|
* Missing argument; leading ':' indicates no error
|
||||||
|
* should be generated.
|
||||||
|
*/
|
||||||
|
if (PRINT_ERROR)
|
||||||
|
warnx(recargstring,
|
||||||
|
current_argv);
|
||||||
|
/*
|
||||||
|
* XXX: GNU sets optopt to val regardless of flag
|
||||||
|
*/
|
||||||
|
if (long_options[match].flag == NULL)
|
||||||
|
optopt = long_options[match].val;
|
||||||
|
else
|
||||||
|
optopt = 0;
|
||||||
|
--optind;
|
||||||
|
return (BADARG);
|
||||||
|
}
|
||||||
|
} else { /* unknown option */
|
||||||
|
if (short_too) {
|
||||||
|
--optind;
|
||||||
|
return (-1);
|
||||||
|
}
|
||||||
|
if (PRINT_ERROR)
|
||||||
|
warnx(illoptstring, current_argv);
|
||||||
|
optopt = 0;
|
||||||
|
return (BADCH);
|
||||||
|
}
|
||||||
|
if (idx)
|
||||||
|
*idx = match;
|
||||||
|
if (long_options[match].flag) {
|
||||||
|
*long_options[match].flag = long_options[match].val;
|
||||||
|
return (0);
|
||||||
|
} else
|
||||||
|
return (long_options[match].val);
|
||||||
|
#undef IDENTICAL_INTERPRETATION
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* getopt_internal --
|
||||||
|
* Parse argc/argv argument vector. Called by user level routines.
|
||||||
|
*/
|
||||||
|
static int
|
||||||
|
getopt_internal(int nargc, char * const *nargv, const char *options,
|
||||||
|
const struct option *long_options, int *idx, int flags)
|
||||||
|
{
|
||||||
|
char *oli; /* option letter list index */
|
||||||
|
int optchar, short_too;
|
||||||
|
static int posixly_correct = -1;
|
||||||
|
|
||||||
|
if (options == NULL)
|
||||||
|
return (-1);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* XXX Some GNU programs (like cvs) set optind to 0 instead of
|
||||||
|
* XXX using optreset. Work around this braindamage.
|
||||||
|
*/
|
||||||
|
if (optind == 0)
|
||||||
|
optind = optreset = 1;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Disable GNU extensions if POSIXLY_CORRECT is set or options
|
||||||
|
* string begins with a '+'.
|
||||||
|
*
|
||||||
|
* CV, 2009-12-14: Check POSIXLY_CORRECT anew if optind == 0 or
|
||||||
|
* optreset != 0 for GNU compatibility.
|
||||||
|
*/
|
||||||
|
if (posixly_correct == -1 || optreset != 0)
|
||||||
|
posixly_correct = (GetEnvironmentVariableW(L"POSIXLY_CORRECT", NULL, 0) != 0);
|
||||||
|
if (*options == '-')
|
||||||
|
flags |= FLAG_ALLARGS;
|
||||||
|
else if (posixly_correct || *options == '+')
|
||||||
|
flags &= ~FLAG_PERMUTE;
|
||||||
|
if (*options == '+' || *options == '-')
|
||||||
|
options++;
|
||||||
|
|
||||||
|
optarg = NULL;
|
||||||
|
if (optreset)
|
||||||
|
nonopt_start = nonopt_end = -1;
|
||||||
|
start:
|
||||||
|
if (optreset || !*place) { /* update scanning pointer */
|
||||||
|
optreset = 0;
|
||||||
|
if (optind >= nargc) { /* end of argument vector */
|
||||||
|
place = EMSG;
|
||||||
|
if (nonopt_end != -1) {
|
||||||
|
/* do permutation, if we have to */
|
||||||
|
permute_args(nonopt_start, nonopt_end,
|
||||||
|
optind, nargv);
|
||||||
|
optind -= nonopt_end - nonopt_start;
|
||||||
|
}
|
||||||
|
else if (nonopt_start != -1) {
|
||||||
|
/*
|
||||||
|
* If we skipped non-options, set optind
|
||||||
|
* to the first of them.
|
||||||
|
*/
|
||||||
|
optind = nonopt_start;
|
||||||
|
}
|
||||||
|
nonopt_start = nonopt_end = -1;
|
||||||
|
return (-1);
|
||||||
|
}
|
||||||
|
if (*(place = nargv[optind]) != '-' ||
|
||||||
|
(place[1] == '\0' && strchr(options, '-') == NULL)) {
|
||||||
|
place = EMSG; /* found non-option */
|
||||||
|
if (flags & FLAG_ALLARGS) {
|
||||||
|
/*
|
||||||
|
* GNU extension:
|
||||||
|
* return non-option as argument to option 1
|
||||||
|
*/
|
||||||
|
optarg = nargv[optind++];
|
||||||
|
return (INORDER);
|
||||||
|
}
|
||||||
|
if (!(flags & FLAG_PERMUTE)) {
|
||||||
|
/*
|
||||||
|
* If no permutation wanted, stop parsing
|
||||||
|
* at first non-option.
|
||||||
|
*/
|
||||||
|
return (-1);
|
||||||
|
}
|
||||||
|
/* do permutation */
|
||||||
|
if (nonopt_start == -1)
|
||||||
|
nonopt_start = optind;
|
||||||
|
else if (nonopt_end != -1) {
|
||||||
|
permute_args(nonopt_start, nonopt_end,
|
||||||
|
optind, nargv);
|
||||||
|
nonopt_start = optind -
|
||||||
|
(nonopt_end - nonopt_start);
|
||||||
|
nonopt_end = -1;
|
||||||
|
}
|
||||||
|
optind++;
|
||||||
|
/* process next argument */
|
||||||
|
goto start;
|
||||||
|
}
|
||||||
|
if (nonopt_start != -1 && nonopt_end == -1)
|
||||||
|
nonopt_end = optind;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* If we have "-" do nothing, if "--" we are done.
|
||||||
|
*/
|
||||||
|
if (place[1] != '\0' && *++place == '-' && place[1] == '\0') {
|
||||||
|
optind++;
|
||||||
|
place = EMSG;
|
||||||
|
/*
|
||||||
|
* We found an option (--), so if we skipped
|
||||||
|
* non-options, we have to permute.
|
||||||
|
*/
|
||||||
|
if (nonopt_end != -1) {
|
||||||
|
permute_args(nonopt_start, nonopt_end,
|
||||||
|
optind, nargv);
|
||||||
|
optind -= nonopt_end - nonopt_start;
|
||||||
|
}
|
||||||
|
nonopt_start = nonopt_end = -1;
|
||||||
|
return (-1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Check long options if:
|
||||||
|
* 1) we were passed some
|
||||||
|
* 2) the arg is not just "-"
|
||||||
|
* 3) either the arg starts with -- we are getopt_long_only()
|
||||||
|
*/
|
||||||
|
if (long_options != NULL && place != nargv[optind] &&
|
||||||
|
(*place == '-' || (flags & FLAG_LONGONLY))) {
|
||||||
|
short_too = 0;
|
||||||
|
if (*place == '-')
|
||||||
|
place++; /* --foo long option */
|
||||||
|
else if (*place != ':' && strchr(options, *place) != NULL)
|
||||||
|
short_too = 1; /* could be short option too */
|
||||||
|
|
||||||
|
optchar = parse_long_options(nargv, options, long_options,
|
||||||
|
idx, short_too);
|
||||||
|
if (optchar != -1) {
|
||||||
|
place = EMSG;
|
||||||
|
return (optchar);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if ((optchar = (int)*place++) == (int)':' ||
|
||||||
|
(optchar == (int)'-' && *place != '\0') ||
|
||||||
|
(oli = strchr(options, optchar)) == NULL) {
|
||||||
|
/*
|
||||||
|
* If the user specified "-" and '-' isn't listed in
|
||||||
|
* options, return -1 (non-option) as per POSIX.
|
||||||
|
* Otherwise, it is an unknown option character (or ':').
|
||||||
|
*/
|
||||||
|
if (optchar == (int)'-' && *place == '\0')
|
||||||
|
return (-1);
|
||||||
|
if (!*place)
|
||||||
|
++optind;
|
||||||
|
if (PRINT_ERROR)
|
||||||
|
warnx(illoptchar, optchar);
|
||||||
|
optopt = optchar;
|
||||||
|
return (BADCH);
|
||||||
|
}
|
||||||
|
if (long_options != NULL && optchar == 'W' && oli[1] == ';') {
|
||||||
|
/* -W long-option */
|
||||||
|
if (*place) /* no space */
|
||||||
|
/* NOTHING */;
|
||||||
|
else if (++optind >= nargc) { /* no arg */
|
||||||
|
place = EMSG;
|
||||||
|
if (PRINT_ERROR)
|
||||||
|
warnx(recargchar, optchar);
|
||||||
|
optopt = optchar;
|
||||||
|
return (BADARG);
|
||||||
|
} else /* white space */
|
||||||
|
place = nargv[optind];
|
||||||
|
optchar = parse_long_options(nargv, options, long_options,
|
||||||
|
idx, 0);
|
||||||
|
place = EMSG;
|
||||||
|
return (optchar);
|
||||||
|
}
|
||||||
|
if (*++oli != ':') { /* doesn't take argument */
|
||||||
|
if (!*place)
|
||||||
|
++optind;
|
||||||
|
} else { /* takes (optional) argument */
|
||||||
|
optarg = NULL;
|
||||||
|
if (*place) /* no white space */
|
||||||
|
optarg = place;
|
||||||
|
else if (oli[1] != ':') { /* arg not optional */
|
||||||
|
if (++optind >= nargc) { /* no arg */
|
||||||
|
place = EMSG;
|
||||||
|
if (PRINT_ERROR)
|
||||||
|
warnx(recargchar, optchar);
|
||||||
|
optopt = optchar;
|
||||||
|
return (BADARG);
|
||||||
|
} else
|
||||||
|
optarg = nargv[optind];
|
||||||
|
}
|
||||||
|
place = EMSG;
|
||||||
|
++optind;
|
||||||
|
}
|
||||||
|
/* dump back option letter */
|
||||||
|
return (optchar);
|
||||||
|
}
|
||||||
|
|
||||||
|
#ifdef REPLACE_GETOPT
|
||||||
|
/*
|
||||||
|
* getopt --
|
||||||
|
* Parse argc/argv argument vector.
|
||||||
|
*
|
||||||
|
* [eventually this will replace the BSD getopt]
|
||||||
|
*/
|
||||||
|
int
|
||||||
|
getopt(int nargc, char * const *nargv, const char *options)
|
||||||
|
{
|
||||||
|
|
||||||
|
/*
|
||||||
|
* We don't pass FLAG_PERMUTE to getopt_internal() since
|
||||||
|
* the BSD getopt(3) (unlike GNU) has never done this.
|
||||||
|
*
|
||||||
|
* Furthermore, since many privileged programs call getopt()
|
||||||
|
* before dropping privileges it makes sense to keep things
|
||||||
|
* as simple (and bug-free) as possible.
|
||||||
|
*/
|
||||||
|
return (getopt_internal(nargc, nargv, options, NULL, NULL, 0));
|
||||||
|
}
|
||||||
|
#endif /* REPLACE_GETOPT */
|
||||||
|
|
||||||
|
/*
|
||||||
|
* getopt_long --
|
||||||
|
* Parse argc/argv argument vector.
|
||||||
|
*/
|
||||||
|
int
|
||||||
|
getopt_long(int nargc, char * const *nargv, const char *options,
|
||||||
|
const struct option *long_options, int *idx)
|
||||||
|
{
|
||||||
|
|
||||||
|
return (getopt_internal(nargc, nargv, options, long_options, idx,
|
||||||
|
FLAG_PERMUTE));
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* getopt_long_only --
|
||||||
|
* Parse argc/argv argument vector.
|
||||||
|
*/
|
||||||
|
int
|
||||||
|
getopt_long_only(int nargc, char * const *nargv, const char *options,
|
||||||
|
const struct option *long_options, int *idx)
|
||||||
|
{
|
||||||
|
|
||||||
|
return (getopt_internal(nargc, nargv, options, long_options, idx,
|
||||||
|
FLAG_PERMUTE|FLAG_LONGONLY));
|
||||||
|
}
|
||||||
95
3rdparty/getopt/getopt.h
vendored
Normal file
@@ -0,0 +1,95 @@
|
|||||||
|
#ifndef __GETOPT_H__
|
||||||
|
/**
|
||||||
|
* DISCLAIMER
|
||||||
|
* This file has no copyright assigned and is placed in the Public Domain.
|
||||||
|
* This file is part of the mingw-w64 runtime package.
|
||||||
|
*
|
||||||
|
* The mingw-w64 runtime package and its code is distributed in the hope that it
|
||||||
|
* will be useful but WITHOUT ANY WARRANTY. ALL WARRANTIES, EXPRESSED OR
|
||||||
|
* IMPLIED ARE HEREBY DISCLAIMED. This includes but is not limited to
|
||||||
|
* warranties of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#define __GETOPT_H__
|
||||||
|
|
||||||
|
/* All the headers include this file. */
|
||||||
|
#include <crtdefs.h>
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
extern "C" {
|
||||||
|
#endif
|
||||||
|
|
||||||
|
extern int optind; /* index of first non-option in argv */
|
||||||
|
extern int optopt; /* single option character, as parsed */
|
||||||
|
extern int opterr; /* flag to enable built-in diagnostics... */
|
||||||
|
/* (user may set to zero, to suppress) */
|
||||||
|
|
||||||
|
extern char *optarg; /* pointer to argument of current option */
|
||||||
|
|
||||||
|
extern int getopt(int nargc, char * const *nargv, const char *options);
|
||||||
|
|
||||||
|
#ifdef _BSD_SOURCE
|
||||||
|
/*
|
||||||
|
* BSD adds the non-standard `optreset' feature, for reinitialisation
|
||||||
|
* of `getopt' parsing. We support this feature, for applications which
|
||||||
|
* proclaim their BSD heritage, before including this header; however,
|
||||||
|
* to maintain portability, developers are advised to avoid it.
|
||||||
|
*/
|
||||||
|
# define optreset __mingw_optreset
|
||||||
|
extern int optreset;
|
||||||
|
#endif
|
||||||
|
#ifdef __cplusplus
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
/*
|
||||||
|
* POSIX requires the `getopt' API to be specified in `unistd.h';
|
||||||
|
* thus, `unistd.h' includes this header. However, we do not want
|
||||||
|
* to expose the `getopt_long' or `getopt_long_only' APIs, when
|
||||||
|
* included in this manner. Thus, close the standard __GETOPT_H__
|
||||||
|
* declarations block, and open an additional __GETOPT_LONG_H__
|
||||||
|
* specific block, only when *not* __UNISTD_H_SOURCED__, in which
|
||||||
|
* to declare the extended API.
|
||||||
|
*/
|
||||||
|
#endif /* !defined(__GETOPT_H__) */
|
||||||
|
|
||||||
|
#if !defined(__UNISTD_H_SOURCED__) && !defined(__GETOPT_LONG_H__)
|
||||||
|
#define __GETOPT_LONG_H__
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
extern "C" {
|
||||||
|
#endif
|
||||||
|
|
||||||
|
struct option /* specification for a long form option... */
|
||||||
|
{
|
||||||
|
const char *name; /* option name, without leading hyphens */
|
||||||
|
int has_arg; /* does it take an argument? */
|
||||||
|
int *flag; /* where to save its status, or NULL */
|
||||||
|
int val; /* its associated status value */
|
||||||
|
};
|
||||||
|
|
||||||
|
enum /* permitted values for its `has_arg' field... */
|
||||||
|
{
|
||||||
|
no_argument = 0, /* option never takes an argument */
|
||||||
|
required_argument, /* option always requires an argument */
|
||||||
|
optional_argument /* option may take an argument */
|
||||||
|
};
|
||||||
|
|
||||||
|
extern int getopt_long(int nargc, char * const *nargv, const char *options,
|
||||||
|
const struct option *long_options, int *idx);
|
||||||
|
extern int getopt_long_only(int nargc, char * const *nargv, const char *options,
|
||||||
|
const struct option *long_options, int *idx);
|
||||||
|
/*
|
||||||
|
* Previous MinGW implementation had...
|
||||||
|
*/
|
||||||
|
#ifndef HAVE_DECL_GETOPT
|
||||||
|
/*
|
||||||
|
* ...for the long form API only; keep this for compatibility.
|
||||||
|
*/
|
||||||
|
# define HAVE_DECL_GETOPT 1
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#endif /* !defined(__UNISTD_H_SOURCED__) && !defined(__GETOPT_LONG_H__) */
|
||||||
58
3rdparty/macdeployqt/main.cpp
vendored
@@ -64,7 +64,6 @@ int main(int argc, char **argv)
|
|||||||
qDebug() << " -appstore-compliant : Skip deployment of components that use private API";
|
qDebug() << " -appstore-compliant : Skip deployment of components that use private API";
|
||||||
qDebug() << " -libpath=<path> : Add the given path to the library search path";
|
qDebug() << " -libpath=<path> : Add the given path to the library search path";
|
||||||
qDebug() << " -fs=<filesystem> : Set the filesystem used for the .dmg disk image (defaults to HFS+)";
|
qDebug() << " -fs=<filesystem> : Set the filesystem used for the .dmg disk image (defaults to HFS+)";
|
||||||
qDebug() << " -plugins-dir=<path> : Set plugins directory";
|
|
||||||
qDebug() << "";
|
qDebug() << "";
|
||||||
qDebug() << "macdeployqt takes an application bundle as input and makes it";
|
qDebug() << "macdeployqt takes an application bundle as input and makes it";
|
||||||
qDebug() << "self-contained by copying in the Qt frameworks and plugins that";
|
qDebug() << "self-contained by copying in the Qt frameworks and plugins that";
|
||||||
@@ -88,7 +87,7 @@ int main(int argc, char **argv)
|
|||||||
|
|
||||||
appBundlePath = QDir::cleanPath(appBundlePath);
|
appBundlePath = QDir::cleanPath(appBundlePath);
|
||||||
|
|
||||||
if (QDir().exists(appBundlePath) == false) {
|
if (!QDir(appBundlePath).exists()) {
|
||||||
qDebug() << "Error: Could not find app bundle" << appBundlePath;
|
qDebug() << "Error: Could not find app bundle" << appBundlePath;
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
@@ -110,7 +109,6 @@ int main(int argc, char **argv)
|
|||||||
extern bool appstoreCompliant;
|
extern bool appstoreCompliant;
|
||||||
extern bool deployFramework;
|
extern bool deployFramework;
|
||||||
extern bool secureTimestamp;
|
extern bool secureTimestamp;
|
||||||
QString plugin_dir;
|
|
||||||
|
|
||||||
for (int i = 2; i < argc; ++i) {
|
for (int i = 2; i < argc; ++i) {
|
||||||
QByteArray argument = QByteArray(argv[i]);
|
QByteArray argument = QByteArray(argv[i]);
|
||||||
@@ -198,7 +196,7 @@ int main(int argc, char **argv)
|
|||||||
LogDebug() << "Argument found:" << argument;
|
LogDebug() << "Argument found:" << argument;
|
||||||
appstoreCompliant = true;
|
appstoreCompliant = true;
|
||||||
|
|
||||||
// Undocumented option, may not work as intented
|
// Undocumented option, may not work as intended
|
||||||
} else if (argument == QByteArray("-deploy-framework")) {
|
} else if (argument == QByteArray("-deploy-framework")) {
|
||||||
LogDebug() << "Argument found:" << argument;
|
LogDebug() << "Argument found:" << argument;
|
||||||
deployFramework = true;
|
deployFramework = true;
|
||||||
@@ -210,13 +208,6 @@ int main(int argc, char **argv)
|
|||||||
LogError() << "Missing filesystem type";
|
LogError() << "Missing filesystem type";
|
||||||
else
|
else
|
||||||
filesystem = argument.mid(index+1);
|
filesystem = argument.mid(index+1);
|
||||||
} else if (argument.startsWith(QByteArray("-plugins-dir"))) {
|
|
||||||
LogDebug() << "Argument found:" << argument;
|
|
||||||
int index = argument.indexOf('=');
|
|
||||||
if (index == -1)
|
|
||||||
LogError() << "Missing filesystem type";
|
|
||||||
else
|
|
||||||
plugin_dir = argument.mid(index+1);
|
|
||||||
} else if (argument.startsWith("-")) {
|
} else if (argument.startsWith("-")) {
|
||||||
LogError() << "Unknown argument" << argument << "\n";
|
LogError() << "Unknown argument" << argument << "\n";
|
||||||
return 1;
|
return 1;
|
||||||
@@ -231,7 +222,7 @@ int main(int argc, char **argv)
|
|||||||
if (deployFramework && deploymentInfo.isFramework)
|
if (deployFramework && deploymentInfo.isFramework)
|
||||||
fixupFramework(appBundlePath);
|
fixupFramework(appBundlePath);
|
||||||
|
|
||||||
// Convenience: Look for .qml files in the current directoty if no -qmldir specified.
|
// Convenience: Look for .qml files in the current directory if no -qmldir specified.
|
||||||
if (qmlDirs.isEmpty()) {
|
if (qmlDirs.isEmpty()) {
|
||||||
QDir dir;
|
QDir dir;
|
||||||
if (!dir.entryList(QStringList() << QStringLiteral("*.qml")).isEmpty()) {
|
if (!dir.entryList(QStringList() << QStringLiteral("*.qml")).isEmpty()) {
|
||||||
@@ -252,40 +243,37 @@ int main(int argc, char **argv)
|
|||||||
deploymentInfo.deployedFrameworks.end()).values();
|
deploymentInfo.deployedFrameworks.end()).values();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Handle plugins
|
||||||
if (plugins) {
|
if (plugins) {
|
||||||
if (plugin_dir.isEmpty()) {
|
// Set the plugins search directory
|
||||||
#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
|
#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
|
||||||
deploymentInfo.pluginPath = QLibraryInfo::path(QLibraryInfo::PluginsPath);
|
deploymentInfo.pluginPath = QLibraryInfo::path(QLibraryInfo::PluginsPath);
|
||||||
#else
|
#else
|
||||||
deploymentInfo.pluginPath = QLibraryInfo::location(QLibraryInfo::PluginsPath);
|
deploymentInfo.pluginPath = QLibraryInfo::location(QLibraryInfo::PluginsPath);
|
||||||
#endif
|
#endif
|
||||||
}
|
// Sanity checks
|
||||||
else {
|
if (deploymentInfo.pluginPath.isEmpty()) {
|
||||||
deploymentInfo.pluginPath = plugin_dir;
|
LogError() << "Missing Qt plugins path\n";
|
||||||
}
|
return 1;
|
||||||
if (deploymentInfo.pluginPath.isEmpty()) {
|
}
|
||||||
LogError() << "Missing Qt plugins path\n";
|
|
||||||
return 1;
|
if (!QDir(deploymentInfo.pluginPath).exists()) {
|
||||||
}
|
LogError() << "Plugins path does not exist" << deploymentInfo.pluginPath << "\n";
|
||||||
if (!QDir(deploymentInfo.pluginPath).exists()) {
|
return 1;
|
||||||
LogError() << "Plugins path does not exist\n" << deploymentInfo.pluginPath;
|
}
|
||||||
return 1;
|
|
||||||
}
|
// Deploy plugins
|
||||||
Q_ASSERT(!deploymentInfo.pluginPath.isEmpty());
|
Q_ASSERT(!deploymentInfo.pluginPath.isEmpty());
|
||||||
if (!deploymentInfo.pluginPath.isEmpty()) {
|
if (!deploymentInfo.pluginPath.isEmpty()) {
|
||||||
LogNormal();
|
LogNormal();
|
||||||
deployPlugins(appBundlePath, deploymentInfo, useDebugLibs);
|
deployPlugins(appBundlePath, deploymentInfo, useDebugLibs);
|
||||||
createQtConf(appBundlePath);
|
createQtConf(appBundlePath);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (runStripEnabled)
|
if (runStripEnabled)
|
||||||
stripAppBinary(appBundlePath);
|
stripAppBinary(appBundlePath);
|
||||||
|
|
||||||
if (!FinalCheck(appBundlePath)) {
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (runCodesign)
|
if (runCodesign)
|
||||||
codesign(codesignIdentiy, appBundlePath);
|
codesign(codesignIdentiy, appBundlePath);
|
||||||
|
|
||||||
|
|||||||
414
3rdparty/macdeployqt/shared.cpp
vendored
@@ -35,9 +35,11 @@
|
|||||||
#include <QStringList>
|
#include <QStringList>
|
||||||
#include <QDebug>
|
#include <QDebug>
|
||||||
#include <iostream>
|
#include <iostream>
|
||||||
|
#include <utility>
|
||||||
#include <QProcess>
|
#include <QProcess>
|
||||||
#include <QDir>
|
#include <QDir>
|
||||||
#include <QSet>
|
#include <QSet>
|
||||||
|
#include <QList>
|
||||||
#include <QStack>
|
#include <QStack>
|
||||||
#include <QDirIterator>
|
#include <QDirIterator>
|
||||||
#include <QLibraryInfo>
|
#include <QLibraryInfo>
|
||||||
@@ -102,7 +104,7 @@ inline QDebug operator<<(QDebug debug, const ApplicationBundleInfo &info)
|
|||||||
|
|
||||||
bool copyFilePrintStatus(const QString &from, const QString &to)
|
bool copyFilePrintStatus(const QString &from, const QString &to)
|
||||||
{
|
{
|
||||||
if (QFile(to).exists()) {
|
if (QFile::exists(to)) {
|
||||||
if (alwaysOwerwriteEnabled) {
|
if (alwaysOwerwriteEnabled) {
|
||||||
QFile(to).remove();
|
QFile(to).remove();
|
||||||
} else {
|
} else {
|
||||||
@@ -139,7 +141,7 @@ bool copyFilePrintStatus(const QString &from, const QString &to)
|
|||||||
|
|
||||||
bool linkFilePrintStatus(const QString &file, const QString &link)
|
bool linkFilePrintStatus(const QString &file, const QString &link)
|
||||||
{
|
{
|
||||||
if (QFile(link).exists()) {
|
if (QFile::exists(link)) {
|
||||||
if (QFile(link).symLinkTarget().isEmpty())
|
if (QFile(link).symLinkTarget().isEmpty())
|
||||||
LogError() << link << "exists but it's a file.";
|
LogError() << link << "exists but it's a file.";
|
||||||
else
|
else
|
||||||
@@ -197,22 +199,19 @@ OtoolInfo findDependencyInfo(const QString &binaryPath)
|
|||||||
}
|
}
|
||||||
|
|
||||||
outputLines.removeFirst(); // remove line containing the binary path
|
outputLines.removeFirst(); // remove line containing the binary path
|
||||||
|
|
||||||
if (binaryPath.contains(".framework/") || binaryPath.endsWith(".dylib")) {
|
if (binaryPath.contains(".framework/") || binaryPath.endsWith(".dylib")) {
|
||||||
const auto match = regexp.match(outputLines.first());
|
const auto match = regexp.match(outputLines.first());
|
||||||
if (match.hasMatch()) {
|
if (match.hasMatch()) {
|
||||||
QString installname = match.captured(1);
|
QString installname = match.captured(1);
|
||||||
if (QFileInfo(binaryPath).fileName() == QFileInfo(installname).fileName()) {
|
if (QFileInfo(binaryPath).fileName() == QFileInfo(installname).fileName()) {
|
||||||
info.installName = installname;
|
info.installName = installname;
|
||||||
info.compatibilityVersion = QVersionNumber::fromString(match.captured(2));
|
info.compatibilityVersion = QVersionNumber::fromString(match.captured(2));
|
||||||
info.currentVersion = QVersionNumber::fromString(match.captured(3));
|
info.currentVersion = QVersionNumber::fromString(match.captured(3));
|
||||||
outputLines.removeFirst();
|
outputLines.removeFirst();
|
||||||
|
} else {
|
||||||
|
info.installName = binaryPath;
|
||||||
}
|
}
|
||||||
else {
|
} else {
|
||||||
info.installName = binaryPath;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
LogError() << "Could not parse otool output line:" << outputLines.first();
|
LogError() << "Could not parse otool output line:" << outputLines.first();
|
||||||
outputLines.removeFirst();
|
outputLines.removeFirst();
|
||||||
}
|
}
|
||||||
@@ -234,7 +233,7 @@ OtoolInfo findDependencyInfo(const QString &binaryPath)
|
|||||||
return info;
|
return info;
|
||||||
}
|
}
|
||||||
|
|
||||||
FrameworkInfo parseOtoolLibraryLine(const QString &line, const QString &appBundlePath, const QSet<QString> &rpaths, bool useDebugLibs)
|
FrameworkInfo parseOtoolLibraryLine(const QString &line, const QString &appBundlePath, const QList<QString> &rpaths, bool useDebugLibs)
|
||||||
{
|
{
|
||||||
FrameworkInfo info;
|
FrameworkInfo info;
|
||||||
QString trimmed = line.trimmed();
|
QString trimmed = line.trimmed();
|
||||||
@@ -252,7 +251,7 @@ FrameworkInfo parseOtoolLibraryLine(const QString &line, const QString &appBundl
|
|||||||
if (trimmed.startsWith("@rpath/")) {
|
if (trimmed.startsWith("@rpath/")) {
|
||||||
QString rpathRelativePath = trimmed.mid(QStringLiteral("@rpath/").length());
|
QString rpathRelativePath = trimmed.mid(QStringLiteral("@rpath/").length());
|
||||||
bool foundInsideBundle = false;
|
bool foundInsideBundle = false;
|
||||||
foreach (const QString &rpath, rpaths) {
|
for (const QString &rpath : std::as_const(rpaths)) {
|
||||||
QString path = QDir::cleanPath(rpath + "/" + rpathRelativePath);
|
QString path = QDir::cleanPath(rpath + "/" + rpathRelativePath);
|
||||||
// Skip paths already inside the bundle.
|
// Skip paths already inside the bundle.
|
||||||
if (!appBundlePath.isEmpty()) {
|
if (!appBundlePath.isEmpty()) {
|
||||||
@@ -293,7 +292,7 @@ FrameworkInfo parseOtoolLibraryLine(const QString &line, const QString &appBundl
|
|||||||
// Split the line into [Qt-path]/lib/qt[Module].framework/Versions/[Version]/
|
// Split the line into [Qt-path]/lib/qt[Module].framework/Versions/[Version]/
|
||||||
QStringList parts = trimmed.split("/");
|
QStringList parts = trimmed.split("/");
|
||||||
while (part < parts.count()) {
|
while (part < parts.count()) {
|
||||||
const QString currentPart = parts.at(part).simplified() ;
|
const QString currentPart = parts.at(part).simplified();
|
||||||
++part;
|
++part;
|
||||||
if (currentPart == "")
|
if (currentPart == "")
|
||||||
continue;
|
continue;
|
||||||
@@ -311,7 +310,7 @@ FrameworkInfo parseOtoolLibraryLine(const QString &line, const QString &appBundl
|
|||||||
} else if (trimmed.startsWith("/") == false) { // If the line does not contain a full path, the app is using a binary Qt package.
|
} else if (trimmed.startsWith("/") == false) { // If the line does not contain a full path, the app is using a binary Qt package.
|
||||||
QStringList partsCopy = parts;
|
QStringList partsCopy = parts;
|
||||||
partsCopy.removeLast();
|
partsCopy.removeLast();
|
||||||
foreach (QString path, librarySearchPath) {
|
for (QString &path : librarySearchPath) {
|
||||||
if (!path.endsWith("/"))
|
if (!path.endsWith("/"))
|
||||||
path += '/';
|
path += '/';
|
||||||
QString nameInPath = path + parts.join(QLatin1Char('/'));
|
QString nameInPath = path + parts.join(QLatin1Char('/'));
|
||||||
@@ -505,7 +504,7 @@ QString findEntitlementsFile(const QString& path)
|
|||||||
return QString();
|
return QString();
|
||||||
}
|
}
|
||||||
|
|
||||||
QList<FrameworkInfo> getQtFrameworks(const QList<DylibInfo> &dependencies, const QString &appBundlePath, const QSet<QString> &rpaths, bool useDebugLibs)
|
QList<FrameworkInfo> getQtFrameworks(const QList<DylibInfo> &dependencies, const QString &appBundlePath, const QList<QString> &rpaths, bool useDebugLibs)
|
||||||
{
|
{
|
||||||
QList<FrameworkInfo> libraries;
|
QList<FrameworkInfo> libraries;
|
||||||
for (const DylibInfo &dylibInfo : dependencies) {
|
for (const DylibInfo &dylibInfo : dependencies) {
|
||||||
@@ -545,9 +544,9 @@ QString resolveDyldPrefix(const QString &path, const QString &loaderPath, const
|
|||||||
return path;
|
return path;
|
||||||
}
|
}
|
||||||
|
|
||||||
QSet<QString> getBinaryRPaths(const QString &path, bool resolve = true, QString executablePath = QString())
|
QList<QString> getBinaryRPaths(const QString &path, bool resolve = true, QString executablePath = QString())
|
||||||
{
|
{
|
||||||
QSet<QString> rpaths;
|
QList<QString> rpaths;
|
||||||
|
|
||||||
QProcess otool;
|
QProcess otool;
|
||||||
otool.start("otool", QStringList() << "-l" << path);
|
otool.start("otool", QStringList() << "-l" << path);
|
||||||
@@ -584,18 +583,20 @@ QSet<QString> getBinaryRPaths(const QString &path, bool resolve = true, QString
|
|||||||
return rpaths;
|
return rpaths;
|
||||||
}
|
}
|
||||||
|
|
||||||
QList<FrameworkInfo> getQtFrameworks(const QString &path, const QString &appBundlePath, const QSet<QString> &rpaths, bool useDebugLibs)
|
QList<FrameworkInfo> getQtFrameworks(const QString &path, const QString &appBundlePath, const QList<QString> &rpaths, bool useDebugLibs)
|
||||||
{
|
{
|
||||||
const OtoolInfo info = findDependencyInfo(path);
|
const OtoolInfo info = findDependencyInfo(path);
|
||||||
return getQtFrameworks(info.dependencies, appBundlePath, rpaths + getBinaryRPaths(path), useDebugLibs);
|
QList<QString> allRPaths = rpaths + getBinaryRPaths(path);
|
||||||
|
allRPaths.removeDuplicates();
|
||||||
|
return getQtFrameworks(info.dependencies, appBundlePath, allRPaths, useDebugLibs);
|
||||||
}
|
}
|
||||||
|
|
||||||
QList<FrameworkInfo> getQtFrameworksForPaths(const QStringList &paths, const QString &appBundlePath, const QSet<QString> &rpaths, bool useDebugLibs)
|
QList<FrameworkInfo> getQtFrameworksForPaths(const QStringList &paths, const QString &appBundlePath, const QList<QString> &rpaths, bool useDebugLibs)
|
||||||
{
|
{
|
||||||
QList<FrameworkInfo> result;
|
QList<FrameworkInfo> result;
|
||||||
QSet<QString> existing;
|
QSet<QString> existing;
|
||||||
foreach (const QString &path, paths) {
|
for (const QString &path : paths) {
|
||||||
foreach (const FrameworkInfo &info, getQtFrameworks(path, appBundlePath, rpaths, useDebugLibs)) {
|
for (const FrameworkInfo &info : getQtFrameworks(path, appBundlePath, rpaths, useDebugLibs)) {
|
||||||
if (!existing.contains(info.frameworkPath)) { // avoid duplicates
|
if (!existing.contains(info.frameworkPath)) { // avoid duplicates
|
||||||
existing.insert(info.frameworkPath);
|
existing.insert(info.frameworkPath);
|
||||||
result << info;
|
result << info;
|
||||||
@@ -614,10 +615,10 @@ QStringList getBinaryDependencies(const QString executablePath,
|
|||||||
const auto dependencies = findDependencyInfo(path).dependencies;
|
const auto dependencies = findDependencyInfo(path).dependencies;
|
||||||
|
|
||||||
bool rpathsLoaded = false;
|
bool rpathsLoaded = false;
|
||||||
QSet<QString> rpaths;
|
QList<QString> rpaths;
|
||||||
|
|
||||||
// return bundle-local dependencies. (those starting with @executable_path)
|
// return bundle-local dependencies. (those starting with @executable_path)
|
||||||
foreach (const DylibInfo &info, dependencies) {
|
for (const DylibInfo &info : dependencies) {
|
||||||
QString trimmedLine = info.binaryPath;
|
QString trimmedLine = info.binaryPath;
|
||||||
if (trimmedLine.startsWith("@executable_path/")) {
|
if (trimmedLine.startsWith("@executable_path/")) {
|
||||||
QString binary = QDir::cleanPath(executablePath + trimmedLine.mid(QStringLiteral("@executable_path/").length()));
|
QString binary = QDir::cleanPath(executablePath + trimmedLine.mid(QStringLiteral("@executable_path/").length()));
|
||||||
@@ -626,14 +627,14 @@ QStringList getBinaryDependencies(const QString executablePath,
|
|||||||
} else if (trimmedLine.startsWith("@rpath/")) {
|
} else if (trimmedLine.startsWith("@rpath/")) {
|
||||||
if (!rpathsLoaded) {
|
if (!rpathsLoaded) {
|
||||||
rpaths = getBinaryRPaths(path, true, executablePath);
|
rpaths = getBinaryRPaths(path, true, executablePath);
|
||||||
foreach (const QString &binaryPath, additionalBinariesContainingRpaths) {
|
for (const QString &binaryPath : additionalBinariesContainingRpaths) {
|
||||||
QSet<QString> binaryRpaths = getBinaryRPaths(binaryPath, true);
|
rpaths += getBinaryRPaths(binaryPath, true);
|
||||||
rpaths += binaryRpaths;
|
|
||||||
}
|
}
|
||||||
|
rpaths.removeDuplicates();
|
||||||
rpathsLoaded = true;
|
rpathsLoaded = true;
|
||||||
}
|
}
|
||||||
bool resolved = false;
|
bool resolved = false;
|
||||||
foreach (const QString &rpath, rpaths) {
|
for (const QString &rpath : std::as_const(rpaths)) {
|
||||||
QString binary = QDir::cleanPath(rpath + "/" + trimmedLine.mid(QStringLiteral("@rpath/").length()));
|
QString binary = QDir::cleanPath(rpath + "/" + trimmedLine.mid(QStringLiteral("@rpath/").length()));
|
||||||
LogDebug() << "Checking for" << binary;
|
LogDebug() << "Checking for" << binary;
|
||||||
if (QFile::exists(binary)) {
|
if (QFile::exists(binary)) {
|
||||||
@@ -662,20 +663,20 @@ bool recursiveCopy(const QString &sourcePath, const QString &destinationPath)
|
|||||||
LogNormal() << "copy:" << sourcePath << destinationPath;
|
LogNormal() << "copy:" << sourcePath << destinationPath;
|
||||||
|
|
||||||
QStringList files = QDir(sourcePath).entryList(QStringList() << "*", QDir::Files | QDir::NoDotAndDotDot);
|
QStringList files = QDir(sourcePath).entryList(QStringList() << "*", QDir::Files | QDir::NoDotAndDotDot);
|
||||||
foreach (QString file, files) {
|
for (const QString &file : files) {
|
||||||
const QString fileSourcePath = sourcePath + "/" + file;
|
const QString fileSourcePath = sourcePath + "/" + file;
|
||||||
const QString fileDestinationPath = destinationPath + "/" + file;
|
const QString fileDestinationPath = destinationPath + "/" + file;
|
||||||
copyFilePrintStatus(fileSourcePath, fileDestinationPath);
|
copyFilePrintStatus(fileSourcePath, fileDestinationPath);
|
||||||
}
|
}
|
||||||
|
|
||||||
QStringList subdirs = QDir(sourcePath).entryList(QStringList() << "*", QDir::Dirs | QDir::NoDotAndDotDot);
|
QStringList subdirs = QDir(sourcePath).entryList(QStringList() << "*", QDir::Dirs | QDir::NoDotAndDotDot);
|
||||||
foreach (QString dir, subdirs) {
|
for (const QString &dir : subdirs) {
|
||||||
recursiveCopy(sourcePath + "/" + dir, destinationPath + "/" + dir);
|
recursiveCopy(sourcePath + "/" + dir, destinationPath + "/" + dir);
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
void recursiveCopyAndDeploy(const QString &appBundlePath, const QSet<QString> &rpaths, const QString &sourcePath, const QString &destinationPath)
|
void recursiveCopyAndDeploy(const QString &appBundlePath, const QList<QString> &rpaths, const QString &sourcePath, const QString &destinationPath)
|
||||||
{
|
{
|
||||||
QDir().mkpath(destinationPath);
|
QDir().mkpath(destinationPath);
|
||||||
|
|
||||||
@@ -683,7 +684,7 @@ void recursiveCopyAndDeploy(const QString &appBundlePath, const QSet<QString> &r
|
|||||||
const bool isDwarfPath = sourcePath.endsWith("DWARF");
|
const bool isDwarfPath = sourcePath.endsWith("DWARF");
|
||||||
|
|
||||||
QStringList files = QDir(sourcePath).entryList(QStringList() << QStringLiteral("*"), QDir::Files | QDir::NoDotAndDotDot);
|
QStringList files = QDir(sourcePath).entryList(QStringList() << QStringLiteral("*"), QDir::Files | QDir::NoDotAndDotDot);
|
||||||
foreach (QString file, files) {
|
for (const QString &file : files) {
|
||||||
const QString fileSourcePath = sourcePath + QLatin1Char('/') + file;
|
const QString fileSourcePath = sourcePath + QLatin1Char('/') + file;
|
||||||
|
|
||||||
if (file.endsWith("_debug.dylib")) {
|
if (file.endsWith("_debug.dylib")) {
|
||||||
@@ -729,7 +730,7 @@ void recursiveCopyAndDeploy(const QString &appBundlePath, const QSet<QString> &r
|
|||||||
}
|
}
|
||||||
|
|
||||||
QStringList subdirs = QDir(sourcePath).entryList(QStringList() << QStringLiteral("*"), QDir::Dirs | QDir::NoDotAndDotDot);
|
QStringList subdirs = QDir(sourcePath).entryList(QStringList() << QStringLiteral("*"), QDir::Dirs | QDir::NoDotAndDotDot);
|
||||||
foreach (QString dir, subdirs) {
|
for (const QString &dir : subdirs) {
|
||||||
recursiveCopyAndDeploy(appBundlePath, rpaths, sourcePath + QLatin1Char('/') + dir, destinationPath + QLatin1Char('/') + dir);
|
recursiveCopyAndDeploy(appBundlePath, rpaths, sourcePath + QLatin1Char('/') + dir, destinationPath + QLatin1Char('/') + dir);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -752,8 +753,8 @@ QString copyDylib(const FrameworkInfo &framework, const QString path)
|
|||||||
return QString();
|
return QString();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Retrun if the dylib has aleardy been deployed
|
// Return if the dylib has already been deployed
|
||||||
if (QFileInfo(dylibDestinationBinaryPath).exists() && !alwaysOwerwriteEnabled)
|
if (QFileInfo::exists(dylibDestinationBinaryPath) && !alwaysOwerwriteEnabled)
|
||||||
return dylibDestinationBinaryPath;
|
return dylibDestinationBinaryPath;
|
||||||
|
|
||||||
// Copy dylib binary
|
// Copy dylib binary
|
||||||
@@ -786,13 +787,13 @@ QString copyFramework(const FrameworkInfo &framework, const QString path)
|
|||||||
|
|
||||||
// Now copy the framework. Some parts should be left out (headers/, .prl files).
|
// Now copy the framework. Some parts should be left out (headers/, .prl files).
|
||||||
// Some parts should be included (Resources/, symlink structure). We want this
|
// Some parts should be included (Resources/, symlink structure). We want this
|
||||||
// function to make as few assumtions about the framework as possible while at
|
// function to make as few assumptions about the framework as possible while at
|
||||||
// the same time producing a codesign-compatible framework.
|
// the same time producing a codesign-compatible framework.
|
||||||
|
|
||||||
// Copy framework binary
|
// Copy framework binary
|
||||||
copyFilePrintStatus(framework.sourceFilePath, frameworkDestinationBinaryPath);
|
copyFilePrintStatus(framework.sourceFilePath, frameworkDestinationBinaryPath);
|
||||||
|
|
||||||
// Copy Resouces/, Libraries/ and Helpers/
|
// Copy Resources/, Libraries/ and Helpers/
|
||||||
const QString resourcesSourcePath = framework.frameworkPath + "/Resources";
|
const QString resourcesSourcePath = framework.frameworkPath + "/Resources";
|
||||||
const QString resourcesDestianationPath = frameworkDestinationDirectory + "/Versions/" + framework.version + "/Resources";
|
const QString resourcesDestianationPath = frameworkDestinationDirectory + "/Versions/" + framework.version + "/Resources";
|
||||||
recursiveCopy(resourcesSourcePath, resourcesDestianationPath);
|
recursiveCopy(resourcesSourcePath, resourcesDestianationPath);
|
||||||
@@ -820,7 +821,7 @@ QString copyFramework(const FrameworkInfo &framework, const QString path)
|
|||||||
// Contents/Info.plist should be Versions/5/Resources/Info.plist
|
// Contents/Info.plist should be Versions/5/Resources/Info.plist
|
||||||
const QString legacyInfoPlistPath = framework.frameworkPath + "/Contents/Info.plist";
|
const QString legacyInfoPlistPath = framework.frameworkPath + "/Contents/Info.plist";
|
||||||
const QString correctInfoPlistPath = frameworkDestinationDirectory + "/Resources/Info.plist";
|
const QString correctInfoPlistPath = frameworkDestinationDirectory + "/Resources/Info.plist";
|
||||||
if (QFile(legacyInfoPlistPath).exists()) {
|
if (QFile::exists(legacyInfoPlistPath)) {
|
||||||
copyFilePrintStatus(legacyInfoPlistPath, correctInfoPlistPath);
|
copyFilePrintStatus(legacyInfoPlistPath, correctInfoPlistPath);
|
||||||
patch_debugInInfoPlist(correctInfoPlistPath);
|
patch_debugInInfoPlist(correctInfoPlistPath);
|
||||||
}
|
}
|
||||||
@@ -850,7 +851,7 @@ void changeIdentification(const QString &id, const QString &binaryPath)
|
|||||||
void changeInstallName(const QString &bundlePath, const FrameworkInfo &framework, const QStringList &binaryPaths, bool useLoaderPath)
|
void changeInstallName(const QString &bundlePath, const FrameworkInfo &framework, const QStringList &binaryPaths, bool useLoaderPath)
|
||||||
{
|
{
|
||||||
const QString absBundlePath = QFileInfo(bundlePath).absoluteFilePath();
|
const QString absBundlePath = QFileInfo(bundlePath).absoluteFilePath();
|
||||||
foreach (const QString &binary, binaryPaths) {
|
for (const QString &binary : binaryPaths) {
|
||||||
QString deployedInstallName;
|
QString deployedInstallName;
|
||||||
if (useLoaderPath) {
|
if (useLoaderPath) {
|
||||||
deployedInstallName = QLatin1String("@loader_path/")
|
deployedInstallName = QLatin1String("@loader_path/")
|
||||||
@@ -858,17 +859,11 @@ void changeInstallName(const QString &bundlePath, const FrameworkInfo &framework
|
|||||||
} else {
|
} else {
|
||||||
deployedInstallName = framework.deployedInstallName;
|
deployedInstallName = framework.deployedInstallName;
|
||||||
}
|
}
|
||||||
changeInstallName(framework.installName, deployedInstallName, binary);
|
if (!framework.sourceFilePath.isEmpty()) {
|
||||||
// Workaround for the case when the library ID name is a symlink, while the dependencies
|
changeInstallName(framework.sourceFilePath, deployedInstallName, binary);
|
||||||
// specified using the canonical path to the library (QTBUG-56814)
|
|
||||||
QString canonicalInstallName = QFileInfo(framework.installName).canonicalFilePath();
|
|
||||||
if (!canonicalInstallName.isEmpty() && canonicalInstallName != framework.installName) {
|
|
||||||
changeInstallName(canonicalInstallName, deployedInstallName, binary);
|
|
||||||
}
|
}
|
||||||
// Homebrew workaround, resolve symlink /usr/local/opt/library to /usr/local/Cellar/library
|
if (!framework.installName.isEmpty() && framework.installName != framework.sourceFilePath) {
|
||||||
if (framework.installName.startsWith("/usr/local/opt/") && framework.installName.count('/') >= 5) {
|
changeInstallName(framework.installName, deployedInstallName, binary);
|
||||||
canonicalInstallName = QFileInfo(framework.installName.section('/', 0, 4)).canonicalFilePath() + "/" + framework.installName.section('/', 5);
|
|
||||||
changeInstallName(canonicalInstallName, deployedInstallName, binary);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -878,7 +873,7 @@ void addRPath(const QString &rpath, const QString &binaryPath)
|
|||||||
runInstallNameTool(QStringList() << "-add_rpath" << rpath << binaryPath);
|
runInstallNameTool(QStringList() << "-add_rpath" << rpath << binaryPath);
|
||||||
}
|
}
|
||||||
|
|
||||||
void deployRPaths(const QString &bundlePath, const QSet<QString> &rpaths, const QString &binaryPath, bool useLoaderPath)
|
void deployRPaths(const QString &bundlePath, const QList<QString> &rpaths, const QString &binaryPath, bool useLoaderPath)
|
||||||
{
|
{
|
||||||
const QString absFrameworksPath = QFileInfo(bundlePath).absoluteFilePath()
|
const QString absFrameworksPath = QFileInfo(bundlePath).absoluteFilePath()
|
||||||
+ QLatin1String("/Contents/Frameworks");
|
+ QLatin1String("/Contents/Frameworks");
|
||||||
@@ -886,7 +881,8 @@ void deployRPaths(const QString &bundlePath, const QSet<QString> &rpaths, const
|
|||||||
const QString loaderPathToFrameworks = QLatin1String("@loader_path/") + relativeFrameworkPath;
|
const QString loaderPathToFrameworks = QLatin1String("@loader_path/") + relativeFrameworkPath;
|
||||||
bool rpathToFrameworksFound = false;
|
bool rpathToFrameworksFound = false;
|
||||||
QStringList args;
|
QStringList args;
|
||||||
foreach (const QString &rpath, getBinaryRPaths(binaryPath, false)) {
|
QList<QString> binaryRPaths = getBinaryRPaths(binaryPath, false);
|
||||||
|
for (const QString &rpath : std::as_const(binaryRPaths)) {
|
||||||
if (rpath == "@executable_path/../Frameworks" ||
|
if (rpath == "@executable_path/../Frameworks" ||
|
||||||
rpath == loaderPathToFrameworks) {
|
rpath == loaderPathToFrameworks) {
|
||||||
rpathToFrameworksFound = true;
|
rpathToFrameworksFound = true;
|
||||||
@@ -912,9 +908,9 @@ void deployRPaths(const QString &bundlePath, const QSet<QString> &rpaths, const
|
|||||||
runInstallNameTool(QStringList() << args << binaryPath);
|
runInstallNameTool(QStringList() << args << binaryPath);
|
||||||
}
|
}
|
||||||
|
|
||||||
void deployRPaths(const QString &bundlePath, const QSet<QString> &rpaths, const QStringList &binaryPaths, bool useLoaderPath)
|
void deployRPaths(const QString &bundlePath, const QList<QString> &rpaths, const QStringList &binaryPaths, bool useLoaderPath)
|
||||||
{
|
{
|
||||||
foreach (const QString &binary, binaryPaths) {
|
for (const QString &binary : binaryPaths) {
|
||||||
deployRPaths(bundlePath, rpaths, binary, useLoaderPath);
|
deployRPaths(bundlePath, rpaths, binary, useLoaderPath);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -980,7 +976,7 @@ DeploymentInfo deployQtFrameworks(QList<FrameworkInfo> frameworks,
|
|||||||
deploymentInfo.useLoaderPath = useLoaderPath;
|
deploymentInfo.useLoaderPath = useLoaderPath;
|
||||||
deploymentInfo.isFramework = bundlePath.contains(".framework");
|
deploymentInfo.isFramework = bundlePath.contains(".framework");
|
||||||
deploymentInfo.isDebug = false;
|
deploymentInfo.isDebug = false;
|
||||||
QSet<QString> rpathsUsed;
|
QList<QString> rpathsUsed;
|
||||||
|
|
||||||
while (frameworks.isEmpty() == false) {
|
while (frameworks.isEmpty() == false) {
|
||||||
const FrameworkInfo framework = frameworks.takeFirst();
|
const FrameworkInfo framework = frameworks.takeFirst();
|
||||||
@@ -1005,8 +1001,9 @@ DeploymentInfo deployQtFrameworks(QList<FrameworkInfo> frameworks,
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!framework.rpathUsed.isEmpty())
|
if (!framework.rpathUsed.isEmpty() && !rpathsUsed.contains(framework.rpathUsed)) {
|
||||||
rpathsUsed << framework.rpathUsed;
|
rpathsUsed.append(framework.rpathUsed);
|
||||||
|
}
|
||||||
|
|
||||||
// Copy the framework/dylib to the app bundle.
|
// Copy the framework/dylib to the app bundle.
|
||||||
const QString deployedBinaryPath = framework.isDylib ? copyDylib(framework, bundlePath)
|
const QString deployedBinaryPath = framework.isDylib ? copyDylib(framework, bundlePath)
|
||||||
@@ -1029,11 +1026,11 @@ DeploymentInfo deployQtFrameworks(QList<FrameworkInfo> frameworks,
|
|||||||
// Check for framework dependencies
|
// Check for framework dependencies
|
||||||
QList<FrameworkInfo> dependencies = getQtFrameworks(deployedBinaryPath, bundlePath, rpathsUsed, useDebugLibs);
|
QList<FrameworkInfo> dependencies = getQtFrameworks(deployedBinaryPath, bundlePath, rpathsUsed, useDebugLibs);
|
||||||
|
|
||||||
foreach (FrameworkInfo dependency, dependencies) {
|
for (const FrameworkInfo &dependency : dependencies) {
|
||||||
if (dependency.rpathUsed.isEmpty()) {
|
if (dependency.rpathUsed.isEmpty()) {
|
||||||
changeInstallName(bundlePath, dependency, QStringList() << deployedBinaryPath, useLoaderPath);
|
changeInstallName(bundlePath, dependency, QStringList() << deployedBinaryPath, useLoaderPath);
|
||||||
} else {
|
} else {
|
||||||
rpathsUsed << dependency.rpathUsed;
|
rpathsUsed.append(dependency.rpathUsed);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Deploy framework if necessary.
|
// Deploy framework if necessary.
|
||||||
@@ -1045,6 +1042,7 @@ DeploymentInfo deployQtFrameworks(QList<FrameworkInfo> frameworks,
|
|||||||
deploymentInfo.deployedFrameworks = copiedFrameworks;
|
deploymentInfo.deployedFrameworks = copiedFrameworks;
|
||||||
deployRPaths(bundlePath, rpathsUsed, binaryPaths, useLoaderPath);
|
deployRPaths(bundlePath, rpathsUsed, binaryPaths, useLoaderPath);
|
||||||
deploymentInfo.rpathsUsed += rpathsUsed;
|
deploymentInfo.rpathsUsed += rpathsUsed;
|
||||||
|
deploymentInfo.rpathsUsed.removeDuplicates();
|
||||||
return deploymentInfo;
|
return deploymentInfo;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1056,12 +1054,14 @@ DeploymentInfo deployQtFrameworks(const QString &appBundlePath, const QStringLis
|
|||||||
applicationBundle.libraryPaths = findAppLibraries(appBundlePath);
|
applicationBundle.libraryPaths = findAppLibraries(appBundlePath);
|
||||||
QStringList allBinaryPaths = QStringList() << applicationBundle.binaryPath << applicationBundle.libraryPaths
|
QStringList allBinaryPaths = QStringList() << applicationBundle.binaryPath << applicationBundle.libraryPaths
|
||||||
<< additionalExecutables;
|
<< additionalExecutables;
|
||||||
QSet<QString> allLibraryPaths = getBinaryRPaths(applicationBundle.binaryPath, true);
|
QList<QString> allLibraryPaths = getBinaryRPaths(applicationBundle.binaryPath, true);
|
||||||
#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
|
#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
|
||||||
allLibraryPaths.insert(QLibraryInfo::path(QLibraryInfo::LibrariesPath));
|
allLibraryPaths.append(QLibraryInfo::path(QLibraryInfo::LibrariesPath));
|
||||||
#else
|
#else
|
||||||
allLibraryPaths.insert(QLibraryInfo::location(QLibraryInfo::LibrariesPath));
|
allLibraryPaths.append(QLibraryInfo::location(QLibraryInfo::LibrariesPath));
|
||||||
#endif
|
#endif
|
||||||
|
allLibraryPaths.removeDuplicates();
|
||||||
|
|
||||||
QList<FrameworkInfo> frameworks = getQtFrameworksForPaths(allBinaryPaths, appBundlePath, allLibraryPaths, useDebugLibs);
|
QList<FrameworkInfo> frameworks = getQtFrameworksForPaths(allBinaryPaths, appBundlePath, allLibraryPaths, useDebugLibs);
|
||||||
if (frameworks.isEmpty() && !alwaysOwerwriteEnabled) {
|
if (frameworks.isEmpty() && !alwaysOwerwriteEnabled) {
|
||||||
LogWarning();
|
LogWarning();
|
||||||
@@ -1077,7 +1077,7 @@ DeploymentInfo deployQtFrameworks(const QString &appBundlePath, const QStringLis
|
|||||||
QString getLibInfix(const QStringList &deployedFrameworks)
|
QString getLibInfix(const QStringList &deployedFrameworks)
|
||||||
{
|
{
|
||||||
QString libInfix;
|
QString libInfix;
|
||||||
foreach (const QString &framework, deployedFrameworks) {
|
for (const QString &framework : deployedFrameworks) {
|
||||||
if (framework.startsWith(QStringLiteral("QtCore")) && framework.endsWith(QStringLiteral(".framework"))) {
|
if (framework.startsWith(QStringLiteral("QtCore")) && framework.endsWith(QStringLiteral(".framework"))) {
|
||||||
Q_ASSERT(framework.length() >= 16);
|
Q_ASSERT(framework.length() >= 16);
|
||||||
// 16 == "QtCore" + ".framework"
|
// 16 == "QtCore" + ".framework"
|
||||||
@@ -1132,7 +1132,7 @@ void deployPlugins(const ApplicationBundleInfo &appBundleInfo, const QString &pl
|
|||||||
|
|
||||||
// Network
|
// Network
|
||||||
if (deploymentInfo.containsModule("Network", libInfix))
|
if (deploymentInfo.containsModule("Network", libInfix))
|
||||||
addPlugins(QStringLiteral("bearer"));
|
addPlugins(QStringLiteral("tls"));
|
||||||
|
|
||||||
// All image formats (svg if QtSvg is used)
|
// All image formats (svg if QtSvg is used)
|
||||||
const bool usesSvg = deploymentInfo.containsModule("Svg", libInfix);
|
const bool usesSvg = deploymentInfo.containsModule("Svg", libInfix);
|
||||||
@@ -1199,7 +1199,7 @@ void deployPlugins(const ApplicationBundleInfo &appBundleInfo, const QString &pl
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
foreach (const QString &plugin, pluginList) {
|
for (const QString &plugin : pluginList) {
|
||||||
QString sourcePath = pluginSourcePath + "/" + plugin;
|
QString sourcePath = pluginSourcePath + "/" + plugin;
|
||||||
const QString destinationPath = pluginDestinationPath + "/" + plugin;
|
const QString destinationPath = pluginDestinationPath + "/" + plugin;
|
||||||
QDir dir;
|
QDir dir;
|
||||||
@@ -1214,19 +1214,34 @@ void deployPlugins(const ApplicationBundleInfo &appBundleInfo, const QString &pl
|
|||||||
|
|
||||||
// GIO modules
|
// GIO modules
|
||||||
{
|
{
|
||||||
QString sourcePath = qgetenv("GIO_EXTRA_MODULES");
|
QString giomodule_path = qgetenv("GIO_EXTRA_MODULES");
|
||||||
if (sourcePath.isEmpty()) {
|
if (giomodule_path.isEmpty()) {
|
||||||
sourcePath = "/usr/local/lib/gio/modules/libgiognutls.so";
|
if (QDir().exists("/usr/local/lib/gio/modules")) {
|
||||||
|
giomodule_path = "/usr/local/lib/gio/modules";
|
||||||
|
}
|
||||||
|
else if (QDir().exists("/opt/local/lib/gio/modules")) {
|
||||||
|
giomodule_path = "/opt/local/lib/gio/modules";
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
qFatal("Missing GIO_EXTRA_MODULES");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
else {
|
|
||||||
sourcePath = sourcePath + "/libgiognutls.so";
|
const QStringList giomodules = QStringList() << "libgiognutls.so" << "libgioopenssl.so";
|
||||||
}
|
for (const QString &giomodule : giomodules) {
|
||||||
const QString destinationPath = appBundleInfo.path + "/Contents/PlugIns/gio-modules/libgiognutls.so";
|
const QString sourcePath = giomodule_path + "/" + giomodule;
|
||||||
QDir dir;
|
QFileInfo fileinfo(sourcePath);
|
||||||
if (dir.mkpath(QFileInfo(destinationPath).path()) && copyFilePrintStatus(sourcePath, destinationPath)) {
|
if (!fileinfo.exists()) {
|
||||||
runStrip(destinationPath);
|
LogError() << "Missing GIO module" << fileinfo.baseName();
|
||||||
QList<FrameworkInfo> frameworks = getQtFrameworks(destinationPath, appBundleInfo.path, deploymentInfo.rpathsUsed, useDebugLibs);
|
qFatal("Missing GIO modules.");
|
||||||
deployQtFrameworks(frameworks, appBundleInfo.path, QStringList() << destinationPath, useDebugLibs, deploymentInfo.useLoaderPath);
|
}
|
||||||
|
const QString destinationPath = appBundleInfo.path + "/Contents/PlugIns/gio-modules/" + giomodule;
|
||||||
|
QDir dir;
|
||||||
|
if (dir.mkpath(QFileInfo(destinationPath).path()) && copyFilePrintStatus(sourcePath, destinationPath)) {
|
||||||
|
runStrip(destinationPath);
|
||||||
|
QList<FrameworkInfo> frameworks = getQtFrameworks(destinationPath, appBundleInfo.path, deploymentInfo.rpathsUsed, useDebugLibs);
|
||||||
|
deployQtFrameworks(frameworks, appBundleInfo.path, QStringList() << destinationPath, useDebugLibs, deploymentInfo.useLoaderPath);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1234,7 +1249,15 @@ void deployPlugins(const ApplicationBundleInfo &appBundleInfo, const QString &pl
|
|||||||
{
|
{
|
||||||
QString sourcePath = qgetenv("GST_PLUGIN_SCANNER");
|
QString sourcePath = qgetenv("GST_PLUGIN_SCANNER");
|
||||||
if (sourcePath.isEmpty()) {
|
if (sourcePath.isEmpty()) {
|
||||||
sourcePath = "/usr/local/opt/gstreamer/libexec/gstreamer-1.0/gst-plugin-scanner";
|
if (QFileInfo::exists("/usr/local/opt/gstreamer/libexec/gstreamer-1.0/gst-plugin-scanner")) {
|
||||||
|
sourcePath = "/usr/local/opt/gstreamer/libexec/gstreamer-1.0/gst-plugin-scanner";
|
||||||
|
}
|
||||||
|
else if (QFileInfo::exists("/opt/local/libexec/gstreamer-1.0/gst-plugin-scanner")) {
|
||||||
|
sourcePath = "/opt/local/libexec/gstreamer-1.0/gst-plugin-scanner";
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
qFatal("Missing GST_PLUGIN_SCANNER.");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
const QString destinationPath = appBundleInfo.path + "/" + "Contents/PlugIns/gst-plugin-scanner";
|
const QString destinationPath = appBundleInfo.path + "/" + "Contents/PlugIns/gst-plugin-scanner";
|
||||||
QDir dir;
|
QDir dir;
|
||||||
@@ -1246,8 +1269,12 @@ void deployPlugins(const ApplicationBundleInfo &appBundleInfo, const QString &pl
|
|||||||
}
|
}
|
||||||
|
|
||||||
// GStreamer plugins.
|
// GStreamer plugins.
|
||||||
QStringList gstreamer_plugins = QStringList() << "libgstapetag.dylib"
|
QStringList gstreamer_plugins = QStringList()
|
||||||
|
<< "libgstaiff.dylib"
|
||||||
|
<< "libgstapetag.dylib"
|
||||||
<< "libgstapp.dylib"
|
<< "libgstapp.dylib"
|
||||||
|
<< "libgstasf.dylib"
|
||||||
|
<< "libgstasfmux.dylib"
|
||||||
<< "libgstaudioconvert.dylib"
|
<< "libgstaudioconvert.dylib"
|
||||||
<< "libgstaudiofx.dylib"
|
<< "libgstaudiofx.dylib"
|
||||||
<< "libgstaudiomixer.dylib"
|
<< "libgstaudiomixer.dylib"
|
||||||
@@ -1255,60 +1282,75 @@ void deployPlugins(const ApplicationBundleInfo &appBundleInfo, const QString &pl
|
|||||||
<< "libgstaudiorate.dylib"
|
<< "libgstaudiorate.dylib"
|
||||||
<< "libgstaudioresample.dylib"
|
<< "libgstaudioresample.dylib"
|
||||||
<< "libgstaudiotestsrc.dylib"
|
<< "libgstaudiotestsrc.dylib"
|
||||||
<< "libgstaudiovisualizers.dylib"
|
|
||||||
<< "libgstauparse.dylib"
|
|
||||||
<< "libgstautoconvert.dylib"
|
|
||||||
<< "libgstautodetect.dylib"
|
<< "libgstautodetect.dylib"
|
||||||
|
<< "libgstbs2b.dylib"
|
||||||
|
<< "libgstcdio.dylib"
|
||||||
<< "libgstcoreelements.dylib"
|
<< "libgstcoreelements.dylib"
|
||||||
|
<< "libgstdash.dylib"
|
||||||
<< "libgstequalizer.dylib"
|
<< "libgstequalizer.dylib"
|
||||||
|
<< "libgstflac.dylib"
|
||||||
|
<< "libgstfaac.dylib"
|
||||||
|
<< "libgstfaad.dylib"
|
||||||
|
<< "libgstfdkaac.dylib"
|
||||||
<< "libgstgio.dylib"
|
<< "libgstgio.dylib"
|
||||||
<< "libgsticydemux.dylib"
|
<< "libgsticydemux.dylib"
|
||||||
<< "libgstid3demux.dylib"
|
<< "libgstid3demux.dylib"
|
||||||
<< "libgstlevel.dylib"
|
<< "libgstisomp4.dylib"
|
||||||
<< "libgstosxaudio.dylib"
|
<< "libgstlame.dylib"
|
||||||
<< "libgstplayback.dylib"
|
<< "libgstlibav.dylib"
|
||||||
<< "libgstrawparse.dylib"
|
<< "libgstmpg123.dylib"
|
||||||
<< "libgstreplaygain.dylib"
|
<< "libgstmusepack.dylib"
|
||||||
<< "libgstsoup.dylib"
|
|
||||||
<< "libgstspectrum.dylib"
|
|
||||||
<< "libgsttypefindfunctions.dylib"
|
|
||||||
<< "libgstvolume.dylib"
|
|
||||||
<< "libgstxingmux.dylib"
|
|
||||||
<< "libgsttcp.dylib"
|
|
||||||
<< "libgstudp.dylib"
|
|
||||||
<< "libgstpbtypes.dylib"
|
|
||||||
<< "libgstrtp.dylib"
|
|
||||||
<< "libgstrtsp.dylib"
|
|
||||||
<< "libgstflac.dylib"
|
|
||||||
<< "libgstwavparse.dylib"
|
|
||||||
<< "libgstfaac.dylib"
|
|
||||||
<< "libgstfaad.dylib"
|
|
||||||
<< "libgstogg.dylib"
|
<< "libgstogg.dylib"
|
||||||
|
<< "libgstopenmpt.dylib"
|
||||||
<< "libgstopus.dylib"
|
<< "libgstopus.dylib"
|
||||||
<< "libgstopusparse.dylib"
|
<< "libgstopusparse.dylib"
|
||||||
<< "libgstasf.dylib"
|
<< "libgstosxaudio.dylib"
|
||||||
|
<< "libgstpbtypes.dylib"
|
||||||
|
<< "libgstplayback.dylib"
|
||||||
|
<< "libgstreplaygain.dylib"
|
||||||
|
<< "libgstrtp.dylib"
|
||||||
|
<< "libgstrtsp.dylib"
|
||||||
|
<< "libgstsoup.dylib"
|
||||||
|
<< "libgstspectrum.dylib"
|
||||||
<< "libgstspeex.dylib"
|
<< "libgstspeex.dylib"
|
||||||
<< "libgsttaglib.dylib"
|
<< "libgsttaglib.dylib"
|
||||||
|
<< "libgsttcp.dylib"
|
||||||
|
<< "libgsttypefindfunctions.dylib"
|
||||||
|
<< "libgstudp.dylib"
|
||||||
|
<< "libgstvolume.dylib"
|
||||||
<< "libgstvorbis.dylib"
|
<< "libgstvorbis.dylib"
|
||||||
<< "libgstisomp4.dylib"
|
<< "libgstwavpack.dylib"
|
||||||
<< "libgstlibav.dylib"
|
<< "libgstwavparse.dylib"
|
||||||
<< "libgstaiff.dylib"
|
<< "libgstxingmux.dylib";
|
||||||
<< "libgstlame.dylib"
|
|
||||||
<< "libgstmusepack.dylib";
|
|
||||||
|
|
||||||
QString gstreamer_plugins_dir = qgetenv("GST_PLUGIN_PATH");
|
QString gstreamer_plugins_dir = qgetenv("GST_PLUGIN_PATH");
|
||||||
if (gstreamer_plugins_dir.isEmpty()) {
|
if (gstreamer_plugins_dir.isEmpty()) {
|
||||||
gstreamer_plugins_dir = "/usr/local/lib/gstreamer-1.0";
|
if (QDir().exists("/usr/local/lib/gstreamer-1.0")) {
|
||||||
}
|
gstreamer_plugins_dir = "/usr/local/lib/gstreamer-1.0";
|
||||||
|
}
|
||||||
|
else if (QDir().exists("/opt/local/lib/gstreamer-1.0")) {
|
||||||
|
gstreamer_plugins_dir = "/opt/local/lib/gstreamer-1.0";
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
qFatal("Missing GST_PLUGIN_PATH.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
for (const QString &plugin : gstreamer_plugins) {
|
for (const QString &plugin : gstreamer_plugins) {
|
||||||
const QString sourcePath = gstreamer_plugins_dir + "/" + plugin;
|
QFileInfo info(gstreamer_plugins_dir + "/" + plugin);
|
||||||
const QString destinationPath = appBundleInfo.path + "/Contents/PlugIns/gstreamer/" + plugin;
|
if (!info.exists()) {
|
||||||
QDir dir;
|
info.setFile(gstreamer_plugins_dir + "/" + info.baseName() + QString(".so"));
|
||||||
if (dir.mkpath(QFileInfo(destinationPath).path()) && copyFilePrintStatus(sourcePath, destinationPath)) {
|
if (!info.exists()) {
|
||||||
runStrip(destinationPath);
|
LogError() << "Missing gstreamer plugin" << info.baseName();
|
||||||
QList<FrameworkInfo> frameworks = getQtFrameworks(destinationPath, appBundleInfo.path, deploymentInfo.rpathsUsed, useDebugLibs);
|
qFatal("Missing %s", info.baseName().toUtf8().constData());
|
||||||
deployQtFrameworks(frameworks, appBundleInfo.path, QStringList() << destinationPath, useDebugLibs, deploymentInfo.useLoaderPath);
|
}
|
||||||
|
}
|
||||||
|
const QString &sourcePath = info.filePath();
|
||||||
|
const QString destinationPath = appBundleInfo.path + "/Contents/PlugIns/gstreamer/" + info.fileName();
|
||||||
|
if (QDir().mkpath(QFileInfo(destinationPath).path()) && copyFilePrintStatus(sourcePath, destinationPath)) {
|
||||||
|
runStrip(destinationPath);
|
||||||
|
QList<FrameworkInfo> frameworks = getQtFrameworks(destinationPath, appBundleInfo.path, deploymentInfo.rpathsUsed, useDebugLibs);
|
||||||
|
deployQtFrameworks(frameworks, appBundleInfo.path, QStringList() << destinationPath, useDebugLibs, deploymentInfo.useLoaderPath);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1355,7 +1397,7 @@ void deployPlugins(const QString &appBundlePath, DeploymentInfo deploymentInfo,
|
|||||||
deployPlugins(applicationBundle, deploymentInfo.pluginPath, pluginDestinationPath, deploymentInfo, useDebugLibs);
|
deployPlugins(applicationBundle, deploymentInfo.pluginPath, pluginDestinationPath, deploymentInfo, useDebugLibs);
|
||||||
}
|
}
|
||||||
|
|
||||||
void deployQmlImport(const QString &appBundlePath, const QSet<QString> &rpaths, const QString &importSourcePath, const QString &importName)
|
void deployQmlImport(const QString &appBundlePath, const QList<QString> &rpaths, const QString &importSourcePath, const QString &importName)
|
||||||
{
|
{
|
||||||
QString importDestinationPath = appBundlePath + "/Contents/Resources/qml/" + importName;
|
QString importDestinationPath = appBundlePath + "/Contents/Resources/qml/" + importName;
|
||||||
|
|
||||||
@@ -1384,19 +1426,19 @@ bool deployQmlImports(const QString &appBundlePath, DeploymentInfo deploymentInf
|
|||||||
LogNormal() << "Application QML file path(s) is" << qmlDirs;
|
LogNormal() << "Application QML file path(s) is" << qmlDirs;
|
||||||
LogNormal() << "QML module search path(s) is" << qmlImportPaths;
|
LogNormal() << "QML module search path(s) is" << qmlImportPaths;
|
||||||
|
|
||||||
// Use qmlimportscanner from QLibraryInfo::BinariesPath
|
// Use qmlimportscanner from QLibraryInfo::LibraryExecutablesPath
|
||||||
#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
|
#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
|
||||||
QString qmlImportScannerPath = QDir::cleanPath(QLibraryInfo::path(QLibraryInfo::BinariesPath) + "/qmlimportscanner");
|
QString qmlImportScannerPath = QDir::cleanPath(QLibraryInfo::path(QLibraryInfo::LibraryExecutablesPath) + "/qmlimportscanner");
|
||||||
#else
|
#else
|
||||||
QString qmlImportScannerPath = QDir::cleanPath(QLibraryInfo::location(QLibraryInfo::BinariesPath) + "/qmlimportscanner");
|
QString qmlImportScannerPath = QDir::cleanPath(QLibraryInfo::location(QLibraryInfo::LibraryExecutablesPath) + "/qmlimportscanner");
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
// Fallback: Look relative to the macdeployqt binary
|
// Fallback: Look relative to the macdeployqt binary
|
||||||
if (!QFile(qmlImportScannerPath).exists())
|
if (!QFile::exists(qmlImportScannerPath))
|
||||||
qmlImportScannerPath = QCoreApplication::applicationDirPath() + "/qmlimportscanner";
|
qmlImportScannerPath = QCoreApplication::applicationDirPath() + "/qmlimportscanner";
|
||||||
|
|
||||||
// Verify that we found a qmlimportscanner binary
|
// Verify that we found a qmlimportscanner binary
|
||||||
if (!QFile(qmlImportScannerPath).exists()) {
|
if (!QFile::exists(qmlImportScannerPath)) {
|
||||||
LogError() << "qmlimportscanner not found at" << qmlImportScannerPath;
|
LogError() << "qmlimportscanner not found at" << qmlImportScannerPath;
|
||||||
LogError() << "Rebuild qtdeclarative/tools/qmlimportscanner";
|
LogError() << "Rebuild qtdeclarative/tools/qmlimportscanner";
|
||||||
return false;
|
return false;
|
||||||
@@ -1405,7 +1447,7 @@ bool deployQmlImports(const QString &appBundlePath, DeploymentInfo deploymentInf
|
|||||||
// build argument list for qmlimportsanner: "-rootPath foo/ -rootPath bar/ -importPath path/to/qt/qml"
|
// build argument list for qmlimportsanner: "-rootPath foo/ -rootPath bar/ -importPath path/to/qt/qml"
|
||||||
// ("rootPath" points to a directory containing app qml, "importPath" is where the Qt imports are installed)
|
// ("rootPath" points to a directory containing app qml, "importPath" is where the Qt imports are installed)
|
||||||
QStringList argumentList;
|
QStringList argumentList;
|
||||||
foreach (const QString &qmlDir, qmlDirs) {
|
for (const QString &qmlDir : qmlDirs) {
|
||||||
argumentList.append("-rootPath");
|
argumentList.append("-rootPath");
|
||||||
argumentList.append(qmlDir);
|
argumentList.append(qmlDir);
|
||||||
}
|
}
|
||||||
@@ -1453,7 +1495,7 @@ bool deployQmlImports(const QString &appBundlePath, DeploymentInfo deploymentInf
|
|||||||
std::sort(array.begin(), array.end(), importLessThan);
|
std::sort(array.begin(), array.end(), importLessThan);
|
||||||
|
|
||||||
// deploy each import
|
// deploy each import
|
||||||
foreach (const QVariant &importValue, array) {
|
for (const QVariant &importValue : array) {
|
||||||
QVariantMap import = importValue.toMap();
|
QVariantMap import = importValue.toMap();
|
||||||
QString name = import["name"].toString();
|
QString name = import["name"].toString();
|
||||||
QString path = import["path"].toString();
|
QString path = import["path"].toString();
|
||||||
@@ -1550,7 +1592,7 @@ QSet<QString> codesignBundle(const QString &identity,
|
|||||||
QString appBundleAbsolutePath = QFileInfo(appBundlePath).absoluteFilePath();
|
QString appBundleAbsolutePath = QFileInfo(appBundlePath).absoluteFilePath();
|
||||||
QString rootBinariesPath = appBundleAbsolutePath + "/Contents/MacOS/";
|
QString rootBinariesPath = appBundleAbsolutePath + "/Contents/MacOS/";
|
||||||
QStringList foundRootBinaries = QDir(rootBinariesPath).entryList(QStringList() << "*", QDir::Files);
|
QStringList foundRootBinaries = QDir(rootBinariesPath).entryList(QStringList() << "*", QDir::Files);
|
||||||
foreach (const QString &binary, foundRootBinaries) {
|
for (const QString &binary : foundRootBinaries) {
|
||||||
QString binaryPath = rootBinariesPath + binary;
|
QString binaryPath = rootBinariesPath + binary;
|
||||||
pendingBinaries.push(binaryPath);
|
pendingBinaries.push(binaryPath);
|
||||||
pendingBinariesSet.insert(binaryPath);
|
pendingBinariesSet.insert(binaryPath);
|
||||||
@@ -1559,14 +1601,14 @@ QSet<QString> codesignBundle(const QString &identity,
|
|||||||
|
|
||||||
bool getAbsoltuePath = true;
|
bool getAbsoltuePath = true;
|
||||||
QStringList foundPluginBinaries = findAppBundleFiles(appBundlePath + "/Contents/PlugIns/", getAbsoltuePath);
|
QStringList foundPluginBinaries = findAppBundleFiles(appBundlePath + "/Contents/PlugIns/", getAbsoltuePath);
|
||||||
foreach (const QString &binary, foundPluginBinaries) {
|
for (const QString &binary : foundPluginBinaries) {
|
||||||
pendingBinaries.push(binary);
|
pendingBinaries.push(binary);
|
||||||
pendingBinariesSet.insert(binary);
|
pendingBinariesSet.insert(binary);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add frameworks for processing.
|
// Add frameworks for processing.
|
||||||
QStringList frameworkPaths = findAppFrameworkPaths(appBundlePath);
|
QStringList frameworkPaths = findAppFrameworkPaths(appBundlePath);
|
||||||
foreach (const QString &frameworkPath, frameworkPaths) {
|
for (const QString &frameworkPath : frameworkPaths) {
|
||||||
|
|
||||||
// Prioritise first to sign any additional inner bundles found in the Helpers folder (e.g
|
// Prioritise first to sign any additional inner bundles found in the Helpers folder (e.g
|
||||||
// used by QtWebEngine).
|
// used by QtWebEngine).
|
||||||
@@ -1575,7 +1617,7 @@ QSet<QString> codesignBundle(const QString &identity,
|
|||||||
helpersIterator.next();
|
helpersIterator.next();
|
||||||
QString helpersPath = helpersIterator.filePath();
|
QString helpersPath = helpersIterator.filePath();
|
||||||
QStringList innerBundleNames = QDir(helpersPath).entryList(QStringList() << "*.app", QDir::Dirs);
|
QStringList innerBundleNames = QDir(helpersPath).entryList(QStringList() << "*.app", QDir::Dirs);
|
||||||
foreach (const QString &innerBundleName, innerBundleNames)
|
for (const QString &innerBundleName : innerBundleNames)
|
||||||
signedBinaries += codesignBundle(identity,
|
signedBinaries += codesignBundle(identity,
|
||||||
helpersPath + "/" + innerBundleName,
|
helpersPath + "/" + innerBundleName,
|
||||||
additionalBinariesContainingRpaths);
|
additionalBinariesContainingRpaths);
|
||||||
@@ -1588,7 +1630,7 @@ QSet<QString> codesignBundle(const QString &identity,
|
|||||||
librariesIterator.next();
|
librariesIterator.next();
|
||||||
QString librariesPath = librariesIterator.filePath();
|
QString librariesPath = librariesIterator.filePath();
|
||||||
QStringList bundleFiles = findAppBundleFiles(librariesPath, getAbsoltuePath);
|
QStringList bundleFiles = findAppBundleFiles(librariesPath, getAbsoltuePath);
|
||||||
foreach (const QString &binary, bundleFiles) {
|
for (const QString &binary : bundleFiles) {
|
||||||
pendingBinaries.push(binary);
|
pendingBinaries.push(binary);
|
||||||
pendingBinariesSet.insert(binary);
|
pendingBinariesSet.insert(binary);
|
||||||
}
|
}
|
||||||
@@ -1613,7 +1655,7 @@ QSet<QString> codesignBundle(const QString &identity,
|
|||||||
pendingBinaries.push(binary);
|
pendingBinaries.push(binary);
|
||||||
pendingBinariesSet.insert(binary);
|
pendingBinariesSet.insert(binary);
|
||||||
int dependenciesSkipped = 0;
|
int dependenciesSkipped = 0;
|
||||||
foreach (const QString &dependency, dependencies) {
|
for (const QString &dependency : std::as_const(dependencies)) {
|
||||||
// Skip dependencies that are outside the current app bundle, because this might
|
// Skip dependencies that are outside the current app bundle, because this might
|
||||||
// cause a codesign error if the current bundle is part of the dependency (e.g.
|
// cause a codesign error if the current bundle is part of the dependency (e.g.
|
||||||
// a bundle is part of a framework helper, and depends on that framework).
|
// a bundle is part of a framework helper, and depends on that framework).
|
||||||
@@ -1724,109 +1766,3 @@ void fixupFramework(const QString &frameworkName)
|
|||||||
addRPath("@loader_path/../../Contents/Frameworks/", frameworkBinary);
|
addRPath("@loader_path/../../Contents/Frameworks/", frameworkBinary);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool FinalCheck(const QString &appBundlePath) {
|
|
||||||
|
|
||||||
bool success = true;
|
|
||||||
|
|
||||||
QDirIterator iter(appBundlePath, QDir::Files | QDir::NoSymLinks, QDirIterator::Subdirectories);
|
|
||||||
while (iter.hasNext()) {
|
|
||||||
iter.next();
|
|
||||||
QString filepath = iter.fileInfo().filePath();
|
|
||||||
|
|
||||||
if (filepath.endsWith(".plist") ||
|
|
||||||
filepath.endsWith(".icns") ||
|
|
||||||
filepath.endsWith(".prl") ||
|
|
||||||
filepath.endsWith(".conf") ||
|
|
||||||
filepath.endsWith(".h") ||
|
|
||||||
filepath.endsWith(".nib") ||
|
|
||||||
filepath.endsWith(".strings") ||
|
|
||||||
filepath.endsWith(".css") ||
|
|
||||||
filepath.endsWith("CodeResources") ||
|
|
||||||
filepath.endsWith("PkgInfo") ||
|
|
||||||
filepath.endsWith(".modulemap")) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
//qDebug() << "Final check on" << filepath;
|
|
||||||
|
|
||||||
QProcess otool;
|
|
||||||
otool.start("otool", QStringList() << "-L" << filepath);
|
|
||||||
otool.waitForFinished();
|
|
||||||
if (otool.exitStatus() != QProcess::NormalExit || otool.exitCode() != 0) {
|
|
||||||
LogError() << otool.readAllStandardError();
|
|
||||||
success = false;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
QString output = otool.readAllStandardOutput();
|
|
||||||
QStringList output_lines = output.split("\n", Qt::SkipEmptyParts);
|
|
||||||
if (output_lines.size() < 2) {
|
|
||||||
LogError() << "Could not parse otool output:" << output;
|
|
||||||
success = false;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
QString first_line = output_lines.first();
|
|
||||||
if (first_line.endsWith(':')) first_line.chop(1);
|
|
||||||
if (first_line == filepath) {
|
|
||||||
output_lines.removeFirst();
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
LogError() << "First line" << first_line << "does not match" << filepath;
|
|
||||||
success = false;
|
|
||||||
}
|
|
||||||
static const QRegularExpression regexp(QStringLiteral("^\\t(.+) \\(compatibility version (\\d+\\.\\d+\\.\\d+), current version (\\d+\\.\\d+\\.\\d+)(, weak)?\\)$"));
|
|
||||||
for (const QString &output_line : output_lines) {
|
|
||||||
|
|
||||||
//qDebug() << "Final check on" << filepath << output_line;
|
|
||||||
|
|
||||||
const auto match = regexp.match(output_line);
|
|
||||||
if (match.hasMatch()) {
|
|
||||||
QString library = match.captured(1);
|
|
||||||
if (QFileInfo(library).fileName() == QFileInfo(filepath).fileName()) { // It's this.
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
else if (library.startsWith("@executable_path")) {
|
|
||||||
QString real_path = library;
|
|
||||||
real_path = real_path.replace("@executable_path", appBundlePath + "/Contents/MacOS");
|
|
||||||
if (!QFile(real_path).exists()) {
|
|
||||||
LogError() << real_path << "does not exist for" << filepath;
|
|
||||||
success = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else if (library.startsWith("@rpath")) {
|
|
||||||
QString real_path = library;
|
|
||||||
real_path = real_path.replace("@rpath", appBundlePath + "/Contents/Frameworks");
|
|
||||||
if (!QFile(real_path).exists() && !real_path.endsWith("QtSvg")) { // FIXME: Ignore broken svg image plugin.
|
|
||||||
LogError() << real_path << "does not exist for" << filepath;
|
|
||||||
success = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else if (library.startsWith("@loader_path")) {
|
|
||||||
QString loader_path = QFileInfo(filepath).path();
|
|
||||||
QString real_path = library;
|
|
||||||
real_path = real_path.replace("@loader_path", loader_path);
|
|
||||||
if (!QFile(real_path).exists()) {
|
|
||||||
LogError() << real_path << "does not exist for" << filepath;
|
|
||||||
success = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else if (library.startsWith("/System/Library/") || library.startsWith("/usr/lib/")) { // System library
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
else if (library.endsWith("libgcc_s.1.dylib")) { // fftw points to it for some reason.
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
LogError() << "File" << filepath << "points to" << library;
|
|
||||||
success = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
LogError() << "Could not parse otool output line:" << output_line;
|
|
||||||
success = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return success;
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|||||||
12
3rdparty/macdeployqt/shared.h
vendored
@@ -62,7 +62,7 @@ public:
|
|||||||
|
|
||||||
bool isDebugLibrary() const
|
bool isDebugLibrary() const
|
||||||
{
|
{
|
||||||
return binaryName.contains(QLatin1String("_debug"));
|
return binaryName.endsWith(QStringLiteral("_debug"));
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -101,7 +101,7 @@ public:
|
|||||||
QString qtPath;
|
QString qtPath;
|
||||||
QString pluginPath;
|
QString pluginPath;
|
||||||
QStringList deployedFrameworks;
|
QStringList deployedFrameworks;
|
||||||
QSet<QString> rpathsUsed;
|
QList<QString> rpathsUsed;
|
||||||
bool useLoaderPath;
|
bool useLoaderPath;
|
||||||
bool isFramework;
|
bool isFramework;
|
||||||
bool isDebug;
|
bool isDebug;
|
||||||
@@ -112,10 +112,10 @@ public:
|
|||||||
inline QDebug operator<<(QDebug debug, const ApplicationBundleInfo &info);
|
inline QDebug operator<<(QDebug debug, const ApplicationBundleInfo &info);
|
||||||
|
|
||||||
OtoolInfo findDependencyInfo(const QString &binaryPath);
|
OtoolInfo findDependencyInfo(const QString &binaryPath);
|
||||||
FrameworkInfo parseOtoolLibraryLine(const QString &line, const QString &appBundlePath, const QSet<QString> &rpaths, bool useDebugLibs);
|
FrameworkInfo parseOtoolLibraryLine(const QString &line, const QString &appBundlePath, const QList<QString> &rpaths, bool useDebugLibs);
|
||||||
QString findAppBinary(const QString &appBundlePath);
|
QString findAppBinary(const QString &appBundlePath);
|
||||||
QList<FrameworkInfo> getQtFrameworks(const QString &path, const QString &appBundlePath, const QSet<QString> &rpaths, bool useDebugLibs);
|
QList<FrameworkInfo> getQtFrameworks(const QString &path, const QString &appBundlePath, const QList<QString> &rpaths, bool useDebugLibs);
|
||||||
QList<FrameworkInfo> getQtFrameworks(const QStringList &otoolLines, const QString &appBundlePath, const QSet<QString> &rpaths, bool useDebugLibs);
|
QList<FrameworkInfo> getQtFrameworks(const QStringList &otoolLines, const QString &appBundlePath, const QList<QString> &rpaths, bool useDebugLibs);
|
||||||
QString copyFramework(const FrameworkInfo &framework, const QString path);
|
QString copyFramework(const FrameworkInfo &framework, const QString path);
|
||||||
DeploymentInfo deployQtFrameworks(const QString &appBundlePath, const QStringList &additionalExecutables, bool useDebugLibs);
|
DeploymentInfo deployQtFrameworks(const QString &appBundlePath, const QStringList &additionalExecutables, bool useDebugLibs);
|
||||||
DeploymentInfo deployQtFrameworks(QList<FrameworkInfo> frameworks,const QString &bundlePath, const QStringList &binaryPaths, bool useDebugLibs, bool useLoaderPath);
|
DeploymentInfo deployQtFrameworks(QList<FrameworkInfo> frameworks,const QString &bundlePath, const QStringList &binaryPaths, bool useDebugLibs, bool useLoaderPath);
|
||||||
@@ -136,6 +136,6 @@ QSet<QString> codesignBundle(const QString &identity,
|
|||||||
void codesign(const QString &identity, const QString &appBundlePath);
|
void codesign(const QString &identity, const QString &appBundlePath);
|
||||||
void createDiskImage(const QString &appBundlePath, const QString &filesystemType);
|
void createDiskImage(const QString &appBundlePath, const QString &filesystemType);
|
||||||
void fixupFramework(const QString &appBundlePath);
|
void fixupFramework(const QString &appBundlePath);
|
||||||
bool FinalCheck(const QString &appBundlePath);
|
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
18
3rdparty/singleapplication/CMakeLists.txt
vendored
@@ -3,18 +3,12 @@ cmake_minimum_required(VERSION 3.0)
|
|||||||
include(CheckIncludeFiles)
|
include(CheckIncludeFiles)
|
||||||
include(CheckFunctionExists)
|
include(CheckFunctionExists)
|
||||||
|
|
||||||
if(CMAKE_VERSION VERSION_GREATER 3.0)
|
check_function_exists(geteuid HAVE_GETEUID)
|
||||||
check_function_exists(geteuid HAVE_GETEUID)
|
check_function_exists(getpwuid HAVE_GETPWUID)
|
||||||
check_function_exists(getpwuid HAVE_GETPWUID)
|
|
||||||
endif()
|
|
||||||
|
|
||||||
set(SINGLEAPP-SOURCES singleapplication.cpp singleapplication_p.cpp)
|
set(SINGLEAPP-SOURCES singleapplication.cpp singleapplication_p.cpp)
|
||||||
set(SINGLEAPP-MOC-HEADERS singleapplication.h singleapplication_p.h)
|
set(SINGLEAPP-MOC-HEADERS singleapplication.h singleapplication_p.h)
|
||||||
if(BUILD_WITH_QT6)
|
qt_wrap_cpp(SINGLEAPP-SOURCES-MOC ${SINGLEAPP-MOC-HEADERS})
|
||||||
qt6_wrap_cpp(SINGLEAPP-SOURCES-MOC ${SINGLEAPP-MOC-HEADERS})
|
|
||||||
else()
|
|
||||||
qt5_wrap_cpp(SINGLEAPP-SOURCES-MOC ${SINGLEAPP-MOC-HEADERS})
|
|
||||||
endif()
|
|
||||||
add_library(singleapplication STATIC ${SINGLEAPP-SOURCES} ${SINGLEAPP-SOURCES-MOC})
|
add_library(singleapplication STATIC ${SINGLEAPP-SOURCES} ${SINGLEAPP-SOURCES-MOC})
|
||||||
target_include_directories(singleapplication PRIVATE
|
target_include_directories(singleapplication PRIVATE
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}
|
${CMAKE_CURRENT_SOURCE_DIR}
|
||||||
@@ -28,11 +22,7 @@ target_link_libraries(singleapplication PRIVATE
|
|||||||
|
|
||||||
set(SINGLECOREAPP-SOURCES singlecoreapplication.cpp singlecoreapplication_p.cpp)
|
set(SINGLECOREAPP-SOURCES singlecoreapplication.cpp singlecoreapplication_p.cpp)
|
||||||
set(SINGLECOREAPP-MOC-HEADERS singlecoreapplication.h singlecoreapplication_p.h)
|
set(SINGLECOREAPP-MOC-HEADERS singlecoreapplication.h singlecoreapplication_p.h)
|
||||||
if(BUILD_WITH_QT6)
|
qt_wrap_cpp(SINGLECOREAPP-SOURCES-MOC ${SINGLECOREAPP-MOC-HEADERS})
|
||||||
qt6_wrap_cpp(SINGLECOREAPP-SOURCES-MOC ${SINGLECOREAPP-MOC-HEADERS})
|
|
||||||
else()
|
|
||||||
qt5_wrap_cpp(SINGLECOREAPP-SOURCES-MOC ${SINGLECOREAPP-MOC-HEADERS})
|
|
||||||
endif()
|
|
||||||
add_library(singlecoreapplication STATIC ${SINGLECOREAPP-SOURCES} ${SINGLECOREAPP-SOURCES-MOC})
|
add_library(singlecoreapplication STATIC ${SINGLECOREAPP-SOURCES} ${SINGLECOREAPP-SOURCES-MOC})
|
||||||
target_include_directories(singlecoreapplication PRIVATE
|
target_include_directories(singlecoreapplication PRIVATE
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}
|
${CMAKE_CURRENT_SOURCE_DIR}
|
||||||
|
|||||||
2
3rdparty/singleapplication/LICENSE
vendored
@@ -1,6 +1,6 @@
|
|||||||
The MIT License (MIT)
|
The MIT License (MIT)
|
||||||
|
|
||||||
Copyright (c) Itay Grudev 2015 - 2016
|
Copyright (c) Itay Grudev 2015 - 2020
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
of this software and associated documentation files (the "Software"), to deal
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
|||||||
86
3rdparty/singleapplication/README.md
vendored
@@ -1,7 +1,8 @@
|
|||||||
SingleApplication
|
SingleApplication
|
||||||
=================
|
=================
|
||||||
|
[](https://github.com/itay-grudev/SingleApplication/actions)
|
||||||
|
|
||||||
This is a replacement of the QtSingleApplication for `Qt5`.
|
This is a replacement of the QtSingleApplication for `Qt5` and `Qt6`.
|
||||||
|
|
||||||
Keeps the Primary Instance of your Application and kills each subsequent
|
Keeps the Primary Instance of your Application and kills each subsequent
|
||||||
instances. It can (if enabled) spawn secondary (non-related to the primary)
|
instances. It can (if enabled) spawn secondary (non-related to the primary)
|
||||||
@@ -15,18 +16,6 @@ class you specify via the `QAPPLICATION_CLASS` macro (`QCoreApplication` is the
|
|||||||
default). Further usage is similar to the use of the `Q[Core|Gui]Application`
|
default). Further usage is similar to the use of the `Q[Core|Gui]Application`
|
||||||
classes.
|
classes.
|
||||||
|
|
||||||
The library sets up a `QLocalServer` and a `QSharedMemory` block. The first
|
|
||||||
instance of your Application is your Primary Instance. It would check if the
|
|
||||||
shared memory block exists and if not it will start a `QLocalServer` and listen
|
|
||||||
for connections. Each subsequent instance of your application would check if the
|
|
||||||
shared memory block exists and if it does, it will connect to the QLocalServer
|
|
||||||
to notify the primary instance that a new instance had been started, after which
|
|
||||||
it would terminate with status code `0`. In the Primary Instance
|
|
||||||
`SingleApplication` would emit the `instanceStarted()` signal upon detecting
|
|
||||||
that a new instance had been started.
|
|
||||||
|
|
||||||
The library uses `stdlib` to terminate the program with the `exit()` function.
|
|
||||||
|
|
||||||
You can use the library as if you use any other `QCoreApplication` derived
|
You can use the library as if you use any other `QCoreApplication` derived
|
||||||
class:
|
class:
|
||||||
|
|
||||||
@@ -43,24 +32,49 @@ int main( int argc, char* argv[] )
|
|||||||
```
|
```
|
||||||
|
|
||||||
To include the library files I would recommend that you add it as a git
|
To include the library files I would recommend that you add it as a git
|
||||||
submodule to your project and include it's contents with a `.pri` file. Here is
|
submodule to your project. Here is how:
|
||||||
how:
|
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
git submodule add git@github.com:itay-grudev/SingleApplication.git singleapplication
|
git submodule add https://github.com/itay-grudev/SingleApplication.git singleapplication
|
||||||
```
|
```
|
||||||
|
|
||||||
Then include the `singleapplication.pri` file in your `.pro` project file. Also
|
**Qmake:**
|
||||||
don't forget to specify which `QCoreApplication` class your app is using if it
|
|
||||||
is not `QCoreApplication`.
|
Then include the `singleapplication.pri` file in your `.pro` project file.
|
||||||
|
|
||||||
```qmake
|
```qmake
|
||||||
include(singleapplication/singleapplication.pri)
|
include(singleapplication/singleapplication.pri)
|
||||||
DEFINES += QAPPLICATION_CLASS=QApplication
|
DEFINES += QAPPLICATION_CLASS=QApplication
|
||||||
```
|
```
|
||||||
|
|
||||||
|
**CMake:**
|
||||||
|
|
||||||
|
Then include the subdirectory in your `CMakeLists.txt` project file.
|
||||||
|
|
||||||
|
```cmake
|
||||||
|
set(QAPPLICATION_CLASS QApplication CACHE STRING "Inheritance class for SingleApplication")
|
||||||
|
add_subdirectory(src/third-party/singleapplication)
|
||||||
|
target_link_libraries(${PROJECT_NAME} SingleApplication::SingleApplication)
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
The library sets up a `QLocalServer` and a `QSharedMemory` block. The first
|
||||||
|
instance of your Application is your Primary Instance. It would check if the
|
||||||
|
shared memory block exists and if not it will start a `QLocalServer` and listen
|
||||||
|
for connections. Each subsequent instance of your application would check if the
|
||||||
|
shared memory block exists and if it does, it will connect to the QLocalServer
|
||||||
|
to notify the primary instance that a new instance had been started, after which
|
||||||
|
it would terminate with status code `0`. In the Primary Instance
|
||||||
|
`SingleApplication` would emit the `instanceStarted()` signal upon detecting
|
||||||
|
that a new instance had been started.
|
||||||
|
|
||||||
|
The library uses `stdlib` to terminate the program with the `exit()` function.
|
||||||
|
|
||||||
|
Also don't forget to specify which `QCoreApplication` class your app is using if it
|
||||||
|
is not `QCoreApplication` as in examples above.
|
||||||
|
|
||||||
The `Instance Started` signal
|
The `Instance Started` signal
|
||||||
------------------------
|
-----------------------------
|
||||||
|
|
||||||
The SingleApplication class implements a `instanceStarted()` signal. You can
|
The SingleApplication class implements a `instanceStarted()` signal. You can
|
||||||
bind to that signal to raise your application's window when a new instance had
|
bind to that signal to raise your application's window when a new instance had
|
||||||
@@ -125,13 +139,22 @@ app.isSecondary();
|
|||||||
*__Note:__ If your Primary Instance is terminated a newly launched instance
|
*__Note:__ If your Primary Instance is terminated a newly launched instance
|
||||||
will replace the Primary one even if the Secondary flag has been set.*
|
will replace the Primary one even if the Secondary flag has been set.*
|
||||||
|
|
||||||
|
Examples
|
||||||
|
--------
|
||||||
|
|
||||||
|
There are three examples provided in this repository:
|
||||||
|
|
||||||
|
* Basic example that prevents a secondary instance from starting [`examples/basic`](https://github.com/itay-grudev/SingleApplication/tree/master/examples/basic)
|
||||||
|
* An example of a graphical application raising it's parent window [`examples/calculator`](https://github.com/itay-grudev/SingleApplication/tree/master/examples/calculator)
|
||||||
|
* A console application sending the primary instance it's command line parameters [`examples/sending_arguments`](https://github.com/itay-grudev/SingleApplication/tree/master/examples/sending_arguments)
|
||||||
|
|
||||||
API
|
API
|
||||||
---
|
---
|
||||||
|
|
||||||
### Members
|
### Members
|
||||||
|
|
||||||
```cpp
|
```cpp
|
||||||
SingleApplication::SingleApplication( int &argc, char *argv[], bool allowSecondary = false, Options options = Mode::User, int timeout = 100 )
|
SingleApplication::SingleApplication( int &argc, char *argv[], bool allowSecondary = false, Options options = Mode::User, int timeout = 100, QString userData = QString() )
|
||||||
```
|
```
|
||||||
|
|
||||||
Depending on whether `allowSecondary` is set, this constructor may terminate
|
Depending on whether `allowSecondary` is set, this constructor may terminate
|
||||||
@@ -140,7 +163,7 @@ can be specified to set whether the SingleApplication block should work
|
|||||||
user-wide or system-wide. Additionally the `Mode::SecondaryNotification` may be
|
user-wide or system-wide. Additionally the `Mode::SecondaryNotification` may be
|
||||||
used to notify the primary instance whenever a secondary instance had been
|
used to notify the primary instance whenever a secondary instance had been
|
||||||
started (disabled by default). `timeout` specifies the maximum time in
|
started (disabled by default). `timeout` specifies the maximum time in
|
||||||
milliseconds to wait for blocking operations.
|
milliseconds to wait for blocking operations. Setting `userData` provides additional data that will isolate this instance from other instances that do not have the same (or any) user data set.
|
||||||
|
|
||||||
*__Note:__ `argc` and `argv` may be changed as Qt removes arguments that it
|
*__Note:__ `argc` and `argv` may be changed as Qt removes arguments that it
|
||||||
recognizes.*
|
recognizes.*
|
||||||
@@ -159,7 +182,8 @@ bool SingleApplication::sendMessage( QByteArray message, int timeout = 100 )
|
|||||||
```
|
```
|
||||||
|
|
||||||
Sends `message` to the Primary Instance. Uses `timeout` as a the maximum timeout
|
Sends `message` to the Primary Instance. Uses `timeout` as a the maximum timeout
|
||||||
in milliseconds for blocking functions
|
in milliseconds for blocking functions. Returns `true` if the message has been sent
|
||||||
|
successfully. If the message can't be sent or the function timeouts - returns `false`.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -192,6 +216,22 @@ qint64 SingleApplication::primaryPid()
|
|||||||
|
|
||||||
Returns the process ID (PID) of the primary instance.
|
Returns the process ID (PID) of the primary instance.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
QString SingleApplication::primaryUser()
|
||||||
|
```
|
||||||
|
|
||||||
|
Returns the username the primary instance is running as.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
QString SingleApplication::currentUser()
|
||||||
|
```
|
||||||
|
|
||||||
|
Returns the username the current instance is running as.
|
||||||
|
|
||||||
### Signals
|
### Signals
|
||||||
|
|
||||||
```cpp
|
```cpp
|
||||||
|
|||||||
51
3rdparty/singleapplication/singleapplication.cpp
vendored
@@ -54,7 +54,7 @@
|
|||||||
* @param options Optional flags to toggle specific behaviour
|
* @param options Optional flags to toggle specific behaviour
|
||||||
* @param timeout Maximum time blocking functions are allowed during app load
|
* @param timeout Maximum time blocking functions are allowed during app load
|
||||||
*/
|
*/
|
||||||
SingleApplication::SingleApplication(int &argc, char *argv[], bool allowSecondary, Options options, int timeout)
|
SingleApplication::SingleApplication(int &argc, char *argv[], const bool allowSecondary, const Options options, const int timeout)
|
||||||
: app_t(argc, argv),
|
: app_t(argc, argv),
|
||||||
d_ptr(new SingleApplicationPrivate(this)) {
|
d_ptr(new SingleApplicationPrivate(this)) {
|
||||||
|
|
||||||
@@ -67,7 +67,7 @@ SingleApplication::SingleApplication(int &argc, char *argv[], bool allowSecondar
|
|||||||
d->genBlockServerName();
|
d->genBlockServerName();
|
||||||
|
|
||||||
// To mitigate QSharedMemory issues with large amount of processes attempting to attach at the same time
|
// To mitigate QSharedMemory issues with large amount of processes attempting to attach at the same time
|
||||||
d->randomSleep();
|
SingleApplicationPrivate::randomSleep();
|
||||||
|
|
||||||
#ifdef Q_OS_UNIX
|
#ifdef Q_OS_UNIX
|
||||||
// By explicitly attaching it and then deleting it we make sure that the memory is deleted even after the process has crashed on Unix.
|
// By explicitly attaching it and then deleting it we make sure that the memory is deleted even after the process has crashed on Unix.
|
||||||
@@ -106,14 +106,14 @@ SingleApplication::SingleApplication(int &argc, char *argv[], bool allowSecondar
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
InstancesInfo *inst = static_cast<InstancesInfo*>(d->memory_->data());
|
InstancesInfo *instance = static_cast<InstancesInfo*>(d->memory_->data());
|
||||||
QElapsedTimer time;
|
QElapsedTimer time;
|
||||||
time.start();
|
time.start();
|
||||||
|
|
||||||
// Make sure the shared memory block is initialised and in consistent state
|
// Make sure the shared memory block is initialised and in consistent state
|
||||||
forever {
|
forever {
|
||||||
// If the shared memory block's checksum is valid continue
|
// If the shared memory block's checksum is valid continue
|
||||||
if (d->blockChecksum() == inst->checksum) break;
|
if (d->blockChecksum() == instance->checksum) break;
|
||||||
|
|
||||||
// If more than 5s have elapsed, assume the primary instance crashed and assume it's position
|
// If more than 5s have elapsed, assume the primary instance crashed and assume it's position
|
||||||
if (time.elapsed() > 5000) {
|
if (time.elapsed() > 5000) {
|
||||||
@@ -127,14 +127,14 @@ SingleApplication::SingleApplication(int &argc, char *argv[], bool allowSecondar
|
|||||||
qDebug() << "SingleApplication: Unable to unlock memory for random wait.";
|
qDebug() << "SingleApplication: Unable to unlock memory for random wait.";
|
||||||
qDebug() << d->memory_->errorString();
|
qDebug() << d->memory_->errorString();
|
||||||
}
|
}
|
||||||
d->randomSleep();
|
SingleApplicationPrivate::randomSleep();
|
||||||
if (!d->memory_->lock()) {
|
if (!d->memory_->lock()) {
|
||||||
qCritical() << "SingleApplication: Unable to lock memory after random wait.";
|
qCritical() << "SingleApplication: Unable to lock memory after random wait.";
|
||||||
abortSafely();
|
abortSafely();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (inst->primary == false) {
|
if (!instance->primary) {
|
||||||
d->startPrimary();
|
d->startPrimary();
|
||||||
if (!d->memory_->unlock()) {
|
if (!d->memory_->unlock()) {
|
||||||
qDebug() << "SingleApplication: Unable to unlock memory after primary start.";
|
qDebug() << "SingleApplication: Unable to unlock memory after primary start.";
|
||||||
@@ -178,8 +178,8 @@ SingleApplication::~SingleApplication() {
|
|||||||
* Checks if the current application instance is primary.
|
* Checks if the current application instance is primary.
|
||||||
* @return Returns true if the instance is primary, false otherwise.
|
* @return Returns true if the instance is primary, false otherwise.
|
||||||
*/
|
*/
|
||||||
bool SingleApplication::isPrimary() {
|
bool SingleApplication::isPrimary() const {
|
||||||
Q_D(SingleApplication);
|
Q_D(const SingleApplication);
|
||||||
return d->server_ != nullptr;
|
return d->server_ != nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -187,8 +187,8 @@ bool SingleApplication::isPrimary() {
|
|||||||
* Checks if the current application instance is secondary.
|
* Checks if the current application instance is secondary.
|
||||||
* @return Returns true if the instance is secondary, false otherwise.
|
* @return Returns true if the instance is secondary, false otherwise.
|
||||||
*/
|
*/
|
||||||
bool SingleApplication::isSecondary() {
|
bool SingleApplication::isSecondary() const {
|
||||||
Q_D(SingleApplication);
|
Q_D(const SingleApplication);
|
||||||
return d->server_ == nullptr;
|
return d->server_ == nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -197,8 +197,8 @@ bool SingleApplication::isSecondary() {
|
|||||||
* It is reset when the first (primary) instance of your app starts and only incremented afterwards.
|
* It is reset when the first (primary) instance of your app starts and only incremented afterwards.
|
||||||
* @return Returns a unique instance id.
|
* @return Returns a unique instance id.
|
||||||
*/
|
*/
|
||||||
quint32 SingleApplication::instanceId() {
|
quint32 SingleApplication::instanceId() const {
|
||||||
Q_D(SingleApplication);
|
Q_D(const SingleApplication);
|
||||||
return d->instanceNumber_;
|
return d->instanceNumber_;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -207,8 +207,8 @@ quint32 SingleApplication::instanceId() {
|
|||||||
* Especially useful when SingleApplication is coupled with OS. specific APIs.
|
* Especially useful when SingleApplication is coupled with OS. specific APIs.
|
||||||
* @return Returns the primary instance PID.
|
* @return Returns the primary instance PID.
|
||||||
*/
|
*/
|
||||||
qint64 SingleApplication::primaryPid() {
|
qint64 SingleApplication::primaryPid() const {
|
||||||
Q_D(SingleApplication);
|
Q_D(const SingleApplication);
|
||||||
return d->primaryPid();
|
return d->primaryPid();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -216,8 +216,8 @@ qint64 SingleApplication::primaryPid() {
|
|||||||
* Returns the username the primary instance is running as.
|
* Returns the username the primary instance is running as.
|
||||||
* @return Returns the username the primary instance is running as.
|
* @return Returns the username the primary instance is running as.
|
||||||
*/
|
*/
|
||||||
QString SingleApplication::primaryUser() {
|
QString SingleApplication::primaryUser() const {
|
||||||
Q_D(SingleApplication);
|
Q_D(const SingleApplication);
|
||||||
return d->primaryUser();
|
return d->primaryUser();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -225,18 +225,17 @@ QString SingleApplication::primaryUser() {
|
|||||||
* Returns the username the current instance is running as.
|
* Returns the username the current instance is running as.
|
||||||
* @return Returns the username the current instance is running as.
|
* @return Returns the username the current instance is running as.
|
||||||
*/
|
*/
|
||||||
QString SingleApplication::currentUser() {
|
QString SingleApplication::currentUser() const {
|
||||||
Q_D(SingleApplication);
|
return SingleApplicationPrivate::getUsername();
|
||||||
return d->getUsername();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sends message to the Primary Instance.
|
* Sends message to the Primary Instance.
|
||||||
* @param message The message to send.
|
* @param message The message to send.
|
||||||
* @param timeout the maximum timeout in milliseconds for blocking functions.
|
* @param timeout the maximum timeout in milliseconds for blocking functions.
|
||||||
* @return true if the message was sent successfuly, false otherwise.
|
* @return true if the message was sent successfully, false otherwise.
|
||||||
*/
|
*/
|
||||||
bool SingleApplication::sendMessage(QByteArray message, int timeout) {
|
bool SingleApplication::sendMessage(const QByteArray &message, const int timeout) {
|
||||||
|
|
||||||
Q_D(SingleApplication);
|
Q_D(SingleApplication);
|
||||||
|
|
||||||
@@ -244,14 +243,11 @@ bool SingleApplication::sendMessage(QByteArray message, int timeout) {
|
|||||||
if (isPrimary()) return false;
|
if (isPrimary()) return false;
|
||||||
|
|
||||||
// Make sure the socket is connected
|
// Make sure the socket is connected
|
||||||
if (!d->connectToPrimary(timeout, SingleApplicationPrivate::Reconnect))
|
if (!d->connectToPrimary(timeout, SingleApplicationPrivate::Reconnect)) {
|
||||||
return false;
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
d->socket_->write(message);
|
return d->writeConfirmedMessage(timeout, message);
|
||||||
const bool dataWritten = d->socket_->waitForBytesWritten(timeout);
|
|
||||||
d->socket_->flush();
|
|
||||||
return dataWritten;
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -264,6 +260,7 @@ void SingleApplication::abortSafely() {
|
|||||||
|
|
||||||
qCritical() << "SingleApplication: " << d->memory_->error() << d->memory_->errorString();
|
qCritical() << "SingleApplication: " << d->memory_->error() << d->memory_->errorString();
|
||||||
delete d;
|
delete d;
|
||||||
|
|
||||||
::exit(EXIT_FAILURE);
|
::exit(EXIT_FAILURE);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
23
3rdparty/singleapplication/singleapplication.h
vendored
@@ -42,13 +42,13 @@
|
|||||||
class SingleApplicationPrivate;
|
class SingleApplicationPrivate;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief The SingleApplication class handles multipe instances of the same Application
|
* @brief The SingleApplication class handles multiple instances of the same Application
|
||||||
* @see QApplication
|
* @see QApplication
|
||||||
*/
|
*/
|
||||||
class SingleApplication : public QApplication {
|
class SingleApplication : public QApplication { // clazy:exclude=ctor-missing-parent-argument
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
|
|
||||||
typedef QApplication app_t;
|
using app_t = QApplication;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
/**
|
/**
|
||||||
@@ -88,46 +88,45 @@ class SingleApplication : public QApplication {
|
|||||||
* operations. It does not guarantee that the SingleApplication
|
* operations. It does not guarantee that the SingleApplication
|
||||||
* initialisation will be completed in given time, though is a good hint.
|
* initialisation will be completed in given time, though is a good hint.
|
||||||
* Usually 4*timeout would be the worst case (fail) scenario.
|
* Usually 4*timeout would be the worst case (fail) scenario.
|
||||||
* @see See the corresponding QAPPLICATION_CLASS constructor for reference
|
|
||||||
*/
|
*/
|
||||||
explicit SingleApplication(int &argc, char *argv[], bool allowSecondary = false, Options options = Mode::User, int timeout = 1000);
|
explicit SingleApplication(int &argc, char *argv[], const bool allowSecondary = false, const Options options = Mode::User, const int timeout = 1000);
|
||||||
~SingleApplication() override;
|
~SingleApplication() override;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Returns if the instance is the primary instance
|
* @brief Returns if the instance is the primary instance
|
||||||
* @returns {bool}
|
* @returns {bool}
|
||||||
*/
|
*/
|
||||||
bool isPrimary();
|
bool isPrimary() const;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Returns if the instance is a secondary instance
|
* @brief Returns if the instance is a secondary instance
|
||||||
* @returns {bool}
|
* @returns {bool}
|
||||||
*/
|
*/
|
||||||
bool isSecondary();
|
bool isSecondary() const;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Returns a unique identifier for the current instance
|
* @brief Returns a unique identifier for the current instance
|
||||||
* @returns {qint32}
|
* @returns {qint32}
|
||||||
*/
|
*/
|
||||||
quint32 instanceId();
|
quint32 instanceId() const;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Returns the process ID (PID) of the primary instance
|
* @brief Returns the process ID (PID) of the primary instance
|
||||||
* @returns {qint64}
|
* @returns {qint64}
|
||||||
*/
|
*/
|
||||||
qint64 primaryPid();
|
qint64 primaryPid() const;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Returns the username of the user running the primary instance
|
* @brief Returns the username of the user running the primary instance
|
||||||
* @returns {QString}
|
* @returns {QString}
|
||||||
*/
|
*/
|
||||||
QString primaryUser();
|
QString primaryUser() const;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Returns the username of the current user
|
* @brief Returns the username of the current user
|
||||||
* @returns {QString}
|
* @returns {QString}
|
||||||
*/
|
*/
|
||||||
QString currentUser();
|
QString currentUser() const;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Sends a message to the primary instance. Returns true on success.
|
* @brief Sends a message to the primary instance. Returns true on success.
|
||||||
@@ -136,7 +135,7 @@ class SingleApplication : public QApplication {
|
|||||||
* @note sendMessage() will return false if invoked from the primary
|
* @note sendMessage() will return false if invoked from the primary
|
||||||
* instance.
|
* instance.
|
||||||
*/
|
*/
|
||||||
bool sendMessage(QByteArray message, int timeout = 1000);
|
bool sendMessage(const QByteArray &message, const int timeout = 1000);
|
||||||
|
|
||||||
signals:
|
signals:
|
||||||
void instanceStarted();
|
void instanceStarted();
|
||||||
|
|||||||
216
3rdparty/singleapplication/singleapplication_p.cpp
vendored
@@ -44,6 +44,14 @@
|
|||||||
# include <pwd.h>
|
# include <pwd.h>
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
#ifdef Q_OS_WIN
|
||||||
|
# ifndef NOMINMAX
|
||||||
|
# define NOMINMAX 1
|
||||||
|
# endif
|
||||||
|
# include <windows.h>
|
||||||
|
# include <lmcons.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
#include <QObject>
|
#include <QObject>
|
||||||
#include <QThread>
|
#include <QThread>
|
||||||
#include <QIODevice>
|
#include <QIODevice>
|
||||||
@@ -53,7 +61,6 @@
|
|||||||
#include <QCryptographicHash>
|
#include <QCryptographicHash>
|
||||||
#include <QLocalServer>
|
#include <QLocalServer>
|
||||||
#include <QLocalSocket>
|
#include <QLocalSocket>
|
||||||
#include <QDir>
|
|
||||||
#include <QElapsedTimer>
|
#include <QElapsedTimer>
|
||||||
#if QT_VERSION >= QT_VERSION_CHECK(5, 10, 0)
|
#if QT_VERSION >= QT_VERSION_CHECK(5, 10, 0)
|
||||||
# include <QRandomGenerator>
|
# include <QRandomGenerator>
|
||||||
@@ -64,11 +71,6 @@
|
|||||||
#include "singleapplication.h"
|
#include "singleapplication.h"
|
||||||
#include "singleapplication_p.h"
|
#include "singleapplication_p.h"
|
||||||
|
|
||||||
#ifdef Q_OS_WIN
|
|
||||||
# include <windows.h>
|
|
||||||
# include <lmcons.h>
|
|
||||||
#endif
|
|
||||||
|
|
||||||
SingleApplicationPrivate::SingleApplicationPrivate(SingleApplication *ptr)
|
SingleApplicationPrivate::SingleApplicationPrivate(SingleApplication *ptr)
|
||||||
: q_ptr(ptr),
|
: q_ptr(ptr),
|
||||||
memory_(nullptr),
|
memory_(nullptr),
|
||||||
@@ -86,14 +88,14 @@ SingleApplicationPrivate::~SingleApplicationPrivate() {
|
|||||||
|
|
||||||
if (memory_ != nullptr) {
|
if (memory_ != nullptr) {
|
||||||
memory_->lock();
|
memory_->lock();
|
||||||
InstancesInfo *inst = static_cast<InstancesInfo*>(memory_->data());
|
InstancesInfo *instance = static_cast<InstancesInfo*>(memory_->data());
|
||||||
if (server_ != nullptr) {
|
if (server_ != nullptr) {
|
||||||
server_->close();
|
server_->close();
|
||||||
delete server_;
|
delete server_;
|
||||||
inst->primary = false;
|
instance->primary = false;
|
||||||
inst->primaryPid = -1;
|
instance->primaryPid = -1;
|
||||||
inst->primaryUser[0] = '\0';
|
instance->primaryUser[0] = '\0';
|
||||||
inst->checksum = blockChecksum();
|
instance->checksum = blockChecksum();
|
||||||
}
|
}
|
||||||
memory_->unlock();
|
memory_->unlock();
|
||||||
|
|
||||||
@@ -142,7 +144,7 @@ QString SingleApplicationPrivate::getUsername() {
|
|||||||
void SingleApplicationPrivate::genBlockServerName() {
|
void SingleApplicationPrivate::genBlockServerName() {
|
||||||
|
|
||||||
QCryptographicHash appData(QCryptographicHash::Sha256);
|
QCryptographicHash appData(QCryptographicHash::Sha256);
|
||||||
appData.addData("SingleApplication", 17);
|
appData.addData("SingleApplication");
|
||||||
appData.addData(SingleApplication::app_t::applicationName().toUtf8());
|
appData.addData(SingleApplication::app_t::applicationName().toUtf8());
|
||||||
appData.addData(SingleApplication::app_t::organizationName().toUtf8());
|
appData.addData(SingleApplication::app_t::organizationName().toUtf8());
|
||||||
appData.addData(SingleApplication::app_t::organizationDomain().toUtf8());
|
appData.addData(SingleApplication::app_t::organizationDomain().toUtf8());
|
||||||
@@ -152,7 +154,15 @@ void SingleApplicationPrivate::genBlockServerName() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (!(options_ & SingleApplication::Mode::ExcludeAppPath)) {
|
if (!(options_ & SingleApplication::Mode::ExcludeAppPath)) {
|
||||||
#ifdef Q_OS_WIN
|
#if defined(Q_OS_UNIX)
|
||||||
|
const QByteArray appImagePath = qgetenv("APPIMAGE");
|
||||||
|
if (appImagePath.isEmpty()) {
|
||||||
|
appData.addData(SingleApplication::app_t::applicationFilePath().toUtf8());
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
appData.addData(appImagePath);
|
||||||
|
};
|
||||||
|
#elif defined(Q_OS_WIN)
|
||||||
appData.addData(SingleApplication::app_t::applicationFilePath().toLower().toUtf8());
|
appData.addData(SingleApplication::app_t::applicationFilePath().toLower().toUtf8());
|
||||||
#else
|
#else
|
||||||
appData.addData(SingleApplication::app_t::applicationFilePath().toUtf8());
|
appData.addData(SingleApplication::app_t::applicationFilePath().toUtf8());
|
||||||
@@ -169,28 +179,26 @@ void SingleApplicationPrivate::genBlockServerName() {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void SingleApplicationPrivate::initializeMemoryBlock() {
|
void SingleApplicationPrivate::initializeMemoryBlock() const {
|
||||||
|
|
||||||
InstancesInfo *inst = static_cast<InstancesInfo*>(memory_->data());
|
InstancesInfo *instance = static_cast<InstancesInfo*>(memory_->data());
|
||||||
inst->primary = false;
|
instance->primary = false;
|
||||||
inst->secondary = 0;
|
instance->secondary = 0;
|
||||||
inst->primaryPid = -1;
|
instance->primaryPid = -1;
|
||||||
inst->primaryUser[0] = '\0';
|
instance->primaryUser[0] = '\0';
|
||||||
inst->checksum = blockChecksum();
|
instance->checksum = blockChecksum();
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void SingleApplicationPrivate::startPrimary() {
|
void SingleApplicationPrivate::startPrimary() {
|
||||||
|
|
||||||
Q_Q(SingleApplication);
|
|
||||||
|
|
||||||
// Reset the number of connections
|
// Reset the number of connections
|
||||||
InstancesInfo *inst = static_cast<InstancesInfo*>(memory_->data());
|
InstancesInfo *instance = static_cast<InstancesInfo*>(memory_->data());
|
||||||
|
|
||||||
inst->primary = true;
|
instance->primary = true;
|
||||||
inst->primaryPid = q->applicationPid();
|
instance->primaryPid = QCoreApplication::applicationPid();
|
||||||
qstrncpy(inst->primaryUser, getUsername().toUtf8().data(), sizeof(inst->primaryUser));
|
qstrncpy(instance->primaryUser, getUsername().toUtf8().data(), sizeof(instance->primaryUser));
|
||||||
inst->checksum = blockChecksum();
|
instance->checksum = blockChecksum();
|
||||||
instanceNumber_ = 0;
|
instanceNumber_ = 0;
|
||||||
// Successful creation means that no main process exists
|
// Successful creation means that no main process exists
|
||||||
// So we start a QLocalServer to listen for connections
|
// So we start a QLocalServer to listen for connections
|
||||||
@@ -212,11 +220,11 @@ void SingleApplicationPrivate::startPrimary() {
|
|||||||
|
|
||||||
void SingleApplicationPrivate::startSecondary() {
|
void SingleApplicationPrivate::startSecondary() {
|
||||||
|
|
||||||
InstancesInfo *inst = static_cast<InstancesInfo*>(memory_->data());
|
InstancesInfo *instance = static_cast<InstancesInfo*>(memory_->data());
|
||||||
|
|
||||||
inst->secondary += 1;
|
instance->secondary += 1;
|
||||||
inst->checksum = blockChecksum();
|
instance->checksum = blockChecksum();
|
||||||
instanceNumber_ = inst->secondary;
|
instanceNumber_ = instance->secondary;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -237,8 +245,9 @@ bool SingleApplicationPrivate::connectToPrimary(const int timeout, const Connect
|
|||||||
forever {
|
forever {
|
||||||
randomSleep();
|
randomSleep();
|
||||||
|
|
||||||
if (socket_->state() != QLocalSocket::ConnectingState)
|
if (socket_->state() != QLocalSocket::ConnectingState) {
|
||||||
socket_->connectToServer(blockServerName_);
|
socket_->connectToServer(blockServerName_);
|
||||||
|
}
|
||||||
|
|
||||||
if (socket_->state() == QLocalSocket::ConnectingState) {
|
if (socket_->state() == QLocalSocket::ConnectingState) {
|
||||||
socket_->waitForConnected(static_cast<int>(timeout - time.elapsed()));
|
socket_->waitForConnected(static_cast<int>(timeout - time.elapsed()));
|
||||||
@@ -262,29 +271,57 @@ bool SingleApplicationPrivate::connectToPrimary(const int timeout, const Connect
|
|||||||
writeStream << instanceNumber_;
|
writeStream << instanceNumber_;
|
||||||
|
|
||||||
#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
|
#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
|
||||||
quint16 checksum = qChecksum(QByteArray(initMsg, static_cast<quint32>(initMsg.length())));
|
quint16 checksum = qChecksum(QByteArray(initMsg, static_cast<quint32>(initMsg.length())));
|
||||||
#else
|
#else
|
||||||
quint16 checksum = qChecksum(initMsg.constData(), static_cast<quint32>(initMsg.length()));
|
quint16 checksum = qChecksum(initMsg.constData(), static_cast<quint32>(initMsg.length()));
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
writeStream << checksum;
|
writeStream << checksum;
|
||||||
|
|
||||||
// The header indicates the message length that follows
|
return writeConfirmedMessage(static_cast<int>(timeout - time.elapsed()), initMsg);
|
||||||
QByteArray header;
|
|
||||||
QDataStream headerStream(&header, QIODevice::WriteOnly);
|
|
||||||
headerStream.setVersion(QDataStream::Qt_5_8);
|
|
||||||
headerStream << static_cast<quint64>(initMsg.length());
|
|
||||||
|
|
||||||
socket_->write(header);
|
|
||||||
socket_->write(initMsg);
|
|
||||||
bool result = socket_->waitForBytesWritten(static_cast<int>(timeout - time.elapsed()));
|
|
||||||
socket_->flush();
|
|
||||||
|
|
||||||
return result;
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
quint16 SingleApplicationPrivate::blockChecksum() {
|
void SingleApplicationPrivate::writeAck(QLocalSocket *sock) {
|
||||||
|
sock->putChar('\n');
|
||||||
|
}
|
||||||
|
|
||||||
|
bool SingleApplicationPrivate::writeConfirmedMessage(const int timeout, const QByteArray &msg) const {
|
||||||
|
|
||||||
|
QElapsedTimer time;
|
||||||
|
time.start();
|
||||||
|
|
||||||
|
// Frame 1: The header indicates the message length that follows
|
||||||
|
QByteArray header;
|
||||||
|
QDataStream headerStream(&header, QIODevice::WriteOnly);
|
||||||
|
headerStream.setVersion(QDataStream::Qt_5_8);
|
||||||
|
headerStream << static_cast<quint64>(msg.length());
|
||||||
|
|
||||||
|
if (!writeConfirmedFrame(static_cast<int>(timeout - time.elapsed()), header)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Frame 2: The message
|
||||||
|
return writeConfirmedFrame(static_cast<int>(timeout - time.elapsed()), msg);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
bool SingleApplicationPrivate::writeConfirmedFrame(const int timeout, const QByteArray &msg) const {
|
||||||
|
|
||||||
|
socket_->write(msg);
|
||||||
|
socket_->flush();
|
||||||
|
|
||||||
|
bool result = socket_->waitForReadyRead(timeout);
|
||||||
|
if (result) {
|
||||||
|
socket_->read(1);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
quint16 SingleApplicationPrivate::blockChecksum() const {
|
||||||
|
|
||||||
#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
|
#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
|
||||||
quint16 checksum = qChecksum(QByteArray(static_cast<const char*>(memory_->constData()), offsetof(InstancesInfo, checksum)));
|
quint16 checksum = qChecksum(QByteArray(static_cast<const char*>(memory_->constData()), offsetof(InstancesInfo, checksum)));
|
||||||
@@ -296,22 +333,22 @@ quint16 SingleApplicationPrivate::blockChecksum() {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
qint64 SingleApplicationPrivate::primaryPid() {
|
qint64 SingleApplicationPrivate::primaryPid() const {
|
||||||
|
|
||||||
memory_->lock();
|
memory_->lock();
|
||||||
InstancesInfo *inst = static_cast<InstancesInfo*>(memory_->data());
|
InstancesInfo *instance = static_cast<InstancesInfo*>(memory_->data());
|
||||||
qint64 pid = inst->primaryPid;
|
qint64 pid = instance->primaryPid;
|
||||||
memory_->unlock();
|
memory_->unlock();
|
||||||
|
|
||||||
return pid;
|
return pid;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
QString SingleApplicationPrivate::primaryUser() {
|
QString SingleApplicationPrivate::primaryUser() const {
|
||||||
|
|
||||||
memory_->lock();
|
memory_->lock();
|
||||||
InstancesInfo *inst = static_cast<InstancesInfo*>(memory_->data());
|
InstancesInfo *instance = static_cast<InstancesInfo*>(memory_->data());
|
||||||
QByteArray username = inst->primaryUser;
|
QByteArray username = instance->primaryUser;
|
||||||
memory_->unlock();
|
memory_->unlock();
|
||||||
|
|
||||||
return QString::fromUtf8(username);
|
return QString::fromUtf8(username);
|
||||||
@@ -327,26 +364,30 @@ void SingleApplicationPrivate::slotConnectionEstablished() {
|
|||||||
connectionMap_.insert(nextConnSocket, ConnectionInfo());
|
connectionMap_.insert(nextConnSocket, ConnectionInfo());
|
||||||
|
|
||||||
QObject::connect(nextConnSocket, &QLocalSocket::aboutToClose, this, [nextConnSocket, this]() {
|
QObject::connect(nextConnSocket, &QLocalSocket::aboutToClose, this, [nextConnSocket, this]() {
|
||||||
auto &info = connectionMap_[nextConnSocket];
|
const ConnectionInfo &info = connectionMap_[nextConnSocket];
|
||||||
slotClientConnectionClosed(nextConnSocket, info.instanceId);
|
slotClientConnectionClosed(nextConnSocket, info.instanceId);
|
||||||
});
|
});
|
||||||
|
|
||||||
QObject::connect(nextConnSocket, &QLocalSocket::disconnected, this, [nextConnSocket, this]() {
|
QObject::connect(nextConnSocket, &QLocalSocket::disconnected, nextConnSocket, &QLocalSocket::deleteLater);
|
||||||
|
|
||||||
|
QObject::connect(nextConnSocket, &QLocalSocket::destroyed, this, [nextConnSocket, this]() {
|
||||||
connectionMap_.remove(nextConnSocket);
|
connectionMap_.remove(nextConnSocket);
|
||||||
nextConnSocket->deleteLater();
|
|
||||||
});
|
});
|
||||||
|
|
||||||
QObject::connect(nextConnSocket, &QLocalSocket::readyRead, this, [nextConnSocket, this]() {
|
QObject::connect(nextConnSocket, &QLocalSocket::readyRead, this, [nextConnSocket, this]() {
|
||||||
auto &info = connectionMap_[nextConnSocket];
|
const ConnectionInfo &info = connectionMap_[nextConnSocket];
|
||||||
switch (info.stage) {
|
switch (info.stage) {
|
||||||
case StageHeader:
|
case StageInitHeader:
|
||||||
readInitMessageHeader(nextConnSocket);
|
readMessageHeader(nextConnSocket, StageInitBody);
|
||||||
break;
|
break;
|
||||||
case StageBody:
|
case StageInitBody:
|
||||||
readInitMessageBody(nextConnSocket);
|
readInitMessageBody(nextConnSocket);
|
||||||
break;
|
break;
|
||||||
case StageConnected:
|
case StageConnectedHeader:
|
||||||
slotDataAvailable(nextConnSocket, info.instanceId);
|
readMessageHeader(nextConnSocket, StageConnectedBody);
|
||||||
|
break;
|
||||||
|
case StageConnectedBody:
|
||||||
|
this->slotDataAvailable(nextConnSocket, info.instanceId);
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
break;
|
break;
|
||||||
@@ -355,7 +396,7 @@ void SingleApplicationPrivate::slotConnectionEstablished() {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void SingleApplicationPrivate::readInitMessageHeader(QLocalSocket *sock) {
|
void SingleApplicationPrivate::readMessageHeader(QLocalSocket *sock, const SingleApplicationPrivate::ConnectionStage nextStage) {
|
||||||
|
|
||||||
if (!connectionMap_.contains(sock)) {
|
if (!connectionMap_.contains(sock)) {
|
||||||
return;
|
return;
|
||||||
@@ -372,30 +413,34 @@ void SingleApplicationPrivate::readInitMessageHeader(QLocalSocket *sock) {
|
|||||||
quint64 msgLen = 0;
|
quint64 msgLen = 0;
|
||||||
headerStream >> msgLen;
|
headerStream >> msgLen;
|
||||||
ConnectionInfo &info = connectionMap_[sock];
|
ConnectionInfo &info = connectionMap_[sock];
|
||||||
info.stage = StageBody;
|
info.stage = nextStage;
|
||||||
info.msgLen = msgLen;
|
info.msgLen = msgLen;
|
||||||
|
|
||||||
if (sock->bytesAvailable() >= static_cast<qint64>(msgLen)) {
|
writeAck(sock);
|
||||||
readInitMessageBody(sock);
|
|
||||||
|
}
|
||||||
|
|
||||||
|
bool SingleApplicationPrivate::isFrameComplete(QLocalSocket *sock) {
|
||||||
|
|
||||||
|
if (!connectionMap_.contains(sock)) {
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const ConnectionInfo &info = connectionMap_[sock];
|
||||||
|
return (sock->bytesAvailable() >= static_cast<qint64>(info.msgLen));
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void SingleApplicationPrivate::readInitMessageBody(QLocalSocket *sock) {
|
void SingleApplicationPrivate::readInitMessageBody(QLocalSocket *sock) {
|
||||||
|
|
||||||
Q_Q(SingleApplication);
|
Q_Q(SingleApplication);
|
||||||
|
|
||||||
if (!connectionMap_.contains(sock)) {
|
if (!isFrameComplete(sock)) {
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
ConnectionInfo &info = connectionMap_[sock];
|
|
||||||
if (sock->bytesAvailable() < static_cast<qint64>(info.msgLen)) {
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Read the message body
|
// Read the message body
|
||||||
QByteArray msgBytes = sock->read(info.msgLen);
|
QByteArray msgBytes = sock->readAll();
|
||||||
QDataStream readStream(msgBytes);
|
QDataStream readStream(msgBytes);
|
||||||
readStream.setVersion(QDataStream::Qt_5_8);
|
readStream.setVersion(QDataStream::Qt_5_8);
|
||||||
|
|
||||||
@@ -430,23 +475,34 @@ void SingleApplicationPrivate::readInitMessageBody(QLocalSocket *sock) {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ConnectionInfo &info = connectionMap_[sock];
|
||||||
info.instanceId = instanceId;
|
info.instanceId = instanceId;
|
||||||
info.stage = StageConnected;
|
info.stage = StageConnectedHeader;
|
||||||
|
|
||||||
if (connectionType == NewInstance || (connectionType == SecondaryInstance && options_ & SingleApplication::Mode::SecondaryNotification)) {
|
if (connectionType == NewInstance || (connectionType == SecondaryInstance && options_ & SingleApplication::Mode::SecondaryNotification)) {
|
||||||
Q_EMIT q->instanceStarted();
|
emit q->instanceStarted();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (sock->bytesAvailable() > 0) {
|
writeAck(sock);
|
||||||
slotDataAvailable(sock, instanceId);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void SingleApplicationPrivate::slotDataAvailable(QLocalSocket *dataSocket, const quint32 instanceId) {
|
void SingleApplicationPrivate::slotDataAvailable(QLocalSocket *dataSocket, const quint32 instanceId) {
|
||||||
|
|
||||||
Q_Q(SingleApplication);
|
Q_Q(SingleApplication);
|
||||||
Q_EMIT q->receivedMessage(instanceId, dataSocket->readAll());
|
|
||||||
|
if (!isFrameComplete(dataSocket)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const QByteArray message = dataSocket->readAll();
|
||||||
|
|
||||||
|
writeAck(dataSocket);
|
||||||
|
|
||||||
|
ConnectionInfo &info = connectionMap_[dataSocket];
|
||||||
|
info.stage = StageConnectedHeader;
|
||||||
|
|
||||||
|
emit q->receivedMessage(instanceId, message);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -461,10 +517,10 @@ void SingleApplicationPrivate::slotClientConnectionClosed(QLocalSocket *closedSo
|
|||||||
void SingleApplicationPrivate::randomSleep() {
|
void SingleApplicationPrivate::randomSleep() {
|
||||||
|
|
||||||
#if QT_VERSION >= QT_VERSION_CHECK(5, 10, 0)
|
#if QT_VERSION >= QT_VERSION_CHECK(5, 10, 0)
|
||||||
QThread::msleep(QRandomGenerator::global()->bounded(8u, 18u));
|
QThread::msleep(QRandomGenerator::global()->bounded(8U, 18U));
|
||||||
#else
|
#else
|
||||||
qsrand(QDateTime::currentMSecsSinceEpoch() % std::numeric_limits<uint>::max());
|
qsrand(QDateTime::currentMSecsSinceEpoch() % std::numeric_limits<uint>::max());
|
||||||
QThread::msleep(8 + static_cast<unsigned long>(static_cast<float>(qrand()) / RAND_MAX * 10));
|
QThread::msleep(qrand() % 11 + 8);
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
31
3rdparty/singleapplication/singleapplication_p.h
vendored
@@ -37,7 +37,7 @@
|
|||||||
#include <QtGlobal>
|
#include <QtGlobal>
|
||||||
#include <QObject>
|
#include <QObject>
|
||||||
#include <QString>
|
#include <QString>
|
||||||
#include <QMap>
|
#include <QHash>
|
||||||
|
|
||||||
#include "singleapplication.h"
|
#include "singleapplication.h"
|
||||||
|
|
||||||
@@ -55,7 +55,7 @@ struct InstancesInfo {
|
|||||||
|
|
||||||
struct ConnectionInfo {
|
struct ConnectionInfo {
|
||||||
explicit ConnectionInfo() : msgLen(0), instanceId(0), stage(0) {}
|
explicit ConnectionInfo() : msgLen(0), instanceId(0), stage(0) {}
|
||||||
qint64 msgLen;
|
quint64 msgLen;
|
||||||
quint32 instanceId;
|
quint32 instanceId;
|
||||||
quint8 stage;
|
quint8 stage;
|
||||||
};
|
};
|
||||||
@@ -71,27 +71,32 @@ class SingleApplicationPrivate : public QObject {
|
|||||||
Reconnect = 3
|
Reconnect = 3
|
||||||
};
|
};
|
||||||
enum ConnectionStage : quint8 {
|
enum ConnectionStage : quint8 {
|
||||||
StageHeader = 0,
|
StageInitHeader = 0,
|
||||||
StageBody = 1,
|
StageInitBody = 1,
|
||||||
StageConnected = 2,
|
StageConnectedHeader = 2,
|
||||||
|
StageConnectedBody = 3,
|
||||||
};
|
};
|
||||||
Q_DECLARE_PUBLIC(SingleApplication)
|
Q_DECLARE_PUBLIC(SingleApplication)
|
||||||
|
|
||||||
explicit SingleApplicationPrivate(SingleApplication *ptr);
|
explicit SingleApplicationPrivate(SingleApplication *ptr);
|
||||||
~SingleApplicationPrivate() override;
|
~SingleApplicationPrivate() override;
|
||||||
|
|
||||||
QString getUsername();
|
static QString getUsername();
|
||||||
void genBlockServerName();
|
void genBlockServerName();
|
||||||
void initializeMemoryBlock();
|
void initializeMemoryBlock() const;
|
||||||
void startPrimary();
|
void startPrimary();
|
||||||
void startSecondary();
|
void startSecondary();
|
||||||
bool connectToPrimary(const int timeout, const ConnectionType connectionType);
|
bool connectToPrimary(const int timeout, const ConnectionType connectionType);
|
||||||
quint16 blockChecksum();
|
quint16 blockChecksum() const;
|
||||||
qint64 primaryPid();
|
qint64 primaryPid() const;
|
||||||
QString primaryUser();
|
QString primaryUser() const;
|
||||||
void readInitMessageHeader(QLocalSocket *socket);
|
bool isFrameComplete(QLocalSocket *sock);
|
||||||
|
void readMessageHeader(QLocalSocket *socket, const ConnectionStage nextStage);
|
||||||
void readInitMessageBody(QLocalSocket *socket);
|
void readInitMessageBody(QLocalSocket *socket);
|
||||||
void randomSleep();
|
void writeAck(QLocalSocket *sock);
|
||||||
|
bool writeConfirmedFrame(const int timeout, const QByteArray &msg) const;
|
||||||
|
bool writeConfirmedMessage(const int timeout, const QByteArray &msg) const;
|
||||||
|
static void randomSleep();
|
||||||
|
|
||||||
SingleApplication *q_ptr;
|
SingleApplication *q_ptr;
|
||||||
QSharedMemory *memory_;
|
QSharedMemory *memory_;
|
||||||
@@ -100,7 +105,7 @@ class SingleApplicationPrivate : public QObject {
|
|||||||
quint32 instanceNumber_;
|
quint32 instanceNumber_;
|
||||||
QString blockServerName_;
|
QString blockServerName_;
|
||||||
SingleApplication::Options options_;
|
SingleApplication::Options options_;
|
||||||
QMap<QLocalSocket*, ConnectionInfo> connectionMap_;
|
QHash<QLocalSocket*, ConnectionInfo> connectionMap_;
|
||||||
|
|
||||||
public slots:
|
public slots:
|
||||||
void slotConnectionEstablished();
|
void slotConnectionEstablished();
|
||||||
|
|||||||
@@ -54,7 +54,7 @@
|
|||||||
* @param options Optional flags to toggle specific behaviour
|
* @param options Optional flags to toggle specific behaviour
|
||||||
* @param timeout Maximum time blocking functions are allowed during app load
|
* @param timeout Maximum time blocking functions are allowed during app load
|
||||||
*/
|
*/
|
||||||
SingleCoreApplication::SingleCoreApplication(int &argc, char *argv[], bool allowSecondary, Options options, int timeout)
|
SingleCoreApplication::SingleCoreApplication(int &argc, char *argv[], const bool allowSecondary, const Options options, const int timeout)
|
||||||
: app_t(argc, argv),
|
: app_t(argc, argv),
|
||||||
d_ptr(new SingleCoreApplicationPrivate(this)) {
|
d_ptr(new SingleCoreApplicationPrivate(this)) {
|
||||||
|
|
||||||
@@ -67,7 +67,7 @@ SingleCoreApplication::SingleCoreApplication(int &argc, char *argv[], bool allow
|
|||||||
d->genBlockServerName();
|
d->genBlockServerName();
|
||||||
|
|
||||||
// To mitigate QSharedMemory issues with large amount of processes attempting to attach at the same time
|
// To mitigate QSharedMemory issues with large amount of processes attempting to attach at the same time
|
||||||
d->randomSleep();
|
SingleCoreApplicationPrivate::randomSleep();
|
||||||
|
|
||||||
#ifdef Q_OS_UNIX
|
#ifdef Q_OS_UNIX
|
||||||
// By explicitly attaching it and then deleting it we make sure that the memory is deleted even after the process has crashed on Unix.
|
// By explicitly attaching it and then deleting it we make sure that the memory is deleted even after the process has crashed on Unix.
|
||||||
@@ -106,14 +106,14 @@ SingleCoreApplication::SingleCoreApplication(int &argc, char *argv[], bool allow
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
InstancesInfo *inst = static_cast<InstancesInfo*>(d->memory_->data());
|
InstancesInfo *instance = static_cast<InstancesInfo*>(d->memory_->data());
|
||||||
QElapsedTimer time;
|
QElapsedTimer time;
|
||||||
time.start();
|
time.start();
|
||||||
|
|
||||||
// Make sure the shared memory block is initialised and in consistent state
|
// Make sure the shared memory block is initialised and in consistent state
|
||||||
forever {
|
forever {
|
||||||
// If the shared memory block's checksum is valid continue
|
// If the shared memory block's checksum is valid continue
|
||||||
if (d->blockChecksum() == inst->checksum) break;
|
if (d->blockChecksum() == instance->checksum) break;
|
||||||
|
|
||||||
// If more than 5s have elapsed, assume the primary instance crashed and assume it's position
|
// If more than 5s have elapsed, assume the primary instance crashed and assume it's position
|
||||||
if (time.elapsed() > 5000) {
|
if (time.elapsed() > 5000) {
|
||||||
@@ -127,14 +127,14 @@ SingleCoreApplication::SingleCoreApplication(int &argc, char *argv[], bool allow
|
|||||||
qDebug() << "SingleCoreApplication: Unable to unlock memory for random wait.";
|
qDebug() << "SingleCoreApplication: Unable to unlock memory for random wait.";
|
||||||
qDebug() << d->memory_->errorString();
|
qDebug() << d->memory_->errorString();
|
||||||
}
|
}
|
||||||
d->randomSleep();
|
SingleCoreApplicationPrivate::randomSleep();
|
||||||
if (!d->memory_->lock()) {
|
if (!d->memory_->lock()) {
|
||||||
qCritical() << "SingleCoreApplication: Unable to lock memory after random wait.";
|
qCritical() << "SingleCoreApplication: Unable to lock memory after random wait.";
|
||||||
abortSafely();
|
abortSafely();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (inst->primary == false) {
|
if (!instance->primary) {
|
||||||
d->startPrimary();
|
d->startPrimary();
|
||||||
if (!d->memory_->unlock()) {
|
if (!d->memory_->unlock()) {
|
||||||
qDebug() << "SingleCoreApplication: Unable to unlock memory after primary start.";
|
qDebug() << "SingleCoreApplication: Unable to unlock memory after primary start.";
|
||||||
@@ -178,8 +178,8 @@ SingleCoreApplication::~SingleCoreApplication() {
|
|||||||
* Checks if the current application instance is primary.
|
* Checks if the current application instance is primary.
|
||||||
* @return Returns true if the instance is primary, false otherwise.
|
* @return Returns true if the instance is primary, false otherwise.
|
||||||
*/
|
*/
|
||||||
bool SingleCoreApplication::isPrimary() {
|
bool SingleCoreApplication::isPrimary() const {
|
||||||
Q_D(SingleCoreApplication);
|
Q_D(const SingleCoreApplication);
|
||||||
return d->server_ != nullptr;
|
return d->server_ != nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -187,8 +187,8 @@ bool SingleCoreApplication::isPrimary() {
|
|||||||
* Checks if the current application instance is secondary.
|
* Checks if the current application instance is secondary.
|
||||||
* @return Returns true if the instance is secondary, false otherwise.
|
* @return Returns true if the instance is secondary, false otherwise.
|
||||||
*/
|
*/
|
||||||
bool SingleCoreApplication::isSecondary() {
|
bool SingleCoreApplication::isSecondary() const {
|
||||||
Q_D(SingleCoreApplication);
|
Q_D(const SingleCoreApplication);
|
||||||
return d->server_ == nullptr;
|
return d->server_ == nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -197,8 +197,8 @@ bool SingleCoreApplication::isSecondary() {
|
|||||||
* It is reset when the first (primary) instance of your app starts and only incremented afterwards.
|
* It is reset when the first (primary) instance of your app starts and only incremented afterwards.
|
||||||
* @return Returns a unique instance id.
|
* @return Returns a unique instance id.
|
||||||
*/
|
*/
|
||||||
quint32 SingleCoreApplication::instanceId() {
|
quint32 SingleCoreApplication::instanceId() const {
|
||||||
Q_D(SingleCoreApplication);
|
Q_D(const SingleCoreApplication);
|
||||||
return d->instanceNumber_;
|
return d->instanceNumber_;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -207,8 +207,8 @@ quint32 SingleCoreApplication::instanceId() {
|
|||||||
* Especially useful when SingleCoreApplication is coupled with OS. specific APIs.
|
* Especially useful when SingleCoreApplication is coupled with OS. specific APIs.
|
||||||
* @return Returns the primary instance PID.
|
* @return Returns the primary instance PID.
|
||||||
*/
|
*/
|
||||||
qint64 SingleCoreApplication::primaryPid() {
|
qint64 SingleCoreApplication::primaryPid() const {
|
||||||
Q_D(SingleCoreApplication);
|
Q_D(const SingleCoreApplication);
|
||||||
return d->primaryPid();
|
return d->primaryPid();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -216,8 +216,8 @@ qint64 SingleCoreApplication::primaryPid() {
|
|||||||
* Returns the username the primary instance is running as.
|
* Returns the username the primary instance is running as.
|
||||||
* @return Returns the username the primary instance is running as.
|
* @return Returns the username the primary instance is running as.
|
||||||
*/
|
*/
|
||||||
QString SingleCoreApplication::primaryUser() {
|
QString SingleCoreApplication::primaryUser() const {
|
||||||
Q_D(SingleCoreApplication);
|
Q_D(const SingleCoreApplication);
|
||||||
return d->primaryUser();
|
return d->primaryUser();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -225,18 +225,17 @@ QString SingleCoreApplication::primaryUser() {
|
|||||||
* Returns the username the current instance is running as.
|
* Returns the username the current instance is running as.
|
||||||
* @return Returns the username the current instance is running as.
|
* @return Returns the username the current instance is running as.
|
||||||
*/
|
*/
|
||||||
QString SingleCoreApplication::currentUser() {
|
QString SingleCoreApplication::currentUser() const {
|
||||||
Q_D(SingleCoreApplication);
|
return SingleCoreApplicationPrivate::getUsername();
|
||||||
return d->getUsername();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sends message to the Primary Instance.
|
* Sends message to the Primary Instance.
|
||||||
* @param message The message to send.
|
* @param message The message to send.
|
||||||
* @param timeout the maximum timeout in milliseconds for blocking functions.
|
* @param timeout the maximum timeout in milliseconds for blocking functions.
|
||||||
* @return true if the message was sent successfuly, false otherwise.
|
* @return true if the message was sent successfully, false otherwise.
|
||||||
*/
|
*/
|
||||||
bool SingleCoreApplication::sendMessage(QByteArray message, int timeout) {
|
bool SingleCoreApplication::sendMessage(const QByteArray &message, const int timeout) {
|
||||||
|
|
||||||
Q_D(SingleCoreApplication);
|
Q_D(SingleCoreApplication);
|
||||||
|
|
||||||
@@ -244,14 +243,11 @@ bool SingleCoreApplication::sendMessage(QByteArray message, int timeout) {
|
|||||||
if (isPrimary()) return false;
|
if (isPrimary()) return false;
|
||||||
|
|
||||||
// Make sure the socket is connected
|
// Make sure the socket is connected
|
||||||
if (!d->connectToPrimary(timeout, SingleCoreApplicationPrivate::Reconnect))
|
if (!d->connectToPrimary(timeout, SingleCoreApplicationPrivate::Reconnect)) {
|
||||||
return false;
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
d->socket_->write(message);
|
return d->writeConfirmedMessage(timeout, message);
|
||||||
const bool dataWritten = d->socket_->waitForBytesWritten(timeout);
|
|
||||||
d->socket_->flush();
|
|
||||||
return dataWritten;
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -264,6 +260,7 @@ void SingleCoreApplication::abortSafely() {
|
|||||||
|
|
||||||
qCritical() << "SingleCoreApplication: " << d->memory_->error() << d->memory_->errorString();
|
qCritical() << "SingleCoreApplication: " << d->memory_->error() << d->memory_->errorString();
|
||||||
delete d;
|
delete d;
|
||||||
|
|
||||||
::exit(EXIT_FAILURE);
|
::exit(EXIT_FAILURE);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -42,13 +42,13 @@
|
|||||||
class SingleCoreApplicationPrivate;
|
class SingleCoreApplicationPrivate;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief The SingleCoreApplication class handles multipe instances of the same Application
|
* @brief The SingleCoreApplication class handles multiple instances of the same Application
|
||||||
* @see QCoreApplication
|
* @see QCoreApplication
|
||||||
*/
|
*/
|
||||||
class SingleCoreApplication : public QCoreApplication {
|
class SingleCoreApplication : public QCoreApplication { // clazy:exclude=ctor-missing-parent-argument
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
|
|
||||||
typedef QCoreApplication app_t;
|
using app_t = QCoreApplication;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
/**
|
/**
|
||||||
@@ -96,37 +96,37 @@ class SingleCoreApplication : public QCoreApplication {
|
|||||||
* @brief Returns if the instance is the primary instance
|
* @brief Returns if the instance is the primary instance
|
||||||
* @returns {bool}
|
* @returns {bool}
|
||||||
*/
|
*/
|
||||||
bool isPrimary();
|
bool isPrimary() const;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Returns if the instance is a secondary instance
|
* @brief Returns if the instance is a secondary instance
|
||||||
* @returns {bool}
|
* @returns {bool}
|
||||||
*/
|
*/
|
||||||
bool isSecondary();
|
bool isSecondary() const;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Returns a unique identifier for the current instance
|
* @brief Returns a unique identifier for the current instance
|
||||||
* @returns {qint32}
|
* @returns {qint32}
|
||||||
*/
|
*/
|
||||||
quint32 instanceId();
|
quint32 instanceId() const;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Returns the process ID (PID) of the primary instance
|
* @brief Returns the process ID (PID) of the primary instance
|
||||||
* @returns {qint64}
|
* @returns {qint64}
|
||||||
*/
|
*/
|
||||||
qint64 primaryPid();
|
qint64 primaryPid() const;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Returns the username of the user running the primary instance
|
* @brief Returns the username of the user running the primary instance
|
||||||
* @returns {QString}
|
* @returns {QString}
|
||||||
*/
|
*/
|
||||||
QString primaryUser();
|
QString primaryUser() const;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Returns the username of the current user
|
* @brief Returns the username of the current user
|
||||||
* @returns {QString}
|
* @returns {QString}
|
||||||
*/
|
*/
|
||||||
QString currentUser();
|
QString currentUser() const;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Sends a message to the primary instance. Returns true on success.
|
* @brief Sends a message to the primary instance. Returns true on success.
|
||||||
@@ -135,7 +135,7 @@ class SingleCoreApplication : public QCoreApplication {
|
|||||||
* @note sendMessage() will return false if invoked from the primary
|
* @note sendMessage() will return false if invoked from the primary
|
||||||
* instance.
|
* instance.
|
||||||
*/
|
*/
|
||||||
bool sendMessage(QByteArray message, int timeout = 1000);
|
bool sendMessage(const QByteArray &message, const int timeout = 1000);
|
||||||
|
|
||||||
signals:
|
signals:
|
||||||
void instanceStarted();
|
void instanceStarted();
|
||||||
|
|||||||
@@ -44,6 +44,14 @@
|
|||||||
# include <pwd.h>
|
# include <pwd.h>
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
#ifdef Q_OS_WIN
|
||||||
|
# ifndef NOMINMAX
|
||||||
|
# define NOMINMAX 1
|
||||||
|
# endif
|
||||||
|
# include <windows.h>
|
||||||
|
# include <lmcons.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
#include <QObject>
|
#include <QObject>
|
||||||
#include <QThread>
|
#include <QThread>
|
||||||
#include <QIODevice>
|
#include <QIODevice>
|
||||||
@@ -53,7 +61,6 @@
|
|||||||
#include <QCryptographicHash>
|
#include <QCryptographicHash>
|
||||||
#include <QLocalServer>
|
#include <QLocalServer>
|
||||||
#include <QLocalSocket>
|
#include <QLocalSocket>
|
||||||
#include <QDir>
|
|
||||||
#include <QElapsedTimer>
|
#include <QElapsedTimer>
|
||||||
#if QT_VERSION >= QT_VERSION_CHECK(5, 10, 0)
|
#if QT_VERSION >= QT_VERSION_CHECK(5, 10, 0)
|
||||||
# include <QRandomGenerator>
|
# include <QRandomGenerator>
|
||||||
@@ -64,11 +71,6 @@
|
|||||||
#include "singlecoreapplication.h"
|
#include "singlecoreapplication.h"
|
||||||
#include "singlecoreapplication_p.h"
|
#include "singlecoreapplication_p.h"
|
||||||
|
|
||||||
#ifdef Q_OS_WIN
|
|
||||||
# include <windows.h>
|
|
||||||
# include <lmcons.h>
|
|
||||||
#endif
|
|
||||||
|
|
||||||
SingleCoreApplicationPrivate::SingleCoreApplicationPrivate(SingleCoreApplication *ptr)
|
SingleCoreApplicationPrivate::SingleCoreApplicationPrivate(SingleCoreApplication *ptr)
|
||||||
: q_ptr(ptr),
|
: q_ptr(ptr),
|
||||||
memory_(nullptr),
|
memory_(nullptr),
|
||||||
@@ -86,14 +88,14 @@ SingleCoreApplicationPrivate::~SingleCoreApplicationPrivate() {
|
|||||||
|
|
||||||
if (memory_ != nullptr) {
|
if (memory_ != nullptr) {
|
||||||
memory_->lock();
|
memory_->lock();
|
||||||
InstancesInfo *inst = static_cast<InstancesInfo*>(memory_->data());
|
InstancesInfo *instance = static_cast<InstancesInfo*>(memory_->data());
|
||||||
if (server_ != nullptr) {
|
if (server_ != nullptr) {
|
||||||
server_->close();
|
server_->close();
|
||||||
delete server_;
|
delete server_;
|
||||||
inst->primary = false;
|
instance->primary = false;
|
||||||
inst->primaryPid = -1;
|
instance->primaryPid = -1;
|
||||||
inst->primaryUser[0] = '\0';
|
instance->primaryUser[0] = '\0';
|
||||||
inst->checksum = blockChecksum();
|
instance->checksum = blockChecksum();
|
||||||
}
|
}
|
||||||
memory_->unlock();
|
memory_->unlock();
|
||||||
|
|
||||||
@@ -142,7 +144,7 @@ QString SingleCoreApplicationPrivate::getUsername() {
|
|||||||
void SingleCoreApplicationPrivate::genBlockServerName() {
|
void SingleCoreApplicationPrivate::genBlockServerName() {
|
||||||
|
|
||||||
QCryptographicHash appData(QCryptographicHash::Sha256);
|
QCryptographicHash appData(QCryptographicHash::Sha256);
|
||||||
appData.addData("SingleApplication", 17);
|
appData.addData("SingleApplication");
|
||||||
appData.addData(SingleCoreApplication::app_t::applicationName().toUtf8());
|
appData.addData(SingleCoreApplication::app_t::applicationName().toUtf8());
|
||||||
appData.addData(SingleCoreApplication::app_t::organizationName().toUtf8());
|
appData.addData(SingleCoreApplication::app_t::organizationName().toUtf8());
|
||||||
appData.addData(SingleCoreApplication::app_t::organizationDomain().toUtf8());
|
appData.addData(SingleCoreApplication::app_t::organizationDomain().toUtf8());
|
||||||
@@ -152,7 +154,15 @@ void SingleCoreApplicationPrivate::genBlockServerName() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (!(options_ & SingleCoreApplication::Mode::ExcludeAppPath)) {
|
if (!(options_ & SingleCoreApplication::Mode::ExcludeAppPath)) {
|
||||||
#ifdef Q_OS_WIN
|
#if defined(Q_OS_UNIX)
|
||||||
|
const QByteArray appImagePath = qgetenv("APPIMAGE");
|
||||||
|
if (appImagePath.isEmpty()) {
|
||||||
|
appData.addData(SingleCoreApplication::app_t::applicationFilePath().toUtf8());
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
appData.addData(appImagePath);
|
||||||
|
};
|
||||||
|
#elif defined(Q_OS_WIN)
|
||||||
appData.addData(SingleCoreApplication::app_t::applicationFilePath().toLower().toUtf8());
|
appData.addData(SingleCoreApplication::app_t::applicationFilePath().toLower().toUtf8());
|
||||||
#else
|
#else
|
||||||
appData.addData(SingleCoreApplication::app_t::applicationFilePath().toUtf8());
|
appData.addData(SingleCoreApplication::app_t::applicationFilePath().toUtf8());
|
||||||
@@ -169,28 +179,26 @@ void SingleCoreApplicationPrivate::genBlockServerName() {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void SingleCoreApplicationPrivate::initializeMemoryBlock() {
|
void SingleCoreApplicationPrivate::initializeMemoryBlock() const {
|
||||||
|
|
||||||
InstancesInfo *inst = static_cast<InstancesInfo*>(memory_->data());
|
InstancesInfo *instance = static_cast<InstancesInfo*>(memory_->data());
|
||||||
inst->primary = false;
|
instance->primary = false;
|
||||||
inst->secondary = 0;
|
instance->secondary = 0;
|
||||||
inst->primaryPid = -1;
|
instance->primaryPid = -1;
|
||||||
inst->primaryUser[0] = '\0';
|
instance->primaryUser[0] = '\0';
|
||||||
inst->checksum = blockChecksum();
|
instance->checksum = blockChecksum();
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void SingleCoreApplicationPrivate::startPrimary() {
|
void SingleCoreApplicationPrivate::startPrimary() {
|
||||||
|
|
||||||
Q_Q(SingleCoreApplication);
|
|
||||||
|
|
||||||
// Reset the number of connections
|
// Reset the number of connections
|
||||||
InstancesInfo *inst = static_cast<InstancesInfo*>(memory_->data());
|
InstancesInfo *instance = static_cast<InstancesInfo*>(memory_->data());
|
||||||
|
|
||||||
inst->primary = true;
|
instance->primary = true;
|
||||||
inst->primaryPid = q->applicationPid();
|
instance->primaryPid = QCoreApplication::applicationPid();
|
||||||
qstrncpy(inst->primaryUser, getUsername().toUtf8().data(), sizeof(inst->primaryUser));
|
qstrncpy(instance->primaryUser, getUsername().toUtf8().data(), sizeof(instance->primaryUser));
|
||||||
inst->checksum = blockChecksum();
|
instance->checksum = blockChecksum();
|
||||||
instanceNumber_ = 0;
|
instanceNumber_ = 0;
|
||||||
// Successful creation means that no main process exists
|
// Successful creation means that no main process exists
|
||||||
// So we start a QLocalServer to listen for connections
|
// So we start a QLocalServer to listen for connections
|
||||||
@@ -212,11 +220,11 @@ void SingleCoreApplicationPrivate::startPrimary() {
|
|||||||
|
|
||||||
void SingleCoreApplicationPrivate::startSecondary() {
|
void SingleCoreApplicationPrivate::startSecondary() {
|
||||||
|
|
||||||
InstancesInfo *inst = static_cast<InstancesInfo*>(memory_->data());
|
InstancesInfo *instance = static_cast<InstancesInfo*>(memory_->data());
|
||||||
|
|
||||||
inst->secondary += 1;
|
instance->secondary += 1;
|
||||||
inst->checksum = blockChecksum();
|
instance->checksum = blockChecksum();
|
||||||
instanceNumber_ = inst->secondary;
|
instanceNumber_ = instance->secondary;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -237,8 +245,9 @@ bool SingleCoreApplicationPrivate::connectToPrimary(const int timeout, const Con
|
|||||||
forever {
|
forever {
|
||||||
randomSleep();
|
randomSleep();
|
||||||
|
|
||||||
if (socket_->state() != QLocalSocket::ConnectingState)
|
if (socket_->state() != QLocalSocket::ConnectingState) {
|
||||||
socket_->connectToServer(blockServerName_);
|
socket_->connectToServer(blockServerName_);
|
||||||
|
}
|
||||||
|
|
||||||
if (socket_->state() == QLocalSocket::ConnectingState) {
|
if (socket_->state() == QLocalSocket::ConnectingState) {
|
||||||
socket_->waitForConnected(static_cast<int>(timeout - time.elapsed()));
|
socket_->waitForConnected(static_cast<int>(timeout - time.elapsed()));
|
||||||
@@ -262,29 +271,57 @@ bool SingleCoreApplicationPrivate::connectToPrimary(const int timeout, const Con
|
|||||||
writeStream << instanceNumber_;
|
writeStream << instanceNumber_;
|
||||||
|
|
||||||
#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
|
#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
|
||||||
quint16 checksum = qChecksum(QByteArray(initMsg, static_cast<quint32>(initMsg.length())));
|
quint16 checksum = qChecksum(QByteArray(initMsg, static_cast<quint32>(initMsg.length())));
|
||||||
#else
|
#else
|
||||||
quint16 checksum = qChecksum(initMsg.constData(), static_cast<quint32>(initMsg.length()));
|
quint16 checksum = qChecksum(initMsg.constData(), static_cast<quint32>(initMsg.length()));
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
writeStream << checksum;
|
writeStream << checksum;
|
||||||
|
|
||||||
// The header indicates the message length that follows
|
return writeConfirmedMessage(static_cast<int>(timeout - time.elapsed()), initMsg);
|
||||||
QByteArray header;
|
|
||||||
QDataStream headerStream(&header, QIODevice::WriteOnly);
|
|
||||||
headerStream.setVersion(QDataStream::Qt_5_8);
|
|
||||||
headerStream << static_cast<quint64>(initMsg.length());
|
|
||||||
|
|
||||||
socket_->write(header);
|
|
||||||
socket_->write(initMsg);
|
|
||||||
bool result = socket_->waitForBytesWritten(timeout - static_cast<int>(time.elapsed()));
|
|
||||||
socket_->flush();
|
|
||||||
|
|
||||||
return result;
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
quint16 SingleCoreApplicationPrivate::blockChecksum() {
|
void SingleCoreApplicationPrivate::writeAck(QLocalSocket *sock) {
|
||||||
|
sock->putChar('\n');
|
||||||
|
}
|
||||||
|
|
||||||
|
bool SingleCoreApplicationPrivate::writeConfirmedMessage(const int timeout, const QByteArray &msg) const {
|
||||||
|
|
||||||
|
QElapsedTimer time;
|
||||||
|
time.start();
|
||||||
|
|
||||||
|
// Frame 1: The header indicates the message length that follows
|
||||||
|
QByteArray header;
|
||||||
|
QDataStream headerStream(&header, QIODevice::WriteOnly);
|
||||||
|
headerStream.setVersion(QDataStream::Qt_5_8);
|
||||||
|
headerStream << static_cast<quint64>(msg.length());
|
||||||
|
|
||||||
|
if (!writeConfirmedFrame(static_cast<int>(timeout - time.elapsed()), header)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Frame 2: The message
|
||||||
|
return writeConfirmedFrame(static_cast<int>(timeout - time.elapsed()), msg);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
bool SingleCoreApplicationPrivate::writeConfirmedFrame(const int timeout, const QByteArray &msg) const {
|
||||||
|
|
||||||
|
socket_->write(msg);
|
||||||
|
socket_->flush();
|
||||||
|
|
||||||
|
bool result = socket_->waitForReadyRead(timeout);
|
||||||
|
if (result) {
|
||||||
|
socket_->read(1);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
quint16 SingleCoreApplicationPrivate::blockChecksum() const {
|
||||||
|
|
||||||
#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
|
#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
|
||||||
quint16 checksum = qChecksum(QByteArray(static_cast<const char*>(memory_->constData()), offsetof(InstancesInfo, checksum)));
|
quint16 checksum = qChecksum(QByteArray(static_cast<const char*>(memory_->constData()), offsetof(InstancesInfo, checksum)));
|
||||||
@@ -296,22 +333,22 @@ quint16 SingleCoreApplicationPrivate::blockChecksum() {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
qint64 SingleCoreApplicationPrivate::primaryPid() {
|
qint64 SingleCoreApplicationPrivate::primaryPid() const {
|
||||||
|
|
||||||
memory_->lock();
|
memory_->lock();
|
||||||
InstancesInfo *inst = static_cast<InstancesInfo*>(memory_->data());
|
InstancesInfo *instance = static_cast<InstancesInfo*>(memory_->data());
|
||||||
qint64 pid = inst->primaryPid;
|
qint64 pid = instance->primaryPid;
|
||||||
memory_->unlock();
|
memory_->unlock();
|
||||||
|
|
||||||
return pid;
|
return pid;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
QString SingleCoreApplicationPrivate::primaryUser() {
|
QString SingleCoreApplicationPrivate::primaryUser() const {
|
||||||
|
|
||||||
memory_->lock();
|
memory_->lock();
|
||||||
InstancesInfo *inst = static_cast<InstancesInfo*>(memory_->data());
|
InstancesInfo *instance = static_cast<InstancesInfo*>(memory_->data());
|
||||||
QByteArray username = inst->primaryUser;
|
QByteArray username = instance->primaryUser;
|
||||||
memory_->unlock();
|
memory_->unlock();
|
||||||
|
|
||||||
return QString::fromUtf8(username);
|
return QString::fromUtf8(username);
|
||||||
@@ -327,26 +364,30 @@ void SingleCoreApplicationPrivate::slotConnectionEstablished() {
|
|||||||
connectionMap_.insert(nextConnSocket, ConnectionInfo());
|
connectionMap_.insert(nextConnSocket, ConnectionInfo());
|
||||||
|
|
||||||
QObject::connect(nextConnSocket, &QLocalSocket::aboutToClose, this, [nextConnSocket, this]() {
|
QObject::connect(nextConnSocket, &QLocalSocket::aboutToClose, this, [nextConnSocket, this]() {
|
||||||
auto &info = connectionMap_[nextConnSocket];
|
const ConnectionInfo &info = connectionMap_[nextConnSocket];
|
||||||
slotClientConnectionClosed(nextConnSocket, info.instanceId);
|
slotClientConnectionClosed(nextConnSocket, info.instanceId);
|
||||||
});
|
});
|
||||||
|
|
||||||
QObject::connect(nextConnSocket, &QLocalSocket::disconnected, this, [nextConnSocket, this]() {
|
QObject::connect(nextConnSocket, &QLocalSocket::disconnected, nextConnSocket, &QLocalSocket::deleteLater);
|
||||||
|
|
||||||
|
QObject::connect(nextConnSocket, &QLocalSocket::destroyed, this, [nextConnSocket, this]() {
|
||||||
connectionMap_.remove(nextConnSocket);
|
connectionMap_.remove(nextConnSocket);
|
||||||
nextConnSocket->deleteLater();
|
|
||||||
});
|
});
|
||||||
|
|
||||||
QObject::connect(nextConnSocket, &QLocalSocket::readyRead, this, [nextConnSocket, this]() {
|
QObject::connect(nextConnSocket, &QLocalSocket::readyRead, this, [nextConnSocket, this]() {
|
||||||
auto &info = connectionMap_[nextConnSocket];
|
const ConnectionInfo &info = connectionMap_[nextConnSocket];
|
||||||
switch (info.stage) {
|
switch (info.stage) {
|
||||||
case StageHeader:
|
case StageInitHeader:
|
||||||
readInitMessageHeader(nextConnSocket);
|
readMessageHeader(nextConnSocket, StageInitBody);
|
||||||
break;
|
break;
|
||||||
case StageBody:
|
case StageInitBody:
|
||||||
readInitMessageBody(nextConnSocket);
|
readInitMessageBody(nextConnSocket);
|
||||||
break;
|
break;
|
||||||
case StageConnected:
|
case StageConnectedHeader:
|
||||||
slotDataAvailable(nextConnSocket, info.instanceId);
|
readMessageHeader(nextConnSocket, StageConnectedBody);
|
||||||
|
break;
|
||||||
|
case StageConnectedBody:
|
||||||
|
this->slotDataAvailable(nextConnSocket, info.instanceId);
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
break;
|
break;
|
||||||
@@ -355,7 +396,7 @@ void SingleCoreApplicationPrivate::slotConnectionEstablished() {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void SingleCoreApplicationPrivate::readInitMessageHeader(QLocalSocket *sock) {
|
void SingleCoreApplicationPrivate::readMessageHeader(QLocalSocket *sock, SingleCoreApplicationPrivate::ConnectionStage nextStage) {
|
||||||
|
|
||||||
if (!connectionMap_.contains(sock)) {
|
if (!connectionMap_.contains(sock)) {
|
||||||
return;
|
return;
|
||||||
@@ -372,30 +413,34 @@ void SingleCoreApplicationPrivate::readInitMessageHeader(QLocalSocket *sock) {
|
|||||||
quint64 msgLen = 0;
|
quint64 msgLen = 0;
|
||||||
headerStream >> msgLen;
|
headerStream >> msgLen;
|
||||||
ConnectionInfo &info = connectionMap_[sock];
|
ConnectionInfo &info = connectionMap_[sock];
|
||||||
info.stage = StageBody;
|
info.stage = nextStage;
|
||||||
info.msgLen = msgLen;
|
info.msgLen = msgLen;
|
||||||
|
|
||||||
if (sock->bytesAvailable() >= static_cast<qint64>(msgLen)) {
|
writeAck(sock);
|
||||||
readInitMessageBody(sock);
|
|
||||||
|
}
|
||||||
|
|
||||||
|
bool SingleCoreApplicationPrivate::isFrameComplete(QLocalSocket *sock) {
|
||||||
|
|
||||||
|
if (!connectionMap_.contains(sock)) {
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ConnectionInfo &info = connectionMap_[sock];
|
||||||
|
return (sock->bytesAvailable() >= static_cast<qint64>(info.msgLen));
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void SingleCoreApplicationPrivate::readInitMessageBody(QLocalSocket *sock) {
|
void SingleCoreApplicationPrivate::readInitMessageBody(QLocalSocket *sock) {
|
||||||
|
|
||||||
Q_Q(SingleCoreApplication);
|
Q_Q(SingleCoreApplication);
|
||||||
|
|
||||||
if (!connectionMap_.contains(sock)) {
|
if (!isFrameComplete(sock)) {
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
ConnectionInfo &info = connectionMap_[sock];
|
|
||||||
if (sock->bytesAvailable() < static_cast<qint64>(info.msgLen)) {
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Read the message body
|
// Read the message body
|
||||||
QByteArray msgBytes = sock->read(info.msgLen);
|
QByteArray msgBytes = sock->readAll();
|
||||||
QDataStream readStream(msgBytes);
|
QDataStream readStream(msgBytes);
|
||||||
readStream.setVersion(QDataStream::Qt_5_8);
|
readStream.setVersion(QDataStream::Qt_5_8);
|
||||||
|
|
||||||
@@ -430,23 +475,34 @@ void SingleCoreApplicationPrivate::readInitMessageBody(QLocalSocket *sock) {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ConnectionInfo &info = connectionMap_[sock];
|
||||||
info.instanceId = instanceId;
|
info.instanceId = instanceId;
|
||||||
info.stage = StageConnected;
|
info.stage = StageConnectedHeader;
|
||||||
|
|
||||||
if (connectionType == NewInstance || (connectionType == SecondaryInstance && options_ & SingleCoreApplication::Mode::SecondaryNotification)) {
|
if (connectionType == NewInstance || (connectionType == SecondaryInstance && options_ & SingleCoreApplication::Mode::SecondaryNotification)) {
|
||||||
Q_EMIT q->instanceStarted();
|
emit q->instanceStarted();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (sock->bytesAvailable() > 0) {
|
writeAck(sock);
|
||||||
slotDataAvailable(sock, instanceId);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void SingleCoreApplicationPrivate::slotDataAvailable(QLocalSocket *dataSocket, const quint32 instanceId) {
|
void SingleCoreApplicationPrivate::slotDataAvailable(QLocalSocket *dataSocket, const quint32 instanceId) {
|
||||||
|
|
||||||
Q_Q(SingleCoreApplication);
|
Q_Q(SingleCoreApplication);
|
||||||
Q_EMIT q->receivedMessage(instanceId, dataSocket->readAll());
|
|
||||||
|
if (!isFrameComplete(dataSocket)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const QByteArray message = dataSocket->readAll();
|
||||||
|
|
||||||
|
writeAck(dataSocket);
|
||||||
|
|
||||||
|
ConnectionInfo &info = connectionMap_[dataSocket];
|
||||||
|
info.stage = StageConnectedHeader;
|
||||||
|
|
||||||
|
emit q->receivedMessage(instanceId, message);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -461,10 +517,10 @@ void SingleCoreApplicationPrivate::slotClientConnectionClosed(QLocalSocket *clos
|
|||||||
void SingleCoreApplicationPrivate::randomSleep() {
|
void SingleCoreApplicationPrivate::randomSleep() {
|
||||||
|
|
||||||
#if QT_VERSION >= QT_VERSION_CHECK(5, 10, 0)
|
#if QT_VERSION >= QT_VERSION_CHECK(5, 10, 0)
|
||||||
QThread::msleep(QRandomGenerator::global()->bounded(8u, 18u));
|
QThread::msleep(QRandomGenerator::global()->bounded(8U, 18U));
|
||||||
#else
|
#else
|
||||||
qsrand(QDateTime::currentMSecsSinceEpoch() % std::numeric_limits<uint>::max());
|
qsrand(QDateTime::currentMSecsSinceEpoch() % std::numeric_limits<uint>::max());
|
||||||
QThread::msleep(8 + static_cast<unsigned long>(static_cast<float>(qrand()) / RAND_MAX * 10));
|
QThread::msleep(qrand() % 11 + 8);
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -37,7 +37,7 @@
|
|||||||
#include <QtGlobal>
|
#include <QtGlobal>
|
||||||
#include <QObject>
|
#include <QObject>
|
||||||
#include <QString>
|
#include <QString>
|
||||||
#include <QMap>
|
#include <QHash>
|
||||||
|
|
||||||
#include "singlecoreapplication.h"
|
#include "singlecoreapplication.h"
|
||||||
|
|
||||||
@@ -55,7 +55,7 @@ struct InstancesInfo {
|
|||||||
|
|
||||||
struct ConnectionInfo {
|
struct ConnectionInfo {
|
||||||
explicit ConnectionInfo() : msgLen(0), instanceId(0), stage(0) {}
|
explicit ConnectionInfo() : msgLen(0), instanceId(0), stage(0) {}
|
||||||
qint64 msgLen;
|
quint64 msgLen;
|
||||||
quint32 instanceId;
|
quint32 instanceId;
|
||||||
quint8 stage;
|
quint8 stage;
|
||||||
};
|
};
|
||||||
@@ -71,27 +71,32 @@ class SingleCoreApplicationPrivate : public QObject {
|
|||||||
Reconnect = 3
|
Reconnect = 3
|
||||||
};
|
};
|
||||||
enum ConnectionStage : quint8 {
|
enum ConnectionStage : quint8 {
|
||||||
StageHeader = 0,
|
StageInitHeader = 0,
|
||||||
StageBody = 1,
|
StageInitBody = 1,
|
||||||
StageConnected = 2,
|
StageConnectedHeader = 2,
|
||||||
|
StageConnectedBody = 3,
|
||||||
};
|
};
|
||||||
Q_DECLARE_PUBLIC(SingleCoreApplication)
|
Q_DECLARE_PUBLIC(SingleCoreApplication)
|
||||||
|
|
||||||
explicit SingleCoreApplicationPrivate(SingleCoreApplication *ptr);
|
explicit SingleCoreApplicationPrivate(SingleCoreApplication *ptr);
|
||||||
~SingleCoreApplicationPrivate() override;
|
~SingleCoreApplicationPrivate() override;
|
||||||
|
|
||||||
QString getUsername();
|
static QString getUsername();
|
||||||
void genBlockServerName();
|
void genBlockServerName();
|
||||||
void initializeMemoryBlock();
|
void initializeMemoryBlock() const;
|
||||||
void startPrimary();
|
void startPrimary();
|
||||||
void startSecondary();
|
void startSecondary();
|
||||||
bool connectToPrimary(const int timeout, const ConnectionType connectionType);
|
bool connectToPrimary(const int timeout, const ConnectionType connectionType);
|
||||||
quint16 blockChecksum();
|
quint16 blockChecksum() const;
|
||||||
qint64 primaryPid();
|
qint64 primaryPid() const;
|
||||||
QString primaryUser();
|
QString primaryUser() const;
|
||||||
void readInitMessageHeader(QLocalSocket *socket);
|
bool isFrameComplete(QLocalSocket *sock);
|
||||||
|
void readMessageHeader(QLocalSocket *socket, const ConnectionStage nextStage);
|
||||||
void readInitMessageBody(QLocalSocket *socket);
|
void readInitMessageBody(QLocalSocket *socket);
|
||||||
void randomSleep();
|
void writeAck(QLocalSocket *sock);
|
||||||
|
bool writeConfirmedFrame(const int timeout, const QByteArray &msg) const;
|
||||||
|
bool writeConfirmedMessage(const int timeout, const QByteArray &msg) const;
|
||||||
|
static void randomSleep();
|
||||||
|
|
||||||
SingleCoreApplication *q_ptr;
|
SingleCoreApplication *q_ptr;
|
||||||
QSharedMemory *memory_;
|
QSharedMemory *memory_;
|
||||||
@@ -100,7 +105,7 @@ class SingleCoreApplicationPrivate : public QObject {
|
|||||||
quint32 instanceNumber_;
|
quint32 instanceNumber_;
|
||||||
QString blockServerName_;
|
QString blockServerName_;
|
||||||
SingleCoreApplication::Options options_;
|
SingleCoreApplication::Options options_;
|
||||||
QMap<QLocalSocket*, ConnectionInfo> connectionMap_;
|
QHash<QLocalSocket*, ConnectionInfo> connectionMap_;
|
||||||
|
|
||||||
public slots:
|
public slots:
|
||||||
void slotConnectionEstablished();
|
void slotConnectionEstablished();
|
||||||
|
|||||||
385
CMakeLists.txt
@@ -12,13 +12,13 @@ include(cmake/Summary.cmake)
|
|||||||
include(cmake/OptionalSource.cmake)
|
include(cmake/OptionalSource.cmake)
|
||||||
include(cmake/ParseArguments.cmake)
|
include(cmake/ParseArguments.cmake)
|
||||||
|
|
||||||
if (${CMAKE_SYSTEM_NAME} STREQUAL "Linux")
|
if(${CMAKE_SYSTEM_NAME} STREQUAL "Linux")
|
||||||
set(LINUX ON)
|
set(LINUX ON)
|
||||||
endif()
|
endif()
|
||||||
if (${CMAKE_SYSTEM_NAME} STREQUAL "FreeBSD")
|
if(${CMAKE_SYSTEM_NAME} STREQUAL "FreeBSD")
|
||||||
set(FREEBSD ON)
|
set(FREEBSD ON)
|
||||||
endif()
|
endif()
|
||||||
if (${CMAKE_SYSTEM_NAME} STREQUAL "OpenBSD")
|
if(${CMAKE_SYSTEM_NAME} STREQUAL "OpenBSD")
|
||||||
set(OPENBSD ON)
|
set(OPENBSD ON)
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
@@ -35,34 +35,38 @@ set(CMAKE_MODULE_PATH ${CMAKE_SOURCE_DIR}/cmake)
|
|||||||
set(CMAKE_CXX_STANDARD 17)
|
set(CMAKE_CXX_STANDARD 17)
|
||||||
set(CMAKE_CXX_STANDARD_REQUIRED ON)
|
set(CMAKE_CXX_STANDARD_REQUIRED ON)
|
||||||
|
|
||||||
list(APPEND COMPILE_OPTIONS
|
if(MSVC)
|
||||||
$<$<COMPILE_LANGUAGE:C>:-std=c99>
|
list(APPEND COMPILE_OPTIONS /std:c++17 /MP)
|
||||||
$<$<COMPILE_LANGUAGE:CXX>:-std=c++17>
|
else()
|
||||||
-Wall
|
list(APPEND COMPILE_OPTIONS
|
||||||
-Wextra
|
$<$<COMPILE_LANGUAGE:C>:-std=c99>
|
||||||
-Wpedantic
|
$<$<COMPILE_LANGUAGE:CXX>:-std=c++17>
|
||||||
-Wunused
|
-Wall
|
||||||
-Wshadow
|
-Wextra
|
||||||
-Wundef
|
-Wpedantic
|
||||||
-Wuninitialized
|
-Wunused
|
||||||
-Wredundant-decls
|
-Wshadow
|
||||||
-Wcast-align
|
-Wundef
|
||||||
-Winit-self
|
-Wuninitialized
|
||||||
-Wmissing-include-dirs
|
-Wredundant-decls
|
||||||
-Wmissing-declarations
|
-Wcast-align
|
||||||
-Wstrict-overflow=2
|
-Winit-self
|
||||||
-Wunused-parameter
|
-Wmissing-include-dirs
|
||||||
-Wformat=2
|
-Wmissing-declarations
|
||||||
-Wdisabled-optimization
|
-Wstrict-overflow=2
|
||||||
$<$<COMPILE_LANGUAGE:CXX>:-Woverloaded-virtual>
|
-Wunused-parameter
|
||||||
$<$<COMPILE_LANGUAGE:CXX>:-Wno-old-style-cast>
|
-Wformat=2
|
||||||
$<$<COMPILE_LANGUAGE:CXX>:-fpermissive>
|
-Wdisabled-optimization
|
||||||
)
|
$<$<COMPILE_LANGUAGE:CXX>:-Woverloaded-virtual>
|
||||||
|
$<$<COMPILE_LANGUAGE:CXX>:-Wno-old-style-cast>
|
||||||
|
$<$<COMPILE_LANGUAGE:CXX>:-fpermissive>
|
||||||
|
)
|
||||||
|
endif()
|
||||||
|
|
||||||
option(BUILD_WERROR "Build with -Werror" OFF)
|
option(BUILD_WERROR "Build with -Werror" OFF)
|
||||||
if(BUILD_WERROR)
|
if(BUILD_WERROR)
|
||||||
list(APPEND COMPILE_OPTIONS -Werror)
|
list(APPEND COMPILE_OPTIONS -Werror)
|
||||||
endif(BUILD_WERROR)
|
endif()
|
||||||
|
|
||||||
add_compile_options(${COMPILE_OPTIONS})
|
add_compile_options(${COMPILE_OPTIONS})
|
||||||
|
|
||||||
@@ -77,33 +81,33 @@ if(APPLE)
|
|||||||
endif()
|
endif()
|
||||||
|
|
||||||
find_program(CCACHE_EXECUTABLE NAMES ccache)
|
find_program(CCACHE_EXECUTABLE NAMES ccache)
|
||||||
if (CCACHE_EXECUTABLE)
|
if(CCACHE_EXECUTABLE)
|
||||||
message(STATUS "ccache found: will be used for compilation and linkage")
|
message(STATUS "ccache found: will be used for compilation and linkage")
|
||||||
SET_PROPERTY(GLOBAL PROPERTY RULE_LAUNCH_COMPILE ${CCACHE_EXECUTABLE})
|
SET_PROPERTY(GLOBAL PROPERTY RULE_LAUNCH_COMPILE ${CCACHE_EXECUTABLE})
|
||||||
SET_PROPERTY(GLOBAL PROPERTY RULE_LAUNCH_LINK ${CCACHE_EXECUTABLE})
|
SET_PROPERTY(GLOBAL PROPERTY RULE_LAUNCH_LINK ${CCACHE_EXECUTABLE})
|
||||||
endif ()
|
endif()
|
||||||
|
|
||||||
find_package(PkgConfig REQUIRED)
|
find_package(PkgConfig REQUIRED)
|
||||||
find_package(Boost REQUIRED)
|
find_package(Boost REQUIRED)
|
||||||
find_package(Threads)
|
find_package(Threads)
|
||||||
find_package(Backtrace QUIET)
|
find_package(Backtrace)
|
||||||
if(Backtrace_FOUND)
|
if(Backtrace_FOUND)
|
||||||
set(HAVE_BACKTRACE ON)
|
set(HAVE_BACKTRACE ON)
|
||||||
endif()
|
endif()
|
||||||
find_package(Iconv QUIET)
|
find_package(Iconv)
|
||||||
find_package(GnuTLS REQUIRED)
|
find_package(GnuTLS REQUIRED)
|
||||||
find_package(Protobuf REQUIRED)
|
find_package(Protobuf REQUIRED)
|
||||||
if (NOT Protobuf_PROTOC_EXECUTABLE)
|
if(NOT Protobuf_PROTOC_EXECUTABLE)
|
||||||
message(FATAL_ERROR "Missing protobuf compiler.")
|
message(FATAL_ERROR "Missing protobuf compiler.")
|
||||||
endif()
|
endif()
|
||||||
if(LINUX)
|
if(LINUX)
|
||||||
find_package(ALSA REQUIRED)
|
find_package(ALSA REQUIRED)
|
||||||
pkg_check_modules(DBUS REQUIRED dbus-1)
|
pkg_check_modules(DBUS REQUIRED dbus-1)
|
||||||
else(LINUX)
|
else()
|
||||||
find_package(ALSA)
|
find_package(ALSA)
|
||||||
pkg_check_modules(DBUS dbus-1)
|
pkg_check_modules(DBUS dbus-1)
|
||||||
endif(LINUX)
|
endif()
|
||||||
if (UNIX AND NOT APPLE)
|
if(UNIX AND NOT APPLE)
|
||||||
find_package(X11)
|
find_package(X11)
|
||||||
pkg_check_modules(XCB xcb)
|
pkg_check_modules(XCB xcb)
|
||||||
endif()
|
endif()
|
||||||
@@ -113,6 +117,9 @@ endif()
|
|||||||
pkg_check_modules(GLIB REQUIRED glib-2.0)
|
pkg_check_modules(GLIB REQUIRED glib-2.0)
|
||||||
pkg_check_modules(GOBJECT REQUIRED gobject-2.0)
|
pkg_check_modules(GOBJECT REQUIRED gobject-2.0)
|
||||||
pkg_check_modules(GIO REQUIRED gio-2.0)
|
pkg_check_modules(GIO REQUIRED gio-2.0)
|
||||||
|
if(UNIX)
|
||||||
|
pkg_check_modules(GIO_UNIX gio-unix-2.0)
|
||||||
|
endif()
|
||||||
pkg_check_modules(LIBCDIO libcdio)
|
pkg_check_modules(LIBCDIO libcdio)
|
||||||
pkg_check_modules(GSTREAMER gstreamer-1.0)
|
pkg_check_modules(GSTREAMER gstreamer-1.0)
|
||||||
pkg_check_modules(GSTREAMER_BASE gstreamer-base-1.0)
|
pkg_check_modules(GSTREAMER_BASE gstreamer-base-1.0)
|
||||||
@@ -123,7 +130,7 @@ pkg_check_modules(GSTREAMER_PBUTILS gstreamer-pbutils-1.0)
|
|||||||
pkg_check_modules(LIBVLC libvlc)
|
pkg_check_modules(LIBVLC libvlc)
|
||||||
pkg_check_modules(SQLITE REQUIRED sqlite3>=3.9)
|
pkg_check_modules(SQLITE REQUIRED sqlite3>=3.9)
|
||||||
pkg_check_modules(LIBPULSE libpulse)
|
pkg_check_modules(LIBPULSE libpulse)
|
||||||
pkg_check_modules(CHROMAPRINT libchromaprint)
|
pkg_check_modules(CHROMAPRINT libchromaprint>=1.4)
|
||||||
pkg_check_modules(LIBGPOD libgpod-1.0>=0.7.92)
|
pkg_check_modules(LIBGPOD libgpod-1.0>=0.7.92)
|
||||||
pkg_check_modules(LIBMTP libmtp>=1.0)
|
pkg_check_modules(LIBMTP libmtp>=1.0)
|
||||||
pkg_check_modules(GDK_PIXBUF gdk-pixbuf-2.0)
|
pkg_check_modules(GDK_PIXBUF gdk-pixbuf-2.0)
|
||||||
@@ -132,81 +139,99 @@ find_package(FFTW3)
|
|||||||
find_package(GTest)
|
find_package(GTest)
|
||||||
find_library(GMOCK_LIBRARY gmock)
|
find_library(GMOCK_LIBRARY gmock)
|
||||||
|
|
||||||
if(NOT QT_DEFAULT_MAJOR_VERSION)
|
option(QT_VERSION_MAJOR "Qt version to use (5 or 6)")
|
||||||
set(QT_DEFAULT_MAJOR_VERSION 5)
|
option(BUILD_WITH_QT5 "Build with Qt 5" OFF)
|
||||||
endif()
|
option(BUILD_WITH_QT6 "Build with Qt 6" OFF)
|
||||||
set(QT_MAJOR_VERSION ${QT_DEFAULT_MAJOR_VERSION} CACHE STRING "Qt version to use (5 or 6), defaults to ${QT_DEFAULT_MAJOR_VERSION}")
|
|
||||||
|
|
||||||
option(BUILD_WITH_QT5 "Use Qt 5" OFF)
|
|
||||||
option(BUILD_WITH_QT6 "Use Qt 6" OFF)
|
|
||||||
|
|
||||||
if(WITH_QT6)
|
if(WITH_QT6)
|
||||||
set(BUILD_WITH_QT6 ON)
|
set(BUILD_WITH_QT6 ON)
|
||||||
endif()
|
endif()
|
||||||
|
if(QT_MAJOR_VERSION)
|
||||||
if(BUILD_WITH_QT5)
|
set(QT_VERSION_MAJOR ${QT_MAJOR_VERSION})
|
||||||
set(QT_MAJOR_VERSION 5)
|
|
||||||
elseif(BUILD_WITH_QT6)
|
|
||||||
set(QT_MAJOR_VERSION 6)
|
|
||||||
else()
|
|
||||||
if(QT_MAJOR_VERSION EQUAL 5)
|
|
||||||
set(BUILD_WITH_QT5 ON)
|
|
||||||
elseif(QT_MAJOR_VERSION EQUAL 6)
|
|
||||||
set(BUILD_WITH_QT6 ON)
|
|
||||||
else()
|
|
||||||
set(BUILD_WITH_QT5 ON)
|
|
||||||
set(QT_MAJOR_VERSION 5)
|
|
||||||
endif()
|
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
set(QT_COMPONENTS Core Concurrent Widgets Network Sql)
|
if(QT_VERSION_MAJOR)
|
||||||
set(QT_OPTIONAL_COMPONENTS Test)
|
set(QT_DEFAULT_MAJOR_VERSION ${QT_VERSION_MAJOR})
|
||||||
|
|
||||||
if(QT_MAJOR_VERSION EQUAL 5)
|
|
||||||
set(QT_MIN_VERSION 5.8)
|
|
||||||
endif()
|
endif()
|
||||||
|
set(QT_COMPONENTS Core Concurrent Gui Widgets Network Sql)
|
||||||
if(DBUS_FOUND AND NOT WIN32)
|
if(DBUS_FOUND AND NOT WIN32)
|
||||||
list(APPEND QT_COMPONENTS DBus)
|
list(APPEND QT_COMPONENTS DBus)
|
||||||
endif()
|
endif()
|
||||||
if(X11_FOUND)
|
set(QT_OPTIONAL_COMPONENTS Test)
|
||||||
|
set(QT_MIN_VERSION 5.9)
|
||||||
|
|
||||||
|
if(BUILD_WITH_QT6 OR QT_VERSION_MAJOR EQUAL 6)
|
||||||
|
set(QT_VERSION_MAJOR 6 CACHE STRING "" FORCE)
|
||||||
|
set(BUILD_WITH_QT6 ON CACHE BOOL "" FORCE)
|
||||||
|
elseif(BUILD_WITH_QT5 OR QT_VERSION_MAJOR EQUAL 5)
|
||||||
|
set(QT_VERSION_MAJOR 5 CACHE STRING "" FORCE)
|
||||||
|
set(BUILD_WITH_QT5 ON CACHE BOOL "" FORCE)
|
||||||
|
else()
|
||||||
|
# Automatically detect Qt version.
|
||||||
|
find_package(QT NAMES Qt6 Qt5 COMPONENTS ${QT_COMPONENTS} REQUIRED)
|
||||||
|
if(QT_FOUND AND QT_VERSION_MAJOR EQUAL 6)
|
||||||
|
set(BUILD_WITH_QT6 ON CACHE BOOL "" FORCE)
|
||||||
|
set(QT_VERSION_MAJOR 6 CACHE STRING "" FORCE)
|
||||||
|
elseif(QT_FOUND AND QT_VERSION_MAJOR EQUAL 5)
|
||||||
|
set(BUILD_WITH_QT5 ON CACHE BOOL "" FORCE)
|
||||||
|
set(QT_VERSION_MAJOR 5 CACHE STRING "" FORCE)
|
||||||
|
else()
|
||||||
|
message(FATAL_ERROR "Missing Qt.")
|
||||||
|
endif()
|
||||||
|
endif()
|
||||||
|
|
||||||
|
if(QT_VERSION_MAJOR)
|
||||||
|
set(QT_DEFAULT_MAJOR_VERSION ${QT_VERSION_MAJOR})
|
||||||
|
endif()
|
||||||
|
|
||||||
|
if(X11_FOUND AND BUILD_WITH_QT5)
|
||||||
list(APPEND QT_OPTIONAL_COMPONENTS X11Extras)
|
list(APPEND QT_OPTIONAL_COMPONENTS X11Extras)
|
||||||
endif()
|
endif()
|
||||||
if(WIN32)
|
|
||||||
list(APPEND QT_OPTIONAL_COMPONENTS WinExtras)
|
|
||||||
endif()
|
|
||||||
|
|
||||||
find_package(Qt${QT_MAJOR_VERSION} ${QT_MIN_VERSION} REQUIRED COMPONENTS ${QT_COMPONENTS} OPTIONAL_COMPONENTS ${QT_OPTIONAL_COMPONENTS})
|
find_package(Qt${QT_VERSION_MAJOR} ${QT_MIN_VERSION} REQUIRED COMPONENTS ${QT_COMPONENTS} OPTIONAL_COMPONENTS ${QT_OPTIONAL_COMPONENTS})
|
||||||
|
|
||||||
set(QtCore_LIBRARIES Qt${QT_MAJOR_VERSION}::Core)
|
set(QtCore_LIBRARIES Qt${QT_VERSION_MAJOR}::Core)
|
||||||
set(QtConcurrent_LIBRARIES Qt${QT_MAJOR_VERSION}::Concurrent)
|
set(QtConcurrent_LIBRARIES Qt${QT_VERSION_MAJOR}::Concurrent)
|
||||||
set(QtGui_LIBRARIES Qt${QT_MAJOR_VERSION}::Gui)
|
set(QtGui_LIBRARIES Qt${QT_VERSION_MAJOR}::Gui)
|
||||||
set(QtWidgets_LIBRARIES Qt${QT_MAJOR_VERSION}::Widgets)
|
set(QtWidgets_LIBRARIES Qt${QT_VERSION_MAJOR}::Widgets)
|
||||||
set(QtNetwork_LIBRARIES Qt${QT_MAJOR_VERSION}::Network)
|
set(QtNetwork_LIBRARIES Qt${QT_VERSION_MAJOR}::Network)
|
||||||
set(QtSql_LIBRARIES Qt${QT_MAJOR_VERSION}::Sql)
|
set(QtSql_LIBRARIES Qt${QT_VERSION_MAJOR}::Sql)
|
||||||
set(QT_LIBRARIES Qt${QT_MAJOR_VERSION}::Core Qt${QT_MAJOR_VERSION}::Concurrent Qt${QT_MAJOR_VERSION}::Gui Qt${QT_MAJOR_VERSION}::Widgets Qt${QT_MAJOR_VERSION}::Network Qt${QT_MAJOR_VERSION}::Sql)
|
set(QT_LIBRARIES Qt${QT_VERSION_MAJOR}::Core Qt${QT_VERSION_MAJOR}::Concurrent Qt${QT_VERSION_MAJOR}::Gui Qt${QT_VERSION_MAJOR}::Widgets Qt${QT_VERSION_MAJOR}::Network Qt${QT_VERSION_MAJOR}::Sql)
|
||||||
if(Qt${QT_MAJOR_VERSION}DBus_FOUND)
|
if(Qt${QT_VERSION_MAJOR}DBus_FOUND)
|
||||||
set(QtDBus_LIBRARIES Qt${QT_MAJOR_VERSION}::DBus)
|
set(QtDBus_LIBRARIES Qt${QT_VERSION_MAJOR}::DBus)
|
||||||
list(APPEND QT_LIBRARIES Qt${QT_MAJOR_VERSION}::DBus)
|
list(APPEND QT_LIBRARIES Qt${QT_VERSION_MAJOR}::DBus)
|
||||||
get_target_property(QT_DBUSXML2CPP_EXECUTABLE Qt${QT_MAJOR_VERSION}::qdbusxml2cpp LOCATION)
|
get_target_property(QT_DBUSXML2CPP_EXECUTABLE Qt${QT_VERSION_MAJOR}::qdbusxml2cpp LOCATION)
|
||||||
endif()
|
endif()
|
||||||
if(Qt${QT_MAJOR_VERSION}X11Extras_FOUND)
|
if(BUILD_WITH_QT5 AND Qt5X11Extras_FOUND)
|
||||||
set(QtX11Extras_LIBRARIES Qt${QT_MAJOR_VERSION}::X11Extras)
|
|
||||||
list(APPEND QT_LIBRARIES Qt${QT_MAJOR_VERSION}::X11Extras)
|
|
||||||
set(HAVE_X11EXTRAS ON)
|
set(HAVE_X11EXTRAS ON)
|
||||||
|
set(QtX11Extras_LIBRARIES Qt5::X11Extras)
|
||||||
|
list(APPEND QT_LIBRARIES Qt5::X11Extras)
|
||||||
endif()
|
endif()
|
||||||
if(Qt${QT_MAJOR_VERSION}WinExtras_FOUND)
|
if(Qt${QT_VERSION_MAJOR}Test_FOUND)
|
||||||
set(QtWinExtras_LIBRARIES Qt${QT_MAJOR_VERSION}::WinExtras)
|
set(QtTest_LIBRARIES Qt${QT_VERSION_MAJOR}::Test)
|
||||||
list(APPEND QT_LIBRARIES Qt${QT_MAJOR_VERSION}::WinExtras)
|
|
||||||
set(HAVE_WINEXTRAS ON)
|
|
||||||
endif()
|
|
||||||
if(Qt${QT_MAJOR_VERSION}Test_FOUND)
|
|
||||||
set(QtTest_LIBRARIES Qt${QT_MAJOR_VERSION}::Test)
|
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
find_package(Qt${QT_MAJOR_VERSION} QUIET COMPONENTS LinguistTools CONFIG)
|
find_package(Qt${QT_VERSION_MAJOR} QUIET COMPONENTS LinguistTools CONFIG)
|
||||||
if(Qt${QT_MAJOR_VERSION}LinguistTools_FOUND)
|
if(Qt${QT_VERSION_MAJOR}LinguistTools_FOUND)
|
||||||
set(QT_LCONVERT_EXECUTABLE Qt${QT_MAJOR_VERSION}::lconvert)
|
set(QT_LCONVERT_EXECUTABLE Qt${QT_VERSION_MAJOR}::lconvert)
|
||||||
|
endif()
|
||||||
|
|
||||||
|
if(BUILD_WITH_QT5 AND Qt5Core_VERSION VERSION_LESS 5.15.0)
|
||||||
|
macro(qt_add_resources)
|
||||||
|
qt5_add_resources(${ARGN})
|
||||||
|
endmacro()
|
||||||
|
macro(qt_wrap_cpp)
|
||||||
|
qt5_wrap_cpp(${ARGN})
|
||||||
|
endmacro()
|
||||||
|
macro(qt_wrap_ui)
|
||||||
|
qt5_wrap_ui(${ARGN})
|
||||||
|
endmacro()
|
||||||
|
macro(qt_add_dbus_adaptor)
|
||||||
|
qt5_add_dbus_adaptor(${ARGN})
|
||||||
|
endmacro()
|
||||||
|
macro(qt_add_dbus_interface)
|
||||||
|
qt5_add_dbus_interface(${ARGN})
|
||||||
|
endmacro()
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
if(X11_FOUND)
|
if(X11_FOUND)
|
||||||
@@ -223,26 +248,45 @@ if(X11_FOUND)
|
|||||||
message(WARNING, "Missing X11/XF86keysym.h")
|
message(WARNING, "Missing X11/XF86keysym.h")
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
find_path(QPA_QPLATFORMNATIVEINTERFACE_H qpa/qplatformnativeinterface.h PATHS ${Qt${QT_MAJOR_VERSION}Gui_PRIVATE_INCLUDE_DIRS})
|
find_path(QPA_QPLATFORMNATIVEINTERFACE_H qpa/qplatformnativeinterface.h PATHS ${Qt${QT_VERSION_MAJOR}Gui_PRIVATE_INCLUDE_DIRS})
|
||||||
if(QPA_QPLATFORMNATIVEINTERFACE_H)
|
if(QPA_QPLATFORMNATIVEINTERFACE_H)
|
||||||
set(HAVE_QPA_QPLATFORMNATIVEINTERFACE_H ON)
|
set(HAVE_QPA_QPLATFORMNATIVEINTERFACE_H ON)
|
||||||
include_directories(${Qt${QT_MAJOR_VERSION}Gui_PRIVATE_INCLUDE_DIRS})
|
|
||||||
message(STATUS "Have qpa/qplatformnativeinterface.h header.")
|
message(STATUS "Have qpa/qplatformnativeinterface.h header.")
|
||||||
else()
|
else()
|
||||||
message(STATUS "Missing qpa/qplatformnativeinterface.h header.")
|
message(STATUS "Missing qpa/qplatformnativeinterface.h header.")
|
||||||
endif()
|
endif()
|
||||||
endif(X11_FOUND)
|
endif(X11_FOUND)
|
||||||
|
|
||||||
|
option(USE_TAGLIB "Build with TagLib" OFF)
|
||||||
|
option(USE_TAGPARSER "Build with TagParser" OFF)
|
||||||
|
|
||||||
|
if(NOT USE_TAGLIB AND NOT USE_TAGPARSER)
|
||||||
|
set(USE_TAGLIB ON)
|
||||||
|
endif()
|
||||||
|
|
||||||
# TAGLIB
|
# TAGLIB
|
||||||
pkg_check_modules(TAGLIB REQUIRED taglib>=1.11.1)
|
if(USE_TAGLIB)
|
||||||
find_path(HAVE_TAGLIB_DSFFILE_H taglib/dsffile.h)
|
pkg_check_modules(TAGLIB REQUIRED taglib>=1.11.1)
|
||||||
find_path(HAVE_TAGLIB_DSDIFFFILE_H taglib/dsdifffile.h)
|
if(TAGLIB_FOUND)
|
||||||
if(HAVE_TAGLIB_DSFFILE_H)
|
find_path(HAVE_TAGLIB_DSFFILE_H taglib/dsffile.h)
|
||||||
set(HAVE_TAGLIB_DSFFILE ON)
|
find_path(HAVE_TAGLIB_DSDIFFFILE_H taglib/dsdifffile.h)
|
||||||
endif(HAVE_TAGLIB_DSFFILE_H)
|
if(HAVE_TAGLIB_DSFFILE_H)
|
||||||
if(HAVE_TAGLIB_DSDIFFFILE_H)
|
set(HAVE_TAGLIB_DSFFILE ON)
|
||||||
set(HAVE_TAGLIB_DSDIFFFILE ON)
|
endif(HAVE_TAGLIB_DSFFILE_H)
|
||||||
endif(HAVE_TAGLIB_DSDIFFFILE_H)
|
if(HAVE_TAGLIB_DSDIFFFILE_H)
|
||||||
|
set(HAVE_TAGLIB_DSDIFFFILE ON)
|
||||||
|
endif(HAVE_TAGLIB_DSDIFFFILE_H)
|
||||||
|
endif()
|
||||||
|
endif()
|
||||||
|
|
||||||
|
# TAGPARSER
|
||||||
|
if(USE_TAGPARSER)
|
||||||
|
pkg_check_modules(TAGPARSER REQUIRED tagparser)
|
||||||
|
endif()
|
||||||
|
|
||||||
|
if(NOT TAGLIB_FOUND AND NOT TAGPARSER_FOUND)
|
||||||
|
message(FATAL_ERROR "You need either TagLib or TagParser!")
|
||||||
|
endif()
|
||||||
|
|
||||||
# SingleApplication
|
# SingleApplication
|
||||||
add_subdirectory(3rdparty/singleapplication)
|
add_subdirectory(3rdparty/singleapplication)
|
||||||
@@ -251,14 +295,14 @@ set(SINGLEAPPLICATION_LIBRARIES singleapplication)
|
|||||||
set(SINGLECOREAPPLICATION_LIBRARIES singlecoreapplication)
|
set(SINGLECOREAPPLICATION_LIBRARIES singlecoreapplication)
|
||||||
|
|
||||||
if(APPLE)
|
if(APPLE)
|
||||||
find_library(SPARKLE Sparkle PATHS "/usr/local/opt/sparkle")
|
|
||||||
add_subdirectory(3rdparty/macdeployqt)
|
|
||||||
add_subdirectory(3rdparty/SPMediaKeyTap)
|
add_subdirectory(3rdparty/SPMediaKeyTap)
|
||||||
set(SPMEDIAKEYTAP_INCLUDE_DIRS ${CMAKE_CURRENT_SOURCE_DIR}/3rdparty/SPMediaKeyTap)
|
set(SPMEDIAKEYTAP_INCLUDE_DIRS ${CMAKE_CURRENT_SOURCE_DIR}/3rdparty/SPMediaKeyTap)
|
||||||
set(SPMEDIAKEYTAP_LIBRARIES SPMediaKeyTap)
|
set(SPMEDIAKEYTAP_LIBRARIES SPMediaKeyTap)
|
||||||
endif(APPLE)
|
add_subdirectory(3rdparty/macdeployqt)
|
||||||
|
add_subdirectory(ext/macdeploycheck)
|
||||||
|
endif()
|
||||||
|
|
||||||
if(NOT SPARKLE AND (APPLE OR WIN32))
|
if(WIN32)
|
||||||
if(BUILD_WITH_QT6)
|
if(BUILD_WITH_QT6)
|
||||||
pkg_check_modules(QTSPARKLE qtsparkle-qt6)
|
pkg_check_modules(QTSPARKLE qtsparkle-qt6)
|
||||||
else()
|
else()
|
||||||
@@ -269,23 +313,28 @@ if(NOT SPARKLE AND (APPLE OR WIN32))
|
|||||||
endif()
|
endif()
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
if (WIN32)
|
if(WIN32 AND MSVC)
|
||||||
|
add_subdirectory(3rdparty/getopt)
|
||||||
|
set(GETOPT_INCLUDE_DIRS ${CMAKE_CURRENT_SOURCE_DIR}/3rdparty/getopt)
|
||||||
|
set(GETOPT_LIBRARIES getopt)
|
||||||
|
endif()
|
||||||
|
|
||||||
|
if(WIN32 AND NOT MSVC)
|
||||||
# RC compiler
|
# RC compiler
|
||||||
string(REPLACE "gcc" "windres" CMAKE_RC_COMPILER_INIT ${CMAKE_C_COMPILER})
|
string(REPLACE "gcc" "windres" CMAKE_RC_COMPILER_INIT ${CMAKE_C_COMPILER})
|
||||||
enable_language(RC)
|
enable_language(RC)
|
||||||
SET(CMAKE_RC_COMPILE_OBJECT "<CMAKE_RC_COMPILER> -O coff -o <OBJECT> <SOURCE> -I ${CMAKE_SOURCE_DIR}/dist/windows")
|
SET(CMAKE_RC_COMPILE_OBJECT "<CMAKE_RC_COMPILER> -O coff -o <OBJECT> <SOURCE> -I ${CMAKE_SOURCE_DIR}/dist/windows")
|
||||||
endif(WIN32)
|
endif()
|
||||||
|
|
||||||
# Optional bits
|
|
||||||
if(WIN32)
|
if(WIN32)
|
||||||
option(ENABLE_WIN32_CONSOLE "Show the windows console even outside Debug mode" OFF)
|
option(ENABLE_WIN32_CONSOLE "Show the windows console even outside Debug mode" OFF)
|
||||||
endif(WIN32)
|
endif()
|
||||||
|
|
||||||
optional_component(ALSA ON "ALSA integration"
|
optional_component(ALSA ON "ALSA integration"
|
||||||
DEPENDS "alsa" ALSA_FOUND
|
DEPENDS "alsa" ALSA_FOUND
|
||||||
)
|
)
|
||||||
|
|
||||||
optional_component(LIBPULSE ON "Pulse audio integration"
|
optional_component(LIBPULSE ON "PulseAudio integration"
|
||||||
DEPENDS "libpulse" LIBPULSE_FOUND
|
DEPENDS "libpulse" LIBPULSE_FOUND
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -306,11 +355,17 @@ optional_component(VLC ON "Engine: VLC backend"
|
|||||||
DEPENDS "libvlc" LIBVLC_FOUND
|
DEPENDS "libvlc" LIBVLC_FOUND
|
||||||
)
|
)
|
||||||
|
|
||||||
optional_component(CHROMAPRINT ON "Chromaprint (Tag fetching from Musicbrainz)"
|
optional_component(SONGFINGERPRINTING ON "Song fingerprinting and tracking"
|
||||||
DEPENDS "chromaprint" CHROMAPRINT_FOUND
|
DEPENDS "chromaprint" CHROMAPRINT_FOUND
|
||||||
|
DEPENDS "gstreamer" GSTREAMER_FOUND
|
||||||
)
|
)
|
||||||
|
|
||||||
if (X11_FOUND OR HAVE_DBUS OR APPLE OR WIN32)
|
optional_component(MUSICBRAINZ ON "MusicBrainz integration"
|
||||||
|
DEPENDS "chromaprint" CHROMAPRINT_FOUND
|
||||||
|
DEPENDS "gstreamer" GSTREAMER_FOUND
|
||||||
|
)
|
||||||
|
|
||||||
|
if(X11_FOUND OR HAVE_DBUS OR APPLE OR WIN32)
|
||||||
set(HAVE_GLOBALSHORTCUTS_SUPPORT ON)
|
set(HAVE_GLOBALSHORTCUTS_SUPPORT ON)
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
@@ -318,12 +373,21 @@ optional_component(GLOBALSHORTCUTS ON "Global shortcuts"
|
|||||||
DEPENDS "D-Bus, X11, Windows or macOS" HAVE_GLOBALSHORTCUTS_SUPPORT
|
DEPENDS "D-Bus, X11, Windows or macOS" HAVE_GLOBALSHORTCUTS_SUPPORT
|
||||||
)
|
)
|
||||||
|
|
||||||
optional_component(X11_GLOBALSHORTCUTS ON "X11 global shortcuts"
|
if(BUILD_WITH_QT6 AND (Qt6Core_VERSION VERSION_EQUAL 6.2.0 OR Qt6Core_VERSION VERSION_GREATER 6.2.0))
|
||||||
DEPENDS "X11Extras" Qt${QT_MAJOR_VERSION}X11Extras_FOUND
|
optional_component(X11_GLOBALSHORTCUTS ON "X11 global shortcuts" DEPENDS "X11" X11_FOUND)
|
||||||
)
|
else()
|
||||||
|
if(HAVE_X11EXTRAS OR HAVE_QPA_QPLATFORMNATIVEINTERFACE_H)
|
||||||
|
set(HAVE_X11EXTRAS_OR_QPA_QPLATFORMNATIVEINTERFACE_H ON)
|
||||||
|
endif()
|
||||||
|
optional_component(X11_GLOBALSHORTCUTS ON "X11 global shortcuts"
|
||||||
|
DEPENDS "X11" X11_FOUND
|
||||||
|
DEPENDS "Qt >= 6.2, X11Extras or qpa/qplatformnativeinterface.h header" HAVE_X11EXTRAS_OR_QPA_QPLATFORMNATIVEINTERFACE_H
|
||||||
|
)
|
||||||
|
endif()
|
||||||
|
|
||||||
optional_component(AUDIOCD ON "Devices: Audio CD support"
|
optional_component(AUDIOCD ON "Devices: Audio CD support"
|
||||||
DEPENDS "libcdio" LIBCDIO_FOUND
|
DEPENDS "libcdio" LIBCDIO_FOUND
|
||||||
|
DEPENDS "gstreamer" GSTREAMER_FOUND
|
||||||
)
|
)
|
||||||
|
|
||||||
optional_component(UDISKS2 ON "Devices: UDisks2 backend"
|
optional_component(UDISKS2 ON "Devices: UDisks2 backend"
|
||||||
@@ -335,6 +399,11 @@ optional_component(GIO ON "Devices: GIO device backend"
|
|||||||
DEPENDS "Unix or Windows" "NOT APPLE"
|
DEPENDS "Unix or Windows" "NOT APPLE"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
optional_component(GIO_UNIX ON "Devices: GIO device backend (Unix support)"
|
||||||
|
DEPENDS "libgio-unix" GIO_UNIX_FOUND
|
||||||
|
DEPENDS "Unix" "UNIX"
|
||||||
|
)
|
||||||
|
|
||||||
optional_component(LIBGPOD ON "Devices: iPod classic support"
|
optional_component(LIBGPOD ON "Devices: iPod classic support"
|
||||||
DEPENDS "libgpod" LIBGPOD_FOUND
|
DEPENDS "libgpod" LIBGPOD_FOUND
|
||||||
DEPENDS "gdk-pixbuf" GDK_PIXBUF_FOUND
|
DEPENDS "gdk-pixbuf" GDK_PIXBUF_FOUND
|
||||||
@@ -344,11 +413,6 @@ optional_component(LIBMTP ON "Devices: MTP support"
|
|||||||
DEPENDS "libmtp" LIBMTP_FOUND
|
DEPENDS "libmtp" LIBMTP_FOUND
|
||||||
)
|
)
|
||||||
|
|
||||||
optional_component(SPARKLE ON "Sparkle integration"
|
|
||||||
DEPENDS "macOS" APPLE
|
|
||||||
DEPENDS "Sparkle" SPARKLE
|
|
||||||
)
|
|
||||||
|
|
||||||
if(BUILD_WITH_QT6)
|
if(BUILD_WITH_QT6)
|
||||||
optional_component(TRANSLATIONS ON "Translations"
|
optional_component(TRANSLATIONS ON "Translations"
|
||||||
DEPENDS "gettext" GETTEXT_FOUND
|
DEPENDS "gettext" GETTEXT_FOUND
|
||||||
@@ -372,24 +436,23 @@ optional_component(MOODBAR ON "Moodbar"
|
|||||||
DEPENDS "gstreamer" HAVE_GSTREAMER
|
DEPENDS "gstreamer" HAVE_GSTREAMER
|
||||||
)
|
)
|
||||||
|
|
||||||
if(LINUX OR APPLE)
|
if(APPLE OR WIN32)
|
||||||
option(USE_BUNDLE "Bundle dependencies" OFF)
|
|
||||||
elseif(WIN32)
|
|
||||||
option(USE_BUNDLE "Bundle dependencies" ON)
|
option(USE_BUNDLE "Bundle dependencies" ON)
|
||||||
|
else()
|
||||||
|
option(USE_BUNDLE "Bundle dependencies" OFF)
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
if (USE_BUNDLE AND NOT USE_BUNDLE_DIR)
|
if(USE_BUNDLE AND NOT USE_BUNDLE_DIR)
|
||||||
if(LINUX)
|
if(LINUX)
|
||||||
set(USE_BUNDLE_DIR "../plugins")
|
set(USE_BUNDLE_DIR "../plugins")
|
||||||
endif(LINUX)
|
endif()
|
||||||
if(APPLE)
|
if(APPLE)
|
||||||
set(USE_BUNDLE_DIR "../PlugIns")
|
set(USE_BUNDLE_DIR "../PlugIns")
|
||||||
endif(APPLE)
|
endif()
|
||||||
endif(USE_BUNDLE AND NOT USE_BUNDLE_DIR)
|
endif()
|
||||||
|
|
||||||
# Check that we have sqlite3 with FTS5
|
|
||||||
|
|
||||||
if(NOT CMAKE_CROSSCOMPILING)
|
if(NOT CMAKE_CROSSCOMPILING)
|
||||||
|
# Check that we have Qt with sqlite driver
|
||||||
set(CMAKE_REQUIRED_FLAGS "-std=c++17")
|
set(CMAKE_REQUIRED_FLAGS "-std=c++17")
|
||||||
set(CMAKE_REQUIRED_LIBRARIES ${QtCore_LIBRARIES} ${QtSql_LIBRARIES})
|
set(CMAKE_REQUIRED_LIBRARIES ${QtCore_LIBRARIES} ${QtSql_LIBRARIES})
|
||||||
check_cxx_source_runs("
|
check_cxx_source_runs("
|
||||||
@@ -400,12 +463,29 @@ if(NOT CMAKE_CROSSCOMPILING)
|
|||||||
db.setDatabaseName(\":memory:\");
|
db.setDatabaseName(\":memory:\");
|
||||||
if (!db.open()) { return 1; }
|
if (!db.open()) { return 1; }
|
||||||
QSqlQuery q(db);
|
QSqlQuery q(db);
|
||||||
q.prepare(\"CREATE VIRTUAL TABLE test_fts USING fts5(test, tokenize = 'unicode61 remove_diacritics 0');\");
|
q.prepare(\"CREATE TABLE test (test TEXT);\");
|
||||||
if (!q.exec()) return 1;
|
if (!q.exec()) return 1;
|
||||||
}
|
}
|
||||||
"
|
"
|
||||||
SQLITE3_FTS5
|
QT_SQLITE_TEST
|
||||||
)
|
)
|
||||||
|
if(QT_SQLITE_TEST)
|
||||||
|
# Check that we have sqlite3 with FTS5
|
||||||
|
check_cxx_source_runs("
|
||||||
|
#include <QSqlDatabase>
|
||||||
|
#include <QSqlQuery>
|
||||||
|
int main() {
|
||||||
|
QSqlDatabase db = QSqlDatabase::addDatabase(\"QSQLITE\");
|
||||||
|
db.setDatabaseName(\":memory:\");
|
||||||
|
if (!db.open()) { return 1; }
|
||||||
|
QSqlQuery q(db);
|
||||||
|
q.prepare(\"CREATE VIRTUAL TABLE test_fts USING fts5(test, tokenize = 'unicode61 remove_diacritics 0');\");
|
||||||
|
if (!q.exec()) return 1;
|
||||||
|
}
|
||||||
|
"
|
||||||
|
SQLITE_FTS5_TEST
|
||||||
|
)
|
||||||
|
endif()
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
# Set up definitions
|
# Set up definitions
|
||||||
@@ -417,6 +497,13 @@ add_definitions(-DQT_USE_QSTRINGBUILDER)
|
|||||||
add_definitions(-DQT_NO_URL_CAST_FROM_STRING)
|
add_definitions(-DQT_NO_URL_CAST_FROM_STRING)
|
||||||
add_definitions(-DQT_NO_CAST_TO_ASCII)
|
add_definitions(-DQT_NO_CAST_TO_ASCII)
|
||||||
|
|
||||||
|
if(WIN32)
|
||||||
|
add_definitions(-DUNICODE)
|
||||||
|
if(MSVC)
|
||||||
|
add_definitions(-DPROTOBUF_USE_DLLS)
|
||||||
|
endif()
|
||||||
|
endif()
|
||||||
|
|
||||||
# Subdirectories
|
# Subdirectories
|
||||||
add_subdirectory(src)
|
add_subdirectory(src)
|
||||||
add_subdirectory(dist)
|
add_subdirectory(dist)
|
||||||
@@ -432,13 +519,9 @@ if(GTest_FOUND AND GMOCK_LIBRARY AND QtTest_LIBRARIES)
|
|||||||
endif()
|
endif()
|
||||||
|
|
||||||
# Uninstall support
|
# Uninstall support
|
||||||
configure_file(
|
configure_file("${CMAKE_CURRENT_SOURCE_DIR}/cmake_uninstall.cmake.in" "${CMAKE_CURRENT_BINARY_DIR}/cmake_uninstall.cmake" IMMEDIATE @ONLY)
|
||||||
"${CMAKE_CURRENT_SOURCE_DIR}/cmake_uninstall.cmake.in"
|
|
||||||
"${CMAKE_CURRENT_BINARY_DIR}/cmake_uninstall.cmake"
|
|
||||||
IMMEDIATE @ONLY)
|
|
||||||
|
|
||||||
add_custom_target(uninstall
|
add_custom_target(uninstall "${CMAKE_COMMAND}" -P "${CMAKE_CURRENT_BINARY_DIR}/cmake_uninstall.cmake")
|
||||||
"${CMAKE_COMMAND}" -P "${CMAKE_CURRENT_BINARY_DIR}/cmake_uninstall.cmake")
|
|
||||||
|
|
||||||
# Show a summary of what we have enabled
|
# Show a summary of what we have enabled
|
||||||
summary_show()
|
summary_show()
|
||||||
@@ -448,10 +531,20 @@ elseif(NOT HAVE_GSTREAMER)
|
|||||||
message(WARNING "GStreamer is the only engine that is fully implemented. Using other engines is possible but not recommended.")
|
message(WARNING "GStreamer is the only engine that is fully implemented. Using other engines is possible but not recommended.")
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
if(NOT SQLITE3_FTS5 AND NOT CMAKE_CROSSCOMPILING)
|
if(QT_VERSION_MAJOR EQUAL 5)
|
||||||
message(WARNING "sqlite3 must be enabled with FTS5. See: https://www.sqlite.org/fts5.html")
|
message(WARNING "It is detected that Strawberry is being built with Qt 5. There are no bugfix releases for the latest minor LTS version of Qt 5 available to open-source users, only commercial users. Therefore Strawberry should be built with Qt 6 when possible. Building with Qt 6 will also take advantage of improvements and new features not available in Qt 5. To build with Qt 6 specify -DBUILD_WITH_QT6=ON to automatically detect Qt 6, or for example -DCMAKE_PREFIX_PATH=/usr/local/lib64/cmake to manually specify the Qt 6 directory.")
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
if(NOT TAGLIB_VERSION VERSION_GREATER_EQUAL 1.12)
|
if(NOT CMAKE_CROSSCOMPILING)
|
||||||
|
if(QT_SQLITE_TEST)
|
||||||
|
if(NOT SQLITE_FTS5_TEST)
|
||||||
|
message(WARNING "sqlite must be enabled with FTS5. See: https://www.sqlite.org/fts5.html")
|
||||||
|
endif()
|
||||||
|
else()
|
||||||
|
message(WARNING "The Qt sqlite driver test failed.")
|
||||||
|
endif()
|
||||||
|
endif()
|
||||||
|
|
||||||
|
if(USE_TAGLIB AND TAGLIB_FOUND AND NOT TAGLIB_VERSION VERSION_GREATER_EQUAL 1.12)
|
||||||
message(WARNING "There is a critical bug in TagLib (1.11.1) that can result in corrupt Ogg files, see: https://github.com/taglib/taglib/issues/864, please consider updating TagLib to the newest version.")
|
message(WARNING "There is a critical bug in TagLib (1.11.1) that can result in corrupt Ogg files, see: https://github.com/taglib/taglib/issues/864, please consider updating TagLib to the newest version.")
|
||||||
endif()
|
endif()
|
||||||
|
|||||||
266
Changelog
@@ -2,7 +2,143 @@ Strawberry Music Player
|
|||||||
=======================
|
=======================
|
||||||
ChangeLog
|
ChangeLog
|
||||||
|
|
||||||
0.9.3:
|
Version 1.0.3 (2022.03.24)
|
||||||
|
|
||||||
|
Bugfixes:
|
||||||
|
* Remove slash and backslash from filenames when saving album covers using album directory cover filenames (#903).
|
||||||
|
* Remove playlist file-extensions from accepted audio file extensions (#909).
|
||||||
|
* Fixed Qobuz requests only receiving the first 50 albums (#922).
|
||||||
|
* (Windows|MinGW) Fixed streaming stopping at the end of each track. libsoup downgraded from 3.0 to 2.74.
|
||||||
|
* (Windows|MSVC) Fixed initial database schema failure caused by CRLF line-endings in schema files.
|
||||||
|
|
||||||
|
New features
|
||||||
|
* Added support for bs2b (Improved headphone listening of stereo audio records using Bauer stereophonic-to-binaural DSP) (#249).
|
||||||
|
|
||||||
|
Version 1.0.2 (2022.02.20)
|
||||||
|
|
||||||
|
Bugfixes:
|
||||||
|
* Fixed showing menu when clicking icon for collection and internet search tool buttons
|
||||||
|
* Fixed ignoring devices with system mounts as defined by GIO (#410).
|
||||||
|
* Fixed updating database when deleting songs from filesystem devices.
|
||||||
|
* Fixed unregistered metatype when listing songs from MTP devices with Qt 6.
|
||||||
|
* Fixed using entered password when testing Subsonic settings before pressing save (#879).
|
||||||
|
* Fixed downloading Subsonic album covers.
|
||||||
|
* Fixed subsonic album covers downloaded several times for each album when MD5 authentication was enabled (#885).
|
||||||
|
* Fixed volume going to 100% when pressing volume down with MRPIS2 and global shortcuts (#884).
|
||||||
|
* Fixed incorrect rounding when setting volume through MPRIS2 (#894).
|
||||||
|
* Fixed delete from disk not showing up in the menu when one or more CUE songs were selected.
|
||||||
|
* Fixed possible crashes when switching songs when fading is enabled (#890).
|
||||||
|
* Fixed X11 global shortcuts not working unless window was in focus with Qt 6.2 and higher (#893).
|
||||||
|
* Fixed scrobbler re-sending scrobbles to fast on error (#898).
|
||||||
|
|
||||||
|
Enhancements
|
||||||
|
* Log Qt version on startup.
|
||||||
|
* Added button for deleting existing Subsonic songs (#883).
|
||||||
|
* Make collection watcher ignore files with "qt_temp" filename and tmp extension.
|
||||||
|
* Require Qt 5.9 or higher.
|
||||||
|
* Added scrollbars to edit tag dialog (#888).
|
||||||
|
* Added advanced settings for configuring collection watcher.
|
||||||
|
* Disable open audio CD menu when compiled without audio CD support.
|
||||||
|
* Replaced use of deprecated QMouseEvent constructor as of Qt 6.4.
|
||||||
|
* Replaced use of deprecated QCryptographicHash::addData overload as of Qt 6.4.
|
||||||
|
|
||||||
|
Removed features:
|
||||||
|
* Removed broken "nomedia" / "nomusic" file handling.
|
||||||
|
|
||||||
|
Version 1.0.1 (2022.01.08)
|
||||||
|
|
||||||
|
Bugfixes:
|
||||||
|
* Fixed collection and internet search filter tool button menu arrow overlap (#796).
|
||||||
|
* Fixed stop after this track button with Qt 6 (#795).
|
||||||
|
* Fixed not updating the URL when songs were moved on disk when the fingerprinting feature is enabled.
|
||||||
|
* Fixed SQL query error for songs with an invalid modification time (#815).
|
||||||
|
* Fixed blocky rendering of the currently playing track with high resolution screens (#794).
|
||||||
|
* Fixed incorrect playlist column filesize for radio streams.
|
||||||
|
* Fixed deleting embedded album cover from Ogg songs.
|
||||||
|
* Fixed parsing of Cue tracks with 1-digit minutes (#836).
|
||||||
|
* Fixed updating of playlist summary after reloading items when adding songs from files outside of the collection (#848).
|
||||||
|
* Fixed always saving metadata when saving playlists for Tidal, Qobuz and Subsonic songs independent of playlist setting (#851).
|
||||||
|
* Fixed setting media shortcuts when using kglobalaccel (#849).
|
||||||
|
* Fixed parsing of Genius lyrics when they are sometimes received in a different HTML format.
|
||||||
|
* Fixed saving MP4 specific tags as UTF-8 (#830).
|
||||||
|
* Fixed clearing "manually set" cover when saving album covers embedded from outside of the tag editor (#858).
|
||||||
|
* Fixed aborting collection scan when Strawberry exists to avoid hang on exit.
|
||||||
|
* Fixed resuming collection scan when adding a new directory after collection scan was aborted.
|
||||||
|
* Fixed excluding hidden songs from the collection.
|
||||||
|
* Disabled moodbar for CUE songs since they can not be supported properly (#865).
|
||||||
|
* (Windows) Added gstreamer gstxingmux plugin to fix transcoding to MP3 (#856).
|
||||||
|
|
||||||
|
Enhancements:
|
||||||
|
* Made playlist header column text elided (#801).
|
||||||
|
* Added support for reading and writing playcounts and ratings from/to tags.
|
||||||
|
* Added support for setting rating using the edit tag dialog.
|
||||||
|
* Added setting to enable/disable playlist toolbar (#809).
|
||||||
|
* Added component type, content_rating type and releases to AppStream data file (#806).
|
||||||
|
* Removed unused "mark as listened" option in organize dialog.
|
||||||
|
* Fixed some clazy warnings and narrowing conversions in the source code.
|
||||||
|
* Replaced uses of macros in the source code.
|
||||||
|
* Added a more user-friendly error message when receiving encrypted streams from Tidal (#824).
|
||||||
|
* Added support for port-pattern entered in the device textbox when using Jack as output (#828).
|
||||||
|
* Added Spanish (Spain) translation.
|
||||||
|
* Added support for more CUE filenames (#835).
|
||||||
|
* (Windows) Add gstreamer dash plugin.
|
||||||
|
|
||||||
|
Version 1.0.0 (2021.10.14)
|
||||||
|
|
||||||
|
Bugfixes:
|
||||||
|
* Fix updating temporary metadata when reloading songs outside of the collection.
|
||||||
|
* Don't strip off "Live" from song title when sending scrobbles.
|
||||||
|
* Fix incorrect use of QFutureWatcher.
|
||||||
|
* Fix compile of Utilities::Hmac with Qt 6.2.
|
||||||
|
* Fix a memory leak when using right click context menu in internet search.
|
||||||
|
* Fix a gstreamer bus leak when adding streams and remote playlists.
|
||||||
|
* Fix "Source ID x was not found when attempting to remove it" error.
|
||||||
|
* Escape ampersands in playlist tabs.
|
||||||
|
* Fix analyzer with S24_32LE audio format.
|
||||||
|
* (macOS) Fix incorrect playlist alternating row colors with dark theme.
|
||||||
|
* (Windows) Fix adding songs with Japanese characters from the files tab.
|
||||||
|
|
||||||
|
Enhancements:
|
||||||
|
* Add replaygain fallback gain setting.
|
||||||
|
* Add option to turn off playlist alternating row colors.
|
||||||
|
* Make the default tabbbar background color lighter.
|
||||||
|
* Remove use of deprecated WinExtras Qt module.
|
||||||
|
* Add CMake test for Qt sqlite support.
|
||||||
|
* Automatically detect Qt version if BUILD_WITH_QT5 or BUILD_WITH_QT6 is not specified.
|
||||||
|
* Correct playlist tabbar favorite tooltip from "click" to "double-click".
|
||||||
|
* Remove scroll over icon to change track option since it does not work reliable.
|
||||||
|
* Improve resume playback on startup.
|
||||||
|
* Re-request stream URL for Tidal and QObuz when resuming playback after pausing for more than 30 seconds.
|
||||||
|
* Add Finnish, Ukrainian, Dutch, Japanese, Chinese, Catalan and Portuguese (Brazil).
|
||||||
|
* Add support for TagParser (https://github.com/Martchus/tagparser) as an alternative to TagLib.
|
||||||
|
* Add Subsonic option to turn off HTTP/2.
|
||||||
|
* Fix minor Clang-Tidy and Clazy warnings.
|
||||||
|
* Use higher resolution images from last.fm API.
|
||||||
|
* Add MD5 token authentication for Subsonic.
|
||||||
|
* Use 500 albums per request when receiving albums from Subsonic.
|
||||||
|
* Use QX11Application with Qt >= 6.2 for X11 global shortcuts.
|
||||||
|
* Allow fading when a ALSA PCM device is selected.
|
||||||
|
* Store Tidal MPEG-DASH file in data uri.
|
||||||
|
* Use XSPF image elements as manually set artwork.
|
||||||
|
* Make error dialog larger.
|
||||||
|
* Show error dialog for failed SQL queries.
|
||||||
|
* Show error dialog when failing to read or write album covers.
|
||||||
|
* Add module music formats (mod, s3m, xm, it) to detected filetypes.
|
||||||
|
* Disable gapless playback for module music formats to workaround gstreamer bug.
|
||||||
|
* Update directory ID and song path immediately when organizing collection songs.
|
||||||
|
* Add right click option to star a playlist in playlist tabs.
|
||||||
|
* Use seconds instead of minutes for scrobble submit delay.
|
||||||
|
* (macOS) Build with libgpod.
|
||||||
|
* (Windows) Fix compile with MSVC.
|
||||||
|
|
||||||
|
New features:
|
||||||
|
* Add ALSA PCM devices.
|
||||||
|
* Add song fingerprinting and tracking.
|
||||||
|
* Add support for native global shortcuts on MATE.
|
||||||
|
* Add radios view with channels from Radio Paradise and SomaFM.
|
||||||
|
|
||||||
|
|
||||||
|
Version 0.9.3 (2021.04.18)
|
||||||
|
|
||||||
Bugfixes:
|
Bugfixes:
|
||||||
* Fix "Show in file browser" to work with thunar.
|
* Fix "Show in file browser" to work with thunar.
|
||||||
@@ -25,7 +161,8 @@ ChangeLog
|
|||||||
* (macOS) Make macdeployqt work with Qt 5 too.
|
* (macOS) Make macdeployqt work with Qt 5 too.
|
||||||
* (macOS) Show keep running option in behaviour settings.
|
* (macOS) Show keep running option in behaviour settings.
|
||||||
|
|
||||||
0.9.2:
|
|
||||||
|
Version 0.9.2 (2021.03.25)
|
||||||
|
|
||||||
Bugfixes:
|
Bugfixes:
|
||||||
* Fix marking songs available.
|
* Fix marking songs available.
|
||||||
@@ -36,7 +173,8 @@ ChangeLog
|
|||||||
* (macOS) Fix crash when opening cover manager.
|
* (macOS) Fix crash when opening cover manager.
|
||||||
* (macOS) Fix broken Qt plugins resulting in album covers not showing.
|
* (macOS) Fix broken Qt plugins resulting in album covers not showing.
|
||||||
|
|
||||||
0.9.1:
|
|
||||||
|
Version 0.9.1 (2021.03.13)
|
||||||
|
|
||||||
Bugfixes:
|
Bugfixes:
|
||||||
* Fix duplicating songs in the DB when organizing songs between 2 different collection directories.
|
* Fix duplicating songs in the DB when organizing songs between 2 different collection directories.
|
||||||
@@ -71,7 +209,8 @@ ChangeLog
|
|||||||
New features:
|
New features:
|
||||||
* Add option and support for saving embedded covers for FLAC, Ogg Vorbis, MP3 and MP4/AAC.
|
* Add option and support for saving embedded covers for FLAC, Ogg Vorbis, MP3 and MP4/AAC.
|
||||||
|
|
||||||
0.8.5:
|
|
||||||
|
Version 0.8.5 (2020.12.19)
|
||||||
|
|
||||||
Bugfixes:
|
Bugfixes:
|
||||||
* Fix return type of SmartPlaylistQueryWizardPlugin::type().
|
* Fix return type of SmartPlaylistQueryWizardPlugin::type().
|
||||||
@@ -90,7 +229,8 @@ ChangeLog
|
|||||||
* Add command line option to play a playlist based on name.
|
* Add command line option to play a playlist based on name.
|
||||||
* Change double-click behaviour in cover manager to open fullsize cover.
|
* Change double-click behaviour in cover manager to open fullsize cover.
|
||||||
|
|
||||||
0.8.4:
|
|
||||||
|
Version 0.8.4 (2020.11.15)
|
||||||
|
|
||||||
Bugfixes:
|
Bugfixes:
|
||||||
* Fix preventing session logout when window is maxmimized.
|
* Fix preventing session logout when window is maxmimized.
|
||||||
@@ -108,15 +248,16 @@ ChangeLog
|
|||||||
* Add support for native global shortcuts on KDE.
|
* Add support for native global shortcuts on KDE.
|
||||||
* Add track progress in system tray icon as an option.
|
* Add track progress in system tray icon as an option.
|
||||||
* Only strip problematic characters in suggested filename when saving a playlist to file.
|
* Only strip problematic characters in suggested filename when saving a playlist to file.
|
||||||
* Change star/unstar playlist to doubleclick instead of singleclick.
|
* Change star/unstar playlist to double-click instead of singleclick.
|
||||||
* Don't edit playlist name on doubleclick in playlists view.
|
* Don't edit playlist name on double-click in playlists view.
|
||||||
* Make context view top label text selectable.
|
* Make context view top label text selectable.
|
||||||
* Add setting to change Qt style.
|
* Add setting to change Qt style.
|
||||||
* Clear ID3v3 tags that are empty, and clear ID3v1 tags when setting ID3v3 tags.
|
* Clear ID3v3 tags that are empty, and clear ID3v1 tags when setting ID3v3 tags.
|
||||||
* Remove remaining uses of QTextCodec.
|
* Remove remaining uses of QTextCodec.
|
||||||
* Remove Core5Compat dependency.
|
* Remove Core5Compat dependency.
|
||||||
|
|
||||||
0.8.3:
|
|
||||||
|
Version 0.8.3 (2020.10.24)
|
||||||
|
|
||||||
Bugfixes:
|
Bugfixes:
|
||||||
* Fixed updating playing widget song details in small cover mode.
|
* Fixed updating playing widget song details in small cover mode.
|
||||||
@@ -131,7 +272,8 @@ ChangeLog
|
|||||||
Enhancements:
|
Enhancements:
|
||||||
* (Windows) Added WASAPI plugin.
|
* (Windows) Added WASAPI plugin.
|
||||||
|
|
||||||
0.8.2:
|
|
||||||
|
Version 0.8.2 (2020.10.13)
|
||||||
|
|
||||||
Bugfixes:
|
Bugfixes:
|
||||||
* Fixed broken transition to next song for CUE files with certain audio formats (regression since version 0.6.13).
|
* Fixed broken transition to next song for CUE files with certain audio formats (regression since version 0.6.13).
|
||||||
@@ -143,7 +285,8 @@ ChangeLog
|
|||||||
* Removed use of HTML in system tray icon tooltip for all desktop environments instead of just KDE and Cinnamon.
|
* Removed use of HTML in system tray icon tooltip for all desktop environments instead of just KDE and Cinnamon.
|
||||||
* (Windows) Ignore "IDirectSoundBuffer_GetStatus The operation completed successfully" false error when switching device while playing.
|
* (Windows) Ignore "IDirectSoundBuffer_GetStatus The operation completed successfully" false error when switching device while playing.
|
||||||
|
|
||||||
0.8.1:
|
|
||||||
|
Version 0.8.1 (2020.10.09)
|
||||||
|
|
||||||
Bugfixes:
|
Bugfixes:
|
||||||
* Fixed engine selection in backend settings with Qt 6.
|
* Fixed engine selection in backend settings with Qt 6.
|
||||||
@@ -190,14 +333,15 @@ ChangeLog
|
|||||||
* Added Subsonic server side scrobbling support.
|
* Added Subsonic server side scrobbling support.
|
||||||
* Load thumbnails from iPods to show under device collection.
|
* Load thumbnails from iPods to show under device collection.
|
||||||
|
|
||||||
0.7.2:
|
Version 0.7.2 (2020.08.15)
|
||||||
|
|
||||||
Bugfixes:
|
Bugfixes:
|
||||||
* Fixed installation directory for translations.
|
* Fixed installation directory for translations.
|
||||||
* Fixed collection sorting for non-ASCII characters.
|
* Fixed collection sorting for non-ASCII characters.
|
||||||
* Fixed closing connected devices on exit.
|
* Fixed closing connected devices on exit.
|
||||||
|
|
||||||
0.7.1:
|
|
||||||
|
Version 0.7.1 (2020.08.15)
|
||||||
|
|
||||||
Bugfixes:
|
Bugfixes:
|
||||||
* Fixed incorrectly mapped global shortcuts keys "2" and "3".
|
* Fixed incorrectly mapped global shortcuts keys "2" and "3".
|
||||||
@@ -236,7 +380,8 @@ ChangeLog
|
|||||||
* Removed Xine engine support.
|
* Removed Xine engine support.
|
||||||
* Removed broken imobiledevice (iPhone) support.
|
* Removed broken imobiledevice (iPhone) support.
|
||||||
|
|
||||||
0.6.13:
|
|
||||||
|
Version 0.6.13 (2020.07.13)
|
||||||
|
|
||||||
Bugfixes:
|
Bugfixes:
|
||||||
* Fixed cut-off text in about dialog.
|
* Fixed cut-off text in about dialog.
|
||||||
@@ -262,7 +407,8 @@ ChangeLog
|
|||||||
* Fixed unit test for testing playlist model.
|
* Fixed unit test for testing playlist model.
|
||||||
* Added new unit tests for tagreader.
|
* Added new unit tests for tagreader.
|
||||||
|
|
||||||
0.6.12:
|
|
||||||
|
Version 0.6.12 (2020.06.07)
|
||||||
|
|
||||||
Bugfixes:
|
Bugfixes:
|
||||||
* Fixed height of about dialog.
|
* Fixed height of about dialog.
|
||||||
@@ -275,7 +421,8 @@ ChangeLog
|
|||||||
* Sort folders added from file view.
|
* Sort folders added from file view.
|
||||||
* Changed default collection grouping to album - disc.
|
* Changed default collection grouping to album - disc.
|
||||||
|
|
||||||
0.6.11:
|
|
||||||
|
Version 0.6.11 (2020.05.16)
|
||||||
|
|
||||||
Bugfixes:
|
Bugfixes:
|
||||||
* Fixed MPRIS missing art url when playing albums with embedded cover.
|
* Fixed MPRIS missing art url when playing albums with embedded cover.
|
||||||
@@ -301,10 +448,11 @@ ChangeLog
|
|||||||
* Added album covers from Musixmatch and Spotify.
|
* Added album covers from Musixmatch and Spotify.
|
||||||
* Added lyrics from Genius, Musixmatch and ChartLyrics.
|
* Added lyrics from Genius, Musixmatch and ChartLyrics.
|
||||||
|
|
||||||
0.6.10:
|
|
||||||
|
Version 0.6.10 (2020.05.01)
|
||||||
|
|
||||||
Bugfixes:
|
Bugfixes:
|
||||||
* Fixed Subsonic album covers not working for albums with non ASCII charcters.
|
* Fixed Subsonic album covers not working for albums with non ASCII characters.
|
||||||
* Fixed reading date and genre from individual tracks in CUE sheets.
|
* Fixed reading date and genre from individual tracks in CUE sheets.
|
||||||
* Fixed resume playback on startup for CUE songs.
|
* Fixed resume playback on startup for CUE songs.
|
||||||
* Fixed album cover manager not showing complete album titles in the list of album covers.
|
* Fixed album cover manager not showing complete album titles in the list of album covers.
|
||||||
@@ -313,7 +461,7 @@ ChangeLog
|
|||||||
* Fixed engine and device in context using too large icons when icons were loaded from the system theme.
|
* Fixed engine and device in context using too large icons when icons were loaded from the system theme.
|
||||||
* Fixed "Secure connection setup failed" problem on Windows when playing streams.
|
* Fixed "Secure connection setup failed" problem on Windows when playing streams.
|
||||||
* Fixed margin for song title text in context.
|
* Fixed margin for song title text in context.
|
||||||
* Fixed UNC paths with non ASCII charcters not working.
|
* Fixed UNC paths with non ASCII characters not working.
|
||||||
|
|
||||||
Enhancements:
|
Enhancements:
|
||||||
* Allowing all characters except slash and backslash when organising music unless options to strip characters is checked.
|
* Allowing all characters except slash and backslash when organising music unless options to strip characters is checked.
|
||||||
@@ -328,9 +476,9 @@ ChangeLog
|
|||||||
* Only showing song length in context when available.
|
* Only showing song length in context when available.
|
||||||
* Sort album cover search results by score and pick the best 3 first before trying others to improve album cover search speed.
|
* Sort album cover search results by score and pick the best 3 first before trying others to improve album cover search speed.
|
||||||
* Make scrobbler work for streams.
|
* Make scrobbler work for streams.
|
||||||
* Added search for lyrics as a seperate option in context.
|
* Added search for lyrics as a separate option in context.
|
||||||
* Made font and font sizes in context configurable.
|
* Made font and font sizes in context configurable.
|
||||||
* Splitting artist and song title to the relevant metadata when artist and song title is sent as title seperated by a dash in streams.
|
* Splitting artist and song title to the relevant metadata when artist and song title is sent as title separated by a dash in streams.
|
||||||
* Added label to show collection pixmap disk cache used in settings.
|
* Added label to show collection pixmap disk cache used in settings.
|
||||||
* Icreased default collection pixmap disk cache to 360.
|
* Icreased default collection pixmap disk cache to 360.
|
||||||
|
|
||||||
@@ -341,7 +489,8 @@ ChangeLog
|
|||||||
Removed features:
|
Removed features:
|
||||||
* Removed Phonon engine support.
|
* Removed Phonon engine support.
|
||||||
|
|
||||||
Version 0.6.9:
|
|
||||||
|
Version 0.6.9 (2020.03.09)
|
||||||
|
|
||||||
BugFixes:
|
BugFixes:
|
||||||
* Fixed playlist metadata updating interfering with manual tag editing.
|
* Fixed playlist metadata updating interfering with manual tag editing.
|
||||||
@@ -373,7 +522,8 @@ Version 0.6.9:
|
|||||||
* Tidal support (No agreement).
|
* Tidal support (No agreement).
|
||||||
* QObuz support (No agreement).
|
* QObuz support (No agreement).
|
||||||
|
|
||||||
Version 0.6.8:
|
|
||||||
|
Version 0.6.8 (2020.01.05)
|
||||||
|
|
||||||
* Fixed stuck tabbar and collection GUI with some themes.
|
* Fixed stuck tabbar and collection GUI with some themes.
|
||||||
* Fixed possible crashes related to QProxyStyle.
|
* Fixed possible crashes related to QProxyStyle.
|
||||||
@@ -390,7 +540,8 @@ Version 0.6.8:
|
|||||||
* (macOS) Fixed filesystem watcher to correctly pick up changed collection directories.
|
* (macOS) Fixed filesystem watcher to correctly pick up changed collection directories.
|
||||||
* (Windows) Fixed translations not being included.
|
* (Windows) Fixed translations not being included.
|
||||||
|
|
||||||
Version 0.6.7:
|
|
||||||
|
Version 0.6.7 (2019.11.27)
|
||||||
|
|
||||||
* Fixed crash when cancelling scrobbler authentication
|
* Fixed crash when cancelling scrobbler authentication
|
||||||
* Fixed "Double clicking a song in the playlist" behaviour setting
|
* Fixed "Double clicking a song in the playlist" behaviour setting
|
||||||
@@ -407,7 +558,8 @@ Version 0.6.7:
|
|||||||
* Removed left click on analyzer to popup menu
|
* Removed left click on analyzer to popup menu
|
||||||
* (Windows) Added killproc executable to terminate running process before uninstalling
|
* (Windows) Added killproc executable to terminate running process before uninstalling
|
||||||
|
|
||||||
Version 0.6.6:
|
|
||||||
|
Version 0.6.6 (2019.11.09)
|
||||||
|
|
||||||
* Fixed lowercased album artist in playlist column
|
* Fixed lowercased album artist in playlist column
|
||||||
* Fixed compiling with different optional features turned off
|
* Fixed compiling with different optional features turned off
|
||||||
@@ -425,7 +577,8 @@ Version 0.6.6:
|
|||||||
* Added option to automatically select current playing track
|
* Added option to automatically select current playing track
|
||||||
* (Windows) Added support for WASAPI
|
* (Windows) Added support for WASAPI
|
||||||
|
|
||||||
Version 0.6.5:
|
|
||||||
|
Version 0.6.5 (2019.09.30)
|
||||||
|
|
||||||
* Fixed scrobbler not to send scrobbles multiple times when metadata is updated
|
* Fixed scrobbler not to send scrobbles multiple times when metadata is updated
|
||||||
* Fixed Listenbrainz scrobbler not don't send "various artists" as album artist
|
* Fixed Listenbrainz scrobbler not don't send "various artists" as album artist
|
||||||
@@ -434,7 +587,8 @@ Version 0.6.5:
|
|||||||
* Fixed OSD pretty positioning on Windows on screens with negative geometry
|
* Fixed OSD pretty positioning on Windows on screens with negative geometry
|
||||||
* Fixed appdata file to pass full validation
|
* Fixed appdata file to pass full validation
|
||||||
|
|
||||||
Version 0.6.4:
|
|
||||||
|
Version 0.6.4 (2019.09.25)
|
||||||
|
|
||||||
* Added setting for fancy tabbar background color
|
* Added setting for fancy tabbar background color
|
||||||
* Added setting to make marking songs unavailable optional
|
* Added setting to make marking songs unavailable optional
|
||||||
@@ -461,18 +615,21 @@ Version 0.6.4:
|
|||||||
* Fixed restoring to original window size when restoring from system tray
|
* Fixed restoring to original window size when restoring from system tray
|
||||||
* Updated 3rdparty taglib
|
* Updated 3rdparty taglib
|
||||||
|
|
||||||
Version 0.6.3:
|
|
||||||
|
Version 0.6.3 (2019.08.05)
|
||||||
|
|
||||||
* Fixed crash when using internet services.
|
* Fixed crash when using internet services.
|
||||||
* Fixed musicbrainz tagfetcher only showing 1 result per song.
|
* Fixed musicbrainz tagfetcher only showing 1 result per song.
|
||||||
* Fixed collection watcher to unwatch deleted directories.
|
* Fixed collection watcher to unwatch deleted directories.
|
||||||
* Added "album - disc" grouping.
|
* Added "album - disc" grouping.
|
||||||
|
|
||||||
Version 0.6.2:
|
|
||||||
|
Version 0.6.2 (2019.08.03)
|
||||||
|
|
||||||
* Disabled fatal error for FTS5 cmake test.
|
* Disabled fatal error for FTS5 cmake test.
|
||||||
|
|
||||||
Version 0.6.1:
|
|
||||||
|
Version 0.6.1 (2019.08.03)
|
||||||
|
|
||||||
* Compare artist and album case-insensitive when generating score for album covers.
|
* Compare artist and album case-insensitive when generating score for album covers.
|
||||||
* Fixed broken return value of sendMessage() in SingleApplication causing application to be started twice.
|
* Fixed broken return value of sendMessage() in SingleApplication causing application to be started twice.
|
||||||
@@ -518,11 +675,13 @@ Version 0.6.1:
|
|||||||
* Fixed certain cases where the playing widget gets stuck when switching fast between context and other widgets.
|
* Fixed certain cases where the playing widget gets stuck when switching fast between context and other widgets.
|
||||||
* Removed ChartLyrics provider (service have been down for a long time).
|
* Removed ChartLyrics provider (service have been down for a long time).
|
||||||
|
|
||||||
Version 0.5.5:
|
|
||||||
|
Version 0.5.5 (2019.05.05)
|
||||||
|
|
||||||
* Fixed Tidal API url
|
* Fixed Tidal API url
|
||||||
|
|
||||||
Version 0.5.4:
|
|
||||||
|
Version 0.5.4 (2019.05.05)
|
||||||
|
|
||||||
* Changed description for offline mode scrobbling for less confusion
|
* Changed description for offline mode scrobbling for less confusion
|
||||||
* Fixed scrobbler to not send "playing now" when in offline mode
|
* Fixed scrobbler to not send "playing now" when in offline mode
|
||||||
@@ -553,7 +712,8 @@ Version 0.5.4:
|
|||||||
* Fixed and improved snap including upgrading to core18 and adding proper alsa support
|
* Fixed and improved snap including upgrading to core18 and adding proper alsa support
|
||||||
* Fixed resume playback on startup not working for other than the first playlist
|
* Fixed resume playback on startup not working for other than the first playlist
|
||||||
|
|
||||||
Version 0.5.3:
|
|
||||||
|
Version 0.5.3 (2019.03.02)
|
||||||
|
|
||||||
* Changed default tagging to albumartist in organise dialog
|
* Changed default tagging to albumartist in organise dialog
|
||||||
* Removed support for older taglib in tagreader
|
* Removed support for older taglib in tagreader
|
||||||
@@ -593,7 +753,8 @@ Version 0.5.3:
|
|||||||
* Added group by format
|
* Added group by format
|
||||||
* Fixed gstreamer leaks
|
* Fixed gstreamer leaks
|
||||||
|
|
||||||
Version 0.5.2:
|
|
||||||
|
Version 0.5.2 (2019.01.26)
|
||||||
|
|
||||||
* Added error handling and message for URL handler
|
* Added error handling and message for URL handler
|
||||||
* Added SingleCoreApplication secondary check
|
* Added SingleCoreApplication secondary check
|
||||||
@@ -610,7 +771,8 @@ Version 0.5.2:
|
|||||||
* Added option to copy album cover in organise dialog (filesystem and libgpod devices)
|
* Added option to copy album cover in organise dialog (filesystem and libgpod devices)
|
||||||
* Added raise() to make sure window is on top when strawberry is started twice
|
* Added raise() to make sure window is on top when strawberry is started twice
|
||||||
|
|
||||||
Version 0.5.1:
|
|
||||||
|
Version 0.5.1 (2019.01.12)
|
||||||
|
|
||||||
* Added scrobbler with support for Last.fm, Libre.fm and ListenBrainz
|
* Added scrobbler with support for Last.fm, Libre.fm and ListenBrainz
|
||||||
* Fixed key up causing playback to reset
|
* Fixed key up causing playback to reset
|
||||||
@@ -641,7 +803,8 @@ Version 0.5.1:
|
|||||||
* Added debian copyright file
|
* Added debian copyright file
|
||||||
* Fixed some compile errors
|
* Fixed some compile errors
|
||||||
|
|
||||||
Version 0.4.2:
|
|
||||||
|
Version 0.4.2 (2018.11.28)
|
||||||
|
|
||||||
* Updated AppStream data file to newer specifications
|
* Updated AppStream data file to newer specifications
|
||||||
* Fixed Deezer engine to use quality setting
|
* Fixed Deezer engine to use quality setting
|
||||||
@@ -655,7 +818,8 @@ Version 0.4.2:
|
|||||||
* (Windows) Corrected uninstalled files on x64 installer
|
* (Windows) Corrected uninstalled files on x64 installer
|
||||||
* (macOS) Fixed poor performance
|
* (macOS) Fixed poor performance
|
||||||
|
|
||||||
Version 0.4.1:
|
|
||||||
|
Version 0.4.1 (2018.11.01)
|
||||||
|
|
||||||
* Fixed crash in analyzer
|
* Fixed crash in analyzer
|
||||||
* Fixed trying to use systray even if the desktop had no systray
|
* Fixed trying to use systray even if the desktop had no systray
|
||||||
@@ -673,11 +837,13 @@ Version 0.4.1:
|
|||||||
* Added AppStream data file
|
* Added AppStream data file
|
||||||
* Fixed compiling with Qt 5 versions of system QtSingleApplication and Qxt library
|
* Fixed compiling with Qt 5 versions of system QtSingleApplication and Qxt library
|
||||||
|
|
||||||
Version 0.3.3:
|
|
||||||
|
Version 0.3.3 (2018.09.24)
|
||||||
|
|
||||||
* Fixed Tidal login
|
* Fixed Tidal login
|
||||||
|
|
||||||
Version 0.3.2:
|
|
||||||
|
Version 0.3.2 (2018.09.24)
|
||||||
|
|
||||||
* Fixed search error not shown in Tidal search
|
* Fixed search error not shown in Tidal search
|
||||||
* Added URL handler for Tidal, now retrieving URL's when playing instead of when searching
|
* Added URL handler for Tidal, now retrieving URL's when playing instead of when searching
|
||||||
@@ -689,7 +855,8 @@ Version 0.3.2:
|
|||||||
* Added encoding of Tidal token in the source code
|
* Added encoding of Tidal token in the source code
|
||||||
* Added encoding of Tidal password in the configuration
|
* Added encoding of Tidal password in the configuration
|
||||||
|
|
||||||
Version 0.3.1:
|
|
||||||
|
Version 0.3.1 (2018.09.15)
|
||||||
|
|
||||||
* Added new lyrics provider with lyrics from AudD and API Seeds
|
* Added new lyrics provider with lyrics from AudD and API Seeds
|
||||||
* New improved context widget with albums and lyrics
|
* New improved context widget with albums and lyrics
|
||||||
@@ -711,7 +878,8 @@ Version 0.3.1:
|
|||||||
* Added support for reading/writing lyrics to tags
|
* Added support for reading/writing lyrics to tags
|
||||||
* Fixed saving tags (APE) for WavPack files
|
* Fixed saving tags (APE) for WavPack files
|
||||||
|
|
||||||
Version 0.2.1:
|
|
||||||
|
Version 0.2.1 (2018.07.05)
|
||||||
|
|
||||||
* Fixed crash with newer Qt
|
* Fixed crash with newer Qt
|
||||||
* Fixed setting output/device for Xine and VLC backend
|
* Fixed setting output/device for Xine and VLC backend
|
||||||
@@ -721,19 +889,23 @@ Version 0.2.1:
|
|||||||
* Fixed device selection on macOS
|
* Fixed device selection on macOS
|
||||||
* Added xine on to windows build
|
* Added xine on to windows build
|
||||||
|
|
||||||
Version 0.1.6:
|
|
||||||
|
Version 0.1.6 (2018.06.07)
|
||||||
* Fixed crash on exit caused by NVIDIA driver
|
* Fixed crash on exit caused by NVIDIA driver
|
||||||
* Fixed PulseAudio device selection
|
* Fixed PulseAudio device selection
|
||||||
* Improvements to device selection
|
* Improvements to device selection
|
||||||
|
|
||||||
Version 0.1.5:
|
|
||||||
|
Version 0.1.5 (2018.05.16)
|
||||||
* Makefile fixes for building
|
* Makefile fixes for building
|
||||||
|
|
||||||
Version 0.1.4:
|
|
||||||
|
Version 0.1.4 (2018.05.14)
|
||||||
* Fixed compliation with clang compiler
|
* Fixed compliation with clang compiler
|
||||||
* This release is mainly to get it working on openbsd and freebsd.
|
* This release is mainly to get it working on openbsd and freebsd.
|
||||||
|
|
||||||
Version 0.1.3:
|
|
||||||
|
Version 0.1.3 (2018.05.12)
|
||||||
* Audio file detection by content
|
* Audio file detection by content
|
||||||
* Added builtin taglib to 3rdparty to support detecting audio by content instead of just file extension
|
* Added builtin taglib to 3rdparty to support detecting audio by content instead of just file extension
|
||||||
* Removed unneeded qsqlite from 3rdparty
|
* Removed unneeded qsqlite from 3rdparty
|
||||||
@@ -741,7 +913,8 @@ Version 0.1.3:
|
|||||||
* Replaced incorrect DLL libgstdirectsoundsink.dll (from gst 1.12.4) instead of libgstdirectsound.dll (from gst 1.14.0) for windows build
|
* Replaced incorrect DLL libgstdirectsoundsink.dll (from gst 1.12.4) instead of libgstdirectsound.dll (from gst 1.14.0) for windows build
|
||||||
* Fixed git versioning
|
* Fixed git versioning
|
||||||
|
|
||||||
Version 0.1.2:
|
|
||||||
|
Version 0.1.2 (2018.05.02)
|
||||||
* Fixed playback of WavPack files
|
* Fixed playback of WavPack files
|
||||||
* Fixed musicbrainz tagfetcher
|
* Fixed musicbrainz tagfetcher
|
||||||
* Use common regex (Song::kCoverRemoveDisc) for removing Disc/CD from album
|
* Use common regex (Song::kCoverRemoveDisc) for removing Disc/CD from album
|
||||||
@@ -751,5 +924,6 @@ Version 0.1.2:
|
|||||||
* Fixed problems with windows build missing some DLL's, only supplying required gstreamer-plugins now
|
* Fixed problems with windows build missing some DLL's, only supplying required gstreamer-plugins now
|
||||||
* Removed redundant code
|
* Removed redundant code
|
||||||
|
|
||||||
Version 0.1.1:
|
|
||||||
|
Version 0.1.1 (2018.04.07)
|
||||||
* Initial release
|
* Initial release
|
||||||
|
|||||||
40
README.md
@@ -1,7 +1,8 @@
|
|||||||
:strawberry: Strawberry Music Player [](https://github.com/strawberrymusicplayer/strawberry/actions)
|
:strawberry: Strawberry Music Player [](https://github.com/strawberrymusicplayer/strawberry/actions)
|
||||||
[](https://paypal.me/jonaskvinge)
|
|
||||||
[](https://patreon.com/jonaskvinge)
|
|
||||||
=======================
|
=======================
|
||||||
|
[](https://github.com/sponsors/jonaski)
|
||||||
|
[](https://patreon.com/jonaskvinge)
|
||||||
|
[](https://paypal.me/jonaskvinge)
|
||||||
|
|
||||||
Strawberry is a music player and music collection organizer. It is a fork of Clementine released in 2018 aimed at music collectors and audiophiles. It's written in C++ using the Qt toolkit.
|
Strawberry is a music player and music collection organizer. It is a fork of Clementine released in 2018 aimed at music collectors and audiophiles. It's written in C++ using the Qt toolkit.
|
||||||
|
|
||||||
@@ -44,14 +45,14 @@ Funding developers is a way to contribute to open source projects you appreciate
|
|||||||
* Playlist management
|
* Playlist management
|
||||||
* Smart and dynamic playlists
|
* Smart and dynamic playlists
|
||||||
* Advanced audio output and device configuration for bit-perfect playback on Linux
|
* Advanced audio output and device configuration for bit-perfect playback on Linux
|
||||||
* Edit tags on music files
|
* Edit tags on audio files
|
||||||
* Fetch tags from MusicBrainz
|
* Fetch tags from MusicBrainz
|
||||||
* Album cover art from [Last.fm](https://www.last.fm/), [Musicbrainz](https://musicbrainz.org/), [Discogs](https://www.discogs.com/), [Musixmatch](https://www.musixmatch.com/), [Deezer](https://www.deezer.com/), [Tidal](https://www.tidal.com/), [Qobuz](https://www.qobuz.com/) and [Spotify](https://www.spotify.com/)
|
* Album cover art from [Last.fm](https://www.last.fm/), [Musicbrainz](https://musicbrainz.org/), [Discogs](https://www.discogs.com/), [Musixmatch](https://www.musixmatch.com/), [Deezer](https://www.deezer.com/), [Tidal](https://www.tidal.com/), [Qobuz](https://www.qobuz.com/) and [Spotify](https://www.spotify.com/)
|
||||||
* Song lyrics from [AudD](https://audd.io/), [Genius](https://genius.com/), [Musixmatch](https://www.musixmatch.com/), [ChartLyrics](http://www.chartlyrics.com/), [lyrics.ovh](https://lyrics.ovh/) and [lololyrics.com](https://www.lololyrics.com/)
|
* Song lyrics from [AudD](https://audd.io/), [Genius](https://genius.com/), [Musixmatch](https://www.musixmatch.com/), [ChartLyrics](http://www.chartlyrics.com/), [lyrics.ovh](https://lyrics.ovh/) and [lololyrics.com](https://www.lololyrics.com/)
|
||||||
* Support for multiple backends
|
* Support for multiple backends
|
||||||
* Audio analyzer
|
* Audio analyzer
|
||||||
* Audio equalizer
|
* Audio equalizer
|
||||||
* Transfer music to iPod, MTP or mass-storage USB player
|
* Transfer music to mass-storage USB players, MTP compatible devices and iPod Nano/Classic
|
||||||
* Scrobbler with support for [Last.fm](https://www.last.fm/), [Libre.fm](https://libre.fm/) and [ListenBrainz](https://listenbrainz.org/)
|
* Scrobbler with support for [Last.fm](https://www.last.fm/), [Libre.fm](https://libre.fm/) and [ListenBrainz](https://listenbrainz.org/)
|
||||||
* Subsonic, Tidal and Qobuz streaming support
|
* Subsonic, Tidal and Qobuz streaming support
|
||||||
|
|
||||||
@@ -64,32 +65,29 @@ It has so far been tested to work on Linux, OpenBSD, FreeBSD, macOS and Windows.
|
|||||||
|
|
||||||
To build Strawberry from source you need the following installed on your system with the additional development packages/headers:
|
To build Strawberry from source you need the following installed on your system with the additional development packages/headers:
|
||||||
|
|
||||||
* [CMake and Make tools](https://cmake.org/)
|
* [CMake](https://cmake.org/)
|
||||||
* [GCC](https://gcc.gnu.org/) or [clang](https://clang.llvm.org/) compiler
|
* [GCC](https://gcc.gnu.org/), [Clang](https://clang.llvm.org/) or [MSVC](https://visualstudio.microsoft.com/vs/features/cplusplus/) compiler
|
||||||
* [Boost](https://www.boost.org/)
|
* [Boost](https://www.boost.org/)
|
||||||
* [POSIX thread (pthread)](http://www.yolinux.com/TUTORIALS/LinuxTutorialPosixThreads.html)
|
|
||||||
* [GLib](https://developer.gnome.org/glib/)
|
* [GLib](https://developer.gnome.org/glib/)
|
||||||
* [Protobuf](https://developers.google.com/protocol-buffers/)
|
* [Qt 5.9 or higher (or Qt 6) with components Core, Gui, Widgets, Concurrent, Network and Sql](https://www.qt.io/)
|
||||||
* [Qt 5.8 or higher (or Qt 6) with components Core, Gui, Widgets, Concurrent, Network and Sql](https://www.qt.io/)
|
|
||||||
* [Qt components X11Extras and D-Bus for Linux/BSD and WinExtras for Windows](https://www.qt.io/)
|
|
||||||
* [SQLite 3.9 or newer with FTS5](https://www.sqlite.org)
|
* [SQLite 3.9 or newer with FTS5](https://www.sqlite.org)
|
||||||
* [Chromaprint](https://acoustid.org/chromaprint)
|
* [Protobuf](https://developers.google.com/protocol-buffers/)
|
||||||
* [ALSA (linux)](https://www.alsa-project.org/)
|
* [ALSA (Required on Linux)](https://www.alsa-project.org/)
|
||||||
* [D-Bus (linux)](https://www.freedesktop.org/wiki/Software/dbus/)
|
* [D-Bus (Required on Linux)](https://www.freedesktop.org/wiki/Software/dbus/)
|
||||||
* [PulseAudio (linux optional)](https://www.freedesktop.org/wiki/Software/PulseAudio/?)
|
|
||||||
* [GStreamer](https://gstreamer.freedesktop.org/) or [VLC](https://www.videolan.org)
|
* [GStreamer](https://gstreamer.freedesktop.org/) or [VLC](https://www.videolan.org)
|
||||||
* [GnuTLS](https://www.gnutls.org/)
|
* [GnuTLS](https://www.gnutls.org/)
|
||||||
* [TagLib](https://www.taglib.org/)
|
* [TagLib 1.11.1 or higher](https://www.taglib.org/) or [TagParser](https://github.com/Martchus/tagparser)
|
||||||
|
|
||||||
Optional dependencies:
|
Optional dependencies:
|
||||||
|
|
||||||
|
* Song fingerprinting and MusicBrainz tagging: [Chromaprint](https://acoustid.org/chromaprint)
|
||||||
|
* Moodbar: [fftw3](http://www.fftw.org/)
|
||||||
|
* PulseAudio integration: [PulseAudio](https://www.freedesktop.org/wiki/Software/PulseAudio/?)
|
||||||
* Audio CD: [libcdio](https://www.gnu.org/software/libcdio/)
|
* Audio CD: [libcdio](https://www.gnu.org/software/libcdio/)
|
||||||
* MTP devices: [libmtp](http://libmtp.sourceforge.net/)
|
* MTP devices: [libmtp](http://libmtp.sourceforge.net/)
|
||||||
* iPod Classic devices: [libgpod](http://www.gtkpod.org/libgpod/)
|
* iPod Classic devices: [libgpod](http://www.gtkpod.org/libgpod/)
|
||||||
* Moodbar: [fftw3](http://www.fftw.org/)
|
|
||||||
|
|
||||||
Either GStreamer or VLC engine is required, but only GStreamer is fully implemented, and works best, it is therefore recommended to use GStreamer.
|
You should also install the gstreamer plugins base and good, and optionally bad, ugly and libav to support all audio formats.
|
||||||
You should also install the gstreamer plugins base and good, and optionally bad, ugly and libav.
|
|
||||||
|
|
||||||
### :wrench: Compiling from source
|
### :wrench: Compiling from source
|
||||||
|
|
||||||
@@ -101,13 +99,13 @@ You should also install the gstreamer plugins base and good, and optionally bad,
|
|||||||
|
|
||||||
cd strawberry
|
cd strawberry
|
||||||
mkdir build && cd build
|
mkdir build && cd build
|
||||||
cmake ..
|
cmake .. -DBUILD_WITH_QT6=ON
|
||||||
make -j$(nproc)
|
make -j$(nproc)
|
||||||
sudo make install
|
sudo make install
|
||||||
|
|
||||||
To compile with Qt 6 use:
|
Strawberry is backwards compatible with Qt 5, to compile with Qt 5 use:
|
||||||
|
|
||||||
cmake .. -DBUILD_WITH_QT6=ON
|
cmake .. -DBUILD_WITH_QT5=ON
|
||||||
|
|
||||||
### :penguin: Packaging status
|
### :penguin: Packaging status
|
||||||
|
|
||||||
|
|||||||
@@ -1,11 +1,10 @@
|
|||||||
#find_program(MACDEPLOYQT_EXECUTABLE NAMES macdeployqt PATHS /usr/local/opt/qt6/bin /usr/local/opt/qt5/bin /usr/local/bin REQUIRED)
|
#find_program(MACDEPLOYQT_EXECUTABLE NAMES macdeployqt PATHS /usr/local/opt/qt6/bin /usr/local/opt/qt5/bin /usr/local/bin REQUIRED)
|
||||||
#if(MACDEPLOYQT_EXECUTABLE)
|
|
||||||
# message(STATUS "Found macdeployqt: ${MACDEPLOYQT_EXECUTABLE}")
|
|
||||||
#else()
|
|
||||||
# message(WARNING "Missing macdeployqt executable.")
|
|
||||||
#endif()
|
|
||||||
|
|
||||||
set(MACDEPLOYQT_EXECUTABLE "${CMAKE_BINARY_DIR}/3rdparty/macdeployqt/macdeployqt")
|
set(MACDEPLOYQT_EXECUTABLE "${CMAKE_BINARY_DIR}/3rdparty/macdeployqt/macdeployqt")
|
||||||
|
if(MACDEPLOYQT_EXECUTABLE)
|
||||||
|
message(STATUS "Found macdeployqt: ${MACDEPLOYQT_EXECUTABLE}")
|
||||||
|
else()
|
||||||
|
message(WARNING "Missing macdeployqt executable.")
|
||||||
|
endif()
|
||||||
|
|
||||||
find_program(CREATEDMG_EXECUTABLE NAMES create-dmg REQUIRED)
|
find_program(CREATEDMG_EXECUTABLE NAMES create-dmg REQUIRED)
|
||||||
if(CREATEDMG_EXECUTABLE)
|
if(CREATEDMG_EXECUTABLE)
|
||||||
@@ -14,24 +13,27 @@ else()
|
|||||||
message(WARNING "Missing create-dmg executable.")
|
message(WARNING "Missing create-dmg executable.")
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
execute_process(COMMAND ${CMAKE_SOURCE_DIR}/dist/macos/macversion.sh OUTPUT_VARIABLE MACOS_VERSION_PACKAGE OUTPUT_STRIP_TRAILING_WHITESPACE)
|
if(MACDEPLOYQT_EXECUTABLE)
|
||||||
if(NOT MACOS_VERSION_PACKAGE)
|
add_custom_target(copy_gstreamer_plugins
|
||||||
message(WARNING "Could not set macOS version.")
|
#COMMAND ${CMAKE_SOURCE_DIR}/dist/macos/macgstcopy.sh strawberry.app
|
||||||
endif()
|
|
||||||
|
|
||||||
if(MACDEPLOYQT_EXECUTABLE AND CREATEDMG_EXECUTABLE AND MACOS_VERSION_PACKAGE)
|
|
||||||
add_custom_target(dmg
|
|
||||||
COMMAND mkdir -p ${CMAKE_BINARY_DIR}/strawberry.app/Contents/Frameworks/
|
|
||||||
COMMAND cp -r /usr/local/opt/sparkle/Sparkle.framework ${CMAKE_BINARY_DIR}/strawberry.app/Contents/Frameworks/
|
|
||||||
COMMAND ${MACDEPLOYQT_EXECUTABLE} strawberry.app -verbose=3 -executable=${CMAKE_BINARY_DIR}/strawberry.app/Contents/PlugIns/strawberry-tagreader
|
|
||||||
COMMAND ${CREATEDMG_EXECUTABLE} --volname strawberry --background "${CMAKE_SOURCE_DIR}/dist/macos/dmg_background.png" --app-drop-link 450 218 --icon strawberry.app 150 218 --window-size 600 450 strawberry-${STRAWBERRY_VERSION_PACKAGE}-${MACOS_VERSION_PACKAGE}-${CMAKE_HOST_SYSTEM_PROCESSOR}.dmg strawberry.app
|
|
||||||
WORKING_DIRECTORY ${CMAKE_BINARY_DIR}
|
|
||||||
)
|
)
|
||||||
add_custom_target(dmg2
|
add_custom_target(deploy
|
||||||
COMMAND mkdir -p ${CMAKE_BINARY_DIR}/strawberry.app/Contents/Frameworks/
|
COMMAND mkdir -p ${CMAKE_BINARY_DIR}/strawberry.app/Contents/{Frameworks,Resources}
|
||||||
COMMAND cp -r /usr/local/opt/sparkle/Sparkle.framework ${CMAKE_BINARY_DIR}/strawberry.app/Contents/Frameworks/
|
COMMAND cp -v ${CMAKE_SOURCE_DIR}/dist/macos/Info.plist ${CMAKE_BINARY_DIR}/strawberry.app/Contents/
|
||||||
|
COMMAND cp -v ${CMAKE_SOURCE_DIR}/dist/macos/strawberry.icns ${CMAKE_BINARY_DIR}/strawberry.app/Contents/Resources/
|
||||||
COMMAND ${MACDEPLOYQT_EXECUTABLE} strawberry.app -verbose=3 -executable=${CMAKE_BINARY_DIR}/strawberry.app/Contents/PlugIns/strawberry-tagreader
|
COMMAND ${MACDEPLOYQT_EXECUTABLE} strawberry.app -verbose=3 -executable=${CMAKE_BINARY_DIR}/strawberry.app/Contents/PlugIns/strawberry-tagreader
|
||||||
COMMAND ${CREATEDMG_EXECUTABLE} --skip-jenkins --volname strawberry --background "${CMAKE_SOURCE_DIR}/dist/macos/dmg_background.png" --app-drop-link 450 218 --icon strawberry.app 150 218 --window-size 600 450 strawberry-${STRAWBERRY_VERSION_PACKAGE}-${MACOS_VERSION_PACKAGE}-${CMAKE_HOST_SYSTEM_PROCESSOR}.dmg strawberry.app
|
|
||||||
WORKING_DIRECTORY ${CMAKE_BINARY_DIR}
|
WORKING_DIRECTORY ${CMAKE_BINARY_DIR}
|
||||||
|
DEPENDS strawberry strawberry-tagreader copy_gstreamer_plugins macdeployqt
|
||||||
)
|
)
|
||||||
|
add_custom_target(deploycheck
|
||||||
|
COMMAND ${CMAKE_BINARY_DIR}/ext/macdeploycheck/macdeploycheck strawberry.app
|
||||||
|
DEPENDS macdeploycheck
|
||||||
|
)
|
||||||
|
if(CREATEDMG_EXECUTABLE)
|
||||||
|
add_custom_target(dmg
|
||||||
|
COMMAND ${CREATEDMG_EXECUTABLE} --volname strawberry --background "${CMAKE_SOURCE_DIR}/dist/macos/dmg_background.png" --app-drop-link 450 218 --icon strawberry.app 150 218 --window-size 600 450 strawberry-${STRAWBERRY_VERSION_PACKAGE}-${CMAKE_HOST_SYSTEM_PROCESSOR}.dmg strawberry.app
|
||||||
|
WORKING_DIRECTORY ${CMAKE_BINARY_DIR}
|
||||||
|
DEPENDS deploy deploycheck
|
||||||
|
)
|
||||||
|
endif()
|
||||||
endif()
|
endif()
|
||||||
|
|||||||
@@ -15,11 +15,7 @@ macro(optional_source TOGGLE)
|
|||||||
list(APPEND OTHER_SOURCES ${OPTIONAL_SOURCE_HEADERS})
|
list(APPEND OTHER_SOURCES ${OPTIONAL_SOURCE_HEADERS})
|
||||||
|
|
||||||
set(_uic_sources)
|
set(_uic_sources)
|
||||||
if(BUILD_WITH_QT6)
|
qt_wrap_ui(_uic_sources ${OPTIONAL_SOURCE_UI})
|
||||||
qt6_wrap_ui(_uic_sources ${OPTIONAL_SOURCE_UI})
|
|
||||||
else()
|
|
||||||
qt5_wrap_ui(_uic_sources ${OPTIONAL_SOURCE_UI})
|
|
||||||
endif()
|
|
||||||
list(APPEND OTHER_SOURCES ${_uic_sources})
|
list(APPEND OTHER_SOURCES ${_uic_sources})
|
||||||
list(APPEND OTHER_UIC_SOURCES ${_uic_sources})
|
list(APPEND OTHER_UIC_SOURCES ${_uic_sources})
|
||||||
endif(${TOGGLE})
|
endif(${TOGGLE})
|
||||||
|
|||||||
@@ -12,10 +12,17 @@ if (LSB_RELEASE_EXEC AND RPMBUILD_EXEC)
|
|||||||
OUTPUT_VARIABLE DIST_RELEASE
|
OUTPUT_VARIABLE DIST_RELEASE
|
||||||
OUTPUT_STRIP_TRAILING_WHITESPACE
|
OUTPUT_STRIP_TRAILING_WHITESPACE
|
||||||
)
|
)
|
||||||
execute_process(COMMAND /bin/sh "-c" "${LSB_RELEASE_EXEC} -ds | tr '[:upper:]' '[:lower:]' | sed 's/\"//g' | sed 's/\\.//g' | cut -d' ' -f3"
|
if (${DIST_NAME} STREQUAL "openmandrivalinux")
|
||||||
OUTPUT_VARIABLE DIST_VERSION
|
execute_process(COMMAND /bin/sh "-c" "${LSB_RELEASE_EXEC} -ds | tr '[:upper:]' '[:lower:]' | sed 's/\"//g' | sed 's/\\./0/g' | cut -d' ' -f3"
|
||||||
OUTPUT_STRIP_TRAILING_WHITESPACE
|
OUTPUT_VARIABLE DIST_VERSION
|
||||||
)
|
OUTPUT_STRIP_TRAILING_WHITESPACE
|
||||||
|
)
|
||||||
|
else()
|
||||||
|
execute_process(COMMAND /bin/sh "-c" "${LSB_RELEASE_EXEC} -ds | tr '[:upper:]' '[:lower:]' | sed 's/\"//g' | sed 's/\\.//g' | cut -d' ' -f3"
|
||||||
|
OUTPUT_VARIABLE DIST_VERSION
|
||||||
|
OUTPUT_STRIP_TRAILING_WHITESPACE
|
||||||
|
)
|
||||||
|
endif()
|
||||||
if (DIST_NAME)
|
if (DIST_NAME)
|
||||||
|
|
||||||
message(STATUS "Distro Name: ${DIST_NAME}")
|
message(STATUS "Distro Name: ${DIST_NAME}")
|
||||||
@@ -44,6 +51,8 @@ if (LSB_RELEASE_EXEC AND RPMBUILD_EXEC)
|
|||||||
set(RPM_DISTRO "el${DIST_VERSION}")
|
set(RPM_DISTRO "el${DIST_VERSION}")
|
||||||
elseif (${DIST_NAME} STREQUAL "mageia" AND DIST_RELEASE)
|
elseif (${DIST_NAME} STREQUAL "mageia" AND DIST_RELEASE)
|
||||||
set(RPM_DISTRO "mga${DIST_RELEASE}")
|
set(RPM_DISTRO "mga${DIST_RELEASE}")
|
||||||
|
elseif (${DIST_NAME} STREQUAL "openmandrivalinux" AND DIST_VERSION)
|
||||||
|
set(RPM_DISTRO "omv${DIST_VERSION}")
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
if(NOT RPM_DISTRO)
|
if(NOT RPM_DISTRO)
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ macro(summary_show)
|
|||||||
list(SORT summary_willbuild)
|
list(SORT summary_willbuild)
|
||||||
list(SORT summary_willnotbuild)
|
list(SORT summary_willnotbuild)
|
||||||
message("")
|
message("")
|
||||||
message("Building strawberry version: ${STRAWBERRY_VERSION_DISPLAY}")
|
message("Building strawberry version: ${STRAWBERRY_VERSION_DISPLAY}, Qt version ${Qt${QT_VERSION_MAJOR}Core_VERSION}")
|
||||||
summary_show_part(summary_willbuild "The following components will be built:")
|
summary_show_part(summary_willbuild "The following components will be built:")
|
||||||
summary_show_part(summary_willnotbuild "The following components WILL NOT be built:")
|
summary_show_part(summary_willnotbuild "The following components WILL NOT be built:")
|
||||||
message("")
|
message("")
|
||||||
|
|||||||
@@ -75,10 +75,6 @@ macro(add_po outfiles po_prefix)
|
|||||||
file(APPEND ${_qrc} "<file>${po_prefix}${_lang}.qm</file>")
|
file(APPEND ${_qrc} "<file>${po_prefix}${_lang}.qm</file>")
|
||||||
endforeach(_lang)
|
endforeach(_lang)
|
||||||
file(APPEND ${_qrc} "</qresource></RCC>")
|
file(APPEND ${_qrc} "</qresource></RCC>")
|
||||||
if(BUILD_WITH_QT6)
|
qt_add_resources(${outfiles} ${_qrc})
|
||||||
qt6_add_resources(${outfiles} ${_qrc})
|
|
||||||
else()
|
|
||||||
qt5_add_resources(${outfiles} ${_qrc})
|
|
||||||
endif()
|
|
||||||
endif()
|
endif()
|
||||||
endmacro(add_po)
|
endmacro(add_po)
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
set(STRAWBERRY_VERSION_MAJOR 0)
|
set(STRAWBERRY_VERSION_MAJOR 1)
|
||||||
set(STRAWBERRY_VERSION_MINOR 9)
|
set(STRAWBERRY_VERSION_MINOR 0)
|
||||||
set(STRAWBERRY_VERSION_PATCH 3)
|
set(STRAWBERRY_VERSION_PATCH 3)
|
||||||
#set(STRAWBERRY_VERSION_PRERELEASE rc1)
|
#set(STRAWBERRY_VERSION_PRERELEASE rc1)
|
||||||
|
|
||||||
|
|||||||
@@ -14,6 +14,8 @@
|
|||||||
<file>schema/schema-11.sql</file>
|
<file>schema/schema-11.sql</file>
|
||||||
<file>schema/schema-12.sql</file>
|
<file>schema/schema-12.sql</file>
|
||||||
<file>schema/schema-13.sql</file>
|
<file>schema/schema-13.sql</file>
|
||||||
|
<file>schema/schema-14.sql</file>
|
||||||
|
<file>schema/schema-15.sql</file>
|
||||||
<file>schema/device-schema.sql</file>
|
<file>schema/device-schema.sql</file>
|
||||||
<file>style/strawberry.css</file>
|
<file>style/strawberry.css</file>
|
||||||
<file>style/smartplaylistsearchterm.css</file>
|
<file>style/smartplaylistsearchterm.css</file>
|
||||||
|
|||||||
@@ -92,6 +92,10 @@
|
|||||||
<file>icons/128x128/tidal.png</file>
|
<file>icons/128x128/tidal.png</file>
|
||||||
<file>icons/128x128/qobuz.png</file>
|
<file>icons/128x128/qobuz.png</file>
|
||||||
<file>icons/128x128/multimedia-player-ipod-standard-black.png</file>
|
<file>icons/128x128/multimedia-player-ipod-standard-black.png</file>
|
||||||
|
<file>icons/128x128/radio.png</file>
|
||||||
|
<file>icons/128x128/somafm.png</file>
|
||||||
|
<file>icons/128x128/radioparadise.png</file>
|
||||||
|
<file>icons/128x128/musicbrainz.png</file>
|
||||||
<file>icons/64x64/albums.png</file>
|
<file>icons/64x64/albums.png</file>
|
||||||
<file>icons/64x64/alsa.png</file>
|
<file>icons/64x64/alsa.png</file>
|
||||||
<file>icons/64x64/application-exit.png</file>
|
<file>icons/64x64/application-exit.png</file>
|
||||||
@@ -185,6 +189,10 @@
|
|||||||
<file>icons/64x64/tidal.png</file>
|
<file>icons/64x64/tidal.png</file>
|
||||||
<file>icons/64x64/qobuz.png</file>
|
<file>icons/64x64/qobuz.png</file>
|
||||||
<file>icons/64x64/multimedia-player-ipod-standard-black.png</file>
|
<file>icons/64x64/multimedia-player-ipod-standard-black.png</file>
|
||||||
|
<file>icons/64x64/radio.png</file>
|
||||||
|
<file>icons/64x64/somafm.png</file>
|
||||||
|
<file>icons/64x64/radioparadise.png</file>
|
||||||
|
<file>icons/64x64/musicbrainz.png</file>
|
||||||
<file>icons/48x48/albums.png</file>
|
<file>icons/48x48/albums.png</file>
|
||||||
<file>icons/48x48/alsa.png</file>
|
<file>icons/48x48/alsa.png</file>
|
||||||
<file>icons/48x48/application-exit.png</file>
|
<file>icons/48x48/application-exit.png</file>
|
||||||
@@ -282,6 +290,10 @@
|
|||||||
<file>icons/48x48/tidal.png</file>
|
<file>icons/48x48/tidal.png</file>
|
||||||
<file>icons/48x48/qobuz.png</file>
|
<file>icons/48x48/qobuz.png</file>
|
||||||
<file>icons/48x48/multimedia-player-ipod-standard-black.png</file>
|
<file>icons/48x48/multimedia-player-ipod-standard-black.png</file>
|
||||||
|
<file>icons/48x48/radio.png</file>
|
||||||
|
<file>icons/48x48/somafm.png</file>
|
||||||
|
<file>icons/48x48/radioparadise.png</file>
|
||||||
|
<file>icons/48x48/musicbrainz.png</file>
|
||||||
<file>icons/32x32/albums.png</file>
|
<file>icons/32x32/albums.png</file>
|
||||||
<file>icons/32x32/alsa.png</file>
|
<file>icons/32x32/alsa.png</file>
|
||||||
<file>icons/32x32/application-exit.png</file>
|
<file>icons/32x32/application-exit.png</file>
|
||||||
@@ -379,6 +391,10 @@
|
|||||||
<file>icons/32x32/tidal.png</file>
|
<file>icons/32x32/tidal.png</file>
|
||||||
<file>icons/32x32/qobuz.png</file>
|
<file>icons/32x32/qobuz.png</file>
|
||||||
<file>icons/32x32/multimedia-player-ipod-standard-black.png</file>
|
<file>icons/32x32/multimedia-player-ipod-standard-black.png</file>
|
||||||
|
<file>icons/32x32/radio.png</file>
|
||||||
|
<file>icons/32x32/somafm.png</file>
|
||||||
|
<file>icons/32x32/radioparadise.png</file>
|
||||||
|
<file>icons/32x32/musicbrainz.png</file>
|
||||||
<file>icons/22x22/albums.png</file>
|
<file>icons/22x22/albums.png</file>
|
||||||
<file>icons/22x22/alsa.png</file>
|
<file>icons/22x22/alsa.png</file>
|
||||||
<file>icons/22x22/application-exit.png</file>
|
<file>icons/22x22/application-exit.png</file>
|
||||||
@@ -476,5 +492,9 @@
|
|||||||
<file>icons/22x22/tidal.png</file>
|
<file>icons/22x22/tidal.png</file>
|
||||||
<file>icons/22x22/qobuz.png</file>
|
<file>icons/22x22/qobuz.png</file>
|
||||||
<file>icons/22x22/multimedia-player-ipod-standard-black.png</file>
|
<file>icons/22x22/multimedia-player-ipod-standard-black.png</file>
|
||||||
|
<file>icons/22x22/radio.png</file>
|
||||||
|
<file>icons/22x22/somafm.png</file>
|
||||||
|
<file>icons/22x22/radioparadise.png</file>
|
||||||
|
<file>icons/22x22/musicbrainz.png</file>
|
||||||
</qresource>
|
</qresource>
|
||||||
</RCC>
|
</RCC>
|
||||||
|
|||||||
BIN
data/icons/128x128/musicbrainz.png
Normal file
|
After Width: | Height: | Size: 6.1 KiB |
BIN
data/icons/128x128/radio.png
Normal file
|
After Width: | Height: | Size: 33 KiB |
BIN
data/icons/128x128/radioparadise.png
Normal file
|
After Width: | Height: | Size: 8.8 KiB |
BIN
data/icons/128x128/somafm.png
Normal file
|
After Width: | Height: | Size: 1.8 KiB |
BIN
data/icons/22x22/musicbrainz.png
Normal file
|
After Width: | Height: | Size: 1.0 KiB |
BIN
data/icons/22x22/radio.png
Normal file
|
After Width: | Height: | Size: 1.1 KiB |
BIN
data/icons/22x22/radioparadise.png
Normal file
|
After Width: | Height: | Size: 1.2 KiB |
BIN
data/icons/22x22/somafm.png
Normal file
|
After Width: | Height: | Size: 639 B |
BIN
data/icons/32x32/musicbrainz.png
Normal file
|
After Width: | Height: | Size: 947 B |
BIN
data/icons/32x32/radio.png
Normal file
|
After Width: | Height: | Size: 2.1 KiB |
BIN
data/icons/32x32/radioparadise.png
Normal file
|
After Width: | Height: | Size: 1.8 KiB |
BIN
data/icons/32x32/somafm.png
Normal file
|
After Width: | Height: | Size: 626 B |
BIN
data/icons/48x48/musicbrainz.png
Normal file
|
After Width: | Height: | Size: 1.3 KiB |
BIN
data/icons/48x48/radio.png
Normal file
|
After Width: | Height: | Size: 5.2 KiB |
BIN
data/icons/48x48/radioparadise.png
Normal file
|
After Width: | Height: | Size: 2.9 KiB |
BIN
data/icons/48x48/somafm.png
Normal file
|
After Width: | Height: | Size: 1.1 KiB |
BIN
data/icons/64x64/musicbrainz.png
Normal file
|
After Width: | Height: | Size: 3.8 KiB |
BIN
data/icons/64x64/radio.png
Normal file
|
After Width: | Height: | Size: 9.6 KiB |
BIN
data/icons/64x64/radioparadise.png
Normal file
|
After Width: | Height: | Size: 3.7 KiB |
BIN
data/icons/64x64/somafm.png
Normal file
|
After Width: | Height: | Size: 996 B |
BIN
data/icons/full/musicbrainz.png
Normal file
|
After Width: | Height: | Size: 13 KiB |
BIN
data/icons/full/radio.png
Normal file
|
After Width: | Height: | Size: 33 KiB |
BIN
data/icons/full/radioparadise.png
Normal file
|
After Width: | Height: | Size: 44 KiB |
BIN
data/icons/full/somafm.png
Normal file
|
After Width: | Height: | Size: 1.8 KiB |
@@ -1,35 +1,35 @@
|
|||||||
CREATE TABLE device_%deviceid_directories (
|
CREATE TABLE device_%deviceid_directories (
|
||||||
path TEXT NOT NULL,
|
path TEXT NOT NULL DEFAULT '',
|
||||||
subdirs INTEGER NOT NULL
|
subdirs INTEGER NOT NULL
|
||||||
);
|
);
|
||||||
|
|
||||||
CREATE TABLE device_%deviceid_subdirectories (
|
CREATE TABLE device_%deviceid_subdirectories (
|
||||||
directory_id INTEGER NOT NULL,
|
directory_id INTEGER NOT NULL,
|
||||||
path TEXT NOT NULL,
|
path TEXT NOT NULL DEFAULT '',
|
||||||
mtime INTEGER NOT NULL
|
mtime INTEGER NOT NULL
|
||||||
);
|
);
|
||||||
|
|
||||||
CREATE TABLE device_%deviceid_songs (
|
CREATE TABLE device_%deviceid_songs (
|
||||||
|
|
||||||
title TEXT,
|
title TEXT DEFAULT '',
|
||||||
album TEXT,
|
album TEXT DEFAULT '',
|
||||||
artist TEXT,
|
artist TEXT DEFAULT '',
|
||||||
albumartist TEXT,
|
albumartist TEXT DEFAULT '',
|
||||||
track INTEGER NOT NULL DEFAULT -1,
|
track INTEGER NOT NULL DEFAULT -1,
|
||||||
disc INTEGER NOT NULL DEFAULT -1,
|
disc INTEGER NOT NULL DEFAULT -1,
|
||||||
year INTEGER NOT NULL DEFAULT -1,
|
year INTEGER NOT NULL DEFAULT -1,
|
||||||
originalyear INTEGER NOT NULL DEFAULT 0,
|
originalyear INTEGER NOT NULL DEFAULT 0,
|
||||||
genre TEXT,
|
genre TEXT DEFAULT '',
|
||||||
compilation INTEGER NOT NULL DEFAULT 0,
|
compilation INTEGER NOT NULL DEFAULT 0,
|
||||||
composer TEXT,
|
composer TEXT DEFAULT '',
|
||||||
performer TEXT,
|
performer TEXT DEFAULT '',
|
||||||
grouping TEXT,
|
grouping TEXT DEFAULT '',
|
||||||
comment TEXT,
|
comment TEXT DEFAULT '',
|
||||||
lyrics TEXT,
|
lyrics TEXT DEFAULT '',
|
||||||
|
|
||||||
artist_id TEXT,
|
artist_id TEXT DEFAULT '',
|
||||||
album_id TEXT,
|
album_id TEXT DEFAULT '',
|
||||||
song_id TEXT,
|
song_id TEXT DEFAULT '',
|
||||||
|
|
||||||
beginning INTEGER NOT NULL DEFAULT 0,
|
beginning INTEGER NOT NULL DEFAULT 0,
|
||||||
length INTEGER NOT NULL DEFAULT 0,
|
length INTEGER NOT NULL DEFAULT 0,
|
||||||
@@ -40,29 +40,32 @@ CREATE TABLE device_%deviceid_songs (
|
|||||||
|
|
||||||
source INTEGER NOT NULL DEFAULT 0,
|
source INTEGER NOT NULL DEFAULT 0,
|
||||||
directory_id INTEGER NOT NULL DEFAULT -1,
|
directory_id INTEGER NOT NULL DEFAULT -1,
|
||||||
url TEXT NOT NULL,
|
url TEXT NOT NULL DEFAULT '',
|
||||||
filetype INTEGER NOT NULL DEFAULT 0,
|
filetype INTEGER NOT NULL DEFAULT 0,
|
||||||
filesize INTEGER NOT NULL DEFAULT -1,
|
filesize INTEGER NOT NULL DEFAULT -1,
|
||||||
mtime INTEGER NOT NULL DEFAULT -1,
|
mtime INTEGER NOT NULL DEFAULT -1,
|
||||||
ctime INTEGER NOT NULL DEFAULT -1,
|
ctime INTEGER NOT NULL DEFAULT -1,
|
||||||
unavailable INTEGER DEFAULT 0,
|
unavailable INTEGER DEFAULT 0,
|
||||||
|
|
||||||
|
fingerprint TEXT DEFAULT '',
|
||||||
|
|
||||||
playcount INTEGER NOT NULL DEFAULT 0,
|
playcount INTEGER NOT NULL DEFAULT 0,
|
||||||
skipcount INTEGER NOT NULL DEFAULT 0,
|
skipcount INTEGER NOT NULL DEFAULT 0,
|
||||||
lastplayed INTEGER NOT NULL DEFAULT -1,
|
lastplayed INTEGER NOT NULL DEFAULT -1,
|
||||||
|
lastseen INTEGER NOT NULL DEFAULT -1,
|
||||||
|
|
||||||
compilation_detected INTEGER DEFAULT 0,
|
compilation_detected INTEGER DEFAULT 0,
|
||||||
compilation_on INTEGER NOT NULL DEFAULT 0,
|
compilation_on INTEGER NOT NULL DEFAULT 0,
|
||||||
compilation_off INTEGER NOT NULL DEFAULT 0,
|
compilation_off INTEGER NOT NULL DEFAULT 0,
|
||||||
compilation_effective INTEGER NOT NULL DEFAULT 0,
|
compilation_effective INTEGER NOT NULL DEFAULT 0,
|
||||||
|
|
||||||
art_automatic TEXT,
|
art_automatic TEXT DEFAULT '',
|
||||||
art_manual TEXT,
|
art_manual TEXT DEFAULT '',
|
||||||
|
|
||||||
effective_albumartist TEXT,
|
effective_albumartist TEXT DEFAULT '',
|
||||||
effective_originalyear INTEGER NOT NULL DEFAULT 0,
|
effective_originalyear INTEGER NOT NULL DEFAULT 0,
|
||||||
|
|
||||||
cue_path TEXT,
|
cue_path TEXT DEFAULT '',
|
||||||
|
|
||||||
rating INTEGER DEFAULT -1
|
rating INTEGER DEFAULT -1
|
||||||
|
|
||||||
@@ -77,4 +80,4 @@ CREATE VIRTUAL TABLE device_%deviceid_fts USING fts5(
|
|||||||
tokenize = "unicode61 remove_diacritics 1"
|
tokenize = "unicode61 remove_diacritics 1"
|
||||||
);
|
);
|
||||||
|
|
||||||
UPDATE devices SET schema_version=2 WHERE ROWID=%deviceid;
|
UPDATE devices SET schema_version=3 WHERE ROWID=%deviceid;
|
||||||
|
|||||||
5
data/schema/schema-14.sql
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
ALTER TABLE %allsongstables ADD COLUMN fingerprint TEXT DEFAULT '';
|
||||||
|
|
||||||
|
ALTER TABLE %allsongstables ADD COLUMN lastseen INTEGER NOT NULL DEFAULT -1;
|
||||||
|
|
||||||
|
UPDATE schema_version SET version=14;
|
||||||
8
data/schema/schema-15.sql
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
CREATE TABLE IF NOT EXISTS radio_channels (
|
||||||
|
source INTEGER NOT NULL DEFAULT 0,
|
||||||
|
name TEXT DEFAULT '',
|
||||||
|
url TEXT DEFAULT '',
|
||||||
|
thumbnail_url TEXT DEFAULT ''
|
||||||
|
);
|
||||||
|
|
||||||
|
UPDATE schema_version SET version=15;
|
||||||
@@ -4,40 +4,40 @@ CREATE TABLE IF NOT EXISTS schema_version (
|
|||||||
|
|
||||||
DELETE FROM schema_version;
|
DELETE FROM schema_version;
|
||||||
|
|
||||||
INSERT INTO schema_version (version) VALUES (13);
|
INSERT INTO schema_version (version) VALUES (15);
|
||||||
|
|
||||||
CREATE TABLE IF NOT EXISTS directories (
|
CREATE TABLE IF NOT EXISTS directories (
|
||||||
path TEXT NOT NULL,
|
path TEXT NOT NULL DEFAULT '',
|
||||||
subdirs INTEGER NOT NULL
|
subdirs INTEGER NOT NULL
|
||||||
);
|
);
|
||||||
|
|
||||||
CREATE TABLE IF NOT EXISTS subdirectories (
|
CREATE TABLE IF NOT EXISTS subdirectories (
|
||||||
directory_id INTEGER NOT NULL,
|
directory_id INTEGER NOT NULL,
|
||||||
path TEXT NOT NULL,
|
path TEXT NOT NULL DEFAULT '',
|
||||||
mtime INTEGER NOT NULL
|
mtime INTEGER NOT NULL
|
||||||
);
|
);
|
||||||
|
|
||||||
CREATE TABLE IF NOT EXISTS songs (
|
CREATE TABLE IF NOT EXISTS songs (
|
||||||
|
|
||||||
title TEXT,
|
title TEXT DEFAULT '',
|
||||||
album TEXT,
|
album TEXT DEFAULT '',
|
||||||
artist TEXT,
|
artist TEXT DEFAULT '',
|
||||||
albumartist TEXT,
|
albumartist TEXT DEFAULT '',
|
||||||
track INTEGER NOT NULL DEFAULT -1,
|
track INTEGER NOT NULL DEFAULT -1,
|
||||||
disc INTEGER NOT NULL DEFAULT -1,
|
disc INTEGER NOT NULL DEFAULT -1,
|
||||||
year INTEGER NOT NULL DEFAULT -1,
|
year INTEGER NOT NULL DEFAULT -1,
|
||||||
originalyear INTEGER NOT NULL DEFAULT -1,
|
originalyear INTEGER NOT NULL DEFAULT -1,
|
||||||
genre TEXT,
|
genre TEXT DEFAULT '',
|
||||||
compilation INTEGER NOT NULL DEFAULT 0,
|
compilation INTEGER NOT NULL DEFAULT 0,
|
||||||
composer TEXT,
|
composer TEXT DEFAULT '',
|
||||||
performer TEXT,
|
performer TEXT DEFAULT '',
|
||||||
grouping TEXT,
|
grouping TEXT DEFAULT '',
|
||||||
comment TEXT,
|
comment TEXT DEFAULT '',
|
||||||
lyrics TEXT,
|
lyrics TEXT DEFAULT '',
|
||||||
|
|
||||||
artist_id TEXT,
|
artist_id TEXT DEFAULT '',
|
||||||
album_id TEXT,
|
album_id TEXT DEFAULT '',
|
||||||
song_id TEXT,
|
song_id TEXT DEFAULT '',
|
||||||
|
|
||||||
beginning INTEGER NOT NULL DEFAULT 0,
|
beginning INTEGER NOT NULL DEFAULT 0,
|
||||||
length INTEGER NOT NULL DEFAULT 0,
|
length INTEGER NOT NULL DEFAULT 0,
|
||||||
@@ -48,29 +48,32 @@ CREATE TABLE IF NOT EXISTS songs (
|
|||||||
|
|
||||||
source INTEGER NOT NULL DEFAULT 0,
|
source INTEGER NOT NULL DEFAULT 0,
|
||||||
directory_id INTEGER NOT NULL DEFAULT -1,
|
directory_id INTEGER NOT NULL DEFAULT -1,
|
||||||
url TEXT NOT NULL,
|
url TEXT NOT NULL DEFAULT '',
|
||||||
filetype INTEGER NOT NULL DEFAULT 0,
|
filetype INTEGER NOT NULL DEFAULT 0,
|
||||||
filesize INTEGER NOT NULL DEFAULT -1,
|
filesize INTEGER NOT NULL DEFAULT -1,
|
||||||
mtime INTEGER NOT NULL DEFAULT -1,
|
mtime INTEGER NOT NULL DEFAULT -1,
|
||||||
ctime INTEGER NOT NULL DEFAULT -1,
|
ctime INTEGER NOT NULL DEFAULT -1,
|
||||||
unavailable INTEGER DEFAULT 0,
|
unavailable INTEGER DEFAULT 0,
|
||||||
|
|
||||||
|
fingerprint TEXT DEFAULT '',
|
||||||
|
|
||||||
playcount INTEGER NOT NULL DEFAULT 0,
|
playcount INTEGER NOT NULL DEFAULT 0,
|
||||||
skipcount INTEGER NOT NULL DEFAULT 0,
|
skipcount INTEGER NOT NULL DEFAULT 0,
|
||||||
lastplayed INTEGER NOT NULL DEFAULT -1,
|
lastplayed INTEGER NOT NULL DEFAULT -1,
|
||||||
|
lastseen INTEGER NOT NULL DEFAULT -1,
|
||||||
|
|
||||||
compilation_detected INTEGER DEFAULT 0,
|
compilation_detected INTEGER DEFAULT 0,
|
||||||
compilation_on INTEGER NOT NULL DEFAULT 0,
|
compilation_on INTEGER NOT NULL DEFAULT 0,
|
||||||
compilation_off INTEGER NOT NULL DEFAULT 0,
|
compilation_off INTEGER NOT NULL DEFAULT 0,
|
||||||
compilation_effective INTEGER NOT NULL DEFAULT 0,
|
compilation_effective INTEGER NOT NULL DEFAULT 0,
|
||||||
|
|
||||||
art_automatic TEXT,
|
art_automatic TEXT DEFAULT '',
|
||||||
art_manual TEXT,
|
art_manual TEXT DEFAULT '',
|
||||||
|
|
||||||
effective_albumartist TEXT,
|
effective_albumartist TEXT DEFAULT '',
|
||||||
effective_originalyear INTEGER NOT NULL DEFAULT 0,
|
effective_originalyear INTEGER NOT NULL DEFAULT 0,
|
||||||
|
|
||||||
cue_path TEXT,
|
cue_path TEXT DEFAULT '',
|
||||||
|
|
||||||
rating INTEGER DEFAULT -1
|
rating INTEGER DEFAULT -1
|
||||||
|
|
||||||
@@ -78,25 +81,25 @@ CREATE TABLE IF NOT EXISTS songs (
|
|||||||
|
|
||||||
CREATE TABLE IF NOT EXISTS subsonic_songs (
|
CREATE TABLE IF NOT EXISTS subsonic_songs (
|
||||||
|
|
||||||
title TEXT,
|
title TEXT DEFAULT '',
|
||||||
album TEXT,
|
album TEXT DEFAULT '',
|
||||||
artist TEXT,
|
artist TEXT DEFAULT '',
|
||||||
albumartist TEXT,
|
albumartist TEXT DEFAULT '',
|
||||||
track INTEGER NOT NULL DEFAULT -1,
|
track INTEGER NOT NULL DEFAULT -1,
|
||||||
disc INTEGER NOT NULL DEFAULT -1,
|
disc INTEGER NOT NULL DEFAULT -1,
|
||||||
year INTEGER NOT NULL DEFAULT -1,
|
year INTEGER NOT NULL DEFAULT -1,
|
||||||
originalyear INTEGER NOT NULL DEFAULT -1,
|
originalyear INTEGER NOT NULL DEFAULT -1,
|
||||||
genre TEXT,
|
genre TEXT DEFAULT '',
|
||||||
compilation INTEGER NOT NULL DEFAULT 0,
|
compilation INTEGER NOT NULL DEFAULT 0,
|
||||||
composer TEXT,
|
composer TEXT DEFAULT '',
|
||||||
performer TEXT,
|
performer TEXT DEFAULT '',
|
||||||
grouping TEXT,
|
grouping TEXT DEFAULT '',
|
||||||
comment TEXT,
|
comment TEXT DEFAULT '',
|
||||||
lyrics TEXT,
|
lyrics TEXT DEFAULT '',
|
||||||
|
|
||||||
artist_id TEXT,
|
artist_id TEXT DEFAULT '',
|
||||||
album_id TEXT,
|
album_id TEXT DEFAULT '',
|
||||||
song_id TEXT,
|
song_id TEXT DEFAULT '',
|
||||||
|
|
||||||
beginning INTEGER NOT NULL DEFAULT 0,
|
beginning INTEGER NOT NULL DEFAULT 0,
|
||||||
length INTEGER NOT NULL DEFAULT 0,
|
length INTEGER NOT NULL DEFAULT 0,
|
||||||
@@ -107,29 +110,32 @@ CREATE TABLE IF NOT EXISTS subsonic_songs (
|
|||||||
|
|
||||||
source INTEGER NOT NULL DEFAULT 0,
|
source INTEGER NOT NULL DEFAULT 0,
|
||||||
directory_id INTEGER NOT NULL DEFAULT -1,
|
directory_id INTEGER NOT NULL DEFAULT -1,
|
||||||
url TEXT NOT NULL,
|
url TEXT NOT NULL DEFAULT '',
|
||||||
filetype INTEGER NOT NULL DEFAULT 0,
|
filetype INTEGER NOT NULL DEFAULT 0,
|
||||||
filesize INTEGER NOT NULL DEFAULT -1,
|
filesize INTEGER NOT NULL DEFAULT -1,
|
||||||
mtime INTEGER NOT NULL DEFAULT -1,
|
mtime INTEGER NOT NULL DEFAULT -1,
|
||||||
ctime INTEGER NOT NULL DEFAULT -1,
|
ctime INTEGER NOT NULL DEFAULT -1,
|
||||||
unavailable INTEGER DEFAULT 0,
|
unavailable INTEGER DEFAULT 0,
|
||||||
|
|
||||||
|
fingerprint TEXT DEFAULT '',
|
||||||
|
|
||||||
playcount INTEGER NOT NULL DEFAULT 0,
|
playcount INTEGER NOT NULL DEFAULT 0,
|
||||||
skipcount INTEGER NOT NULL DEFAULT 0,
|
skipcount INTEGER NOT NULL DEFAULT 0,
|
||||||
lastplayed INTEGER NOT NULL DEFAULT -1,
|
lastplayed INTEGER NOT NULL DEFAULT -1,
|
||||||
|
lastseen INTEGER NOT NULL DEFAULT -1,
|
||||||
|
|
||||||
compilation_detected INTEGER DEFAULT 0,
|
compilation_detected INTEGER DEFAULT 0,
|
||||||
compilation_on INTEGER NOT NULL DEFAULT 0,
|
compilation_on INTEGER NOT NULL DEFAULT 0,
|
||||||
compilation_off INTEGER NOT NULL DEFAULT 0,
|
compilation_off INTEGER NOT NULL DEFAULT 0,
|
||||||
compilation_effective INTEGER NOT NULL DEFAULT 0,
|
compilation_effective INTEGER NOT NULL DEFAULT 0,
|
||||||
|
|
||||||
art_automatic TEXT,
|
art_automatic TEXT DEFAULT '',
|
||||||
art_manual TEXT,
|
art_manual TEXT DEFAULT '',
|
||||||
|
|
||||||
effective_albumartist TEXT,
|
effective_albumartist TEXT DEFAULT '',
|
||||||
effective_originalyear INTEGER NOT NULL DEFAULT 0,
|
effective_originalyear INTEGER NOT NULL DEFAULT 0,
|
||||||
|
|
||||||
cue_path TEXT,
|
cue_path TEXT DEFAULT '',
|
||||||
|
|
||||||
rating INTEGER DEFAULT -1
|
rating INTEGER DEFAULT -1
|
||||||
|
|
||||||
@@ -137,25 +143,25 @@ CREATE TABLE IF NOT EXISTS subsonic_songs (
|
|||||||
|
|
||||||
CREATE TABLE IF NOT EXISTS tidal_artists_songs (
|
CREATE TABLE IF NOT EXISTS tidal_artists_songs (
|
||||||
|
|
||||||
title TEXT,
|
title TEXT DEFAULT '',
|
||||||
album TEXT,
|
album TEXT DEFAULT '',
|
||||||
artist TEXT,
|
artist TEXT DEFAULT '',
|
||||||
albumartist TEXT,
|
albumartist TEXT DEFAULT '',
|
||||||
track INTEGER NOT NULL DEFAULT -1,
|
track INTEGER NOT NULL DEFAULT -1,
|
||||||
disc INTEGER NOT NULL DEFAULT -1,
|
disc INTEGER NOT NULL DEFAULT -1,
|
||||||
year INTEGER NOT NULL DEFAULT -1,
|
year INTEGER NOT NULL DEFAULT -1,
|
||||||
originalyear INTEGER NOT NULL DEFAULT -1,
|
originalyear INTEGER NOT NULL DEFAULT -1,
|
||||||
genre TEXT,
|
genre TEXT DEFAULT '',
|
||||||
compilation INTEGER NOT NULL DEFAULT 0,
|
compilation INTEGER NOT NULL DEFAULT 0,
|
||||||
composer TEXT,
|
composer TEXT DEFAULT '',
|
||||||
performer TEXT,
|
performer TEXT DEFAULT '',
|
||||||
grouping TEXT,
|
grouping TEXT DEFAULT '',
|
||||||
comment TEXT,
|
comment TEXT DEFAULT '',
|
||||||
lyrics TEXT,
|
lyrics TEXT DEFAULT '',
|
||||||
|
|
||||||
artist_id TEXT,
|
artist_id TEXT DEFAULT '',
|
||||||
album_id TEXT,
|
album_id TEXT DEFAULT '',
|
||||||
song_id TEXT,
|
song_id TEXT DEFAULT '',
|
||||||
|
|
||||||
beginning INTEGER NOT NULL DEFAULT 0,
|
beginning INTEGER NOT NULL DEFAULT 0,
|
||||||
length INTEGER NOT NULL DEFAULT 0,
|
length INTEGER NOT NULL DEFAULT 0,
|
||||||
@@ -166,29 +172,32 @@ CREATE TABLE IF NOT EXISTS tidal_artists_songs (
|
|||||||
|
|
||||||
source INTEGER NOT NULL DEFAULT 0,
|
source INTEGER NOT NULL DEFAULT 0,
|
||||||
directory_id INTEGER NOT NULL DEFAULT -1,
|
directory_id INTEGER NOT NULL DEFAULT -1,
|
||||||
url TEXT NOT NULL,
|
url TEXT NOT NULL DEFAULT '',
|
||||||
filetype INTEGER NOT NULL DEFAULT 0,
|
filetype INTEGER NOT NULL DEFAULT 0,
|
||||||
filesize INTEGER NOT NULL DEFAULT -1,
|
filesize INTEGER NOT NULL DEFAULT -1,
|
||||||
mtime INTEGER NOT NULL DEFAULT -1,
|
mtime INTEGER NOT NULL DEFAULT -1,
|
||||||
ctime INTEGER NOT NULL DEFAULT -1,
|
ctime INTEGER NOT NULL DEFAULT -1,
|
||||||
unavailable INTEGER DEFAULT 0,
|
unavailable INTEGER DEFAULT 0,
|
||||||
|
|
||||||
|
fingerprint TEXT DEFAULT '',
|
||||||
|
|
||||||
playcount INTEGER NOT NULL DEFAULT 0,
|
playcount INTEGER NOT NULL DEFAULT 0,
|
||||||
skipcount INTEGER NOT NULL DEFAULT 0,
|
skipcount INTEGER NOT NULL DEFAULT 0,
|
||||||
lastplayed INTEGER NOT NULL DEFAULT -1,
|
lastplayed INTEGER NOT NULL DEFAULT -1,
|
||||||
|
lastseen INTEGER NOT NULL DEFAULT -1,
|
||||||
|
|
||||||
compilation_detected INTEGER DEFAULT 0,
|
compilation_detected INTEGER DEFAULT 0,
|
||||||
compilation_on INTEGER NOT NULL DEFAULT 0,
|
compilation_on INTEGER NOT NULL DEFAULT 0,
|
||||||
compilation_off INTEGER NOT NULL DEFAULT 0,
|
compilation_off INTEGER NOT NULL DEFAULT 0,
|
||||||
compilation_effective INTEGER NOT NULL DEFAULT 0,
|
compilation_effective INTEGER NOT NULL DEFAULT 0,
|
||||||
|
|
||||||
art_automatic TEXT,
|
art_automatic TEXT DEFAULT '',
|
||||||
art_manual TEXT,
|
art_manual TEXT DEFAULT '',
|
||||||
|
|
||||||
effective_albumartist TEXT,
|
effective_albumartist TEXT DEFAULT '',
|
||||||
effective_originalyear INTEGER NOT NULL DEFAULT 0,
|
effective_originalyear INTEGER NOT NULL DEFAULT 0,
|
||||||
|
|
||||||
cue_path TEXT,
|
cue_path TEXT DEFAULT '',
|
||||||
|
|
||||||
rating INTEGER DEFAULT -1
|
rating INTEGER DEFAULT -1
|
||||||
|
|
||||||
@@ -196,25 +205,25 @@ CREATE TABLE IF NOT EXISTS tidal_artists_songs (
|
|||||||
|
|
||||||
CREATE TABLE IF NOT EXISTS tidal_albums_songs (
|
CREATE TABLE IF NOT EXISTS tidal_albums_songs (
|
||||||
|
|
||||||
title TEXT,
|
title TEXT DEFAULT '',
|
||||||
album TEXT,
|
album TEXT DEFAULT '',
|
||||||
artist TEXT,
|
artist TEXT DEFAULT '',
|
||||||
albumartist TEXT,
|
albumartist TEXT DEFAULT '',
|
||||||
track INTEGER NOT NULL DEFAULT -1,
|
track INTEGER NOT NULL DEFAULT -1,
|
||||||
disc INTEGER NOT NULL DEFAULT -1,
|
disc INTEGER NOT NULL DEFAULT -1,
|
||||||
year INTEGER NOT NULL DEFAULT -1,
|
year INTEGER NOT NULL DEFAULT -1,
|
||||||
originalyear INTEGER NOT NULL DEFAULT -1,
|
originalyear INTEGER NOT NULL DEFAULT -1,
|
||||||
genre TEXT,
|
genre TEXT DEFAULT '',
|
||||||
compilation INTEGER NOT NULL DEFAULT 0,
|
compilation INTEGER NOT NULL DEFAULT 0,
|
||||||
composer TEXT,
|
composer TEXT DEFAULT '',
|
||||||
performer TEXT,
|
performer TEXT DEFAULT '',
|
||||||
grouping TEXT,
|
grouping TEXT DEFAULT '',
|
||||||
comment TEXT,
|
comment TEXT DEFAULT '',
|
||||||
lyrics TEXT,
|
lyrics TEXT DEFAULT '',
|
||||||
|
|
||||||
artist_id TEXT,
|
artist_id TEXT DEFAULT '',
|
||||||
album_id TEXT,
|
album_id TEXT DEFAULT '',
|
||||||
song_id TEXT,
|
song_id TEXT DEFAULT '',
|
||||||
|
|
||||||
beginning INTEGER NOT NULL DEFAULT 0,
|
beginning INTEGER NOT NULL DEFAULT 0,
|
||||||
length INTEGER NOT NULL DEFAULT 0,
|
length INTEGER NOT NULL DEFAULT 0,
|
||||||
@@ -225,29 +234,32 @@ CREATE TABLE IF NOT EXISTS tidal_albums_songs (
|
|||||||
|
|
||||||
source INTEGER NOT NULL DEFAULT 0,
|
source INTEGER NOT NULL DEFAULT 0,
|
||||||
directory_id INTEGER NOT NULL DEFAULT -1,
|
directory_id INTEGER NOT NULL DEFAULT -1,
|
||||||
url TEXT NOT NULL,
|
url TEXT NOT NULL DEFAULT '',
|
||||||
filetype INTEGER NOT NULL DEFAULT 0,
|
filetype INTEGER NOT NULL DEFAULT 0,
|
||||||
filesize INTEGER NOT NULL DEFAULT -1,
|
filesize INTEGER NOT NULL DEFAULT -1,
|
||||||
mtime INTEGER NOT NULL DEFAULT -1,
|
mtime INTEGER NOT NULL DEFAULT -1,
|
||||||
ctime INTEGER NOT NULL DEFAULT -1,
|
ctime INTEGER NOT NULL DEFAULT -1,
|
||||||
unavailable INTEGER DEFAULT 0,
|
unavailable INTEGER DEFAULT 0,
|
||||||
|
|
||||||
|
fingerprint TEXT DEFAULT '',
|
||||||
|
|
||||||
playcount INTEGER NOT NULL DEFAULT 0,
|
playcount INTEGER NOT NULL DEFAULT 0,
|
||||||
skipcount INTEGER NOT NULL DEFAULT 0,
|
skipcount INTEGER NOT NULL DEFAULT 0,
|
||||||
lastplayed INTEGER NOT NULL DEFAULT -1,
|
lastplayed INTEGER NOT NULL DEFAULT -1,
|
||||||
|
lastseen INTEGER NOT NULL DEFAULT -1,
|
||||||
|
|
||||||
compilation_detected INTEGER DEFAULT 0,
|
compilation_detected INTEGER DEFAULT 0,
|
||||||
compilation_on INTEGER NOT NULL DEFAULT 0,
|
compilation_on INTEGER NOT NULL DEFAULT 0,
|
||||||
compilation_off INTEGER NOT NULL DEFAULT 0,
|
compilation_off INTEGER NOT NULL DEFAULT 0,
|
||||||
compilation_effective INTEGER NOT NULL DEFAULT 0,
|
compilation_effective INTEGER NOT NULL DEFAULT 0,
|
||||||
|
|
||||||
art_automatic TEXT,
|
art_automatic TEXT DEFAULT '',
|
||||||
art_manual TEXT,
|
art_manual TEXT DEFAULT '',
|
||||||
|
|
||||||
effective_albumartist TEXT,
|
effective_albumartist TEXT DEFAULT '',
|
||||||
effective_originalyear INTEGER NOT NULL DEFAULT 0,
|
effective_originalyear INTEGER NOT NULL DEFAULT 0,
|
||||||
|
|
||||||
cue_path TEXT,
|
cue_path TEXT DEFAULT '',
|
||||||
|
|
||||||
rating INTEGER DEFAULT -1
|
rating INTEGER DEFAULT -1
|
||||||
|
|
||||||
@@ -255,25 +267,25 @@ CREATE TABLE IF NOT EXISTS tidal_albums_songs (
|
|||||||
|
|
||||||
CREATE TABLE IF NOT EXISTS tidal_songs (
|
CREATE TABLE IF NOT EXISTS tidal_songs (
|
||||||
|
|
||||||
title TEXT,
|
title TEXT DEFAULT '',
|
||||||
album TEXT,
|
album TEXT DEFAULT '',
|
||||||
artist TEXT,
|
artist TEXT DEFAULT '',
|
||||||
albumartist TEXT,
|
albumartist TEXT DEFAULT '',
|
||||||
track INTEGER NOT NULL DEFAULT -1,
|
track INTEGER NOT NULL DEFAULT -1,
|
||||||
disc INTEGER NOT NULL DEFAULT -1,
|
disc INTEGER NOT NULL DEFAULT -1,
|
||||||
year INTEGER NOT NULL DEFAULT -1,
|
year INTEGER NOT NULL DEFAULT -1,
|
||||||
originalyear INTEGER NOT NULL DEFAULT -1,
|
originalyear INTEGER NOT NULL DEFAULT -1,
|
||||||
genre TEXT,
|
genre TEXT DEFAULT '',
|
||||||
compilation INTEGER NOT NULL DEFAULT 0,
|
compilation INTEGER NOT NULL DEFAULT 0,
|
||||||
composer TEXT,
|
composer TEXT DEFAULT '',
|
||||||
performer TEXT,
|
performer TEXT DEFAULT '',
|
||||||
grouping TEXT,
|
grouping TEXT DEFAULT '',
|
||||||
comment TEXT,
|
comment TEXT DEFAULT '',
|
||||||
lyrics TEXT,
|
lyrics TEXT DEFAULT '',
|
||||||
|
|
||||||
artist_id TEXT,
|
artist_id TEXT DEFAULT '',
|
||||||
album_id TEXT,
|
album_id TEXT DEFAULT '',
|
||||||
song_id TEXT,
|
song_id TEXT DEFAULT '',
|
||||||
|
|
||||||
beginning INTEGER NOT NULL DEFAULT 0,
|
beginning INTEGER NOT NULL DEFAULT 0,
|
||||||
length INTEGER NOT NULL DEFAULT 0,
|
length INTEGER NOT NULL DEFAULT 0,
|
||||||
@@ -284,29 +296,32 @@ CREATE TABLE IF NOT EXISTS tidal_songs (
|
|||||||
|
|
||||||
source INTEGER NOT NULL DEFAULT 0,
|
source INTEGER NOT NULL DEFAULT 0,
|
||||||
directory_id INTEGER NOT NULL DEFAULT -1,
|
directory_id INTEGER NOT NULL DEFAULT -1,
|
||||||
url TEXT NOT NULL,
|
url TEXT NOT NULL DEFAULT '',
|
||||||
filetype INTEGER NOT NULL DEFAULT 0,
|
filetype INTEGER NOT NULL DEFAULT 0,
|
||||||
filesize INTEGER NOT NULL DEFAULT -1,
|
filesize INTEGER NOT NULL DEFAULT -1,
|
||||||
mtime INTEGER NOT NULL DEFAULT -1,
|
mtime INTEGER NOT NULL DEFAULT -1,
|
||||||
ctime INTEGER NOT NULL DEFAULT -1,
|
ctime INTEGER NOT NULL DEFAULT -1,
|
||||||
unavailable INTEGER DEFAULT 0,
|
unavailable INTEGER DEFAULT 0,
|
||||||
|
|
||||||
|
fingerprint TEXT DEFAULT '',
|
||||||
|
|
||||||
playcount INTEGER NOT NULL DEFAULT 0,
|
playcount INTEGER NOT NULL DEFAULT 0,
|
||||||
skipcount INTEGER NOT NULL DEFAULT 0,
|
skipcount INTEGER NOT NULL DEFAULT 0,
|
||||||
lastplayed INTEGER NOT NULL DEFAULT -1,
|
lastplayed INTEGER NOT NULL DEFAULT -1,
|
||||||
|
lastseen INTEGER NOT NULL DEFAULT -1,
|
||||||
|
|
||||||
compilation_detected INTEGER DEFAULT 0,
|
compilation_detected INTEGER DEFAULT 0,
|
||||||
compilation_on INTEGER NOT NULL DEFAULT 0,
|
compilation_on INTEGER NOT NULL DEFAULT 0,
|
||||||
compilation_off INTEGER NOT NULL DEFAULT 0,
|
compilation_off INTEGER NOT NULL DEFAULT 0,
|
||||||
compilation_effective INTEGER NOT NULL DEFAULT 0,
|
compilation_effective INTEGER NOT NULL DEFAULT 0,
|
||||||
|
|
||||||
art_automatic TEXT,
|
art_automatic TEXT DEFAULT '',
|
||||||
art_manual TEXT,
|
art_manual TEXT DEFAULT '',
|
||||||
|
|
||||||
effective_albumartist TEXT,
|
effective_albumartist TEXT DEFAULT '',
|
||||||
effective_originalyear INTEGER NOT NULL DEFAULT 0,
|
effective_originalyear INTEGER NOT NULL DEFAULT 0,
|
||||||
|
|
||||||
cue_path TEXT,
|
cue_path TEXT DEFAULT '',
|
||||||
|
|
||||||
rating INTEGER DEFAULT -1
|
rating INTEGER DEFAULT -1
|
||||||
|
|
||||||
@@ -314,25 +329,25 @@ CREATE TABLE IF NOT EXISTS tidal_songs (
|
|||||||
|
|
||||||
CREATE TABLE IF NOT EXISTS qobuz_artists_songs (
|
CREATE TABLE IF NOT EXISTS qobuz_artists_songs (
|
||||||
|
|
||||||
title TEXT,
|
title TEXT DEFAULT '',
|
||||||
album TEXT,
|
album TEXT DEFAULT '',
|
||||||
artist TEXT,
|
artist TEXT DEFAULT '',
|
||||||
albumartist TEXT,
|
albumartist TEXT DEFAULT '',
|
||||||
track INTEGER NOT NULL DEFAULT -1,
|
track INTEGER NOT NULL DEFAULT -1,
|
||||||
disc INTEGER NOT NULL DEFAULT -1,
|
disc INTEGER NOT NULL DEFAULT -1,
|
||||||
year INTEGER NOT NULL DEFAULT -1,
|
year INTEGER NOT NULL DEFAULT -1,
|
||||||
originalyear INTEGER NOT NULL DEFAULT -1,
|
originalyear INTEGER NOT NULL DEFAULT -1,
|
||||||
genre TEXT,
|
genre TEXT DEFAULT '',
|
||||||
compilation INTEGER NOT NULL DEFAULT 0,
|
compilation INTEGER NOT NULL DEFAULT 0,
|
||||||
composer TEXT,
|
composer TEXT DEFAULT '',
|
||||||
performer TEXT,
|
performer TEXT DEFAULT '',
|
||||||
grouping TEXT,
|
grouping TEXT DEFAULT '',
|
||||||
comment TEXT,
|
comment TEXT DEFAULT '',
|
||||||
lyrics TEXT,
|
lyrics TEXT DEFAULT '',
|
||||||
|
|
||||||
artist_id TEXT,
|
artist_id TEXT DEFAULT '',
|
||||||
album_id TEXT,
|
album_id TEXT DEFAULT '',
|
||||||
song_id TEXT,
|
song_id TEXT DEFAULT '',
|
||||||
|
|
||||||
beginning INTEGER NOT NULL DEFAULT 0,
|
beginning INTEGER NOT NULL DEFAULT 0,
|
||||||
length INTEGER NOT NULL DEFAULT 0,
|
length INTEGER NOT NULL DEFAULT 0,
|
||||||
@@ -343,29 +358,32 @@ CREATE TABLE IF NOT EXISTS qobuz_artists_songs (
|
|||||||
|
|
||||||
source INTEGER NOT NULL DEFAULT 0,
|
source INTEGER NOT NULL DEFAULT 0,
|
||||||
directory_id INTEGER NOT NULL DEFAULT -1,
|
directory_id INTEGER NOT NULL DEFAULT -1,
|
||||||
url TEXT NOT NULL,
|
url TEXT NOT NULL DEFAULT '',
|
||||||
filetype INTEGER NOT NULL DEFAULT 0,
|
filetype INTEGER NOT NULL DEFAULT 0,
|
||||||
filesize INTEGER NOT NULL DEFAULT -1,
|
filesize INTEGER NOT NULL DEFAULT -1,
|
||||||
mtime INTEGER NOT NULL DEFAULT -1,
|
mtime INTEGER NOT NULL DEFAULT -1,
|
||||||
ctime INTEGER NOT NULL DEFAULT -1,
|
ctime INTEGER NOT NULL DEFAULT -1,
|
||||||
unavailable INTEGER DEFAULT 0,
|
unavailable INTEGER DEFAULT 0,
|
||||||
|
|
||||||
|
fingerprint TEXT DEFAULT '',
|
||||||
|
|
||||||
playcount INTEGER NOT NULL DEFAULT 0,
|
playcount INTEGER NOT NULL DEFAULT 0,
|
||||||
skipcount INTEGER NOT NULL DEFAULT 0,
|
skipcount INTEGER NOT NULL DEFAULT 0,
|
||||||
lastplayed INTEGER NOT NULL DEFAULT -1,
|
lastplayed INTEGER NOT NULL DEFAULT -1,
|
||||||
|
lastseen INTEGER NOT NULL DEFAULT -1,
|
||||||
|
|
||||||
compilation_detected INTEGER DEFAULT 0,
|
compilation_detected INTEGER DEFAULT 0,
|
||||||
compilation_on INTEGER NOT NULL DEFAULT 0,
|
compilation_on INTEGER NOT NULL DEFAULT 0,
|
||||||
compilation_off INTEGER NOT NULL DEFAULT 0,
|
compilation_off INTEGER NOT NULL DEFAULT 0,
|
||||||
compilation_effective INTEGER NOT NULL DEFAULT 0,
|
compilation_effective INTEGER NOT NULL DEFAULT 0,
|
||||||
|
|
||||||
art_automatic TEXT,
|
art_automatic TEXT DEFAULT '',
|
||||||
art_manual TEXT,
|
art_manual TEXT DEFAULT '',
|
||||||
|
|
||||||
effective_albumartist TEXT,
|
effective_albumartist TEXT DEFAULT '',
|
||||||
effective_originalyear INTEGER NOT NULL DEFAULT 0,
|
effective_originalyear INTEGER NOT NULL DEFAULT 0,
|
||||||
|
|
||||||
cue_path TEXT,
|
cue_path TEXT DEFAULT '',
|
||||||
|
|
||||||
rating INTEGER DEFAULT -1
|
rating INTEGER DEFAULT -1
|
||||||
|
|
||||||
@@ -373,25 +391,25 @@ CREATE TABLE IF NOT EXISTS qobuz_artists_songs (
|
|||||||
|
|
||||||
CREATE TABLE IF NOT EXISTS qobuz_albums_songs (
|
CREATE TABLE IF NOT EXISTS qobuz_albums_songs (
|
||||||
|
|
||||||
title TEXT,
|
title TEXT DEFAULT '',
|
||||||
album TEXT,
|
album TEXT DEFAULT '',
|
||||||
artist TEXT,
|
artist TEXT DEFAULT '',
|
||||||
albumartist TEXT,
|
albumartist TEXT DEFAULT '',
|
||||||
track INTEGER NOT NULL DEFAULT -1,
|
track INTEGER NOT NULL DEFAULT -1,
|
||||||
disc INTEGER NOT NULL DEFAULT -1,
|
disc INTEGER NOT NULL DEFAULT -1,
|
||||||
year INTEGER NOT NULL DEFAULT -1,
|
year INTEGER NOT NULL DEFAULT -1,
|
||||||
originalyear INTEGER NOT NULL DEFAULT -1,
|
originalyear INTEGER NOT NULL DEFAULT -1,
|
||||||
genre TEXT,
|
genre TEXT DEFAULT '',
|
||||||
compilation INTEGER NOT NULL DEFAULT 0,
|
compilation INTEGER NOT NULL DEFAULT 0,
|
||||||
composer TEXT,
|
composer TEXT DEFAULT '',
|
||||||
performer TEXT,
|
performer TEXT DEFAULT '',
|
||||||
grouping TEXT,
|
grouping TEXT DEFAULT '',
|
||||||
comment TEXT,
|
comment TEXT DEFAULT '',
|
||||||
lyrics TEXT,
|
lyrics TEXT DEFAULT '',
|
||||||
|
|
||||||
artist_id TEXT,
|
artist_id TEXT DEFAULT '',
|
||||||
album_id TEXT,
|
album_id TEXT DEFAULT '',
|
||||||
song_id TEXT,
|
song_id TEXT DEFAULT '',
|
||||||
|
|
||||||
beginning INTEGER NOT NULL DEFAULT 0,
|
beginning INTEGER NOT NULL DEFAULT 0,
|
||||||
length INTEGER NOT NULL DEFAULT 0,
|
length INTEGER NOT NULL DEFAULT 0,
|
||||||
@@ -402,29 +420,32 @@ CREATE TABLE IF NOT EXISTS qobuz_albums_songs (
|
|||||||
|
|
||||||
source INTEGER NOT NULL DEFAULT 0,
|
source INTEGER NOT NULL DEFAULT 0,
|
||||||
directory_id INTEGER NOT NULL DEFAULT -1,
|
directory_id INTEGER NOT NULL DEFAULT -1,
|
||||||
url TEXT NOT NULL,
|
url TEXT NOT NULL DEFAULT '',
|
||||||
filetype INTEGER NOT NULL DEFAULT 0,
|
filetype INTEGER NOT NULL DEFAULT 0,
|
||||||
filesize INTEGER NOT NULL DEFAULT -1,
|
filesize INTEGER NOT NULL DEFAULT -1,
|
||||||
mtime INTEGER NOT NULL DEFAULT -1,
|
mtime INTEGER NOT NULL DEFAULT -1,
|
||||||
ctime INTEGER NOT NULL DEFAULT -1,
|
ctime INTEGER NOT NULL DEFAULT -1,
|
||||||
unavailable INTEGER DEFAULT 0,
|
unavailable INTEGER DEFAULT 0,
|
||||||
|
|
||||||
|
fingerprint TEXT DEFAULT '',
|
||||||
|
|
||||||
playcount INTEGER NOT NULL DEFAULT 0,
|
playcount INTEGER NOT NULL DEFAULT 0,
|
||||||
skipcount INTEGER NOT NULL DEFAULT 0,
|
skipcount INTEGER NOT NULL DEFAULT 0,
|
||||||
lastplayed INTEGER NOT NULL DEFAULT -1,
|
lastplayed INTEGER NOT NULL DEFAULT -1,
|
||||||
|
lastseen INTEGER NOT NULL DEFAULT -1,
|
||||||
|
|
||||||
compilation_detected INTEGER DEFAULT 0,
|
compilation_detected INTEGER DEFAULT 0,
|
||||||
compilation_on INTEGER NOT NULL DEFAULT 0,
|
compilation_on INTEGER NOT NULL DEFAULT 0,
|
||||||
compilation_off INTEGER NOT NULL DEFAULT 0,
|
compilation_off INTEGER NOT NULL DEFAULT 0,
|
||||||
compilation_effective INTEGER NOT NULL DEFAULT 0,
|
compilation_effective INTEGER NOT NULL DEFAULT 0,
|
||||||
|
|
||||||
art_automatic TEXT,
|
art_automatic TEXT DEFAULT '',
|
||||||
art_manual TEXT,
|
art_manual TEXT DEFAULT '',
|
||||||
|
|
||||||
effective_albumartist TEXT,
|
effective_albumartist TEXT DEFAULT '',
|
||||||
effective_originalyear INTEGER NOT NULL DEFAULT 0,
|
effective_originalyear INTEGER NOT NULL DEFAULT 0,
|
||||||
|
|
||||||
cue_path TEXT,
|
cue_path TEXT DEFAULT '',
|
||||||
|
|
||||||
rating INTEGER DEFAULT -1
|
rating INTEGER DEFAULT -1
|
||||||
|
|
||||||
@@ -432,25 +453,25 @@ CREATE TABLE IF NOT EXISTS qobuz_albums_songs (
|
|||||||
|
|
||||||
CREATE TABLE IF NOT EXISTS qobuz_songs (
|
CREATE TABLE IF NOT EXISTS qobuz_songs (
|
||||||
|
|
||||||
title TEXT,
|
title TEXT DEFAULT '',
|
||||||
album TEXT,
|
album TEXT DEFAULT '',
|
||||||
artist TEXT,
|
artist TEXT DEFAULT '',
|
||||||
albumartist TEXT,
|
albumartist TEXT DEFAULT '',
|
||||||
track INTEGER NOT NULL DEFAULT -1,
|
track INTEGER NOT NULL DEFAULT -1,
|
||||||
disc INTEGER NOT NULL DEFAULT -1,
|
disc INTEGER NOT NULL DEFAULT -1,
|
||||||
year INTEGER NOT NULL DEFAULT -1,
|
year INTEGER NOT NULL DEFAULT -1,
|
||||||
originalyear INTEGER NOT NULL DEFAULT -1,
|
originalyear INTEGER NOT NULL DEFAULT -1,
|
||||||
genre TEXT,
|
genre TEXT DEFAULT '',
|
||||||
compilation INTEGER NOT NULL DEFAULT 0,
|
compilation INTEGER NOT NULL DEFAULT 0,
|
||||||
composer TEXT,
|
composer TEXT DEFAULT '',
|
||||||
performer TEXT,
|
performer TEXT DEFAULT '',
|
||||||
grouping TEXT,
|
grouping TEXT DEFAULT '',
|
||||||
comment TEXT,
|
comment TEXT DEFAULT '',
|
||||||
lyrics TEXT,
|
lyrics TEXT DEFAULT '',
|
||||||
|
|
||||||
artist_id TEXT,
|
artist_id TEXT DEFAULT '',
|
||||||
album_id TEXT,
|
album_id TEXT DEFAULT '',
|
||||||
song_id TEXT,
|
song_id TEXT DEFAULT '',
|
||||||
|
|
||||||
beginning INTEGER NOT NULL DEFAULT 0,
|
beginning INTEGER NOT NULL DEFAULT 0,
|
||||||
length INTEGER NOT NULL DEFAULT 0,
|
length INTEGER NOT NULL DEFAULT 0,
|
||||||
@@ -461,29 +482,32 @@ CREATE TABLE IF NOT EXISTS qobuz_songs (
|
|||||||
|
|
||||||
source INTEGER NOT NULL DEFAULT 0,
|
source INTEGER NOT NULL DEFAULT 0,
|
||||||
directory_id INTEGER NOT NULL DEFAULT -1,
|
directory_id INTEGER NOT NULL DEFAULT -1,
|
||||||
url TEXT NOT NULL,
|
url TEXT NOT NULL DEFAULT '',
|
||||||
filetype INTEGER NOT NULL DEFAULT 0,
|
filetype INTEGER NOT NULL DEFAULT 0,
|
||||||
filesize INTEGER NOT NULL DEFAULT -1,
|
filesize INTEGER NOT NULL DEFAULT -1,
|
||||||
mtime INTEGER NOT NULL DEFAULT -1,
|
mtime INTEGER NOT NULL DEFAULT -1,
|
||||||
ctime INTEGER NOT NULL DEFAULT -1,
|
ctime INTEGER NOT NULL DEFAULT -1,
|
||||||
unavailable INTEGER DEFAULT 0,
|
unavailable INTEGER DEFAULT 0,
|
||||||
|
|
||||||
|
fingerprint TEXT DEFAULT '',
|
||||||
|
|
||||||
playcount INTEGER NOT NULL DEFAULT 0,
|
playcount INTEGER NOT NULL DEFAULT 0,
|
||||||
skipcount INTEGER NOT NULL DEFAULT 0,
|
skipcount INTEGER NOT NULL DEFAULT 0,
|
||||||
lastplayed INTEGER NOT NULL DEFAULT -1,
|
lastplayed INTEGER NOT NULL DEFAULT -1,
|
||||||
|
lastseen INTEGER NOT NULL DEFAULT -1,
|
||||||
|
|
||||||
compilation_detected INTEGER DEFAULT 0,
|
compilation_detected INTEGER DEFAULT 0,
|
||||||
compilation_on INTEGER NOT NULL DEFAULT 0,
|
compilation_on INTEGER NOT NULL DEFAULT 0,
|
||||||
compilation_off INTEGER NOT NULL DEFAULT 0,
|
compilation_off INTEGER NOT NULL DEFAULT 0,
|
||||||
compilation_effective INTEGER NOT NULL DEFAULT 0,
|
compilation_effective INTEGER NOT NULL DEFAULT 0,
|
||||||
|
|
||||||
art_automatic TEXT,
|
art_automatic TEXT DEFAULT '',
|
||||||
art_manual TEXT,
|
art_manual TEXT DEFAULT '',
|
||||||
|
|
||||||
effective_albumartist TEXT,
|
effective_albumartist TEXT DEFAULT '',
|
||||||
effective_originalyear INTEGER NOT NULL DEFAULT 0,
|
effective_originalyear INTEGER NOT NULL DEFAULT 0,
|
||||||
|
|
||||||
cue_path TEXT,
|
cue_path TEXT DEFAULT '',
|
||||||
|
|
||||||
rating INTEGER DEFAULT -1
|
rating INTEGER DEFAULT -1
|
||||||
|
|
||||||
@@ -491,15 +515,15 @@ CREATE TABLE IF NOT EXISTS qobuz_songs (
|
|||||||
|
|
||||||
CREATE TABLE IF NOT EXISTS playlists (
|
CREATE TABLE IF NOT EXISTS playlists (
|
||||||
|
|
||||||
name TEXT NOT NULL,
|
name TEXT NOT NULL DEFAULT '',
|
||||||
last_played INTEGER NOT NULL DEFAULT -1,
|
last_played INTEGER NOT NULL DEFAULT -1,
|
||||||
ui_order INTEGER NOT NULL DEFAULT 0,
|
ui_order INTEGER NOT NULL DEFAULT 0,
|
||||||
special_type TEXT,
|
special_type TEXT DEFAULT '',
|
||||||
ui_path TEXT,
|
ui_path TEXT DEFAULT '',
|
||||||
is_favorite INTEGER NOT NULL DEFAULT 0,
|
is_favorite INTEGER NOT NULL DEFAULT 0,
|
||||||
|
|
||||||
dynamic_playlist_type INTEGER,
|
dynamic_playlist_type INTEGER,
|
||||||
dynamic_playlist_backend TEXT,
|
dynamic_playlist_backend TEXT DEFAULT '',
|
||||||
dynamic_playlist_data BLOB
|
dynamic_playlist_data BLOB
|
||||||
|
|
||||||
);
|
);
|
||||||
@@ -509,27 +533,27 @@ CREATE TABLE IF NOT EXISTS playlist_items (
|
|||||||
playlist INTEGER NOT NULL,
|
playlist INTEGER NOT NULL,
|
||||||
type INTEGER NOT NULL DEFAULT 0,
|
type INTEGER NOT NULL DEFAULT 0,
|
||||||
collection_id INTEGER,
|
collection_id INTEGER,
|
||||||
playlist_url TEXT,
|
playlist_url TEXT DEFAULT '',
|
||||||
|
|
||||||
title TEXT,
|
title TEXT DEFAULT '',
|
||||||
album TEXT,
|
album TEXT DEFAULT '',
|
||||||
artist TEXT,
|
artist TEXT DEFAULT '',
|
||||||
albumartist TEXT,
|
albumartist TEXT DEFAULT '',
|
||||||
track INTEGER,
|
track INTEGER,
|
||||||
disc INTEGER,
|
disc INTEGER,
|
||||||
year INTEGER,
|
year INTEGER,
|
||||||
originalyear INTEGER,
|
originalyear INTEGER,
|
||||||
genre TEXT,
|
genre TEXT DEFAULT '',
|
||||||
compilation INTEGER DEFAULT 0,
|
compilation INTEGER DEFAULT 0,
|
||||||
composer TEXT,
|
composer TEXT DEFAULT '',
|
||||||
performer TEXT,
|
performer TEXT DEFAULT '',
|
||||||
grouping TEXT,
|
grouping TEXT DEFAULT '',
|
||||||
comment TEXT,
|
comment TEXT DEFAULT '',
|
||||||
lyrics TEXT,
|
lyrics TEXT DEFAULT '',
|
||||||
|
|
||||||
artist_id TEXT,
|
artist_id TEXT DEFAULT '',
|
||||||
album_id TEXT,
|
album_id TEXT DEFAULT '',
|
||||||
song_id TEXT,
|
song_id TEXT DEFAULT '',
|
||||||
|
|
||||||
beginning INTEGER,
|
beginning INTEGER,
|
||||||
length INTEGER,
|
length INTEGER,
|
||||||
@@ -540,44 +564,54 @@ CREATE TABLE IF NOT EXISTS playlist_items (
|
|||||||
|
|
||||||
source INTEGER,
|
source INTEGER,
|
||||||
directory_id INTEGER,
|
directory_id INTEGER,
|
||||||
url TEXT,
|
url TEXT DEFAULT '',
|
||||||
filetype INTEGER,
|
filetype INTEGER,
|
||||||
filesize INTEGER,
|
filesize INTEGER,
|
||||||
mtime INTEGER,
|
mtime INTEGER,
|
||||||
ctime INTEGER,
|
ctime INTEGER,
|
||||||
unavailable INTEGER DEFAULT 0,
|
unavailable INTEGER DEFAULT 0,
|
||||||
|
|
||||||
|
fingerprint TEXT DEFAULT '',
|
||||||
|
|
||||||
playcount INTEGER DEFAULT 0,
|
playcount INTEGER DEFAULT 0,
|
||||||
skipcount INTEGER DEFAULT 0,
|
skipcount INTEGER DEFAULT 0,
|
||||||
lastplayed INTEGER DEFAULT 0,
|
lastplayed INTEGER DEFAULT -1,
|
||||||
|
lastseen INTEGER DEFAULT -1,
|
||||||
|
|
||||||
compilation_detected INTEGER DEFAULT 0,
|
compilation_detected INTEGER DEFAULT 0,
|
||||||
compilation_on INTEGER DEFAULT 0,
|
compilation_on INTEGER DEFAULT 0,
|
||||||
compilation_off INTEGER DEFAULT 0,
|
compilation_off INTEGER DEFAULT 0,
|
||||||
compilation_effective INTEGER DEFAULT 0,
|
compilation_effective INTEGER DEFAULT 0,
|
||||||
|
|
||||||
art_automatic TEXT,
|
art_automatic TEXT DEFAULT '',
|
||||||
art_manual TEXT,
|
art_manual TEXT DEFAULT '',
|
||||||
|
|
||||||
effective_albumartist TEXT,
|
effective_albumartist TEXT DEFAULT '',
|
||||||
effective_originalyear INTEGER,
|
effective_originalyear INTEGER,
|
||||||
|
|
||||||
cue_path TEXT,
|
cue_path TEXT DEFAULT '',
|
||||||
|
|
||||||
rating INTEGER DEFAULT -1
|
rating INTEGER DEFAULT -1
|
||||||
|
|
||||||
);
|
);
|
||||||
|
|
||||||
CREATE TABLE IF NOT EXISTS devices (
|
CREATE TABLE IF NOT EXISTS devices (
|
||||||
unique_id TEXT NOT NULL,
|
unique_id TEXT NOT NULL DEFAULT '',
|
||||||
friendly_name TEXT,
|
friendly_name TEXT DEFAULT '',
|
||||||
size INTEGER,
|
size INTEGER,
|
||||||
icon TEXT,
|
icon TEXT DEFAULT '',
|
||||||
schema_version INTEGER NOT NULL DEFAULT 0,
|
schema_version INTEGER NOT NULL DEFAULT 0,
|
||||||
transcode_mode NOT NULL DEFAULT 3,
|
transcode_mode NOT NULL DEFAULT 3,
|
||||||
transcode_format NOT NULL DEFAULT 5
|
transcode_format NOT NULL DEFAULT 5
|
||||||
);
|
);
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS radio_channels (
|
||||||
|
source INTEGER NOT NULL DEFAULT 0,
|
||||||
|
name TEXT DEFAULT '',
|
||||||
|
url TEXT DEFAULT '',
|
||||||
|
thumbnail_url TEXT DEFAULT ''
|
||||||
|
);
|
||||||
|
|
||||||
CREATE INDEX IF NOT EXISTS idx_url ON songs (url);
|
CREATE INDEX IF NOT EXISTS idx_url ON songs (url);
|
||||||
|
|
||||||
CREATE INDEX IF NOT EXISTS idx_comp_artist ON songs (compilation_effective, artist);
|
CREATE INDEX IF NOT EXISTS idx_comp_artist ON songs (compilation_effective, artist);
|
||||||
|
|||||||
@@ -19,47 +19,6 @@
|
|||||||
background-clip: content;
|
background-clip: content;
|
||||||
}
|
}
|
||||||
|
|
||||||
QToolButton {
|
|
||||||
border: 2px solid transparent;
|
|
||||||
border-radius: 3px;
|
|
||||||
padding: 1px;
|
|
||||||
}
|
|
||||||
|
|
||||||
QToolButton::menu-button {
|
|
||||||
width: 16px;
|
|
||||||
border: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
QToolButton[popupMode="1"] {
|
|
||||||
padding-right: 16px;
|
|
||||||
}
|
|
||||||
|
|
||||||
QToolButton:hover {
|
|
||||||
border: 2px solid %palette-highlight;
|
|
||||||
background-color: %palette-highlight-lighter;
|
|
||||||
}
|
|
||||||
|
|
||||||
QToolButton:hover[popupMode="1"] {
|
|
||||||
padding-right: 16px;
|
|
||||||
}
|
|
||||||
|
|
||||||
QToolButton:pressed {
|
|
||||||
border: 2px solid %palette-highlight-darker;
|
|
||||||
background-color: %palette-highlight-lighter;
|
|
||||||
}
|
|
||||||
|
|
||||||
QToolButton:pressed[popupMode="1"] {
|
|
||||||
padding-right: 16px;
|
|
||||||
}
|
|
||||||
|
|
||||||
macos {
|
|
||||||
font-size: 11pt;
|
|
||||||
}
|
|
||||||
|
|
||||||
macos QMenu {
|
|
||||||
font-size: 13pt;
|
|
||||||
}
|
|
||||||
|
|
||||||
#context-layout-container {
|
#context-layout-container {
|
||||||
background-color: %palette-base;
|
background-color: %palette-base;
|
||||||
}
|
}
|
||||||
@@ -71,3 +30,41 @@ macos QMenu {
|
|||||||
#context-layout-scrollarea {
|
#context-layout-scrollarea {
|
||||||
background-color: %palette-base;
|
background-color: %palette-base;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
QToolButton {
|
||||||
|
border: 2px solid transparent;
|
||||||
|
border-radius: 3px;
|
||||||
|
padding: 1px;
|
||||||
|
}
|
||||||
|
|
||||||
|
QToolButton:hover {
|
||||||
|
border: 2px solid %palette-highlight;
|
||||||
|
background-color: %palette-highlight-lighter;
|
||||||
|
}
|
||||||
|
|
||||||
|
QToolButton:pressed {
|
||||||
|
border: 2px solid %palette-highlight-darker;
|
||||||
|
background-color: %palette-highlight-lighter;
|
||||||
|
}
|
||||||
|
|
||||||
|
QToolButton[popupMode="MenuButtonPopup"], QToolButton:hover[popupMode="MenuButtonPopup"], QToolButton:pressed[popupMode="MenuButtonPopup"] {
|
||||||
|
padding-right: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* For backwards compatibility with Qt 5 as it does not support property name */
|
||||||
|
QToolButton[popupMode="1"], QToolButton:hover[popupMode="1"], QToolButton:pressed[popupMode="1"] {
|
||||||
|
padding-right: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
QToolButton::menu-button {
|
||||||
|
width: 16px;
|
||||||
|
border: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
macos {
|
||||||
|
font-size: 11pt;
|
||||||
|
}
|
||||||
|
|
||||||
|
macos QMenu {
|
||||||
|
font-size: 13pt;
|
||||||
|
}
|
||||||
|
|||||||
13
debian/control
vendored
@@ -17,6 +17,7 @@ Build-Depends: debhelper (>= 11),
|
|||||||
libpulse-dev,
|
libpulse-dev,
|
||||||
libtag1-dev,
|
libtag1-dev,
|
||||||
qtbase5-dev,
|
qtbase5-dev,
|
||||||
|
qtbase5-private-dev,
|
||||||
qtbase5-dev-tools,
|
qtbase5-dev-tools,
|
||||||
qttools5-dev,
|
qttools5-dev,
|
||||||
libqt5x11extras5-dev,
|
libqt5x11extras5-dev,
|
||||||
@@ -41,24 +42,26 @@ Depends: ${shlibs:Depends},
|
|||||||
gstreamer1.0-pulseaudio
|
gstreamer1.0-pulseaudio
|
||||||
Homepage: http://www.strawberrymusicplayer.org/
|
Homepage: http://www.strawberrymusicplayer.org/
|
||||||
Description: Audio player and music collection organizer
|
Description: Audio player and music collection organizer
|
||||||
Strawberry is a music player aimed at music collectors, audio enthusiasts and audiophiles.
|
Strawberry is a music player aimed at music collectors and audiophiles.
|
||||||
.
|
.
|
||||||
Features:
|
Features:
|
||||||
- Play and organize music
|
- Play and organize music
|
||||||
- Supports WAV, FLAC, WavPack, Ogg Vorbis, Speex, MPC, TrueAudio, AIFF, MP4, MP3 and ASF
|
- Supports WAV, FLAC, WavPack, Ogg Vorbis, Speex, MPC, TrueAudio, AIFF, MP4, MP3 and ASF
|
||||||
- Audio CD playback
|
- Audio CD playback
|
||||||
- Native desktop notifications
|
- Native desktop notifications
|
||||||
|
- Playlist management
|
||||||
- Playlists in multiple formats
|
- Playlists in multiple formats
|
||||||
- Advanced audio output and device configuration for bit-perfect playback on Linux
|
- Advanced audio output and device configuration for bit-perfect playback on Linux
|
||||||
- Edit tags on music files
|
- Edit tags on audio files
|
||||||
- Fetch tags from MusicBrainz
|
- Automatically retrieve tags from MusicBrainz
|
||||||
- Album cover art from Last.fm, Musicbrainz, Discogs, Musixmatch, Deezer, Tidal, Qobuz and Spotify
|
- Album cover art from Last.fm, Musicbrainz, Discogs, Musixmatch, Deezer, Tidal, Qobuz and Spotify
|
||||||
- Song lyrics from AudD, Genius, Musixmatch, ChartLyrics, lyrics.ovh and lololyrics.com
|
- Song lyrics from AudD, Genius, Musixmatch, ChartLyrics, lyrics.ovh and lololyrics.com
|
||||||
- Support for multiple backends
|
- Support for multiple backends
|
||||||
- Audio analyzer
|
- Audio analyzer
|
||||||
- Audio equalizer
|
- Audio equalizer
|
||||||
- Transfer music to iPod, iPhone, MTP or mass-storage USB player
|
- Transfer music to mass-storage USB players, MTP compatible devices and iPod Nano/Classic
|
||||||
- Scrobbler with support for Last.fm, Libre.fm and ListenBrainz
|
- Scrobbler with support for Last.fm, Libre.fm and ListenBrainz
|
||||||
- Streaming support for Subsonic
|
- Streaming support for Subsonic-compatible servers
|
||||||
|
- Unofficial streaming support for Tidal and Qobuz
|
||||||
.
|
.
|
||||||
It is a fork of Clementine. The name is inspired by the band Strawbs.
|
It is a fork of Clementine. The name is inspired by the band Strawbs.
|
||||||
|
|||||||
20
dist/CMakeLists.txt
vendored
@@ -7,24 +7,24 @@ if(DEB_CODENAME AND DEB_DATE)
|
|||||||
endif(DEB_CODENAME AND DEB_DATE)
|
endif(DEB_CODENAME AND DEB_DATE)
|
||||||
configure_file(${CMAKE_CURRENT_SOURCE_DIR}/unix/PKGBUILD.in ${CMAKE_CURRENT_SOURCE_DIR}/unix/PKGBUILD @ONLY)
|
configure_file(${CMAKE_CURRENT_SOURCE_DIR}/unix/PKGBUILD.in ${CMAKE_CURRENT_SOURCE_DIR}/unix/PKGBUILD @ONLY)
|
||||||
|
|
||||||
if (APPLE)
|
if(APPLE)
|
||||||
configure_file(${CMAKE_CURRENT_SOURCE_DIR}/macos/Info.plist.in ${CMAKE_CURRENT_SOURCE_DIR}/macos/Info.plist)
|
configure_file(${CMAKE_CURRENT_SOURCE_DIR}/macos/Info.plist.in ${CMAKE_CURRENT_SOURCE_DIR}/macos/Info.plist)
|
||||||
endif (APPLE)
|
endif(APPLE)
|
||||||
|
|
||||||
if (WIN32)
|
if(WIN32)
|
||||||
configure_file(${CMAKE_CURRENT_SOURCE_DIR}/windows/strawberry.nsi.in ${CMAKE_CURRENT_SOURCE_DIR}/windows/strawberry.nsi @ONLY)
|
configure_file(${CMAKE_CURRENT_SOURCE_DIR}/windows/strawberry.nsi.in ${CMAKE_CURRENT_SOURCE_DIR}/windows/strawberry.nsi @ONLY)
|
||||||
endif (WIN32)
|
endif(WIN32)
|
||||||
|
|
||||||
if (UNIX AND NOT APPLE)
|
if(UNIX AND NOT APPLE)
|
||||||
install(FILES ../data/icons/48x48/strawberry.png DESTINATION share/icons/hicolor/48x48/apps/)
|
install(FILES ../data/icons/48x48/strawberry.png DESTINATION share/icons/hicolor/48x48/apps/)
|
||||||
install(FILES ../data/icons/64x64/strawberry.png DESTINATION share/icons/hicolor/64x64/apps/)
|
install(FILES ../data/icons/64x64/strawberry.png DESTINATION share/icons/hicolor/64x64/apps/)
|
||||||
install(FILES ../data/icons/128x128/strawberry.png DESTINATION share/icons/hicolor/128x128/apps/)
|
install(FILES ../data/icons/128x128/strawberry.png DESTINATION share/icons/hicolor/128x128/apps/)
|
||||||
install(FILES unix/org.strawberrymusicplayer.strawberry.desktop DESTINATION share/applications)
|
install(FILES unix/org.strawberrymusicplayer.strawberry.desktop DESTINATION share/applications)
|
||||||
install(FILES unix/org.strawberrymusicplayer.strawberry.appdata.xml DESTINATION share/metainfo)
|
install(FILES unix/org.strawberrymusicplayer.strawberry.appdata.xml DESTINATION share/metainfo)
|
||||||
install(FILES unix/strawberry.1 unix/strawberry-tagreader.1 DESTINATION share/man/man1)
|
install(FILES unix/strawberry.1 unix/strawberry-tagreader.1 DESTINATION share/man/man1)
|
||||||
endif (UNIX AND NOT APPLE)
|
endif(UNIX AND NOT APPLE)
|
||||||
|
|
||||||
if (APPLE)
|
if(APPLE)
|
||||||
install(FILES "${CMAKE_CURRENT_SOURCE_DIR}/../dist/macos/Info.plist" DESTINATION "${CMAKE_BINARY_DIR}/strawberry.app/Contents")
|
install(FILES "${CMAKE_CURRENT_SOURCE_DIR}/macos/Info.plist" DESTINATION "${CMAKE_BINARY_DIR}/strawberry.app/Contents")
|
||||||
install(FILES "${CMAKE_CURRENT_SOURCE_DIR}/../dist/macos/strawberry.icns" DESTINATION "${CMAKE_BINARY_DIR}/strawberry.app/Contents/Resources")
|
install(FILES "${CMAKE_CURRENT_SOURCE_DIR}/macos/strawberry.icns" DESTINATION "${CMAKE_BINARY_DIR}/strawberry.app/Contents/Resources")
|
||||||
endif (APPLE)
|
endif()
|
||||||
|
|||||||
111
dist/macos/macgstcopy.sh
vendored
Executable file
@@ -0,0 +1,111 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
|
||||||
|
# Script to copy gstreamer plugins before macdeployqt is run.
|
||||||
|
|
||||||
|
if [ "$1" = "" ]; then
|
||||||
|
echo "Usage: $0 <bundledir>"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
bundledir=$1
|
||||||
|
|
||||||
|
if [ "$GIO_EXTRA_MODULES" = "" ]; then
|
||||||
|
echo "Error: Set the GIO_EXTRA_MODULES environment variable to the path containing libgiognutls.so."
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ "$GST_PLUGIN_SCANNER" = "" ]; then
|
||||||
|
echo "Error: Set the GST_PLUGIN_SCANNER environment variable to the gst-plugin-scanner."
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ "$GST_PLUGIN_PATH" = "" ]; then
|
||||||
|
echo "Error: Set the GST_PLUGIN_PATH environment variable to the path containing gstreamer plugins."
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
mkdir -p "${bundledir}/Contents/PlugIns/gio-modules" || exit 1
|
||||||
|
mkdir -p "${bundledir}/Contents/PlugIns/gstreamer" || exit 1
|
||||||
|
|
||||||
|
if ! [ -f "${GIO_EXTRA_MODULES}/libgiognutls.so" ]; then
|
||||||
|
echo "Error: Missing ${GIO_EXTRA_MODULES}/libgiognutls.so."
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
cp -v -f "${GIO_EXTRA_MODULES}/libgiognutls.so" "${bundledir}/Contents/PlugIns/gio-modules/" || exit 1
|
||||||
|
|
||||||
|
if ! [ -f "${GST_PLUGIN_SCANNER}" ]; then
|
||||||
|
echo "Error: Missing ${GST_PLUGIN_SCANNER}"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
cp -v -f "${GST_PLUGIN_SCANNER}" "${bundledir}/Contents/PlugIns/" || exit 1
|
||||||
|
|
||||||
|
gst_plugins="
|
||||||
|
libgstaes.dylib
|
||||||
|
libgstaiff.dylib
|
||||||
|
libgstapetag.dylib
|
||||||
|
libgstapp.dylib
|
||||||
|
libgstasf.dylib
|
||||||
|
libgstasfmux.dylib
|
||||||
|
libgstaudioconvert.dylib
|
||||||
|
libgstaudiofx.dylib
|
||||||
|
libgstaudiomixer.dylib
|
||||||
|
libgstaudioparsers.dylib
|
||||||
|
libgstaudiorate.dylib
|
||||||
|
libgstaudioresample.dylib
|
||||||
|
libgstaudiotestsrc.dylib
|
||||||
|
libgstautodetect.dylib
|
||||||
|
libgstbs2b.dylib
|
||||||
|
libgstcdio.dylib
|
||||||
|
libgstcoreelements.dylib
|
||||||
|
libgstdash.dylib
|
||||||
|
libgstequalizer.dylib
|
||||||
|
libgstflac.dylib
|
||||||
|
libgstfaac.dylib
|
||||||
|
libgstfaad.dylib
|
||||||
|
libgstfdkaac.dylib
|
||||||
|
libgstgio.dylib
|
||||||
|
libgsticydemux.dylib
|
||||||
|
libgstid3demux.dylib
|
||||||
|
libgstisomp4.dylib
|
||||||
|
libgstlame.dylib
|
||||||
|
libgstlibav.dylib
|
||||||
|
libgstmpg123.dylib
|
||||||
|
libgstmusepack.dylib
|
||||||
|
libgstogg.dylib
|
||||||
|
libgstopenmpt.dylib
|
||||||
|
libgstopus.dylib
|
||||||
|
libgstopusparse.dylib
|
||||||
|
libgstosxaudio.dylib
|
||||||
|
libgstpbtypes.dylib
|
||||||
|
libgstplayback.dylib
|
||||||
|
libgstreplaygain.dylib
|
||||||
|
libgstrtp.dylib
|
||||||
|
libgstrtsp.dylib
|
||||||
|
libgstsoup.dylib
|
||||||
|
libgstspectrum.dylib
|
||||||
|
libgstspeex.dylib
|
||||||
|
libgsttaglib.dylib
|
||||||
|
libgsttcp.dylib
|
||||||
|
libgsttypefindfunctions.dylib
|
||||||
|
libgstudp.dylib
|
||||||
|
libgstvolume.dylib
|
||||||
|
libgstvorbis.dylib
|
||||||
|
libgstwavpack.dylib
|
||||||
|
libgstwavparse.dylib
|
||||||
|
libgstxingmux.dylib;
|
||||||
|
"
|
||||||
|
|
||||||
|
gst_plugins=$(echo "$gst_plugins" | tr '\n' ' ' | sed -e 's/^ //g' | sed -e 's/ / /g')
|
||||||
|
|
||||||
|
for gst_plugin in $gst_plugins
|
||||||
|
do
|
||||||
|
if [ -f "${GST_PLUGIN_PATH}/${gst_plugin}" ]; then
|
||||||
|
cp -v -f "${GST_PLUGIN_PATH}/${gst_plugin}" "${bundledir}/Contents/PlugIns/gstreamer/" || exit 1
|
||||||
|
else
|
||||||
|
echo "Warning: Missing gstreamer plugin ${GST_PLUGIN_PATH}/${gst_plugin}"
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
if [ -f "/usr/local/lib/libbrotlicommon.1.dylib" ]; then
|
||||||
|
mkdir -p ${bundledir}/Contents/Frameworks
|
||||||
|
cp -v -f "/usr/local/lib/libbrotlicommon.1.dylib" "${bundledir}/Contents/Frameworks/"
|
||||||
|
fi
|
||||||
179
dist/scripts/import-from-clementine.sh
vendored
Executable file
@@ -0,0 +1,179 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
# Strawberry Music Player
|
||||||
|
# Copyright 2020, Jonas Kvinge <jonas@jkvinge.net>
|
||||||
|
# 2021 Alexey Vazhnov
|
||||||
|
#
|
||||||
|
# Strawberry 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.
|
||||||
|
#
|
||||||
|
# Strawberry 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 Strawberry. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
# SPDX-License-Identifier: GPL-3.0-only
|
||||||
|
|
||||||
|
|
||||||
|
# Based on https://github.com/strawberrymusicplayer/strawberry/wiki/Import-collection-library-and-playlists-data-from-Clementine
|
||||||
|
|
||||||
|
set -o nounset
|
||||||
|
set -o errexit
|
||||||
|
set -o pipefail
|
||||||
|
shopt -s dotglob
|
||||||
|
|
||||||
|
# Use hardcoded path if no parameters. No need in quotes here! See `man bash`, "Parameter Expansion":
|
||||||
|
FILE_SRC=${1:-~/.config/Clementine/clementine.db}
|
||||||
|
FILE_DST=${2:-~/.local/share/strawberry/strawberry/strawberry.db}
|
||||||
|
|
||||||
|
test -f "$FILE_SRC" || { echo "No such file: $FILE_SRC"; exit 1; }
|
||||||
|
test -f "$FILE_DST" || { echo "No such file: $FILE_DST"; exit 1; }
|
||||||
|
|
||||||
|
echo "Will try to copy information from $FILE_SRC to $FILE_DST."
|
||||||
|
echo
|
||||||
|
echo 'This script will **delete all information** from Strawberry database!'
|
||||||
|
read -r -p 'Do you want to continue? (the only YES is accepted) ' answer
|
||||||
|
if [ "$answer" != "YES" ]; then exit 1; fi
|
||||||
|
|
||||||
|
# 'heredoc' with substitution of variables, see `man bash`, "Here Documents":
|
||||||
|
sqlite3 -batch << EOF
|
||||||
|
.echo on
|
||||||
|
ATTACH '$FILE_DST' AS strawberry;
|
||||||
|
ATTACH '$FILE_SRC' AS clementine;
|
||||||
|
.bail on
|
||||||
|
.databases
|
||||||
|
|
||||||
|
/* This must be done when importing all data from Clementine because playlists are based on ROWIDs */
|
||||||
|
|
||||||
|
DELETE FROM strawberry.directories;
|
||||||
|
DELETE FROM strawberry.subdirectories;
|
||||||
|
DELETE FROM strawberry.songs;
|
||||||
|
DELETE FROM strawberry.playlists;
|
||||||
|
DELETE FROM strawberry.playlist_items;
|
||||||
|
|
||||||
|
/* Import all data from the collection library songs */
|
||||||
|
|
||||||
|
INSERT INTO strawberry.directories (path, subdirs) SELECT path, subdirs FROM clementine.directories;
|
||||||
|
INSERT INTO strawberry.subdirectories (directory_id, path, mtime) SELECT directory, path, mtime FROM clementine.subdirectories;
|
||||||
|
INSERT INTO strawberry.songs (ROWID, title, album, artist, albumartist, track, disc, year, originalyear, genre, compilation, composer, performer, grouping, comment, lyrics, beginning, length, bitrate, samplerate, directory_id, url, filetype, filesize, mtime, ctime, unavailable, playcount, skipcount, lastplayed, compilation_detected, compilation_on, compilation_off, compilation_effective, art_automatic, art_manual, effective_albumartist, effective_originalyear, cue_path, rating)
|
||||||
|
SELECT ROWID, title, album, artist, albumartist, track, disc, year, originalyear, genre, compilation, composer, performer, grouping, comment, lyrics, beginning, length, bitrate, samplerate, directory, filename, filetype, filesize, mtime, ctime, unavailable, playcount, skipcount, lastplayed, sampler, forced_compilation_on, forced_compilation_off, effective_compilation, art_automatic, art_manual, effective_albumartist, effective_originalyear, cue_path, rating FROM clementine.songs WHERE unavailable = 0;
|
||||||
|
UPDATE strawberry.songs SET source = 2;
|
||||||
|
UPDATE strawberry.songs SET artist_id = "";
|
||||||
|
UPDATE strawberry.songs SET album_id = "";
|
||||||
|
UPDATE strawberry.songs SET song_id = "";
|
||||||
|
|
||||||
|
/* Import playlists */
|
||||||
|
|
||||||
|
INSERT INTO strawberry.playlists (ROWID, name, last_played, special_type, ui_path, is_favorite, dynamic_playlist_type, dynamic_playlist_data, dynamic_playlist_backend)
|
||||||
|
SELECT ROWID, name, last_played, special_type, ui_path, is_favorite, dynamic_playlist_type, dynamic_playlist_data, dynamic_playlist_backend FROM clementine.playlists WHERE dynamic_playlist_type ISNULL;
|
||||||
|
|
||||||
|
/* Import playlist items */
|
||||||
|
|
||||||
|
INSERT INTO strawberry.playlist_items
|
||||||
|
(ROWID,
|
||||||
|
playlist,
|
||||||
|
collection_id,
|
||||||
|
title,
|
||||||
|
album,
|
||||||
|
artist,
|
||||||
|
albumartist,
|
||||||
|
track,
|
||||||
|
disc,
|
||||||
|
year,
|
||||||
|
originalyear,
|
||||||
|
genre,
|
||||||
|
compilation,
|
||||||
|
composer,
|
||||||
|
performer,
|
||||||
|
grouping,
|
||||||
|
comment,
|
||||||
|
lyrics,
|
||||||
|
beginning,
|
||||||
|
length,
|
||||||
|
bitrate,
|
||||||
|
samplerate,
|
||||||
|
directory_id,
|
||||||
|
url,
|
||||||
|
filetype,
|
||||||
|
filesize,
|
||||||
|
mtime,
|
||||||
|
ctime,
|
||||||
|
unavailable,
|
||||||
|
playcount,
|
||||||
|
skipcount,
|
||||||
|
lastplayed,
|
||||||
|
compilation_detected,
|
||||||
|
compilation_on,
|
||||||
|
compilation_off,
|
||||||
|
compilation_effective,
|
||||||
|
art_automatic,
|
||||||
|
art_manual,
|
||||||
|
effective_albumartist,
|
||||||
|
effective_originalyear,
|
||||||
|
cue_path,
|
||||||
|
rating
|
||||||
|
)
|
||||||
|
SELECT ROWID,
|
||||||
|
playlist,
|
||||||
|
library_id,
|
||||||
|
title,
|
||||||
|
album,
|
||||||
|
artist,
|
||||||
|
albumartist,
|
||||||
|
track,
|
||||||
|
disc,
|
||||||
|
year,
|
||||||
|
originalyear,
|
||||||
|
genre,
|
||||||
|
compilation,
|
||||||
|
composer,
|
||||||
|
performer,
|
||||||
|
grouping,
|
||||||
|
comment,
|
||||||
|
lyrics,
|
||||||
|
beginning,
|
||||||
|
length,
|
||||||
|
bitrate,
|
||||||
|
samplerate,
|
||||||
|
directory,
|
||||||
|
filename,
|
||||||
|
filetype,
|
||||||
|
filesize,
|
||||||
|
mtime,
|
||||||
|
ctime,
|
||||||
|
unavailable,
|
||||||
|
playcount,
|
||||||
|
skipcount,
|
||||||
|
lastplayed,
|
||||||
|
sampler,
|
||||||
|
forced_compilation_on,
|
||||||
|
forced_compilation_off,
|
||||||
|
effective_compilation,
|
||||||
|
art_automatic,
|
||||||
|
art_manual,
|
||||||
|
effective_albumartist,
|
||||||
|
effective_originalyear,
|
||||||
|
cue_path,
|
||||||
|
rating FROM clementine.playlist_items WHERE type = 'Library';
|
||||||
|
|
||||||
|
UPDATE strawberry.playlist_items SET source = 2;
|
||||||
|
UPDATE strawberry.playlist_items SET type = 2;
|
||||||
|
UPDATE strawberry.playlist_items SET artist_id = "";
|
||||||
|
UPDATE strawberry.playlist_items SET album_id = "";
|
||||||
|
UPDATE strawberry.playlist_items SET song_id = "";
|
||||||
|
|
||||||
|
/* Recreate the FTS tables */
|
||||||
|
|
||||||
|
DELETE FROM strawberry.songs_fts;
|
||||||
|
INSERT INTO strawberry.songs_fts (ROWID, ftstitle, ftsalbum, ftsartist, ftsalbumartist, ftscomposer, ftsperformer, ftsgrouping, ftsgenre, ftscomment)
|
||||||
|
SELECT ROWID, title, album, artist, albumartist, composer, performer, grouping, genre, comment
|
||||||
|
FROM strawberry.songs;
|
||||||
|
|
||||||
|
EOF
|
||||||
|
|
||||||
|
# To be sure script didn't exit because of any error (because we use `set -o errexit`):
|
||||||
|
echo "Script finished"
|
||||||
25
dist/scripts/maketarball.sh.in
vendored
@@ -11,10 +11,33 @@ if ! [ "$gitrev" = "ON" ]; then
|
|||||||
exclude_vcs="--exclude-vcs"
|
exclude_vcs="--exclude-vcs"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
cmds="gtar tar"
|
||||||
|
for cmd in $cmds
|
||||||
|
do
|
||||||
|
which $cmd >/dev/null 2>&1
|
||||||
|
if [ ! $? -eq 0 ]; then
|
||||||
|
continue
|
||||||
|
fi
|
||||||
|
result=$($cmd --version 2>&1 | head -n1)
|
||||||
|
if [ ! $? -eq 0 ]; then
|
||||||
|
continue
|
||||||
|
fi
|
||||||
|
echo "$result" | grep "^.* (GNU tar) .*$" >/dev/null 2>&1
|
||||||
|
if [ ! $? -eq 0 ]; then
|
||||||
|
continue
|
||||||
|
fi
|
||||||
|
TAR=$cmd
|
||||||
|
done
|
||||||
|
|
||||||
|
if [ "$TAR" = "" ]; then
|
||||||
|
echo "ERROR: Missing GNU Tar"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
echo "Creating $name-$version.tar.xz..."
|
echo "Creating $name-$version.tar.xz..."
|
||||||
|
|
||||||
rm -f "$name-$version.tar.xz"
|
rm -f "$name-$version.tar.xz"
|
||||||
tar -cJf $name-$version.tar.xz \
|
${TAR} -cJf $name-$version.tar.xz \
|
||||||
--transform "s,^$rootnoslash,$name-$version," $exclude_vcs \
|
--transform "s,^$rootnoslash,$name-$version," $exclude_vcs \
|
||||||
--exclude=".directory" \
|
--exclude=".directory" \
|
||||||
--exclude="*.tar" \
|
--exclude="*.tar" \
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<component>
|
<component type="desktop-application">
|
||||||
<id>org.strawberrymusicplayer.strawberry</id>
|
<id>org.strawberrymusicplayer.strawberry</id>
|
||||||
<launchable type="desktop-id">org.strawberrymusicplayer.strawberry.desktop</launchable>
|
<launchable type="desktop-id">org.strawberrymusicplayer.strawberry.desktop</launchable>
|
||||||
<metadata_license>CC0-1.0</metadata_license>
|
<metadata_license>CC0-1.0</metadata_license>
|
||||||
@@ -13,37 +13,43 @@
|
|||||||
<url type="homepage">https://www.strawberrymusicplayer.org/</url>
|
<url type="homepage">https://www.strawberrymusicplayer.org/</url>
|
||||||
<url type="bugtracker">https://github.com/strawberrymusicplayer/strawberry/</url>
|
<url type="bugtracker">https://github.com/strawberrymusicplayer/strawberry/</url>
|
||||||
<translation type="qt">strawberry</translation>
|
<translation type="qt">strawberry</translation>
|
||||||
|
<content_rating type="oars-1.1" />
|
||||||
<description>
|
<description>
|
||||||
<p>
|
<p>
|
||||||
Strawberry is a music player and music collection organizer. It is a fork of Clementine released in 2018 aimed at music collectors, audio enthusiasts and audiophiles. The name is inspired by the band Strawbs. It's based on a heavily modified version of Clementine created in 2012-2013. It's written in C++ and Qt 5.
|
Strawberry is a music player and music collection organizer. It is aimed at music collectors and audiophiles. With Strawberry you can play and manage your digital music collection, or stream your favorite radios. It also has unofficial streaming support for Tidal and Qobuz. Strawberry is free software released under GPL. The source code is available on GitHub. It's written in C++ using the Qt toolkit and GStreamer. Strawberry is compatible with both Qt version 5 and 6.
|
||||||
</p>
|
</p>
|
||||||
<p>Features:</p>
|
<p>Features:</p>
|
||||||
<ul>
|
<ul>
|
||||||
<li>Play and organize music</li>
|
<li>Play and organize music</li>
|
||||||
<li>Supports most popular audio formats and CD playback</li>
|
<li>Supports most popular audio formats and CD playback</li>
|
||||||
<li>Native desktop notifications</li>
|
<li>Native desktop notifications</li>
|
||||||
<li>Playlists in multiple formats</li>
|
<li>Playlist management and playlists in multiple formats</li>
|
||||||
|
<li>Smart and dynamic playlists</li>
|
||||||
<li>Advanced audio output and device configuration for bit-perfect playback on Linux</li>
|
<li>Advanced audio output and device configuration for bit-perfect playback on Linux</li>
|
||||||
<li>Edit tags on music files</li>
|
<li>Edit tags on audio files</li>
|
||||||
<li>Fetch tags from MusicBrainz</li>
|
<li>Automatically retrieve tags from MusicBrainz</li>
|
||||||
<li>Album cover art from Last.fm, Musicbrainz, Discogs, Musixmatch, Deezer, Tidal, Qobuz and Spotify</li>
|
<li>Album cover art from Last.fm, Musicbrainz, Discogs, Musixmatch, Deezer, Tidal, Qobuz and Spotify</li>
|
||||||
<li>Song lyrics from AudD, Genius, Musixmatch, ChartLyrics, lyrics.ovh and lololyrics.com</li>
|
<li>Song lyrics from AudD, Genius, Musixmatch, ChartLyrics, lyrics.ovh and lololyrics.com</li>
|
||||||
<li>Support for multiple backends</li>
|
<li>Support for multiple backends</li>
|
||||||
<li>Audio analyzer and equalizer</li>
|
<li>Audio analyzer and equalizer</li>
|
||||||
<li>Transfer music to iPod, iPhone, MTP or mass-storage USB player</li>
|
<li>Transfer music to mass-storage USB players, MTP compatible devices and iPod Nano/Classic</li>
|
||||||
<li>Scrobbler with support for Last.fm, Libre.fm and ListenBrainz</li>
|
<li>Scrobbler with support for Last.fm, Libre.fm and ListenBrainz</li>
|
||||||
<li>Streaming support for Subsonic</li>
|
<li>Streaming support for Subsonic-compatible servers</li>
|
||||||
|
<li>Unofficial streaming support for Tidal and Qobuz</li>
|
||||||
</ul>
|
</ul>
|
||||||
</description>
|
</description>
|
||||||
<screenshots>
|
<screenshots>
|
||||||
<screenshot type="default">
|
<screenshot type="default">
|
||||||
<caption>Song playing showing context</caption>
|
<caption>Song playing showing context</caption>
|
||||||
<image width="1600" height="874">https://www.strawberrymusicplayer.org/pictures/appdata-screenshot-001.png</image>
|
<image width="1432" height="834">https://www.strawberrymusicplayer.org/pictures/appdata-screenshot-003.png</image>
|
||||||
</screenshot>
|
</screenshot>
|
||||||
<screenshot>
|
<screenshot>
|
||||||
<caption>Collection overview</caption>
|
<caption>Collection overview</caption>
|
||||||
<image width="1600" height="874">https://www.strawberrymusicplayer.org/pictures/appdata-screenshot-002.png</image>
|
<image width="1432" height="834">https://www.strawberrymusicplayer.org/pictures/appdata-screenshot-004.png</image>
|
||||||
</screenshot>
|
</screenshot>
|
||||||
</screenshots>
|
</screenshots>
|
||||||
<update_contact>eclipseo@fedoraproject.org</update_contact>
|
<update_contact>eclipseo@fedoraproject.org</update_contact>
|
||||||
|
<releases>
|
||||||
|
<release version="1.0.0" date="2021-10-16"/>
|
||||||
|
</releases>
|
||||||
</component>
|
</component>
|
||||||
|
|||||||
18
dist/unix/strawberry.1
vendored
@@ -5,7 +5,7 @@ Strawberry \- music player and music collection organizer
|
|||||||
.B strawberry
|
.B strawberry
|
||||||
[\fI\,options\/\fR] [\fI\,URL(s)\/\fR]
|
[\fI\,options\/\fR] [\fI\,URL(s)\/\fR]
|
||||||
.SH DESCRIPTION
|
.SH DESCRIPTION
|
||||||
Strawberry is a music player especially aimed at audiophiles.
|
Strawberry is a music player aimed at music collectors and audiophiles.
|
||||||
.TP
|
.TP
|
||||||
Features:
|
Features:
|
||||||
.br
|
.br
|
||||||
@@ -17,13 +17,15 @@ Features:
|
|||||||
.br
|
.br
|
||||||
- Native desktop notifications
|
- Native desktop notifications
|
||||||
.br
|
.br
|
||||||
|
- Playlist management
|
||||||
|
.br
|
||||||
- Playlists in multiple formats
|
- Playlists in multiple formats
|
||||||
.br
|
.br
|
||||||
- Advanced output and device options with support for bit perfect playback on Linux
|
- Advanced output and device options with support for bit perfect playback on Linux
|
||||||
.br
|
.br
|
||||||
- Edit tags on music files
|
- Edit tags on audio files
|
||||||
.br
|
.br
|
||||||
- Fetch tags from MusicBrainz
|
- Automatically retrieve tags from MusicBrainz
|
||||||
.br
|
.br
|
||||||
- Album cover art from Last.fm, Musicbrainz, Discogs, Musixmatch, Deezer, Tidal, Qobuz and Spotify
|
- Album cover art from Last.fm, Musicbrainz, Discogs, Musixmatch, Deezer, Tidal, Qobuz and Spotify
|
||||||
.br
|
.br
|
||||||
@@ -33,11 +35,15 @@ Features:
|
|||||||
.br
|
.br
|
||||||
- Audio analyzer
|
- Audio analyzer
|
||||||
.br
|
.br
|
||||||
- Equalizer
|
- Audio Equalizer
|
||||||
.br
|
.br
|
||||||
- Transfer music to iPod, iPhone, MTP or mass-storage USB player
|
- Transfer music to mass-storage USB players, MTP compatible devices and iPod Nano/Classic
|
||||||
.br
|
.br
|
||||||
- Streaming from Subsonic
|
- Scrobbler with support for Last.fm, Libre.fm and ListenBrainz
|
||||||
|
.br
|
||||||
|
- Streaming support for Subsonic-compatible servers
|
||||||
|
.br
|
||||||
|
- Unofficial streaming support for Tidal and Qobuz
|
||||||
.TP
|
.TP
|
||||||
It is a fork of Clementine. The name is inspired by the band Strawbs.
|
It is a fork of Clementine. The name is inspired by the band Strawbs.
|
||||||
.SH OPTIONS
|
.SH OPTIONS
|
||||||
|
|||||||
64
dist/unix/strawberry.spec.in
vendored
@@ -1,6 +1,6 @@
|
|||||||
Name: strawberry
|
Name: strawberry
|
||||||
Version: @STRAWBERRY_VERSION_RPM_V@
|
Version: @STRAWBERRY_VERSION_RPM_V@
|
||||||
%if 0%{?fedora} || 0%{?rhel_version} || 0%{?centos}
|
%if 0%{?fedora} || 0%{?rhel_version} || 0%{?centos} || "%{?_vendor}" == "openmandriva"
|
||||||
Release: @STRAWBERRY_VERSION_RPM_R@%{?dist}
|
Release: @STRAWBERRY_VERSION_RPM_R@%{?dist}
|
||||||
%else
|
%else
|
||||||
Release: @STRAWBERRY_VERSION_RPM_R@.@RPM_DISTRO@
|
Release: @STRAWBERRY_VERSION_RPM_R@.@RPM_DISTRO@
|
||||||
@@ -49,28 +49,31 @@ BuildRequires: pkgconfig(sqlite3) >= 3.9
|
|||||||
BuildRequires: pkgconfig(taglib)
|
BuildRequires: pkgconfig(taglib)
|
||||||
%endif
|
%endif
|
||||||
BuildRequires: pkgconfig(fftw3)
|
BuildRequires: pkgconfig(fftw3)
|
||||||
%if "@QT_MAJOR_VERSION@" == "5" && ( 0%{?fedora} || 0%{?rhel_version} || 0%{?centos} )
|
%if "@QT_VERSION_MAJOR@" == "5" && ( 0%{?fedora} || 0%{?rhel_version} || 0%{?centos} )
|
||||||
BuildRequires: pkgconfig(Qt@QT_MAJOR_VERSION@Core)
|
BuildRequires: pkgconfig(Qt@QT_VERSION_MAJOR@Core)
|
||||||
BuildRequires: pkgconfig(Qt@QT_MAJOR_VERSION@Gui)
|
BuildRequires: pkgconfig(Qt@QT_VERSION_MAJOR@Gui)
|
||||||
BuildRequires: pkgconfig(Qt@QT_MAJOR_VERSION@Widgets)
|
BuildRequires: pkgconfig(Qt@QT_VERSION_MAJOR@Widgets)
|
||||||
BuildRequires: pkgconfig(Qt@QT_MAJOR_VERSION@Concurrent)
|
BuildRequires: pkgconfig(Qt@QT_VERSION_MAJOR@Concurrent)
|
||||||
BuildRequires: pkgconfig(Qt@QT_MAJOR_VERSION@Network)
|
BuildRequires: pkgconfig(Qt@QT_VERSION_MAJOR@Network)
|
||||||
BuildRequires: pkgconfig(Qt@QT_MAJOR_VERSION@Sql)
|
BuildRequires: pkgconfig(Qt@QT_VERSION_MAJOR@Sql)
|
||||||
BuildRequires: pkgconfig(Qt@QT_MAJOR_VERSION@X11Extras)
|
BuildRequires: pkgconfig(Qt@QT_VERSION_MAJOR@DBus)
|
||||||
BuildRequires: pkgconfig(Qt@QT_MAJOR_VERSION@DBus)
|
BuildRequires: pkgconfig(Qt@QT_VERSION_MAJOR@Test)
|
||||||
BuildRequires: pkgconfig(Qt@QT_MAJOR_VERSION@Test)
|
BuildRequires: pkgconfig(Qt@QT_VERSION_MAJOR@X11Extras)
|
||||||
%else
|
%else
|
||||||
BuildRequires: cmake(Qt@QT_MAJOR_VERSION@Core)
|
BuildRequires: cmake(Qt@QT_VERSION_MAJOR@Core)
|
||||||
BuildRequires: cmake(Qt@QT_MAJOR_VERSION@Gui)
|
BuildRequires: cmake(Qt@QT_VERSION_MAJOR@Gui)
|
||||||
BuildRequires: cmake(Qt@QT_MAJOR_VERSION@Widgets)
|
BuildRequires: cmake(Qt@QT_VERSION_MAJOR@Widgets)
|
||||||
BuildRequires: cmake(Qt@QT_MAJOR_VERSION@Concurrent)
|
BuildRequires: cmake(Qt@QT_VERSION_MAJOR@Concurrent)
|
||||||
BuildRequires: cmake(Qt@QT_MAJOR_VERSION@Network)
|
BuildRequires: cmake(Qt@QT_VERSION_MAJOR@Network)
|
||||||
BuildRequires: cmake(Qt@QT_MAJOR_VERSION@Sql)
|
BuildRequires: cmake(Qt@QT_VERSION_MAJOR@Sql)
|
||||||
BuildRequires: cmake(Qt@QT_MAJOR_VERSION@DBus)
|
BuildRequires: cmake(Qt@QT_VERSION_MAJOR@DBus)
|
||||||
BuildRequires: cmake(Qt@QT_MAJOR_VERSION@Test)
|
BuildRequires: cmake(Qt@QT_VERSION_MAJOR@Test)
|
||||||
|
%if "@QT_VERSION_MAJOR@" == "5"
|
||||||
|
BuildRequires: cmake(Qt@QT_VERSION_MAJOR@X11Extras)
|
||||||
|
%endif
|
||||||
%endif
|
%endif
|
||||||
%if 0%{?suse_version} || 0%{?fedora_version} || 0%{?mageia}
|
%if 0%{?suse_version} || 0%{?fedora_version} || 0%{?mageia}
|
||||||
BuildRequires: cmake(Qt@QT_MAJOR_VERSION@LinguistTools)
|
BuildRequires: cmake(Qt@QT_VERSION_MAJOR@LinguistTools)
|
||||||
%endif
|
%endif
|
||||||
BuildRequires: pkgconfig(gstreamer-1.0)
|
BuildRequires: pkgconfig(gstreamer-1.0)
|
||||||
BuildRequires: pkgconfig(gstreamer-app-1.0)
|
BuildRequires: pkgconfig(gstreamer-app-1.0)
|
||||||
@@ -89,10 +92,11 @@ BuildRequires: pkgconfig(libvlc)
|
|||||||
%endif
|
%endif
|
||||||
|
|
||||||
%if 0%{?suse_version}
|
%if 0%{?suse_version}
|
||||||
%if "@QT_MAJOR_VERSION@" == "6"
|
%if "@QT_VERSION_MAJOR@" == "6"
|
||||||
Requires: qt6-sql-sqlite
|
Requires: qt6-sql-sqlite
|
||||||
|
Requires: qt6-network-tls
|
||||||
%endif
|
%endif
|
||||||
%if "@QT_MAJOR_VERSION@" == "5"
|
%if "@QT_VERSION_MAJOR@" == "5"
|
||||||
Requires: libQt5Sql5-sqlite
|
Requires: libQt5Sql5-sqlite
|
||||||
%endif
|
%endif
|
||||||
%endif
|
%endif
|
||||||
@@ -107,18 +111,20 @@ Features:
|
|||||||
MPC, TrueAudio, AIFF, MP4, MP3, ASF and Monkey's Audio.
|
MPC, TrueAudio, AIFF, MP4, MP3, ASF and Monkey's Audio.
|
||||||
- Audio CD playback
|
- Audio CD playback
|
||||||
- Native desktop notifications
|
- Native desktop notifications
|
||||||
|
- Playlist management
|
||||||
- Playlists in multiple formats
|
- Playlists in multiple formats
|
||||||
- Advanced audio output and device configuration for bit-perfect playback on Linux
|
- Advanced audio output and device configuration for bit-perfect playback on Linux
|
||||||
- Edit tags on music files
|
- Edit tags on audio files
|
||||||
- Fetch tags from MusicBrainz
|
- Automatically retrieve tags from MusicBrainz
|
||||||
- Album cover art from Last.fm, Musicbrainz, Discogs, Musixmatch, Deezer, Tidal, Qobuz and Spotify
|
- Album cover art from Last.fm, Musicbrainz, Discogs, Musixmatch, Deezer, Tidal, Qobuz and Spotify
|
||||||
- Song lyrics from AudD, Genius, Musixmatch, ChartLyrics, lyrics.ovh and lololyrics.com
|
- Song lyrics from AudD, Genius, Musixmatch, ChartLyrics, lyrics.ovh and lololyrics.com
|
||||||
- Support for multiple backends
|
- Support for multiple backends
|
||||||
- Audio analyzer
|
- Audio analyzer
|
||||||
- Audio equalizer
|
- Audio equalizer
|
||||||
- Transfer music to iPod, MTP or mass-storage USB player
|
|
||||||
- Scrobbler with support for Last.fm, Libre.fm and ListenBrainz
|
- Scrobbler with support for Last.fm, Libre.fm and ListenBrainz
|
||||||
- Streaming support for Subsonic
|
- Transfer music to mass-storage USB players, MTP compatible devices and iPod Nano/Classic
|
||||||
|
- Streaming support for Subsonic-compatible servers
|
||||||
|
- Unofficial streaming support for Tidal and Qobuz
|
||||||
|
|
||||||
%if 0%{?suse_version}
|
%if 0%{?suse_version}
|
||||||
%debug_package
|
%debug_package
|
||||||
@@ -131,8 +137,8 @@ Features:
|
|||||||
%if 0%{?fedora} || 0%{?rhel_version} || 0%{?centos}
|
%if 0%{?fedora} || 0%{?rhel_version} || 0%{?centos}
|
||||||
export CXXFLAGS="-fPIC $RPM_OPT_FLAGS"
|
export CXXFLAGS="-fPIC $RPM_OPT_FLAGS"
|
||||||
%endif
|
%endif
|
||||||
%{cmake} -DCMAKE_BUILD_TYPE:STRING=Release -DQT_MAJOR_VERSION=@QT_MAJOR_VERSION@
|
%{cmake} -DQT_VERSION_MAJOR=@QT_VERSION_MAJOR@
|
||||||
%if 0%{?centos} || (0%{?mageia} && 0%{?mageia} <= 7)
|
%if 0%{?centos} || (0%{?mageia} && 0%{?mageia} <= 7) || "%{?_vendor}" == "openmandriva"
|
||||||
%make_build
|
%make_build
|
||||||
%else
|
%else
|
||||||
%cmake_build
|
%cmake_build
|
||||||
@@ -142,7 +148,7 @@ Features:
|
|||||||
%if 0%{?centos}
|
%if 0%{?centos}
|
||||||
%make_install
|
%make_install
|
||||||
%else
|
%else
|
||||||
%if 0%{?mageia}
|
%if 0%{?mageia} || "%{?_vendor}" == "openmandriva"
|
||||||
%make_install -C build
|
%make_install -C build
|
||||||
%else
|
%else
|
||||||
%cmake_install
|
%cmake_install
|
||||||
|
|||||||
186
dist/windows/Registry.nsh
vendored
Normal file
@@ -0,0 +1,186 @@
|
|||||||
|
!define registry::Open `!insertmacro registry::Open`
|
||||||
|
|
||||||
|
!macro registry::Open _PATH _OPTIONS _HANDLE
|
||||||
|
registry::_Open /NOUNLOAD `${_PATH}` `${_OPTIONS}`
|
||||||
|
Pop ${_HANDLE}
|
||||||
|
!macroend
|
||||||
|
|
||||||
|
|
||||||
|
!define registry::Find `!insertmacro registry::Find`
|
||||||
|
|
||||||
|
!macro registry::Find _HANDLE _PATH _VALUEORKEY _STRING _TYPE
|
||||||
|
registry::_Find /NOUNLOAD `${_HANDLE}`
|
||||||
|
Pop ${_PATH}
|
||||||
|
Pop ${_VALUEORKEY}
|
||||||
|
Pop ${_STRING}
|
||||||
|
Pop ${_TYPE}
|
||||||
|
!macroend
|
||||||
|
|
||||||
|
|
||||||
|
!define registry::Close `!insertmacro registry::Close`
|
||||||
|
|
||||||
|
!macro registry::Close _HANDLE
|
||||||
|
registry::_Close /NOUNLOAD `${_HANDLE}`
|
||||||
|
!macroend
|
||||||
|
|
||||||
|
|
||||||
|
!define registry::KeyExists `!insertmacro registry::KeyExists`
|
||||||
|
|
||||||
|
!macro registry::KeyExists _PATH _ERR
|
||||||
|
registry::_KeyExists /NOUNLOAD `${_PATH}`
|
||||||
|
Pop ${_ERR}
|
||||||
|
!macroend
|
||||||
|
|
||||||
|
|
||||||
|
!define registry::Read `!insertmacro registry::Read`
|
||||||
|
|
||||||
|
!macro registry::Read _PATH _VALUE _STRING _TYPE
|
||||||
|
registry::_Read /NOUNLOAD `${_PATH}` `${_VALUE}`
|
||||||
|
Pop ${_STRING}
|
||||||
|
Pop ${_TYPE}
|
||||||
|
!macroend
|
||||||
|
|
||||||
|
|
||||||
|
!define registry::Write `!insertmacro registry::Write`
|
||||||
|
|
||||||
|
!macro registry::Write _PATH _VALUE _STRING _TYPE _ERR
|
||||||
|
registry::_Write /NOUNLOAD `${_PATH}` `${_VALUE}` `${_STRING}` `${_TYPE}`
|
||||||
|
Pop ${_ERR}
|
||||||
|
!macroend
|
||||||
|
|
||||||
|
|
||||||
|
!define registry::ReadExtra `!insertmacro registry::ReadExtra`
|
||||||
|
|
||||||
|
!macro registry::ReadExtra _PATH _VALUE _NUMBER _STRING _TYPE
|
||||||
|
registry::_ReadExtra /NOUNLOAD `${_PATH}` `${_VALUE}` `${_NUMBER}`
|
||||||
|
Pop ${_STRING}
|
||||||
|
Pop ${_TYPE}
|
||||||
|
!macroend
|
||||||
|
|
||||||
|
|
||||||
|
!define registry::WriteExtra `!insertmacro registry::WriteExtra`
|
||||||
|
|
||||||
|
!macro registry::WriteExtra _PATH _VALUE _STRING _ERR
|
||||||
|
registry::_WriteExtra /NOUNLOAD `${_PATH}` `${_VALUE}` `${_STRING}`
|
||||||
|
Pop ${_ERR}
|
||||||
|
!macroend
|
||||||
|
|
||||||
|
|
||||||
|
!define registry::CreateKey `!insertmacro registry::CreateKey`
|
||||||
|
|
||||||
|
!macro registry::CreateKey _PATH _ERR
|
||||||
|
registry::_CreateKey /NOUNLOAD `${_PATH}`
|
||||||
|
Pop ${_ERR}
|
||||||
|
!macroend
|
||||||
|
|
||||||
|
|
||||||
|
!define registry::DeleteValue `!insertmacro registry::DeleteValue`
|
||||||
|
|
||||||
|
!macro registry::DeleteValue _PATH _VALUE _ERR
|
||||||
|
registry::_DeleteValue /NOUNLOAD `${_PATH}` `${_VALUE}`
|
||||||
|
Pop ${_ERR}
|
||||||
|
!macroend
|
||||||
|
|
||||||
|
|
||||||
|
!define registry::DeleteKey `!insertmacro registry::DeleteKey`
|
||||||
|
|
||||||
|
!macro registry::DeleteKey _PATH _ERR
|
||||||
|
registry::_DeleteKey /NOUNLOAD `${_PATH}`
|
||||||
|
Pop ${_ERR}
|
||||||
|
!macroend
|
||||||
|
|
||||||
|
|
||||||
|
!define registry::DeleteKeyEmpty `!insertmacro registry::DeleteKeyEmpty`
|
||||||
|
|
||||||
|
!macro registry::DeleteKeyEmpty _PATH _ERR
|
||||||
|
registry::_DeleteKeyEmpty /NOUNLOAD `${_PATH}`
|
||||||
|
Pop ${_ERR}
|
||||||
|
!macroend
|
||||||
|
|
||||||
|
|
||||||
|
!define registry::CopyValue `!insertmacro registry::CopyValue`
|
||||||
|
|
||||||
|
!macro registry::CopyValue _PATH_SOURCE _VALUE_SOURCE _PATH_TARGET _VALUE_TARGET _ERR
|
||||||
|
registry::_CopyValue /NOUNLOAD `${_PATH_SOURCE}` `${_VALUE_SOURCE}` `${_PATH_TARGET}` `${_VALUE_TARGET}`
|
||||||
|
Pop ${_ERR}
|
||||||
|
!macroend
|
||||||
|
|
||||||
|
|
||||||
|
!define registry::MoveValue `!insertmacro registry::MoveValue`
|
||||||
|
|
||||||
|
!macro registry::MoveValue _PATH_SOURCE _VALUE_SOURCE _PATH_TARGET _VALUE_TARGET _ERR
|
||||||
|
registry::_MoveValue /NOUNLOAD `${_PATH_SOURCE}` `${_VALUE_SOURCE}` `${_PATH_TARGET}` `${_VALUE_TARGET}`
|
||||||
|
Pop ${_ERR}
|
||||||
|
!macroend
|
||||||
|
|
||||||
|
|
||||||
|
!define registry::CopyKey `!insertmacro registry::CopyKey`
|
||||||
|
|
||||||
|
!macro registry::CopyKey _PATH_SOURCE _PATH_TARGET _ERR
|
||||||
|
registry::_CopyKey /NOUNLOAD `${_PATH_SOURCE}` `${_PATH_TARGET}`
|
||||||
|
Pop ${_ERR}
|
||||||
|
!macroend
|
||||||
|
|
||||||
|
|
||||||
|
!define registry::MoveKey `!insertmacro registry::MoveKey`
|
||||||
|
|
||||||
|
!macro registry::MoveKey _PATH_SOURCE _PATH_TARGET _ERR
|
||||||
|
registry::_MoveKey /NOUNLOAD `${_PATH_SOURCE}` `${_PATH_TARGET}`
|
||||||
|
Pop ${_ERR}
|
||||||
|
!macroend
|
||||||
|
|
||||||
|
|
||||||
|
!define registry::SaveKey `!insertmacro registry::SaveKey`
|
||||||
|
|
||||||
|
!macro registry::SaveKey _PATH _FILE _OPTIONS _ERR
|
||||||
|
registry::_SaveKey /NOUNLOAD `${_PATH}` `${_FILE}` `${_OPTIONS}`
|
||||||
|
Pop ${_ERR}
|
||||||
|
!macroend
|
||||||
|
|
||||||
|
|
||||||
|
!define registry::RestoreKey `!insertmacro registry::RestoreKey`
|
||||||
|
|
||||||
|
!macro registry::RestoreKey _FILE _ERR
|
||||||
|
registry::_RestoreKey /NOUNLOAD `${_FILE}`
|
||||||
|
Pop ${_ERR}
|
||||||
|
!macroend
|
||||||
|
|
||||||
|
|
||||||
|
!define registry::StrToHex `!insertmacro registry::StrToHexA`
|
||||||
|
!define registry::StrToHexA `!insertmacro registry::StrToHexA`
|
||||||
|
|
||||||
|
!macro registry::StrToHexA _STRING _HEX_STRING
|
||||||
|
registry::_StrToHexA /NOUNLOAD `${_STRING}`
|
||||||
|
Pop ${_HEX_STRING}
|
||||||
|
!macroend
|
||||||
|
|
||||||
|
|
||||||
|
!define registry::StrToHexW `!insertmacro registry::StrToHexW`
|
||||||
|
|
||||||
|
!macro registry::StrToHexW _STRING _HEX_STRING
|
||||||
|
registry::_StrToHexW /NOUNLOAD `${_STRING}`
|
||||||
|
Pop ${_HEX_STRING}
|
||||||
|
!macroend
|
||||||
|
|
||||||
|
|
||||||
|
!define registry::HexToStr `!insertmacro registry::HexToStrA`
|
||||||
|
!define registry::HexToStrA `!insertmacro registry::HexToStrA`
|
||||||
|
|
||||||
|
!macro registry::HexToStrA _HEX_STRING _STRING
|
||||||
|
registry::_HexToStrA /NOUNLOAD `${_HEX_STRING}`
|
||||||
|
Pop ${_STRING}
|
||||||
|
!macroend
|
||||||
|
|
||||||
|
!define registry::HexToStrW `!insertmacro registry::HexToStrW`
|
||||||
|
|
||||||
|
!macro registry::HexToStrW _HEX_STRING _STRING
|
||||||
|
registry::_HexToStrW /NOUNLOAD `${_HEX_STRING}`
|
||||||
|
Pop ${_STRING}
|
||||||
|
!macroend
|
||||||
|
|
||||||
|
|
||||||
|
!define registry::Unload `!insertmacro registry::Unload`
|
||||||
|
|
||||||
|
!macro registry::Unload
|
||||||
|
registry::_Unload
|
||||||
|
!macroend
|
||||||
755
dist/windows/strawberry.nsi.in
vendored
@@ -33,24 +33,13 @@
|
|||||||
|
|
||||||
#include "gstfastspectrum.h"
|
#include "gstfastspectrum.h"
|
||||||
|
|
||||||
GST_DEBUG_CATEGORY_STATIC (gst_fastspectrum_debug);
|
GST_DEBUG_CATEGORY_STATIC(gst_fastspectrum_debug);
|
||||||
#define GST_CAT_DEFAULT gst_fastspectrum_debug
|
|
||||||
|
|
||||||
/* elementfactory information */
|
namespace {
|
||||||
#if G_BYTE_ORDER == G_LITTLE_ENDIAN
|
|
||||||
# define FORMATS "{ S16LE, S24LE, S32LE, F32LE, F64LE }"
|
|
||||||
#else
|
|
||||||
# define FORMATS "{ S16BE, S24BE, S32BE, F32BE, F64BE }"
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#define ALLOWED_CAPS \
|
// Spectrum properties
|
||||||
GST_AUDIO_CAPS_MAKE (FORMATS) ", " \
|
constexpr auto DEFAULT_INTERVAL = (GST_SECOND / 10);
|
||||||
"layout = (string) interleaved, " \
|
constexpr auto DEFAULT_BANDS = 128;
|
||||||
"channels = 1"
|
|
||||||
|
|
||||||
/* Spectrum properties */
|
|
||||||
#define DEFAULT_INTERVAL (GST_SECOND / 10)
|
|
||||||
#define DEFAULT_BANDS 128
|
|
||||||
|
|
||||||
enum {
|
enum {
|
||||||
PROP_0,
|
PROP_0,
|
||||||
@@ -58,93 +47,97 @@ enum {
|
|||||||
PROP_BANDS
|
PROP_BANDS
|
||||||
};
|
};
|
||||||
|
|
||||||
|
} // namespace
|
||||||
|
|
||||||
#define gst_fastspectrum_parent_class parent_class
|
#define gst_fastspectrum_parent_class parent_class
|
||||||
G_DEFINE_TYPE (GstFastSpectrum, gst_fastspectrum, GST_TYPE_AUDIO_FILTER)
|
G_DEFINE_TYPE(GstFastSpectrum, gst_fastspectrum, GST_TYPE_AUDIO_FILTER)
|
||||||
|
|
||||||
static void gst_fastspectrum_finalize (GObject * object);
|
static void gst_fastspectrum_finalize(GObject *object);
|
||||||
static void gst_fastspectrum_set_property (GObject * object, guint prop_id, const GValue * value, GParamSpec * pspec);
|
static void gst_fastspectrum_set_property(GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec);
|
||||||
static void gst_fastspectrum_get_property (GObject * object, guint prop_id, GValue * value, GParamSpec * pspec);
|
static void gst_fastspectrum_get_property(GObject *object, guint prop_id, GValue *value, GParamSpec *pspec);
|
||||||
static gboolean gst_fastspectrum_start (GstBaseTransform * trans);
|
static gboolean gst_fastspectrum_start(GstBaseTransform *trans);
|
||||||
static gboolean gst_fastspectrum_stop (GstBaseTransform * trans);
|
static gboolean gst_fastspectrum_stop(GstBaseTransform *trans);
|
||||||
static GstFlowReturn gst_fastspectrum_transform_ip (GstBaseTransform *trans, GstBuffer *buffer);
|
static GstFlowReturn gst_fastspectrum_transform_ip(GstBaseTransform *trans, GstBuffer *buffer);
|
||||||
static gboolean gst_fastspectrum_setup (GstAudioFilter * base, const GstAudioInfo * info);
|
static gboolean gst_fastspectrum_setup(GstAudioFilter *base, const GstAudioInfo *info);
|
||||||
|
|
||||||
static void gst_fastspectrum_class_init (GstFastSpectrumClass * klass) {
|
static void gst_fastspectrum_class_init(GstFastSpectrumClass *klass) {
|
||||||
|
|
||||||
GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
|
GObjectClass *gobject_class = G_OBJECT_CLASS(klass);
|
||||||
GstElementClass *element_class = GST_ELEMENT_CLASS (klass);
|
GstElementClass *element_class = GST_ELEMENT_CLASS(klass);
|
||||||
GstBaseTransformClass *trans_class = GST_BASE_TRANSFORM_CLASS (klass);
|
GstBaseTransformClass *trans_class = GST_BASE_TRANSFORM_CLASS(klass);
|
||||||
GstAudioFilterClass *filter_class = GST_AUDIO_FILTER_CLASS (klass);
|
GstAudioFilterClass *filter_class = GST_AUDIO_FILTER_CLASS(klass);
|
||||||
GstCaps *caps = nullptr;
|
GstCaps *caps = nullptr;
|
||||||
|
|
||||||
gobject_class->set_property = gst_fastspectrum_set_property;
|
gobject_class->set_property = gst_fastspectrum_set_property;
|
||||||
gobject_class->get_property = gst_fastspectrum_get_property;
|
gobject_class->get_property = gst_fastspectrum_get_property;
|
||||||
gobject_class->finalize = gst_fastspectrum_finalize;
|
gobject_class->finalize = gst_fastspectrum_finalize;
|
||||||
|
|
||||||
trans_class->start = GST_DEBUG_FUNCPTR (gst_fastspectrum_start);
|
trans_class->start = GST_DEBUG_FUNCPTR(gst_fastspectrum_start);
|
||||||
trans_class->stop = GST_DEBUG_FUNCPTR (gst_fastspectrum_stop);
|
trans_class->stop = GST_DEBUG_FUNCPTR(gst_fastspectrum_stop);
|
||||||
trans_class->transform_ip = GST_DEBUG_FUNCPTR (gst_fastspectrum_transform_ip);
|
trans_class->transform_ip = GST_DEBUG_FUNCPTR(gst_fastspectrum_transform_ip);
|
||||||
trans_class->passthrough_on_same_caps = TRUE;
|
trans_class->passthrough_on_same_caps = TRUE;
|
||||||
|
|
||||||
filter_class->setup = GST_DEBUG_FUNCPTR (gst_fastspectrum_setup);
|
filter_class->setup = GST_DEBUG_FUNCPTR(gst_fastspectrum_setup);
|
||||||
|
|
||||||
g_object_class_install_property (gobject_class, PROP_INTERVAL,
|
g_object_class_install_property(gobject_class, PROP_INTERVAL, g_param_spec_uint64("interval", "Interval", "Interval of time between message posts (in nanoseconds)", 1, G_MAXUINT64, DEFAULT_INTERVAL, GParamFlags(G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)));
|
||||||
g_param_spec_uint64 ("interval", "Interval", "Interval of time between message posts (in nanoseconds)", 1, G_MAXUINT64, DEFAULT_INTERVAL, GParamFlags(G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)));
|
|
||||||
|
|
||||||
g_object_class_install_property (gobject_class, PROP_BANDS, g_param_spec_uint ("bands", "Bands", "Number of frequency bands", 0, G_MAXUINT, DEFAULT_BANDS, GParamFlags(G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)));
|
g_object_class_install_property(gobject_class, PROP_BANDS, g_param_spec_uint("bands", "Bands", "Number of frequency bands", 0, G_MAXUINT, DEFAULT_BANDS, GParamFlags(G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)));
|
||||||
|
|
||||||
GST_DEBUG_CATEGORY_INIT (gst_fastspectrum_debug, "spectrum", 0,
|
GST_DEBUG_CATEGORY_INIT(gst_fastspectrum_debug, "spectrum", 0, "audio spectrum analyser element");
|
||||||
"audio spectrum analyser element");
|
|
||||||
|
|
||||||
gst_element_class_set_static_metadata (element_class, "Spectrum analyzer",
|
gst_element_class_set_static_metadata(element_class, "Spectrum analyzer",
|
||||||
"Filter/Analyzer/Audio",
|
"Filter/Analyzer/Audio",
|
||||||
"Run an FFT on the audio signal, output spectrum data",
|
"Run an FFT on the audio signal, output spectrum data",
|
||||||
"Erik Walthinsen <omega@cse.ogi.edu>, "
|
"Erik Walthinsen <omega@cse.ogi.edu>, "
|
||||||
"Stefan Kost <ensonic@users.sf.net>, "
|
"Stefan Kost <ensonic@users.sf.net>, "
|
||||||
"Sebastian Dröge <sebastian.droege@collabora.co.uk>");
|
"Sebastian Dröge <sebastian.droege@collabora.co.uk>");
|
||||||
|
|
||||||
caps = gst_caps_from_string (ALLOWED_CAPS);
|
#if G_BYTE_ORDER == G_LITTLE_ENDIAN
|
||||||
gst_audio_filter_class_add_pad_templates (filter_class, caps);
|
caps = gst_caps_from_string(GST_AUDIO_CAPS_MAKE("{ S16LE, S24LE, S32LE, F32LE, F64LE }") ", layout = (string) interleaved, channels = 1");
|
||||||
gst_caps_unref (caps);
|
#else
|
||||||
|
caps = gst_caps_from_string(GST_AUDIO_CAPS_MAKE("{ S16BE, S24BE, S32BE, F32BE, F64BE }") ", layout = (string) interleaved, channels = 1");
|
||||||
|
#endif
|
||||||
|
|
||||||
|
gst_audio_filter_class_add_pad_templates(filter_class, caps);
|
||||||
|
gst_caps_unref(caps);
|
||||||
|
|
||||||
klass->fftw_lock = new QMutex;
|
klass->fftw_lock = new QMutex;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static void gst_fastspectrum_init (GstFastSpectrum * spectrum) {
|
static void gst_fastspectrum_init(GstFastSpectrum *spectrum) {
|
||||||
|
|
||||||
spectrum->interval = DEFAULT_INTERVAL;
|
spectrum->interval = DEFAULT_INTERVAL;
|
||||||
spectrum->bands = DEFAULT_BANDS;
|
spectrum->bands = DEFAULT_BANDS;
|
||||||
|
|
||||||
spectrum->channel_data_initialized = false;
|
spectrum->channel_data_initialized = false;
|
||||||
|
|
||||||
g_mutex_init (&spectrum->lock);
|
g_mutex_init(&spectrum->lock);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static void gst_fastspectrum_alloc_channel_data (GstFastSpectrum * spectrum) {
|
static void gst_fastspectrum_alloc_channel_data(GstFastSpectrum *spectrum) {
|
||||||
|
|
||||||
guint bands = spectrum->bands;
|
guint bands = spectrum->bands;
|
||||||
guint nfft = 2 * bands - 2;
|
guint nfft = 2 * bands - 2;
|
||||||
|
|
||||||
spectrum->input_ring_buffer = new double[nfft];
|
spectrum->input_ring_buffer = new double[nfft];
|
||||||
spectrum->fft_input = reinterpret_cast<double*>( fftw_malloc(sizeof(double) * nfft));
|
spectrum->fft_input = reinterpret_cast<double*>(fftw_malloc(sizeof(double) * nfft));
|
||||||
spectrum->fft_output =reinterpret_cast<fftw_complex*>( fftw_malloc(sizeof(fftw_complex) * (nfft/2+1)));
|
spectrum->fft_output = reinterpret_cast<fftw_complex*>(fftw_malloc(sizeof(fftw_complex) * (nfft / 2 + 1)));
|
||||||
|
|
||||||
spectrum->spect_magnitude = new double[bands]{};
|
spectrum->spect_magnitude = new double[bands] {};
|
||||||
|
|
||||||
GstFastSpectrumClass* klass = reinterpret_cast<GstFastSpectrumClass*>(G_OBJECT_GET_CLASS(spectrum));
|
GstFastSpectrumClass *klass = reinterpret_cast<GstFastSpectrumClass*>(G_OBJECT_GET_CLASS(spectrum));
|
||||||
{
|
{
|
||||||
QMutexLocker l(klass->fftw_lock);
|
QMutexLocker l(klass->fftw_lock);
|
||||||
spectrum->plan = fftw_plan_dft_r2c_1d(nfft, spectrum->fft_input, spectrum->fft_output, FFTW_ESTIMATE);
|
spectrum->plan = fftw_plan_dft_r2c_1d(static_cast<int>(nfft), spectrum->fft_input, spectrum->fft_output, FFTW_ESTIMATE);
|
||||||
}
|
}
|
||||||
spectrum->channel_data_initialized = true;
|
spectrum->channel_data_initialized = true;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static void gst_fastspectrum_free_channel_data (GstFastSpectrum * spectrum) {
|
static void gst_fastspectrum_free_channel_data(GstFastSpectrum *spectrum) {
|
||||||
|
|
||||||
GstFastSpectrumClass* klass = reinterpret_cast<GstFastSpectrumClass*>(G_OBJECT_GET_CLASS(spectrum));
|
GstFastSpectrumClass *klass = reinterpret_cast<GstFastSpectrumClass*>(G_OBJECT_GET_CLASS(spectrum));
|
||||||
if (spectrum->channel_data_initialized) {
|
if (spectrum->channel_data_initialized) {
|
||||||
{
|
{
|
||||||
QMutexLocker l(klass->fftw_lock);
|
QMutexLocker l(klass->fftw_lock);
|
||||||
@@ -160,7 +153,7 @@ static void gst_fastspectrum_free_channel_data (GstFastSpectrum * spectrum) {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static void gst_fastspectrum_flush (GstFastSpectrum * spectrum) {
|
static void gst_fastspectrum_flush(GstFastSpectrum *spectrum) {
|
||||||
|
|
||||||
spectrum->num_frames = 0;
|
spectrum->num_frames = 0;
|
||||||
spectrum->num_fft = 0;
|
spectrum->num_fft = 0;
|
||||||
@@ -169,150 +162,149 @@ static void gst_fastspectrum_flush (GstFastSpectrum * spectrum) {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static void gst_fastspectrum_reset_state (GstFastSpectrum * spectrum) {
|
static void gst_fastspectrum_reset_state(GstFastSpectrum *spectrum) {
|
||||||
|
|
||||||
GST_DEBUG_OBJECT (spectrum, "resetting state");
|
GST_DEBUG_OBJECT(spectrum, "resetting state");
|
||||||
|
|
||||||
gst_fastspectrum_free_channel_data (spectrum);
|
gst_fastspectrum_free_channel_data(spectrum);
|
||||||
gst_fastspectrum_flush (spectrum);
|
gst_fastspectrum_flush(spectrum);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static void gst_fastspectrum_finalize (GObject * object) {
|
static void gst_fastspectrum_finalize(GObject *object) {
|
||||||
|
|
||||||
GstFastSpectrum *spectrum = reinterpret_cast<GstFastSpectrum*>(object);
|
GstFastSpectrum *spectrum = reinterpret_cast<GstFastSpectrum*>(object);
|
||||||
|
|
||||||
gst_fastspectrum_reset_state (spectrum);
|
gst_fastspectrum_reset_state(spectrum);
|
||||||
g_mutex_clear (&spectrum->lock);
|
g_mutex_clear(&spectrum->lock);
|
||||||
|
|
||||||
G_OBJECT_CLASS (parent_class)->finalize (object);
|
G_OBJECT_CLASS(parent_class)->finalize(object);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static void gst_fastspectrum_set_property (GObject * object, guint prop_id, const GValue * value, GParamSpec * pspec) {
|
static void gst_fastspectrum_set_property(GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec) {
|
||||||
|
|
||||||
GstFastSpectrum *filter = reinterpret_cast<GstFastSpectrum*>(object);
|
GstFastSpectrum *filter = reinterpret_cast<GstFastSpectrum*>(object);
|
||||||
|
|
||||||
switch (prop_id) {
|
switch (prop_id) {
|
||||||
case PROP_INTERVAL:{
|
case PROP_INTERVAL: {
|
||||||
guint64 interval = g_value_get_uint64 (value);
|
guint64 interval = g_value_get_uint64(value);
|
||||||
g_mutex_lock (&filter->lock);
|
g_mutex_lock(&filter->lock);
|
||||||
if (filter->interval != interval) {
|
if (filter->interval != interval) {
|
||||||
filter->interval = interval;
|
filter->interval = interval;
|
||||||
gst_fastspectrum_reset_state (filter);
|
gst_fastspectrum_reset_state(filter);
|
||||||
}
|
}
|
||||||
g_mutex_unlock (&filter->lock);
|
g_mutex_unlock(&filter->lock);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case PROP_BANDS:{
|
case PROP_BANDS: {
|
||||||
guint bands = g_value_get_uint (value);
|
guint bands = g_value_get_uint(value);
|
||||||
g_mutex_lock (&filter->lock);
|
g_mutex_lock(&filter->lock);
|
||||||
if (filter->bands != bands) {
|
if (filter->bands != bands) {
|
||||||
filter->bands = bands;
|
filter->bands = bands;
|
||||||
gst_fastspectrum_reset_state (filter);
|
gst_fastspectrum_reset_state(filter);
|
||||||
}
|
}
|
||||||
g_mutex_unlock (&filter->lock);
|
g_mutex_unlock(&filter->lock);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
default:
|
default:
|
||||||
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
|
G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static void gst_fastspectrum_get_property (GObject * object, guint prop_id, GValue * value, GParamSpec * pspec) {
|
static void gst_fastspectrum_get_property(GObject *object, guint prop_id, GValue *value, GParamSpec *pspec) {
|
||||||
|
|
||||||
GstFastSpectrum *filter = reinterpret_cast<GstFastSpectrum*>(object);
|
GstFastSpectrum *filter = reinterpret_cast<GstFastSpectrum*>(object);
|
||||||
|
|
||||||
switch (prop_id) {
|
switch (prop_id) {
|
||||||
case PROP_INTERVAL:
|
case PROP_INTERVAL:
|
||||||
g_value_set_uint64 (value, filter->interval);
|
g_value_set_uint64(value, filter->interval);
|
||||||
break;
|
break;
|
||||||
case PROP_BANDS:
|
case PROP_BANDS:
|
||||||
g_value_set_uint (value, filter->bands);
|
g_value_set_uint(value, filter->bands);
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
|
G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static gboolean gst_fastspectrum_start (GstBaseTransform * trans) {
|
static gboolean gst_fastspectrum_start(GstBaseTransform *trans) {
|
||||||
|
|
||||||
GstFastSpectrum *spectrum = reinterpret_cast<GstFastSpectrum*>(trans);
|
GstFastSpectrum *spectrum = reinterpret_cast<GstFastSpectrum*>(trans);
|
||||||
|
|
||||||
gst_fastspectrum_reset_state (spectrum);
|
gst_fastspectrum_reset_state(spectrum);
|
||||||
|
|
||||||
return TRUE;
|
return TRUE;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static gboolean gst_fastspectrum_stop (GstBaseTransform * trans) {
|
static gboolean gst_fastspectrum_stop(GstBaseTransform *trans) {
|
||||||
|
|
||||||
GstFastSpectrum *spectrum = reinterpret_cast<GstFastSpectrum*>(trans);
|
GstFastSpectrum *spectrum = reinterpret_cast<GstFastSpectrum*>(trans);
|
||||||
|
|
||||||
gst_fastspectrum_reset_state (spectrum);
|
gst_fastspectrum_reset_state(spectrum);
|
||||||
|
|
||||||
return TRUE;
|
return TRUE;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* mixing data readers */
|
// Mixing data readers
|
||||||
|
|
||||||
static void input_data_mixed_float(const guint8* _in, double* out, guint len, double max_value, guint op, guint nfft) {
|
static void input_data_mixed_float(const guint8 *_in, double *out, guint len, double max_value, guint op, guint nfft) {
|
||||||
|
|
||||||
Q_UNUSED(max_value);
|
Q_UNUSED(max_value);
|
||||||
|
|
||||||
guint j = 0, ip = 0;
|
|
||||||
const gfloat *in = reinterpret_cast<const gfloat*>(_in);
|
const gfloat *in = reinterpret_cast<const gfloat*>(_in);
|
||||||
|
guint ip = 0;
|
||||||
|
|
||||||
for (j = 0; j < len; j++) {
|
for (guint j = 0; j < len; j++) {
|
||||||
out[op] = in[ip++];
|
out[op] = in[ip++];
|
||||||
op = (op + 1) % nfft;
|
op = (op + 1) % nfft;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static void input_data_mixed_double (const guint8 * _in, double* out, guint len, double max_value, guint op, guint nfft) {
|
static void input_data_mixed_double(const guint8 *_in, double *out, guint len, double max_value, guint op, guint nfft) {
|
||||||
|
|
||||||
Q_UNUSED(max_value);
|
Q_UNUSED(max_value);
|
||||||
|
|
||||||
guint j = 0, ip = 0;
|
|
||||||
const gdouble *in = reinterpret_cast<const gdouble*>(_in);
|
const gdouble *in = reinterpret_cast<const gdouble*>(_in);
|
||||||
|
guint ip = 0;
|
||||||
|
|
||||||
for (j = 0; j < len; j++) {
|
for (guint j = 0; j < len; j++) {
|
||||||
out[op] = in[ip++];
|
out[op] = in[ip++];
|
||||||
op = (op + 1) % nfft;
|
op = (op + 1) % nfft;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static void input_data_mixed_int32_max (const guint8 * _in, double* out, guint len, double max_value, guint op, guint nfft) {
|
static void input_data_mixed_int32_max(const guint8 *_in, double *out, guint len, double max_value, guint op, guint nfft) {
|
||||||
|
|
||||||
guint j = 0, ip = 0;
|
|
||||||
const gint32 *in = reinterpret_cast<const gint32*>(_in);
|
const gint32 *in = reinterpret_cast<const gint32*>(_in);
|
||||||
|
guint ip = 0;
|
||||||
|
|
||||||
for (j = 0; j < len; j++) {
|
for (guint j = 0; j < len; j++) {
|
||||||
out[op] = in[ip++] / max_value;
|
out[op] = in[ip++] / max_value;
|
||||||
op = (op + 1) % nfft;
|
op = (op + 1) % nfft;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static void input_data_mixed_int24_max (const guint8 * _in, double* out, guint len, double max_value, guint op, guint nfft) {
|
static void input_data_mixed_int24_max(const guint8 *_in, double *out, guint len, double max_value, guint op, guint nfft) {
|
||||||
|
|
||||||
guint j = 0;
|
for (guint j = 0; j < len; j++) {
|
||||||
|
|
||||||
for (j = 0; j < len; j++) {
|
|
||||||
#if G_BYTE_ORDER == G_BIG_ENDIAN
|
#if G_BYTE_ORDER == G_BIG_ENDIAN
|
||||||
gint32 value = GST_READ_UINT24_BE (_in);
|
guint32 value = GST_READ_UINT24_BE(_in);
|
||||||
#else
|
#else
|
||||||
gint32 value = GST_READ_UINT24_LE (_in);
|
guint32 value = GST_READ_UINT24_LE(_in);
|
||||||
#endif
|
#endif
|
||||||
if (value & 0x00800000)
|
if (value & 0x00800000) {
|
||||||
value |= 0xff000000;
|
value |= 0xff000000;
|
||||||
|
}
|
||||||
|
|
||||||
out[op] = value / max_value;
|
out[op] = value / max_value;
|
||||||
op = (op + 1) % nfft;
|
op = (op + 1) % nfft;
|
||||||
@@ -321,25 +313,25 @@ static void input_data_mixed_int24_max (const guint8 * _in, double* out, guint l
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static void input_data_mixed_int16_max (const guint8 * _in, double * out, guint len, double max_value, guint op, guint nfft) {
|
static void input_data_mixed_int16_max(const guint8 *_in, double *out, guint len, double max_value, guint op, guint nfft) {
|
||||||
|
|
||||||
guint j = 0, ip = 0;
|
|
||||||
const gint16 *in = reinterpret_cast<const gint16*>(_in);
|
const gint16 *in = reinterpret_cast<const gint16*>(_in);
|
||||||
|
guint ip = 0;
|
||||||
|
|
||||||
for (j = 0; j < len; j++) {
|
for (guint j = 0; j < len; j++) {
|
||||||
out[op] = in[ip++] / max_value;
|
out[op] = in[ip++] / max_value;
|
||||||
op = (op + 1) % nfft;
|
op = (op + 1) % nfft;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static gboolean gst_fastspectrum_setup (GstAudioFilter * base, const GstAudioInfo * info) {
|
static gboolean gst_fastspectrum_setup(GstAudioFilter *base, const GstAudioInfo *info) {
|
||||||
|
|
||||||
GstFastSpectrum *spectrum = reinterpret_cast<GstFastSpectrum*>(base);
|
GstFastSpectrum *spectrum = reinterpret_cast<GstFastSpectrum*>(base);
|
||||||
GstFastSpectrumInputData input_data = nullptr;
|
GstFastSpectrumInputData input_data = nullptr;
|
||||||
|
|
||||||
g_mutex_lock (&spectrum->lock);
|
g_mutex_lock(&spectrum->lock);
|
||||||
switch (GST_AUDIO_INFO_FORMAT (info)) {
|
switch (GST_AUDIO_INFO_FORMAT(info)) {
|
||||||
case GST_AUDIO_FORMAT_S16:
|
case GST_AUDIO_FORMAT_S16:
|
||||||
input_data = input_data_mixed_int16_max;
|
input_data = input_data_mixed_int16_max;
|
||||||
break;
|
break;
|
||||||
@@ -356,34 +348,33 @@ static gboolean gst_fastspectrum_setup (GstAudioFilter * base, const GstAudioInf
|
|||||||
input_data = input_data_mixed_double;
|
input_data = input_data_mixed_double;
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
g_assert_not_reached ();
|
g_assert_not_reached();
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
spectrum->input_data = input_data;
|
spectrum->input_data = input_data;
|
||||||
|
|
||||||
gst_fastspectrum_reset_state (spectrum);
|
gst_fastspectrum_reset_state(spectrum);
|
||||||
g_mutex_unlock (&spectrum->lock);
|
g_mutex_unlock(&spectrum->lock);
|
||||||
|
|
||||||
return TRUE;
|
return TRUE;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static void gst_fastspectrum_run_fft (GstFastSpectrum * spectrum, guint input_pos) {
|
static void gst_fastspectrum_run_fft(GstFastSpectrum *spectrum, guint input_pos) {
|
||||||
|
|
||||||
guint i = 0;
|
|
||||||
guint bands = spectrum->bands;
|
guint bands = spectrum->bands;
|
||||||
guint nfft = 2 * bands - 2;
|
guint nfft = 2 * bands - 2;
|
||||||
|
|
||||||
for (i = 0; i < nfft; i++)
|
for (guint i = 0; i < nfft; i++) {
|
||||||
spectrum->fft_input[i] = spectrum->input_ring_buffer[(input_pos + i) % nfft];
|
spectrum->fft_input[i] = spectrum->input_ring_buffer[(input_pos + i) % nfft];
|
||||||
|
}
|
||||||
|
|
||||||
// Should be safe to execute the same plan multiple times in parallel.
|
// Should be safe to execute the same plan multiple times in parallel.
|
||||||
fftw_execute(spectrum->plan);
|
fftw_execute(spectrum->plan);
|
||||||
|
|
||||||
gdouble val = 0.0;
|
// Calculate magnitude in db
|
||||||
/* Calculate magnitude in db */
|
for (guint i = 0; i < bands; i++) {
|
||||||
for (i = 0; i < bands; i++) {
|
gdouble val = spectrum->fft_output[i][0] * spectrum->fft_output[i][0];
|
||||||
val = spectrum->fft_output[i][0] * spectrum->fft_output[i][0];
|
|
||||||
val += spectrum->fft_output[i][1] * spectrum->fft_output[i][1];
|
val += spectrum->fft_output[i][1] * spectrum->fft_output[i][1];
|
||||||
val /= nfft * nfft;
|
val /= nfft * nfft;
|
||||||
spectrum->spect_magnitude[i] += val;
|
spectrum->spect_magnitude[i] += val;
|
||||||
@@ -391,83 +382,76 @@ static void gst_fastspectrum_run_fft (GstFastSpectrum * spectrum, guint input_po
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static GstFlowReturn gst_fastspectrum_transform_ip (GstBaseTransform *trans, GstBuffer *buffer) {
|
static GstFlowReturn gst_fastspectrum_transform_ip(GstBaseTransform *trans, GstBuffer *buffer) {
|
||||||
|
|
||||||
GstFastSpectrum *spectrum = reinterpret_cast<GstFastSpectrum*>(trans);
|
GstFastSpectrum *spectrum = reinterpret_cast<GstFastSpectrum*>(trans);
|
||||||
guint rate = GST_AUDIO_FILTER_RATE (spectrum);
|
guint rate = GST_AUDIO_FILTER_RATE(spectrum);
|
||||||
guint bps = GST_AUDIO_FILTER_BPS (spectrum);
|
guint bps = GST_AUDIO_FILTER_BPS(spectrum);
|
||||||
guint bpf = GST_AUDIO_FILTER_BPF (spectrum);
|
guint bpf = GST_AUDIO_FILTER_BPF(spectrum);
|
||||||
double max_value = (1UL << ((bps << 3) - 1)) - 1;
|
double max_value = static_cast<double>((1UL << ((bps << 3) - 1)) - 1);
|
||||||
guint bands = spectrum->bands;
|
guint bands = spectrum->bands;
|
||||||
guint nfft = 2 * bands - 2;
|
guint nfft = 2 * bands - 2;
|
||||||
guint input_pos = 0;
|
guint input_pos = 0;
|
||||||
GstMapInfo map;
|
GstMapInfo map;
|
||||||
const guint8 *data = nullptr;
|
const guint8 *data = nullptr;
|
||||||
gsize size = 0;
|
gsize size = 0;
|
||||||
guint fft_todo = 0, msg_todo = 0, block_size = 0;
|
|
||||||
gboolean have_full_interval = false;
|
|
||||||
GstFastSpectrumInputData input_data = nullptr;
|
GstFastSpectrumInputData input_data = nullptr;
|
||||||
|
|
||||||
g_mutex_lock (&spectrum->lock);
|
g_mutex_lock(&spectrum->lock);
|
||||||
gst_buffer_map (buffer, &map, GST_MAP_READ);
|
gst_buffer_map(buffer, &map, GST_MAP_READ);
|
||||||
data = map.data;
|
data = map.data;
|
||||||
size = map.size;
|
size = map.size;
|
||||||
|
|
||||||
GST_LOG_OBJECT (spectrum, "input size: %" G_GSIZE_FORMAT " bytes", size);
|
GST_LOG_OBJECT(spectrum, "input size: %" G_GSIZE_FORMAT " bytes", size);
|
||||||
|
|
||||||
if (GST_BUFFER_IS_DISCONT (buffer)) {
|
if (GST_BUFFER_IS_DISCONT(buffer)) {
|
||||||
GST_DEBUG_OBJECT (spectrum, "Discontinuity detected -- flushing");
|
GST_DEBUG_OBJECT(spectrum, "Discontinuity detected -- flushing");
|
||||||
gst_fastspectrum_flush (spectrum);
|
gst_fastspectrum_flush(spectrum);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* If we don't have a FFT context yet (or it was reset due to parameter
|
// If we don't have a FFT context yet (or it was reset due to parameter changes) get one and allocate memory for everything
|
||||||
* changes) get one and allocate memory for everything
|
|
||||||
*/
|
|
||||||
if (!spectrum->channel_data_initialized) {
|
if (!spectrum->channel_data_initialized) {
|
||||||
GST_DEBUG_OBJECT (spectrum, "allocating for bands %u", bands);
|
GST_DEBUG_OBJECT(spectrum, "allocating for bands %u", bands);
|
||||||
|
|
||||||
gst_fastspectrum_alloc_channel_data (spectrum);
|
gst_fastspectrum_alloc_channel_data(spectrum);
|
||||||
|
|
||||||
/* number of sample frames we process before posting a message
|
// Number of sample frames we process before posting a message interval is in ns
|
||||||
* interval is in ns */
|
spectrum->frames_per_interval = gst_util_uint64_scale(spectrum->interval, rate, GST_SECOND);
|
||||||
spectrum->frames_per_interval = gst_util_uint64_scale (spectrum->interval, rate, GST_SECOND);
|
|
||||||
spectrum->frames_todo = spectrum->frames_per_interval;
|
spectrum->frames_todo = spectrum->frames_per_interval;
|
||||||
/* rounding error for frames_per_interval in ns,
|
// Rounding error for frames_per_interval in ns, aggregated it in accumulated_error
|
||||||
* aggregated it in accumulated_error */
|
|
||||||
spectrum->error_per_interval = (spectrum->interval * rate) % GST_SECOND;
|
spectrum->error_per_interval = (spectrum->interval * rate) % GST_SECOND;
|
||||||
if (spectrum->frames_per_interval == 0)
|
if (spectrum->frames_per_interval == 0) {
|
||||||
spectrum->frames_per_interval = 1;
|
spectrum->frames_per_interval = 1;
|
||||||
|
}
|
||||||
|
|
||||||
GST_INFO_OBJECT (spectrum, "interval %" GST_TIME_FORMAT ", fpi %"
|
GST_INFO_OBJECT(spectrum, "interval %" GST_TIME_FORMAT ", fpi %" G_GUINT64_FORMAT ", error %" GST_TIME_FORMAT, GST_TIME_ARGS(spectrum->interval), spectrum->frames_per_interval, GST_TIME_ARGS(spectrum->error_per_interval));
|
||||||
G_GUINT64_FORMAT ", error %" GST_TIME_FORMAT,
|
|
||||||
GST_TIME_ARGS (spectrum->interval), spectrum->frames_per_interval,
|
|
||||||
GST_TIME_ARGS (spectrum->error_per_interval));
|
|
||||||
|
|
||||||
spectrum->input_pos = 0;
|
spectrum->input_pos = 0;
|
||||||
|
|
||||||
gst_fastspectrum_flush (spectrum);
|
gst_fastspectrum_flush(spectrum);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (spectrum->num_frames == 0)
|
if (spectrum->num_frames == 0) {
|
||||||
spectrum->message_ts = GST_BUFFER_TIMESTAMP (buffer);
|
spectrum->message_ts = GST_BUFFER_TIMESTAMP(buffer);
|
||||||
|
}
|
||||||
|
|
||||||
input_pos = spectrum->input_pos;
|
input_pos = spectrum->input_pos;
|
||||||
input_data = spectrum->input_data;
|
input_data = spectrum->input_data;
|
||||||
|
|
||||||
while (size >= bpf) {
|
while (size >= bpf) {
|
||||||
/* run input_data for a chunk of data */
|
// Run input_data for a chunk of data
|
||||||
fft_todo = nfft - (spectrum->num_frames % nfft);
|
guint fft_todo = nfft - (spectrum->num_frames % nfft);
|
||||||
msg_todo = spectrum->frames_todo - spectrum->num_frames;
|
guint msg_todo = spectrum->frames_todo - spectrum->num_frames;
|
||||||
GST_LOG_OBJECT (spectrum,
|
GST_LOG_OBJECT(spectrum, "message frames todo: %u, fft frames todo: %u, input frames %" G_GSIZE_FORMAT, msg_todo, fft_todo, (size / bpf));
|
||||||
"message frames todo: %u, fft frames todo: %u, input frames %"
|
guint block_size = msg_todo;
|
||||||
G_GSIZE_FORMAT, msg_todo, fft_todo, (size / bpf));
|
if (block_size > (size / bpf)) {
|
||||||
block_size = msg_todo;
|
|
||||||
if (block_size > (size / bpf))
|
|
||||||
block_size = (size / bpf);
|
block_size = (size / bpf);
|
||||||
if (block_size > fft_todo)
|
}
|
||||||
|
if (block_size > fft_todo) {
|
||||||
block_size = fft_todo;
|
block_size = fft_todo;
|
||||||
|
}
|
||||||
|
|
||||||
/* Move the current frames into our ringbuffers */
|
// Move the current frames into our ringbuffers
|
||||||
input_data(data, spectrum->input_ring_buffer, block_size, max_value, input_pos, nfft);
|
input_data(data, spectrum->input_ring_buffer, block_size, max_value, input_pos, nfft);
|
||||||
|
|
||||||
data += block_size * bpf;
|
data += block_size * bpf;
|
||||||
@@ -475,25 +459,19 @@ static GstFlowReturn gst_fastspectrum_transform_ip (GstBaseTransform *trans, Gst
|
|||||||
input_pos = (input_pos + block_size) % nfft;
|
input_pos = (input_pos + block_size) % nfft;
|
||||||
spectrum->num_frames += block_size;
|
spectrum->num_frames += block_size;
|
||||||
|
|
||||||
have_full_interval = (spectrum->num_frames == spectrum->frames_todo);
|
gboolean have_full_interval = (spectrum->num_frames == spectrum->frames_todo);
|
||||||
|
|
||||||
GST_LOG_OBJECT (spectrum,
|
GST_LOG_OBJECT(spectrum, "size: %" G_GSIZE_FORMAT ", do-fft = %d, do-message = %d", size, (spectrum->num_frames % nfft == 0), have_full_interval);
|
||||||
"size: %" G_GSIZE_FORMAT ", do-fft = %d, do-message = %d", size,
|
|
||||||
(spectrum->num_frames % nfft == 0), have_full_interval);
|
|
||||||
|
|
||||||
/* If we have enough frames for an FFT or we have all frames required for
|
// If we have enough frames for an FFT or we have all frames required for the interval and we haven't run a FFT, then run an FFT
|
||||||
* the interval and we haven't run a FFT, then run an FFT */
|
|
||||||
if ((spectrum->num_frames % nfft == 0) || (have_full_interval && !spectrum->num_fft)) {
|
if ((spectrum->num_frames % nfft == 0) || (have_full_interval && !spectrum->num_fft)) {
|
||||||
gst_fastspectrum_run_fft (spectrum, input_pos);
|
gst_fastspectrum_run_fft(spectrum, input_pos);
|
||||||
spectrum->num_fft++;
|
spectrum->num_fft++;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Do we have the FFTs for one interval? */
|
// Do we have the FFTs for one interval?
|
||||||
if (have_full_interval) {
|
if (have_full_interval) {
|
||||||
GST_DEBUG_OBJECT (spectrum, "nfft: %u frames: %" G_GUINT64_FORMAT
|
GST_DEBUG_OBJECT(spectrum, "nfft: %u frames: %" G_GUINT64_FORMAT " fpi: %" G_GUINT64_FORMAT " error: %" GST_TIME_FORMAT, nfft, spectrum->num_frames, spectrum->frames_per_interval, GST_TIME_ARGS(spectrum->accumulated_error));
|
||||||
" fpi: %" G_GUINT64_FORMAT " error: %" GST_TIME_FORMAT, nfft,
|
|
||||||
spectrum->num_frames, spectrum->frames_per_interval,
|
|
||||||
GST_TIME_ARGS (spectrum->accumulated_error));
|
|
||||||
|
|
||||||
spectrum->frames_todo = spectrum->frames_per_interval;
|
spectrum->frames_todo = spectrum->frames_per_interval;
|
||||||
if (spectrum->accumulated_error >= GST_SECOND) {
|
if (spectrum->accumulated_error >= GST_SECOND) {
|
||||||
@@ -505,17 +483,18 @@ static GstFlowReturn gst_fastspectrum_transform_ip (GstBaseTransform *trans, Gst
|
|||||||
if (spectrum->output_callback) {
|
if (spectrum->output_callback) {
|
||||||
// Calculate average
|
// Calculate average
|
||||||
for (guint i = 0; i < spectrum->bands; i++) {
|
for (guint i = 0; i < spectrum->bands; i++) {
|
||||||
spectrum->spect_magnitude[i] /= spectrum->num_fft;
|
spectrum->spect_magnitude[i] /= static_cast<double>(spectrum->num_fft);
|
||||||
}
|
}
|
||||||
|
|
||||||
spectrum->output_callback(spectrum->spect_magnitude, spectrum->bands);
|
spectrum->output_callback(spectrum->spect_magnitude, static_cast<int>(spectrum->bands));
|
||||||
|
|
||||||
// Reset spectrum accumulators
|
// Reset spectrum accumulators
|
||||||
memset(spectrum->spect_magnitude, 0, spectrum->bands * sizeof(double));
|
memset(spectrum->spect_magnitude, 0, spectrum->bands * sizeof(double));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (GST_CLOCK_TIME_IS_VALID (spectrum->message_ts))
|
if (GST_CLOCK_TIME_IS_VALID(spectrum->message_ts)) {
|
||||||
spectrum->message_ts += gst_util_uint64_scale (spectrum->num_frames, GST_SECOND, rate);
|
spectrum->message_ts += gst_util_uint64_scale(spectrum->num_frames, GST_SECOND, rate);
|
||||||
|
}
|
||||||
|
|
||||||
spectrum->num_frames = 0;
|
spectrum->num_frames = 0;
|
||||||
spectrum->num_fft = 0;
|
spectrum->num_fft = 0;
|
||||||
@@ -524,10 +503,10 @@ static GstFlowReturn gst_fastspectrum_transform_ip (GstBaseTransform *trans, Gst
|
|||||||
|
|
||||||
spectrum->input_pos = input_pos;
|
spectrum->input_pos = input_pos;
|
||||||
|
|
||||||
gst_buffer_unmap (buffer, &map);
|
gst_buffer_unmap(buffer, &map);
|
||||||
g_mutex_unlock (&spectrum->lock);
|
g_mutex_unlock(&spectrum->lock);
|
||||||
|
|
||||||
g_assert (size == 0);
|
g_assert(size == 0);
|
||||||
|
|
||||||
return GST_FLOW_OK;
|
return GST_FLOW_OK;
|
||||||
|
|
||||||
|
|||||||
@@ -38,38 +38,37 @@
|
|||||||
G_BEGIN_DECLS
|
G_BEGIN_DECLS
|
||||||
|
|
||||||
#define GST_TYPE_FASTSPECTRUM (gst_fastspectrum_get_type())
|
#define GST_TYPE_FASTSPECTRUM (gst_fastspectrum_get_type())
|
||||||
#define GST_FASTSPECTRUM(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj),GST_TYPE_FASTSPECTRUM,GstFastSpectrum))
|
#define GST_FASTSPECTRUM(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj), GST_TYPE_FASTSPECTRUM, GstFastSpectrum))
|
||||||
#define GST_IS_FASTSPECTRUM(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj),GST_TYPE_FASTSPECTRUM))
|
#define GST_IS_FASTSPECTRUM(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj), GST_TYPE_FASTSPECTRUM))
|
||||||
#define GST_FASTSPECTRUM_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST((klass), GST_TYPE_FASTSPECTRUM,GstFastSpectrumClass))
|
#define GST_FASTSPECTRUM_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST((klass), GST_TYPE_FASTSPECTRUM, GstFastSpectrumClass))
|
||||||
#define GST_IS_FASTSPECTRUM_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass), GST_TYPE_FASTSPECTRUM))
|
#define GST_IS_FASTSPECTRUM_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass), GST_TYPE_FASTSPECTRUM))
|
||||||
|
|
||||||
class QMutex;
|
class QMutex;
|
||||||
|
|
||||||
typedef void (*GstFastSpectrumInputData)(const guint8* in, double* out, guint len, double max_value, guint op, guint nfft);
|
typedef void (*GstFastSpectrumInputData)(const guint8 *in, double *out, guint len, double max_value, guint op, guint nfft);
|
||||||
|
|
||||||
typedef std::function<void(double* magnitudes, int size)> OutputCallback;
|
typedef std::function<void(double *magnitudes, int size)> OutputCallback;
|
||||||
|
|
||||||
struct GstFastSpectrum {
|
struct GstFastSpectrum {
|
||||||
GstAudioFilter parent;
|
GstAudioFilter parent;
|
||||||
|
|
||||||
/* properties */
|
// Properties
|
||||||
guint64 interval; /* how many nanoseconds between emits */
|
guint64 interval; // How many nanoseconds between emits
|
||||||
guint64 frames_per_interval; /* how many frames per interval */
|
guint64 frames_per_interval; // How many frames per interval
|
||||||
guint64 frames_todo;
|
guint64 frames_todo;
|
||||||
guint bands; /* number of spectrum bands */
|
guint bands; // Number of spectrum bands
|
||||||
gboolean multi_channel; /* send separate channel results */
|
gboolean multi_channel; // Send separate channel results
|
||||||
|
|
||||||
guint64 num_frames; /* frame count (1 sample per channel)
|
guint64 num_frames; // Frame count (1 sample per channel) since last emit
|
||||||
* since last emit */
|
guint64 num_fft; // Number of FFTs since last emit
|
||||||
guint64 num_fft; /* number of FFTs since last emit */
|
GstClockTime message_ts; // Starttime for next message
|
||||||
GstClockTime message_ts; /* starttime for next message */
|
|
||||||
|
|
||||||
/* <private> */
|
// <private>
|
||||||
bool channel_data_initialized;
|
bool channel_data_initialized;
|
||||||
double* input_ring_buffer;
|
double *input_ring_buffer;
|
||||||
double* fft_input;
|
double *fft_input;
|
||||||
fftw_complex* fft_output;
|
fftw_complex *fft_output;
|
||||||
double* spect_magnitude;
|
double *spect_magnitude;
|
||||||
fftw_plan plan;
|
fftw_plan plan;
|
||||||
|
|
||||||
guint input_pos;
|
guint input_pos;
|
||||||
@@ -87,11 +86,11 @@ struct GstFastSpectrumClass {
|
|||||||
GstAudioFilterClass parent_class;
|
GstAudioFilterClass parent_class;
|
||||||
|
|
||||||
// Static lock for creating & destroying FFTW plans.
|
// Static lock for creating & destroying FFTW plans.
|
||||||
QMutex* fftw_lock;
|
QMutex *fftw_lock;
|
||||||
};
|
};
|
||||||
|
|
||||||
GType gst_fastspectrum_get_type (void);
|
GType gst_fastspectrum_get_type(void);
|
||||||
|
|
||||||
G_END_DECLS
|
G_END_DECLS
|
||||||
|
|
||||||
#endif // GST_MOODBAR_FASTSPECTRUM_H
|
#endif // GST_MOODBAR_FASTSPECTRUM_H
|
||||||
|
|||||||
@@ -23,14 +23,13 @@
|
|||||||
|
|
||||||
namespace {
|
namespace {
|
||||||
|
|
||||||
static gboolean gst_moodbar_plugin_init(GstPlugin* plugin) {
|
static gboolean gst_moodbar_plugin_init(GstPlugin *plugin) {
|
||||||
|
|
||||||
if (!gst_element_register(plugin, "fastspectrum", GST_RANK_NONE, GST_TYPE_FASTSPECTRUM)) {
|
if (!gst_element_register(plugin, "fastspectrum", GST_RANK_NONE, GST_TYPE_FASTSPECTRUM)) {
|
||||||
return FALSE;
|
return FALSE;
|
||||||
}
|
}
|
||||||
|
|
||||||
return TRUE;
|
return TRUE;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|||||||
@@ -19,7 +19,7 @@
|
|||||||
#define GST_MOODBAR_PLUGIN_H
|
#define GST_MOODBAR_PLUGIN_H
|
||||||
|
|
||||||
extern "C" {
|
extern "C" {
|
||||||
int gstfastspectrum_register_static();
|
int gstfastspectrum_register_static();
|
||||||
}
|
}
|
||||||
|
|
||||||
#endif // GST_MOODBAR_PLUGIN_H
|
#endif // GST_MOODBAR_PLUGIN_H
|
||||||
|
|||||||
@@ -8,38 +8,29 @@ set(SOURCES
|
|||||||
)
|
)
|
||||||
|
|
||||||
set(HEADERS
|
set(HEADERS
|
||||||
|
core/logging.h
|
||||||
core/messagehandler.h
|
core/messagehandler.h
|
||||||
core/messagereply.h
|
core/messagereply.h
|
||||||
core/workerpool.h
|
core/workerpool.h
|
||||||
)
|
)
|
||||||
|
|
||||||
if(APPLE)
|
qt_wrap_cpp(MOC ${HEADERS})
|
||||||
list(APPEND SOURCES core/scoped_nsautorelease_pool.mm)
|
|
||||||
endif(APPLE)
|
|
||||||
|
|
||||||
if(BUILD_WITH_QT6)
|
link_directories(${GLIB_LIBRARY_DIRS})
|
||||||
qt6_wrap_cpp(MOC ${HEADERS})
|
|
||||||
else()
|
|
||||||
qt5_wrap_cpp(MOC ${HEADERS})
|
|
||||||
endif()
|
|
||||||
|
|
||||||
link_directories(
|
|
||||||
${GLIB_LIBRARY_DIRS}
|
|
||||||
)
|
|
||||||
|
|
||||||
add_library(libstrawberry-common STATIC ${SOURCES} ${MOC})
|
add_library(libstrawberry-common STATIC ${SOURCES} ${MOC})
|
||||||
|
|
||||||
target_include_directories(libstrawberry-common SYSTEM PRIVATE
|
target_include_directories(libstrawberry-common SYSTEM PRIVATE ${GLIB_INCLUDE_DIRS})
|
||||||
${GLIB_INCLUDE_DIRS}
|
|
||||||
)
|
|
||||||
|
|
||||||
target_include_directories(libstrawberry-common PRIVATE
|
target_include_directories(libstrawberry-common PRIVATE
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}
|
${CMAKE_CURRENT_SOURCE_DIR}
|
||||||
${CMAKE_SOURCE_DIR}
|
${CMAKE_SOURCE_DIR}
|
||||||
${CMAKE_SOURCE_DIR}/src
|
|
||||||
${CMAKE_BINARY_DIR}/src
|
${CMAKE_BINARY_DIR}/src
|
||||||
)
|
)
|
||||||
|
|
||||||
|
if(Backtrace_FOUND)
|
||||||
|
target_include_directories(libstrawberry-common SYSTEM PRIVATE ${Backtrace_INCLUDE_DIRS})
|
||||||
|
endif()
|
||||||
|
|
||||||
target_link_libraries(libstrawberry-common PRIVATE
|
target_link_libraries(libstrawberry-common PRIVATE
|
||||||
${CMAKE_THREAD_LIBS_INIT}
|
${CMAKE_THREAD_LIBS_INIT}
|
||||||
${GLIB_LIBRARIES}
|
${GLIB_LIBRARIES}
|
||||||
@@ -48,6 +39,5 @@ target_link_libraries(libstrawberry-common PRIVATE
|
|||||||
)
|
)
|
||||||
|
|
||||||
if(Backtrace_FOUND)
|
if(Backtrace_FOUND)
|
||||||
target_include_directories(libstrawberry-common PRIVATE ${Backtrace_INCLUDE_DIRS})
|
|
||||||
target_link_libraries(libstrawberry-common PRIVATE ${Backtrace_LIBRARIES})
|
target_link_libraries(libstrawberry-common PRIVATE ${Backtrace_LIBRARIES})
|
||||||
endif(Backtrace_FOUND)
|
endif()
|
||||||
|
|||||||
@@ -24,7 +24,9 @@
|
|||||||
#include <cstring>
|
#include <cstring>
|
||||||
#include <iostream>
|
#include <iostream>
|
||||||
#include <memory>
|
#include <memory>
|
||||||
#include <cxxabi.h>
|
#ifndef _MSC_VER
|
||||||
|
# include <cxxabi.h>
|
||||||
|
#endif
|
||||||
#include <glib.h>
|
#include <glib.h>
|
||||||
|
|
||||||
#ifdef HAVE_BACKTRACE
|
#ifdef HAVE_BACKTRACE
|
||||||
@@ -50,18 +52,17 @@
|
|||||||
namespace logging {
|
namespace logging {
|
||||||
|
|
||||||
static Level sDefaultLevel = Level_Debug;
|
static Level sDefaultLevel = Level_Debug;
|
||||||
static QMap<QString, Level>* sClassLevels = nullptr;
|
static QMap<QString, Level> *sClassLevels = nullptr;
|
||||||
static QIODevice *sNullDevice = nullptr;
|
static QIODevice *sNullDevice = nullptr;
|
||||||
|
|
||||||
//const char* kDefaultLogLevels = "*:3";
|
const char *kDefaultLogLevels = "*:3";
|
||||||
const char* kDefaultLogLevels = "GstEnginePipeline:2,*:3";
|
|
||||||
|
|
||||||
static const char *kMessageHandlerMagic = "__logging_message__";
|
static const char *kMessageHandlerMagic = "__logging_message__";
|
||||||
static const int kMessageHandlerMagicLength = strlen(kMessageHandlerMagic);
|
static const size_t kMessageHandlerMagicLength = strlen(kMessageHandlerMagic);
|
||||||
static QtMessageHandler sOriginalMessageHandler = nullptr;
|
static QtMessageHandler sOriginalMessageHandler = nullptr;
|
||||||
|
|
||||||
template <class T>
|
template<class T>
|
||||||
static T CreateLogger(Level level, const QString& class_name, int line, const char* category);
|
static T CreateLogger(Level level, const QString &class_name, int line, const char *category);
|
||||||
|
|
||||||
void GLog(const char *domain, int level, const char *message, void*) {
|
void GLog(const char *domain, int level, const char *message, void*) {
|
||||||
|
|
||||||
@@ -87,19 +88,19 @@ void GLog(const char *domain, int level, const char *message, void*) {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
template <class T>
|
template<class T>
|
||||||
class DebugBase : public QDebug {
|
class DebugBase : public QDebug {
|
||||||
public:
|
public:
|
||||||
DebugBase() : QDebug(sNullDevice) {}
|
DebugBase() : QDebug(sNullDevice) {}
|
||||||
explicit DebugBase(QtMsgType t) : QDebug(t) {}
|
explicit DebugBase(QtMsgType t) : QDebug(t) {}
|
||||||
T& space() { return static_cast<T&>(QDebug::space()); }
|
T &space() { return static_cast<T&>(QDebug::space()); }
|
||||||
T& noSpace() { return static_cast<T&>(QDebug::nospace()); }
|
T &nospace() { return static_cast<T&>(QDebug::nospace()); }
|
||||||
};
|
};
|
||||||
|
|
||||||
// Debug message will be stored in a buffer.
|
// Debug message will be stored in a buffer.
|
||||||
class BufferedDebug : public DebugBase<BufferedDebug> {
|
class BufferedDebug : public DebugBase<BufferedDebug> {
|
||||||
public:
|
public:
|
||||||
BufferedDebug() {}
|
BufferedDebug() = default;
|
||||||
explicit BufferedDebug(QtMsgType) : buf_(new QBuffer, later_deleter) {
|
explicit BufferedDebug(QtMsgType) : buf_(new QBuffer, later_deleter) {
|
||||||
buf_->open(QIODevice::WriteOnly);
|
buf_->open(QIODevice::WriteOnly);
|
||||||
|
|
||||||
@@ -110,7 +111,7 @@ class BufferedDebug : public DebugBase<BufferedDebug> {
|
|||||||
|
|
||||||
// Delete function for the buffer. Since a base class is holding a reference to the raw pointer,
|
// Delete function for the buffer. Since a base class is holding a reference to the raw pointer,
|
||||||
// it shouldn't be deleted until after the deletion of this object is complete.
|
// it shouldn't be deleted until after the deletion of this object is complete.
|
||||||
static void later_deleter(QBuffer* b) { b->deleteLater(); }
|
static void later_deleter(QBuffer *b) { b->deleteLater(); }
|
||||||
|
|
||||||
std::shared_ptr<QBuffer> buf_;
|
std::shared_ptr<QBuffer> buf_;
|
||||||
};
|
};
|
||||||
@@ -118,14 +119,15 @@ class BufferedDebug : public DebugBase<BufferedDebug> {
|
|||||||
// Debug message will be logged immediately.
|
// Debug message will be logged immediately.
|
||||||
class LoggedDebug : public DebugBase<LoggedDebug> {
|
class LoggedDebug : public DebugBase<LoggedDebug> {
|
||||||
public:
|
public:
|
||||||
LoggedDebug() {}
|
LoggedDebug() = default;
|
||||||
explicit LoggedDebug(QtMsgType t) : DebugBase(t) { nospace() << kMessageHandlerMagic; }
|
explicit LoggedDebug(QtMsgType t) : DebugBase(t) { nospace() << kMessageHandlerMagic; }
|
||||||
};
|
};
|
||||||
|
|
||||||
static void MessageHandler(QtMsgType type, const QMessageLogContext&, const QString &message) {
|
static void MessageHandler(QtMsgType type, const QMessageLogContext&, const QString &message) {
|
||||||
|
|
||||||
if (strncmp(kMessageHandlerMagic, message.toLocal8Bit().data(), kMessageHandlerMagicLength) == 0) {
|
if (message.startsWith(kMessageHandlerMagic)) {
|
||||||
fprintf(stderr, "%s\n", message.toLocal8Bit().data() + kMessageHandlerMagicLength);
|
fprintf(type == QtCriticalMsg || type == QtFatalMsg ? stderr : stdout, "%s\n", message.toUtf8().data() + kMessageHandlerMagicLength);
|
||||||
|
fflush(type == QtCriticalMsg || type == QtFatalMsg ? stderr : stdout);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -144,12 +146,13 @@ static void MessageHandler(QtMsgType type, const QMessageLogContext&, const QStr
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
for (const QString& line : message.split('\n')) {
|
for (const QString &line : message.split('\n')) {
|
||||||
BufferedDebug d = CreateLogger<BufferedDebug>(level, "unknown", -1, nullptr);
|
BufferedDebug d = CreateLogger<BufferedDebug>(level, "unknown", -1, nullptr);
|
||||||
d << line.toLocal8Bit().constData();
|
d << line.toLocal8Bit().constData();
|
||||||
if (d.buf_) {
|
if (d.buf_) {
|
||||||
d.buf_->close();
|
d.buf_->close();
|
||||||
fprintf(stderr, "%s\n", d.buf_->buffer().data());
|
fprintf(type == QtCriticalMsg || type == QtFatalMsg ? stderr : stdout, "%s\n", d.buf_->buffer().data());
|
||||||
|
fflush(type == QtCriticalMsg || type == QtFatalMsg ? stderr : stdout);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -172,13 +175,14 @@ void Init() {
|
|||||||
if (!sOriginalMessageHandler) {
|
if (!sOriginalMessageHandler) {
|
||||||
sOriginalMessageHandler = qInstallMessageHandler(MessageHandler);
|
sOriginalMessageHandler = qInstallMessageHandler(MessageHandler);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void SetLevels(const QString &levels) {
|
void SetLevels(const QString &levels) {
|
||||||
|
|
||||||
if (!sClassLevels) return;
|
if (!sClassLevels) return;
|
||||||
|
|
||||||
for (const QString& item : levels.split(',')) {
|
for (const QString &item : levels.split(',')) {
|
||||||
const QStringList class_level = item.split(':');
|
const QStringList class_level = item.split(':');
|
||||||
|
|
||||||
QString class_name;
|
QString class_name;
|
||||||
@@ -211,9 +215,9 @@ static QString ParsePrettyFunction(const char *pretty_function) {
|
|||||||
|
|
||||||
// Get the class name out of the function name.
|
// Get the class name out of the function name.
|
||||||
QString class_name = pretty_function;
|
QString class_name = pretty_function;
|
||||||
const int paren = class_name.indexOf('(');
|
const qint64 paren = class_name.indexOf('(');
|
||||||
if (paren != -1) {
|
if (paren != -1) {
|
||||||
const int colons = class_name.lastIndexOf("::", paren);
|
const qint64 colons = class_name.lastIndexOf("::", paren);
|
||||||
if (colons != -1) {
|
if (colons != -1) {
|
||||||
class_name = class_name.left(colons);
|
class_name = class_name.left(colons);
|
||||||
}
|
}
|
||||||
@@ -222,16 +226,17 @@ static QString ParsePrettyFunction(const char *pretty_function) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const int space = class_name.lastIndexOf(' ');
|
const qint64 space = class_name.lastIndexOf(' ');
|
||||||
if (space != -1) {
|
if (space != -1) {
|
||||||
class_name = class_name.mid(space+1);
|
class_name = class_name.mid(space + 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
return class_name;
|
return class_name;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
template <class T>
|
template <class T>
|
||||||
static T CreateLogger(Level level, const QString &class_name, int line, const char* category) {
|
static T CreateLogger(Level level, const QString &class_name, int line, const char *category) {
|
||||||
|
|
||||||
// Map the level to a string
|
// Map the level to a string
|
||||||
const char *level_name = nullptr;
|
const char *level_name = nullptr;
|
||||||
@@ -268,19 +273,18 @@ static T CreateLogger(Level level, const QString &class_name, int line, const ch
|
|||||||
}
|
}
|
||||||
|
|
||||||
T ret(type);
|
T ret(type);
|
||||||
ret.nospace() << QDateTime::currentDateTime().toString("hh:mm:ss.zzz").toLatin1().constData()
|
ret.nospace() << QDateTime::currentDateTime().toString("hh:mm:ss.zzz").toLatin1().constData() << level_name << function_line.leftJustified(32).toLatin1().constData();
|
||||||
<< level_name
|
|
||||||
<< function_line.leftJustified(32).toLatin1().constData();
|
|
||||||
|
|
||||||
return ret.space();
|
return ret.space();
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#ifdef Q_OS_UNIX
|
||||||
QString CXXDemangle(const QString &mangled_function);
|
QString CXXDemangle(const QString &mangled_function);
|
||||||
|
|
||||||
QString CXXDemangle(const QString &mangled_function) {
|
QString CXXDemangle(const QString &mangled_function) {
|
||||||
|
|
||||||
int status = 0;
|
int status = 0;
|
||||||
char* demangled_function = abi::__cxa_demangle(mangled_function.toLatin1().constData(), nullptr, nullptr, &status);
|
char *demangled_function = abi::__cxa_demangle(mangled_function.toLatin1().constData(), nullptr, nullptr, &status);
|
||||||
if (status == 0) {
|
if (status == 0) {
|
||||||
QString ret = QString::fromLatin1(demangled_function);
|
QString ret = QString::fromLatin1(demangled_function);
|
||||||
free(demangled_function);
|
free(demangled_function);
|
||||||
@@ -289,23 +293,10 @@ QString CXXDemangle(const QString &mangled_function) {
|
|||||||
return mangled_function; // Probably not a C++ function.
|
return mangled_function; // Probably not a C++ function.
|
||||||
|
|
||||||
}
|
}
|
||||||
|
#endif // Q_OS_UNIX
|
||||||
|
|
||||||
QString DarwinDemangle(const QString &symbol);
|
#ifdef Q_OS_LINUX
|
||||||
|
|
||||||
QString DarwinDemangle(const QString &symbol) {
|
|
||||||
|
|
||||||
#if QT_VERSION >= QT_VERSION_CHECK(5, 14, 0)
|
|
||||||
QStringList split = symbol.split(' ', Qt::SkipEmptyParts);
|
|
||||||
#else
|
|
||||||
QStringList split = symbol.split(' ', QString::SkipEmptyParts);
|
|
||||||
#endif
|
|
||||||
QString mangled_function = split[3];
|
|
||||||
return CXXDemangle(mangled_function);
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
QString LinuxDemangle(const QString &symbol);
|
QString LinuxDemangle(const QString &symbol);
|
||||||
|
|
||||||
QString LinuxDemangle(const QString &symbol) {
|
QString LinuxDemangle(const QString &symbol) {
|
||||||
|
|
||||||
QRegularExpression regex("\\(([^+]+)");
|
QRegularExpression regex("\\(([^+]+)");
|
||||||
@@ -317,10 +308,26 @@ QString LinuxDemangle(const QString &symbol) {
|
|||||||
return CXXDemangle(mangled_function);
|
return CXXDemangle(mangled_function);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
#endif // Q_OS_LINUX
|
||||||
|
|
||||||
|
#ifdef Q_OS_MACOS
|
||||||
|
QString DarwinDemangle(const QString &symbol);
|
||||||
|
QString DarwinDemangle(const QString &symbol) {
|
||||||
|
|
||||||
|
# if QT_VERSION >= QT_VERSION_CHECK(5, 14, 0)
|
||||||
|
QStringList split = symbol.split(' ', Qt::SkipEmptyParts);
|
||||||
|
# else
|
||||||
|
QStringList split = symbol.split(' ', QString::SkipEmptyParts);
|
||||||
|
# endif
|
||||||
|
QString mangled_function = split[3];
|
||||||
|
return CXXDemangle(mangled_function);
|
||||||
|
|
||||||
|
}
|
||||||
|
#endif // Q_OS_MACOS
|
||||||
|
|
||||||
QString DemangleSymbol(const QString &symbol);
|
QString DemangleSymbol(const QString &symbol);
|
||||||
|
|
||||||
QString DemangleSymbol(const QString &symbol) {
|
QString DemangleSymbol(const QString &symbol) {
|
||||||
|
|
||||||
#ifdef Q_OS_MACOS
|
#ifdef Q_OS_MACOS
|
||||||
return DarwinDemangle(symbol);
|
return DarwinDemangle(symbol);
|
||||||
#elif defined(Q_OS_LINUX)
|
#elif defined(Q_OS_LINUX)
|
||||||
@@ -328,13 +335,15 @@ QString DemangleSymbol(const QString &symbol) {
|
|||||||
#else
|
#else
|
||||||
return symbol;
|
return symbol;
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void DumpStackTrace() {
|
void DumpStackTrace() {
|
||||||
|
|
||||||
#ifdef HAVE_BACKTRACE
|
#ifdef HAVE_BACKTRACE
|
||||||
void* callstack[128];
|
void *callstack[128];
|
||||||
int callstack_size = backtrace(reinterpret_cast<void**>(&callstack), sizeof(callstack));
|
int callstack_size = backtrace(reinterpret_cast<void**>(&callstack), sizeof(callstack));
|
||||||
char** symbols = backtrace_symbols(reinterpret_cast<void**>(&callstack), callstack_size);
|
char **symbols = backtrace_symbols(reinterpret_cast<void**>(&callstack), callstack_size);
|
||||||
// Start from 1 to skip ourself.
|
// Start from 1 to skip ourself.
|
||||||
for (int i = 1; i < callstack_size; ++i) {
|
for (int i = 1; i < callstack_size; ++i) {
|
||||||
std::cerr << DemangleSymbol(QString::fromLatin1(symbols[i])).toStdString() << std::endl;
|
std::cerr << DemangleSymbol(QString::fromLatin1(symbols[i])).toStdString() << std::endl;
|
||||||
@@ -343,35 +352,35 @@ void DumpStackTrace() {
|
|||||||
#else
|
#else
|
||||||
qLog(Debug) << "FIXME: Implement printing stack traces on this platform";
|
qLog(Debug) << "FIXME: Implement printing stack traces on this platform";
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// These are the functions that create loggers for the rest of Clementine.
|
// These are the functions that create loggers for the rest of Strawberry.
|
||||||
// It's okay that the LoggedDebug instance is copied to a QDebug in these. It
|
// It's okay that the LoggedDebug instance is copied to a QDebug in these. It doesn't override any behavior that should be needed after return.
|
||||||
// doesn't override any behavior that should be needed after return.
|
|
||||||
#define qCreateLogger(line, pretty_function, category, level) logging::CreateLogger<LoggedDebug>(logging::Level_##level, logging::ParsePrettyFunction(pretty_function), line, category)
|
#define qCreateLogger(line, pretty_function, category, level) logging::CreateLogger<LoggedDebug>(logging::Level_##level, logging::ParsePrettyFunction(pretty_function), line, category)
|
||||||
|
|
||||||
QDebug CreateLoggerInfo(int line, const char *pretty_function, const char* category) { return qCreateLogger(line, pretty_function, category, Info); }
|
QDebug CreateLoggerInfo(int line, const char *pretty_function, const char *category) { return qCreateLogger(line, pretty_function, category, Info); }
|
||||||
QDebug CreateLoggerFatal(int line, const char *pretty_function, const char* category) { return qCreateLogger(line, pretty_function, category, Fatal); }
|
QDebug CreateLoggerFatal(int line, const char *pretty_function, const char *category) { return qCreateLogger(line, pretty_function, category, Fatal); }
|
||||||
QDebug CreateLoggerError(int line, const char *pretty_function, const char* category) { return qCreateLogger(line, pretty_function, category, Error); }
|
QDebug CreateLoggerError(int line, const char *pretty_function, const char *category) { return qCreateLogger(line, pretty_function, category, Error); }
|
||||||
|
|
||||||
#ifdef QT_NO_WARNING_OUTPUT
|
#ifdef QT_NO_WARNING_OUTPUT
|
||||||
QNoDebug CreateLoggerWarning(int, const char*, const char*) { return QNoDebug(); }
|
QNoDebug CreateLoggerWarning(int, const char*, const char*) { return QNoDebug(); }
|
||||||
#else
|
#else
|
||||||
QDebug CreateLoggerWarning(int line, const char *pretty_function, const char* category) { return qCreateLogger(line, pretty_function, category, Warning); }
|
QDebug CreateLoggerWarning(int line, const char *pretty_function, const char *category) { return qCreateLogger(line, pretty_function, category, Warning); }
|
||||||
#endif // QT_NO_WARNING_OUTPUT
|
#endif // QT_NO_WARNING_OUTPUT
|
||||||
|
|
||||||
#ifdef QT_NO_DEBUG_OUTPUT
|
#ifdef QT_NO_DEBUG_OUTPUT
|
||||||
QNoDebug CreateLoggerDebug(int, const char*, const char*) { return QNoDebug(); }
|
QNoDebug CreateLoggerDebug(int, const char*, const char*) { return QNoDebug(); }
|
||||||
#else
|
#else
|
||||||
QDebug CreateLoggerDebug(int line, const char *pretty_function, const char* category) { return qCreateLogger(line, pretty_function, category, Debug); }
|
QDebug CreateLoggerDebug(int line, const char *pretty_function, const char *category) { return qCreateLogger(line, pretty_function, category, Debug); }
|
||||||
#endif // QT_NO_DEBUG_OUTPUT
|
#endif // QT_NO_DEBUG_OUTPUT
|
||||||
|
|
||||||
} // namespace logging
|
} // namespace logging
|
||||||
|
|
||||||
namespace {
|
namespace {
|
||||||
|
|
||||||
template <typename T>
|
template<typename T>
|
||||||
QString print_duration(T duration, const std::string& unit) {
|
QString print_duration(T duration, const std::string &unit) {
|
||||||
return QString("%1%2").arg(duration.count()).arg(unit.c_str());
|
return QString("%1%2").arg(duration.count()).arg(unit.c_str());
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -381,4 +390,3 @@ QDebug operator<<(QDebug dbg, std::chrono::seconds secs) {
|
|||||||
dbg.nospace() << print_duration(secs, "s");
|
dbg.nospace() << print_duration(secs, "s");
|
||||||
return dbg.space();
|
return dbg.space();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -29,18 +29,31 @@
|
|||||||
# define qLog(level) while (false) QNoDebug()
|
# define qLog(level) while (false) QNoDebug()
|
||||||
# define qLogCat(level, category) while (false) QNoDebug()
|
# define qLogCat(level, category) while (false) QNoDebug()
|
||||||
#else
|
#else
|
||||||
# define qLog(level) logging::CreateLogger##level(__LINE__, __PRETTY_FUNCTION__, nullptr)
|
# ifdef _MSC_VER
|
||||||
|
# define qLog(level) logging::CreateLogger##level(__LINE__, __FUNCSIG__, nullptr)
|
||||||
|
# else
|
||||||
|
# define qLog(level) logging::CreateLogger##level(__LINE__, __PRETTY_FUNCTION__, nullptr)
|
||||||
|
# endif // _MSC_VER
|
||||||
|
|
||||||
// This macro specifies a separate category for message filtering.
|
// This macro specifies a separate category for message filtering.
|
||||||
// The default qLog will use the class name extracted from the function name for this purpose.
|
// The default qLog will use the class name extracted from the function name for this purpose.
|
||||||
// The category is also printed in the message along with the class name.
|
// The category is also printed in the message along with the class name.
|
||||||
# define qLogCat(level, category) logging::CreateLogger##level(__LINE__, __PRETTY_FUNCTION__, category)
|
# ifdef _MSC_VER
|
||||||
|
# define qLogCat(level, category) logging::CreateLogger##level(__LINE__, __FUNCSIG__, category)
|
||||||
|
# else
|
||||||
|
# define qLogCat(level, category) logging::CreateLogger##level(__LINE__, __PRETTY_FUNCTION__, category)
|
||||||
|
# endif // _MSC_VER
|
||||||
|
|
||||||
#endif // QT_NO_DEBUG_STREAM
|
#endif // QT_NO_DEBUG_STREAM
|
||||||
|
|
||||||
namespace logging {
|
namespace logging {
|
||||||
|
|
||||||
class NullDevice : public QIODevice {
|
class NullDevice : public QIODevice {
|
||||||
|
Q_OBJECT
|
||||||
|
|
||||||
|
public:
|
||||||
|
NullDevice(QObject *parent = nullptr) : QIODevice(parent) {}
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
qint64 readData(char*, qint64) override { return -1; }
|
qint64 readData(char*, qint64) override { return -1; }
|
||||||
qint64 writeData(const char*, qint64 len) override { return len; }
|
qint64 writeData(const char*, qint64 len) override { return len; }
|
||||||
@@ -55,28 +68,27 @@ enum Level {
|
|||||||
};
|
};
|
||||||
|
|
||||||
void Init();
|
void Init();
|
||||||
void SetLevels(const QString& levels);
|
void SetLevels(const QString &levels);
|
||||||
|
|
||||||
void DumpStackTrace();
|
void DumpStackTrace();
|
||||||
|
|
||||||
QDebug CreateLoggerInfo(int line, const char *pretty_function, const char* category);
|
QDebug CreateLoggerInfo(int line, const char *pretty_function, const char *category);
|
||||||
QDebug CreateLoggerFatal(int line, const char *pretty_function, const char* category);
|
QDebug CreateLoggerFatal(int line, const char *pretty_function, const char *category);
|
||||||
QDebug CreateLoggerError(int line, const char *pretty_function, const char* category);
|
QDebug CreateLoggerError(int line, const char *pretty_function, const char *category);
|
||||||
|
|
||||||
#ifdef QT_NO_WARNING_OUTPUT
|
#ifdef QT_NO_WARNING_OUTPUT
|
||||||
QNoDebug CreateLoggerWarning(int, const char*, const char*);
|
QNoDebug CreateLoggerWarning(int, const char*, const char*);
|
||||||
#else
|
#else
|
||||||
QDebug CreateLoggerWarning(int line, const char *pretty_function, const char* category);
|
QDebug CreateLoggerWarning(int line, const char *pretty_function, const char *category);
|
||||||
#endif // QT_NO_WARNING_OUTPUT
|
#endif // QT_NO_WARNING_OUTPUT
|
||||||
|
|
||||||
#ifdef QT_NO_DEBUG_OUTPUT
|
#ifdef QT_NO_DEBUG_OUTPUT
|
||||||
QNoDebug CreateLoggerDebug(int, const char*, const char*);
|
QNoDebug CreateLoggerDebug(int, const char*, const char*);
|
||||||
#else
|
#else
|
||||||
QDebug CreateLoggerDebug(int line, const char *pretty_function, const char* category);
|
QDebug CreateLoggerDebug(int line, const char *pretty_function, const char *category);
|
||||||
#endif // QT_NO_DEBUG_OUTPUT
|
#endif // QT_NO_DEBUG_OUTPUT
|
||||||
|
|
||||||
|
void GLog(const char *domain, int level, const char *message, void *user_data);
|
||||||
void GLog(const char* domain, int level, const char* message, void* user_data);
|
|
||||||
|
|
||||||
extern const char *kDefaultLogLevels;
|
extern const char *kDefaultLogLevels;
|
||||||
|
|
||||||
|
|||||||
@@ -64,7 +64,7 @@ void _MessageHandlerBase::SetDevice(QIODevice *device) {
|
|||||||
|
|
||||||
void _MessageHandlerBase::DeviceReadyRead() {
|
void _MessageHandlerBase::DeviceReadyRead() {
|
||||||
|
|
||||||
while (device_->bytesAvailable()) {
|
while (device_->bytesAvailable() > 0) {
|
||||||
if (!reading_protobuf_) {
|
if (!reading_protobuf_) {
|
||||||
// Read the length of the next message
|
// Read the length of the next message
|
||||||
QDataStream s(device_);
|
QDataStream s(device_);
|
||||||
@@ -99,7 +99,7 @@ void _MessageHandlerBase::WriteMessage(const QByteArray &data) {
|
|||||||
|
|
||||||
QDataStream s(device_);
|
QDataStream s(device_);
|
||||||
s << quint32(data.length());
|
s << quint32(data.length());
|
||||||
s.writeRawData(data.data(), data.length());
|
s.writeRawData(data.data(), static_cast<int>(data.length()));
|
||||||
|
|
||||||
// Sorry.
|
// Sorry.
|
||||||
if (flush_abstract_socket_) {
|
if (flush_abstract_socket_) {
|
||||||
|
|||||||
@@ -79,7 +79,7 @@ class _MessageHandlerBase : public QObject {
|
|||||||
|
|
||||||
// Reads and writes uint32 length encoded MessageType messages to a socket.
|
// Reads and writes uint32 length encoded MessageType messages to a socket.
|
||||||
// You should subclass this and implement the MessageArrived(MessageType) method.
|
// You should subclass this and implement the MessageArrived(MessageType) method.
|
||||||
template <typename MT>
|
template<typename MT>
|
||||||
class AbstractMessageHandler : public _MessageHandlerBase {
|
class AbstractMessageHandler : public _MessageHandlerBase {
|
||||||
public:
|
public:
|
||||||
AbstractMessageHandler(QIODevice *device, QObject *parent);
|
AbstractMessageHandler(QIODevice *device, QObject *parent);
|
||||||
@@ -115,11 +115,11 @@ class AbstractMessageHandler : public _MessageHandlerBase {
|
|||||||
QMap<int, ReplyType*> pending_replies_;
|
QMap<int, ReplyType*> pending_replies_;
|
||||||
};
|
};
|
||||||
|
|
||||||
template <typename MT>
|
template<typename MT>
|
||||||
AbstractMessageHandler<MT>::AbstractMessageHandler(QIODevice *device, QObject *parent)
|
AbstractMessageHandler<MT>::AbstractMessageHandler(QIODevice *device, QObject *parent)
|
||||||
: _MessageHandlerBase(device, parent) {}
|
: _MessageHandlerBase(device, parent) {}
|
||||||
|
|
||||||
template <typename MT>
|
template<typename MT>
|
||||||
void AbstractMessageHandler<MT>::SendMessage(const MessageType &message) {
|
void AbstractMessageHandler<MT>::SendMessage(const MessageType &message) {
|
||||||
Q_ASSERT(QThread::currentThread() == thread());
|
Q_ASSERT(QThread::currentThread() == thread());
|
||||||
|
|
||||||
@@ -127,10 +127,10 @@ void AbstractMessageHandler<MT>::SendMessage(const MessageType &message) {
|
|||||||
WriteMessage(QByteArray(data.data(), data.size()));
|
WriteMessage(QByteArray(data.data(), data.size()));
|
||||||
}
|
}
|
||||||
|
|
||||||
template <typename MT>
|
template<typename MT>
|
||||||
void AbstractMessageHandler<MT>::SendMessageAsync(const MessageType &message) {
|
void AbstractMessageHandler<MT>::SendMessageAsync(const MessageType &message) {
|
||||||
std::string data = message.SerializeAsString();
|
std::string data = message.SerializeAsString();
|
||||||
metaObject()->invokeMethod(this, "WriteMessage", Qt::QueuedConnection, Q_ARG(QByteArray, QByteArray(data.data(), data.size())));
|
QMetaObject::invokeMethod(this, "WriteMessage", Qt::QueuedConnection, Q_ARG(QByteArray, QByteArray(data.data(), data.size())));
|
||||||
}
|
}
|
||||||
|
|
||||||
template<typename MT>
|
template<typename MT>
|
||||||
|
|||||||
@@ -56,7 +56,7 @@ class _MessageReplyBase : public QObject {
|
|||||||
};
|
};
|
||||||
|
|
||||||
// A reply future class that is returned immediately for requests that will occur in the background. Similar to QNetworkReply.
|
// A reply future class that is returned immediately for requests that will occur in the background. Similar to QNetworkReply.
|
||||||
template <typename MessageType>
|
template<typename MessageType>
|
||||||
class MessageReply : public _MessageReplyBase {
|
class MessageReply : public _MessageReplyBase {
|
||||||
public:
|
public:
|
||||||
explicit MessageReply(const MessageType &request_message, QObject *parent = nullptr);
|
explicit MessageReply(const MessageType &request_message, QObject *parent = nullptr);
|
||||||
|
|||||||
@@ -19,6 +19,9 @@
|
|||||||
#ifndef WORKERPOOL_H
|
#ifndef WORKERPOOL_H
|
||||||
#define WORKERPOOL_H
|
#define WORKERPOOL_H
|
||||||
|
|
||||||
|
#include "config.h"
|
||||||
|
|
||||||
|
#include <cstdio>
|
||||||
#include <cstddef>
|
#include <cstddef>
|
||||||
|
|
||||||
#include <QtGlobal>
|
#include <QtGlobal>
|
||||||
@@ -56,6 +59,8 @@ class _WorkerPoolBase : public QObject {
|
|||||||
protected slots:
|
protected slots:
|
||||||
virtual void DoStart() {}
|
virtual void DoStart() {}
|
||||||
virtual void NewConnection() {}
|
virtual void NewConnection() {}
|
||||||
|
virtual void ProcessReadyReadStandardOutput() {}
|
||||||
|
virtual void ProcessReadyReadStandardError() {}
|
||||||
virtual void ProcessError(QProcess::ProcessError) {}
|
virtual void ProcessError(QProcess::ProcessError) {}
|
||||||
virtual void SendQueuedMessages() {}
|
virtual void SendQueuedMessages() {}
|
||||||
};
|
};
|
||||||
@@ -65,7 +70,7 @@ class _WorkerPoolBase : public QObject {
|
|||||||
// A local socket server is started for each process, and the address is passed to the process as argv[1].
|
// A local socket server is started for each process, and the address is passed to the process as argv[1].
|
||||||
// The process is expected to connect back to the socket server, and when it does a HandlerType is created for it.
|
// The process is expected to connect back to the socket server, and when it does a HandlerType is created for it.
|
||||||
// Instances of HandlerType are created in the WorkerPool's thread.
|
// Instances of HandlerType are created in the WorkerPool's thread.
|
||||||
template <typename HandlerType>
|
template<typename HandlerType>
|
||||||
class WorkerPool : public _WorkerPoolBase {
|
class WorkerPool : public _WorkerPoolBase {
|
||||||
public:
|
public:
|
||||||
explicit WorkerPool(QObject *parent = nullptr);
|
explicit WorkerPool(QObject *parent = nullptr);
|
||||||
@@ -98,6 +103,8 @@ class WorkerPool : public _WorkerPoolBase {
|
|||||||
// These are all reimplemented slots, they are called on the WorkerPool's thread.
|
// These are all reimplemented slots, they are called on the WorkerPool's thread.
|
||||||
void DoStart() override;
|
void DoStart() override;
|
||||||
void NewConnection() override;
|
void NewConnection() override;
|
||||||
|
void ProcessReadyReadStandardOutput() override;
|
||||||
|
void ProcessReadyReadStandardError() override;
|
||||||
void ProcessError(QProcess::ProcessError error) override;
|
void ProcessError(QProcess::ProcessError error) override;
|
||||||
void SendQueuedMessages() override;
|
void SendQueuedMessages() override;
|
||||||
|
|
||||||
@@ -114,9 +121,9 @@ class WorkerPool : public _WorkerPoolBase {
|
|||||||
// Must only ever be called on my thread.
|
// Must only ever be called on my thread.
|
||||||
void StartOneWorker(Worker *worker);
|
void StartOneWorker(Worker *worker);
|
||||||
|
|
||||||
template <typename T>
|
template<typename T>
|
||||||
Worker *FindWorker(T Worker::*member, T value) {
|
Worker *FindWorker(T Worker::*member, T value) {
|
||||||
for (typename QList<Worker>::iterator it = workers_.begin() ; it != workers_.end() ; ++it) {
|
for (typename QList<Worker>::iterator it = workers_.begin(); it != workers_.end(); ++it) {
|
||||||
if ((*it).*member == value) {
|
if ((*it).*member == value) {
|
||||||
return &(*it);
|
return &(*it);
|
||||||
}
|
}
|
||||||
@@ -124,7 +131,7 @@ class WorkerPool : public _WorkerPoolBase {
|
|||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
template <typename T>
|
template<typename T>
|
||||||
void DeleteQObjectPointerLater(T **p) {
|
void DeleteQObjectPointerLater(T **p) {
|
||||||
if (*p) {
|
if (*p) {
|
||||||
(*p)->deleteLater();
|
(*p)->deleteLater();
|
||||||
@@ -151,30 +158,33 @@ class WorkerPool : public _WorkerPoolBase {
|
|||||||
QAtomicInt next_id_;
|
QAtomicInt next_id_;
|
||||||
|
|
||||||
QMutex message_queue_mutex_;
|
QMutex message_queue_mutex_;
|
||||||
QQueue<ReplyType*> message_queue_;
|
QQueue<ReplyType *> message_queue_;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
template <typename HandlerType>
|
template<typename HandlerType>
|
||||||
WorkerPool<HandlerType>::WorkerPool(QObject *parent)
|
WorkerPool<HandlerType>::WorkerPool(QObject *parent)
|
||||||
: _WorkerPoolBase(parent),
|
: _WorkerPoolBase(parent),
|
||||||
next_worker_(0),
|
next_worker_(0),
|
||||||
next_id_(0) {
|
next_id_(0) {
|
||||||
|
|
||||||
worker_count_ = qBound(1, QThread::idealThreadCount() / 2, 4);
|
worker_count_ = qBound(1, QThread::idealThreadCount() / 2, 4);
|
||||||
local_server_name_ = qApp->applicationName().toLower();
|
local_server_name_ = qApp->applicationName().toLower();
|
||||||
|
|
||||||
if (local_server_name_.isEmpty())
|
if (local_server_name_.isEmpty()) {
|
||||||
local_server_name_ = "workerpool";
|
local_server_name_ = "workerpool";
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
template <typename HandlerType>
|
template<typename HandlerType>
|
||||||
WorkerPool<HandlerType>::~WorkerPool() {
|
WorkerPool<HandlerType>::~WorkerPool() {
|
||||||
|
|
||||||
for (const Worker &worker : workers_) {
|
for (const Worker &worker : workers_) {
|
||||||
if (worker.local_socket_ && worker.process_) {
|
if (worker.local_socket_ && worker.process_) {
|
||||||
QObject::disconnect(worker.process_, &QProcess::errorOccurred, this, &WorkerPool::ProcessError);
|
QObject::disconnect(worker.process_, &QProcess::errorOccurred, this, &WorkerPool::ProcessError);
|
||||||
|
QObject::disconnect(worker.process_, &QProcess::readyReadStandardOutput, this, &WorkerPool::ProcessReadyReadStandardOutput);
|
||||||
|
QObject::disconnect(worker.process_, &QProcess::readyReadStandardError, this, &WorkerPool::ProcessReadyReadStandardError);
|
||||||
|
|
||||||
// The worker is connected. Close his socket and wait for him to exit.
|
// The worker is connected. Close his socket and wait for him to exit.
|
||||||
qLog(Debug) << "Closing worker socket";
|
qLog(Debug) << "Closing worker socket";
|
||||||
@@ -198,30 +208,30 @@ WorkerPool<HandlerType>::~WorkerPool() {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
template <typename HandlerType>
|
template<typename HandlerType>
|
||||||
void WorkerPool<HandlerType>::SetWorkerCount(const int count) {
|
void WorkerPool<HandlerType>::SetWorkerCount(const int count) {
|
||||||
Q_ASSERT(workers_.isEmpty());
|
Q_ASSERT(workers_.isEmpty());
|
||||||
worker_count_ = count;
|
worker_count_ = count;
|
||||||
}
|
}
|
||||||
|
|
||||||
template <typename HandlerType>
|
template<typename HandlerType>
|
||||||
void WorkerPool<HandlerType>::SetLocalServerName(const QString &local_server_name) {
|
void WorkerPool<HandlerType>::SetLocalServerName(const QString &local_server_name) {
|
||||||
Q_ASSERT(workers_.isEmpty());
|
Q_ASSERT(workers_.isEmpty());
|
||||||
local_server_name_ = local_server_name;
|
local_server_name_ = local_server_name;
|
||||||
}
|
}
|
||||||
|
|
||||||
template <typename HandlerType>
|
template<typename HandlerType>
|
||||||
void WorkerPool<HandlerType>::SetExecutableName(const QString &executable_name) {
|
void WorkerPool<HandlerType>::SetExecutableName(const QString &executable_name) {
|
||||||
Q_ASSERT(workers_.isEmpty());
|
Q_ASSERT(workers_.isEmpty());
|
||||||
executable_name_ = executable_name;
|
executable_name_ = executable_name;
|
||||||
}
|
}
|
||||||
|
|
||||||
template <typename HandlerType>
|
template<typename HandlerType>
|
||||||
void WorkerPool<HandlerType>::Start() {
|
void WorkerPool<HandlerType>::Start() {
|
||||||
metaObject()->invokeMethod(this, "DoStart");
|
QMetaObject::invokeMethod(this, "DoStart");
|
||||||
}
|
}
|
||||||
|
|
||||||
template <typename HandlerType>
|
template<typename HandlerType>
|
||||||
void WorkerPool<HandlerType>::DoStart() {
|
void WorkerPool<HandlerType>::DoStart() {
|
||||||
|
|
||||||
Q_ASSERT(workers_.isEmpty());
|
Q_ASSERT(workers_.isEmpty());
|
||||||
@@ -232,9 +242,9 @@ void WorkerPool<HandlerType>::DoStart() {
|
|||||||
executable_path_ = executable_name_;
|
executable_path_ = executable_name_;
|
||||||
|
|
||||||
QStringList search_path;
|
QStringList search_path;
|
||||||
search_path << qApp->applicationDirPath();
|
search_path << QCoreApplication::applicationDirPath();
|
||||||
#if defined(Q_OS_MACOS) && defined(USE_BUNDLE)
|
#if defined(Q_OS_MACOS) && defined(USE_BUNDLE)
|
||||||
search_path << qApp->applicationDirPath() + "/" + USE_BUNDLE_DIR;
|
search_path << QCoreApplication::applicationDirPath() + "/" + USE_BUNDLE_DIR;
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
for (const QString &path_prefix : search_path) {
|
for (const QString &path_prefix : search_path) {
|
||||||
@@ -260,7 +270,7 @@ void WorkerPool<HandlerType>::DoStart() {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
template <typename HandlerType>
|
template<typename HandlerType>
|
||||||
void WorkerPool<HandlerType>::StartOneWorker(Worker *worker) {
|
void WorkerPool<HandlerType>::StartOneWorker(Worker *worker) {
|
||||||
|
|
||||||
Q_ASSERT(QThread::currentThread() == thread());
|
Q_ASSERT(QThread::currentThread() == thread());
|
||||||
@@ -275,6 +285,8 @@ void WorkerPool<HandlerType>::StartOneWorker(Worker *worker) {
|
|||||||
|
|
||||||
QObject::connect(worker->local_server_, &QLocalServer::newConnection, this, &WorkerPool::NewConnection);
|
QObject::connect(worker->local_server_, &QLocalServer::newConnection, this, &WorkerPool::NewConnection);
|
||||||
QObject::connect(worker->process_, &QProcess::errorOccurred, this, &WorkerPool::ProcessError);
|
QObject::connect(worker->process_, &QProcess::errorOccurred, this, &WorkerPool::ProcessError);
|
||||||
|
QObject::connect(worker->process_, &QProcess::readyReadStandardOutput, this, &WorkerPool::ProcessReadyReadStandardOutput);
|
||||||
|
QObject::connect(worker->process_, &QProcess::readyReadStandardError, this, &WorkerPool::ProcessReadyReadStandardError);
|
||||||
|
|
||||||
// Create a server, find an unused name and start listening
|
// Create a server, find an unused name and start listening
|
||||||
forever {
|
forever {
|
||||||
@@ -292,17 +304,16 @@ void WorkerPool<HandlerType>::StartOneWorker(Worker *worker) {
|
|||||||
|
|
||||||
qLog(Debug) << "Starting worker" << worker << executable_path_ << worker->local_server_->fullServerName();
|
qLog(Debug) << "Starting worker" << worker << executable_path_ << worker->local_server_->fullServerName();
|
||||||
|
|
||||||
// Start the process
|
#ifdef Q_OS_WIN32
|
||||||
#if defined(Q_OS_WIN32) && defined(QT_NO_DEBUG_OUTPUT) && !defined(ENABLE_WIN32_CONSOLE)
|
|
||||||
worker->process_->setProcessChannelMode(QProcess::SeparateChannels);
|
worker->process_->setProcessChannelMode(QProcess::SeparateChannels);
|
||||||
#else
|
#else
|
||||||
worker->process_->setProcessChannelMode(QProcess::ForwardedChannels);
|
worker->process_->setProcessChannelMode(QProcess::ForwardedChannels);
|
||||||
#endif
|
#endif
|
||||||
worker->process_->start(executable_path_, QStringList() << worker->local_server_->fullServerName());
|
|
||||||
|
|
||||||
|
worker->process_->start(executable_path_, QStringList() << worker->local_server_->fullServerName());
|
||||||
}
|
}
|
||||||
|
|
||||||
template <typename HandlerType>
|
template<typename HandlerType>
|
||||||
void WorkerPool<HandlerType>::NewConnection() {
|
void WorkerPool<HandlerType>::NewConnection() {
|
||||||
|
|
||||||
Q_ASSERT(QThread::currentThread() == thread());
|
Q_ASSERT(QThread::currentThread() == thread());
|
||||||
@@ -327,9 +338,10 @@ void WorkerPool<HandlerType>::NewConnection() {
|
|||||||
worker->handler_ = new HandlerType(worker->local_socket_, this);
|
worker->handler_ = new HandlerType(worker->local_socket_, this);
|
||||||
|
|
||||||
SendQueuedMessages();
|
SendQueuedMessages();
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
template <typename HandlerType>
|
template<typename HandlerType>
|
||||||
void WorkerPool<HandlerType>::ProcessError(QProcess::ProcessError error) {
|
void WorkerPool<HandlerType>::ProcessError(QProcess::ProcessError error) {
|
||||||
|
|
||||||
Q_ASSERT(QThread::currentThread() == thread());
|
Q_ASSERT(QThread::currentThread() == thread());
|
||||||
@@ -357,6 +369,32 @@ void WorkerPool<HandlerType>::ProcessError(QProcess::ProcessError error) {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
template<typename HandlerType>
|
||||||
|
void WorkerPool<HandlerType>::ProcessReadyReadStandardOutput() {
|
||||||
|
|
||||||
|
Q_ASSERT(QThread::currentThread() == thread());
|
||||||
|
|
||||||
|
QProcess *process = qobject_cast<QProcess*>(sender());
|
||||||
|
QByteArray data = process->readAllStandardOutput();
|
||||||
|
|
||||||
|
fprintf(stdout, "%s", data.data());
|
||||||
|
fflush(stdout);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
template<typename HandlerType>
|
||||||
|
void WorkerPool<HandlerType>::ProcessReadyReadStandardError() {
|
||||||
|
|
||||||
|
Q_ASSERT(QThread::currentThread() == thread());
|
||||||
|
|
||||||
|
QProcess *process = qobject_cast<QProcess*>(sender());
|
||||||
|
QByteArray data = process->readAllStandardError();
|
||||||
|
|
||||||
|
fprintf(stderr, "%s", data.data());
|
||||||
|
fflush(stderr);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
template <typename HandlerType>
|
template <typename HandlerType>
|
||||||
typename WorkerPool<HandlerType>::ReplyType*
|
typename WorkerPool<HandlerType>::ReplyType*
|
||||||
WorkerPool<HandlerType>::NewReply(MessageType *message) {
|
WorkerPool<HandlerType>::NewReply(MessageType *message) {
|
||||||
@@ -381,13 +419,13 @@ WorkerPool<HandlerType>::SendMessageWithReply(MessageType *message) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Wake up the main thread
|
// Wake up the main thread
|
||||||
metaObject()->invokeMethod(this, "SendQueuedMessages", Qt::QueuedConnection);
|
QMetaObject::invokeMethod(this, "SendQueuedMessages", Qt::QueuedConnection);
|
||||||
|
|
||||||
return reply;
|
return reply;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
template <typename HandlerType>
|
template<typename HandlerType>
|
||||||
void WorkerPool<HandlerType>::SendQueuedMessages() {
|
void WorkerPool<HandlerType>::SendQueuedMessages() {
|
||||||
|
|
||||||
QMutexLocker l(&message_queue_mutex_);
|
QMutexLocker l(&message_queue_mutex_);
|
||||||
@@ -409,7 +447,7 @@ void WorkerPool<HandlerType>::SendQueuedMessages() {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
template <typename HandlerType>
|
template<typename HandlerType>
|
||||||
HandlerType *WorkerPool<HandlerType>::NextHandler() const {
|
HandlerType *WorkerPool<HandlerType>::NextHandler() const {
|
||||||
|
|
||||||
for (int i = 0; i < workers_.count(); ++i) {
|
for (int i = 0; i < workers_.count(); ++i) {
|
||||||
|
|||||||
@@ -1,22 +1,36 @@
|
|||||||
cmake_minimum_required(VERSION 3.0)
|
cmake_minimum_required(VERSION 3.0)
|
||||||
|
|
||||||
set(MESSAGES tagreadermessages.proto)
|
set(MESSAGES tagreadermessages.proto)
|
||||||
set(SOURCES tagreader.cpp)
|
set(SOURCES tagreaderbase.cpp)
|
||||||
|
|
||||||
|
if(USE_TAGLIB AND TAGLIB_FOUND)
|
||||||
|
list(APPEND SOURCES tagreadertaglib.cpp)
|
||||||
|
endif()
|
||||||
|
|
||||||
|
if(USE_TAGPARSER AND TAGPARSER_FOUND)
|
||||||
|
list(APPEND SOURCES tagreadertagparser.cpp)
|
||||||
|
endif()
|
||||||
|
|
||||||
protobuf_generate_cpp(PROTO_SOURCES PROTO_HEADERS ${MESSAGES})
|
protobuf_generate_cpp(PROTO_SOURCES PROTO_HEADERS ${MESSAGES})
|
||||||
|
|
||||||
link_directories(
|
link_directories(
|
||||||
${GLIB_LIBRARY_DIRS}
|
${GLIB_LIBRARY_DIRS}
|
||||||
${PROTOBUF_LIBRARY_DIRS}
|
${PROTOBUF_LIBRARY_DIRS}
|
||||||
${TAGLIB_LIBRARY_DIRS}
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
if(USE_TAGLIB AND TAGLIB_FOUND)
|
||||||
|
link_directories(${TAGLIB_LIBRARY_DIRS})
|
||||||
|
endif()
|
||||||
|
|
||||||
|
if(USE_TAGPARSER AND TAGPARSER_FOUND)
|
||||||
|
link_directories(${TAGPARSER_LIBRARY_DIRS})
|
||||||
|
endif()
|
||||||
|
|
||||||
add_library(libstrawberry-tagreader STATIC ${PROTO_SOURCES} ${SOURCES})
|
add_library(libstrawberry-tagreader STATIC ${PROTO_SOURCES} ${SOURCES})
|
||||||
|
|
||||||
target_include_directories(libstrawberry-tagreader SYSTEM PRIVATE
|
target_include_directories(libstrawberry-tagreader SYSTEM PRIVATE
|
||||||
${GLIB_INCLUDE_DIRS}
|
${GLIB_INCLUDE_DIRS}
|
||||||
${PROTOBUF_INCLUDE_DIRS}
|
${PROTOBUF_INCLUDE_DIRS}
|
||||||
${TAGLIB_INCLUDE_DIRS}
|
|
||||||
)
|
)
|
||||||
|
|
||||||
target_include_directories(libstrawberry-tagreader PRIVATE
|
target_include_directories(libstrawberry-tagreader PRIVATE
|
||||||
@@ -30,8 +44,17 @@ target_include_directories(libstrawberry-tagreader PRIVATE
|
|||||||
target_link_libraries(libstrawberry-tagreader PRIVATE
|
target_link_libraries(libstrawberry-tagreader PRIVATE
|
||||||
${GLIB_LIBRARIES}
|
${GLIB_LIBRARIES}
|
||||||
${PROTOBUF_LIBRARY}
|
${PROTOBUF_LIBRARY}
|
||||||
${TAGLIB_LIBRARIES}
|
|
||||||
${QtCore_LIBRARIES}
|
${QtCore_LIBRARIES}
|
||||||
${QtNetwork_LIBRARIES}
|
${QtNetwork_LIBRARIES}
|
||||||
libstrawberry-common
|
libstrawberry-common
|
||||||
)
|
)
|
||||||
|
|
||||||
|
if(USE_TAGLIB AND TAGLIB_FOUND)
|
||||||
|
target_include_directories(libstrawberry-tagreader SYSTEM PRIVATE ${TAGLIB_INCLUDE_DIRS})
|
||||||
|
target_link_libraries(libstrawberry-tagreader PRIVATE ${TAGLIB_LIBRARIES})
|
||||||
|
endif()
|
||||||
|
|
||||||
|
if(USE_TAGPARSER AND TAGPARSER_FOUND)
|
||||||
|
target_include_directories(libstrawberry-tagreader SYSTEM PRIVATE ${TAGPARSER_INCLUDE_DIRS})
|
||||||
|
target_link_libraries(libstrawberry-tagreader PRIVATE ${TAGPARSER_LIBRARIES})
|
||||||
|
endif()
|
||||||
|
|||||||
27
ext/libstrawberry-tagreader/tagreaderbase.cpp
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
/* This file is part of Strawberry.
|
||||||
|
Copyright 2018-2021, Jonas Kvinge <jonas@jkvinge.net>
|
||||||
|
|
||||||
|
Strawberry 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.
|
||||||
|
|
||||||
|
Strawberry 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 Strawberry. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "config.h"
|
||||||
|
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
#include "tagreaderbase.h"
|
||||||
|
|
||||||
|
const std::string TagReaderBase::kEmbeddedCover = "(embedded)";
|
||||||
|
|
||||||
|
TagReaderBase::TagReaderBase() = default;
|
||||||
|
TagReaderBase::~TagReaderBase() = default;
|
||||||
56
ext/libstrawberry-tagreader/tagreaderbase.h
Normal file
@@ -0,0 +1,56 @@
|
|||||||
|
/* This file is part of Strawberry.
|
||||||
|
Copyright 2018-2021, Jonas Kvinge <jonas@jkvinge.net>
|
||||||
|
|
||||||
|
Strawberry 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.
|
||||||
|
|
||||||
|
Strawberry 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 Strawberry. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef TAGREADERBASE_H
|
||||||
|
#define TAGREADERBASE_H
|
||||||
|
|
||||||
|
#include "config.h"
|
||||||
|
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
#include <QByteArray>
|
||||||
|
#include <QString>
|
||||||
|
|
||||||
|
#include "tagreadermessages.pb.h"
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This class holds all useful methods to read and write tags from/to files.
|
||||||
|
* You should not use it directly in the main process but rather use a TagReaderWorker process (using TagReaderClient)
|
||||||
|
*/
|
||||||
|
class TagReaderBase {
|
||||||
|
public:
|
||||||
|
explicit TagReaderBase();
|
||||||
|
~TagReaderBase();
|
||||||
|
|
||||||
|
virtual bool IsMediaFile(const QString &filename) const = 0;
|
||||||
|
|
||||||
|
virtual void ReadFile(const QString &filename, spb::tagreader::SongMetadata *song) const = 0;
|
||||||
|
virtual bool SaveFile(const QString &filename, const spb::tagreader::SongMetadata &song) const = 0;
|
||||||
|
|
||||||
|
virtual QByteArray LoadEmbeddedArt(const QString &filename) const = 0;
|
||||||
|
virtual bool SaveEmbeddedArt(const QString &filename, const QByteArray &data) = 0;
|
||||||
|
|
||||||
|
virtual bool SaveSongPlaycountToFile(const QString &filename, const spb::tagreader::SongMetadata &song) const = 0;
|
||||||
|
virtual bool SaveSongRatingToFile(const QString &filename, const spb::tagreader::SongMetadata &song) const = 0;
|
||||||
|
|
||||||
|
protected:
|
||||||
|
static const std::string kEmbeddedCover;
|
||||||
|
|
||||||
|
Q_DISABLE_COPY(TagReaderBase)
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif // TAGREADERBASE_H
|
||||||
@@ -23,6 +23,10 @@ message SongMetadata {
|
|||||||
DSDIFF = 15;
|
DSDIFF = 15;
|
||||||
PCM = 16;
|
PCM = 16;
|
||||||
APE = 17;
|
APE = 17;
|
||||||
|
MOD = 18;
|
||||||
|
S3M = 19;
|
||||||
|
XM = 20;
|
||||||
|
IT = 21;
|
||||||
CDDA = 90;
|
CDDA = 90;
|
||||||
STREAM = 91;
|
STREAM = 91;
|
||||||
}
|
}
|
||||||
@@ -54,17 +58,21 @@ message SongMetadata {
|
|||||||
optional string url = 21;
|
optional string url = 21;
|
||||||
optional string basefilename = 22;
|
optional string basefilename = 22;
|
||||||
optional FileType filetype = 23;
|
optional FileType filetype = 23;
|
||||||
optional int32 filesize = 24;
|
optional int64 filesize = 24;
|
||||||
optional int64 mtime = 25;
|
optional int64 mtime = 25;
|
||||||
optional int64 ctime = 26;
|
optional int64 ctime = 26;
|
||||||
|
|
||||||
optional int32 playcount = 27;
|
optional uint32 playcount = 27;
|
||||||
optional int32 skipcount = 28;
|
optional uint32 skipcount = 28;
|
||||||
optional int32 lastplayed = 29;
|
optional int64 lastplayed = 29;
|
||||||
|
optional int64 lastseen = 30;
|
||||||
|
|
||||||
optional bool suspicious_tags = 30;
|
|
||||||
optional string art_automatic = 31;
|
optional string art_automatic = 31;
|
||||||
|
|
||||||
|
optional float rating = 32;
|
||||||
|
|
||||||
|
optional bool suspicious_tags = 40;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
message ReadFileRequest {
|
message ReadFileRequest {
|
||||||
@@ -109,6 +117,24 @@ message SaveEmbeddedArtResponse {
|
|||||||
optional bool success = 1;
|
optional bool success = 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
message SaveSongPlaycountToFileRequest {
|
||||||
|
optional string filename = 1;
|
||||||
|
optional SongMetadata metadata = 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
message SaveSongPlaycountToFileResponse {
|
||||||
|
optional bool success = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
message SaveSongRatingToFileRequest {
|
||||||
|
optional string filename = 1;
|
||||||
|
optional SongMetadata metadata = 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
message SaveSongRatingToFileResponse {
|
||||||
|
optional bool success = 1;
|
||||||
|
}
|
||||||
|
|
||||||
message Message {
|
message Message {
|
||||||
optional int32 id = 1;
|
optional int32 id = 1;
|
||||||
|
|
||||||
@@ -127,4 +153,10 @@ message Message {
|
|||||||
optional SaveEmbeddedArtRequest save_embedded_art_request = 10;
|
optional SaveEmbeddedArtRequest save_embedded_art_request = 10;
|
||||||
optional SaveEmbeddedArtResponse save_embedded_art_response = 11;
|
optional SaveEmbeddedArtResponse save_embedded_art_response = 11;
|
||||||
|
|
||||||
|
optional SaveSongPlaycountToFileRequest save_song_playcount_to_file_request = 12;
|
||||||
|
optional SaveSongPlaycountToFileResponse save_song_playcount_to_file_response = 13;
|
||||||
|
|
||||||
|
optional SaveSongRatingToFileRequest save_song_rating_to_file_request = 14;
|
||||||
|
optional SaveSongRatingToFileResponse save_song_rating_to_file_response = 15;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,8 +16,8 @@
|
|||||||
along with Strawberry. If not, see <http://www.gnu.org/licenses/>.
|
along with Strawberry. If not, see <http://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#ifndef TAGREADER_H
|
#ifndef TAGREADERTAGLIB_H
|
||||||
#define TAGREADER_H
|
#define TAGREADERTAGLIB_H
|
||||||
|
|
||||||
#include "config.h"
|
#include "config.h"
|
||||||
|
|
||||||
@@ -32,30 +32,36 @@
|
|||||||
#include <taglib/apetag.h>
|
#include <taglib/apetag.h>
|
||||||
#include <taglib/apefile.h>
|
#include <taglib/apefile.h>
|
||||||
#include <taglib/id3v2tag.h>
|
#include <taglib/id3v2tag.h>
|
||||||
|
#include <taglib/popularimeterframe.h>
|
||||||
|
|
||||||
|
#include "tagreaderbase.h"
|
||||||
#include "tagreadermessages.pb.h"
|
#include "tagreadermessages.pb.h"
|
||||||
|
|
||||||
class FileRefFactory;
|
class FileRefFactory;
|
||||||
|
|
||||||
/**
|
/*
|
||||||
* This class holds all useful methods to read and write tags from/to files.
|
* This class holds all useful methods to read and write tags from/to files.
|
||||||
* You should not use it directly in the main process but rather use a TagReaderWorker process (using TagReaderClient)
|
* You should not use it directly in the main process but rather use a TagReaderWorker process (using TagReaderClient)
|
||||||
*/
|
*/
|
||||||
class TagReader {
|
class TagReaderTagLib : public TagReaderBase {
|
||||||
public:
|
public:
|
||||||
explicit TagReader();
|
explicit TagReaderTagLib();
|
||||||
~TagReader();
|
~TagReaderTagLib();
|
||||||
|
|
||||||
bool IsMediaFile(const QString &filename) const;
|
bool IsMediaFile(const QString &filename) const override;
|
||||||
|
|
||||||
|
void ReadFile(const QString &filename, spb::tagreader::SongMetadata *song) const override;
|
||||||
|
bool SaveFile(const QString &filename, const spb::tagreader::SongMetadata &song) const override;
|
||||||
|
|
||||||
|
QByteArray LoadEmbeddedArt(const QString &filename) const override;
|
||||||
|
bool SaveEmbeddedArt(const QString &filename, const QByteArray &data) override;
|
||||||
|
|
||||||
|
bool SaveSongPlaycountToFile(const QString &filename, const spb::tagreader::SongMetadata &song) const override;
|
||||||
|
bool SaveSongRatingToFile(const QString &filename, const spb::tagreader::SongMetadata &song) const override;
|
||||||
|
|
||||||
|
private:
|
||||||
spb::tagreader::SongMetadata_FileType GuessFileType(TagLib::FileRef *fileref) const;
|
spb::tagreader::SongMetadata_FileType GuessFileType(TagLib::FileRef *fileref) const;
|
||||||
|
|
||||||
void ReadFile(const QString &filename, spb::tagreader::SongMetadata *song) const;
|
|
||||||
bool SaveFile(const QString &filename, const spb::tagreader::SongMetadata &song) const;
|
|
||||||
|
|
||||||
QByteArray LoadEmbeddedArt(const QString &filename) const;
|
|
||||||
QByteArray LoadEmbeddedAPEArt(const TagLib::APE::ItemListMap &map) const;
|
|
||||||
bool SaveEmbeddedArt(const QString &filename, const QByteArray &data);
|
|
||||||
|
|
||||||
static void Decode(const TagLib::String &tag, std::string *output);
|
static void Decode(const TagLib::String &tag, std::string *output);
|
||||||
static void Decode(const QString &tag, std::string *output);
|
static void Decode(const QString &tag, std::string *output);
|
||||||
|
|
||||||
@@ -67,12 +73,20 @@ class TagReader {
|
|||||||
|
|
||||||
void SetTextFrame(const char *id, const QString &value, TagLib::ID3v2::Tag *tag) const;
|
void SetTextFrame(const char *id, const QString &value, TagLib::ID3v2::Tag *tag) const;
|
||||||
void SetTextFrame(const char *id, const std::string &value, TagLib::ID3v2::Tag *tag) const;
|
void SetTextFrame(const char *id, const std::string &value, TagLib::ID3v2::Tag *tag) const;
|
||||||
void SetUnsyncLyricsFrame(const std::string& value, TagLib::ID3v2::Tag* tag) const;
|
void SetUserTextFrame(const QString &description, const QString &value, TagLib::ID3v2::Tag *tag) const;
|
||||||
|
void SetUserTextFrame(const std::string &description, const std::string &value, TagLib::ID3v2::Tag *tag) const;
|
||||||
|
void SetUnsyncLyricsFrame(const std::string &value, TagLib::ID3v2::Tag *tag) const;
|
||||||
|
|
||||||
|
QByteArray LoadEmbeddedAPEArt(const TagLib::APE::ItemListMap &map) const;
|
||||||
|
|
||||||
|
static float ConvertPOPMRating(const int POPM_rating);
|
||||||
|
static int ConvertToPOPMRating(const float rating);
|
||||||
|
static TagLib::ID3v2::PopularimeterFrame *GetPOPMFrameFromTag(TagLib::ID3v2::Tag *tag);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
FileRefFactory *factory_;
|
FileRefFactory *factory_;
|
||||||
|
|
||||||
const std::string kEmbeddedCover;
|
Q_DISABLE_COPY(TagReaderTagLib)
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif // TAGREADER_H
|
#endif // TAGREADERTAGLIB_H
|
||||||
482
ext/libstrawberry-tagreader/tagreadertagparser.cpp
Normal file
@@ -0,0 +1,482 @@
|
|||||||
|
/* This file is part of Strawberry.
|
||||||
|
Copyright 2021, Jonas Kvinge <jonas@jkvinge.net>
|
||||||
|
|
||||||
|
Strawberry 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.
|
||||||
|
|
||||||
|
Strawberry 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 Strawberry. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "config.h"
|
||||||
|
|
||||||
|
#include "tagreadertagparser.h"
|
||||||
|
|
||||||
|
#include <string>
|
||||||
|
#include <memory>
|
||||||
|
#include <algorithm>
|
||||||
|
#include <sys/stat.h>
|
||||||
|
|
||||||
|
#include <tagparser/mediafileinfo.h>
|
||||||
|
#include <tagparser/diagnostics.h>
|
||||||
|
#include <tagparser/progressfeedback.h>
|
||||||
|
#include <tagparser/tag.h>
|
||||||
|
#include <tagparser/abstracttrack.h>
|
||||||
|
|
||||||
|
#include <QtGlobal>
|
||||||
|
#include <QFile>
|
||||||
|
#include <QFileInfo>
|
||||||
|
#include <QByteArray>
|
||||||
|
#include <QString>
|
||||||
|
#include <QUrl>
|
||||||
|
#include <QDateTime>
|
||||||
|
#include <QtDebug>
|
||||||
|
|
||||||
|
#include "core/logging.h"
|
||||||
|
#include "core/messagehandler.h"
|
||||||
|
#include "core/timeconstants.h"
|
||||||
|
|
||||||
|
TagReaderTagParser::TagReaderTagParser() = default;
|
||||||
|
|
||||||
|
TagReaderTagParser::~TagReaderTagParser() = default;
|
||||||
|
|
||||||
|
bool TagReaderTagParser::IsMediaFile(const QString &filename) const {
|
||||||
|
|
||||||
|
qLog(Debug) << "Checking for valid file" << filename;
|
||||||
|
|
||||||
|
QFileInfo fileinfo(filename);
|
||||||
|
if (!fileinfo.exists() || fileinfo.suffix().compare("bak", Qt::CaseInsensitive) == 0) return false;
|
||||||
|
|
||||||
|
try {
|
||||||
|
TagParser::MediaFileInfo taginfo;
|
||||||
|
TagParser::Diagnostics diag;
|
||||||
|
TagParser::AbortableProgressFeedback progress;
|
||||||
|
|
||||||
|
taginfo.setPath(QFile::encodeName(filename).toStdString());
|
||||||
|
taginfo.open(true);
|
||||||
|
|
||||||
|
taginfo.parseContainerFormat(diag, progress);
|
||||||
|
if (progress.isAborted()) {
|
||||||
|
taginfo.close();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
taginfo.parseTracks(diag, progress);
|
||||||
|
if (progress.isAborted()) {
|
||||||
|
taginfo.close();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const TagParser::DiagMessage &msg : diag) {
|
||||||
|
qLog(Debug) << QString::fromStdString(msg.message());
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto tracks = taginfo.tracks();
|
||||||
|
for (const auto track : tracks) {
|
||||||
|
if (track->mediaType() == TagParser::MediaType::Audio) {
|
||||||
|
taginfo.close();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
taginfo.close();
|
||||||
|
}
|
||||||
|
catch(...) {}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
void TagReaderTagParser::ReadFile(const QString &filename, spb::tagreader::SongMetadata *song) const {
|
||||||
|
|
||||||
|
qLog(Debug) << "Reading tags from" << filename;
|
||||||
|
|
||||||
|
const QFileInfo fileinfo(filename);
|
||||||
|
|
||||||
|
if (!fileinfo.exists() || fileinfo.suffix().compare("bak", Qt::CaseInsensitive) == 0) return;
|
||||||
|
|
||||||
|
const QByteArray url(QUrl::fromLocalFile(filename).toEncoded());
|
||||||
|
|
||||||
|
song->set_basefilename(DataCommaSizeFromQString(fileinfo.fileName()));
|
||||||
|
song->set_url(url.constData(), url.size());
|
||||||
|
song->set_filesize(fileinfo.size());
|
||||||
|
song->set_mtime(fileinfo.lastModified().isValid() ? fileinfo.lastModified().toSecsSinceEpoch() : 0);
|
||||||
|
#if (QT_VERSION >= QT_VERSION_CHECK(5, 10, 0))
|
||||||
|
song->set_ctime(fileinfo.birthTime().isValid() ? fileinfo.birthTime().toSecsSinceEpoch() : fileinfo.lastModified().isValid() ? fileinfo.lastModified().toSecsSinceEpoch() : 0);
|
||||||
|
#else
|
||||||
|
song->set_ctime(fileinfo.created().isValid() ? fileinfo.created().toSecsSinceEpoch() : fileinfo.lastModified().isValid() ? fileinfo.lastModified().toSecsSinceEpoch() : 0);
|
||||||
|
#endif
|
||||||
|
song->set_lastseen(QDateTime::currentDateTime().toSecsSinceEpoch());
|
||||||
|
|
||||||
|
try {
|
||||||
|
TagParser::MediaFileInfo taginfo;
|
||||||
|
TagParser::Diagnostics diag;
|
||||||
|
TagParser::AbortableProgressFeedback progress;
|
||||||
|
|
||||||
|
#ifdef Q_OS_WIN32
|
||||||
|
taginfo.setPath(filename.toStdWString().toStdString());
|
||||||
|
#else
|
||||||
|
taginfo.setPath(QFile::encodeName(filename).toStdString());
|
||||||
|
#endif
|
||||||
|
|
||||||
|
taginfo.open(true);
|
||||||
|
|
||||||
|
taginfo.parseContainerFormat(diag, progress);
|
||||||
|
if (progress.isAborted()) {
|
||||||
|
taginfo.close();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
taginfo.parseTracks(diag, progress);
|
||||||
|
if (progress.isAborted()) {
|
||||||
|
taginfo.close();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
taginfo.parseTags(diag, progress);
|
||||||
|
if (progress.isAborted()) {
|
||||||
|
taginfo.close();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const TagParser::DiagMessage &msg : diag) {
|
||||||
|
qLog(Debug) << QString::fromStdString(msg.message());
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto tracks = taginfo.tracks();
|
||||||
|
for (const auto track : tracks) {
|
||||||
|
switch (track->format().general) {
|
||||||
|
case TagParser::GeneralMediaFormat::Flac:
|
||||||
|
song->set_filetype(spb::tagreader::SongMetadata_FileType::SongMetadata_FileType_FLAC);
|
||||||
|
break;
|
||||||
|
case TagParser::GeneralMediaFormat::WavPack:
|
||||||
|
song->set_filetype(spb::tagreader::SongMetadata_FileType::SongMetadata_FileType_WAVPACK);
|
||||||
|
break;
|
||||||
|
case TagParser::GeneralMediaFormat::MonkeysAudio:
|
||||||
|
song->set_filetype(spb::tagreader::SongMetadata_FileType::SongMetadata_FileType_APE);
|
||||||
|
break;
|
||||||
|
case TagParser::GeneralMediaFormat::WindowsMediaAudio:
|
||||||
|
song->set_filetype(spb::tagreader::SongMetadata_FileType::SongMetadata_FileType_ASF);
|
||||||
|
break;
|
||||||
|
case TagParser::GeneralMediaFormat::Vorbis:
|
||||||
|
song->set_filetype(spb::tagreader::SongMetadata_FileType::SongMetadata_FileType_OGGVORBIS);
|
||||||
|
break;
|
||||||
|
case TagParser::GeneralMediaFormat::Opus:
|
||||||
|
song->set_filetype(spb::tagreader::SongMetadata_FileType::SongMetadata_FileType_OGGOPUS);
|
||||||
|
break;
|
||||||
|
case TagParser::GeneralMediaFormat::Speex:
|
||||||
|
song->set_filetype(spb::tagreader::SongMetadata_FileType::SongMetadata_FileType_OGGSPEEX);
|
||||||
|
break;
|
||||||
|
case TagParser::GeneralMediaFormat::Mpeg1Audio:
|
||||||
|
switch (track->format().sub) {
|
||||||
|
case TagParser::SubFormats::Mpeg1Layer3:
|
||||||
|
song->set_filetype(spb::tagreader::SongMetadata_FileType::SongMetadata_FileType_MPEG);
|
||||||
|
break;
|
||||||
|
case TagParser::SubFormats::None:
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case TagParser::GeneralMediaFormat::Mpc:
|
||||||
|
song->set_filetype(spb::tagreader::SongMetadata_FileType::SongMetadata_FileType_MPC);
|
||||||
|
break;
|
||||||
|
case TagParser::GeneralMediaFormat::Pcm:
|
||||||
|
song->set_filetype(spb::tagreader::SongMetadata_FileType::SongMetadata_FileType_PCM);
|
||||||
|
break;
|
||||||
|
case TagParser::GeneralMediaFormat::Unknown:
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
song->set_length_nanosec(track->duration().totalMilliseconds() * kNsecPerMsec);
|
||||||
|
song->set_samplerate(track->samplingFrequency());
|
||||||
|
song->set_bitdepth(track->bitsPerSample());
|
||||||
|
song->set_bitrate(std::max(track->bitrate(), track->maxBitrate()));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (song->filetype() == spb::tagreader::SongMetadata_FileType::SongMetadata_FileType_UNKNOWN) {
|
||||||
|
taginfo.close();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const auto tag : taginfo.tags()) {
|
||||||
|
song->set_albumartist(tag->value(TagParser::KnownField::AlbumArtist).toString(TagParser::TagTextEncoding::Utf8));
|
||||||
|
song->set_artist(tag->value(TagParser::KnownField::Artist).toString(TagParser::TagTextEncoding::Utf8));
|
||||||
|
song->set_album(tag->value(TagParser::KnownField::Album).toString(TagParser::TagTextEncoding::Utf8));
|
||||||
|
song->set_title(tag->value(TagParser::KnownField::Title).toString(TagParser::TagTextEncoding::Utf8));
|
||||||
|
song->set_genre(tag->value(TagParser::KnownField::Genre).toString(TagParser::TagTextEncoding::Utf8));
|
||||||
|
song->set_composer(tag->value(TagParser::KnownField::Composer).toString(TagParser::TagTextEncoding::Utf8));
|
||||||
|
song->set_performer(tag->value(TagParser::KnownField::Performers).toString(TagParser::TagTextEncoding::Utf8));
|
||||||
|
song->set_grouping(tag->value(TagParser::KnownField::Grouping).toString(TagParser::TagTextEncoding::Utf8));
|
||||||
|
song->set_comment(tag->value(TagParser::KnownField::Comment).toString(TagParser::TagTextEncoding::Utf8));
|
||||||
|
song->set_lyrics(tag->value(TagParser::KnownField::Lyrics).toString(TagParser::TagTextEncoding::Utf8));
|
||||||
|
song->set_year(tag->value(TagParser::KnownField::RecordDate).toInteger());
|
||||||
|
song->set_originalyear(tag->value(TagParser::KnownField::ReleaseDate).toInteger());
|
||||||
|
song->set_track(tag->value(TagParser::KnownField::TrackPosition).toInteger());
|
||||||
|
song->set_disc(tag->value(TagParser::KnownField::DiskPosition).toInteger());
|
||||||
|
if (!tag->value(TagParser::KnownField::Cover).empty() && tag->value(TagParser::KnownField::Cover).dataSize() > 0) {
|
||||||
|
song->set_art_automatic(kEmbeddedCover);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set integer fields to -1 if they're not valid
|
||||||
|
if (song->track() <= 0) { song->set_track(-1); }
|
||||||
|
if (song->disc() <= 0) { song->set_disc(-1); }
|
||||||
|
if (song->year() <= 0) { song->set_year(-1); }
|
||||||
|
if (song->originalyear() <= 0) { song->set_originalyear(-1); }
|
||||||
|
if (song->samplerate() <= 0) { song->set_samplerate(-1); }
|
||||||
|
if (song->bitdepth() <= 0) { song->set_bitdepth(-1); }
|
||||||
|
if (song->bitrate() <= 0) { song->set_bitrate(-1); }
|
||||||
|
if (song->lastplayed() <= 0) { song->set_lastplayed(-1); }
|
||||||
|
|
||||||
|
song->set_valid(true);
|
||||||
|
|
||||||
|
taginfo.close();
|
||||||
|
|
||||||
|
}
|
||||||
|
catch(...) {}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
bool TagReaderTagParser::SaveFile(const QString &filename, const spb::tagreader::SongMetadata &song) const {
|
||||||
|
|
||||||
|
if (filename.isEmpty()) return false;
|
||||||
|
|
||||||
|
qLog(Debug) << "Saving tags to" << filename;
|
||||||
|
|
||||||
|
try {
|
||||||
|
TagParser::MediaFileInfo taginfo;
|
||||||
|
TagParser::Diagnostics diag;
|
||||||
|
TagParser::AbortableProgressFeedback progress;
|
||||||
|
#ifdef Q_OS_WIN32
|
||||||
|
taginfo.setPath(filename.toStdWString().toStdString());
|
||||||
|
#else
|
||||||
|
taginfo.setPath(QFile::encodeName(filename).toStdString());
|
||||||
|
#endif
|
||||||
|
taginfo.open(false);
|
||||||
|
|
||||||
|
taginfo.parseContainerFormat(diag, progress);
|
||||||
|
if (progress.isAborted()) {
|
||||||
|
taginfo.close();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
taginfo.parseTracks(diag, progress);
|
||||||
|
if (progress.isAborted()) {
|
||||||
|
taginfo.close();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
taginfo.parseTags(diag, progress);
|
||||||
|
if (progress.isAborted()) {
|
||||||
|
taginfo.close();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (taginfo.tags().size() <= 0) {
|
||||||
|
taginfo.createAppropriateTags();
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const auto tag : taginfo.tags()) {
|
||||||
|
tag->setValue(TagParser::KnownField::AlbumArtist, TagParser::TagValue(song.albumartist(), TagParser::TagTextEncoding::Utf8, tag->proposedTextEncoding()));
|
||||||
|
tag->setValue(TagParser::KnownField::Artist, TagParser::TagValue(song.artist(), TagParser::TagTextEncoding::Utf8, tag->proposedTextEncoding()));
|
||||||
|
tag->setValue(TagParser::KnownField::Album, TagParser::TagValue(song.album(), TagParser::TagTextEncoding::Utf8, tag->proposedTextEncoding()));
|
||||||
|
tag->setValue(TagParser::KnownField::Title, TagParser::TagValue(song.title(), TagParser::TagTextEncoding::Utf8, tag->proposedTextEncoding()));
|
||||||
|
tag->setValue(TagParser::KnownField::Genre, TagParser::TagValue(song.genre(), TagParser::TagTextEncoding::Utf8, tag->proposedTextEncoding()));
|
||||||
|
tag->setValue(TagParser::KnownField::Composer, TagParser::TagValue(song.composer(), TagParser::TagTextEncoding::Utf8, tag->proposedTextEncoding()));
|
||||||
|
tag->setValue(TagParser::KnownField::Performers, TagParser::TagValue(song.performer(), TagParser::TagTextEncoding::Utf8, tag->proposedTextEncoding()));
|
||||||
|
tag->setValue(TagParser::KnownField::Grouping, TagParser::TagValue(song.grouping(), TagParser::TagTextEncoding::Utf8, tag->proposedTextEncoding()));
|
||||||
|
tag->setValue(TagParser::KnownField::Comment, TagParser::TagValue(song.comment(), TagParser::TagTextEncoding::Utf8, tag->proposedTextEncoding()));
|
||||||
|
tag->setValue(TagParser::KnownField::Lyrics, TagParser::TagValue(song.lyrics(), TagParser::TagTextEncoding::Utf8, tag->proposedTextEncoding()));
|
||||||
|
tag->setValue(TagParser::KnownField::TrackPosition, TagParser::TagValue(song.track()));
|
||||||
|
tag->setValue(TagParser::KnownField::DiskPosition, TagParser::TagValue(song.disc()));
|
||||||
|
tag->setValue(TagParser::KnownField::RecordDate, TagParser::TagValue(song.year()));
|
||||||
|
tag->setValue(TagParser::KnownField::ReleaseDate, TagParser::TagValue(song.originalyear()));
|
||||||
|
}
|
||||||
|
taginfo.applyChanges(diag, progress);
|
||||||
|
taginfo.close();
|
||||||
|
|
||||||
|
for (const TagParser::DiagMessage &msg : diag) {
|
||||||
|
qLog(Debug) << QString::fromStdString(msg.message());
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
catch(...) {}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
QByteArray TagReaderTagParser::LoadEmbeddedArt(const QString &filename) const {
|
||||||
|
|
||||||
|
if (filename.isEmpty()) return QByteArray();
|
||||||
|
|
||||||
|
qLog(Debug) << "Loading art from" << filename;
|
||||||
|
|
||||||
|
try {
|
||||||
|
|
||||||
|
TagParser::MediaFileInfo taginfo;
|
||||||
|
TagParser::Diagnostics diag;
|
||||||
|
TagParser::AbortableProgressFeedback progress;
|
||||||
|
|
||||||
|
#ifdef Q_OS_WIN32
|
||||||
|
taginfo.setPath(filename.toStdWString().toStdString());
|
||||||
|
#else
|
||||||
|
taginfo.setPath(QFile::encodeName(filename).toStdString());
|
||||||
|
#endif
|
||||||
|
|
||||||
|
taginfo.open();
|
||||||
|
|
||||||
|
taginfo.parseContainerFormat(diag, progress);
|
||||||
|
if (progress.isAborted()) {
|
||||||
|
taginfo.close();
|
||||||
|
return QByteArray();
|
||||||
|
}
|
||||||
|
|
||||||
|
taginfo.parseTags(diag, progress);
|
||||||
|
if (progress.isAborted()) {
|
||||||
|
taginfo.close();
|
||||||
|
return QByteArray();
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const auto tag : taginfo.tags()) {
|
||||||
|
if (!tag->value(TagParser::KnownField::Cover).empty() && tag->value(TagParser::KnownField::Cover).dataSize() > 0) {
|
||||||
|
QByteArray data(tag->value(TagParser::KnownField::Cover).dataPointer(), tag->value(TagParser::KnownField::Cover).dataSize());
|
||||||
|
taginfo.close();
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
taginfo.close();
|
||||||
|
|
||||||
|
for (const TagParser::DiagMessage &msg : diag) {
|
||||||
|
qLog(Debug) << QString::fromStdString(msg.message());
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
catch(...) {}
|
||||||
|
|
||||||
|
return QByteArray();
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
bool TagReaderTagParser::SaveEmbeddedArt(const QString &filename, const QByteArray &data) {
|
||||||
|
|
||||||
|
if (filename.isEmpty()) return false;
|
||||||
|
|
||||||
|
qLog(Debug) << "Saving art to" << filename;
|
||||||
|
|
||||||
|
try {
|
||||||
|
|
||||||
|
TagParser::MediaFileInfo taginfo;
|
||||||
|
TagParser::Diagnostics diag;
|
||||||
|
TagParser::AbortableProgressFeedback progress;
|
||||||
|
|
||||||
|
#ifdef Q_OS_WIN32
|
||||||
|
taginfo.setPath(filename.toStdWString().toStdString());
|
||||||
|
#else
|
||||||
|
taginfo.setPath(QFile::encodeName(filename).toStdString());
|
||||||
|
#endif
|
||||||
|
|
||||||
|
taginfo.open();
|
||||||
|
|
||||||
|
taginfo.parseContainerFormat(diag, progress);
|
||||||
|
if (progress.isAborted()) {
|
||||||
|
taginfo.close();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
taginfo.parseTags(diag, progress);
|
||||||
|
if (progress.isAborted()) {
|
||||||
|
taginfo.close();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (taginfo.tags().size() <= 0) {
|
||||||
|
taginfo.createAppropriateTags();
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const auto tag : taginfo.tags()) {
|
||||||
|
tag->setValue(TagParser::KnownField::Cover, TagParser::TagValue(data.toStdString()));
|
||||||
|
}
|
||||||
|
|
||||||
|
taginfo.applyChanges(diag, progress);
|
||||||
|
taginfo.close();
|
||||||
|
|
||||||
|
for (const TagParser::DiagMessage &msg : diag) {
|
||||||
|
qLog(Debug) << QString::fromStdString(msg.message());
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
|
||||||
|
}
|
||||||
|
catch(...) {}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
bool TagReaderTagParser::SaveSongPlaycountToFile(const QString&, const spb::tagreader::SongMetadata&) const { return false; }
|
||||||
|
|
||||||
|
bool TagReaderTagParser::SaveSongRatingToFile(const QString &filename, const spb::tagreader::SongMetadata &song) const {
|
||||||
|
|
||||||
|
if (filename.isEmpty()) return false;
|
||||||
|
|
||||||
|
qLog(Debug) << "Saving song rating to" << filename;
|
||||||
|
|
||||||
|
try {
|
||||||
|
TagParser::MediaFileInfo taginfo;
|
||||||
|
TagParser::Diagnostics diag;
|
||||||
|
TagParser::AbortableProgressFeedback progress;
|
||||||
|
#ifdef Q_OS_WIN32
|
||||||
|
taginfo.setPath(filename.toStdWString().toStdString());
|
||||||
|
#else
|
||||||
|
taginfo.setPath(QFile::encodeName(filename).toStdString());
|
||||||
|
#endif
|
||||||
|
taginfo.open(false);
|
||||||
|
|
||||||
|
taginfo.parseContainerFormat(diag, progress);
|
||||||
|
if (progress.isAborted()) {
|
||||||
|
taginfo.close();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
taginfo.parseTracks(diag, progress);
|
||||||
|
if (progress.isAborted()) {
|
||||||
|
taginfo.close();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
taginfo.parseTags(diag, progress);
|
||||||
|
if (progress.isAborted()) {
|
||||||
|
taginfo.close();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (taginfo.tags().size() <= 0) {
|
||||||
|
taginfo.createAppropriateTags();
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const auto tag : taginfo.tags()) {
|
||||||
|
tag->setValue(TagParser::KnownField::Rating, TagParser::TagValue(song.rating()));
|
||||||
|
}
|
||||||
|
taginfo.applyChanges(diag, progress);
|
||||||
|
taginfo.close();
|
||||||
|
|
||||||
|
for (const TagParser::DiagMessage &msg : diag) {
|
||||||
|
qLog(Debug) << QString::fromStdString(msg.message());
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
catch(...) {}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
|
||||||
|
}
|
||||||
54
ext/libstrawberry-tagreader/tagreadertagparser.h
Normal file
@@ -0,0 +1,54 @@
|
|||||||
|
/* This file is part of Strawberry.
|
||||||
|
Copyright 2021, Jonas Kvinge <jonas@jkvinge.net>
|
||||||
|
|
||||||
|
Strawberry 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.
|
||||||
|
|
||||||
|
Strawberry 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 Strawberry. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef TAGREADERTAGPARSER_H
|
||||||
|
#define TAGREADERTAGPARSER_H
|
||||||
|
|
||||||
|
#include "config.h"
|
||||||
|
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
#include <QByteArray>
|
||||||
|
#include <QString>
|
||||||
|
|
||||||
|
#include "tagreadermessages.pb.h"
|
||||||
|
#include "tagreaderbase.h"
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This class holds all useful methods to read and write tags from/to files.
|
||||||
|
* You should not use it directly in the main process but rather use a TagReaderWorker process (using TagReaderClient)
|
||||||
|
*/
|
||||||
|
class TagReaderTagParser : public TagReaderBase {
|
||||||
|
public:
|
||||||
|
explicit TagReaderTagParser();
|
||||||
|
~TagReaderTagParser();
|
||||||
|
|
||||||
|
bool IsMediaFile(const QString &filename) const override;
|
||||||
|
|
||||||
|
void ReadFile(const QString &filename, spb::tagreader::SongMetadata *song) const override;
|
||||||
|
bool SaveFile(const QString &filename, const spb::tagreader::SongMetadata &song) const override;
|
||||||
|
|
||||||
|
QByteArray LoadEmbeddedArt(const QString &filename) const override;
|
||||||
|
bool SaveEmbeddedArt(const QString &filename, const QByteArray &data) override;
|
||||||
|
|
||||||
|
bool SaveSongPlaycountToFile(const QString &filename, const spb::tagreader::SongMetadata &song) const override;
|
||||||
|
bool SaveSongRatingToFile(const QString &filename, const spb::tagreader::SongMetadata &song) const override;
|
||||||
|
|
||||||
|
Q_DISABLE_COPY(TagReaderTagParser)
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif // TAGREADERTAGPARSER_H
|
||||||
15
ext/macdeploycheck/CMakeLists.txt
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
qt_wrap_cpp(MACDEPLOYCHECK_MOC ${CMAKE_SOURCE_DIR}/ext/libstrawberry-common/core/logging.h)
|
||||||
|
link_directories(${GLIB_LIBRARY_DIRS})
|
||||||
|
add_executable(macdeploycheck macdeploycheck.cpp ${CMAKE_SOURCE_DIR}/ext/libstrawberry-common/core/logging.cpp ${MACDEPLOYCHECK_MOC})
|
||||||
|
target_include_directories(macdeploycheck PUBLIC SYSTEM
|
||||||
|
${GLIB_INCLUDE_DIRS}
|
||||||
|
)
|
||||||
|
target_include_directories(macdeploycheck PUBLIC
|
||||||
|
${CMAKE_SOURCE_DIR}/ext/libstrawberry-common
|
||||||
|
${CMAKE_BINARY_DIR}/src
|
||||||
|
)
|
||||||
|
target_link_libraries(macdeploycheck PUBLIC
|
||||||
|
"-framework AppKit"
|
||||||
|
${GLIB_LIBRARIES}
|
||||||
|
${QtCore_LIBRARIES}
|
||||||
|
)
|
||||||
150
ext/macdeploycheck/macdeploycheck.cpp
Normal file
@@ -0,0 +1,150 @@
|
|||||||
|
/* This file is part of Strawberry.
|
||||||
|
Copyright 2021, Jonas Kvinge <jonas@jkvinge.net>
|
||||||
|
|
||||||
|
Strawberry 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.
|
||||||
|
|
||||||
|
Strawberry 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 Strawberry. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <QCoreApplication>
|
||||||
|
#include <QString>
|
||||||
|
#include <QStringList>
|
||||||
|
#include <QFile>
|
||||||
|
#include <QFileInfo>
|
||||||
|
#include <QDir>
|
||||||
|
#include <QDirIterator>
|
||||||
|
#include <QProcess>
|
||||||
|
#include <QRegularExpression>
|
||||||
|
#include <QRegularExpressionMatch>
|
||||||
|
|
||||||
|
#include "core/logging.h"
|
||||||
|
|
||||||
|
int main(int argc, char **argv);
|
||||||
|
int main(int argc, char **argv) {
|
||||||
|
|
||||||
|
QCoreApplication app(argc, argv);
|
||||||
|
|
||||||
|
logging::Init();
|
||||||
|
|
||||||
|
qLog(Info) << "Running macdeploycheck";
|
||||||
|
|
||||||
|
if (argc < 1) {
|
||||||
|
qLog(Error) << "Usage: macdeploycheck <bundledir>";
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
QString bundle_path = QString::fromLocal8Bit(argv[1]);
|
||||||
|
|
||||||
|
bool success = true;
|
||||||
|
|
||||||
|
QDirIterator iter(bundle_path, QDir::Files | QDir::NoSymLinks, QDirIterator::Subdirectories);
|
||||||
|
while (iter.hasNext()) {
|
||||||
|
|
||||||
|
iter.next();
|
||||||
|
|
||||||
|
QString filepath = iter.fileInfo().filePath();
|
||||||
|
|
||||||
|
// Ignore these files.
|
||||||
|
if (filepath.endsWith(".plist") ||
|
||||||
|
filepath.endsWith(".icns") ||
|
||||||
|
filepath.endsWith(".prl") ||
|
||||||
|
filepath.endsWith(".conf") ||
|
||||||
|
filepath.endsWith(".h") ||
|
||||||
|
filepath.endsWith(".nib") ||
|
||||||
|
filepath.endsWith(".strings") ||
|
||||||
|
filepath.endsWith(".css") ||
|
||||||
|
filepath.endsWith("CodeResources") ||
|
||||||
|
filepath.endsWith("PkgInfo") ||
|
||||||
|
filepath.endsWith(".modulemap")) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
QProcess otool;
|
||||||
|
otool.start("otool", QStringList() << "-L" << filepath);
|
||||||
|
otool.waitForFinished();
|
||||||
|
if (otool.exitStatus() != QProcess::NormalExit || otool.exitCode() != 0) {
|
||||||
|
qLog(Error) << "otool failed for" << filepath << ":" << otool.readAllStandardError();
|
||||||
|
success = false;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
QString output = otool.readAllStandardOutput();
|
||||||
|
QStringList output_lines = output.split("\n", Qt::SkipEmptyParts);
|
||||||
|
if (output_lines.size() < 2) {
|
||||||
|
qLog(Error) << "Could not parse otool output:" << output;
|
||||||
|
success = false;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
QString first_line = output_lines.first();
|
||||||
|
if (first_line.endsWith(':')) first_line.chop(1);
|
||||||
|
if (first_line == filepath) {
|
||||||
|
output_lines.removeFirst();
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
qLog(Error) << "First line" << first_line << "does not match" << filepath;
|
||||||
|
success = false;
|
||||||
|
}
|
||||||
|
QRegularExpression regexp(QStringLiteral("^\\t(.+) \\(compatibility version (\\d+\\.\\d+\\.\\d+), current version (\\d+\\.\\d+\\.\\d+)(, weak)?\\)$"));
|
||||||
|
for (const QString &output_line : output_lines) {
|
||||||
|
|
||||||
|
//qDebug() << "Final check on" << filepath << output_line;
|
||||||
|
|
||||||
|
QRegularExpressionMatch match = regexp.match(output_line);
|
||||||
|
if (match.hasMatch()) {
|
||||||
|
QString library = match.captured(1);
|
||||||
|
if (QFileInfo(library).fileName() == QFileInfo(filepath).fileName()) { // It's this.
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
else if (library.startsWith("@executable_path")) {
|
||||||
|
QString real_path = library;
|
||||||
|
real_path = real_path.replace("@executable_path", bundle_path + "/Contents/MacOS");
|
||||||
|
if (!QFile(real_path).exists()) {
|
||||||
|
qLog(Error) << real_path << "does not exist for" << filepath;
|
||||||
|
success = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (library.startsWith("@rpath")) {
|
||||||
|
QString real_path = library;
|
||||||
|
real_path = real_path.replace("@rpath", bundle_path + "/Contents/Frameworks");
|
||||||
|
if (!QFile(real_path).exists() && !real_path.endsWith("QtSvg")) { // FIXME: Ignore broken svg image plugin.
|
||||||
|
qLog(Error) << real_path << "does not exist for" << filepath;
|
||||||
|
success = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (library.startsWith("@loader_path")) {
|
||||||
|
QString loader_path = QFileInfo(filepath).path();
|
||||||
|
QString real_path = library;
|
||||||
|
real_path = real_path.replace("@loader_path", loader_path);
|
||||||
|
if (!QFile(real_path).exists()) {
|
||||||
|
qLog(Error) << real_path << "does not exist for" << filepath;
|
||||||
|
success = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (library.startsWith("/System/Library/") || library.startsWith("/usr/lib/")) { // System library
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
else if (library.endsWith("libgcc_s.1.dylib")) { // fftw points to it for some reason.
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
qLog(Error) << "File" << filepath << "points to" << library;
|
||||||
|
success = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
qLog(Error) << "Could not parse otool output line:" << output_line;
|
||||||
|
success = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return success ? 0 : 1;
|
||||||
|
|
||||||
|
}
|
||||||
@@ -3,26 +3,25 @@ cmake_minimum_required(VERSION 3.0)
|
|||||||
set(EXECUTABLE_OUTPUT_PATH ${CMAKE_BINARY_DIR})
|
set(EXECUTABLE_OUTPUT_PATH ${CMAKE_BINARY_DIR})
|
||||||
|
|
||||||
set(SOURCES main.cpp tagreaderworker.cpp)
|
set(SOURCES main.cpp tagreaderworker.cpp)
|
||||||
|
set(HEADERS tagreaderworker.h)
|
||||||
|
|
||||||
if(BUILD_WITH_QT6)
|
qt_wrap_cpp(MOC ${HEADERS})
|
||||||
qt6_wrap_cpp(MOC ${HEADERS})
|
|
||||||
qt6_add_resources(QRC data/data.qrc)
|
link_directories(${GLIB_LIBRARY_DIRS})
|
||||||
else()
|
|
||||||
qt5_wrap_cpp(MOC ${HEADERS})
|
if(USE_TAGLIB AND TAGLIB_FOUND)
|
||||||
qt5_add_resources(QRC data/data.qrc)
|
link_directories(${TAGLIB_LIBRARY_DIRS})
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
link_directories(
|
if(USE_TAGPARSER AND TAGPARSER_FOUND)
|
||||||
${GLIB_LIBRARY_DIRS}
|
link_directories(${TAGPARSER_LIBRARY_DIRS})
|
||||||
${TAGLIB_LIBRARY_DIRS}
|
endif()
|
||||||
)
|
|
||||||
|
|
||||||
add_executable(strawberry-tagreader ${SOURCES} ${MOC} ${QRC})
|
add_executable(strawberry-tagreader ${SOURCES} ${MOC} ${QRC})
|
||||||
|
|
||||||
target_include_directories(strawberry-tagreader SYSTEM PRIVATE
|
target_include_directories(strawberry-tagreader SYSTEM PRIVATE
|
||||||
${GLIB_INCLUDE_DIRS}
|
${GLIB_INCLUDE_DIRS}
|
||||||
${PROTOBUF_INCLUDE_DIRS}
|
${PROTOBUF_INCLUDE_DIRS}
|
||||||
${TAGLIB_INCLUDE_DIRS}
|
|
||||||
)
|
)
|
||||||
|
|
||||||
target_include_directories(strawberry-tagreader PRIVATE
|
target_include_directories(strawberry-tagreader PRIVATE
|
||||||
@@ -34,20 +33,29 @@ target_include_directories(strawberry-tagreader PRIVATE
|
|||||||
|
|
||||||
target_link_libraries(strawberry-tagreader PRIVATE
|
target_link_libraries(strawberry-tagreader PRIVATE
|
||||||
${GLIB_LIBRARIES}
|
${GLIB_LIBRARIES}
|
||||||
${TAGLIB_LIBRARIES}
|
|
||||||
${QtCore_LIBRARIES}
|
${QtCore_LIBRARIES}
|
||||||
${QtNetwork_LIBRARIES}
|
${QtNetwork_LIBRARIES}
|
||||||
libstrawberry-common
|
libstrawberry-common
|
||||||
libstrawberry-tagreader
|
libstrawberry-tagreader
|
||||||
)
|
)
|
||||||
|
|
||||||
if(${CMAKE_SYSTEM_NAME} MATCHES "FreeBSD")
|
if(USE_TAGLIB AND TAGLIB_FOUND)
|
||||||
|
target_include_directories(strawberry-tagreader SYSTEM PRIVATE ${TAGLIB_INCLUDE_DIRS})
|
||||||
|
target_link_libraries(strawberry-tagreader PRIVATE ${TAGLIB_LIBRARIES})
|
||||||
|
endif()
|
||||||
|
|
||||||
|
if(USE_TAGPARSER AND TAGPARSER_FOUND)
|
||||||
|
target_include_directories(strawberry-tagreader SYSTEM PRIVATE ${TAGPARSER_INCLUDE_DIRS})
|
||||||
|
target_link_libraries(strawberry-tagreader PRIVATE ${TAGPARSER_LIBRARIES})
|
||||||
|
endif()
|
||||||
|
|
||||||
|
if(FREEBSD)
|
||||||
target_link_libraries(strawberry-tagreader PRIVATE execinfo)
|
target_link_libraries(strawberry-tagreader PRIVATE execinfo)
|
||||||
endif(${CMAKE_SYSTEM_NAME} MATCHES "FreeBSD")
|
endif()
|
||||||
|
|
||||||
if(APPLE)
|
if(APPLE)
|
||||||
target_link_libraries(strawberry-tagreader PRIVATE /System/Library/Frameworks/Foundation.framework)
|
target_link_libraries(strawberry-tagreader PRIVATE /System/Library/Frameworks/Foundation.framework)
|
||||||
endif(APPLE)
|
endif()
|
||||||
|
|
||||||
if(APPLE)
|
if(APPLE)
|
||||||
install(TARGETS strawberry-tagreader DESTINATION ${CMAKE_BINARY_DIR}/strawberry.app/Contents/PlugIns)
|
install(TARGETS strawberry-tagreader DESTINATION ${CMAKE_BINARY_DIR}/strawberry.app/Contents/PlugIns)
|
||||||
|
|||||||
@@ -1,5 +0,0 @@
|
|||||||
<RCC>
|
|
||||||
<qresource prefix="/certs">
|
|
||||||
<file>godaddy-root.pem</file>
|
|
||||||
</qresource>
|
|
||||||
</RCC>
|
|
||||||
@@ -1,24 +0,0 @@
|
|||||||
-----BEGIN CERTIFICATE-----
|
|
||||||
MIIEADCCAuigAwIBAgIBADANBgkqhkiG9w0BAQUFADBjMQswCQYDVQQGEwJVUzEh
|
|
||||||
MB8GA1UEChMYVGhlIEdvIERhZGR5IEdyb3VwLCBJbmMuMTEwLwYDVQQLEyhHbyBE
|
|
||||||
YWRkeSBDbGFzcyAyIENlcnRpZmljYXRpb24gQXV0aG9yaXR5MB4XDTA0MDYyOTE3
|
|
||||||
MDYyMFoXDTM0MDYyOTE3MDYyMFowYzELMAkGA1UEBhMCVVMxITAfBgNVBAoTGFRo
|
|
||||||
ZSBHbyBEYWRkeSBHcm91cCwgSW5jLjExMC8GA1UECxMoR28gRGFkZHkgQ2xhc3Mg
|
|
||||||
MiBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTCCASAwDQYJKoZIhvcNAQEBBQADggEN
|
|
||||||
ADCCAQgCggEBAN6d1+pXGEmhW+vXX0iG6r7d/+TvZxz0ZWizV3GgXne77ZtJ6XCA
|
|
||||||
PVYYYwhv2vLM0D9/AlQiVBDYsoHUwHU9S3/Hd8M+eKsaA7Ugay9qK7HFiH7Eux6w
|
|
||||||
wdhFJ2+qN1j3hybX2C32qRe3H3I2TqYXP2WYktsqbl2i/ojgC95/5Y0V4evLOtXi
|
|
||||||
EqITLdiOr18SPaAIBQi2XKVlOARFmR6jYGB0xUGlcmIbYsUfb18aQr4CUWWoriMY
|
|
||||||
avx4A6lNf4DD+qta/KFApMoZFv6yyO9ecw3ud72a9nmYvLEHZ6IVDd2gWMZEewo+
|
|
||||||
YihfukEHU1jPEX44dMX4/7VpkI+EdOqXG68CAQOjgcAwgb0wHQYDVR0OBBYEFNLE
|
|
||||||
sNKR1EwRcbNhyz2h/t2oatTjMIGNBgNVHSMEgYUwgYKAFNLEsNKR1EwRcbNhyz2h
|
|
||||||
/t2oatTjoWekZTBjMQswCQYDVQQGEwJVUzEhMB8GA1UEChMYVGhlIEdvIERhZGR5
|
|
||||||
IEdyb3VwLCBJbmMuMTEwLwYDVQQLEyhHbyBEYWRkeSBDbGFzcyAyIENlcnRpZmlj
|
|
||||||
YXRpb24gQXV0aG9yaXR5ggEAMAwGA1UdEwQFMAMBAf8wDQYJKoZIhvcNAQEFBQAD
|
|
||||||
ggEBADJL87LKPpH8EsahB4yOd6AzBhRckB4Y9wimPQoZ+YeAEW5p5JYXMP80kWNy
|
|
||||||
OO7MHAGjHZQopDH2esRU1/blMVgDoszOYtuURXO1v0XJJLXVggKtI3lpjbi2Tc7P
|
|
||||||
TMozI+gciKqdi0FuFskg5YmezTvacPd+mSYgFFQlq25zheabIZ0KbIIOqPjCDPoQ
|
|
||||||
HmyW74cNxA9hi63ugyuV+I6ShHI56yDqg+2DzZduCLzrTia2cyvk0/ZM/iZx4mER
|
|
||||||
dEr/VxqHD3VILs9RaRegAhJhldXRQLIQTO7ErBBDpqWeCtWVYpoNz4iCxTIM5Cuf
|
|
||||||
ReYNnyicsbkqWletNw+vHX/bvZ8=
|
|
||||||
-----END CERTIFICATE-----
|
|
||||||
@@ -18,18 +18,18 @@
|
|||||||
|
|
||||||
#include "config.h"
|
#include "config.h"
|
||||||
|
|
||||||
#include <sys/time.h>
|
#include <QtGlobal>
|
||||||
|
|
||||||
|
#if QT_VERSION < QT_VERSION_CHECK(5, 10, 0)
|
||||||
|
# include <sys/time.h>
|
||||||
|
#endif
|
||||||
#include <iostream>
|
#include <iostream>
|
||||||
|
|
||||||
#include <QtGlobal>
|
|
||||||
#include <QCoreApplication>
|
#include <QCoreApplication>
|
||||||
#include <QList>
|
#include <QList>
|
||||||
#include <QLocalSocket>
|
#include <QString>
|
||||||
#include <QSsl>
|
|
||||||
#include <QSslCertificate>
|
|
||||||
#include <QSslSocket>
|
|
||||||
#include <QStringList>
|
#include <QStringList>
|
||||||
#include <QtDebug>
|
#include <QLocalSocket>
|
||||||
|
|
||||||
#include "core/logging.h"
|
#include "core/logging.h"
|
||||||
#include "tagreaderworker.h"
|
#include "tagreaderworker.h"
|
||||||
@@ -64,10 +64,6 @@ int main(int argc, char **argv) {
|
|||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
#if QT_VERSION < QT_VERSION_CHECK(5, 15, 0)
|
|
||||||
QSslSocket::addDefaultCaCertificates(QSslCertificate::fromPath(":/certs/godaddy-root.pem", QSsl::Pem));
|
|
||||||
#endif
|
|
||||||
|
|
||||||
TagReaderWorker worker(&socket);
|
TagReaderWorker worker(&socket);
|
||||||
|
|
||||||
return a.exec();
|
return a.exec();
|
||||||
|
|||||||
@@ -28,28 +28,34 @@
|
|||||||
#include "tagreaderworker.h"
|
#include "tagreaderworker.h"
|
||||||
|
|
||||||
TagReaderWorker::TagReaderWorker(QIODevice *socket, QObject *parent)
|
TagReaderWorker::TagReaderWorker(QIODevice *socket, QObject *parent)
|
||||||
: AbstractMessageHandler<spb::tagreader::Message>(socket, parent) {}
|
: AbstractMessageHandler<spb::tagreader::Message>(socket, parent) {}
|
||||||
|
|
||||||
void TagReaderWorker::MessageArrived(const spb::tagreader::Message &message) {
|
void TagReaderWorker::MessageArrived(const spb::tagreader::Message &message) {
|
||||||
|
|
||||||
spb::tagreader::Message reply;
|
spb::tagreader::Message reply;
|
||||||
|
|
||||||
if (message.has_read_file_request()) {
|
if (message.has_is_media_file_request()) {
|
||||||
|
reply.mutable_is_media_file_response()->set_success(tag_reader_.IsMediaFile(QStringFromStdString(message.is_media_file_request().filename())));
|
||||||
|
}
|
||||||
|
else if (message.has_read_file_request()) {
|
||||||
tag_reader_.ReadFile(QStringFromStdString(message.read_file_request().filename()), reply.mutable_read_file_response()->mutable_metadata());
|
tag_reader_.ReadFile(QStringFromStdString(message.read_file_request().filename()), reply.mutable_read_file_response()->mutable_metadata());
|
||||||
}
|
}
|
||||||
else if (message.has_save_file_request()) {
|
else if (message.has_save_file_request()) {
|
||||||
reply.mutable_save_file_response()->set_success(tag_reader_.SaveFile(QStringFromStdString(message.save_file_request().filename()), message.save_file_request().metadata()));
|
reply.mutable_save_file_response()->set_success(tag_reader_.SaveFile(QStringFromStdString(message.save_file_request().filename()), message.save_file_request().metadata()));
|
||||||
}
|
}
|
||||||
|
|
||||||
else if (message.has_is_media_file_request()) {
|
|
||||||
reply.mutable_is_media_file_response()->set_success(tag_reader_.IsMediaFile(QStringFromStdString(message.is_media_file_request().filename())));
|
|
||||||
}
|
|
||||||
else if (message.has_load_embedded_art_request()) {
|
else if (message.has_load_embedded_art_request()) {
|
||||||
QByteArray data = tag_reader_.LoadEmbeddedArt(QStringFromStdString(message.load_embedded_art_request().filename()));
|
QByteArray data = tag_reader_.LoadEmbeddedArt(QStringFromStdString(message.load_embedded_art_request().filename()));
|
||||||
reply.mutable_load_embedded_art_response()->set_data(data.constData(), data.size());
|
reply.mutable_load_embedded_art_response()->set_data(data.constData(), data.size());
|
||||||
}
|
}
|
||||||
else if (message.has_save_embedded_art_request()) {
|
else if (message.has_save_embedded_art_request()) {
|
||||||
reply.mutable_save_embedded_art_response()->set_success(tag_reader_.SaveEmbeddedArt(QStringFromStdString(message.save_embedded_art_request().filename()), QByteArray(message.save_embedded_art_request().data().data(), message.save_embedded_art_request().data().size())));
|
reply.mutable_save_embedded_art_response()->set_success(tag_reader_.SaveEmbeddedArt(QStringFromStdString(message.save_embedded_art_request().filename()), QByteArray(message.save_embedded_art_request().data().data(), static_cast<qint64>(message.save_embedded_art_request().data().size()))));
|
||||||
|
}
|
||||||
|
|
||||||
|
else if (message.has_save_song_playcount_to_file_request()) {
|
||||||
|
reply.mutable_save_song_playcount_to_file_response()->set_success(tag_reader_.SaveSongPlaycountToFile(QStringFromStdString(message.save_song_playcount_to_file_request().filename()), message.save_song_playcount_to_file_request().metadata()));
|
||||||
|
}
|
||||||
|
else if (message.has_save_song_rating_to_file_request()) {
|
||||||
|
reply.mutable_save_song_rating_to_file_response()->set_success(tag_reader_.SaveSongRatingToFile(QStringFromStdString(message.save_song_rating_to_file_request().filename()), message.save_song_rating_to_file_request().metadata()));
|
||||||
}
|
}
|
||||||
|
|
||||||
SendReply(message, &reply);
|
SendReply(message, &reply);
|
||||||
@@ -59,5 +65,5 @@ void TagReaderWorker::MessageArrived(const spb::tagreader::Message &message) {
|
|||||||
void TagReaderWorker::DeviceClosed() {
|
void TagReaderWorker::DeviceClosed() {
|
||||||
AbstractMessageHandler<spb::tagreader::Message>::DeviceClosed();
|
AbstractMessageHandler<spb::tagreader::Message>::DeviceClosed();
|
||||||
|
|
||||||
qApp->exit();
|
QCoreApplication::exit();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -24,12 +24,18 @@
|
|||||||
#include <QObject>
|
#include <QObject>
|
||||||
|
|
||||||
#include "core/messagehandler.h"
|
#include "core/messagehandler.h"
|
||||||
#include "tagreader.h"
|
#if defined(USE_TAGLIB)
|
||||||
|
# include "tagreadertaglib.h"
|
||||||
|
#elif defined(USE_TAGPARSER)
|
||||||
|
# include "tagreadertagparser.h"
|
||||||
|
#endif
|
||||||
#include "tagreadermessages.pb.h"
|
#include "tagreadermessages.pb.h"
|
||||||
|
|
||||||
class QIODevice;
|
class QIODevice;
|
||||||
|
|
||||||
class TagReaderWorker : public AbstractMessageHandler<spb::tagreader::Message> {
|
class TagReaderWorker : public AbstractMessageHandler<spb::tagreader::Message> {
|
||||||
|
Q_OBJECT
|
||||||
|
|
||||||
public:
|
public:
|
||||||
explicit TagReaderWorker(QIODevice *socket, QObject *parent = nullptr);
|
explicit TagReaderWorker(QIODevice *socket, QObject *parent = nullptr);
|
||||||
|
|
||||||
@@ -38,7 +44,11 @@ class TagReaderWorker : public AbstractMessageHandler<spb::tagreader::Message> {
|
|||||||
void DeviceClosed() override;
|
void DeviceClosed() override;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
TagReader tag_reader_;
|
#if defined(USE_TAGLIB)
|
||||||
|
TagReaderTagLib tag_reader_;
|
||||||
|
#elif defined(USE_TAGPARSER)
|
||||||
|
TagReaderTagParser tag_reader_;
|
||||||
|
#endif
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif // TAGREADERWORKER_H
|
#endif // TAGREADERWORKER_H
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ cmake_minimum_required(VERSION 3.0)
|
|||||||
|
|
||||||
if(HAVE_TRANSLATIONS)
|
if(HAVE_TRANSLATIONS)
|
||||||
include(../cmake/Translations.cmake)
|
include(../cmake/Translations.cmake)
|
||||||
endif(HAVE_TRANSLATIONS)
|
endif()
|
||||||
|
|
||||||
set(SOURCES
|
set(SOURCES
|
||||||
core/mainwindow.cpp
|
core/mainwindow.cpp
|
||||||
@@ -11,6 +11,7 @@ set(SOURCES
|
|||||||
core/player.cpp
|
core/player.cpp
|
||||||
core/commandlineoptions.cpp
|
core/commandlineoptions.cpp
|
||||||
core/database.cpp
|
core/database.cpp
|
||||||
|
core/sqlquery.cpp
|
||||||
core/metatypes.cpp
|
core/metatypes.cpp
|
||||||
core/deletefiles.cpp
|
core/deletefiles.cpp
|
||||||
core/filesystemmusicstorage.cpp
|
core/filesystemmusicstorage.cpp
|
||||||
@@ -36,11 +37,10 @@ set(SOURCES
|
|||||||
core/utilities.cpp
|
core/utilities.cpp
|
||||||
core/imageutils.cpp
|
core/imageutils.cpp
|
||||||
core/iconloader.cpp
|
core/iconloader.cpp
|
||||||
core/qtsystemtrayicon.cpp
|
|
||||||
core/standarditemiconloader.cpp
|
core/standarditemiconloader.cpp
|
||||||
core/systemtrayicon.cpp
|
|
||||||
core/scopedtransaction.cpp
|
core/scopedtransaction.cpp
|
||||||
core/translations.cpp
|
core/translations.cpp
|
||||||
|
core/systemtrayicon.cpp
|
||||||
|
|
||||||
engine/enginetype.cpp
|
engine/enginetype.cpp
|
||||||
engine/enginebase.cpp
|
engine/enginebase.cpp
|
||||||
@@ -76,6 +76,7 @@ set(SOURCES
|
|||||||
collection/sqlrow.cpp
|
collection/sqlrow.cpp
|
||||||
collection/savedgroupingmanager.cpp
|
collection/savedgroupingmanager.cpp
|
||||||
collection/groupbydialog.cpp
|
collection/groupbydialog.cpp
|
||||||
|
collection/collectiontask.cpp
|
||||||
|
|
||||||
playlist/playlist.cpp
|
playlist/playlist.cpp
|
||||||
playlist/playlistbackend.cpp
|
playlist/playlistbackend.cpp
|
||||||
@@ -215,9 +216,6 @@ set(SOURCES
|
|||||||
osd/osdbase.cpp
|
osd/osdbase.cpp
|
||||||
osd/osdpretty.cpp
|
osd/osdpretty.cpp
|
||||||
|
|
||||||
musicbrainz/acoustidclient.cpp
|
|
||||||
musicbrainz/musicbrainzclient.cpp
|
|
||||||
|
|
||||||
internet/internetservices.cpp
|
internet/internetservices.cpp
|
||||||
internet/internetservice.cpp
|
internet/internetservice.cpp
|
||||||
internet/internetplaylistitem.cpp
|
internet/internetplaylistitem.cpp
|
||||||
@@ -232,6 +230,17 @@ set(SOURCES
|
|||||||
internet/internetcollectionviewcontainer.cpp
|
internet/internetcollectionviewcontainer.cpp
|
||||||
internet/internetsearchview.cpp
|
internet/internetsearchview.cpp
|
||||||
|
|
||||||
|
radios/radioservices.cpp
|
||||||
|
radios/radiobackend.cpp
|
||||||
|
radios/radiomodel.cpp
|
||||||
|
radios/radioview.cpp
|
||||||
|
radios/radioviewcontainer.cpp
|
||||||
|
radios/radioservice.cpp
|
||||||
|
radios/radioplaylistitem.cpp
|
||||||
|
radios/radiochannel.cpp
|
||||||
|
radios/somafmservice.cpp
|
||||||
|
radios/radioparadiseservice.cpp
|
||||||
|
|
||||||
scrobbler/audioscrobbler.cpp
|
scrobbler/audioscrobbler.cpp
|
||||||
scrobbler/scrobblerservices.cpp
|
scrobbler/scrobblerservices.cpp
|
||||||
scrobbler/scrobblerservice.cpp
|
scrobbler/scrobblerservice.cpp
|
||||||
@@ -253,11 +262,13 @@ set(SOURCES
|
|||||||
set(HEADERS
|
set(HEADERS
|
||||||
core/mainwindow.h
|
core/mainwindow.h
|
||||||
core/application.h
|
core/application.h
|
||||||
|
core/appearance.h
|
||||||
core/player.h
|
core/player.h
|
||||||
core/database.h
|
core/database.h
|
||||||
core/deletefiles.h
|
core/deletefiles.h
|
||||||
core/filesystemwatcherinterface.h
|
core/filesystemwatcherinterface.h
|
||||||
core/mergedproxymodel.h
|
core/mergedproxymodel.h
|
||||||
|
core/multisortfilterproxy.h
|
||||||
core/networkaccessmanager.h
|
core/networkaccessmanager.h
|
||||||
core/threadsafenetworkdiskcache.h
|
core/threadsafenetworkdiskcache.h
|
||||||
core/networktimeouts.h
|
core/networktimeouts.h
|
||||||
@@ -265,10 +276,11 @@ set(HEADERS
|
|||||||
core/songloader.h
|
core/songloader.h
|
||||||
core/tagreaderclient.h
|
core/tagreaderclient.h
|
||||||
core/taskmanager.h
|
core/taskmanager.h
|
||||||
|
core/thread.h
|
||||||
core/urlhandler.h
|
core/urlhandler.h
|
||||||
core/qtsystemtrayicon.h
|
|
||||||
core/standarditemiconloader.h
|
core/standarditemiconloader.h
|
||||||
core/systemtrayicon.h
|
core/translations.h
|
||||||
|
core/potranslator.h
|
||||||
core/mimedata.h
|
core/mimedata.h
|
||||||
core/stylesheetloader.h
|
core/stylesheetloader.h
|
||||||
|
|
||||||
@@ -310,6 +322,7 @@ set(HEADERS
|
|||||||
playlist/playlistlistcontainer.h
|
playlist/playlistlistcontainer.h
|
||||||
playlist/playlistlistmodel.h
|
playlist/playlistlistmodel.h
|
||||||
playlist/playlistlistview.h
|
playlist/playlistlistview.h
|
||||||
|
playlist/playlistlistsortfiltermodel.h
|
||||||
playlist/playlistmanager.h
|
playlist/playlistmanager.h
|
||||||
playlist/playlistsaveoptionsdialog.h
|
playlist/playlistsaveoptionsdialog.h
|
||||||
playlist/playlistsequence.h
|
playlist/playlistsequence.h
|
||||||
@@ -330,10 +343,13 @@ set(HEADERS
|
|||||||
playlistparsers/parserbase.h
|
playlistparsers/parserbase.h
|
||||||
playlistparsers/playlistparser.h
|
playlistparsers/playlistparser.h
|
||||||
playlistparsers/plsparser.h
|
playlistparsers/plsparser.h
|
||||||
|
playlistparsers/wplparser.h
|
||||||
|
playlistparsers/xmlparser.h
|
||||||
playlistparsers/xspfparser.h
|
playlistparsers/xspfparser.h
|
||||||
|
|
||||||
smartplaylists/playlistgenerator.h
|
smartplaylists/playlistgenerator.h
|
||||||
smartplaylists/playlistgeneratorinserter.h
|
smartplaylists/playlistgeneratorinserter.h
|
||||||
|
smartplaylists/playlistquerygenerator.h
|
||||||
smartplaylists/playlistgeneratormimedata.h
|
smartplaylists/playlistgeneratormimedata.h
|
||||||
smartplaylists/smartplaylistquerywizardplugin.h
|
smartplaylists/smartplaylistquerywizardplugin.h
|
||||||
smartplaylists/smartplaylistsearchpreview.h
|
smartplaylists/smartplaylistsearchpreview.h
|
||||||
@@ -428,24 +444,33 @@ set(HEADERS
|
|||||||
widgets/loginstatewidget.h
|
widgets/loginstatewidget.h
|
||||||
widgets/qsearchfield.h
|
widgets/qsearchfield.h
|
||||||
widgets/ratingwidget.h
|
widgets/ratingwidget.h
|
||||||
|
widgets/forcescrollperpixel.h
|
||||||
|
|
||||||
osd/osdbase.h
|
osd/osdbase.h
|
||||||
osd/osdpretty.h
|
osd/osdpretty.h
|
||||||
|
|
||||||
musicbrainz/acoustidclient.h
|
|
||||||
musicbrainz/musicbrainzclient.h
|
|
||||||
|
|
||||||
internet/internetservices.h
|
internet/internetservices.h
|
||||||
internet/internetservice.h
|
internet/internetservice.h
|
||||||
internet/internetsongmimedata.h
|
internet/internetsongmimedata.h
|
||||||
internet/internetsearchview.h
|
|
||||||
internet/internetsearchmodel.h
|
internet/internetsearchmodel.h
|
||||||
|
internet/internetsearchsortmodel.h
|
||||||
|
internet/internetsearchitemdelegate.h
|
||||||
|
internet/internetsearchview.h
|
||||||
internet/localredirectserver.h
|
internet/localredirectserver.h
|
||||||
internet/internetsongsview.h
|
internet/internetsongsview.h
|
||||||
internet/internettabsview.h
|
internet/internettabsview.h
|
||||||
internet/internetcollectionview.h
|
internet/internetcollectionview.h
|
||||||
internet/internetcollectionviewcontainer.h
|
internet/internetcollectionviewcontainer.h
|
||||||
internet/internetsearchview.h
|
|
||||||
|
radios/radioservices.h
|
||||||
|
radios/radiobackend.h
|
||||||
|
radios/radiomodel.h
|
||||||
|
radios/radioview.h
|
||||||
|
radios/radioviewcontainer.h
|
||||||
|
radios/radioservice.h
|
||||||
|
radios/radiomimedata.h
|
||||||
|
radios/somafmservice.h
|
||||||
|
radios/radioparadiseservice.h
|
||||||
|
|
||||||
scrobbler/audioscrobbler.h
|
scrobbler/audioscrobbler.h
|
||||||
scrobbler/scrobblerservices.h
|
scrobbler/scrobblerservices.h
|
||||||
@@ -530,6 +555,8 @@ set(UI
|
|||||||
internet/internetcollectionviewcontainer.ui
|
internet/internetcollectionviewcontainer.ui
|
||||||
internet/internetsearchview.ui
|
internet/internetsearchview.ui
|
||||||
|
|
||||||
|
radios/radioviewcontainer.ui
|
||||||
|
|
||||||
organize/organizedialog.ui
|
organize/organizedialog.ui
|
||||||
organize/organizeerrordialog.ui
|
organize/organizeerrordialog.ui
|
||||||
|
|
||||||
@@ -542,7 +569,7 @@ option(USE_INSTALL_PREFIX "Look for data in CMAKE_INSTALL_PREFIX" ON)
|
|||||||
|
|
||||||
if(NOT APPLE)
|
if(NOT APPLE)
|
||||||
set(NOT_APPLE ON)
|
set(NOT_APPLE ON)
|
||||||
optional_source(NOT_APPLE SOURCES widgets/qsearchfield_nonmac.cpp)
|
optional_source(NOT_APPLE SOURCES widgets/qsearchfield_nonmac.cpp core/qtsystemtrayicon.cpp HEADERS core/qtsystemtrayicon.h)
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
if(HAVE_GLOBALSHORTCUTS)
|
if(HAVE_GLOBALSHORTCUTS)
|
||||||
@@ -551,211 +578,75 @@ if(HAVE_GLOBALSHORTCUTS)
|
|||||||
HEADERS globalshortcuts/globalshortcutsmanager.h globalshortcuts/globalshortcutsbackend.h globalshortcuts/globalshortcutgrabber.h settings/globalshortcutssettingspage.h
|
HEADERS globalshortcuts/globalshortcutsmanager.h globalshortcuts/globalshortcutsbackend.h globalshortcuts/globalshortcutgrabber.h settings/globalshortcutssettingspage.h
|
||||||
UI globalshortcuts/globalshortcutgrabber.ui settings/globalshortcutssettingspage.ui
|
UI globalshortcuts/globalshortcutgrabber.ui settings/globalshortcutssettingspage.ui
|
||||||
)
|
)
|
||||||
if(HAVE_X11EXTRAS OR WIN32)
|
|
||||||
set(X11_OR_WIN ON)
|
|
||||||
endif()
|
|
||||||
optional_source(X11_OR_WIN
|
|
||||||
SOURCES globalshortcuts/globalshortcutsbackend-system.cpp globalshortcuts/globalshortcut.cpp
|
|
||||||
HEADERS globalshortcuts/globalshortcutsbackend-system.h globalshortcuts/globalshortcut.h
|
|
||||||
)
|
|
||||||
optional_source(HAVE_X11EXTRAS
|
|
||||||
SOURCES globalshortcuts/globalshortcut-x11.cpp
|
|
||||||
)
|
|
||||||
optional_source(HAVE_DBUS
|
optional_source(HAVE_DBUS
|
||||||
SOURCES globalshortcuts/globalshortcutsbackend-gsd.cpp globalshortcuts/globalshortcutsbackend-kde.cpp
|
SOURCES globalshortcuts/globalshortcutsbackend-kde.cpp globalshortcuts/globalshortcutsbackend-gnome.cpp globalshortcuts/globalshortcutsbackend-mate.cpp
|
||||||
HEADERS globalshortcuts/globalshortcutsbackend-gsd.h globalshortcuts/globalshortcutsbackend-kde.h
|
HEADERS globalshortcuts/globalshortcutsbackend-kde.h globalshortcuts/globalshortcutsbackend-gnome.h globalshortcuts/globalshortcutsbackend-mate.h
|
||||||
|
)
|
||||||
|
optional_source(HAVE_X11_GLOBALSHORTCUTS
|
||||||
|
SOURCES globalshortcuts/globalshortcutsbackend-x11.cpp globalshortcuts/globalshortcut.cpp globalshortcuts/globalshortcut-x11.cpp
|
||||||
|
HEADERS globalshortcuts/globalshortcutsbackend-x11.h globalshortcuts/globalshortcut.h
|
||||||
)
|
)
|
||||||
optional_source(WIN32
|
optional_source(WIN32
|
||||||
SOURCES globalshortcuts/globalshortcut-win.cpp
|
SOURCES globalshortcuts/globalshortcutsbackend-win.cpp globalshortcuts/globalshortcut.cpp globalshortcuts/globalshortcut-win.cpp
|
||||||
|
HEADERS globalshortcuts/globalshortcutsbackend-win.h globalshortcuts/globalshortcut.h
|
||||||
)
|
)
|
||||||
endif(HAVE_GLOBALSHORTCUTS)
|
endif(HAVE_GLOBALSHORTCUTS)
|
||||||
|
|
||||||
# ALSA
|
# ALSA
|
||||||
optional_source(HAVE_ALSA
|
optional_source(HAVE_ALSA SOURCES engine/alsadevicefinder.cpp engine/alsapcmdevicefinder.cpp)
|
||||||
SOURCES
|
|
||||||
engine/alsadevicefinder.cpp
|
|
||||||
)
|
|
||||||
|
|
||||||
# DBUS
|
# DBUS
|
||||||
optional_source(HAVE_DBUS
|
optional_source(HAVE_DBUS SOURCES osd/osddbus.cpp HEADERS osd/osddbus.h)
|
||||||
SOURCES
|
|
||||||
osd/osddbus.cpp
|
|
||||||
HEADERS
|
|
||||||
osd/osddbus.h
|
|
||||||
)
|
|
||||||
|
|
||||||
# GStreamer
|
# GStreamer
|
||||||
optional_source(HAVE_GSTREAMER
|
optional_source(HAVE_GSTREAMER
|
||||||
SOURCES engine/gststartup.cpp engine/gstengine.cpp engine/gstenginepipeline.cpp engine/gstelementdeleter.cpp
|
SOURCES engine/gststartup.cpp engine/gstengine.cpp engine/gstenginepipeline.cpp
|
||||||
HEADERS engine/gststartup.h engine/gstengine.h engine/gstenginepipeline.h engine/gstelementdeleter.h
|
HEADERS engine/gststartup.h engine/gstengine.h engine/gstenginepipeline.h
|
||||||
)
|
)
|
||||||
|
|
||||||
# VLC
|
# VLC
|
||||||
optional_source(HAVE_VLC
|
optional_source(HAVE_VLC SOURCES engine/vlcengine.cpp HEADERS engine/vlcengine.h)
|
||||||
SOURCES engine/vlcengine.cpp
|
|
||||||
HEADERS engine/vlcengine.h
|
|
||||||
)
|
|
||||||
|
|
||||||
# DBUS and MPRIS - Unix specific
|
# DBUS and MPRIS - Unix specific
|
||||||
if(UNIX AND HAVE_DBUS)
|
if(UNIX AND HAVE_DBUS)
|
||||||
file(MAKE_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/dbus)
|
file(MAKE_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/dbus)
|
||||||
|
|
||||||
optional_source(HAVE_DBUS
|
optional_source(HAVE_DBUS SOURCES core/mpris2.cpp HEADERS core/mpris2.h)
|
||||||
SOURCES core/mpris.cpp core/mpris2.cpp
|
optional_source(HAVE_UDISKS2 SOURCES device/udisks2lister.cpp HEADERS device/udisks2lister.h)
|
||||||
HEADERS core/mpris.h core/mpris2.h
|
|
||||||
)
|
|
||||||
optional_source(HAVE_UDISKS2
|
|
||||||
SOURCES device/udisks2lister.cpp
|
|
||||||
HEADERS device/udisks2lister.h
|
|
||||||
)
|
|
||||||
|
|
||||||
if (BUILD_WITH_QT6)
|
# MPRIS 2.0 DBUS interfaces
|
||||||
|
qt_add_dbus_adaptor(SOURCES dbus/org.mpris.MediaPlayer2.Player.xml core/mpris2.h mpris::Mpris2 core/mpris2_player Mpris2Player)
|
||||||
|
qt_add_dbus_adaptor(SOURCES dbus/org.mpris.MediaPlayer2.xml core/mpris2.h mpris::Mpris2 core/mpris2_root Mpris2Root)
|
||||||
|
qt_add_dbus_adaptor(SOURCES dbus/org.mpris.MediaPlayer2.TrackList.xml core/mpris2.h mpris::Mpris2 core/mpris2_tracklist Mpris2TrackList)
|
||||||
|
|
||||||
# MPRIS 2.0 DBUS interfaces
|
# MPRIS 2.1 DBUS interfaces
|
||||||
qt6_add_dbus_adaptor(SOURCES
|
qt_add_dbus_adaptor(SOURCES dbus/org.mpris.MediaPlayer2.Playlists.xml core/mpris2.h mpris::Mpris2 core/mpris2_playlists Mpris2Playlists)
|
||||||
dbus/org.mpris.MediaPlayer2.Player.xml
|
|
||||||
core/mpris2.h mpris::Mpris2 core/mpris2_player Mpris2Player)
|
|
||||||
qt6_add_dbus_adaptor(SOURCES
|
|
||||||
dbus/org.mpris.MediaPlayer2.xml
|
|
||||||
core/mpris2.h mpris::Mpris2 core/mpris2_root Mpris2Root)
|
|
||||||
qt6_add_dbus_adaptor(SOURCES
|
|
||||||
dbus/org.mpris.MediaPlayer2.TrackList.xml
|
|
||||||
core/mpris2.h mpris::Mpris2 core/mpris2_tracklist Mpris2TrackList)
|
|
||||||
|
|
||||||
# MPRIS 2.1 DBUS interfaces
|
# org.freedesktop.Notifications DBUS interface
|
||||||
qt6_add_dbus_adaptor(SOURCES
|
qt_add_dbus_interface(SOURCES dbus/org.freedesktop.Notifications.xml dbus/notification)
|
||||||
dbus/org.mpris.MediaPlayer2.Playlists.xml
|
|
||||||
core/mpris2.h mpris::Mpris2 core/mpris2_playlists Mpris2Playlists)
|
|
||||||
|
|
||||||
# org.freedesktop.Notifications DBUS interface
|
# org.gnome.SettingsDaemon interface
|
||||||
qt6_add_dbus_interface(SOURCES
|
qt_add_dbus_interface(SOURCES dbus/org.gnome.SettingsDaemon.MediaKeys.xml dbus/gnomesettingsdaemon)
|
||||||
dbus/org.freedesktop.Notifications.xml
|
|
||||||
dbus/notification)
|
|
||||||
|
|
||||||
# org.gnome.SettingsDaemon interface
|
# org.mate.SettingsDaemon interface
|
||||||
qt6_add_dbus_interface(SOURCES
|
qt_add_dbus_interface(SOURCES dbus/org.mate.SettingsDaemon.MediaKeys.xml dbus/matesettingsdaemon)
|
||||||
dbus/org.gnome.SettingsDaemon.MediaKeys.xml
|
|
||||||
dbus/gnomesettingsdaemon)
|
|
||||||
|
|
||||||
# org.kde.KGlobalAccel interface
|
# org.kde.KGlobalAccel interface
|
||||||
qt6_add_dbus_interface(SOURCES
|
qt_add_dbus_interface(SOURCES dbus/org.kde.KGlobalAccel.xml dbus/kglobalaccel)
|
||||||
dbus/org.kde.KGlobalAccel.xml
|
qt_add_dbus_interface(SOURCES dbus/org.kde.KGlobalAccel.Component.xml dbus/kglobalaccelcomponent)
|
||||||
dbus/kglobalaccel)
|
|
||||||
qt6_add_dbus_interface(SOURCES
|
|
||||||
dbus/org.kde.KGlobalAccel.Component.xml
|
|
||||||
dbus/kglobalaccelcomponent)
|
|
||||||
|
|
||||||
else()
|
|
||||||
|
|
||||||
# MPRIS 2.0 DBUS interfaces
|
|
||||||
qt5_add_dbus_adaptor(SOURCES
|
|
||||||
dbus/org.mpris.MediaPlayer2.Player.xml
|
|
||||||
core/mpris2.h mpris::Mpris2 core/mpris2_player Mpris2Player)
|
|
||||||
qt5_add_dbus_adaptor(SOURCES
|
|
||||||
dbus/org.mpris.MediaPlayer2.xml
|
|
||||||
core/mpris2.h mpris::Mpris2 core/mpris2_root Mpris2Root)
|
|
||||||
qt5_add_dbus_adaptor(SOURCES
|
|
||||||
dbus/org.mpris.MediaPlayer2.TrackList.xml
|
|
||||||
core/mpris2.h mpris::Mpris2 core/mpris2_tracklist Mpris2TrackList)
|
|
||||||
|
|
||||||
# MPRIS 2.1 DBUS interfaces
|
|
||||||
qt5_add_dbus_adaptor(SOURCES
|
|
||||||
dbus/org.mpris.MediaPlayer2.Playlists.xml
|
|
||||||
core/mpris2.h mpris::Mpris2 core/mpris2_playlists Mpris2Playlists)
|
|
||||||
|
|
||||||
# org.freedesktop.Notifications DBUS interface
|
|
||||||
qt5_add_dbus_interface(SOURCES
|
|
||||||
dbus/org.freedesktop.Notifications.xml
|
|
||||||
dbus/notification)
|
|
||||||
|
|
||||||
# org.gnome.SettingsDaemon interface
|
|
||||||
qt5_add_dbus_interface(SOURCES
|
|
||||||
dbus/org.gnome.SettingsDaemon.MediaKeys.xml
|
|
||||||
dbus/gnomesettingsdaemon)
|
|
||||||
|
|
||||||
# org.kde.KGlobalAccel interface
|
|
||||||
qt5_add_dbus_interface(SOURCES
|
|
||||||
dbus/org.kde.KGlobalAccel.xml
|
|
||||||
dbus/kglobalaccel)
|
|
||||||
qt5_add_dbus_interface(SOURCES
|
|
||||||
dbus/org.kde.KGlobalAccel.Component.xml
|
|
||||||
dbus/kglobalaccelcomponent)
|
|
||||||
|
|
||||||
endif()
|
|
||||||
|
|
||||||
# org.freedesktop.Avahi.Server interface
|
|
||||||
add_custom_command(
|
|
||||||
OUTPUT
|
|
||||||
${CMAKE_CURRENT_BINARY_DIR}/dbus/avahiserver.cpp
|
|
||||||
${CMAKE_CURRENT_BINARY_DIR}/dbus/avahiserver.h
|
|
||||||
COMMAND ${QT_DBUSXML2CPP_EXECUTABLE}
|
|
||||||
dbus/org.freedesktop.Avahi.Server.xml
|
|
||||||
-p ${CMAKE_CURRENT_BINARY_DIR}/dbus/avahiserver
|
|
||||||
-i dbus/metatypes.h
|
|
||||||
DEPENDS dbus/org.freedesktop.Avahi.Server.xml
|
|
||||||
WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR})
|
|
||||||
list(APPEND HEADERS ${CMAKE_CURRENT_BINARY_DIR}/dbus/avahiserver.h)
|
|
||||||
list(APPEND SOURCES ${CMAKE_CURRENT_BINARY_DIR}/dbus/avahiserver.cpp)
|
|
||||||
|
|
||||||
# org.freedesktop.Avahi.EntryGroup interface
|
|
||||||
add_custom_command(
|
|
||||||
OUTPUT
|
|
||||||
${CMAKE_CURRENT_BINARY_DIR}/dbus/avahientrygroup.cpp
|
|
||||||
${CMAKE_CURRENT_BINARY_DIR}/dbus/avahientrygroup.h
|
|
||||||
COMMAND ${QT_DBUSXML2CPP_EXECUTABLE}
|
|
||||||
dbus/org.freedesktop.Avahi.EntryGroup.xml
|
|
||||||
-p ${CMAKE_CURRENT_BINARY_DIR}/dbus/avahientrygroup
|
|
||||||
-i dbus/metatypes.h
|
|
||||||
DEPENDS dbus/org.freedesktop.Avahi.EntryGroup.xml
|
|
||||||
WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR})
|
|
||||||
list(APPEND HEADERS ${CMAKE_CURRENT_BINARY_DIR}/dbus/avahientrygroup.h)
|
|
||||||
list(APPEND SOURCES ${CMAKE_CURRENT_BINARY_DIR}/dbus/avahientrygroup.cpp)
|
|
||||||
|
|
||||||
if(HAVE_UDISKS2)
|
if(HAVE_UDISKS2)
|
||||||
set_source_files_properties(dbus/org.freedesktop.DBus.ObjectManager.xml
|
set_source_files_properties(dbus/org.freedesktop.DBus.ObjectManager.xml PROPERTIES NO_NAMESPACE dbus/objectmanager INCLUDE dbus/metatypes.h)
|
||||||
PROPERTIES NO_NAMESPACE dbus/objectmanager INCLUDE dbus/metatypes.h)
|
set_source_files_properties(dbus/org.freedesktop.UDisks2.Filesystem.xml PROPERTIES NO_NAMESPACE dbus/udisks2filesystem INCLUDE dbus/metatypes.h)
|
||||||
set_source_files_properties(dbus/org.freedesktop.UDisks2.Filesystem.xml
|
set_source_files_properties(dbus/org.freedesktop.UDisks2.Block.xml PROPERTIES NO_NAMESPACE dbus/udisks2block INCLUDE dbus/metatypes.h)
|
||||||
PROPERTIES NO_NAMESPACE dbus/udisks2filesystem INCLUDE dbus/metatypes.h)
|
set_source_files_properties(dbus/org.freedesktop.UDisks2.Drive.xml PROPERTIES NO_NAMESPACE dbus/udisks2drive INCLUDE dbus/metatypes.h)
|
||||||
set_source_files_properties(dbus/org.freedesktop.UDisks2.Block.xml
|
set_source_files_properties(dbus/org.freedesktop.UDisks2.Job.xml PROPERTIES NO_NAMESPACE dbus/udisks2job INCLUDE dbus/metatypes.h)
|
||||||
PROPERTIES NO_NAMESPACE dbus/udisks2block INCLUDE dbus/metatypes.h)
|
qt_add_dbus_interface(SOURCES dbus/org.freedesktop.DBus.ObjectManager.xml dbus/objectmanager)
|
||||||
set_source_files_properties(dbus/org.freedesktop.UDisks2.Drive.xml
|
qt_add_dbus_interface(SOURCES dbus/org.freedesktop.UDisks2.Filesystem.xml dbus/udisks2filesystem)
|
||||||
PROPERTIES NO_NAMESPACE dbus/udisks2drive INCLUDE dbus/metatypes.h)
|
qt_add_dbus_interface(SOURCES dbus/org.freedesktop.UDisks2.Block.xml dbus/udisks2block)
|
||||||
set_source_files_properties(dbus/org.freedesktop.UDisks2.Job.xml
|
qt_add_dbus_interface(SOURCES dbus/org.freedesktop.UDisks2.Drive.xml dbus/udisks2drive)
|
||||||
PROPERTIES NO_NAMESPACE dbus/udisks2job INCLUDE dbus/metatypes.h)
|
qt_add_dbus_interface(SOURCES dbus/org.freedesktop.UDisks2.Job.xml dbus/udisks2job)
|
||||||
if(BUILD_WITH_QT6)
|
endif(HAVE_UDISKS2)
|
||||||
qt6_add_dbus_interface(SOURCES
|
|
||||||
dbus/org.freedesktop.DBus.ObjectManager.xml
|
|
||||||
dbus/objectmanager)
|
|
||||||
qt6_add_dbus_interface(SOURCES
|
|
||||||
dbus/org.freedesktop.UDisks2.Filesystem.xml
|
|
||||||
dbus/udisks2filesystem)
|
|
||||||
qt6_add_dbus_interface(SOURCES
|
|
||||||
dbus/org.freedesktop.UDisks2.Block.xml
|
|
||||||
dbus/udisks2block)
|
|
||||||
qt6_add_dbus_interface(SOURCES
|
|
||||||
dbus/org.freedesktop.UDisks2.Drive.xml
|
|
||||||
dbus/udisks2drive)
|
|
||||||
qt6_add_dbus_interface(SOURCES
|
|
||||||
dbus/org.freedesktop.UDisks2.Job.xml
|
|
||||||
dbus/udisks2job)
|
|
||||||
else()
|
|
||||||
qt5_add_dbus_interface(SOURCES
|
|
||||||
dbus/org.freedesktop.DBus.ObjectManager.xml
|
|
||||||
dbus/objectmanager)
|
|
||||||
qt5_add_dbus_interface(SOURCES
|
|
||||||
dbus/org.freedesktop.UDisks2.Filesystem.xml
|
|
||||||
dbus/udisks2filesystem)
|
|
||||||
qt5_add_dbus_interface(SOURCES
|
|
||||||
dbus/org.freedesktop.UDisks2.Block.xml
|
|
||||||
dbus/udisks2block)
|
|
||||||
qt5_add_dbus_interface(SOURCES
|
|
||||||
dbus/org.freedesktop.UDisks2.Drive.xml
|
|
||||||
dbus/udisks2drive)
|
|
||||||
qt5_add_dbus_interface(SOURCES
|
|
||||||
dbus/org.freedesktop.UDisks2.Job.xml
|
|
||||||
dbus/udisks2job)
|
|
||||||
endif()
|
|
||||||
endif(HAVE_UDISKS2)
|
|
||||||
|
|
||||||
endif(UNIX AND HAVE_DBUS)
|
endif(UNIX AND HAVE_DBUS)
|
||||||
|
|
||||||
@@ -797,29 +688,24 @@ optional_source(HAVE_LIBGPOD
|
|||||||
)
|
)
|
||||||
|
|
||||||
# GIO device backend
|
# GIO device backend
|
||||||
optional_source(HAVE_GIO
|
optional_source(HAVE_GIO SOURCES device/giolister.cpp HEADERS device/giolister.h)
|
||||||
SOURCES device/giolister.cpp
|
|
||||||
HEADERS device/giolister.h
|
|
||||||
)
|
|
||||||
|
|
||||||
# mtp device
|
# MTP device
|
||||||
optional_source(HAVE_LIBMTP
|
optional_source(HAVE_LIBMTP
|
||||||
SOURCES
|
SOURCES
|
||||||
device/mtpconnection.cpp
|
device/mtpconnection.cpp
|
||||||
device/mtpdevice.cpp
|
device/mtpdevice.cpp
|
||||||
device/mtploader.cpp
|
device/mtploader.cpp
|
||||||
HEADERS
|
HEADERS
|
||||||
|
device/mtpconnection.h
|
||||||
device/mtpdevice.h
|
device/mtpdevice.h
|
||||||
device/mtploader.h
|
device/mtploader.h
|
||||||
)
|
)
|
||||||
|
|
||||||
# Pulse audio integration
|
# Pulse audio integration
|
||||||
optional_source(HAVE_LIBPULSE
|
optional_source(HAVE_LIBPULSE SOURCES engine/pulsedevicefinder.cpp)
|
||||||
SOURCES
|
|
||||||
engine/pulsedevicefinder.cpp
|
|
||||||
)
|
|
||||||
|
|
||||||
# MusicBrainz and transcoder require GStreamer
|
# Transcoder require GStreamer
|
||||||
optional_source(HAVE_GSTREAMER
|
optional_source(HAVE_GSTREAMER
|
||||||
SOURCES
|
SOURCES
|
||||||
transcoder/transcoder.cpp
|
transcoder/transcoder.cpp
|
||||||
@@ -838,6 +724,14 @@ HEADERS
|
|||||||
transcoder/transcoder.h
|
transcoder/transcoder.h
|
||||||
transcoder/transcodedialog.h
|
transcoder/transcodedialog.h
|
||||||
transcoder/transcoderoptionsdialog.h
|
transcoder/transcoderoptionsdialog.h
|
||||||
|
transcoder/transcoderoptionsinterface.h
|
||||||
|
transcoder/transcoderoptionsflac.h
|
||||||
|
transcoder/transcoderoptionswavpack.h
|
||||||
|
transcoder/transcoderoptionsvorbis.h
|
||||||
|
transcoder/transcoderoptionsopus.h
|
||||||
|
transcoder/transcoderoptionsspeex.h
|
||||||
|
transcoder/transcoderoptionsaac.h
|
||||||
|
transcoder/transcoderoptionsasf.h
|
||||||
transcoder/transcoderoptionsmp3.h
|
transcoder/transcoderoptionsmp3.h
|
||||||
settings/transcodersettingspage.h
|
settings/transcodersettingspage.h
|
||||||
UI
|
UI
|
||||||
@@ -855,30 +749,39 @@ UI
|
|||||||
settings/transcodersettingspage.ui
|
settings/transcodersettingspage.ui
|
||||||
)
|
)
|
||||||
|
|
||||||
# CDIO backend and device
|
# CHROMAPRINT
|
||||||
if(HAVE_GSTREAMER)
|
if(HAVE_SONGFINGERPRINTING OR HAVE_MUSICBRAINZ)
|
||||||
optional_source(HAVE_CHROMAPRINT
|
optional_source(CHROMAPRINT_FOUND SOURCES engine/chromaprinter.cpp)
|
||||||
SOURCES
|
|
||||||
musicbrainz/chromaprinter.cpp
|
|
||||||
musicbrainz/tagfetcher.cpp
|
|
||||||
HEADERS
|
|
||||||
musicbrainz/tagfetcher.h
|
|
||||||
)
|
|
||||||
optional_source(HAVE_AUDIOCD
|
|
||||||
SOURCES
|
|
||||||
device/cddadevice.cpp
|
|
||||||
device/cddalister.cpp
|
|
||||||
device/cddasongloader.cpp
|
|
||||||
HEADERS
|
|
||||||
device/cddadevice.h
|
|
||||||
device/cddalister.h
|
|
||||||
device/cddasongloader.h
|
|
||||||
)
|
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
|
# MusicBrainz
|
||||||
|
optional_source(HAVE_MUSICBRAINZ
|
||||||
|
SOURCES
|
||||||
|
musicbrainz/acoustidclient.cpp
|
||||||
|
musicbrainz/musicbrainzclient.cpp
|
||||||
|
musicbrainz/tagfetcher.cpp
|
||||||
|
HEADERS
|
||||||
|
musicbrainz/acoustidclient.h
|
||||||
|
musicbrainz/musicbrainzclient.h
|
||||||
|
musicbrainz/tagfetcher.h
|
||||||
|
)
|
||||||
|
|
||||||
|
# CDIO backend and device
|
||||||
|
optional_source(HAVE_AUDIOCD
|
||||||
|
SOURCES
|
||||||
|
device/cddadevice.cpp
|
||||||
|
device/cddalister.cpp
|
||||||
|
device/cddasongloader.cpp
|
||||||
|
HEADERS
|
||||||
|
device/cddadevice.h
|
||||||
|
device/cddalister.h
|
||||||
|
device/cddasongloader.h
|
||||||
|
)
|
||||||
|
|
||||||
# Platform specific - macOS
|
# Platform specific - macOS
|
||||||
optional_source(APPLE
|
optional_source(APPLE
|
||||||
SOURCES
|
SOURCES
|
||||||
|
core/scoped_nsautorelease_pool.mm
|
||||||
core/mac_utilities.mm
|
core/mac_utilities.mm
|
||||||
core/mac_startup.mm
|
core/mac_startup.mm
|
||||||
core/macsystemtrayicon.mm
|
core/macsystemtrayicon.mm
|
||||||
@@ -888,22 +791,15 @@ optional_source(APPLE
|
|||||||
engine/macosdevicefinder.cpp
|
engine/macosdevicefinder.cpp
|
||||||
globalshortcuts/globalshortcutsbackend-macos.mm
|
globalshortcuts/globalshortcutsbackend-macos.mm
|
||||||
globalshortcuts/globalshortcutgrabber.mm
|
globalshortcuts/globalshortcutgrabber.mm
|
||||||
|
device/macosdevicelister.mm
|
||||||
HEADERS
|
HEADERS
|
||||||
core/macsystemtrayicon.h
|
core/macsystemtrayicon.h
|
||||||
core/macfslistener.h
|
core/macfslistener.h
|
||||||
osd/osdmac.h
|
osd/osdmac.h
|
||||||
globalshortcuts/globalshortcutsbackend-macos.h
|
globalshortcuts/globalshortcutsbackend-macos.h
|
||||||
|
device/macosdevicelister.h
|
||||||
)
|
)
|
||||||
|
|
||||||
if (APPLE)
|
|
||||||
optional_source(HAVE_LIBMTP
|
|
||||||
SOURCES
|
|
||||||
device/macosdevicelister.mm
|
|
||||||
HEADERS
|
|
||||||
device/macosdevicelister.h
|
|
||||||
)
|
|
||||||
endif()
|
|
||||||
|
|
||||||
# Platform specific - Windows
|
# Platform specific - Windows
|
||||||
optional_source(WIN32
|
optional_source(WIN32
|
||||||
SOURCES
|
SOURCES
|
||||||
@@ -1004,33 +900,27 @@ optional_source(HAVE_MOODBAR
|
|||||||
configure_file(${CMAKE_CURRENT_SOURCE_DIR}/config.h.in ${CMAKE_CURRENT_BINARY_DIR}/config.h)
|
configure_file(${CMAKE_CURRENT_SOURCE_DIR}/config.h.in ${CMAKE_CURRENT_BINARY_DIR}/config.h)
|
||||||
configure_file(${CMAKE_CURRENT_SOURCE_DIR}/version.h.in ${CMAKE_CURRENT_BINARY_DIR}/version.h)
|
configure_file(${CMAKE_CURRENT_SOURCE_DIR}/version.h.in ${CMAKE_CURRENT_BINARY_DIR}/version.h)
|
||||||
|
|
||||||
if(BUILD_WITH_QT6)
|
qt_wrap_cpp(MOC ${HEADERS})
|
||||||
qt6_wrap_cpp(MOC ${HEADERS})
|
qt_wrap_ui(UIC ${UI})
|
||||||
qt6_wrap_ui(UIC ${UI})
|
qt_add_resources(QRC ${RESOURCES})
|
||||||
qt6_add_resources(QRC ${RESOURCES})
|
|
||||||
else()
|
|
||||||
qt5_wrap_cpp(MOC ${HEADERS})
|
|
||||||
qt5_wrap_ui(UIC ${UI})
|
|
||||||
qt5_add_resources(QRC ${RESOURCES})
|
|
||||||
endif()
|
|
||||||
|
|
||||||
if(HAVE_TRANSLATIONS)
|
if(HAVE_TRANSLATIONS)
|
||||||
|
|
||||||
set(LINGUAS "All" CACHE STRING "A space-seperated list of translations to compile in to Strawberry, or \"None\".")
|
set(LINGUAS "All" CACHE STRING "A space-seperated list of translations to compile in to Strawberry, or \"None\".")
|
||||||
if (LINGUAS STREQUAL "All")
|
if(LINGUAS STREQUAL "All")
|
||||||
# build LANGUAGES from all existing .po files
|
# build LANGUAGES from all existing .po files
|
||||||
file(GLOB pofiles translations/*.po)
|
file(GLOB pofiles translations/*.po)
|
||||||
foreach(pofile ${pofiles})
|
foreach(pofile ${pofiles})
|
||||||
get_filename_component(lang ${pofile} NAME_WE)
|
get_filename_component(lang ${pofile} NAME_WE)
|
||||||
list(APPEND LANGUAGES ${lang})
|
list(APPEND LANGUAGES ${lang})
|
||||||
endforeach(pofile)
|
endforeach(pofile)
|
||||||
else (LINGUAS STREQUAL "All")
|
else(LINGUAS STREQUAL "All")
|
||||||
if (NOT LINGUAS OR LINGUAS STREQUAL "None")
|
if(NOT LINGUAS OR LINGUAS STREQUAL "None")
|
||||||
set (LANGUAGES "")
|
set(LANGUAGES "")
|
||||||
else (NOT LINGUAS OR LINGUAS STREQUAL "None")
|
else(NOT LINGUAS OR LINGUAS STREQUAL "None")
|
||||||
string(REGEX MATCHALL [a-zA-Z_@]+ LANGUAGES ${LINGUAS})
|
string(REGEX MATCHALL [a-zA-Z_@]+ LANGUAGES ${LINGUAS})
|
||||||
endif (NOT LINGUAS OR LINGUAS STREQUAL "None")
|
endif(NOT LINGUAS OR LINGUAS STREQUAL "None")
|
||||||
endif (LINGUAS STREQUAL "All")
|
endif(LINGUAS STREQUAL "All")
|
||||||
|
|
||||||
add_pot(POT
|
add_pot(POT
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/translations/header
|
${CMAKE_CURRENT_SOURCE_DIR}/translations/header
|
||||||
@@ -1051,16 +941,14 @@ link_directories(
|
|||||||
${GOBJECT_LIBRARY_DIRS}
|
${GOBJECT_LIBRARY_DIRS}
|
||||||
${GNUTLS_LIBRARY_DIRS}
|
${GNUTLS_LIBRARY_DIRS}
|
||||||
${SQLITE_LIBRARY_DIRS}
|
${SQLITE_LIBRARY_DIRS}
|
||||||
${TAGLIB_LIBRARY_DIRS}
|
|
||||||
${SINGLEAPPLICATION_LIBRARY_DIRS}
|
${SINGLEAPPLICATION_LIBRARY_DIRS}
|
||||||
${SINGLECOREAPPLICATION_LIBRARY_DIRS}
|
${SINGLECOREAPPLICATION_LIBRARY_DIRS}
|
||||||
${QTSPARKLE_LIBRARY_DIRS}
|
|
||||||
${Iconv_LIBRARY_DIRS}
|
${Iconv_LIBRARY_DIRS}
|
||||||
)
|
)
|
||||||
|
|
||||||
if(HAVE_ALSA)
|
if(HAVE_ALSA)
|
||||||
link_directories(${ALSA_LIBRARY_DIRS})
|
link_directories(${ALSA_LIBRARY_DIRS})
|
||||||
endif(HAVE_ALSA)
|
endif()
|
||||||
|
|
||||||
if(HAVE_LIBPULSE)
|
if(HAVE_LIBPULSE)
|
||||||
link_directories(${LIBPULSE_LIBRARY_DIRS})
|
link_directories(${LIBPULSE_LIBRARY_DIRS})
|
||||||
@@ -1075,40 +963,55 @@ if(HAVE_GSTREAMER)
|
|||||||
${GSTREAMER_TAG_LIBRARY_DIRS}
|
${GSTREAMER_TAG_LIBRARY_DIRS}
|
||||||
${GSTREAMER_PBUTILS_LIBRARY_DIRS}
|
${GSTREAMER_PBUTILS_LIBRARY_DIRS}
|
||||||
)
|
)
|
||||||
endif(HAVE_GSTREAMER)
|
endif()
|
||||||
|
|
||||||
if(HAVE_VLC)
|
if(HAVE_VLC)
|
||||||
link_directories(${LIBVLC_LIBRARY_DIRS})
|
link_directories(${LIBVLC_LIBRARY_DIRS})
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
if(HAVE_CHROMAPRINT)
|
if(HAVE_SONGFINGERPRINTING OR HAVE_MUSICBRAINZ)
|
||||||
link_directories(${CHROMAPRINT_LIBRARY_DIRS})
|
link_directories(${CHROMAPRINT_LIBRARY_DIRS})
|
||||||
endif(HAVE_CHROMAPRINT)
|
endif()
|
||||||
|
|
||||||
if(X11_FOUND)
|
if(X11_FOUND)
|
||||||
link_directories(${X11_LIBRARY_DIRS})
|
link_directories(${X11_LIBRARY_DIRS})
|
||||||
endif(X11_FOUND)
|
endif()
|
||||||
|
|
||||||
if(XCB_FOUND)
|
if(XCB_FOUND)
|
||||||
link_directories(${XCB_LIBRARY_DIRS})
|
link_directories(${XCB_LIBRARY_DIRS})
|
||||||
endif(XCB_FOUND)
|
endif()
|
||||||
|
|
||||||
if(HAVE_GIO)
|
if(HAVE_GIO)
|
||||||
link_directories(${GIO_LIBRARY_DIRS})
|
link_directories(${GIO_LIBRARY_DIRS})
|
||||||
endif(HAVE_GIO)
|
endif()
|
||||||
|
|
||||||
|
if(HAVE_GIO_UNIX)
|
||||||
|
link_directories(${GIO_UNIX_LIBRARY_DIRS})
|
||||||
|
endif()
|
||||||
|
|
||||||
if(HAVE_AUDIOCD)
|
if(HAVE_AUDIOCD)
|
||||||
link_directories(${LIBCDIO_LIBRARY_DIRS})
|
link_directories(${LIBCDIO_LIBRARY_DIRS})
|
||||||
endif(HAVE_AUDIOCD)
|
endif()
|
||||||
|
|
||||||
if(HAVE_LIBGPOD)
|
if(HAVE_LIBGPOD)
|
||||||
link_directories(${LIBGPOD_LIBRARY_DIRS})
|
link_directories(${LIBGPOD_LIBRARY_DIRS} ${GDK_PIXBUF_LIBRARY_DIRS})
|
||||||
link_directories(${GDK_PIXBUF_LIBRARY_DIRS})
|
endif()
|
||||||
endif(HAVE_LIBGPOD)
|
|
||||||
|
|
||||||
if(HAVE_LIBMTP)
|
if(HAVE_LIBMTP)
|
||||||
link_directories(${LIBMTP_LIBRARY_DIRS})
|
link_directories(${LIBMTP_LIBRARY_DIRS})
|
||||||
endif(HAVE_LIBMTP)
|
endif()
|
||||||
|
|
||||||
|
if(USE_TAGLIB AND TAGLIB_FOUND)
|
||||||
|
link_directories(${TAGLIB_LIBRARY_DIRS})
|
||||||
|
endif()
|
||||||
|
|
||||||
|
if(USE_TAGPARSER AND TAGPARSER_FOUND)
|
||||||
|
link_directories(${TAGPARSER_LIBRARY_DIRS})
|
||||||
|
endif()
|
||||||
|
|
||||||
|
if(HAVE_QTSPARKLE)
|
||||||
|
link_directories(${QTSPARKLE_LIBRARY_DIRS})
|
||||||
|
endif()
|
||||||
|
|
||||||
add_library(strawberry_lib STATIC
|
add_library(strawberry_lib STATIC
|
||||||
${SOURCES}
|
${SOURCES}
|
||||||
@@ -1126,9 +1029,13 @@ target_include_directories(strawberry_lib SYSTEM PUBLIC
|
|||||||
${GOBJECT_INCLUDE_DIRS}
|
${GOBJECT_INCLUDE_DIRS}
|
||||||
${GNUTLS_INCLUDE_DIRS}
|
${GNUTLS_INCLUDE_DIRS}
|
||||||
${SQLITE_INCLUDE_DIRS}
|
${SQLITE_INCLUDE_DIRS}
|
||||||
${TAGLIB_INCLUDE_DIRS}
|
${PROTOBUF_INCLUDE_DIRS}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
if(HAVE_QPA_QPLATFORMNATIVEINTERFACE_H)
|
||||||
|
target_include_directories(strawberry_lib SYSTEM PUBLIC ${Qt${QT_VERSION_MAJOR}Gui_PRIVATE_INCLUDE_DIRS})
|
||||||
|
endif()
|
||||||
|
|
||||||
target_include_directories(strawberry_lib PUBLIC
|
target_include_directories(strawberry_lib PUBLIC
|
||||||
${CMAKE_SOURCE_DIR}
|
${CMAKE_SOURCE_DIR}
|
||||||
${CMAKE_BINARY_DIR}
|
${CMAKE_BINARY_DIR}
|
||||||
@@ -1147,12 +1054,10 @@ target_link_libraries(strawberry_lib PUBLIC
|
|||||||
${GOBJECT_LIBRARIES}
|
${GOBJECT_LIBRARIES}
|
||||||
${GNUTLS_LIBRARIES}
|
${GNUTLS_LIBRARIES}
|
||||||
${SQLITE_LIBRARIES}
|
${SQLITE_LIBRARIES}
|
||||||
${TAGLIB_LIBRARIES}
|
|
||||||
${QT_LIBRARIES}
|
${QT_LIBRARIES}
|
||||||
${SINGLEAPPLICATION_LIBRARIES}
|
${SINGLEAPPLICATION_LIBRARIES}
|
||||||
${SINGLECOREAPPLICATION_LIBRARIES}
|
${SINGLECOREAPPLICATION_LIBRARIES}
|
||||||
${QTSPARKLE_LIBRARIES}
|
${Iconv_LIBRARIES}
|
||||||
${Iconv_LIBRARY}
|
|
||||||
libstrawberry-common
|
libstrawberry-common
|
||||||
libstrawberry-tagreader
|
libstrawberry-tagreader
|
||||||
)
|
)
|
||||||
@@ -1160,7 +1065,7 @@ target_link_libraries(strawberry_lib PUBLIC
|
|||||||
if(HAVE_ALSA)
|
if(HAVE_ALSA)
|
||||||
target_include_directories(strawberry_lib SYSTEM PRIVATE ${ALSA_INCLUDE_DIRS})
|
target_include_directories(strawberry_lib SYSTEM PRIVATE ${ALSA_INCLUDE_DIRS})
|
||||||
target_link_libraries(strawberry_lib PRIVATE ${ALSA_LIBRARIES})
|
target_link_libraries(strawberry_lib PRIVATE ${ALSA_LIBRARIES})
|
||||||
endif(HAVE_ALSA)
|
endif()
|
||||||
|
|
||||||
if(HAVE_LIBPULSE)
|
if(HAVE_LIBPULSE)
|
||||||
target_include_directories(strawberry_lib SYSTEM PRIVATE ${LIBPULSE_INCLUDE_DIRS})
|
target_include_directories(strawberry_lib SYSTEM PRIVATE ${LIBPULSE_INCLUDE_DIRS})
|
||||||
@@ -1184,7 +1089,7 @@ if(HAVE_GSTREAMER)
|
|||||||
${GSTREAMER_TAG_LIBRARIES}
|
${GSTREAMER_TAG_LIBRARIES}
|
||||||
${GSTREAMER_PBUTILS_LIBRARIES}
|
${GSTREAMER_PBUTILS_LIBRARIES}
|
||||||
)
|
)
|
||||||
endif(HAVE_GSTREAMER)
|
endif()
|
||||||
|
|
||||||
if(HAVE_MOODBAR)
|
if(HAVE_MOODBAR)
|
||||||
target_link_libraries(strawberry_lib PRIVATE gstmoodbar)
|
target_link_libraries(strawberry_lib PRIVATE gstmoodbar)
|
||||||
@@ -1195,41 +1100,49 @@ if(HAVE_VLC)
|
|||||||
target_link_libraries(strawberry_lib PRIVATE ${LIBVLC_LIBRARIES})
|
target_link_libraries(strawberry_lib PRIVATE ${LIBVLC_LIBRARIES})
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
if(HAVE_CHROMAPRINT)
|
if(HAVE_SONGFINGERPRINTING OR HAVE_MUSICBRAINZ)
|
||||||
target_include_directories(strawberry_lib SYSTEM PRIVATE ${CHROMAPRINT_INCLUDE_DIRS})
|
target_include_directories(strawberry_lib SYSTEM PRIVATE ${CHROMAPRINT_INCLUDE_DIRS})
|
||||||
target_link_libraries(strawberry_lib PRIVATE ${CHROMAPRINT_LIBRARIES})
|
target_link_libraries(strawberry_lib PRIVATE ${CHROMAPRINT_LIBRARIES})
|
||||||
endif(HAVE_CHROMAPRINT)
|
endif()
|
||||||
|
|
||||||
if(X11_FOUND)
|
if(X11_FOUND)
|
||||||
target_include_directories(strawberry_lib SYSTEM PRIVATE ${X11_INCLUDE_DIR})
|
target_include_directories(strawberry_lib SYSTEM PRIVATE ${X11_INCLUDE_DIR})
|
||||||
target_link_libraries(strawberry_lib PRIVATE ${X11_LIBRARIES})
|
target_link_libraries(strawberry_lib PRIVATE ${X11_LIBRARIES})
|
||||||
endif(X11_FOUND)
|
endif()
|
||||||
|
|
||||||
if(XCB_FOUND)
|
if(XCB_FOUND)
|
||||||
target_include_directories(strawberry_lib SYSTEM PRIVATE ${XCB_INCLUDE_DIR})
|
target_include_directories(strawberry_lib SYSTEM PRIVATE ${XCB_INCLUDE_DIR})
|
||||||
target_link_libraries(strawberry_lib PRIVATE ${XCB_LIBRARIES})
|
target_link_libraries(strawberry_lib PRIVATE ${XCB_LIBRARIES})
|
||||||
endif(XCB_FOUND)
|
endif()
|
||||||
|
|
||||||
if(HAVE_GIO)
|
if(HAVE_GIO)
|
||||||
target_include_directories(strawberry_lib SYSTEM PRIVATE ${GIO_INCLUDE_DIRS})
|
target_include_directories(strawberry_lib SYSTEM PRIVATE ${GIO_INCLUDE_DIRS})
|
||||||
target_link_libraries(strawberry_lib PRIVATE ${GIO_LIBRARIES})
|
target_link_libraries(strawberry_lib PRIVATE ${GIO_LIBRARIES})
|
||||||
endif(HAVE_GIO)
|
endif()
|
||||||
|
|
||||||
|
if(HAVE_GIO_UNIX)
|
||||||
|
target_include_directories(strawberry_lib SYSTEM PRIVATE ${GIO_UNIX_INCLUDE_DIRS})
|
||||||
|
target_link_libraries(strawberry_lib PRIVATE ${GIO_UNIX_LIBRARIES})
|
||||||
|
endif()
|
||||||
|
|
||||||
if(HAVE_AUDIOCD)
|
if(HAVE_AUDIOCD)
|
||||||
target_include_directories(strawberry_lib SYSTEM PRIVATE ${LIBCDIO_INCLUDE_DIRS})
|
target_include_directories(strawberry_lib SYSTEM PRIVATE ${LIBCDIO_INCLUDE_DIRS})
|
||||||
target_link_libraries(strawberry_lib PRIVATE ${LIBCDIO_LIBRARIES})
|
target_link_libraries(strawberry_lib PRIVATE ${LIBCDIO_LIBRARIES})
|
||||||
endif(HAVE_AUDIOCD)
|
endif()
|
||||||
|
|
||||||
if(HAVE_LIBGPOD)
|
if(HAVE_LIBGPOD)
|
||||||
target_include_directories(strawberry_lib SYSTEM PRIVATE ${LIBGPOD_INCLUDE_DIRS} ${GDK_PIXBUF_INCLUDE_DIRS})
|
target_include_directories(strawberry_lib SYSTEM PRIVATE ${LIBGPOD_INCLUDE_DIRS} ${GDK_PIXBUF_INCLUDE_DIRS})
|
||||||
target_link_libraries(strawberry_lib PRIVATE ${LIBGPOD_LIBRARIES} ${GDK_PIXBUF_LIBRARIES})
|
target_link_libraries(strawberry_lib PRIVATE ${LIBGPOD_LIBRARIES} ${GDK_PIXBUF_LIBRARIES})
|
||||||
endif(HAVE_LIBGPOD)
|
endif()
|
||||||
|
|
||||||
if(HAVE_LIBMTP)
|
if(HAVE_LIBMTP)
|
||||||
target_include_directories(strawberry_lib SYSTEM PRIVATE ${LIBMTP_INCLUDE_DIRS})
|
target_include_directories(strawberry_lib SYSTEM PRIVATE ${LIBMTP_INCLUDE_DIRS})
|
||||||
target_link_libraries(strawberry_lib PRIVATE ${LIBMTP_LIBRARIES})
|
target_link_libraries(strawberry_lib PRIVATE ${LIBMTP_LIBRARIES})
|
||||||
endif(HAVE_LIBMTP)
|
endif()
|
||||||
|
|
||||||
|
if(FREEBSD)
|
||||||
|
target_link_libraries(strawberry_lib PRIVATE iconv)
|
||||||
|
endif()
|
||||||
|
|
||||||
if(APPLE)
|
if(APPLE)
|
||||||
target_link_libraries(strawberry_lib PRIVATE
|
target_link_libraries(strawberry_lib PRIVATE
|
||||||
@@ -1242,27 +1155,26 @@ if(APPLE)
|
|||||||
"-framework ScriptingBridge"
|
"-framework ScriptingBridge"
|
||||||
)
|
)
|
||||||
target_link_libraries(strawberry_lib PRIVATE ${SPMEDIAKEYTAP_LIBRARIES})
|
target_link_libraries(strawberry_lib PRIVATE ${SPMEDIAKEYTAP_LIBRARIES})
|
||||||
if(HAVE_SPARKLE)
|
endif()
|
||||||
target_include_directories(strawberry_lib SYSTEM PRIVATE ${SPARKLE}/Headers)
|
|
||||||
target_link_libraries(strawberry_lib PRIVATE ${SPARKLE})
|
|
||||||
endif(HAVE_SPARKLE)
|
|
||||||
endif(APPLE)
|
|
||||||
|
|
||||||
if(WIN32)
|
if(WIN32)
|
||||||
target_link_libraries(strawberry_lib PRIVATE dsound)
|
target_link_libraries(strawberry_lib PRIVATE dsound dwmapi)
|
||||||
endif(WIN32)
|
if(MSVC)
|
||||||
|
target_link_libraries(strawberry_lib PRIVATE sqlite3)
|
||||||
if(X11_FOUND)
|
if (GETOPT_INCLUDE_DIRS)
|
||||||
# Hack: the Gold linker pays attention to the order that libraries are specified on the link line.
|
target_include_directories(strawberry_lib SYSTEM PRIVATE ${GETOPT_INCLUDE_DIRS})
|
||||||
# -lX11 and -ldl are provided earlier in the link command but they're actually used by libraries that appear after them, so they end up getting ignored.
|
endif()
|
||||||
# This appends them to the very end of the link line, ensuring they're always used.
|
if(GETOPT_LIBRARIES)
|
||||||
if(FREEBSD)
|
target_link_libraries(strawberry_lib PRIVATE ${GETOPT_LIBRARIES})
|
||||||
target_link_libraries(strawberry_lib PRIVATE ${X11_X11_LIB})
|
endif()
|
||||||
else()
|
|
||||||
target_link_libraries(strawberry_lib PRIVATE ${X11_X11_LIB} ${CMAKE_DL_LIBS})
|
|
||||||
endif()
|
endif()
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
|
if(HAVE_QTSPARKLE)
|
||||||
|
target_include_directories(strawberry_lib SYSTEM PRIVATE ${QTSPARKLE_INCLUDE_DIRS})
|
||||||
|
target_link_libraries(strawberry_lib PRIVATE ${QTSPARKLE_LIBRARIES})
|
||||||
|
endif()
|
||||||
|
|
||||||
###############################################################################
|
###############################################################################
|
||||||
|
|
||||||
set(EXECUTABLE_OUTPUT_PATH ..)
|
set(EXECUTABLE_OUTPUT_PATH ..)
|
||||||
@@ -1270,13 +1182,13 @@ set(EXECUTABLE_OUTPUT_PATH ..)
|
|||||||
# Show the console window in debug mode on Windows
|
# Show the console window in debug mode on Windows
|
||||||
if(NOT CMAKE_BUILD_TYPE STREQUAL "Debug" AND NOT ENABLE_WIN32_CONSOLE)
|
if(NOT CMAKE_BUILD_TYPE STREQUAL "Debug" AND NOT ENABLE_WIN32_CONSOLE)
|
||||||
set(STRAWBERRY-WIN32-FLAG WIN32)
|
set(STRAWBERRY-WIN32-FLAG WIN32)
|
||||||
endif(NOT CMAKE_BUILD_TYPE STREQUAL "Debug" AND NOT ENABLE_WIN32_CONSOLE)
|
endif()
|
||||||
|
|
||||||
# Resource file for windows
|
# Resource file for windows
|
||||||
if(WIN32)
|
if(WIN32)
|
||||||
configure_file(${CMAKE_CURRENT_SOURCE_DIR}/../dist/windows/windres.rc.in ${CMAKE_CURRENT_BINARY_DIR}/windres.rc)
|
configure_file(${CMAKE_CURRENT_SOURCE_DIR}/../dist/windows/windres.rc.in ${CMAKE_CURRENT_BINARY_DIR}/windres.rc)
|
||||||
set(STRAWBERRY-WIN32-RESOURCES windres.rc)
|
set(STRAWBERRY-WIN32-RESOURCES windres.rc)
|
||||||
endif(WIN32)
|
endif()
|
||||||
|
|
||||||
add_executable(strawberry
|
add_executable(strawberry
|
||||||
MACOSX_BUNDLE
|
MACOSX_BUNDLE
|
||||||
@@ -1304,4 +1216,4 @@ endif()
|
|||||||
|
|
||||||
if(APPLE)
|
if(APPLE)
|
||||||
set_target_properties(strawberry PROPERTIES MACOSX_BUNDLE_INFO_PLIST "${CMAKE_CURRENT_SOURCE_DIR}/../dist/macos/Info.plist")
|
set_target_properties(strawberry PROPERTIES MACOSX_BUNDLE_INFO_PLIST "${CMAKE_CURRENT_SOURCE_DIR}/../dist/macos/Info.plist")
|
||||||
endif (APPLE)
|
endif()
|
||||||
|
|||||||