Compare commits
900 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e2f5486987 | ||
|
|
b0d390aaf1 | ||
|
|
bae1b42394 | ||
|
|
a2533edd57 | ||
|
|
7b88c198fe | ||
|
|
c49cb0c119 | ||
|
|
03aabeb848 | ||
|
|
1d3a837f7a | ||
|
|
92adc18b8f | ||
|
|
e4697c8ff1 | ||
|
|
b741f1a580 | ||
|
|
8ee6de4162 | ||
|
|
2b9e7db924 | ||
|
|
49384ce294 | ||
|
|
ac59fff346 | ||
|
|
a3e9f152d8 | ||
|
|
ffe6a81b9a | ||
|
|
9bb051b4eb | ||
|
|
c1465a890f | ||
|
|
c3ee3318d7 | ||
|
|
272976f0b7 | ||
|
|
4724b170b1 | ||
|
|
337bd4bcef | ||
|
|
0604c78453 | ||
|
|
c8169adf7c | ||
|
|
7b610d131c | ||
|
|
18da55453f | ||
|
|
2aeab8b672 | ||
|
|
9c21707a55 | ||
|
|
b02ac833ad | ||
|
|
4de912cf41 | ||
|
|
ed260c6e20 | ||
|
|
fab38f693d | ||
|
|
aedbd52e9d | ||
|
|
4cbcb9d99c | ||
|
|
e967d15b4e | ||
|
|
bb43cc63ec | ||
|
|
ca176c319d | ||
|
|
17c960ecd4 | ||
|
|
b766ae3498 | ||
|
|
faf4c817cd | ||
|
|
b4e1f283c9 | ||
|
|
52c83d592c | ||
|
|
c8caea0d30 | ||
|
|
7a6c54d8e7 | ||
|
|
7082e52a4f | ||
|
|
6cdca617e0 | ||
|
|
a1adc1a75a | ||
|
|
b16bec704a | ||
|
|
664a8c79a1 | ||
|
|
bbf3d8e1a4 | ||
|
|
a35146440c | ||
|
|
8e79fafca9 | ||
|
|
6f8780d3cc | ||
|
|
264065a355 | ||
|
|
b606e4cd1a | ||
|
|
01d0eeaed0 | ||
|
|
8c4e24e65d | ||
|
|
19c9f9698d | ||
|
|
ae87c1b578 | ||
|
|
c42b1f5548 | ||
|
|
87ffbb0a85 | ||
|
|
702851a958 | ||
|
|
650f200a0b | ||
|
|
14d215bdf3 | ||
|
|
12aebc2fe9 | ||
|
|
f41b051ec7 | ||
|
|
25144eee89 | ||
|
|
0c62147536 | ||
|
|
7b282e21de | ||
|
|
c1fbe6d84c | ||
|
|
e20cbe4170 | ||
|
|
394955a03f | ||
|
|
16b4f5d065 | ||
|
|
c95295d8b4 | ||
|
|
658dce2607 | ||
|
|
01f8d0a27e | ||
|
|
39e1bfc84f | ||
|
|
e394416fa7 | ||
|
|
f1108bc0e2 | ||
|
|
ff31815d49 | ||
|
|
604aa63b47 | ||
|
|
ec4d036f50 | ||
|
|
1bf1c4ac63 | ||
|
|
981d46fbd4 | ||
|
|
eb6a353c31 | ||
|
|
ff673b1941 | ||
|
|
8b55cf8a3a | ||
|
|
d8682b4403 | ||
|
|
40a4bf195a | ||
|
|
312c5cbc3f | ||
|
|
f314c56ef0 | ||
|
|
ea8e5180ff | ||
|
|
7f76c3f2ce | ||
|
|
e4c5e99d0f | ||
|
|
80cfca5de2 | ||
|
|
9556a14de9 | ||
|
|
2025fc8325 | ||
|
|
2dd0f6a9ba | ||
|
|
a42039d6e5 | ||
|
|
7fafa8adfb | ||
|
|
f789657552 | ||
|
|
3e183bc10e | ||
|
|
16e5d93be3 | ||
|
|
6515e06a13 | ||
|
|
9ae0b32318 | ||
|
|
4cb3bc4185 | ||
|
|
f9ca24598e | ||
|
|
3ccc892d6a | ||
|
|
d7cacea843 | ||
|
|
e30233ac74 | ||
|
|
7c57631fcf | ||
|
|
0adc084dad | ||
|
|
e22199817c | ||
|
|
78f691d006 | ||
|
|
749bae1f16 | ||
|
|
1043e24322 | ||
|
|
a6d10b1fa7 | ||
|
|
a3159423f8 | ||
|
|
ecb5ca321b | ||
|
|
fd827fdfd8 | ||
|
|
92d77b14d5 | ||
|
|
be67d89d8b | ||
|
|
ddd1ce732a | ||
|
|
012d82183c | ||
|
|
f9c98ebcb3 | ||
|
|
10fe861dde | ||
|
|
16c027ecab | ||
|
|
cc578e7cc5 | ||
|
|
6972d3c4f9 | ||
|
|
46b164f2fb | ||
|
|
5431307527 | ||
|
|
79bf194ed6 | ||
|
|
506e670aa7 | ||
|
|
fdfe164dd1 | ||
|
|
af37056179 | ||
|
|
b0fc7187cf | ||
|
|
33ad1a7a86 | ||
|
|
dd72fb4ca5 | ||
|
|
e6c5f76872 | ||
|
|
14aa22d590 | ||
|
|
5ed4293641 | ||
|
|
f9fefcda57 | ||
|
|
99b40293db | ||
|
|
9b06e85f94 | ||
|
|
93d1d40ea5 | ||
|
|
98597c047a | ||
|
|
a5c1f4b0ee | ||
|
|
3d4c98d981 | ||
|
|
384e7dedb5 | ||
|
|
7df4453560 | ||
|
|
d406a1c341 | ||
|
|
6671d97b4a | ||
|
|
d02de72830 | ||
|
|
08f5172028 | ||
|
|
04f062547d | ||
|
|
4717d783dc | ||
|
|
93af064b36 | ||
|
|
c78d73d727 | ||
|
|
b69b3228be | ||
|
|
377f54700d | ||
|
|
d276339c80 | ||
|
|
b982a6a762 | ||
|
|
536fe637aa | ||
|
|
69f36eaa25 | ||
|
|
d6927a70bb | ||
|
|
1b1892a187 | ||
|
|
5bea71cd5c | ||
|
|
bb8d4e70ae | ||
|
|
8d8c7e8b7b | ||
|
|
9ff1f4d7b4 | ||
|
|
3a60dfe025 | ||
|
|
d358854e16 | ||
|
|
129587e94a | ||
|
|
0f575f4639 | ||
|
|
7aac741571 | ||
|
|
b8a9da8a4e | ||
|
|
be6b974334 | ||
|
|
194e81205b | ||
|
|
d711dcc99d | ||
|
|
3b72a12540 | ||
|
|
38a1b7765a | ||
|
|
02f2b8b6f0 | ||
|
|
7bfa75102c | ||
|
|
b0f3e7351c | ||
|
|
b5fa401db9 | ||
|
|
41f2710dea | ||
|
|
f19dda57f0 | ||
|
|
cc4a99ad80 | ||
|
|
1c1a3fc417 | ||
|
|
e97be5850b | ||
|
|
2c302654b7 | ||
|
|
3ee4dc77b2 | ||
|
|
db55f314c9 | ||
|
|
0b536b287f | ||
|
|
6f298a9917 | ||
|
|
70f829a2e5 | ||
|
|
1dfe07003f | ||
|
|
286b908062 | ||
|
|
4ec028e736 | ||
|
|
35a6d1437a | ||
|
|
7e3cb3de89 | ||
|
|
bd945039f1 | ||
|
|
46743c8f15 | ||
|
|
9b75284447 | ||
|
|
dca3b0052a | ||
|
|
6d05bb2de5 | ||
|
|
8b2e8d3804 | ||
|
|
1a2ab19ab4 | ||
|
|
04f010aa7b | ||
|
|
25323b4a3a | ||
|
|
f353c022f6 | ||
|
|
9d8b3d3428 | ||
|
|
e4b06772c0 | ||
|
|
a2b5c3ea08 | ||
|
|
f632e95600 | ||
|
|
4b5da8c6d4 | ||
|
|
77d7ffd10a | ||
|
|
d2f720011a | ||
|
|
6329a7711d | ||
|
|
fd14d044e3 | ||
|
|
6368d507e1 | ||
|
|
205b7f2401 | ||
|
|
f7d10f9530 | ||
|
|
66154bb51e | ||
|
|
fac4ad5313 | ||
|
|
fee63891ac | ||
|
|
efa979ee03 | ||
|
|
c756346357 | ||
|
|
cc95db25cc | ||
|
|
44970c3321 | ||
|
|
43c7934af7 | ||
|
|
3e7b51163c | ||
|
|
4d5f8a53f0 | ||
|
|
0b73ca8318 | ||
|
|
754cfc3bfd | ||
|
|
2ed1a5c06a | ||
|
|
b2073df3c3 | ||
|
|
6267edaa81 | ||
|
|
292f2de3e6 | ||
|
|
7803dc8be3 | ||
|
|
2a1260b79e | ||
|
|
f9ec438b7f | ||
|
|
de544c4856 | ||
|
|
55e04dd520 | ||
|
|
b2d06f900b | ||
|
|
b92ec71810 | ||
|
|
3a4199240e | ||
|
|
157de12bdf | ||
|
|
7c0c9fccdb | ||
|
|
2cb29d06b3 | ||
|
|
1fdeb50d93 | ||
|
|
f59632ae59 | ||
|
|
67b503da44 | ||
|
|
a351de6904 | ||
|
|
f94742abd5 | ||
|
|
891182637e | ||
|
|
086681c915 | ||
|
|
3970d2d02b | ||
|
|
2b3fddd015 | ||
|
|
30fa0480a3 | ||
|
|
320a81ce69 | ||
|
|
950411ef56 | ||
|
|
cb9cf084c0 | ||
|
|
bb18af09ae | ||
|
|
049bf0c7f9 | ||
|
|
ebc73ef775 | ||
|
|
1bd6f59355 | ||
|
|
c16396a690 | ||
|
|
32a9fe3e9c | ||
|
|
f48d133bc6 | ||
|
|
0678526d7d | ||
|
|
26d3e8371f | ||
|
|
6768f614c7 | ||
|
|
171dc84df1 | ||
|
|
5ca6513c04 | ||
|
|
41aeb0ac80 | ||
|
|
f871c4adef | ||
|
|
ad4d0e89b1 | ||
|
|
0d0c49caa1 | ||
|
|
7cbc7bdcfb | ||
|
|
6b46c8ed4a | ||
|
|
e33ffd1c8a | ||
|
|
8dce9cc938 | ||
|
|
f06218f8e3 | ||
|
|
5711eef2be | ||
|
|
db6aac603c | ||
|
|
f10e928106 | ||
|
|
99d963b99c | ||
|
|
5d35c22238 | ||
|
|
54fc11a235 | ||
|
|
ec99df3144 | ||
|
|
d722035883 | ||
|
|
52139fbaa0 | ||
|
|
88854eb558 | ||
|
|
c82bba01ee | ||
|
|
eaa33a03d7 | ||
|
|
89e8518f31 | ||
|
|
c7bf2e1da8 | ||
|
|
bf904a6afa | ||
|
|
43c14ae71b | ||
|
|
4abb8ef3c9 | ||
|
|
e46b92dd7d | ||
|
|
1a25faa5b9 | ||
|
|
3f4bf5f512 | ||
|
|
ae7dbf15ed | ||
|
|
b22320c48f | ||
|
|
61204e8d35 | ||
|
|
b0704c654c | ||
|
|
f40f8a8873 | ||
|
|
560a7db506 | ||
|
|
15baa8f70e | ||
|
|
97a7637294 | ||
|
|
3454656207 | ||
|
|
7dff6f26bc | ||
|
|
9e835a23fd | ||
|
|
c0259fb6ce | ||
|
|
acf106220b | ||
|
|
5d4dc9c907 | ||
|
|
2b9a56af7f | ||
|
|
f1e3ac65ac | ||
|
|
143f72cf6b | ||
|
|
5c8e49296c | ||
|
|
b2558b703c | ||
|
|
bf9d87106e | ||
|
|
5b7f507d9b | ||
|
|
59a7835ace | ||
|
|
2495838fe3 | ||
|
|
bab687bedf | ||
|
|
6467c3c8ee | ||
|
|
66c2b7aaa6 | ||
|
|
ab72c52661 | ||
|
|
f0bf1b8a54 | ||
|
|
119251719f | ||
|
|
0348400132 | ||
|
|
813473805b | ||
|
|
fe6368561b | ||
|
|
7ff06f424d | ||
|
|
31958592c7 | ||
|
|
adc21f4f75 | ||
|
|
9cecd89d6f | ||
|
|
d866b9b4d4 | ||
|
|
93f12baf51 | ||
|
|
78d6fd634b | ||
|
|
b5fc19f08a | ||
|
|
2dae6a6546 | ||
|
|
b6bba46391 | ||
|
|
b791d97116 | ||
|
|
6aa8255f34 | ||
|
|
98e2140761 | ||
|
|
a59e064778 | ||
|
|
655c4c66a7 | ||
|
|
407c128f65 | ||
|
|
59a261a5be | ||
|
|
ba4e1afefe | ||
|
|
98b53b81d8 | ||
|
|
8270cc0aa2 | ||
|
|
74207b1a87 | ||
|
|
77983445ce | ||
|
|
162190bcb8 | ||
|
|
86b92b20b7 | ||
|
|
2f5b60d548 | ||
|
|
c19661c977 | ||
|
|
82d101ca27 | ||
|
|
32f9c4e670 | ||
|
|
34c7225ab7 | ||
|
|
b02eb502e4 | ||
|
|
7d6b3dfd20 | ||
|
|
3ac39968d2 | ||
|
|
2b24ac54a0 | ||
|
|
6da6b794c7 | ||
|
|
ed689e27c9 | ||
|
|
186f9c3f18 | ||
|
|
42cdde3203 | ||
|
|
b9091702e9 | ||
|
|
d42ecbe74e | ||
|
|
e6c5446d63 | ||
|
|
cc277211b3 | ||
|
|
6703c15c52 | ||
|
|
4ecdaf573e | ||
|
|
71ec3e61be | ||
|
|
216fdb2393 | ||
|
|
c39acc6e3c | ||
|
|
d97b0478a7 | ||
|
|
d15d64eb67 | ||
|
|
3acd2656ee | ||
|
|
a4b003534a | ||
|
|
8752538a02 | ||
|
|
bde435753a | ||
|
|
d347e6fc5f | ||
|
|
27b01d3642 | ||
|
|
b78677e285 | ||
|
|
f8f6c74bcb | ||
|
|
7ce123939b | ||
|
|
e7b02cfb15 | ||
|
|
5931077b08 | ||
|
|
a521bdc0e3 | ||
|
|
80da565609 | ||
|
|
6bc46e4598 | ||
|
|
6562258db5 | ||
|
|
c219995218 | ||
|
|
62035431ed | ||
|
|
fa1fbca7dc | ||
|
|
391b7476b3 | ||
|
|
9c04ce665f | ||
|
|
dd87268197 | ||
|
|
8d9af59db2 | ||
|
|
1b754a35ff | ||
|
|
7230f91f61 | ||
|
|
93edfc315c | ||
|
|
74c8269531 | ||
|
|
acb6c0fc83 | ||
|
|
553d4cce93 | ||
|
|
ca81f144e6 | ||
|
|
ec84960347 | ||
|
|
38db0764af | ||
|
|
a647f63bb0 | ||
|
|
5b7087cc9e | ||
|
|
1a6fcd5da6 | ||
|
|
e31dd9f553 | ||
|
|
22844716d1 | ||
|
|
6abed76f70 | ||
|
|
72ff502e0f | ||
|
|
2b781df32b | ||
|
|
bf601c3906 | ||
|
|
b753e0ebb0 | ||
|
|
564211aceb | ||
|
|
538c759fef | ||
|
|
95edc34100 | ||
|
|
98682a2da9 | ||
|
|
33581fa61d | ||
|
|
b02e0aae98 | ||
|
|
6fa4e1cb6d | ||
|
|
9f2ec22e95 | ||
|
|
65b2e506b1 | ||
|
|
8bc07b5286 | ||
|
|
fe0af63795 | ||
|
|
44ce7f4c67 | ||
|
|
252936134b | ||
|
|
e255b06945 | ||
|
|
d726568a23 | ||
|
|
66880a6de7 | ||
|
|
fda622a0c0 | ||
|
|
efa7d10f40 | ||
|
|
348a3fabab | ||
|
|
1bbaee605c | ||
|
|
22edf7a2b3 | ||
|
|
3ffcc29249 | ||
|
|
21f1fe52c0 | ||
|
|
99840c9e4f | ||
|
|
3194fe7d8e | ||
|
|
81df72b0cb | ||
|
|
57b056ac43 | ||
|
|
c5876d0d48 | ||
|
|
b5e65401b6 | ||
|
|
ebea5b48ea | ||
|
|
7d081e581a | ||
|
|
5e819cf04b | ||
|
|
fd9ab43681 | ||
|
|
4935cdc722 | ||
|
|
26a3c7ad6a | ||
|
|
c358216ad9 | ||
|
|
bb9302143c | ||
|
|
3a73553aac | ||
|
|
6447a17e3e | ||
|
|
4bd5c8ffb3 | ||
|
|
89f137b211 | ||
|
|
3425572c66 | ||
|
|
ad50875f9c | ||
|
|
34e5645dab | ||
|
|
e8ae91c230 | ||
|
|
44b83994fa | ||
|
|
201f585350 | ||
|
|
e4d2f1925e | ||
|
|
658c116eac | ||
|
|
dbbedee77f | ||
|
|
16ac9ea061 | ||
|
|
6afbc71b4a | ||
|
|
71263863ad | ||
|
|
05232065f8 | ||
|
|
f289ae4143 | ||
|
|
91703ecec4 | ||
|
|
6fb0858b87 | ||
|
|
f1c6620df9 | ||
|
|
08fed9a5ee | ||
|
|
eb28b3b05b | ||
|
|
f0c354b0c9 | ||
|
|
07d88e86a2 | ||
|
|
f41a3b9bb2 | ||
|
|
bfd9e76f40 | ||
|
|
38ce208a43 | ||
|
|
fcd148b8d5 | ||
|
|
fc6c3774b0 | ||
|
|
18023d2e18 | ||
|
|
467e834ad6 | ||
|
|
629d9e7ae0 | ||
|
|
4487d292e8 | ||
|
|
76711b9a66 | ||
|
|
b54c749e43 | ||
|
|
d82fd421ed | ||
|
|
abdcadb5fa | ||
|
|
fa3891e383 | ||
|
|
f1dc0f95c8 | ||
|
|
5c721d243a | ||
|
|
2770384f1f | ||
|
|
f4e5b83039 | ||
|
|
124681605b | ||
|
|
a217faae7c | ||
|
|
184c39bbb7 | ||
|
|
508e2a70bb | ||
|
|
5aac56fe96 | ||
|
|
dab7dad966 | ||
|
|
25e48d6cae | ||
|
|
9b743e55d1 | ||
|
|
dc1f9edfaf | ||
|
|
b6b68edf1e | ||
|
|
a2320b99ae | ||
|
|
be01e28068 | ||
|
|
d6084083a5 | ||
|
|
019bf5102c | ||
|
|
a1633224d1 | ||
|
|
e62bd26cb9 | ||
|
|
c29f517292 | ||
|
|
d80144f80c | ||
|
|
4733c84a86 | ||
|
|
23f3d2095b | ||
|
|
4f1f2b6fc6 | ||
|
|
1ea70b085f | ||
|
|
6c0b395f4a | ||
|
|
0ee67f186f | ||
|
|
d133addbaa | ||
|
|
683991b1c9 | ||
|
|
32ac02007b | ||
|
|
d716617ae0 | ||
|
|
26e1f015d2 | ||
|
|
1c94093a4b | ||
|
|
27eccd456a | ||
|
|
eed4447560 | ||
|
|
3c8d0ebd52 | ||
|
|
7ec0d2f2cc | ||
|
|
35da91a997 | ||
|
|
788747c071 | ||
|
|
36eb131289 | ||
|
|
f231f28818 | ||
|
|
aebdc89f77 | ||
|
|
4a3a379871 | ||
|
|
a7f27add2d | ||
|
|
cd1d4247cf | ||
|
|
6d618c4b78 | ||
|
|
ad469531ff | ||
|
|
cf9b4b1246 | ||
|
|
18a2692dc1 | ||
|
|
a2dad982f8 | ||
|
|
8f27b6a4c9 | ||
|
|
21c4022fca | ||
|
|
237933855a | ||
|
|
dff7068a03 | ||
|
|
8f28a85a6d | ||
|
|
2414eb2598 | ||
|
|
4851f6bffd | ||
|
|
948be36d3c | ||
|
|
edd088927d | ||
|
|
4cfe8dd95e | ||
|
|
e1ce81c5bf | ||
|
|
0f40b5f022 | ||
|
|
94c5ffa92e | ||
|
|
3c9bf56767 | ||
|
|
9024acb16e | ||
|
|
7732402122 | ||
|
|
2059bce6a7 | ||
|
|
499c86a8b8 | ||
|
|
ef084eb145 | ||
|
|
3f3ae7c38f | ||
|
|
83d762287f | ||
|
|
5af0acf147 | ||
|
|
907dfee6f7 | ||
|
|
d587d24603 | ||
|
|
c246b8f164 | ||
|
|
deecafa053 | ||
|
|
f8810106a2 | ||
|
|
8e35fbe532 | ||
|
|
b1fdccde6e | ||
|
|
6741f100a1 | ||
|
|
e05e9ea1b2 | ||
|
|
197341de5e | ||
|
|
2933482be3 | ||
|
|
c7ce23239f | ||
|
|
79115f7439 | ||
|
|
4457103672 | ||
|
|
d204875f72 | ||
|
|
c690e73b1a | ||
|
|
b5d39c5f21 | ||
|
|
7f8834cb04 | ||
|
|
0dab7e293c | ||
|
|
8f016880af | ||
|
|
f471462a84 | ||
|
|
58eec8df1e | ||
|
|
2655b8b43a | ||
|
|
ea86c043a4 | ||
|
|
c1faa616bc | ||
|
|
a68bf5a30d | ||
|
|
9568b8e0e5 | ||
|
|
b34321ef87 | ||
|
|
4971f1c5bf | ||
|
|
962b52bd5b | ||
|
|
9449bfa414 | ||
|
|
5b8e9066c6 | ||
|
|
8690be7fd2 | ||
|
|
497267016b | ||
|
|
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 |
213
.clang-format
@@ -1,105 +1,216 @@
|
||||
BasedOnStyle: Chromium
|
||||
Language: Cpp
|
||||
Standard: Cpp11
|
||||
---
|
||||
Language: Cpp
|
||||
# BasedOnStyle: LLVM
|
||||
AccessModifierOffset: -1
|
||||
AlignAfterOpenBracket: false
|
||||
AlignConsecutiveAssignments: false
|
||||
AlignConsecutiveDeclarations: false
|
||||
AlignConsecutiveMacros: true
|
||||
AlignEscapedNewlines: true
|
||||
AlignOperands: false
|
||||
AlignAfterOpenBracket: Align
|
||||
AlignArrayOfStructures: None
|
||||
AlignConsecutiveAssignments:
|
||||
Enabled: false
|
||||
AcrossEmptyLines: false
|
||||
AcrossComments: false
|
||||
AlignCompound: false
|
||||
PadOperators: true
|
||||
AlignConsecutiveBitFields:
|
||||
Enabled: false
|
||||
AcrossEmptyLines: false
|
||||
AcrossComments: false
|
||||
AlignCompound: false
|
||||
PadOperators: false
|
||||
AlignConsecutiveDeclarations:
|
||||
Enabled: false
|
||||
AcrossEmptyLines: false
|
||||
AcrossComments: false
|
||||
AlignCompound: false
|
||||
PadOperators: false
|
||||
AlignConsecutiveMacros:
|
||||
Enabled: true
|
||||
AcrossEmptyLines: false
|
||||
AcrossComments: false
|
||||
AlignCompound: false
|
||||
PadOperators: false
|
||||
AlignEscapedNewlines: Left
|
||||
AlignOperands: DontAlign
|
||||
AlignTrailingComments: true
|
||||
AllowAllArgumentsOnNextLine: false
|
||||
AllowAllConstructorInitializersOnNextLine: true
|
||||
AllowAllParametersOfDeclarationOnNextLine: false
|
||||
AllowShortBlocksOnASingleLine: false
|
||||
AllowShortEnumsOnASingleLine: true
|
||||
AllowShortBlocksOnASingleLine: Never
|
||||
AllowShortCaseLabelsOnASingleLine: true
|
||||
AllowShortFunctionsOnASingleLine: true
|
||||
AllowShortIfStatementsOnASingleLine: true
|
||||
AllowShortLambdasOnASingleLine: true
|
||||
AllowShortFunctionsOnASingleLine: All
|
||||
AllowShortLambdasOnASingleLine: All
|
||||
AllowShortIfStatementsOnASingleLine: AllIfsAndElse
|
||||
AllowShortLoopsOnASingleLine: true
|
||||
AlwaysBreakAfterDefinitionReturnType: None
|
||||
AlwaysBreakAfterReturnType: None
|
||||
AlwaysBreakBeforeMultilineStrings: false
|
||||
AlwaysBreakTemplateDeclarations: No
|
||||
AttributeMacros:
|
||||
- __capability
|
||||
BinPackArguments: true
|
||||
BinPackParameters: true
|
||||
BreakBeforeBraces: false
|
||||
BreakBeforeBinaryOperators: false
|
||||
BreakBeforeBraces: Stroustrup
|
||||
BreakBeforeTernaryOperators: false
|
||||
BreakConstructorInitializers: BeforeColon
|
||||
BraceWrapping:
|
||||
AfterCaseLabel: false
|
||||
AfterClass: false
|
||||
AfterControlStatement: Never
|
||||
AfterEnum: false
|
||||
AfterFunction: false
|
||||
AfterNamespace: false
|
||||
AfterObjCDeclaration: false
|
||||
AfterStruct: false
|
||||
AfterUnion: false
|
||||
AfterExternBlock: false
|
||||
BeforeCatch: false
|
||||
BeforeElse: true
|
||||
BeforeLambdaBody: false
|
||||
BeforeWhile: false
|
||||
IndentBraces: false
|
||||
SplitEmptyFunction: false
|
||||
SplitEmptyRecord: false
|
||||
SplitEmptyNamespace: false
|
||||
BreakBeforeBinaryOperators: None
|
||||
BreakBeforeConceptDeclarations: Always
|
||||
BreakBeforeBraces: Custom
|
||||
BreakBeforeInheritanceComma: false
|
||||
BreakInheritanceList: BeforeColon
|
||||
BreakBeforeTernaryOperators: false
|
||||
BreakConstructorInitializersBeforeComma: false
|
||||
BreakConstructorInitializers: BeforeColon
|
||||
BreakAfterJavaFieldAnnotations: false
|
||||
BreakStringLiterals: false
|
||||
ColumnLimit: 0
|
||||
ColumnLimit: 0
|
||||
CommentPragmas: '^ IWYU pragma:'
|
||||
QualifierAlignment: Leave
|
||||
CompactNamespaces: false
|
||||
ConstructorInitializerAllOnOneLineOrOnePerLine: false
|
||||
ConstructorInitializerIndentWidth: 4
|
||||
ContinuationIndentWidth: 2
|
||||
Cpp11BracedListStyle: false
|
||||
DeriveLineEnding: true
|
||||
DerivePointerAlignment: true
|
||||
DisableFormat: false
|
||||
ExperimentalAutoDetectBinPacking: true
|
||||
DerivePointerAlignment: false
|
||||
DisableFormat: false
|
||||
EmptyLineAfterAccessModifier: Never
|
||||
EmptyLineBeforeAccessModifier: LogicalBlock
|
||||
ExperimentalAutoDetectBinPacking: false
|
||||
PackConstructorInitializers: BinPack
|
||||
BasedOnStyle: ''
|
||||
ConstructorInitializerAllOnOneLineOrOnePerLine: false
|
||||
AllowAllConstructorInitializersOnNextLine: true
|
||||
FixNamespaceComments: true
|
||||
IncludeBlocks: Preserve
|
||||
ForEachMacros:
|
||||
- foreach
|
||||
- Q_FOREACH
|
||||
- BOOST_FOREACH
|
||||
IfMacros:
|
||||
- KJ_IF_MAYBE
|
||||
IncludeBlocks: Preserve
|
||||
IncludeCategories:
|
||||
- Regex: '^"(llvm|llvm-c|clang|clang-c)/'
|
||||
Priority: 2
|
||||
SortPriority: 0
|
||||
CaseSensitive: false
|
||||
- Regex: '^(<|"(gtest|gmock|isl|json)/)'
|
||||
Priority: 3
|
||||
SortPriority: 0
|
||||
CaseSensitive: false
|
||||
- Regex: '.*'
|
||||
Priority: 1
|
||||
SortPriority: 0
|
||||
CaseSensitive: false
|
||||
IncludeIsMainRegex: '(Test)?$'
|
||||
IncludeIsMainSourceRegex: ''
|
||||
IndentAccessModifiers: false
|
||||
IndentCaseLabels: true
|
||||
IndentCaseBlocks: false
|
||||
IndentGotoLabels: true
|
||||
IndentPPDirectives: AfterHash
|
||||
IndentWidth: 2
|
||||
IndentExternBlock: AfterExternBlock
|
||||
IndentRequiresClause: true
|
||||
IndentWidth: 2
|
||||
IndentWrappedFunctionNames: false
|
||||
InsertBraces: false
|
||||
InsertTrailingCommas: None
|
||||
JavaScriptQuotes: Leave
|
||||
JavaScriptWrapImports: true
|
||||
KeepEmptyLinesAtTheStartOfBlocks: true
|
||||
LambdaBodyIndentation: Signature
|
||||
MacroBlockBegin: ''
|
||||
MacroBlockEnd: ''
|
||||
MaxEmptyLinesToKeep: 100
|
||||
NamespaceIndentation: None
|
||||
ObjCBinPackProtocolList: Auto
|
||||
ObjCBlockIndentWidth: 2
|
||||
ObjCBreakBeforeNestedBlockParam: true
|
||||
ObjCSpaceAfterProperty: false
|
||||
ObjCSpaceBeforeProtocolList: false
|
||||
ObjCSpaceBeforeProtocolList: true
|
||||
PenaltyBreakAssignment: 0
|
||||
PenaltyBreakBeforeFirstCallParameter: 0
|
||||
PenaltyBreakComment: 0
|
||||
PenaltyBreakFirstLessLess: 0
|
||||
PenaltyBreakOpenParenthesis: 0
|
||||
PenaltyBreakString: 0
|
||||
PenaltyBreakTemplateDeclaration: 0
|
||||
PenaltyExcessCharacter: 0
|
||||
PenaltyReturnTypeOnItsOwnLine: 0
|
||||
PenaltyIndentedWhitespace: 0
|
||||
PointerAlignment: Right
|
||||
ReflowComments: false
|
||||
SortIncludes: false
|
||||
PPIndentWidth: -1
|
||||
ReferenceAlignment: Pointer
|
||||
ReflowComments: false
|
||||
RemoveBracesLLVM: false
|
||||
RequiresClausePosition: OwnLine
|
||||
SeparateDefinitionBlocks: Leave
|
||||
ShortNamespaceLines: 1
|
||||
SortIncludes: false
|
||||
SortJavaStaticImport: Before
|
||||
SortUsingDeclarations: false
|
||||
SpaceAfterCStyleCast: false
|
||||
SpaceAfterLogicalNot: false
|
||||
SpaceAfterTemplateKeyword: false
|
||||
SpaceBeforeAssignmentOperators: true
|
||||
SpaceBeforeCaseColon: false
|
||||
SpaceBeforeCpp11BracedList: true
|
||||
SpaceBeforeCtorInitializerColon: true
|
||||
SpaceBeforeInheritanceColon: true
|
||||
SpaceBeforeParens: ControlStatements
|
||||
SpaceBeforeParensOptions:
|
||||
AfterControlStatements: true
|
||||
AfterForeachMacros: true
|
||||
AfterFunctionDefinitionName: false
|
||||
AfterFunctionDeclarationName: false
|
||||
AfterIfMacros: true
|
||||
AfterOverloadedOperator: false
|
||||
AfterRequiresInClause: false
|
||||
AfterRequiresInExpression: false
|
||||
BeforeNonEmptyParentheses: false
|
||||
SpaceAroundPointerQualifiers: Default
|
||||
SpaceBeforeRangeBasedForLoopColon: true
|
||||
SpaceBeforeSquareBrackets: false
|
||||
SpaceInEmptyBlock: false
|
||||
SpaceInEmptyParentheses: false
|
||||
SpacesBeforeTrailingComments: 2
|
||||
SpacesInAngles: false
|
||||
SpacesInCStyleCastParentheses: false
|
||||
SpacesInAngles: Never
|
||||
SpacesInConditionalStatement: false
|
||||
SpacesInContainerLiterals: true
|
||||
SpacesInCStyleCastParentheses: false
|
||||
SpacesInLineCommentPrefix:
|
||||
Minimum: 1
|
||||
Maximum: -1
|
||||
SpacesInParentheses: false
|
||||
SpacesInSquareBrackets: false
|
||||
UseCRLF: false
|
||||
UseTab: Never
|
||||
BreakBeforeBraces: Custom
|
||||
BraceWrapping:
|
||||
AfterFunction: false
|
||||
AfterCaseLabel: false
|
||||
AfterStruct: false
|
||||
AfterClass: false
|
||||
AfterEnum: false
|
||||
AfterUnion: false
|
||||
AfterControlStatement: Never
|
||||
AfterNamespace: false
|
||||
AfterObjCDeclaration: false
|
||||
AfterExternBlock: false
|
||||
BeforeCatch: false
|
||||
BeforeElse: true
|
||||
SplitEmptyFunction: false
|
||||
SplitEmptyRecord: false
|
||||
SplitEmptyNamespace: false
|
||||
SpaceBeforeSquareBrackets: false
|
||||
BitFieldColonSpacing: Both
|
||||
Standard: Latest
|
||||
StatementAttributeLikeMacros:
|
||||
- Q_EMIT
|
||||
StatementMacros:
|
||||
- Q_UNUSED
|
||||
- QT_REQUIRE_VERSION
|
||||
TabWidth: 8
|
||||
UseCRLF: false
|
||||
UseTab: Never
|
||||
WhitespaceSensitiveMacros:
|
||||
- STRINGIZE
|
||||
- PP_STRINGIZE
|
||||
- BOOST_PP_STRINGIZE
|
||||
- NS_SWIFT_NAME
|
||||
- CF_SWIFT_NAME
|
||||
...
|
||||
|
||||
|
||||
9
.github/ISSUE_TEMPLATE.md
vendored
@@ -7,12 +7,9 @@ assignees: ''
|
||||
|
||||
---
|
||||
|
||||
For technical issues, questions and feature suggestions/requests please use the forum on https://forum.strawberrymusicplayer.org/
|
||||
|
||||
Check the Changelog to see if the issue is already fixed:
|
||||
https://github.com/strawberrymusicplayer/strawberry/blob/master/Changelog
|
||||
|
||||
If it's fixed, try the latest development build from: https://builds.strawberrymusicplayer.org/
|
||||
- [ ] I have checked the [FAQ](https://wiki.strawberrymusicplayer.org/wiki/FAQ) for answers.
|
||||
- [ ] I have checked the [Changelog](https://github.com/strawberrymusicplayer/strawberry/blob/master/Changelog) that the issue is not already fixed.
|
||||
- [ ] I believe this issue is a bug, and not a general technical issue, question or feature requests that can be discussed on the [forum](https://forum.strawberrymusicplayer.org/).
|
||||
|
||||
**Describe the bug**
|
||||
A clear and concise description of what the bug is.
|
||||
|
||||
6
.github/dependabot.yml
vendored
Normal file
@@ -0,0 +1,6 @@
|
||||
version: 2
|
||||
updates:
|
||||
- package-ecosystem: "github-actions"
|
||||
directory: "/"
|
||||
schedule:
|
||||
interval: daily
|
||||
1063
.github/workflows/build.yml
vendored
Normal file
1324
.github/workflows/ccpp.yml
vendored
88
.github/workflows/codeql.yml
vendored
Normal file
@@ -0,0 +1,88 @@
|
||||
name: CodeQL
|
||||
on: [push, pull_request]
|
||||
|
||||
jobs:
|
||||
codeql:
|
||||
name: CodeQL Analyze
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
container:
|
||||
image: opensuse/tumbleweed
|
||||
|
||||
steps:
|
||||
|
||||
- name: Refresh repositories
|
||||
run: zypper -n --gpg-auto-import-keys ref
|
||||
|
||||
- name: Upgrade packages
|
||||
run: zypper -n --gpg-auto-import-keys dup
|
||||
|
||||
- name: Install packages
|
||||
run: >
|
||||
zypper -n --gpg-auto-import-keys in
|
||||
lsb-release
|
||||
rpm-build
|
||||
git
|
||||
tar
|
||||
gcc
|
||||
gcc-c++
|
||||
make
|
||||
cmake
|
||||
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
|
||||
vlc-devel
|
||||
taglib-devel
|
||||
libicu-devel
|
||||
libcdio-devel
|
||||
libgpod-devel
|
||||
libmtp-devel
|
||||
libchromaprint-devel
|
||||
qt6-core-devel
|
||||
qt6-gui-devel
|
||||
qt6-widgets-devel
|
||||
qt6-concurrent-devel
|
||||
qt6-network-devel
|
||||
qt6-sql-devel
|
||||
qt6-dbus-devel
|
||||
qt6-test-devel
|
||||
qt6-base-common-devel
|
||||
qt6-sql-sqlite
|
||||
qt6-linguist-devel
|
||||
desktop-file-utils
|
||||
update-desktop-files
|
||||
appstream-glib
|
||||
hicolor-icon-theme
|
||||
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v3
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Add safe git directory
|
||||
run: git config --global --add safe.directory ${GITHUB_WORKSPACE}
|
||||
|
||||
- name: Initialize CodeQL
|
||||
uses: github/codeql-action/init@v2
|
||||
with:
|
||||
languages: cpp
|
||||
|
||||
- name: Autobuild
|
||||
uses: github/codeql-action/autobuild@v2
|
||||
|
||||
- name: Perform CodeQL Analysis
|
||||
uses: github/codeql-action/analyze@v2
|
||||
with:
|
||||
category: "/language:cpp"
|
||||
41
.gitignore
vendored
@@ -65,19 +65,6 @@ ui_*.h
|
||||
*.moc
|
||||
*.qm
|
||||
|
||||
# QtCreator
|
||||
CMakeLists.txt.user*
|
||||
*.pro.user
|
||||
*.pro.user.*
|
||||
*creator.user*
|
||||
target_wrapper.*
|
||||
compile_commands.json
|
||||
|
||||
*.kdev4
|
||||
*.vscode
|
||||
*.code-workspace
|
||||
*.sublime-workspace
|
||||
|
||||
# Temporary files
|
||||
*~
|
||||
*.autosave
|
||||
@@ -105,17 +92,29 @@ Thumbs.db
|
||||
# Stuff in dist
|
||||
maketarball.sh
|
||||
changelog
|
||||
PKGBUILD
|
||||
|
||||
# Translations
|
||||
translations.pot
|
||||
zanata.xml
|
||||
.zanata-cache/
|
||||
|
||||
# Snap
|
||||
parts/
|
||||
prime/
|
||||
stage/
|
||||
*.snap
|
||||
/snap/.snapcraft/
|
||||
/*_source.tar.bz2
|
||||
# QtCreator
|
||||
CMakeLists.txt.user*
|
||||
*.pro.user
|
||||
*.pro.user.*
|
||||
*creator.user*
|
||||
target_wrapper.*
|
||||
compile_commands.json
|
||||
|
||||
*.kdev4
|
||||
*.vscode
|
||||
*.code-workspace
|
||||
*.sublime-workspace
|
||||
|
||||
# MSVC
|
||||
CMakeSettings.json
|
||||
/.vs
|
||||
/out
|
||||
|
||||
# CLion
|
||||
/.idea
|
||||
|
||||
5
3rdparty/README.md
vendored
@@ -23,3 +23,8 @@ macdeployqt
|
||||
A modified version of Qt's official macdeployqt utility that fixes some issues,
|
||||
this version also deploys gstreamer plugins.
|
||||
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__) */
|
||||
2
3rdparty/macdeployqt/main.cpp
vendored
@@ -87,7 +87,7 @@ int main(int argc, char **argv)
|
||||
|
||||
appBundlePath = QDir::cleanPath(appBundlePath);
|
||||
|
||||
if (QDir().exists(appBundlePath) == false) {
|
||||
if (!QDir(appBundlePath).exists()) {
|
||||
qDebug() << "Error: Could not find app bundle" << appBundlePath;
|
||||
return 1;
|
||||
}
|
||||
|
||||
177
3rdparty/macdeployqt/shared.cpp
vendored
@@ -40,6 +40,8 @@
|
||||
#include <QDir>
|
||||
#include <QSet>
|
||||
#include <QList>
|
||||
#include <QVariant>
|
||||
#include <QVariantMap>
|
||||
#include <QStack>
|
||||
#include <QDirIterator>
|
||||
#include <QLibraryInfo>
|
||||
@@ -104,7 +106,7 @@ inline QDebug operator<<(QDebug debug, const ApplicationBundleInfo &info)
|
||||
|
||||
bool copyFilePrintStatus(const QString &from, const QString &to)
|
||||
{
|
||||
if (QFile(to).exists()) {
|
||||
if (QFile::exists(to)) {
|
||||
if (alwaysOwerwriteEnabled) {
|
||||
QFile(to).remove();
|
||||
} else {
|
||||
@@ -141,7 +143,7 @@ bool copyFilePrintStatus(const QString &from, const QString &to)
|
||||
|
||||
bool linkFilePrintStatus(const QString &file, const QString &link)
|
||||
{
|
||||
if (QFile(link).exists()) {
|
||||
if (QFile::exists(link)) {
|
||||
if (QFile(link).symLinkTarget().isEmpty())
|
||||
LogError() << link << "exists but it's a file.";
|
||||
else
|
||||
@@ -187,9 +189,7 @@ OtoolInfo findDependencyInfo(const QString &binaryPath)
|
||||
return info;
|
||||
}
|
||||
|
||||
static const QRegularExpression regexp(QStringLiteral(
|
||||
"^\\t(.+) \\(compatibility version (\\d+\\.\\d+\\.\\d+), "
|
||||
"current version (\\d+\\.\\d+\\.\\d+)(, weak)?\\)$"));
|
||||
static const QRegularExpression regexp(QStringLiteral("^\\t(.+) \\(compatibility version (\\d+\\.\\d+\\.\\d+), current version (\\d+\\.\\d+\\.\\d+)(, weak|, reexport)?\\)$"));
|
||||
|
||||
QString output = otool.readAllStandardOutput();
|
||||
QStringList outputLines = output.split("\n", Qt::SkipEmptyParts);
|
||||
@@ -220,6 +220,8 @@ OtoolInfo findDependencyInfo(const QString &binaryPath)
|
||||
for (const QString &outputLine : outputLines) {
|
||||
const auto match = regexp.match(outputLine);
|
||||
if (match.hasMatch()) {
|
||||
if (match.captured(1) == info.installName)
|
||||
continue; // Another arch reference to the same binary
|
||||
DylibInfo dylib;
|
||||
dylib.binaryPath = match.captured(1);
|
||||
dylib.compatibilityVersion = QVersionNumber::fromString(match.captured(2));
|
||||
@@ -300,11 +302,11 @@ FrameworkInfo parseOtoolLibraryLine(const QString &line, const QString &appBundl
|
||||
if (state == QtPath) {
|
||||
// Check for library name part
|
||||
if (part < parts.count() && parts.at(part).contains(".dylib")) {
|
||||
info.frameworkDirectory += "/" + (qtPath + currentPart + "/").simplified();
|
||||
info.frameworkDirectory += "/" + QString(qtPath + currentPart + "/").simplified();
|
||||
state = DylibName;
|
||||
continue;
|
||||
} else if (part < parts.count() && parts.at(part).endsWith(".framework")) {
|
||||
info.frameworkDirectory += "/" + (qtPath + "lib/").simplified();
|
||||
info.frameworkDirectory += "/" + QString(qtPath + "lib/").simplified();
|
||||
state = FrameworkName;
|
||||
continue;
|
||||
} else if (trimmed.startsWith("/") == false) { // If the line does not contain a full path, the app is using a binary Qt package.
|
||||
@@ -754,7 +756,7 @@ QString copyDylib(const FrameworkInfo &framework, const QString path)
|
||||
}
|
||||
|
||||
// Return if the dylib has already been deployed
|
||||
if (QFileInfo(dylibDestinationBinaryPath).exists() && !alwaysOwerwriteEnabled)
|
||||
if (QFileInfo::exists(dylibDestinationBinaryPath) && !alwaysOwerwriteEnabled)
|
||||
return dylibDestinationBinaryPath;
|
||||
|
||||
// Copy dylib binary
|
||||
@@ -821,7 +823,7 @@ QString copyFramework(const FrameworkInfo &framework, const QString path)
|
||||
// Contents/Info.plist should be Versions/5/Resources/Info.plist
|
||||
const QString legacyInfoPlistPath = framework.frameworkPath + "/Contents/Info.plist";
|
||||
const QString correctInfoPlistPath = frameworkDestinationDirectory + "/Resources/Info.plist";
|
||||
if (QFile(legacyInfoPlistPath).exists()) {
|
||||
if (QFile::exists(legacyInfoPlistPath)) {
|
||||
copyFilePrintStatus(legacyInfoPlistPath, correctInfoPlistPath);
|
||||
patch_debugInInfoPlist(correctInfoPlistPath);
|
||||
}
|
||||
@@ -865,6 +867,18 @@ void changeInstallName(const QString &bundlePath, const FrameworkInfo &framework
|
||||
if (!framework.installName.isEmpty() && framework.installName != framework.sourceFilePath) {
|
||||
changeInstallName(framework.installName, deployedInstallName, binary);
|
||||
}
|
||||
// Workaround for the case when the library ID name is a symlink, while the dependencies
|
||||
// specified using the canonical path to the library (QTBUG-56814)
|
||||
QFileInfo fileInfo= QFileInfo(framework.installName);
|
||||
QString canonicalInstallName = fileInfo.canonicalFilePath();
|
||||
if (!canonicalInstallName.isEmpty() && canonicalInstallName != framework.installName) {
|
||||
changeInstallName(canonicalInstallName, deployedInstallName, binary);
|
||||
// some libraries' inner dependencies (such as ffmpeg, nettle) use symbol link (QTBUG-100093)
|
||||
QString innerDependency = fileInfo.canonicalPath() + "/" + fileInfo.fileName();
|
||||
if (innerDependency != canonicalInstallName && innerDependency != framework.installName) {
|
||||
changeInstallName(innerDependency, deployedInstallName, binary);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1078,7 +1092,7 @@ QString getLibInfix(const QStringList &deployedFrameworks)
|
||||
{
|
||||
QString libInfix;
|
||||
for (const QString &framework : deployedFrameworks) {
|
||||
if (framework.startsWith(QStringLiteral("QtCore")) && framework.endsWith(QStringLiteral(".framework"))) {
|
||||
if (framework.startsWith(QStringLiteral("QtCore")) && framework.endsWith(QStringLiteral(".framework")) && !framework.contains(QStringLiteral("5Compat"))) {
|
||||
Q_ASSERT(framework.length() >= 16);
|
||||
// 16 == "QtCore" + ".framework"
|
||||
const int lengthOfLibInfix = framework.length() - 16;
|
||||
@@ -1214,28 +1228,42 @@ void deployPlugins(const ApplicationBundleInfo &appBundleInfo, const QString &pl
|
||||
|
||||
// GIO modules
|
||||
{
|
||||
QString sourcePath = qgetenv("GIO_EXTRA_MODULES");
|
||||
if (sourcePath.isEmpty()) {
|
||||
if (QFileInfo::exists("/usr/local/lib/gio/modules/libgiognutls.so")) {
|
||||
sourcePath = "/usr/local/lib/gio/modules/libgiognutls.so";
|
||||
QString giomodule_path = qgetenv("GIO_EXTRA_MODULES");
|
||||
if (giomodule_path.isEmpty()) {
|
||||
if (QDir().exists("/usr/local/lib/gio/modules")) {
|
||||
giomodule_path = "/usr/local/lib/gio/modules";
|
||||
}
|
||||
else if (QFileInfo::exists("/opt/local/lib/gio/modules/libgiognutls.so")) {
|
||||
sourcePath = "/opt/local/lib/gio/modules/libgiognutls.so";
|
||||
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";
|
||||
bool have_giomodule = false;
|
||||
for (const QString &giomodule : giomodules) {
|
||||
const QString sourcePath = giomodule_path + "/" + giomodule;
|
||||
QFileInfo fileinfo(sourcePath);
|
||||
if (!fileinfo.exists()) {
|
||||
LogError() << "Missing GIO module" << fileinfo.baseName();
|
||||
continue;
|
||||
}
|
||||
have_giomodule = true;
|
||||
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);
|
||||
}
|
||||
}
|
||||
const QString destinationPath = appBundleInfo.path + "/Contents/PlugIns/gio-modules/libgiognutls.so";
|
||||
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);
|
||||
|
||||
if (!have_giomodule) {
|
||||
qFatal("Missing GIO modules.");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// gst-plugin-scanner
|
||||
@@ -1262,8 +1290,13 @@ void deployPlugins(const ApplicationBundleInfo &appBundleInfo, const QString &pl
|
||||
}
|
||||
|
||||
// GStreamer plugins.
|
||||
QStringList gstreamer_plugins = QStringList() << "libgstapetag.dylib"
|
||||
QStringList gstreamer_plugins = QStringList()
|
||||
<< "libgstaes.dylib"
|
||||
<< "libgstaiff.dylib"
|
||||
<< "libgstapetag.dylib"
|
||||
<< "libgstapp.dylib"
|
||||
<< "libgstasf.dylib"
|
||||
<< "libgstasfmux.dylib"
|
||||
<< "libgstaudioconvert.dylib"
|
||||
<< "libgstaudiofx.dylib"
|
||||
<< "libgstaudiomixer.dylib"
|
||||
@@ -1271,48 +1304,51 @@ void deployPlugins(const ApplicationBundleInfo &appBundleInfo, const QString &pl
|
||||
<< "libgstaudiorate.dylib"
|
||||
<< "libgstaudioresample.dylib"
|
||||
<< "libgstaudiotestsrc.dylib"
|
||||
<< "libgstaudiovisualizers.dylib"
|
||||
<< "libgstauparse.dylib"
|
||||
<< "libgstautoconvert.dylib"
|
||||
<< "libgstautodetect.dylib"
|
||||
<< "libgstbs2b.dylib"
|
||||
<< "libgstcdio.dylib"
|
||||
<< "libgstcoreelements.dylib"
|
||||
<< "libgstdash.dylib"
|
||||
<< "libgstequalizer.dylib"
|
||||
<< "libgstfaac.dylib"
|
||||
<< "libgstfaad.dylib"
|
||||
<< "libgstfdkaac.dylib"
|
||||
<< "libgstflac.dylib"
|
||||
<< "libgstgio.dylib"
|
||||
//<< "libgstgme.dylib"
|
||||
<< "libgsthls.dylib"
|
||||
<< "libgsticydemux.dylib"
|
||||
<< "libgstid3demux.dylib"
|
||||
<< "libgstlevel.dylib"
|
||||
<< "libgstid3tag.dylib"
|
||||
<< "libgstisomp4.dylib"
|
||||
<< "libgstlame.dylib"
|
||||
<< "libgstlibav.dylib"
|
||||
<< "libgstmpg123.dylib"
|
||||
<< "libgstmusepack.dylib"
|
||||
<< "libgstogg.dylib"
|
||||
<< "libgstopenmpt.dylib"
|
||||
<< "libgstopus.dylib"
|
||||
<< "libgstopusparse.dylib"
|
||||
<< "libgstosxaudio.dylib"
|
||||
<< "libgstplayback.dylib"
|
||||
<< "libgstrawparse.dylib"
|
||||
<< "libgstreplaygain.dylib"
|
||||
<< "libgstsoup.dylib"
|
||||
<< "libgstspectrum.dylib"
|
||||
<< "libgsttypefindfunctions.dylib"
|
||||
<< "libgstvolume.dylib"
|
||||
<< "libgstxingmux.dylib"
|
||||
<< "libgsttcp.dylib"
|
||||
<< "libgstudp.dylib"
|
||||
<< "libgstpbtypes.dylib"
|
||||
<< "libgstplayback.dylib"
|
||||
<< "libgstreplaygain.dylib"
|
||||
<< "libgstrtp.dylib"
|
||||
<< "libgstrtsp.dylib"
|
||||
<< "libgstflac.dylib"
|
||||
<< "libgstwavparse.dylib"
|
||||
<< "libgstfaad.dylib"
|
||||
<< "libgstogg.dylib"
|
||||
<< "libgstopus.dylib"
|
||||
<< "libgstasf.dylib"
|
||||
<< "libgstsoup.dylib"
|
||||
<< "libgstspectrum.dylib"
|
||||
<< "libgstspeex.dylib"
|
||||
<< "libgsttaglib.dylib"
|
||||
<< "libgsttcp.dylib"
|
||||
<< "libgsttwolame.dylib"
|
||||
<< "libgsttypefindfunctions.dylib"
|
||||
<< "libgstudp.dylib"
|
||||
<< "libgstvolume.dylib"
|
||||
<< "libgstvorbis.dylib"
|
||||
<< "libgstisomp4.dylib"
|
||||
<< "libgstlibav.dylib"
|
||||
<< "libgstaiff.dylib"
|
||||
<< "libgstlame.dylib";
|
||||
|
||||
// macports does not have these.
|
||||
QStringList gstreamer_plugins_optional = QStringList() << "libgstopusparse.dylib"
|
||||
<< "libgstfaac.dylib"
|
||||
<< "libgstmusepack.dylib";
|
||||
<< "libgstwavenc.dylib"
|
||||
<< "libgstwavpack.dylib"
|
||||
<< "libgstwavparse.dylib"
|
||||
<< "libgstxingmux.dylib";
|
||||
|
||||
QString gstreamer_plugins_dir = qgetenv("GST_PLUGIN_PATH");
|
||||
if (gstreamer_plugins_dir.isEmpty()) {
|
||||
@@ -1327,13 +1363,16 @@ void deployPlugins(const ApplicationBundleInfo &appBundleInfo, const QString &pl
|
||||
}
|
||||
}
|
||||
|
||||
QStringList missing_gst_plugins;
|
||||
|
||||
for (const QString &plugin : gstreamer_plugins) {
|
||||
QFileInfo info(gstreamer_plugins_dir + "/" + plugin);
|
||||
if (!info.exists()) {
|
||||
info.setFile(gstreamer_plugins_dir + "/" + info.baseName() + QString(".so"));
|
||||
if (!info.exists()) {
|
||||
LogError() << "Missing gstreamer plugin" << info.baseName();
|
||||
qFatal("Missing %s", info.baseName().toUtf8().constData());
|
||||
missing_gst_plugins << info.baseName();
|
||||
continue;
|
||||
}
|
||||
}
|
||||
const QString &sourcePath = info.filePath();
|
||||
@@ -1345,22 +1384,8 @@ void deployPlugins(const ApplicationBundleInfo &appBundleInfo, const QString &pl
|
||||
}
|
||||
}
|
||||
|
||||
for (const QString &plugin : gstreamer_plugins_optional) {
|
||||
QFileInfo info(gstreamer_plugins_dir + "/" + plugin);
|
||||
if (!info.exists()) {
|
||||
info.setFile(gstreamer_plugins_dir + "/" + info.baseName() + QString(".so"));
|
||||
if (!info.exists()) {
|
||||
LogWarning() << "Skip missing gstreamer plugin" << info.baseName();
|
||||
continue;
|
||||
}
|
||||
}
|
||||
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);
|
||||
}
|
||||
if (!missing_gst_plugins.isEmpty()) {
|
||||
LogError() << "Missing gstreamer plugins" << missing_gst_plugins;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1443,11 +1468,11 @@ bool deployQmlImports(const QString &appBundlePath, DeploymentInfo deploymentInf
|
||||
#endif
|
||||
|
||||
// Fallback: Look relative to the macdeployqt binary
|
||||
if (!QFile(qmlImportScannerPath).exists())
|
||||
if (!QFile::exists(qmlImportScannerPath))
|
||||
qmlImportScannerPath = QCoreApplication::applicationDirPath() + "/qmlimportscanner";
|
||||
|
||||
// Verify that we found a qmlimportscanner binary
|
||||
if (!QFile(qmlImportScannerPath).exists()) {
|
||||
if (!QFile::exists(qmlImportScannerPath)) {
|
||||
LogError() << "qmlimportscanner not found at" << qmlImportScannerPath;
|
||||
LogError() << "Rebuild qtdeclarative/tools/qmlimportscanner";
|
||||
return false;
|
||||
@@ -1463,9 +1488,9 @@ bool deployQmlImports(const QString &appBundlePath, DeploymentInfo deploymentInf
|
||||
for (const QString &importPath : qmlImportPaths)
|
||||
argumentList << "-importPath" << importPath;
|
||||
#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
|
||||
QString qmlImportsPath = QLibraryInfo::path(QLibraryInfo::Qml2ImportsPath);
|
||||
QString qmlImportsPath = QLibraryInfo::path(QLibraryInfo::QmlImportsPath);
|
||||
#else
|
||||
QString qmlImportsPath = QLibraryInfo::location(QLibraryInfo::Qml2ImportsPath);
|
||||
QString qmlImportsPath = QLibraryInfo::location(QLibraryInfo::QmlImportsPath);
|
||||
#endif
|
||||
argumentList.append( "-importPath");
|
||||
argumentList.append(qmlImportsPath);
|
||||
@@ -1477,7 +1502,7 @@ bool deployQmlImports(const QString &appBundlePath, DeploymentInfo deploymentInf
|
||||
LogError() << "Could not start qmlimpoortscanner. Process error is" << qmlImportScanner.errorString();
|
||||
return false;
|
||||
}
|
||||
qmlImportScanner.waitForFinished();
|
||||
qmlImportScanner.waitForFinished(-1);
|
||||
|
||||
// log qmlimportscanner errors
|
||||
qmlImportScanner.setReadChannel(QProcess::StandardError);
|
||||
|
||||
38
3rdparty/singleapplication/CMakeLists.txt
vendored
@@ -1,38 +1,12 @@
|
||||
cmake_minimum_required(VERSION 3.0)
|
||||
cmake_minimum_required(VERSION 3.7)
|
||||
|
||||
include(CheckIncludeFiles)
|
||||
include(CheckFunctionExists)
|
||||
|
||||
if(CMAKE_VERSION VERSION_GREATER 3.0)
|
||||
check_function_exists(geteuid HAVE_GETEUID)
|
||||
check_function_exists(getpwuid HAVE_GETPWUID)
|
||||
endif()
|
||||
|
||||
set(SINGLEAPP-SOURCES singleapplication.cpp singleapplication_p.cpp)
|
||||
set(SINGLEAPP-MOC-HEADERS singleapplication.h singleapplication_p.h)
|
||||
qt_wrap_cpp(SINGLEAPP-SOURCES-MOC ${SINGLEAPP-MOC-HEADERS})
|
||||
add_library(singleapplication STATIC ${SINGLEAPP-SOURCES} ${SINGLEAPP-SOURCES-MOC})
|
||||
target_include_directories(singleapplication PRIVATE
|
||||
${CMAKE_CURRENT_SOURCE_DIR}
|
||||
${CMAKE_CURRENT_BINARY_DIR}
|
||||
)
|
||||
target_link_libraries(singleapplication PRIVATE
|
||||
${QtCore_LIBRARIES}
|
||||
${QtWidgets_LIBRARIES}
|
||||
${QtNetwork_LIBRARIES}
|
||||
)
|
||||
|
||||
set(SINGLECOREAPP-SOURCES singlecoreapplication.cpp singlecoreapplication_p.cpp)
|
||||
set(SINGLECOREAPP-MOC-HEADERS singlecoreapplication.h singlecoreapplication_p.h)
|
||||
qt_wrap_cpp(SINGLECOREAPP-SOURCES-MOC ${SINGLECOREAPP-MOC-HEADERS})
|
||||
add_library(singlecoreapplication STATIC ${SINGLECOREAPP-SOURCES} ${SINGLECOREAPP-SOURCES-MOC})
|
||||
target_include_directories(singlecoreapplication PRIVATE
|
||||
${CMAKE_CURRENT_SOURCE_DIR}
|
||||
${CMAKE_CURRENT_BINARY_DIR}
|
||||
)
|
||||
target_link_libraries(singlecoreapplication PRIVATE
|
||||
${QtCore_LIBRARIES}
|
||||
${QtNetwork_LIBRARIES}
|
||||
)
|
||||
check_function_exists(geteuid HAVE_GETEUID)
|
||||
check_function_exists(getpwuid HAVE_GETPWUID)
|
||||
|
||||
configure_file(config.h.in "${CMAKE_CURRENT_BINARY_DIR}/config.h")
|
||||
|
||||
add_subdirectory(singleapplication)
|
||||
add_subdirectory(singlecoreapplication)
|
||||
|
||||
2
3rdparty/singleapplication/LICENSE
vendored
@@ -1,6 +1,6 @@
|
||||
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
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
|
||||
86
3rdparty/singleapplication/README.md
vendored
@@ -1,7 +1,8 @@
|
||||
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
|
||||
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`
|
||||
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
|
||||
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
|
||||
submodule to your project and include it's contents with a `.pri` file. Here is
|
||||
how:
|
||||
submodule to your project. Here is how:
|
||||
|
||||
```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
|
||||
don't forget to specify which `QCoreApplication` class your app is using if it
|
||||
is not `QCoreApplication`.
|
||||
**Qmake:**
|
||||
|
||||
Then include the `singleapplication.pri` file in your `.pro` project file.
|
||||
|
||||
```qmake
|
||||
include(singleapplication/singleapplication.pri)
|
||||
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 SingleApplication class implements a `instanceStarted()` signal. You can
|
||||
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
|
||||
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
|
||||
---
|
||||
|
||||
### Members
|
||||
|
||||
```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
|
||||
@@ -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
|
||||
used to notify the primary instance whenever a secondary instance had been
|
||||
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
|
||||
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
|
||||
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.
|
||||
|
||||
---
|
||||
|
||||
```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
|
||||
|
||||
```cpp
|
||||
|
||||
270
3rdparty/singleapplication/singleapplication.cpp
vendored
@@ -1,270 +0,0 @@
|
||||
// The MIT License (MIT)
|
||||
//
|
||||
// Copyright (c) Itay Grudev 2015 - 2020
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the "Software"), to deal
|
||||
// in the Software without restriction, including without limitation the rights
|
||||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
// copies of the Software, and to permit persons to whom the Software is
|
||||
// furnished to do so, subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in
|
||||
// all copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
// THE SOFTWARE.
|
||||
|
||||
//
|
||||
// W A R N I N G !!!
|
||||
// -----------------
|
||||
//
|
||||
// This is a modified version of SingleApplication,
|
||||
// The original version is at:
|
||||
//
|
||||
// https://github.com/itay-grudev/SingleApplication
|
||||
//
|
||||
//
|
||||
|
||||
#include <cstdlib>
|
||||
#include <limits>
|
||||
|
||||
#include <QtGlobal>
|
||||
#include <QApplication>
|
||||
#include <QThread>
|
||||
#include <QSharedMemory>
|
||||
#include <QLocalSocket>
|
||||
#include <QByteArray>
|
||||
#include <QElapsedTimer>
|
||||
#include <QtDebug>
|
||||
|
||||
#include "singleapplication.h"
|
||||
#include "singleapplication_p.h"
|
||||
|
||||
/**
|
||||
* @brief Constructor. Checks and fires up LocalServer or closes the program if another instance already exists
|
||||
* @param argc
|
||||
* @param argv
|
||||
* @param allowSecondary Whether to enable secondary instance support
|
||||
* @param options Optional flags to toggle specific behaviour
|
||||
* @param timeout Maximum time blocking functions are allowed during app load
|
||||
*/
|
||||
SingleApplication::SingleApplication(int &argc, char *argv[], bool allowSecondary, Options options, int timeout)
|
||||
: app_t(argc, argv),
|
||||
d_ptr(new SingleApplicationPrivate(this)) {
|
||||
|
||||
Q_D(SingleApplication);
|
||||
|
||||
// Store the current mode of the program
|
||||
d->options_ = options;
|
||||
|
||||
// Generating an application ID used for identifying the shared memory block and QLocalServer
|
||||
d->genBlockServerName();
|
||||
|
||||
// To mitigate QSharedMemory issues with large amount of processes attempting to attach at the same time
|
||||
d->randomSleep();
|
||||
|
||||
#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.
|
||||
d->memory_ = new QSharedMemory(d->blockServerName_);
|
||||
d->memory_->attach();
|
||||
delete d->memory_;
|
||||
#endif
|
||||
|
||||
// Guarantee thread safe behaviour with a shared memory block.
|
||||
d->memory_ = new QSharedMemory(d->blockServerName_);
|
||||
|
||||
// Create a shared memory block
|
||||
if (d->memory_->create(sizeof(InstancesInfo))) {
|
||||
// Initialize the shared memory block
|
||||
if (!d->memory_->lock()) {
|
||||
qCritical() << "SingleApplication: Unable to lock memory block after create.";
|
||||
abortSafely();
|
||||
}
|
||||
d->initializeMemoryBlock();
|
||||
}
|
||||
else {
|
||||
if (d->memory_->error() == QSharedMemory::AlreadyExists) {
|
||||
// Attempt to attach to the memory segment
|
||||
if (!d->memory_->attach()) {
|
||||
qCritical() << "SingleApplication: Unable to attach to shared memory block.";
|
||||
abortSafely();
|
||||
}
|
||||
if (!d->memory_->lock()) {
|
||||
qCritical() << "SingleApplication: Unable to lock memory block after attach.";
|
||||
abortSafely();
|
||||
}
|
||||
}
|
||||
else {
|
||||
qCritical() << "SingleApplication: Unable to create block.";
|
||||
abortSafely();
|
||||
}
|
||||
}
|
||||
|
||||
InstancesInfo *inst = static_cast<InstancesInfo*>(d->memory_->data());
|
||||
QElapsedTimer time;
|
||||
time.start();
|
||||
|
||||
// Make sure the shared memory block is initialised and in consistent state
|
||||
forever {
|
||||
// If the shared memory block's checksum is valid continue
|
||||
if (d->blockChecksum() == inst->checksum) break;
|
||||
|
||||
// If more than 5s have elapsed, assume the primary instance crashed and assume it's position
|
||||
if (time.elapsed() > 5000) {
|
||||
qWarning() << "SingleApplication: Shared memory block has been in an inconsistent state from more than 5s. Assuming primary instance failure.";
|
||||
d->initializeMemoryBlock();
|
||||
}
|
||||
|
||||
// Otherwise wait for a random period and try again.
|
||||
// The random sleep here limits the probability of a collision between two racing apps and allows the app to initialise faster
|
||||
if (!d->memory_->unlock()) {
|
||||
qDebug() << "SingleApplication: Unable to unlock memory for random wait.";
|
||||
qDebug() << d->memory_->errorString();
|
||||
}
|
||||
d->randomSleep();
|
||||
if (!d->memory_->lock()) {
|
||||
qCritical() << "SingleApplication: Unable to lock memory after random wait.";
|
||||
abortSafely();
|
||||
}
|
||||
}
|
||||
|
||||
if (!inst->primary) {
|
||||
d->startPrimary();
|
||||
if (!d->memory_->unlock()) {
|
||||
qDebug() << "SingleApplication: Unable to unlock memory after primary start.";
|
||||
qDebug() << d->memory_->errorString();
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// Check if another instance can be started
|
||||
if (allowSecondary) {
|
||||
d->startSecondary();
|
||||
if (d->options_ & Mode::SecondaryNotification) {
|
||||
d->connectToPrimary(timeout, SingleApplicationPrivate::SecondaryInstance);
|
||||
}
|
||||
if (!d->memory_->unlock()) {
|
||||
qDebug() << "SingleApplication: Unable to unlock memory after secondary start.";
|
||||
qDebug() << d->memory_->errorString();
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (!d->memory_->unlock()) {
|
||||
qDebug() << "SingleApplication: Unable to unlock memory at end of execution.";
|
||||
qDebug() << d->memory_->errorString();
|
||||
}
|
||||
|
||||
d->connectToPrimary(timeout, SingleApplicationPrivate::NewInstance);
|
||||
|
||||
delete d;
|
||||
|
||||
::exit(EXIT_SUCCESS);
|
||||
|
||||
}
|
||||
|
||||
SingleApplication::~SingleApplication() {
|
||||
Q_D(SingleApplication);
|
||||
delete d;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the current application instance is primary.
|
||||
* @return Returns true if the instance is primary, false otherwise.
|
||||
*/
|
||||
bool SingleApplication::isPrimary() {
|
||||
Q_D(SingleApplication);
|
||||
return d->server_ != nullptr;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the current application instance is secondary.
|
||||
* @return Returns true if the instance is secondary, false otherwise.
|
||||
*/
|
||||
bool SingleApplication::isSecondary() {
|
||||
Q_D(SingleApplication);
|
||||
return d->server_ == nullptr;
|
||||
}
|
||||
|
||||
/**
|
||||
* Allows you to identify an instance by returning unique consecutive instance ids.
|
||||
* It is reset when the first (primary) instance of your app starts and only incremented afterwards.
|
||||
* @return Returns a unique instance id.
|
||||
*/
|
||||
quint32 SingleApplication::instanceId() {
|
||||
Q_D(SingleApplication);
|
||||
return d->instanceNumber_;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the OS PID (Process Identifier) of the process running the primary instance.
|
||||
* Especially useful when SingleApplication is coupled with OS. specific APIs.
|
||||
* @return Returns the primary instance PID.
|
||||
*/
|
||||
qint64 SingleApplication::primaryPid() {
|
||||
Q_D(SingleApplication);
|
||||
return d->primaryPid();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the username the primary instance is running as.
|
||||
* @return Returns the username the primary instance is running as.
|
||||
*/
|
||||
QString SingleApplication::primaryUser() {
|
||||
Q_D(SingleApplication);
|
||||
return d->primaryUser();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the username the current instance is running as.
|
||||
* @return Returns the username the current instance is running as.
|
||||
*/
|
||||
QString SingleApplication::currentUser() {
|
||||
Q_D(SingleApplication);
|
||||
return d->getUsername();
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends message to the Primary Instance.
|
||||
* @param message The message to send.
|
||||
* @param timeout the maximum timeout in milliseconds for blocking functions.
|
||||
* @return true if the message was sent successfully, false otherwise.
|
||||
*/
|
||||
bool SingleApplication::sendMessage(const QByteArray &message, const int timeout) {
|
||||
|
||||
Q_D(SingleApplication);
|
||||
|
||||
// Nobody to connect to
|
||||
if (isPrimary()) return false;
|
||||
|
||||
// Make sure the socket is connected
|
||||
if (!d->connectToPrimary(timeout, SingleApplicationPrivate::Reconnect)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
d->socket_->write(message);
|
||||
const bool dataWritten = d->socket_->waitForBytesWritten(timeout);
|
||||
d->socket_->flush();
|
||||
return dataWritten;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Cleans up the shared memory block and exits with a failure.
|
||||
* This function halts program execution.
|
||||
*/
|
||||
void SingleApplication::abortSafely() {
|
||||
|
||||
Q_D(SingleApplication);
|
||||
|
||||
qCritical() << "SingleApplication: " << d->memory_->error() << d->memory_->errorString();
|
||||
delete d;
|
||||
::exit(EXIT_FAILURE);
|
||||
|
||||
}
|
||||
18
3rdparty/singleapplication/singleapplication/CMakeLists.txt
vendored
Normal file
@@ -0,0 +1,18 @@
|
||||
cmake_minimum_required(VERSION 3.7)
|
||||
|
||||
add_definitions(-DSINGLEAPPLICATION)
|
||||
|
||||
set(SOURCES ../singleapplication_t.cpp ../singleapplication_p.cpp)
|
||||
set(HEADERS ../singleapplication_t.h ../singleapplication_p.h)
|
||||
qt_wrap_cpp(MOC ${HEADERS})
|
||||
add_library(singleapplication STATIC ${SOURCES} ${MOC})
|
||||
target_include_directories(singleapplication PUBLIC
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/..
|
||||
${CMAKE_CURRENT_BINARY_DIR}/..
|
||||
${Boost_INCLUDE_DIRS}
|
||||
)
|
||||
target_link_libraries(singleapplication PUBLIC
|
||||
${QtCore_LIBRARIES}
|
||||
${QtWidgets_LIBRARIES}
|
||||
${QtNetwork_LIBRARIES}
|
||||
)
|
||||
13
3rdparty/singleapplication/singleapplication/singleapplication.h
vendored
Normal file
@@ -0,0 +1,13 @@
|
||||
#ifndef SINGLEAPPLICATION_H
|
||||
#define SINGLEAPPLICATION_H
|
||||
|
||||
#ifdef SINGLEAPPLICATION
|
||||
# error "SINGLEAPPLICATION already defined."
|
||||
#endif
|
||||
|
||||
#define SINGLEAPPLICATION
|
||||
#include "../singleapplication_t.h"
|
||||
#undef SINGLEAPPLICATION_T_H
|
||||
#undef SINGLEAPPLICATION
|
||||
|
||||
#endif // SINGLEAPPLICATION_H
|
||||
293
3rdparty/singleapplication/singleapplication_p.cpp
vendored
@@ -44,6 +44,14 @@
|
||||
# include <pwd.h>
|
||||
#endif
|
||||
|
||||
#ifdef Q_OS_WIN
|
||||
# ifndef NOMINMAX
|
||||
# define NOMINMAX 1
|
||||
# endif
|
||||
# include <windows.h>
|
||||
# include <lmcons.h>
|
||||
#endif
|
||||
|
||||
#include <QObject>
|
||||
#include <QThread>
|
||||
#include <QIODevice>
|
||||
@@ -53,7 +61,6 @@
|
||||
#include <QCryptographicHash>
|
||||
#include <QLocalServer>
|
||||
#include <QLocalSocket>
|
||||
#include <QDir>
|
||||
#include <QElapsedTimer>
|
||||
#if QT_VERSION >= QT_VERSION_CHECK(5, 10, 0)
|
||||
# include <QRandomGenerator>
|
||||
@@ -61,49 +68,41 @@
|
||||
# include <QDateTime>
|
||||
#endif
|
||||
|
||||
#include "singleapplication.h"
|
||||
#include "singleapplication_t.h"
|
||||
#include "singleapplication_p.h"
|
||||
|
||||
#ifdef Q_OS_WIN
|
||||
# include <windows.h>
|
||||
# include <lmcons.h>
|
||||
#endif
|
||||
|
||||
SingleApplicationPrivate::SingleApplicationPrivate(SingleApplication *ptr)
|
||||
SingleApplicationPrivateClass::SingleApplicationPrivateClass(SingleApplicationClass *ptr)
|
||||
: q_ptr(ptr),
|
||||
memory_(nullptr),
|
||||
socket_(nullptr),
|
||||
server_(nullptr),
|
||||
instanceNumber_(-1) {}
|
||||
|
||||
SingleApplicationPrivate::~SingleApplicationPrivate() {
|
||||
SingleApplicationPrivateClass::~SingleApplicationPrivateClass() {
|
||||
|
||||
if (socket_ != nullptr) {
|
||||
if (socket_ != nullptr && socket_->isOpen()) {
|
||||
socket_->close();
|
||||
delete socket_;
|
||||
socket_ = nullptr;
|
||||
}
|
||||
|
||||
if (memory_ != nullptr) {
|
||||
memory_->lock();
|
||||
InstancesInfo *inst = static_cast<InstancesInfo*>(memory_->data());
|
||||
if (server_ != nullptr) {
|
||||
server_->close();
|
||||
delete server_;
|
||||
inst->primary = false;
|
||||
inst->primaryPid = -1;
|
||||
inst->primaryUser[0] = '\0';
|
||||
inst->checksum = blockChecksum();
|
||||
InstancesInfo *instance = static_cast<InstancesInfo*>(memory_->data());
|
||||
instance->primary = false;
|
||||
instance->primaryPid = -1;
|
||||
instance->primaryUser[0] = '\0';
|
||||
instance->checksum = blockChecksum();
|
||||
}
|
||||
memory_->unlock();
|
||||
|
||||
delete memory_;
|
||||
memory_ = nullptr;
|
||||
if (memory_->isAttached()) {
|
||||
memory_->detach();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
QString SingleApplicationPrivate::getUsername() {
|
||||
QString SingleApplicationPrivateClass::getUsername() {
|
||||
|
||||
#ifdef Q_OS_UNIX
|
||||
QString username;
|
||||
@@ -139,28 +138,36 @@ QString SingleApplicationPrivate::getUsername() {
|
||||
|
||||
}
|
||||
|
||||
void SingleApplicationPrivate::genBlockServerName() {
|
||||
void SingleApplicationPrivateClass::genBlockServerName() {
|
||||
|
||||
QCryptographicHash appData(QCryptographicHash::Sha256);
|
||||
appData.addData("SingleApplication", 17);
|
||||
appData.addData(SingleApplication::app_t::applicationName().toUtf8());
|
||||
appData.addData(SingleApplication::app_t::organizationName().toUtf8());
|
||||
appData.addData(SingleApplication::app_t::organizationDomain().toUtf8());
|
||||
appData.addData("SingleApplication");
|
||||
appData.addData(SingleApplicationClass::applicationName().toUtf8());
|
||||
appData.addData(SingleApplicationClass::organizationName().toUtf8());
|
||||
appData.addData(SingleApplicationClass::organizationDomain().toUtf8());
|
||||
|
||||
if (!(options_ & SingleApplication::Mode::ExcludeAppVersion)) {
|
||||
appData.addData(SingleApplication::app_t::applicationVersion().toUtf8());
|
||||
if (!(options_ & SingleApplicationClass::Mode::ExcludeAppVersion)) {
|
||||
appData.addData(SingleApplicationClass::applicationVersion().toUtf8());
|
||||
}
|
||||
|
||||
if (!(options_ & SingleApplication::Mode::ExcludeAppPath)) {
|
||||
#ifdef Q_OS_WIN
|
||||
appData.addData(SingleApplication::app_t::applicationFilePath().toLower().toUtf8());
|
||||
if (!(options_ & SingleApplicationClass::Mode::ExcludeAppPath)) {
|
||||
#if defined(Q_OS_UNIX)
|
||||
const QByteArray appImagePath = qgetenv("APPIMAGE");
|
||||
if (appImagePath.isEmpty()) {
|
||||
appData.addData(SingleApplicationClass::applicationFilePath().toUtf8());
|
||||
}
|
||||
else {
|
||||
appData.addData(appImagePath);
|
||||
}
|
||||
#elif defined(Q_OS_WIN)
|
||||
appData.addData(SingleApplicationClass::applicationFilePath().toLower().toUtf8());
|
||||
#else
|
||||
appData.addData(SingleApplication::app_t::applicationFilePath().toUtf8());
|
||||
appData.addData(SingleApplicationClass::applicationFilePath().toUtf8());
|
||||
#endif
|
||||
}
|
||||
|
||||
// User level block requires a user specific data in the hash
|
||||
if (options_ & SingleApplication::Mode::User) {
|
||||
if (options_ & SingleApplicationClass::Mode::User) {
|
||||
appData.addData(getUsername().toUtf8());
|
||||
}
|
||||
|
||||
@@ -169,36 +176,34 @@ void SingleApplicationPrivate::genBlockServerName() {
|
||||
|
||||
}
|
||||
|
||||
void SingleApplicationPrivate::initializeMemoryBlock() const {
|
||||
void SingleApplicationPrivateClass::initializeMemoryBlock() const {
|
||||
|
||||
InstancesInfo *inst = static_cast<InstancesInfo*>(memory_->data());
|
||||
inst->primary = false;
|
||||
inst->secondary = 0;
|
||||
inst->primaryPid = -1;
|
||||
inst->primaryUser[0] = '\0';
|
||||
inst->checksum = blockChecksum();
|
||||
InstancesInfo *instance = static_cast<InstancesInfo*>(memory_->data());
|
||||
instance->primary = false;
|
||||
instance->secondary = 0;
|
||||
instance->primaryPid = -1;
|
||||
instance->primaryUser[0] = '\0';
|
||||
instance->checksum = blockChecksum();
|
||||
|
||||
}
|
||||
|
||||
void SingleApplicationPrivate::startPrimary() {
|
||||
|
||||
Q_Q(SingleApplication);
|
||||
void SingleApplicationPrivateClass::startPrimary() {
|
||||
|
||||
// Reset the number of connections
|
||||
InstancesInfo *inst = static_cast<InstancesInfo*>(memory_->data());
|
||||
InstancesInfo *instance = static_cast<InstancesInfo*>(memory_->data());
|
||||
|
||||
inst->primary = true;
|
||||
inst->primaryPid = q->applicationPid();
|
||||
qstrncpy(inst->primaryUser, getUsername().toUtf8().data(), sizeof(inst->primaryUser));
|
||||
inst->checksum = blockChecksum();
|
||||
instance->primary = true;
|
||||
instance->primaryPid = QCoreApplication::applicationPid();
|
||||
qstrncpy(instance->primaryUser, getUsername().toUtf8().data(), sizeof(instance->primaryUser));
|
||||
instance->checksum = blockChecksum();
|
||||
instanceNumber_ = 0;
|
||||
// Successful creation means that no main process exists
|
||||
// So we start a QLocalServer to listen for connections
|
||||
QLocalServer::removeServer(blockServerName_);
|
||||
server_ = new QLocalServer();
|
||||
server_ = new QLocalServer(this);
|
||||
|
||||
// Restrict access to the socket according to the SingleApplication::Mode::User flag on User level or no restrictions
|
||||
if (options_ & SingleApplication::Mode::User) {
|
||||
if (options_ & SingleApplicationClass::Mode::User) {
|
||||
server_->setSocketOptions(QLocalServer::UserAccessOption);
|
||||
}
|
||||
else {
|
||||
@@ -206,32 +211,32 @@ void SingleApplicationPrivate::startPrimary() {
|
||||
}
|
||||
|
||||
server_->listen(blockServerName_);
|
||||
QObject::connect(server_, &QLocalServer::newConnection, this, &SingleApplicationPrivate::slotConnectionEstablished);
|
||||
QObject::connect(server_, &QLocalServer::newConnection, this, &SingleApplicationPrivateClass::slotConnectionEstablished);
|
||||
|
||||
}
|
||||
|
||||
void SingleApplicationPrivate::startSecondary() {
|
||||
void SingleApplicationPrivateClass::startSecondary() {
|
||||
|
||||
InstancesInfo *inst = static_cast<InstancesInfo*>(memory_->data());
|
||||
InstancesInfo *instance = static_cast<InstancesInfo*>(memory_->data());
|
||||
|
||||
inst->secondary += 1;
|
||||
inst->checksum = blockChecksum();
|
||||
instanceNumber_ = inst->secondary;
|
||||
instance->secondary += 1;
|
||||
instance->checksum = blockChecksum();
|
||||
instanceNumber_ = instance->secondary;
|
||||
|
||||
}
|
||||
|
||||
bool SingleApplicationPrivate::connectToPrimary(const int timeout, const ConnectionType connectionType) {
|
||||
|
||||
QElapsedTimer time;
|
||||
time.start();
|
||||
bool SingleApplicationPrivateClass::connectToPrimary(const int timeout, const ConnectionType connectionType) {
|
||||
|
||||
// Connect to the Local Server of the Primary Instance if not already connected.
|
||||
if (socket_ == nullptr) {
|
||||
socket_ = new QLocalSocket();
|
||||
socket_ = new QLocalSocket(this);
|
||||
}
|
||||
|
||||
if (socket_->state() == QLocalSocket::ConnectedState) return true;
|
||||
|
||||
QElapsedTimer time;
|
||||
time.start();
|
||||
|
||||
if (socket_->state() != QLocalSocket::ConnectedState) {
|
||||
|
||||
forever {
|
||||
@@ -253,7 +258,7 @@ bool SingleApplicationPrivate::connectToPrimary(const int timeout, const Connect
|
||||
}
|
||||
}
|
||||
|
||||
// Initialisation message according to the SingleApplication protocol
|
||||
// Initialization message according to the SingleApplication protocol
|
||||
QByteArray initMsg;
|
||||
QDataStream writeStream(&initMsg, QIODevice::WriteOnly);
|
||||
writeStream.setVersion(QDataStream::Qt_5_8);
|
||||
@@ -263,29 +268,57 @@ bool SingleApplicationPrivate::connectToPrimary(const int timeout, const Connect
|
||||
writeStream << instanceNumber_;
|
||||
|
||||
#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
|
||||
quint16 checksum = qChecksum(initMsg.constData(), static_cast<quint32>(initMsg.length()));
|
||||
#endif
|
||||
|
||||
writeStream << checksum;
|
||||
|
||||
// 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>(initMsg.length());
|
||||
|
||||
socket_->write(header);
|
||||
socket_->write(initMsg);
|
||||
bool result = socket_->waitForBytesWritten(static_cast<int>(timeout - time.elapsed()));
|
||||
socket_->flush();
|
||||
|
||||
return result;
|
||||
return writeConfirmedMessage(static_cast<int>(timeout - time.elapsed()), initMsg);
|
||||
|
||||
}
|
||||
|
||||
quint16 SingleApplicationPrivate::blockChecksum() const {
|
||||
void SingleApplicationPrivateClass::writeAck(QLocalSocket *sock) {
|
||||
sock->putChar('\n');
|
||||
}
|
||||
|
||||
bool SingleApplicationPrivateClass::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 SingleApplicationPrivateClass::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 SingleApplicationPrivateClass::blockChecksum() const {
|
||||
|
||||
#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
|
||||
quint16 checksum = qChecksum(QByteArray(static_cast<const char*>(memory_->constData()), offsetof(InstancesInfo, checksum)));
|
||||
@@ -297,22 +330,22 @@ quint16 SingleApplicationPrivate::blockChecksum() const {
|
||||
|
||||
}
|
||||
|
||||
qint64 SingleApplicationPrivate::primaryPid() const {
|
||||
qint64 SingleApplicationPrivateClass::primaryPid() const {
|
||||
|
||||
memory_->lock();
|
||||
InstancesInfo *inst = static_cast<InstancesInfo*>(memory_->data());
|
||||
qint64 pid = inst->primaryPid;
|
||||
InstancesInfo *instance = static_cast<InstancesInfo*>(memory_->data());
|
||||
qint64 pid = instance->primaryPid;
|
||||
memory_->unlock();
|
||||
|
||||
return pid;
|
||||
|
||||
}
|
||||
|
||||
QString SingleApplicationPrivate::primaryUser() const {
|
||||
QString SingleApplicationPrivateClass::primaryUser() const {
|
||||
|
||||
memory_->lock();
|
||||
InstancesInfo *inst = static_cast<InstancesInfo*>(memory_->data());
|
||||
QByteArray username = inst->primaryUser;
|
||||
InstancesInfo *instance = static_cast<InstancesInfo*>(memory_->data());
|
||||
QByteArray username = instance->primaryUser;
|
||||
memory_->unlock();
|
||||
|
||||
return QString::fromUtf8(username);
|
||||
@@ -322,41 +355,45 @@ QString SingleApplicationPrivate::primaryUser() const {
|
||||
/**
|
||||
* @brief Executed when a connection has been made to the LocalServer
|
||||
*/
|
||||
void SingleApplicationPrivate::slotConnectionEstablished() {
|
||||
void SingleApplicationPrivateClass::slotConnectionEstablished() {
|
||||
|
||||
QLocalSocket *nextConnSocket = server_->nextPendingConnection();
|
||||
connectionMap_.insert(nextConnSocket, ConnectionInfo());
|
||||
|
||||
QObject::connect(nextConnSocket, &QLocalSocket::aboutToClose, this, [nextConnSocket, this]() {
|
||||
const ConnectionInfo info = connectionMap_[nextConnSocket];
|
||||
QObject::connect(nextConnSocket, &QLocalSocket::aboutToClose, this, [this, nextConnSocket]() {
|
||||
const ConnectionInfo &info = connectionMap_[nextConnSocket];
|
||||
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, [this, nextConnSocket]() {
|
||||
connectionMap_.remove(nextConnSocket);
|
||||
nextConnSocket->deleteLater();
|
||||
});
|
||||
|
||||
QObject::connect(nextConnSocket, &QLocalSocket::readyRead, this, [nextConnSocket, this]() {
|
||||
const ConnectionInfo info = connectionMap_[nextConnSocket];
|
||||
QObject::connect(nextConnSocket, &QLocalSocket::readyRead, this, [this, nextConnSocket]() {
|
||||
const ConnectionInfo &info = connectionMap_[nextConnSocket];
|
||||
switch (info.stage) {
|
||||
case StageHeader:
|
||||
readInitMessageHeader(nextConnSocket);
|
||||
case StageInitHeader:
|
||||
readMessageHeader(nextConnSocket, StageInitBody);
|
||||
break;
|
||||
case StageBody:
|
||||
case StageInitBody:
|
||||
readInitMessageBody(nextConnSocket);
|
||||
break;
|
||||
case StageConnected:
|
||||
case StageConnectedHeader:
|
||||
readMessageHeader(nextConnSocket, StageConnectedBody);
|
||||
break;
|
||||
case StageConnectedBody:
|
||||
slotDataAvailable(nextConnSocket, info.instanceId);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
};
|
||||
}
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
void SingleApplicationPrivate::readInitMessageHeader(QLocalSocket *sock) {
|
||||
void SingleApplicationPrivateClass::readMessageHeader(QLocalSocket *sock, const SingleApplicationPrivateClass::ConnectionStage nextStage) {
|
||||
|
||||
if (!connectionMap_.contains(sock)) {
|
||||
return;
|
||||
@@ -373,30 +410,34 @@ void SingleApplicationPrivate::readInitMessageHeader(QLocalSocket *sock) {
|
||||
quint64 msgLen = 0;
|
||||
headerStream >> msgLen;
|
||||
ConnectionInfo &info = connectionMap_[sock];
|
||||
info.stage = StageBody;
|
||||
info.stage = nextStage;
|
||||
info.msgLen = msgLen;
|
||||
|
||||
if (sock->bytesAvailable() >= static_cast<qint64>(msgLen)) {
|
||||
readInitMessageBody(sock);
|
||||
}
|
||||
writeAck(sock);
|
||||
|
||||
}
|
||||
|
||||
void SingleApplicationPrivate::readInitMessageBody(QLocalSocket *sock) {
|
||||
|
||||
Q_Q(SingleApplication);
|
||||
bool SingleApplicationPrivateClass::isFrameComplete(QLocalSocket *sock) {
|
||||
|
||||
if (!connectionMap_.contains(sock)) {
|
||||
return;
|
||||
return false;
|
||||
}
|
||||
|
||||
ConnectionInfo &info = connectionMap_[sock];
|
||||
if (sock->bytesAvailable() < static_cast<qint64>(info.msgLen)) {
|
||||
const ConnectionInfo &info = connectionMap_[sock];
|
||||
return (sock->bytesAvailable() >= static_cast<qint64>(info.msgLen));
|
||||
|
||||
}
|
||||
|
||||
void SingleApplicationPrivateClass::readInitMessageBody(QLocalSocket *sock) {
|
||||
|
||||
Q_Q(SingleApplicationClass);
|
||||
|
||||
if (!isFrameComplete(sock)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Read the message body
|
||||
QByteArray msgBytes = sock->read(info.msgLen);
|
||||
QByteArray msgBytes = sock->readAll();
|
||||
QDataStream readStream(msgBytes);
|
||||
readStream.setVersion(QDataStream::Qt_5_8);
|
||||
|
||||
@@ -405,10 +446,9 @@ void SingleApplicationPrivate::readInitMessageBody(QLocalSocket *sock) {
|
||||
readStream >> latin1Name;
|
||||
|
||||
// connection type
|
||||
ConnectionType connectionType = InvalidConnection;
|
||||
quint8 connTypeVal = InvalidConnection;
|
||||
readStream >> connTypeVal;
|
||||
connectionType = static_cast<ConnectionType>(connTypeVal);
|
||||
const ConnectionType connectionType = static_cast<ConnectionType>(connTypeVal);
|
||||
|
||||
// instance id
|
||||
quint32 instanceId = 0;
|
||||
@@ -431,27 +471,38 @@ void SingleApplicationPrivate::readInitMessageBody(QLocalSocket *sock) {
|
||||
return;
|
||||
}
|
||||
|
||||
ConnectionInfo &info = connectionMap_[sock];
|
||||
info.instanceId = instanceId;
|
||||
info.stage = StageConnected;
|
||||
info.stage = StageConnectedHeader;
|
||||
|
||||
if (connectionType == NewInstance || (connectionType == SecondaryInstance && options_ & SingleApplication::Mode::SecondaryNotification)) {
|
||||
Q_EMIT q->instanceStarted();
|
||||
if (connectionType == NewInstance || (connectionType == SecondaryInstance && options_ & SingleApplicationClass::Mode::SecondaryNotification)) {
|
||||
emit q->instanceStarted();
|
||||
}
|
||||
|
||||
if (sock->bytesAvailable() > 0) {
|
||||
slotDataAvailable(sock, instanceId);
|
||||
}
|
||||
writeAck(sock);
|
||||
|
||||
}
|
||||
|
||||
void SingleApplicationPrivate::slotDataAvailable(QLocalSocket *dataSocket, const quint32 instanceId) {
|
||||
void SingleApplicationPrivateClass::slotDataAvailable(QLocalSocket *dataSocket, const quint32 instanceId) {
|
||||
|
||||
Q_Q(SingleApplication);
|
||||
Q_EMIT q->receivedMessage(instanceId, dataSocket->readAll());
|
||||
Q_Q(SingleApplicationClass);
|
||||
|
||||
if (!isFrameComplete(dataSocket)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const QByteArray message = dataSocket->readAll();
|
||||
|
||||
writeAck(dataSocket);
|
||||
|
||||
ConnectionInfo &info = connectionMap_[dataSocket];
|
||||
info.stage = StageConnectedHeader;
|
||||
|
||||
emit q->receivedMessage(instanceId, message);
|
||||
|
||||
}
|
||||
|
||||
void SingleApplicationPrivate::slotClientConnectionClosed(QLocalSocket *closedSocket, const quint32 instanceId) {
|
||||
void SingleApplicationPrivateClass::slotClientConnectionClosed(QLocalSocket *closedSocket, const quint32 instanceId) {
|
||||
|
||||
if (closedSocket->bytesAvailable() > 0) {
|
||||
slotDataAvailable(closedSocket, instanceId);
|
||||
@@ -459,13 +510,13 @@ void SingleApplicationPrivate::slotClientConnectionClosed(QLocalSocket *closedSo
|
||||
|
||||
}
|
||||
|
||||
void SingleApplicationPrivate::randomSleep() {
|
||||
void SingleApplicationPrivateClass::randomSleep() {
|
||||
|
||||
#if QT_VERSION >= QT_VERSION_CHECK(5, 10, 0)
|
||||
QThread::msleep(QRandomGenerator::global()->bounded(8U, 18U));
|
||||
#else
|
||||
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
|
||||
|
||||
}
|
||||
|
||||
58
3rdparty/singleapplication/singleapplication_p.h
vendored
@@ -39,31 +39,19 @@
|
||||
#include <QString>
|
||||
#include <QHash>
|
||||
|
||||
#include "singleapplication.h"
|
||||
#include "singleapplication_t.h"
|
||||
|
||||
class QLocalServer;
|
||||
class QLocalSocket;
|
||||
class QSharedMemory;
|
||||
|
||||
struct InstancesInfo {
|
||||
bool primary;
|
||||
quint32 secondary;
|
||||
qint64 primaryPid;
|
||||
char primaryUser[128];
|
||||
quint16 checksum;
|
||||
};
|
||||
|
||||
struct ConnectionInfo {
|
||||
explicit ConnectionInfo() : msgLen(0), instanceId(0), stage(0) {}
|
||||
quint64 msgLen;
|
||||
quint32 instanceId;
|
||||
quint8 stage;
|
||||
};
|
||||
|
||||
class SingleApplicationPrivate : public QObject {
|
||||
class SingleApplicationPrivateClass : public QObject {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit SingleApplicationPrivateClass(SingleApplicationClass *ptr);
|
||||
~SingleApplicationPrivateClass() override;
|
||||
|
||||
enum ConnectionType : quint8 {
|
||||
InvalidConnection = 0,
|
||||
NewInstance = 1,
|
||||
@@ -71,14 +59,28 @@ class SingleApplicationPrivate : public QObject {
|
||||
Reconnect = 3
|
||||
};
|
||||
enum ConnectionStage : quint8 {
|
||||
StageHeader = 0,
|
||||
StageBody = 1,
|
||||
StageConnected = 2,
|
||||
StageInitHeader = 0,
|
||||
StageInitBody = 1,
|
||||
StageConnectedHeader = 2,
|
||||
StageConnectedBody = 3
|
||||
};
|
||||
Q_DECLARE_PUBLIC(SingleApplication)
|
||||
Q_DECLARE_PUBLIC(SingleApplicationClass)
|
||||
|
||||
explicit SingleApplicationPrivate(SingleApplication *ptr);
|
||||
~SingleApplicationPrivate() override;
|
||||
struct InstancesInfo {
|
||||
explicit InstancesInfo() : primary(false), secondary(0), primaryPid(0), checksum(0) {}
|
||||
bool primary;
|
||||
quint32 secondary;
|
||||
qint64 primaryPid;
|
||||
char primaryUser[128];
|
||||
quint16 checksum;
|
||||
};
|
||||
|
||||
struct ConnectionInfo {
|
||||
explicit ConnectionInfo() : msgLen(0), instanceId(0), stage(0) {}
|
||||
quint64 msgLen;
|
||||
quint32 instanceId;
|
||||
quint8 stage;
|
||||
};
|
||||
|
||||
static QString getUsername();
|
||||
void genBlockServerName();
|
||||
@@ -89,17 +91,21 @@ class SingleApplicationPrivate : public QObject {
|
||||
quint16 blockChecksum() const;
|
||||
qint64 primaryPid() const;
|
||||
QString primaryUser() const;
|
||||
void readInitMessageHeader(QLocalSocket *socket);
|
||||
bool isFrameComplete(QLocalSocket *sock);
|
||||
void readMessageHeader(QLocalSocket *socket, const ConnectionStage nextStage);
|
||||
void readInitMessageBody(QLocalSocket *socket);
|
||||
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;
|
||||
SingleApplicationClass *q_ptr;
|
||||
QSharedMemory *memory_;
|
||||
QLocalSocket *socket_;
|
||||
QLocalServer *server_;
|
||||
quint32 instanceNumber_;
|
||||
QString blockServerName_;
|
||||
SingleApplication::Options options_;
|
||||
SingleApplicationClass::Options options_;
|
||||
QHash<QLocalSocket*, ConnectionInfo> connectionMap_;
|
||||
|
||||
public slots:
|
||||
|
||||
330
3rdparty/singleapplication/singleapplication_t.cpp
vendored
Normal file
@@ -0,0 +1,330 @@
|
||||
// The MIT License (MIT)
|
||||
//
|
||||
// Copyright (c) Itay Grudev 2015 - 2020
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the "Software"), to deal
|
||||
// in the Software without restriction, including without limitation the rights
|
||||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
// copies of the Software, and to permit persons to whom the Software is
|
||||
// furnished to do so, subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in
|
||||
// all copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
// THE SOFTWARE.
|
||||
|
||||
//
|
||||
// W A R N I N G !!!
|
||||
// -----------------
|
||||
//
|
||||
// This is a modified version of SingleApplication,
|
||||
// The original version is at:
|
||||
//
|
||||
// https://github.com/itay-grudev/SingleApplication
|
||||
//
|
||||
//
|
||||
|
||||
#include <cstdlib>
|
||||
#include <limits>
|
||||
#include <memory>
|
||||
|
||||
#include <boost/scope_exit.hpp>
|
||||
|
||||
#include <QtGlobal>
|
||||
#include <QThread>
|
||||
#include <QSharedMemory>
|
||||
#include <QLocalSocket>
|
||||
#include <QByteArray>
|
||||
#include <QElapsedTimer>
|
||||
#include <QtDebug>
|
||||
#if QT_VERSION >= QT_VERSION_CHECK(6, 6, 0)
|
||||
# include <QNativeIpcKey>
|
||||
#endif
|
||||
|
||||
#include "singleapplication_t.h"
|
||||
#include "singleapplication_p.h"
|
||||
|
||||
/**
|
||||
* @brief Constructor. Checks and fires up LocalServer or closes the program if another instance already exists
|
||||
* @param argc
|
||||
* @param argv
|
||||
* @param allowSecondary Whether to enable secondary instance support
|
||||
* @param options Optional flags to toggle specific behaviour
|
||||
* @param timeout Maximum time blocking functions are allowed during app load
|
||||
*/
|
||||
SingleApplicationClass::SingleApplicationClass(int &argc, char *argv[], const bool allowSecondary, const Options options, const int timeout)
|
||||
: ApplicationClass(argc, argv),
|
||||
d_ptr(new SingleApplicationPrivateClass(this)) {
|
||||
|
||||
#if defined(SINGLEAPPLICATION)
|
||||
Q_D(SingleApplication);
|
||||
#elif defined(SINGLECOREAPPLICATION)
|
||||
Q_D(SingleCoreApplication);
|
||||
#endif
|
||||
|
||||
// Store the current mode of the program
|
||||
d->options_ = options;
|
||||
|
||||
// Generating an application ID used for identifying the shared memory block and QLocalServer
|
||||
d->genBlockServerName();
|
||||
|
||||
// To mitigate QSharedMemory issues with large amount of processes attempting to attach at the same time
|
||||
SingleApplicationPrivateClass::randomSleep();
|
||||
|
||||
#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.
|
||||
{
|
||||
# if QT_VERSION >= QT_VERSION_CHECK(6, 6, 0)
|
||||
std::unique_ptr<QSharedMemory> memory = std::make_unique<QSharedMemory>(QNativeIpcKey(d->blockServerName_));
|
||||
# else
|
||||
std::unique_ptr<QSharedMemory> memory = std::make_unique<QSharedMemory>(d->blockServerName_);
|
||||
# endif
|
||||
if (memory->attach()) {
|
||||
memory->detach();
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
// Guarantee thread safe behaviour with a shared memory block.
|
||||
#if QT_VERSION >= QT_VERSION_CHECK(6, 6, 0)
|
||||
QSharedMemory *memory = new QSharedMemory(QNativeIpcKey(d->blockServerName_), this);
|
||||
#else
|
||||
QSharedMemory *memory = new QSharedMemory(d->blockServerName_, this);
|
||||
#endif
|
||||
d->memory_ = memory;
|
||||
|
||||
bool primary = false;
|
||||
|
||||
// Create a shared memory block
|
||||
if (d->memory_->create(sizeof(SingleApplicationPrivateClass::InstancesInfo))) {
|
||||
primary = true;
|
||||
}
|
||||
else if (d->memory_->error() == QSharedMemory::AlreadyExists) {
|
||||
if (!d->memory_->attach()) {
|
||||
qCritical() << "SingleApplication: Unable to attach to shared memory block:" << d->memory_->error() << d->memory_->errorString();
|
||||
return;
|
||||
}
|
||||
}
|
||||
else {
|
||||
qCritical() << "SingleApplication: Unable to create shared memory block:" << d->memory_->error() << d->memory_->errorString();
|
||||
return;
|
||||
}
|
||||
|
||||
bool locked = false;
|
||||
|
||||
BOOST_SCOPE_EXIT((memory)(&locked)) {
|
||||
if (locked && !memory->unlock()) {
|
||||
qWarning() << "SingleApplication: Unable to unlock shared memory block:" << memory->error() << memory->errorString();
|
||||
return;
|
||||
}
|
||||
}BOOST_SCOPE_EXIT_END
|
||||
|
||||
if (!d->memory_->lock()) {
|
||||
qCritical() << "SingleApplication: Unable to lock shared memory block:" << d->memory_->error() << d->memory_->errorString();
|
||||
return;
|
||||
}
|
||||
locked = true;
|
||||
|
||||
if (primary) {
|
||||
// Initialize the shared memory block
|
||||
d->initializeMemoryBlock();
|
||||
}
|
||||
|
||||
SingleApplicationPrivateClass::InstancesInfo *instance = static_cast<SingleApplicationPrivateClass::InstancesInfo*>(d->memory_->data());
|
||||
QElapsedTimer time;
|
||||
time.start();
|
||||
|
||||
// Make sure the shared memory block is initialized and in a consistent state
|
||||
while (d->blockChecksum() != instance->checksum) {
|
||||
|
||||
// If more than 5 seconds have elapsed, assume the primary instance crashed and assume its position
|
||||
if (time.elapsed() > 5000) {
|
||||
qWarning() << "SingleApplication: Shared memory block has been in an inconsistent state from more than 5 seconds. Assuming primary instance failure.";
|
||||
d->initializeMemoryBlock();
|
||||
}
|
||||
|
||||
// Otherwise wait for a random period and try again.
|
||||
// The random sleep here limits the probability of a collision between two racing apps and allows the app to initialize faster
|
||||
if (locked) {
|
||||
if (d->memory_->unlock()) {
|
||||
locked = false;
|
||||
}
|
||||
else {
|
||||
qCritical() << "SingleApplication: Unable to unlock shared memory block for random wait:" << memory->error() << memory->errorString();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
SingleApplicationPrivateClass::randomSleep();
|
||||
|
||||
if (!d->memory_->lock()) {
|
||||
qCritical() << "SingleApplication: Unable to lock shared memory block after random wait:" << memory->error() << memory->errorString();
|
||||
return;
|
||||
}
|
||||
locked = true;
|
||||
|
||||
}
|
||||
|
||||
if (instance->primary) {
|
||||
// Check if another instance can be started
|
||||
if (allowSecondary) {
|
||||
d->startSecondary();
|
||||
if (d->options_ & Mode::SecondaryNotification) {
|
||||
d->connectToPrimary(timeout, SingleApplicationPrivateClass::SecondaryInstance);
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
d->startPrimary();
|
||||
primary = true;
|
||||
}
|
||||
|
||||
if (locked) {
|
||||
if (d->memory_->unlock()) {
|
||||
locked = false;
|
||||
}
|
||||
else {
|
||||
qWarning() << "SingleApplication: Unable to unlock shared memory block:" << memory->error() << memory->errorString();
|
||||
}
|
||||
}
|
||||
|
||||
if (!primary && !allowSecondary) {
|
||||
d->connectToPrimary(timeout, SingleApplicationPrivateClass::NewInstance);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
SingleApplicationClass::~SingleApplicationClass() {
|
||||
|
||||
#if defined(SINGLEAPPLICATION)
|
||||
Q_D(SingleApplication);
|
||||
#elif defined(SINGLECOREAPPLICATION)
|
||||
Q_D(SingleCoreApplication);
|
||||
#endif
|
||||
|
||||
delete d;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the current application instance is primary.
|
||||
* @return Returns true if the instance is primary, false otherwise.
|
||||
*/
|
||||
bool SingleApplicationClass::isPrimary() const {
|
||||
|
||||
#if defined(SINGLEAPPLICATION)
|
||||
Q_D(const SingleApplication);
|
||||
#elif defined(SINGLECOREAPPLICATION)
|
||||
Q_D(const SingleCoreApplication);
|
||||
#endif
|
||||
|
||||
return d->server_ != nullptr;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the current application instance is secondary.
|
||||
* @return Returns true if the instance is secondary, false otherwise.
|
||||
*/
|
||||
bool SingleApplicationClass::isSecondary() const {
|
||||
|
||||
#if defined(SINGLEAPPLICATION)
|
||||
Q_D(const SingleApplication);
|
||||
#elif defined(SINGLECOREAPPLICATION)
|
||||
Q_D(const SingleCoreApplication);
|
||||
#endif
|
||||
|
||||
return d->server_ == nullptr;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Allows you to identify an instance by returning unique consecutive instance ids.
|
||||
* It is reset when the first (primary) instance of your app starts and only incremented afterwards.
|
||||
* @return Returns a unique instance id.
|
||||
*/
|
||||
quint32 SingleApplicationClass::instanceId() const {
|
||||
|
||||
#if defined(SINGLEAPPLICATION)
|
||||
Q_D(const SingleApplication);
|
||||
#elif defined(SINGLECOREAPPLICATION)
|
||||
Q_D(const SingleCoreApplication);
|
||||
#endif
|
||||
|
||||
return d->instanceNumber_;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the OS PID (Process Identifier) of the process running the primary instance.
|
||||
* Especially useful when SingleApplication is coupled with OS. specific APIs.
|
||||
* @return Returns the primary instance PID.
|
||||
*/
|
||||
qint64 SingleApplicationClass::primaryPid() const {
|
||||
|
||||
#if defined(SINGLEAPPLICATION)
|
||||
Q_D(const SingleApplication);
|
||||
#elif defined(SINGLECOREAPPLICATION)
|
||||
Q_D(const SingleCoreApplication);
|
||||
#endif
|
||||
|
||||
return d->primaryPid();
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the username the primary instance is running as.
|
||||
* @return Returns the username the primary instance is running as.
|
||||
*/
|
||||
QString SingleApplicationClass::primaryUser() const {
|
||||
|
||||
#if defined(SINGLEAPPLICATION)
|
||||
Q_D(const SingleApplication);
|
||||
#elif defined(SINGLECOREAPPLICATION)
|
||||
Q_D(const SingleCoreApplication);
|
||||
#endif
|
||||
|
||||
return d->primaryUser();
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the username the current instance is running as.
|
||||
* @return Returns the username the current instance is running as.
|
||||
*/
|
||||
QString SingleApplicationClass::currentUser() const {
|
||||
return SingleApplicationPrivateClass::getUsername();
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends message to the Primary Instance.
|
||||
* @param message The message to send.
|
||||
* @param timeout the maximum timeout in milliseconds for blocking functions.
|
||||
* @return true if the message was sent successfully, false otherwise.
|
||||
*/
|
||||
bool SingleApplicationClass::sendMessage(const QByteArray &message, const int timeout) {
|
||||
|
||||
#if defined(SINGLEAPPLICATION)
|
||||
Q_D(SingleApplication);
|
||||
#elif defined(SINGLECOREAPPLICATION)
|
||||
Q_D(SingleCoreApplication);
|
||||
#endif
|
||||
|
||||
// Nobody to connect to
|
||||
if (isPrimary()) return false;
|
||||
|
||||
// Make sure the socket is connected
|
||||
if (!d->connectToPrimary(timeout, SingleApplicationPrivateClass::Reconnect)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return d->writeConfirmedMessage(timeout, message);
|
||||
|
||||
}
|
||||
@@ -31,25 +31,41 @@
|
||||
//
|
||||
//
|
||||
|
||||
#ifndef SINGLEAPPLICATION_H
|
||||
#define SINGLEAPPLICATION_H
|
||||
#ifndef SINGLEAPPLICATION_T_H
|
||||
#define SINGLEAPPLICATION_T_H
|
||||
|
||||
#include <QtGlobal>
|
||||
#include <QApplication>
|
||||
|
||||
#undef ApplicationClass
|
||||
#undef SingleApplicationClass
|
||||
#undef SingleApplicationPrivateClass
|
||||
|
||||
#if defined(SINGLEAPPLICATION)
|
||||
# include <QApplication>
|
||||
# define ApplicationClass QApplication
|
||||
# define SingleApplicationClass SingleApplication
|
||||
# define SingleApplicationPrivateClass SingleApplicationPrivate
|
||||
#elif defined(SINGLECOREAPPLICATION)
|
||||
# include <QCoreApplication>
|
||||
# define ApplicationClass QCoreApplication
|
||||
# define SingleApplicationClass SingleCoreApplication
|
||||
# define SingleApplicationPrivateClass SingleCoreApplicationPrivate
|
||||
#else
|
||||
# error "Define SINGLEAPPLICATION or SINGLECOREAPPLICATION."
|
||||
#endif
|
||||
|
||||
#include <QFlags>
|
||||
#include <QByteArray>
|
||||
|
||||
class SingleApplicationPrivate;
|
||||
class SingleApplicationPrivateClass;
|
||||
|
||||
/**
|
||||
* @brief The SingleApplication class handles multiple instances of the same Application
|
||||
* @see QApplication
|
||||
*/
|
||||
class SingleApplication : public QApplication { // clazy:exclude=ctor-missing-parent-argument
|
||||
class SingleApplicationClass : public ApplicationClass { // clazy:exclude=ctor-missing-parent-argument
|
||||
Q_OBJECT
|
||||
|
||||
typedef QApplication app_t;
|
||||
|
||||
public:
|
||||
/**
|
||||
* @brief Mode of operation of SingleApplication.
|
||||
@@ -61,7 +77,7 @@ class SingleApplication : public QApplication { // clazy:exclude=ctor-missing-p
|
||||
* block will be user wide.
|
||||
* @enum
|
||||
*/
|
||||
enum Mode {
|
||||
enum class Mode {
|
||||
User = 1 << 0,
|
||||
System = 1 << 1,
|
||||
SecondaryNotification = 1 << 2,
|
||||
@@ -86,48 +102,47 @@ class SingleApplication : public QApplication { // clazy:exclude=ctor-missing-p
|
||||
* instance and the secondary instance.
|
||||
* @note The timeout is just a hint for the maximum time of blocking
|
||||
* operations. It does not guarantee that the SingleApplication
|
||||
* initialisation will be completed in given time, though is a good hint.
|
||||
* initialization will be completed in given time, though is a good hint.
|
||||
* 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);
|
||||
~SingleApplication() override;
|
||||
explicit SingleApplicationClass(int &argc, char *argv[], const bool allowSecondary = false, const Options options = Mode::User, const int timeout = 1000);
|
||||
~SingleApplicationClass() override;
|
||||
|
||||
/**
|
||||
* @brief Returns if the instance is the primary instance
|
||||
* @returns {bool}
|
||||
*/
|
||||
bool isPrimary();
|
||||
bool isPrimary() const;
|
||||
|
||||
/**
|
||||
* @brief Returns if the instance is a secondary instance
|
||||
* @returns {bool}
|
||||
*/
|
||||
bool isSecondary();
|
||||
bool isSecondary() const;
|
||||
|
||||
/**
|
||||
* @brief Returns a unique identifier for the current instance
|
||||
* @returns {qint32}
|
||||
*/
|
||||
quint32 instanceId();
|
||||
quint32 instanceId() const;
|
||||
|
||||
/**
|
||||
* @brief Returns the process ID (PID) of the primary instance
|
||||
* @returns {qint64}
|
||||
*/
|
||||
qint64 primaryPid();
|
||||
qint64 primaryPid() const;
|
||||
|
||||
/**
|
||||
* @brief Returns the username of the user running the primary instance
|
||||
* @returns {QString}
|
||||
*/
|
||||
QString primaryUser();
|
||||
QString primaryUser() const;
|
||||
|
||||
/**
|
||||
* @brief Returns the username of the current user
|
||||
* @returns {QString}
|
||||
*/
|
||||
QString currentUser();
|
||||
QString currentUser() const;
|
||||
|
||||
/**
|
||||
* @brief Sends a message to the primary instance. Returns true on success.
|
||||
@@ -143,11 +158,15 @@ class SingleApplication : public QApplication { // clazy:exclude=ctor-missing-p
|
||||
void receivedMessage(quint32 instanceId, QByteArray message);
|
||||
|
||||
private:
|
||||
SingleApplicationPrivate *d_ptr;
|
||||
SingleApplicationPrivateClass *d_ptr;
|
||||
#if defined(SINGLEAPPLICATION)
|
||||
Q_DECLARE_PRIVATE(SingleApplication)
|
||||
#elif defined(SINGLECOREAPPLICATION)
|
||||
Q_DECLARE_PRIVATE(SingleCoreApplication)
|
||||
#endif
|
||||
void abortSafely();
|
||||
};
|
||||
|
||||
Q_DECLARE_OPERATORS_FOR_FLAGS(SingleApplication::Options)
|
||||
Q_DECLARE_OPERATORS_FOR_FLAGS(SingleApplicationClass::Options)
|
||||
|
||||
#endif // SINGLEAPPLICATION_H
|
||||
#endif // SINGLEAPPLICATION_T_H
|
||||
270
3rdparty/singleapplication/singlecoreapplication.cpp
vendored
@@ -1,270 +0,0 @@
|
||||
// The MIT License (MIT)
|
||||
//
|
||||
// Copyright (c) Itay Grudev 2015 - 2020
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the "Software"), to deal
|
||||
// in the Software without restriction, including without limitation the rights
|
||||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
// copies of the Software, and to permit persons to whom the Software is
|
||||
// furnished to do so, subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in
|
||||
// all copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
// THE SOFTWARE.
|
||||
|
||||
//
|
||||
// W A R N I N G !!!
|
||||
// -----------------
|
||||
//
|
||||
// This is a modified version of SingleApplication,
|
||||
// The original version is at:
|
||||
//
|
||||
// https://github.com/itay-grudev/SingleApplication
|
||||
//
|
||||
//
|
||||
|
||||
#include <cstdlib>
|
||||
#include <limits>
|
||||
|
||||
#include <QtGlobal>
|
||||
#include <QCoreApplication>
|
||||
#include <QThread>
|
||||
#include <QSharedMemory>
|
||||
#include <QLocalSocket>
|
||||
#include <QByteArray>
|
||||
#include <QElapsedTimer>
|
||||
#include <QtDebug>
|
||||
|
||||
#include "singlecoreapplication.h"
|
||||
#include "singlecoreapplication_p.h"
|
||||
|
||||
/**
|
||||
* @brief Constructor. Checks and fires up LocalServer or closes the program if another instance already exists
|
||||
* @param argc
|
||||
* @param argv
|
||||
* @param allowSecondary Whether to enable secondary instance support
|
||||
* @param options Optional flags to toggle specific behaviour
|
||||
* @param timeout Maximum time blocking functions are allowed during app load
|
||||
*/
|
||||
SingleCoreApplication::SingleCoreApplication(int &argc, char *argv[], bool allowSecondary, Options options, int timeout)
|
||||
: app_t(argc, argv),
|
||||
d_ptr(new SingleCoreApplicationPrivate(this)) {
|
||||
|
||||
Q_D(SingleCoreApplication);
|
||||
|
||||
// Store the current mode of the program
|
||||
d->options_ = options;
|
||||
|
||||
// Generating an application ID used for identifying the shared memory block and QLocalServer
|
||||
d->genBlockServerName();
|
||||
|
||||
// To mitigate QSharedMemory issues with large amount of processes attempting to attach at the same time
|
||||
d->randomSleep();
|
||||
|
||||
#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.
|
||||
d->memory_ = new QSharedMemory(d->blockServerName_);
|
||||
d->memory_->attach();
|
||||
delete d->memory_;
|
||||
#endif
|
||||
|
||||
// Guarantee thread safe behaviour with a shared memory block.
|
||||
d->memory_ = new QSharedMemory(d->blockServerName_);
|
||||
|
||||
// Create a shared memory block
|
||||
if (d->memory_->create(sizeof(InstancesInfo))) {
|
||||
// Initialize the shared memory block
|
||||
if (!d->memory_->lock()) {
|
||||
qCritical() << "SingleCoreApplication: Unable to lock memory block after create.";
|
||||
abortSafely();
|
||||
}
|
||||
d->initializeMemoryBlock();
|
||||
}
|
||||
else {
|
||||
if (d->memory_->error() == QSharedMemory::AlreadyExists) {
|
||||
// Attempt to attach to the memory segment
|
||||
if (!d->memory_->attach()) {
|
||||
qCritical() << "SingleCoreApplication: Unable to attach to shared memory block.";
|
||||
abortSafely();
|
||||
}
|
||||
if (!d->memory_->lock()) {
|
||||
qCritical() << "SingleCoreApplication: Unable to lock memory block after attach.";
|
||||
abortSafely();
|
||||
}
|
||||
}
|
||||
else {
|
||||
qCritical() << "SingleCoreApplication: Unable to create block.";
|
||||
abortSafely();
|
||||
}
|
||||
}
|
||||
|
||||
InstancesInfo *inst = static_cast<InstancesInfo*>(d->memory_->data());
|
||||
QElapsedTimer time;
|
||||
time.start();
|
||||
|
||||
// Make sure the shared memory block is initialised and in consistent state
|
||||
forever {
|
||||
// If the shared memory block's checksum is valid continue
|
||||
if (d->blockChecksum() == inst->checksum) break;
|
||||
|
||||
// If more than 5s have elapsed, assume the primary instance crashed and assume it's position
|
||||
if (time.elapsed() > 5000) {
|
||||
qWarning() << "SingleCoreApplication: Shared memory block has been in an inconsistent state from more than 5s. Assuming primary instance failure.";
|
||||
d->initializeMemoryBlock();
|
||||
}
|
||||
|
||||
// Otherwise wait for a random period and try again.
|
||||
// The random sleep here limits the probability of a collision between two racing apps and allows the app to initialise faster
|
||||
if (!d->memory_->unlock()) {
|
||||
qDebug() << "SingleCoreApplication: Unable to unlock memory for random wait.";
|
||||
qDebug() << d->memory_->errorString();
|
||||
}
|
||||
d->randomSleep();
|
||||
if (!d->memory_->lock()) {
|
||||
qCritical() << "SingleCoreApplication: Unable to lock memory after random wait.";
|
||||
abortSafely();
|
||||
}
|
||||
}
|
||||
|
||||
if (!inst->primary) {
|
||||
d->startPrimary();
|
||||
if (!d->memory_->unlock()) {
|
||||
qDebug() << "SingleCoreApplication: Unable to unlock memory after primary start.";
|
||||
qDebug() << d->memory_->errorString();
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// Check if another instance can be started
|
||||
if (allowSecondary) {
|
||||
d->startSecondary();
|
||||
if (d->options_ & Mode::SecondaryNotification) {
|
||||
d->connectToPrimary(timeout, SingleCoreApplicationPrivate::SecondaryInstance);
|
||||
}
|
||||
if (!d->memory_->unlock()) {
|
||||
qDebug() << "SingleCoreApplication: Unable to unlock memory after secondary start.";
|
||||
qDebug() << d->memory_->errorString();
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (!d->memory_->unlock()) {
|
||||
qDebug() << "SingleCoreApplication: Unable to unlock memory at end of execution.";
|
||||
qDebug() << d->memory_->errorString();
|
||||
}
|
||||
|
||||
d->connectToPrimary(timeout, SingleCoreApplicationPrivate::NewInstance);
|
||||
|
||||
delete d;
|
||||
|
||||
::exit(EXIT_SUCCESS);
|
||||
|
||||
}
|
||||
|
||||
SingleCoreApplication::~SingleCoreApplication() {
|
||||
Q_D(SingleCoreApplication);
|
||||
delete d;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the current application instance is primary.
|
||||
* @return Returns true if the instance is primary, false otherwise.
|
||||
*/
|
||||
bool SingleCoreApplication::isPrimary() {
|
||||
Q_D(SingleCoreApplication);
|
||||
return d->server_ != nullptr;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the current application instance is secondary.
|
||||
* @return Returns true if the instance is secondary, false otherwise.
|
||||
*/
|
||||
bool SingleCoreApplication::isSecondary() {
|
||||
Q_D(SingleCoreApplication);
|
||||
return d->server_ == nullptr;
|
||||
}
|
||||
|
||||
/**
|
||||
* Allows you to identify an instance by returning unique consecutive instance ids.
|
||||
* It is reset when the first (primary) instance of your app starts and only incremented afterwards.
|
||||
* @return Returns a unique instance id.
|
||||
*/
|
||||
quint32 SingleCoreApplication::instanceId() {
|
||||
Q_D(SingleCoreApplication);
|
||||
return d->instanceNumber_;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the OS PID (Process Identifier) of the process running the primary instance.
|
||||
* Especially useful when SingleCoreApplication is coupled with OS. specific APIs.
|
||||
* @return Returns the primary instance PID.
|
||||
*/
|
||||
qint64 SingleCoreApplication::primaryPid() {
|
||||
Q_D(SingleCoreApplication);
|
||||
return d->primaryPid();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the username the primary instance is running as.
|
||||
* @return Returns the username the primary instance is running as.
|
||||
*/
|
||||
QString SingleCoreApplication::primaryUser() {
|
||||
Q_D(SingleCoreApplication);
|
||||
return d->primaryUser();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the username the current instance is running as.
|
||||
* @return Returns the username the current instance is running as.
|
||||
*/
|
||||
QString SingleCoreApplication::currentUser() {
|
||||
Q_D(SingleCoreApplication);
|
||||
return d->getUsername();
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends message to the Primary Instance.
|
||||
* @param message The message to send.
|
||||
* @param timeout the maximum timeout in milliseconds for blocking functions.
|
||||
* @return true if the message was sent successfully, false otherwise.
|
||||
*/
|
||||
bool SingleCoreApplication::sendMessage(const QByteArray &message, const int timeout) {
|
||||
|
||||
Q_D(SingleCoreApplication);
|
||||
|
||||
// Nobody to connect to
|
||||
if (isPrimary()) return false;
|
||||
|
||||
// Make sure the socket is connected
|
||||
if (!d->connectToPrimary(timeout, SingleCoreApplicationPrivate::Reconnect)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
d->socket_->write(message);
|
||||
const bool dataWritten = d->socket_->waitForBytesWritten(timeout);
|
||||
d->socket_->flush();
|
||||
return dataWritten;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Cleans up the shared memory block and exits with a failure.
|
||||
* This function halts program execution.
|
||||
*/
|
||||
void SingleCoreApplication::abortSafely() {
|
||||
|
||||
Q_D(SingleCoreApplication);
|
||||
|
||||
qCritical() << "SingleCoreApplication: " << d->memory_->error() << d->memory_->errorString();
|
||||
delete d;
|
||||
::exit(EXIT_FAILURE);
|
||||
|
||||
}
|
||||
152
3rdparty/singleapplication/singlecoreapplication.h
vendored
@@ -1,152 +0,0 @@
|
||||
// The MIT License (MIT)
|
||||
//
|
||||
// Copyright (c) Itay Grudev 2015 - 2020
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the "Software"), to deal
|
||||
// in the Software without restriction, including without limitation the rights
|
||||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
// copies of the Software, and to permit persons to whom the Software is
|
||||
// furnished to do so, subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in
|
||||
// all copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
// THE SOFTWARE.
|
||||
|
||||
//
|
||||
// W A R N I N G !!!
|
||||
// -----------------
|
||||
//
|
||||
// This is a modified version of SingleApplication,
|
||||
// The original version is at:
|
||||
//
|
||||
// https://github.com/itay-grudev/SingleApplication
|
||||
//
|
||||
//
|
||||
|
||||
#ifndef SINGLECOREAPPLICATION_H
|
||||
#define SINGLECOREAPPLICATION_H
|
||||
|
||||
#include <QtGlobal>
|
||||
#include <QCoreApplication>
|
||||
#include <QFlags>
|
||||
#include <QByteArray>
|
||||
|
||||
class SingleCoreApplicationPrivate;
|
||||
|
||||
/**
|
||||
* @brief The SingleCoreApplication class handles multiple instances of the same Application
|
||||
* @see QCoreApplication
|
||||
*/
|
||||
class SingleCoreApplication : public QCoreApplication {
|
||||
Q_OBJECT
|
||||
|
||||
typedef QCoreApplication app_t;
|
||||
|
||||
public:
|
||||
/**
|
||||
* @brief Mode of operation of SingleCoreApplication.
|
||||
* Whether the block should be user-wide or system-wide and whether the
|
||||
* primary instance should be notified when a secondary instance had been
|
||||
* started.
|
||||
* @note Operating system can restrict the shared memory blocks to the same
|
||||
* user, in which case the User/System modes will have no effect and the
|
||||
* block will be user wide.
|
||||
* @enum
|
||||
*/
|
||||
enum Mode {
|
||||
User = 1 << 0,
|
||||
System = 1 << 1,
|
||||
SecondaryNotification = 1 << 2,
|
||||
ExcludeAppVersion = 1 << 3,
|
||||
ExcludeAppPath = 1 << 4
|
||||
};
|
||||
Q_DECLARE_FLAGS(Options, Mode)
|
||||
|
||||
/**
|
||||
* @brief Intitializes a SingleCoreApplication instance with argc command line
|
||||
* arguments in argv
|
||||
* @arg {int &} argc - Number of arguments in argv
|
||||
* @arg {const char *[]} argv - Supplied command line arguments
|
||||
* @arg {bool} allowSecondary - Whether to start the instance as secondary
|
||||
* if there is already a primary instance.
|
||||
* @arg {Mode} mode - Whether for the SingleCoreApplication block to be applied
|
||||
* User wide or System wide.
|
||||
* @arg {int} timeout - Timeout to wait in milliseconds.
|
||||
* @note argc and argv may be changed as Qt removes arguments that it
|
||||
* recognizes
|
||||
* @note Mode::SecondaryNotification only works if set on both the primary
|
||||
* instance and the secondary instance.
|
||||
* @note The timeout is just a hint for the maximum time of blocking
|
||||
* operations. It does not guarantee that the SingleCoreApplication
|
||||
* initialisation will be completed in given time, though is a good hint.
|
||||
* Usually 4*timeout would be the worst case (fail) scenario.
|
||||
*/
|
||||
explicit SingleCoreApplication(int &argc, char *argv[], const bool allowSecondary = false, const Options options = Mode::User, const int timeout = 1000);
|
||||
~SingleCoreApplication() override;
|
||||
|
||||
/**
|
||||
* @brief Returns if the instance is the primary instance
|
||||
* @returns {bool}
|
||||
*/
|
||||
bool isPrimary();
|
||||
|
||||
/**
|
||||
* @brief Returns if the instance is a secondary instance
|
||||
* @returns {bool}
|
||||
*/
|
||||
bool isSecondary();
|
||||
|
||||
/**
|
||||
* @brief Returns a unique identifier for the current instance
|
||||
* @returns {qint32}
|
||||
*/
|
||||
quint32 instanceId();
|
||||
|
||||
/**
|
||||
* @brief Returns the process ID (PID) of the primary instance
|
||||
* @returns {qint64}
|
||||
*/
|
||||
qint64 primaryPid();
|
||||
|
||||
/**
|
||||
* @brief Returns the username of the user running the primary instance
|
||||
* @returns {QString}
|
||||
*/
|
||||
QString primaryUser();
|
||||
|
||||
/**
|
||||
* @brief Returns the username of the current user
|
||||
* @returns {QString}
|
||||
*/
|
||||
QString currentUser();
|
||||
|
||||
/**
|
||||
* @brief Sends a message to the primary instance. Returns true on success.
|
||||
* @param {int} timeout - Timeout for connecting
|
||||
* @returns {bool}
|
||||
* @note sendMessage() will return false if invoked from the primary
|
||||
* instance.
|
||||
*/
|
||||
bool sendMessage(const QByteArray &message, const int timeout = 1000);
|
||||
|
||||
signals:
|
||||
void instanceStarted();
|
||||
void receivedMessage(quint32 instanceId, QByteArray message);
|
||||
|
||||
private:
|
||||
SingleCoreApplicationPrivate *d_ptr;
|
||||
Q_DECLARE_PRIVATE(SingleCoreApplication)
|
||||
void abortSafely();
|
||||
};
|
||||
|
||||
Q_DECLARE_OPERATORS_FOR_FLAGS(SingleCoreApplication::Options)
|
||||
|
||||
#endif // SINGLECOREAPPLICATION_H
|
||||
17
3rdparty/singleapplication/singlecoreapplication/CMakeLists.txt
vendored
Normal file
@@ -0,0 +1,17 @@
|
||||
cmake_minimum_required(VERSION 3.7)
|
||||
|
||||
add_definitions(-DSINGLECOREAPPLICATION)
|
||||
|
||||
set(SOURCES ../singleapplication_t.cpp ../singleapplication_p.cpp)
|
||||
set(HEADERS ../singleapplication_t.h ../singleapplication_p.h)
|
||||
qt_wrap_cpp(MOC ${HEADERS})
|
||||
add_library(singlecoreapplication STATIC ${SOURCES} ${MOC})
|
||||
target_include_directories(singlecoreapplication PUBLIC
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/..
|
||||
${CMAKE_CURRENT_BINARY_DIR}/..
|
||||
${Boost_INCLUDE_DIRS}
|
||||
)
|
||||
target_link_libraries(singlecoreapplication PUBLIC
|
||||
${QtCore_LIBRARIES}
|
||||
${QtNetwork_LIBRARIES}
|
||||
)
|
||||
13
3rdparty/singleapplication/singlecoreapplication/singlecoreapplication.h
vendored
Normal file
@@ -0,0 +1,13 @@
|
||||
#ifndef SINGLECOREAPPLICATION_H
|
||||
#define SINGLECOREAPPLICATION_H
|
||||
|
||||
#ifdef SINGLECOREAPPLICATION
|
||||
# error "SINGLECOREAPPLICATION already defined."
|
||||
#endif
|
||||
|
||||
#define SINGLECOREAPPLICATION
|
||||
#include "../singleapplication_t.h"
|
||||
#undef SINGLEAPPLICATION_T_H
|
||||
#undef SINGLECOREAPPLICATION
|
||||
|
||||
#endif // SINGLECOREAPPLICATION_H
|
||||
@@ -1,471 +0,0 @@
|
||||
// The MIT License (MIT)
|
||||
//
|
||||
// Copyright (c) Itay Grudev 2015 - 2020
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the "Software"), to deal
|
||||
// in the Software without restriction, including without limitation the rights
|
||||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
// copies of the Software, and to permit persons to whom the Software is
|
||||
// furnished to do so, subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in
|
||||
// all copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
// THE SOFTWARE.
|
||||
|
||||
//
|
||||
// W A R N I N G !!!
|
||||
// -----------------
|
||||
//
|
||||
// This is a modified version of SingleApplication,
|
||||
// The original version is at:
|
||||
//
|
||||
// https://github.com/itay-grudev/SingleApplication
|
||||
//
|
||||
//
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#include <QtGlobal>
|
||||
|
||||
#include <cstdlib>
|
||||
#include <cstddef>
|
||||
|
||||
#ifdef Q_OS_UNIX
|
||||
# include <unistd.h>
|
||||
# include <sys/types.h>
|
||||
# include <pwd.h>
|
||||
#endif
|
||||
|
||||
#include <QObject>
|
||||
#include <QThread>
|
||||
#include <QIODevice>
|
||||
#include <QSharedMemory>
|
||||
#include <QByteArray>
|
||||
#include <QDataStream>
|
||||
#include <QCryptographicHash>
|
||||
#include <QLocalServer>
|
||||
#include <QLocalSocket>
|
||||
#include <QDir>
|
||||
#include <QElapsedTimer>
|
||||
#if QT_VERSION >= QT_VERSION_CHECK(5, 10, 0)
|
||||
# include <QRandomGenerator>
|
||||
#else
|
||||
# include <QDateTime>
|
||||
#endif
|
||||
|
||||
#include "singlecoreapplication.h"
|
||||
#include "singlecoreapplication_p.h"
|
||||
|
||||
#ifdef Q_OS_WIN
|
||||
# include <windows.h>
|
||||
# include <lmcons.h>
|
||||
#endif
|
||||
|
||||
SingleCoreApplicationPrivate::SingleCoreApplicationPrivate(SingleCoreApplication *ptr)
|
||||
: q_ptr(ptr),
|
||||
memory_(nullptr),
|
||||
socket_(nullptr),
|
||||
server_(nullptr),
|
||||
instanceNumber_(-1) {}
|
||||
|
||||
SingleCoreApplicationPrivate::~SingleCoreApplicationPrivate() {
|
||||
|
||||
if (socket_ != nullptr) {
|
||||
socket_->close();
|
||||
delete socket_;
|
||||
socket_ = nullptr;
|
||||
}
|
||||
|
||||
if (memory_ != nullptr) {
|
||||
memory_->lock();
|
||||
InstancesInfo *inst = static_cast<InstancesInfo*>(memory_->data());
|
||||
if (server_ != nullptr) {
|
||||
server_->close();
|
||||
delete server_;
|
||||
inst->primary = false;
|
||||
inst->primaryPid = -1;
|
||||
inst->primaryUser[0] = '\0';
|
||||
inst->checksum = blockChecksum();
|
||||
}
|
||||
memory_->unlock();
|
||||
|
||||
delete memory_;
|
||||
memory_ = nullptr;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
QString SingleCoreApplicationPrivate::getUsername() {
|
||||
|
||||
#ifdef Q_OS_UNIX
|
||||
QString username;
|
||||
#if defined(HAVE_GETEUID) && defined(HAVE_GETPWUID)
|
||||
struct passwd *pw = getpwuid(geteuid());
|
||||
if (pw) {
|
||||
username = QString::fromLocal8Bit(pw->pw_name);
|
||||
}
|
||||
#endif
|
||||
if (username.isEmpty()) {
|
||||
#if QT_VERSION >= QT_VERSION_CHECK(5, 10, 0)
|
||||
username = qEnvironmentVariable("USER");
|
||||
#else
|
||||
username = QString::fromLocal8Bit(qgetenv("USER"));
|
||||
#endif
|
||||
}
|
||||
return username;
|
||||
#endif
|
||||
|
||||
#ifdef Q_OS_WIN
|
||||
wchar_t username[UNLEN + 1];
|
||||
// Specifies size of the buffer on input
|
||||
DWORD usernameLength = UNLEN + 1;
|
||||
if (GetUserNameW(username, &usernameLength)) {
|
||||
return QString::fromWCharArray(username);
|
||||
}
|
||||
#if QT_VERSION >= QT_VERSION_CHECK(5, 10, 0)
|
||||
return qEnvironmentVariable("USERNAME");
|
||||
#else
|
||||
return QString::fromLocal8Bit(qgetenv("USERNAME"));
|
||||
#endif
|
||||
#endif
|
||||
|
||||
}
|
||||
|
||||
void SingleCoreApplicationPrivate::genBlockServerName() {
|
||||
|
||||
QCryptographicHash appData(QCryptographicHash::Sha256);
|
||||
appData.addData("SingleApplication", 17);
|
||||
appData.addData(SingleCoreApplication::app_t::applicationName().toUtf8());
|
||||
appData.addData(SingleCoreApplication::app_t::organizationName().toUtf8());
|
||||
appData.addData(SingleCoreApplication::app_t::organizationDomain().toUtf8());
|
||||
|
||||
if (!(options_ & SingleCoreApplication::Mode::ExcludeAppVersion)) {
|
||||
appData.addData(SingleCoreApplication::app_t::applicationVersion().toUtf8());
|
||||
}
|
||||
|
||||
if (!(options_ & SingleCoreApplication::Mode::ExcludeAppPath)) {
|
||||
#ifdef Q_OS_WIN
|
||||
appData.addData(SingleCoreApplication::app_t::applicationFilePath().toLower().toUtf8());
|
||||
#else
|
||||
appData.addData(SingleCoreApplication::app_t::applicationFilePath().toUtf8());
|
||||
#endif
|
||||
}
|
||||
|
||||
// User level block requires a user specific data in the hash
|
||||
if (options_ & SingleCoreApplication::Mode::User) {
|
||||
appData.addData(getUsername().toUtf8());
|
||||
}
|
||||
|
||||
// Replace the backslash in RFC 2045 Base64 [a-zA-Z0-9+/=] to comply with server naming requirements.
|
||||
blockServerName_ = appData.result().toBase64().replace("/", "_");
|
||||
|
||||
}
|
||||
|
||||
void SingleCoreApplicationPrivate::initializeMemoryBlock() const {
|
||||
|
||||
InstancesInfo *inst = static_cast<InstancesInfo*>(memory_->data());
|
||||
inst->primary = false;
|
||||
inst->secondary = 0;
|
||||
inst->primaryPid = -1;
|
||||
inst->primaryUser[0] = '\0';
|
||||
inst->checksum = blockChecksum();
|
||||
|
||||
}
|
||||
|
||||
void SingleCoreApplicationPrivate::startPrimary() {
|
||||
|
||||
Q_Q(SingleCoreApplication);
|
||||
|
||||
// Reset the number of connections
|
||||
InstancesInfo *inst = static_cast<InstancesInfo*>(memory_->data());
|
||||
|
||||
inst->primary = true;
|
||||
inst->primaryPid = q->applicationPid();
|
||||
qstrncpy(inst->primaryUser, getUsername().toUtf8().data(), sizeof(inst->primaryUser));
|
||||
inst->checksum = blockChecksum();
|
||||
instanceNumber_ = 0;
|
||||
// Successful creation means that no main process exists
|
||||
// So we start a QLocalServer to listen for connections
|
||||
QLocalServer::removeServer(blockServerName_);
|
||||
server_ = new QLocalServer();
|
||||
|
||||
// Restrict access to the socket according to the SingleCoreApplication::Mode::User flag on User level or no restrictions
|
||||
if (options_ & SingleCoreApplication::Mode::User) {
|
||||
server_->setSocketOptions(QLocalServer::UserAccessOption);
|
||||
}
|
||||
else {
|
||||
server_->setSocketOptions(QLocalServer::WorldAccessOption);
|
||||
}
|
||||
|
||||
server_->listen(blockServerName_);
|
||||
QObject::connect(server_, &QLocalServer::newConnection, this, &SingleCoreApplicationPrivate::slotConnectionEstablished);
|
||||
|
||||
}
|
||||
|
||||
void SingleCoreApplicationPrivate::startSecondary() {
|
||||
|
||||
InstancesInfo *inst = static_cast<InstancesInfo*>(memory_->data());
|
||||
|
||||
inst->secondary += 1;
|
||||
inst->checksum = blockChecksum();
|
||||
instanceNumber_ = inst->secondary;
|
||||
|
||||
}
|
||||
|
||||
bool SingleCoreApplicationPrivate::connectToPrimary(const int timeout, const ConnectionType connectionType) {
|
||||
|
||||
QElapsedTimer time;
|
||||
time.start();
|
||||
|
||||
// Connect to the Local Server of the Primary Instance if not already connected.
|
||||
if (socket_ == nullptr) {
|
||||
socket_ = new QLocalSocket();
|
||||
}
|
||||
|
||||
if (socket_->state() == QLocalSocket::ConnectedState) return true;
|
||||
|
||||
if (socket_->state() != QLocalSocket::ConnectedState) {
|
||||
|
||||
forever {
|
||||
randomSleep();
|
||||
|
||||
if (socket_->state() != QLocalSocket::ConnectingState) {
|
||||
socket_->connectToServer(blockServerName_);
|
||||
}
|
||||
|
||||
if (socket_->state() == QLocalSocket::ConnectingState) {
|
||||
socket_->waitForConnected(static_cast<int>(timeout - time.elapsed()));
|
||||
}
|
||||
|
||||
// If connected break out of the loop
|
||||
if (socket_->state() == QLocalSocket::ConnectedState) break;
|
||||
|
||||
// If elapsed time since start is longer than the method timeout return
|
||||
if (time.elapsed() >= timeout) return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Initialisation message according to the SingleCoreApplication protocol
|
||||
QByteArray initMsg;
|
||||
QDataStream writeStream(&initMsg, QIODevice::WriteOnly);
|
||||
writeStream.setVersion(QDataStream::Qt_5_8);
|
||||
|
||||
writeStream << blockServerName_.toLatin1();
|
||||
writeStream << static_cast<quint8>(connectionType);
|
||||
writeStream << instanceNumber_;
|
||||
|
||||
#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
|
||||
quint16 checksum = qChecksum(QByteArray(initMsg, static_cast<quint32>(initMsg.length())));
|
||||
#else
|
||||
quint16 checksum = qChecksum(initMsg.constData(), static_cast<quint32>(initMsg.length()));
|
||||
#endif
|
||||
|
||||
writeStream << checksum;
|
||||
|
||||
// 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>(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() const {
|
||||
|
||||
#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
|
||||
quint16 checksum = qChecksum(QByteArray(static_cast<const char*>(memory_->constData()), offsetof(InstancesInfo, checksum)));
|
||||
#else
|
||||
quint16 checksum = qChecksum(static_cast<const char*>(memory_->constData()), offsetof(InstancesInfo, checksum));
|
||||
#endif
|
||||
|
||||
return checksum;
|
||||
|
||||
}
|
||||
|
||||
qint64 SingleCoreApplicationPrivate::primaryPid() const {
|
||||
|
||||
memory_->lock();
|
||||
InstancesInfo *inst = static_cast<InstancesInfo*>(memory_->data());
|
||||
qint64 pid = inst->primaryPid;
|
||||
memory_->unlock();
|
||||
|
||||
return pid;
|
||||
|
||||
}
|
||||
|
||||
QString SingleCoreApplicationPrivate::primaryUser() const {
|
||||
|
||||
memory_->lock();
|
||||
InstancesInfo *inst = static_cast<InstancesInfo*>(memory_->data());
|
||||
QByteArray username = inst->primaryUser;
|
||||
memory_->unlock();
|
||||
|
||||
return QString::fromUtf8(username);
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Executed when a connection has been made to the LocalServer
|
||||
*/
|
||||
void SingleCoreApplicationPrivate::slotConnectionEstablished() {
|
||||
|
||||
QLocalSocket *nextConnSocket = server_->nextPendingConnection();
|
||||
connectionMap_.insert(nextConnSocket, ConnectionInfo());
|
||||
|
||||
QObject::connect(nextConnSocket, &QLocalSocket::aboutToClose, this, [nextConnSocket, this]() {
|
||||
const ConnectionInfo info = connectionMap_[nextConnSocket];
|
||||
slotClientConnectionClosed(nextConnSocket, info.instanceId);
|
||||
});
|
||||
|
||||
QObject::connect(nextConnSocket, &QLocalSocket::disconnected, this, [nextConnSocket, this]() {
|
||||
connectionMap_.remove(nextConnSocket);
|
||||
nextConnSocket->deleteLater();
|
||||
});
|
||||
|
||||
QObject::connect(nextConnSocket, &QLocalSocket::readyRead, this, [nextConnSocket, this]() {
|
||||
const ConnectionInfo info = connectionMap_[nextConnSocket];
|
||||
switch (info.stage) {
|
||||
case StageHeader:
|
||||
readInitMessageHeader(nextConnSocket);
|
||||
break;
|
||||
case StageBody:
|
||||
readInitMessageBody(nextConnSocket);
|
||||
break;
|
||||
case StageConnected:
|
||||
slotDataAvailable(nextConnSocket, info.instanceId);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
};
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
void SingleCoreApplicationPrivate::readInitMessageHeader(QLocalSocket *sock) {
|
||||
|
||||
if (!connectionMap_.contains(sock)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (sock->bytesAvailable() < static_cast<qint64>(sizeof(quint64))) {
|
||||
return;
|
||||
}
|
||||
|
||||
QDataStream headerStream(sock);
|
||||
headerStream.setVersion(QDataStream::Qt_5_8);
|
||||
|
||||
// Read the header to know the message length
|
||||
quint64 msgLen = 0;
|
||||
headerStream >> msgLen;
|
||||
ConnectionInfo &info = connectionMap_[sock];
|
||||
info.stage = StageBody;
|
||||
info.msgLen = msgLen;
|
||||
|
||||
if (sock->bytesAvailable() >= static_cast<qint64>(msgLen)) {
|
||||
readInitMessageBody(sock);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
void SingleCoreApplicationPrivate::readInitMessageBody(QLocalSocket *sock) {
|
||||
|
||||
Q_Q(SingleCoreApplication);
|
||||
|
||||
if (!connectionMap_.contains(sock)) {
|
||||
return;
|
||||
}
|
||||
|
||||
ConnectionInfo &info = connectionMap_[sock];
|
||||
if (sock->bytesAvailable() < static_cast<qint64>(info.msgLen)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Read the message body
|
||||
QByteArray msgBytes = sock->read(info.msgLen);
|
||||
QDataStream readStream(msgBytes);
|
||||
readStream.setVersion(QDataStream::Qt_5_8);
|
||||
|
||||
// server name
|
||||
QByteArray latin1Name;
|
||||
readStream >> latin1Name;
|
||||
|
||||
// connection type
|
||||
ConnectionType connectionType = InvalidConnection;
|
||||
quint8 connTypeVal = InvalidConnection;
|
||||
readStream >> connTypeVal;
|
||||
connectionType = static_cast<ConnectionType>(connTypeVal);
|
||||
|
||||
// instance id
|
||||
quint32 instanceId = 0;
|
||||
readStream >> instanceId;
|
||||
|
||||
// checksum
|
||||
quint16 msgChecksum = 0;
|
||||
readStream >> msgChecksum;
|
||||
|
||||
#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
|
||||
const quint16 actualChecksum = qChecksum(QByteArray(msgBytes, static_cast<quint32>(msgBytes.length() - sizeof(quint16))));
|
||||
#else
|
||||
const quint16 actualChecksum = qChecksum(msgBytes.constData(), static_cast<quint32>(msgBytes.length() - sizeof(quint16)));
|
||||
#endif
|
||||
|
||||
bool isValid = readStream.status() == QDataStream::Ok && QLatin1String(latin1Name) == blockServerName_ && msgChecksum == actualChecksum;
|
||||
|
||||
if (!isValid) {
|
||||
sock->close();
|
||||
return;
|
||||
}
|
||||
|
||||
info.instanceId = instanceId;
|
||||
info.stage = StageConnected;
|
||||
|
||||
if (connectionType == NewInstance || (connectionType == SecondaryInstance && options_ & SingleCoreApplication::Mode::SecondaryNotification)) {
|
||||
Q_EMIT q->instanceStarted();
|
||||
}
|
||||
|
||||
if (sock->bytesAvailable() > 0) {
|
||||
slotDataAvailable(sock, instanceId);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
void SingleCoreApplicationPrivate::slotDataAvailable(QLocalSocket *dataSocket, const quint32 instanceId) {
|
||||
|
||||
Q_Q(SingleCoreApplication);
|
||||
Q_EMIT q->receivedMessage(instanceId, dataSocket->readAll());
|
||||
|
||||
}
|
||||
|
||||
void SingleCoreApplicationPrivate::slotClientConnectionClosed(QLocalSocket *closedSocket, const quint32 instanceId) {
|
||||
|
||||
if (closedSocket->bytesAvailable() > 0) {
|
||||
slotDataAvailable(closedSocket, instanceId);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
void SingleCoreApplicationPrivate::randomSleep() {
|
||||
|
||||
#if QT_VERSION >= QT_VERSION_CHECK(5, 10, 0)
|
||||
QThread::msleep(QRandomGenerator::global()->bounded(8U, 18U));
|
||||
#else
|
||||
qsrand(QDateTime::currentMSecsSinceEpoch() % std::numeric_limits<uint>::max());
|
||||
QThread::msleep(8 + static_cast<unsigned long>(static_cast<float>(qrand()) / RAND_MAX * 10));
|
||||
#endif
|
||||
|
||||
}
|
||||
111
3rdparty/singleapplication/singlecoreapplication_p.h
vendored
@@ -1,111 +0,0 @@
|
||||
// The MIT License (MIT)
|
||||
//
|
||||
// Copyright (c) Itay Grudev 2015 - 2020
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the "Software"), to deal
|
||||
// in the Software without restriction, including without limitation the rights
|
||||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
// copies of the Software, and to permit persons to whom the Software is
|
||||
// furnished to do so, subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in
|
||||
// all copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
// THE SOFTWARE.
|
||||
|
||||
//
|
||||
// W A R N I N G !!!
|
||||
// -----------------
|
||||
//
|
||||
// This is a modified version of SingleApplication,
|
||||
// The original version is at:
|
||||
//
|
||||
// https://github.com/itay-grudev/SingleApplication
|
||||
//
|
||||
//
|
||||
|
||||
#ifndef SINGLECOREAPPLICATION_P_H
|
||||
#define SINGLECOREAPPLICATION_P_H
|
||||
|
||||
#include <QtGlobal>
|
||||
#include <QObject>
|
||||
#include <QString>
|
||||
#include <QHash>
|
||||
|
||||
#include "singlecoreapplication.h"
|
||||
|
||||
class QLocalServer;
|
||||
class QLocalSocket;
|
||||
class QSharedMemory;
|
||||
|
||||
struct InstancesInfo {
|
||||
bool primary;
|
||||
quint32 secondary;
|
||||
qint64 primaryPid;
|
||||
char primaryUser[128];
|
||||
quint16 checksum;
|
||||
};
|
||||
|
||||
struct ConnectionInfo {
|
||||
explicit ConnectionInfo() : msgLen(0), instanceId(0), stage(0) {}
|
||||
qint64 msgLen;
|
||||
quint32 instanceId;
|
||||
quint8 stage;
|
||||
};
|
||||
|
||||
class SingleCoreApplicationPrivate : public QObject {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
enum ConnectionType : quint8 {
|
||||
InvalidConnection = 0,
|
||||
NewInstance = 1,
|
||||
SecondaryInstance = 2,
|
||||
Reconnect = 3
|
||||
};
|
||||
enum ConnectionStage : quint8 {
|
||||
StageHeader = 0,
|
||||
StageBody = 1,
|
||||
StageConnected = 2,
|
||||
};
|
||||
Q_DECLARE_PUBLIC(SingleCoreApplication)
|
||||
|
||||
explicit SingleCoreApplicationPrivate(SingleCoreApplication *ptr);
|
||||
~SingleCoreApplicationPrivate() override;
|
||||
|
||||
static QString getUsername();
|
||||
void genBlockServerName();
|
||||
void initializeMemoryBlock() const;
|
||||
void startPrimary();
|
||||
void startSecondary();
|
||||
bool connectToPrimary(const int timeout, const ConnectionType connectionType);
|
||||
quint16 blockChecksum() const;
|
||||
qint64 primaryPid() const;
|
||||
QString primaryUser() const;
|
||||
void readInitMessageHeader(QLocalSocket *socket);
|
||||
void readInitMessageBody(QLocalSocket *socket);
|
||||
static void randomSleep();
|
||||
|
||||
SingleCoreApplication *q_ptr;
|
||||
QSharedMemory *memory_;
|
||||
QLocalSocket *socket_;
|
||||
QLocalServer *server_;
|
||||
quint32 instanceNumber_;
|
||||
QString blockServerName_;
|
||||
SingleCoreApplication::Options options_;
|
||||
QHash<QLocalSocket*, ConnectionInfo> connectionMap_;
|
||||
|
||||
public slots:
|
||||
void slotConnectionEstablished();
|
||||
void slotDataAvailable(QLocalSocket*, const quint32);
|
||||
void slotClientConnectionClosed(QLocalSocket*, const quint32);
|
||||
};
|
||||
|
||||
#endif // SINGLECOREAPPLICATION_P_H
|
||||
126
CMakeLists.txt
@@ -1,7 +1,13 @@
|
||||
cmake_minimum_required(VERSION 3.7)
|
||||
|
||||
project(strawberry)
|
||||
|
||||
cmake_minimum_required(VERSION 3.0)
|
||||
cmake_policy(SET CMP0054 NEW)
|
||||
if(POLICY CMP0054)
|
||||
cmake_policy(SET CMP0054 NEW)
|
||||
endif()
|
||||
if(POLICY CMP0074)
|
||||
cmake_policy(SET CMP0074 NEW)
|
||||
endif()
|
||||
|
||||
include(CheckCXXCompilerFlag)
|
||||
include(CheckCXXSourceRuns)
|
||||
@@ -12,13 +18,13 @@ include(cmake/Summary.cmake)
|
||||
include(cmake/OptionalSource.cmake)
|
||||
include(cmake/ParseArguments.cmake)
|
||||
|
||||
if(${CMAKE_SYSTEM_NAME} STREQUAL "Linux")
|
||||
if(CMAKE_SYSTEM_NAME STREQUAL "Linux")
|
||||
set(LINUX ON)
|
||||
endif()
|
||||
if(${CMAKE_SYSTEM_NAME} STREQUAL "FreeBSD")
|
||||
if(CMAKE_SYSTEM_NAME STREQUAL "FreeBSD")
|
||||
set(FREEBSD ON)
|
||||
endif()
|
||||
if(${CMAKE_SYSTEM_NAME} STREQUAL "OpenBSD")
|
||||
if(CMAKE_SYSTEM_NAME STREQUAL "OpenBSD")
|
||||
set(OPENBSD ON)
|
||||
endif()
|
||||
|
||||
@@ -32,14 +38,22 @@ endif()
|
||||
|
||||
set(CMAKE_MODULE_PATH ${CMAKE_SOURCE_DIR}/cmake)
|
||||
|
||||
if(MSVC)
|
||||
set(CMAKE_C_STANDARD 99)
|
||||
else()
|
||||
set(CMAKE_C_STANDARD 11)
|
||||
endif()
|
||||
|
||||
set(CMAKE_C_STANDARD_REQUIRED ON)
|
||||
|
||||
set(CMAKE_CXX_STANDARD 17)
|
||||
set(CMAKE_CXX_STANDARD_REQUIRED ON)
|
||||
|
||||
if(MSVC)
|
||||
list(APPEND COMPILE_OPTIONS /std:c++17 /MP)
|
||||
list(APPEND COMPILE_OPTIONS /MP)
|
||||
else()
|
||||
list(APPEND COMPILE_OPTIONS
|
||||
$<$<COMPILE_LANGUAGE:C>:-std=c99>
|
||||
$<$<COMPILE_LANGUAGE:C>:-std=c11>
|
||||
$<$<COMPILE_LANGUAGE:CXX>:-std=c++17>
|
||||
-Wall
|
||||
-Wextra
|
||||
@@ -58,26 +72,24 @@ else()
|
||||
-Wformat=2
|
||||
-Wdisabled-optimization
|
||||
$<$<COMPILE_LANGUAGE:CXX>:-Woverloaded-virtual>
|
||||
$<$<COMPILE_LANGUAGE:CXX>:-Wno-old-style-cast>
|
||||
$<$<COMPILE_LANGUAGE:CXX>:-fpermissive>
|
||||
$<$<COMPILE_LANGUAGE:CXX>:-Wold-style-cast>
|
||||
)
|
||||
endif()
|
||||
|
||||
option(BUILD_WERROR "Build with -Werror" OFF)
|
||||
if(BUILD_WERROR)
|
||||
list(APPEND COMPILE_OPTIONS -Werror)
|
||||
option(BUILD_WERROR "Build with -Werror" OFF)
|
||||
if(BUILD_WERROR)
|
||||
list(APPEND COMPILE_OPTIONS -Werror)
|
||||
endif()
|
||||
endif()
|
||||
|
||||
add_compile_options(${COMPILE_OPTIONS})
|
||||
|
||||
if(${CMAKE_BUILD_TYPE} MATCHES "Release")
|
||||
if(CMAKE_BUILD_TYPE MATCHES "Release")
|
||||
add_definitions(-DNDEBUG)
|
||||
add_definitions(-DQT_NO_DEBUG_OUTPUT)
|
||||
endif()
|
||||
|
||||
if(APPLE)
|
||||
set(CMAKE_BUILD_WITH_INSTALL_RPATH TRUE)
|
||||
set(CMAKE_INSTALL_RPATH "@loader_path/../Frameworks")
|
||||
option(USE_RPATH "Use RPATH" APPLE)
|
||||
if(USE_RPATH)
|
||||
set(CMAKE_INSTALL_RPATH_USE_LINK_PATH TRUE)
|
||||
endif()
|
||||
|
||||
find_program(CCACHE_EXECUTABLE NAMES ccache)
|
||||
@@ -87,6 +99,8 @@ if(CCACHE_EXECUTABLE)
|
||||
SET_PROPERTY(GLOBAL PROPERTY RULE_LAUNCH_LINK ${CCACHE_EXECUTABLE})
|
||||
endif()
|
||||
|
||||
option(USE_ICU "Use ICU" ON)
|
||||
|
||||
find_package(PkgConfig REQUIRED)
|
||||
find_package(Boost REQUIRED)
|
||||
find_package(Threads)
|
||||
@@ -94,7 +108,14 @@ find_package(Backtrace)
|
||||
if(Backtrace_FOUND)
|
||||
set(HAVE_BACKTRACE ON)
|
||||
endif()
|
||||
find_package(Iconv)
|
||||
if(USE_ICU)
|
||||
find_package(ICU COMPONENTS uc i18n REQUIRED)
|
||||
if(ICU_FOUND)
|
||||
set(HAVE_ICU ON)
|
||||
endif()
|
||||
else()
|
||||
find_package(Iconv)
|
||||
endif()
|
||||
find_package(GnuTLS REQUIRED)
|
||||
find_package(Protobuf REQUIRED)
|
||||
if(NOT Protobuf_PROTOC_EXECUTABLE)
|
||||
@@ -117,6 +138,9 @@ endif()
|
||||
pkg_check_modules(GLIB REQUIRED glib-2.0)
|
||||
pkg_check_modules(GOBJECT REQUIRED gobject-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(GSTREAMER gstreamer-1.0)
|
||||
pkg_check_modules(GSTREAMER_BASE gstreamer-base-1.0)
|
||||
@@ -155,7 +179,7 @@ if(DBUS_FOUND AND NOT WIN32)
|
||||
list(APPEND QT_COMPONENTS DBus)
|
||||
endif()
|
||||
set(QT_OPTIONAL_COMPONENTS Test)
|
||||
set(QT_MIN_VERSION 5.8)
|
||||
set(QT_MIN_VERSION 5.9)
|
||||
|
||||
if(BUILD_WITH_QT6 OR QT_VERSION_MAJOR EQUAL 6)
|
||||
set(QT_VERSION_MAJOR 6 CACHE STRING "" FORCE)
|
||||
@@ -287,12 +311,14 @@ endif()
|
||||
|
||||
# SingleApplication
|
||||
add_subdirectory(3rdparty/singleapplication)
|
||||
set(SINGLEAPPLICATION_INCLUDE_DIRS ${CMAKE_CURRENT_SOURCE_DIR}/3rdparty/singleapplication)
|
||||
set(SINGLEAPPLICATION_INCLUDE_DIRS
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/3rdparty/singleapplication/singleapplication
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/3rdparty/singleapplication/singlecoreapplication
|
||||
)
|
||||
set(SINGLEAPPLICATION_LIBRARIES singleapplication)
|
||||
set(SINGLECOREAPPLICATION_LIBRARIES singlecoreapplication)
|
||||
|
||||
if(APPLE)
|
||||
find_library(SPARKLE Sparkle PATHS "/usr/local/opt/sparkle")
|
||||
add_subdirectory(3rdparty/SPMediaKeyTap)
|
||||
set(SPMEDIAKEYTAP_INCLUDE_DIRS ${CMAKE_CURRENT_SOURCE_DIR}/3rdparty/SPMediaKeyTap)
|
||||
set(SPMEDIAKEYTAP_LIBRARIES SPMediaKeyTap)
|
||||
@@ -300,7 +326,7 @@ if(APPLE)
|
||||
add_subdirectory(ext/macdeploycheck)
|
||||
endif()
|
||||
|
||||
if(NOT SPARKLE AND (APPLE OR WIN32))
|
||||
if(WIN32)
|
||||
if(BUILD_WITH_QT6)
|
||||
pkg_check_modules(QTSPARKLE qtsparkle-qt6)
|
||||
else()
|
||||
@@ -311,6 +337,12 @@ if(NOT SPARKLE AND (APPLE OR WIN32))
|
||||
endif()
|
||||
endif()
|
||||
|
||||
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
|
||||
string(REPLACE "gcc" "windres" CMAKE_RC_COMPILER_INIT ${CMAKE_C_COMPILER})
|
||||
@@ -349,12 +381,12 @@ optional_component(VLC ON "Engine: VLC backend"
|
||||
|
||||
optional_component(SONGFINGERPRINTING ON "Song fingerprinting and tracking"
|
||||
DEPENDS "chromaprint" CHROMAPRINT_FOUND
|
||||
DEPENDS "gstreamer" GSTREAMER_FOUND
|
||||
DEPENDS "gstreamer" HAVE_GSTREAMER
|
||||
)
|
||||
|
||||
optional_component(MUSICBRAINZ ON "MusicBrainz integration"
|
||||
DEPENDS "chromaprint" CHROMAPRINT_FOUND
|
||||
DEPENDS "gstreamer" GSTREAMER_FOUND
|
||||
DEPENDS "gstreamer" HAVE_GSTREAMER
|
||||
)
|
||||
|
||||
if(X11_FOUND OR HAVE_DBUS OR APPLE OR WIN32)
|
||||
@@ -379,7 +411,7 @@ endif()
|
||||
|
||||
optional_component(AUDIOCD ON "Devices: Audio CD support"
|
||||
DEPENDS "libcdio" LIBCDIO_FOUND
|
||||
DEPENDS "gstreamer" GSTREAMER_FOUND
|
||||
DEPENDS "gstreamer" HAVE_GSTREAMER
|
||||
)
|
||||
|
||||
optional_component(UDISKS2 ON "Devices: UDisks2 backend"
|
||||
@@ -391,6 +423,11 @@ optional_component(GIO ON "Devices: GIO device backend"
|
||||
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"
|
||||
DEPENDS "libgpod" LIBGPOD_FOUND
|
||||
DEPENDS "gdk-pixbuf" GDK_PIXBUF_FOUND
|
||||
@@ -400,11 +437,6 @@ optional_component(LIBMTP ON "Devices: MTP support"
|
||||
DEPENDS "libmtp" LIBMTP_FOUND
|
||||
)
|
||||
|
||||
optional_component(SPARKLE ON "Sparkle integration"
|
||||
DEPENDS "macOS" APPLE
|
||||
DEPENDS "Sparkle" SPARKLE
|
||||
)
|
||||
|
||||
if(BUILD_WITH_QT6)
|
||||
optional_component(TRANSLATIONS ON "Translations"
|
||||
DEPENDS "gettext" GETTEXT_FOUND
|
||||
@@ -419,9 +451,9 @@ endif()
|
||||
|
||||
option(INSTALL_TRANSLATIONS "Install translations" OFF)
|
||||
|
||||
optional_component(SUBSONIC ON "Subsonic support")
|
||||
optional_component(TIDAL ON "Tidal support")
|
||||
optional_component(QOBUZ ON "Qobuz support")
|
||||
optional_component(SUBSONIC ON "Streaming: Subsonic")
|
||||
optional_component(TIDAL ON "Streaming: Tidal")
|
||||
optional_component(QOBUZ ON "Streaming: Qobuz")
|
||||
|
||||
optional_component(MOODBAR ON "Moodbar"
|
||||
DEPENDS "fftw3" FFTW3_FOUND
|
||||
@@ -482,12 +514,16 @@ endif()
|
||||
|
||||
# Set up definitions
|
||||
|
||||
add_definitions(-DBOOST_BIND_NO_PLACEHOLDERS)
|
||||
add_definitions(${QT_DEFINITIONS})
|
||||
add_definitions(-DQT_STRICT_ITERATORS)
|
||||
add_definitions(-DQT_USE_QSTRINGBUILDER)
|
||||
add_definitions(-DQT_NO_URL_CAST_FROM_STRING)
|
||||
add_definitions(-DQT_NO_CAST_TO_ASCII)
|
||||
add_definitions(
|
||||
-DBOOST_BIND_NO_PLACEHOLDERS
|
||||
-DQT_STRICT_ITERATORS
|
||||
-DQT_NO_CAST_FROM_BYTEARRAY
|
||||
-DQT_USE_QSTRINGBUILDER
|
||||
-DQT_NO_URL_CAST_FROM_STRING
|
||||
-DQT_NO_CAST_TO_ASCII
|
||||
-DQT_NO_NARROWING_CONVERSIONS_IN_CONNECT
|
||||
-DQT_NO_FOREACH
|
||||
)
|
||||
|
||||
if(WIN32)
|
||||
add_definitions(-DUNICODE)
|
||||
@@ -510,6 +546,10 @@ if(GTest_FOUND AND GMOCK_LIBRARY AND QtTest_LIBRARIES)
|
||||
add_subdirectory(tests)
|
||||
endif()
|
||||
|
||||
if(LINUX AND LSB_RELEASE_EXEC AND DPKG_BUILDPACKAGE)
|
||||
add_subdirectory(debian)
|
||||
endif()
|
||||
|
||||
# Uninstall support
|
||||
configure_file("${CMAKE_CURRENT_SOURCE_DIR}/cmake_uninstall.cmake.in" "${CMAKE_CURRENT_BINARY_DIR}/cmake_uninstall.cmake" IMMEDIATE @ONLY)
|
||||
|
||||
@@ -518,11 +558,15 @@ add_custom_target(uninstall "${CMAKE_COMMAND}" -P "${CMAKE_CURRENT_BINARY_DIR}/c
|
||||
# Show a summary of what we have enabled
|
||||
summary_show()
|
||||
if(NOT HAVE_GSTREAMER AND NOT HAVE_VLC)
|
||||
message(FATAL_ERROR "You need to have either GStreamer or VLC to compile!")
|
||||
message(FATAL_ERROR "You need to have either GStreamer or libvlc to compile!")
|
||||
elseif(NOT HAVE_GSTREAMER)
|
||||
message(WARNING "GStreamer is the only engine that is fully implemented. Using other engines is possible but not recommended.")
|
||||
endif()
|
||||
|
||||
if(QT_VERSION_MAJOR EQUAL 5)
|
||||
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()
|
||||
|
||||
if(NOT CMAKE_CROSSCOMPILING)
|
||||
if(QT_SQLITE_TEST)
|
||||
if(NOT SQLITE_FTS5_TEST)
|
||||
|
||||
112
CONTRIBUTING.md
Normal file
@@ -0,0 +1,112 @@
|
||||
# Contribution guidelines
|
||||
|
||||
Strawberry is an free and open-source project, it is possible and encouraged to participate in the development.
|
||||
You can also participate by answering questions, reporting bugs or helping with documentation.
|
||||
|
||||
|
||||
## Submitting a pull request
|
||||
|
||||
You should start by creating a fork of the Strawberry repository using the GitHub
|
||||
fork button, after that you can clone the repository from your fork.
|
||||
Replace "username" with your own.
|
||||
|
||||
|
||||
### Clone the repository
|
||||
|
||||
git clone git@github.com:username/strawberry.git
|
||||
cd strawberry
|
||||
|
||||
|
||||
### Setup the remote
|
||||
|
||||
git remote add upstream git@github.com:strawberrymusicplayer/strawberry.git
|
||||
|
||||
|
||||
### Create a new branch
|
||||
|
||||
This creates a new branch from the master branch that you use for specific
|
||||
changes.
|
||||
|
||||
git checkout -b your-branch
|
||||
|
||||
|
||||
### Stage changes
|
||||
|
||||
Once you've finished working on a specific change, stage the changes for
|
||||
a specific commit.
|
||||
|
||||
Always keep your commits relevant to the pull request, and each commit as
|
||||
small as possible.
|
||||
|
||||
git add -p
|
||||
|
||||
|
||||
### Commit changes
|
||||
|
||||
git commit
|
||||
|
||||
|
||||
### Commit messages
|
||||
|
||||
The first line should start with "Class:", which referer to the class
|
||||
that is changed. Don't use a trailing period after the first line.
|
||||
If this change affects more than one class, omit the class and write a
|
||||
more general message.
|
||||
You only need to include a main description (body) for larger changes
|
||||
where the one line is not enough to describe everything.
|
||||
The main description starts after two newlines, it is normal prose and
|
||||
should use normal punctuation and capital letters where appropriate.
|
||||
|
||||
An example of the expected format for git commit messages is as follows:
|
||||
|
||||
```
|
||||
class: Short explanation of the commit
|
||||
|
||||
Longer explanation explaining exactly what's changed, why it's changed,
|
||||
and what bugs were fixed.
|
||||
|
||||
Fixes #1234
|
||||
```
|
||||
|
||||
|
||||
### Push the changes to GitHub
|
||||
|
||||
Once you've finished working on the changes, push the branch
|
||||
to the Git repository and open a new pull request.
|
||||
|
||||
|
||||
git push origin your-branch
|
||||
|
||||
|
||||
### Update your fork's master branch
|
||||
|
||||
git checkout master
|
||||
git pull --rebase origin master
|
||||
git fetch upstream
|
||||
git merge upstream/master
|
||||
git push origin master
|
||||
|
||||
|
||||
### Update your branch
|
||||
|
||||
git checkout your-branch
|
||||
git fetch upstream
|
||||
git rebase upstream/master
|
||||
git push origin your-branch --force-with-lease
|
||||
|
||||
|
||||
### Rebase your branch
|
||||
|
||||
If you need fix any issues with your commits, you need to rebase your
|
||||
branch to squash any commits, or to change the commit message.
|
||||
|
||||
git checkout your-branch
|
||||
git log
|
||||
git rebase -i commit_sha~
|
||||
git push origin your-branch --force-with-lease
|
||||
|
||||
|
||||
### Delete your fork
|
||||
|
||||
If you do not plan to work more on Strawberry, please delete your fork from GitHub
|
||||
once the pull requests are merged.
|
||||
414
Changelog
@@ -2,7 +2,292 @@ Strawberry Music Player
|
||||
=======================
|
||||
ChangeLog
|
||||
|
||||
1.0.0:
|
||||
Version 1.0.17 (2023.03.29):
|
||||
|
||||
Bugfixes:
|
||||
* Fixed over-sized context album cover with device pixel ratio higher than 1.0 (#1166).
|
||||
* Fixed playing widget fading from a blurry previous cover with device pixel ratio higher than 1.0.
|
||||
* Made playlist source icon, album cover manager and OSD pretty cover respect device pixel ratio.
|
||||
|
||||
Version 1.0.16 (2023.03.27):
|
||||
|
||||
Bugfixes:
|
||||
* Fixed lyrics from Musixmatch.
|
||||
* Fixed possible file corruption when saving both tags and embedded cover using the tag editor (#1158).
|
||||
* Fixed compile without GStreamer.
|
||||
* Fixed context and playing now album art rendering on High DPI displays (#1161).
|
||||
* Fixed setting source properties (device, user-agent, ssl-strict) with GStreamer 1.22 (playbin3) and higher (#1148).
|
||||
* Fixed rescan songs feature not ignoring mtime.
|
||||
* Search lyrics by artist instead of album artist by default.
|
||||
|
||||
Code improvements:
|
||||
* Replace use of deprecated QSqlDatabase::exec().
|
||||
|
||||
Added features:
|
||||
* Added backend setting for strict SSL mode.
|
||||
* Read AcoustID and MusicBrainz tags.
|
||||
* Submit MusicBrainz tags with ListenBrainz.
|
||||
|
||||
Version 1.0.15 (2023.03.04):
|
||||
|
||||
Bugfixes:
|
||||
* Fixed playlist column showing invalid last played date for streams.
|
||||
* Fixed crash when the audio bin failed to initialize (#1123, #1133).
|
||||
* Fixed duplicated filename when organizing files using dot in the filename (#1136).
|
||||
* Fixed tag inline editing for streams (#1130).
|
||||
* Fixed resetting play statistics using tag edit dialog (#1124).
|
||||
* Fixed compilation songs not showing if group by was set to other than (Album) Artist / Album (#1140).
|
||||
|
||||
Enhancements:
|
||||
* Added lyrics from stands4 (lyrics.com).
|
||||
* Added Sonogram analyzer.
|
||||
* Use GStreamer playbin3 with GStreamer 1.22.0 and higher.
|
||||
|
||||
Code improvements:
|
||||
* Made use of C++11 enum class where possible.
|
||||
* Use new QNativeIpcKey based QSharedMemory constructor with Qt 6.6 and higher.
|
||||
|
||||
Version 1.0.14 (2023.01.13):
|
||||
|
||||
Bugfixes:
|
||||
* Fix initial volume not set when using Auto as output (#1104).
|
||||
* Fix saving moodbar if the URL contains host, ie.: UNC paths for SMB (#1101).
|
||||
* Fix CollectionBackendTest compile error (#1100).
|
||||
* Remove explicitly enabling debug messages (#1106).
|
||||
|
||||
Version 1.0.13 (2023.01.09):
|
||||
|
||||
Bugfixes:
|
||||
* Fixed volume synchronization leading to infinite loop resulting in crash when adjusting volume while playing (#1089).
|
||||
* Fixed incorrect volume.
|
||||
* Fixed collection organizing incorrectly handling slashes inside {} brackets for variables (#1091).
|
||||
* Fixed saving relative playlists to non-existing playlist files (#1092).
|
||||
* Fixed intermittent crash on collection model query (#1095).
|
||||
* Require system icons for fancy tabbar and settings sidebar to be larger than 22x22 (#1084).
|
||||
|
||||
Version 1.0.12 (2023.01.02):
|
||||
|
||||
Bugfixes:
|
||||
* Fixed crash when adjusting volume with mouse wheel (#1089).
|
||||
* Fixed playback stopping in certain cases where the next track was unavailable (#958).
|
||||
* (Windows) Apply patch for fonts too large on High DPI screen (QTBUG-108593).
|
||||
|
||||
Removed features:
|
||||
* Removed appearance settings for changing palette colors, it was never properly implemented.
|
||||
|
||||
Version 1.0.11 (2022.12.30):
|
||||
|
||||
Bugfixes:
|
||||
* Capitalize GLib application name so it appears nicely in GNOME and PulseAudio Volume Control (#1066).
|
||||
* Fixed missing application icon for PulseAudio Volume Control (#1066).
|
||||
* Ignore errors for missing albums when updating Tidal collection if there are results (#1061).
|
||||
* Only run periodic collection scan when moitoring collection setting is on.
|
||||
* Fixed an edge case where the context headline text was being cut short (#1067).
|
||||
* Made "Show in file browser" support SpaceFM filemanager (#1073).
|
||||
* Fixed incorrect tab order in edit tag dialog (#1075).
|
||||
* Changed "FMPS_PlayCount" to "FMPS_Playcount" when saving tag (#1074).
|
||||
* Fixed compilation tag read and write for MP4 (#1076).
|
||||
* Removed incorrect use of "TPE1" for performer when reading ID3 tags (#1076).
|
||||
* Disable tag fields for unsupported tags in tag editor.
|
||||
* Don't allow organizing files without unique tags (track or title) for filename (#1077).
|
||||
* Don't remove disc from album title when creating cover hash to allow different covers for each disc on an album (#1069).
|
||||
* Fixed incorrect relative paths for song filenames when saving playlists if the saved playlist location is a symablic link to the song filename (#1071).
|
||||
* Scrobble "Various Artists" as album artist (#1082).
|
||||
|
||||
Enhancements:
|
||||
* Use system volume instead of own software volume when available (#1037).
|
||||
* Improved Tidal and Qobuz support with timed requests.
|
||||
* Support MPRIS2 xesam:userRating.
|
||||
|
||||
Version 1.0.10 (2022.10.21):
|
||||
|
||||
Bugfixes:
|
||||
* Fixed "Could not open settings file for writing: No such file or directory" error before settings file is created.
|
||||
* Fixed visual glitch on currently playing track (#1051).
|
||||
* Fixed "Unknown error" on Tidal search (#1047).
|
||||
* Fixed incomplete lyrics from Genius.
|
||||
* Fixed icons not showing in the file view on some systems (#1024).
|
||||
* Fixed issues with context and playing widget stopping when using VLC (#1054).
|
||||
* (macOS) Fixed search field related crash when playlist toolbar is turned off.
|
||||
|
||||
Enhancements:
|
||||
* Fixed narrowing conversions in connects.
|
||||
* Fixed casts from QByteArray.
|
||||
* Removed subdir for generated dbus files
|
||||
* Removed use of fixed font in context (#1040).
|
||||
* Improve Musixmatch lyrics search.
|
||||
|
||||
Version 1.0.9 (2022.09.03):
|
||||
|
||||
Bugfixes:
|
||||
* Fixed parsing album title from radio stream metadata (#1023).
|
||||
* (macOS) Fixed Strawberry not starting, incorrect rpath for libgcc_s.1.1.dylib (#1025).
|
||||
* (macOS) Fixed HTTP streaming.
|
||||
|
||||
Version 1.0.8 (2022.08.29):
|
||||
|
||||
Bugfixes:
|
||||
* Fixed backslash being appended to filter text when switching playlist (#1005).
|
||||
* Fixed OSD notifications service registering taking too long to timeout when not available.
|
||||
* Fixed radio stream added twice when double-clicked (#1015).
|
||||
* Fixed translating undo and redo buttons (#1017).
|
||||
|
||||
Enhancements:
|
||||
* Use ICU instead of iconv to transliterate characters for filenames.
|
||||
* Make separating albums by grouping tag optional in collection group by album.
|
||||
* Added support for video game music formats VGM and SPC.
|
||||
* Added setting for explicitly turning on HTTP/2 for streaming. Strawberry will set the
|
||||
libsoup SOUP_FORCE_HTTP1 environment variable when the HTTP/2 is not checked (#1016).
|
||||
* (Windows|MSVC) Install Visual C++ runtime redistributable automatically in installer.
|
||||
|
||||
Version 1.0.7 (2022.07.25)
|
||||
|
||||
Bugfixes:
|
||||
* Fixed checking file extension case-insensitive when loading and saving playlists.
|
||||
* Fixed reading and saving rating with TagParser.
|
||||
* (macOS/Windows) Fixed playlist column alignment. Applied patch for Qt bug QTBUG-103576 (#999).
|
||||
* (Windows|MinGW) Fixed HLS streaming.
|
||||
* (Windows|MSVC) Fixed MP3 encoding.
|
||||
|
||||
Enhancements
|
||||
* Added option for selecting file extension when saving all playlists.
|
||||
|
||||
Version 1.0.6 (2022.07.17)
|
||||
|
||||
Bugfixes:
|
||||
* Fixed certain albums not added to playlist in correct track order from search for Tidal and QObuz.
|
||||
* Fixed songs not added to playlist in numeric order when added from file view with right click (#977).
|
||||
* Fixed "Stop after this track" graying out next track in dynamic mode (#912).
|
||||
* Fixed a gstreamer caps leak when transcoding songs.
|
||||
* Fixed errors in translation files (#994).
|
||||
|
||||
Enhancements
|
||||
* Add songs to the collection even when they have invalid ctime or mtime.
|
||||
* Made ListenBrainz scrobbler respect "Prefer album artist" option (#989).
|
||||
* Send track duration, number, player name and version when scrobbling to ListenBrainz (#995).
|
||||
* (macOS) Added missing HLS streaming plugin.
|
||||
|
||||
Version 1.0.5 (2022.06.10)
|
||||
|
||||
Bugfixes:
|
||||
* Fixed smart playlist filetype search.
|
||||
* Fixed Radio Paradise URLs to use HTTPS instead of HTTP.
|
||||
* Fixed horizontal scrolling not affecting currently playing track (#952).
|
||||
* Fixed keep running in the background when window is closed with Wayland (#964).
|
||||
* Fixed percent-encoding of URLs when loading and saving XSPF playlists (#821).
|
||||
* Fixed fancy tabbar context menu showing on right clicks outside of tabbar when a song is playing.
|
||||
* Fixed possible duplicating songs in the database when moving songs to the collection using the organize feature.
|
||||
* (Windows|MSVC) Fixed moodbar fftw3 crash with (older) CPU's that does not support AVX2 (#944).
|
||||
* (Windows|MSVC) Fixed using libiconv for converting characters when organizing files like with MinGW.
|
||||
|
||||
Enhancements
|
||||
* Show more details in error dialog on GStreamer errors (#958).
|
||||
* Allow setting blur amount of playlist background image up to 100px (#939).
|
||||
* Include 128x128 icon sizes (#954).
|
||||
* Show right click copy context menu in context view on top text and lyrics (#965).
|
||||
* Improve fading between album covers in context view.
|
||||
* Added option for overwriting database playcounts in collection settings (#962).
|
||||
* Added option for disabling bar on currently playing track (#972).
|
||||
* (Debian) Added Qt 6 support to debian files and build with Qt 6 for Debian Bookworm, Ubuntu Jammy and newer.
|
||||
* (Windows|MSVC) Added libav/ffmpeg plugin.
|
||||
|
||||
Version 1.0.4 (2022.04.10)
|
||||
|
||||
Bugfixes:
|
||||
* Fixed use-after-free memory in ALSA PCM device finder.
|
||||
* Translate global shortcuts.
|
||||
* (Windows) Fixed registering 0-9 numpad keys in global shortcuts.
|
||||
|
||||
Enhancements
|
||||
* Added save all playlists action.
|
||||
* (Windows) Made updater support both MSVC and MinGW.
|
||||
* (Windows) Added HLS support.
|
||||
|
||||
Other:
|
||||
* Removed use of custom font in context.
|
||||
|
||||
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.
|
||||
@@ -56,12 +341,13 @@ ChangeLog
|
||||
* Add support for native global shortcuts on MATE.
|
||||
* Add radios view with channels from Radio Paradise and SomaFM.
|
||||
|
||||
0.9.3:
|
||||
|
||||
Version 0.9.3 (2021.04.18)
|
||||
|
||||
Bugfixes:
|
||||
* Fix "Show in file browser" to work with thunar.
|
||||
* Check that the clicked rating position is to the right or left of the rectangle.
|
||||
* Fix rescan when collection directory is removed and readded.
|
||||
* Fix rescan when collection directory is removed and re-added.
|
||||
* Create GLib main event loop on non-glib systems to fix stream discoverer.
|
||||
* (macOS) Fix intermittent abort on startup.
|
||||
* (macOS) Fix Tidal and Qobuz search field not showing.
|
||||
@@ -79,7 +365,8 @@ ChangeLog
|
||||
* (macOS) Make macdeployqt work with Qt 5 too.
|
||||
* (macOS) Show keep running option in behaviour settings.
|
||||
|
||||
0.9.2:
|
||||
|
||||
Version 0.9.2 (2021.03.25)
|
||||
|
||||
Bugfixes:
|
||||
* Fix marking songs available.
|
||||
@@ -90,7 +377,8 @@ ChangeLog
|
||||
* (macOS) Fix crash when opening cover manager.
|
||||
* (macOS) Fix broken Qt plugins resulting in album covers not showing.
|
||||
|
||||
0.9.1:
|
||||
|
||||
Version 0.9.1 (2021.03.13)
|
||||
|
||||
Bugfixes:
|
||||
* Fix duplicating songs in the DB when organizing songs between 2 different collection directories.
|
||||
@@ -125,7 +413,8 @@ ChangeLog
|
||||
New features:
|
||||
* 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:
|
||||
* Fix return type of SmartPlaylistQueryWizardPlugin::type().
|
||||
@@ -144,7 +433,8 @@ ChangeLog
|
||||
* Add command line option to play a playlist based on name.
|
||||
* Change double-click behaviour in cover manager to open fullsize cover.
|
||||
|
||||
0.8.4:
|
||||
|
||||
Version 0.8.4 (2020.11.15)
|
||||
|
||||
Bugfixes:
|
||||
* Fix preventing session logout when window is maxmimized.
|
||||
@@ -170,7 +460,8 @@ ChangeLog
|
||||
* Remove remaining uses of QTextCodec.
|
||||
* Remove Core5Compat dependency.
|
||||
|
||||
0.8.3:
|
||||
|
||||
Version 0.8.3 (2020.10.24)
|
||||
|
||||
Bugfixes:
|
||||
* Fixed updating playing widget song details in small cover mode.
|
||||
@@ -185,7 +476,8 @@ ChangeLog
|
||||
Enhancements:
|
||||
* (Windows) Added WASAPI plugin.
|
||||
|
||||
0.8.2:
|
||||
|
||||
Version 0.8.2 (2020.10.13)
|
||||
|
||||
Bugfixes:
|
||||
* Fixed broken transition to next song for CUE files with certain audio formats (regression since version 0.6.13).
|
||||
@@ -197,7 +489,8 @@ ChangeLog
|
||||
* 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.
|
||||
|
||||
0.8.1:
|
||||
|
||||
Version 0.8.1 (2020.10.09)
|
||||
|
||||
Bugfixes:
|
||||
* Fixed engine selection in backend settings with Qt 6.
|
||||
@@ -244,14 +537,15 @@ ChangeLog
|
||||
* Added Subsonic server side scrobbling support.
|
||||
* Load thumbnails from iPods to show under device collection.
|
||||
|
||||
0.7.2:
|
||||
Version 0.7.2 (2020.08.15)
|
||||
|
||||
Bugfixes:
|
||||
* Fixed installation directory for translations.
|
||||
* Fixed collection sorting for non-ASCII characters.
|
||||
* Fixed closing connected devices on exit.
|
||||
|
||||
0.7.1:
|
||||
|
||||
Version 0.7.1 (2020.08.15)
|
||||
|
||||
Bugfixes:
|
||||
* Fixed incorrectly mapped global shortcuts keys "2" and "3".
|
||||
@@ -290,7 +584,8 @@ ChangeLog
|
||||
* Removed Xine engine support.
|
||||
* Removed broken imobiledevice (iPhone) support.
|
||||
|
||||
0.6.13:
|
||||
|
||||
Version 0.6.13 (2020.07.13)
|
||||
|
||||
Bugfixes:
|
||||
* Fixed cut-off text in about dialog.
|
||||
@@ -316,7 +611,8 @@ ChangeLog
|
||||
* Fixed unit test for testing playlist model.
|
||||
* Added new unit tests for tagreader.
|
||||
|
||||
0.6.12:
|
||||
|
||||
Version 0.6.12 (2020.06.07)
|
||||
|
||||
Bugfixes:
|
||||
* Fixed height of about dialog.
|
||||
@@ -329,7 +625,8 @@ ChangeLog
|
||||
* Sort folders added from file view.
|
||||
* Changed default collection grouping to album - disc.
|
||||
|
||||
0.6.11:
|
||||
|
||||
Version 0.6.11 (2020.05.16)
|
||||
|
||||
Bugfixes:
|
||||
* Fixed MPRIS missing art url when playing albums with embedded cover.
|
||||
@@ -355,7 +652,8 @@ ChangeLog
|
||||
* Added album covers from Musixmatch and Spotify.
|
||||
* Added lyrics from Genius, Musixmatch and ChartLyrics.
|
||||
|
||||
0.6.10:
|
||||
|
||||
Version 0.6.10 (2020.05.01)
|
||||
|
||||
Bugfixes:
|
||||
* Fixed Subsonic album covers not working for albums with non ASCII characters.
|
||||
@@ -386,7 +684,7 @@ ChangeLog
|
||||
* 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 separated by a dash in streams.
|
||||
* Added label to show collection pixmap disk cache used in settings.
|
||||
* Icreased default collection pixmap disk cache to 360.
|
||||
* Increased default collection pixmap disk cache to 360.
|
||||
|
||||
New features:
|
||||
* Added back Tidal streaming support.
|
||||
@@ -395,7 +693,8 @@ ChangeLog
|
||||
Removed features:
|
||||
* Removed Phonon engine support.
|
||||
|
||||
Version 0.6.9:
|
||||
|
||||
Version 0.6.9 (2020.03.09)
|
||||
|
||||
BugFixes:
|
||||
* Fixed playlist metadata updating interfering with manual tag editing.
|
||||
@@ -427,7 +726,8 @@ Version 0.6.9:
|
||||
* Tidal 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 possible crashes related to QProxyStyle.
|
||||
@@ -444,14 +744,15 @@ Version 0.6.8:
|
||||
* (macOS) Fixed filesystem watcher to correctly pick up changed collection directories.
|
||||
* (Windows) Fixed translations not being included.
|
||||
|
||||
Version 0.6.7:
|
||||
|
||||
Version 0.6.7 (2019.11.27)
|
||||
|
||||
* Fixed crash when cancelling scrobbler authentication
|
||||
* Fixed "Double clicking a song in the playlist" behaviour setting
|
||||
* Fixed "Pressing Previous in player" behaviour setting
|
||||
* Fixed updating compilations where there are spaces or special characters in filenames
|
||||
* Fixed cases where songs were stuck in "Various Artists" because not all songs in
|
||||
the same compilation was removed from the model before readded with actual artist.
|
||||
the same compilation was removed from the model before re-added with actual artist.
|
||||
* Fixed a bug when importing playlists where metadata was reset
|
||||
* Fixed scrobbler to also scrobble songs without album title
|
||||
* Fixed text for replay gain setting not loading in backend setting
|
||||
@@ -461,7 +762,8 @@ Version 0.6.7:
|
||||
* Removed left click on analyzer to popup menu
|
||||
* (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 compiling with different optional features turned off
|
||||
@@ -479,7 +781,8 @@ Version 0.6.6:
|
||||
* Added option to automatically select current playing track
|
||||
* (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 Listenbrainz scrobbler not don't send "various artists" as album artist
|
||||
@@ -488,7 +791,8 @@ Version 0.6.5:
|
||||
* Fixed OSD pretty positioning on Windows on screens with negative geometry
|
||||
* 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 to make marking songs unavailable optional
|
||||
@@ -515,18 +819,21 @@ Version 0.6.4:
|
||||
* Fixed restoring to original window size when restoring from system tray
|
||||
* Updated 3rdparty taglib
|
||||
|
||||
Version 0.6.3:
|
||||
|
||||
Version 0.6.3 (2019.08.05)
|
||||
|
||||
* Fixed crash when using internet services.
|
||||
* Fixed musicbrainz tagfetcher only showing 1 result per song.
|
||||
* Fixed collection watcher to unwatch deleted directories.
|
||||
* Added "album - disc" grouping.
|
||||
|
||||
Version 0.6.2:
|
||||
|
||||
Version 0.6.2 (2019.08.03)
|
||||
|
||||
* 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.
|
||||
* Fixed broken return value of sendMessage() in SingleApplication causing application to be started twice.
|
||||
@@ -572,11 +879,13 @@ Version 0.6.1:
|
||||
* 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).
|
||||
|
||||
Version 0.5.5:
|
||||
|
||||
Version 0.5.5 (2019.05.05)
|
||||
|
||||
* Fixed Tidal API url
|
||||
|
||||
Version 0.5.4:
|
||||
|
||||
Version 0.5.4 (2019.05.05)
|
||||
|
||||
* Changed description for offline mode scrobbling for less confusion
|
||||
* Fixed scrobbler to not send "playing now" when in offline mode
|
||||
@@ -607,7 +916,8 @@ Version 0.5.4:
|
||||
* 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
|
||||
|
||||
Version 0.5.3:
|
||||
|
||||
Version 0.5.3 (2019.03.02)
|
||||
|
||||
* Changed default tagging to albumartist in organise dialog
|
||||
* Removed support for older taglib in tagreader
|
||||
@@ -647,7 +957,8 @@ Version 0.5.3:
|
||||
* Added group by format
|
||||
* Fixed gstreamer leaks
|
||||
|
||||
Version 0.5.2:
|
||||
|
||||
Version 0.5.2 (2019.01.26)
|
||||
|
||||
* Added error handling and message for URL handler
|
||||
* Added SingleCoreApplication secondary check
|
||||
@@ -664,7 +975,8 @@ Version 0.5.2:
|
||||
* 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
|
||||
|
||||
Version 0.5.1:
|
||||
|
||||
Version 0.5.1 (2019.01.12)
|
||||
|
||||
* Added scrobbler with support for Last.fm, Libre.fm and ListenBrainz
|
||||
* Fixed key up causing playback to reset
|
||||
@@ -695,7 +1007,8 @@ Version 0.5.1:
|
||||
* Added debian copyright file
|
||||
* Fixed some compile errors
|
||||
|
||||
Version 0.4.2:
|
||||
|
||||
Version 0.4.2 (2018.11.28)
|
||||
|
||||
* Updated AppStream data file to newer specifications
|
||||
* Fixed Deezer engine to use quality setting
|
||||
@@ -709,7 +1022,8 @@ Version 0.4.2:
|
||||
* (Windows) Corrected uninstalled files on x64 installer
|
||||
* (macOS) Fixed poor performance
|
||||
|
||||
Version 0.4.1:
|
||||
|
||||
Version 0.4.1 (2018.11.01)
|
||||
|
||||
* Fixed crash in analyzer
|
||||
* Fixed trying to use systray even if the desktop had no systray
|
||||
@@ -727,11 +1041,13 @@ Version 0.4.1:
|
||||
* Added AppStream data file
|
||||
* 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
|
||||
|
||||
Version 0.3.2:
|
||||
|
||||
Version 0.3.2 (2018.09.24)
|
||||
|
||||
* Fixed search error not shown in Tidal search
|
||||
* Added URL handler for Tidal, now retrieving URL's when playing instead of when searching
|
||||
@@ -743,7 +1059,8 @@ Version 0.3.2:
|
||||
* Added encoding of Tidal token in the source code
|
||||
* 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
|
||||
* New improved context widget with albums and lyrics
|
||||
@@ -765,7 +1082,8 @@ Version 0.3.1:
|
||||
* Added support for reading/writing lyrics to tags
|
||||
* Fixed saving tags (APE) for WavPack files
|
||||
|
||||
Version 0.2.1:
|
||||
|
||||
Version 0.2.1 (2018.07.05)
|
||||
|
||||
* Fixed crash with newer Qt
|
||||
* Fixed setting output/device for Xine and VLC backend
|
||||
@@ -775,19 +1093,23 @@ Version 0.2.1:
|
||||
* Fixed device selection on macOS
|
||||
* 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 PulseAudio device selection
|
||||
* Improvements to device selection
|
||||
|
||||
Version 0.1.5:
|
||||
|
||||
Version 0.1.5 (2018.05.16)
|
||||
* Makefile fixes for building
|
||||
|
||||
Version 0.1.4:
|
||||
* Fixed compliation with clang compiler
|
||||
|
||||
Version 0.1.4 (2018.05.14)
|
||||
* Fixed compilation with clang compiler
|
||||
* 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
|
||||
* Added builtin taglib to 3rdparty to support detecting audio by content instead of just file extension
|
||||
* Removed unneeded qsqlite from 3rdparty
|
||||
@@ -795,7 +1117,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
|
||||
* Fixed git versioning
|
||||
|
||||
Version 0.1.2:
|
||||
|
||||
Version 0.1.2 (2018.05.02)
|
||||
* Fixed playback of WavPack files
|
||||
* Fixed musicbrainz tagfetcher
|
||||
* Use common regex (Song::kCoverRemoveDisc) for removing Disc/CD from album
|
||||
@@ -805,5 +1128,6 @@ Version 0.1.2:
|
||||
* Fixed problems with windows build missing some DLL's, only supplying required gstreamer-plugins now
|
||||
* Removed redundant code
|
||||
|
||||
Version 0.1.1:
|
||||
|
||||
Version 0.1.1 (2018.04.07)
|
||||
* Initial release
|
||||
|
||||
44
README.md
@@ -1,4 +1,4 @@
|
||||
:strawberry: Strawberry Music Player [](https://github.com/strawberrymusicplayer/strawberry/actions)
|
||||
:strawberry: Strawberry Music Player [](https://github.com/strawberrymusicplayer/strawberry/actions)
|
||||
=======================
|
||||
[](https://github.com/sponsors/jonaski)
|
||||
[](https://patreon.com/jonaskvinge)
|
||||
@@ -11,6 +11,7 @@ Strawberry is a music player and music collection organizer. It is a fork of Cle
|
||||
Resources:
|
||||
|
||||
* Website: https://www.strawberrymusicplayer.org/
|
||||
* Wiki: https://wiki.strawberrymusicplayer.org/
|
||||
* Forum: https://forum.strawberrymusicplayer.org/
|
||||
* Github: https://github.com/strawberrymusicplayer/strawberry
|
||||
* Buildbot: https://buildbot.strawberrymusicplayer.org/
|
||||
@@ -22,8 +23,10 @@ Resources:
|
||||
|
||||
### :bangbang: Opening an issue:
|
||||
|
||||
* Read the FAQ: https://wiki.strawberrymusicplayer.org/wiki/FAQ
|
||||
* Search for the issue to see if it is already solved, or if there is an open issue for it already. If there is an open issue already, you can comment on it if you have additional information that could be useful to us.
|
||||
* For technical problems, questions and feature requests please use our forum on https://forum.strawberrymusicplayer.org/ that is better suited for discussion. It also better allows answers from the community instead of just the developers on GitHub.
|
||||
* For technical problems, discussion, questions and feature suggestions use the forum (https://forum.strawberrymusicplayer.org/) instead. The forum is better suited for discussion.
|
||||
* We do not take feature requests from users on GitHub. Any issues related to feature requests will be closed. This does not necessarily mean that we won't add new features, but we don't have time to take feature requests or answer questions about new features from users. It is still possible to suggest or discuss new features on the forum (https://forum.strawberrymusicplayer.org/).
|
||||
|
||||
### :moneybag: Sponsoring:
|
||||
|
||||
@@ -48,7 +51,7 @@ Funding developers is a way to contribute to open source projects you appreciate
|
||||
* Edit tags on audio files
|
||||
* 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/)
|
||||
* 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 [Lyrics.com](https://www.lyrics.com/), [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
|
||||
* Audio analyzer
|
||||
* Audio equalizer
|
||||
@@ -66,18 +69,19 @@ 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:
|
||||
|
||||
* [CMake](https://cmake.org/)
|
||||
* [GNU Make](https://www.gnu.org/software/make/)
|
||||
* [GCC](https://gcc.gnu.org/) or [clang](https://clang.llvm.org/) compiler
|
||||
* C/C++ compiler ([GCC](https://gcc.gnu.org/), [Clang](https://clang.llvm.org/) or [MSVC](https://visualstudio.microsoft.com/vs/features/cplusplus/))
|
||||
* [pkg-config](https://www.freedesktop.org/wiki/Software/pkg-config/) or [pkgconf](https://github.com/pkgconf/pkgconf)
|
||||
* [Boost](https://www.boost.org/)
|
||||
* [GLib](https://developer.gnome.org/glib/)
|
||||
* [Protobuf](https://developers.google.com/protocol-buffers/)
|
||||
* [Qt 5.8 or higher (or Qt 6) with components Core, Gui, Widgets, Concurrent, Network and Sql](https://www.qt.io/)
|
||||
* [Qt 6 or Qt 5.9 or higher with components Core, Gui, Widgets, Concurrent, Network and Sql](https://www.qt.io/)
|
||||
* [SQLite 3.9 or newer with FTS5](https://www.sqlite.org)
|
||||
* [ALSA (Linux required)](https://www.alsa-project.org/)
|
||||
* [D-Bus (Linux required)](https://www.freedesktop.org/wiki/Software/dbus/)
|
||||
* [Protobuf](https://developers.google.com/protocol-buffers/)
|
||||
* [ALSA (Required on Linux)](https://www.alsa-project.org/)
|
||||
* [D-Bus (Required on Linux)](https://www.freedesktop.org/wiki/Software/dbus/)
|
||||
* [GStreamer](https://gstreamer.freedesktop.org/) or [VLC](https://www.videolan.org)
|
||||
* [GnuTLS](https://www.gnutls.org/)
|
||||
* [TagLib 1.11.1 or higher](https://www.taglib.org/) or [TagParser](https://github.com/Martchus/tagparser)
|
||||
* [ICU](https://unicode-org.github.io/icu/)
|
||||
|
||||
Optional dependencies:
|
||||
|
||||
@@ -88,8 +92,7 @@ Optional dependencies:
|
||||
* MTP devices: [libmtp](http://libmtp.sourceforge.net/)
|
||||
* iPod Classic devices: [libgpod](http://www.gtkpod.org/libgpod/)
|
||||
|
||||
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.
|
||||
You should also install the gstreamer plugins base and good, and optionally bad, ugly and libav to support all audio formats.
|
||||
|
||||
### :wrench: Compiling from source
|
||||
|
||||
@@ -100,16 +103,19 @@ You should also install the gstreamer plugins base and good, and optionally bad,
|
||||
### Compile and install:
|
||||
|
||||
cd strawberry
|
||||
mkdir build && cd build
|
||||
cmake ..
|
||||
make -j$(nproc)
|
||||
sudo make install
|
||||
|
||||
To compile with Qt 6 use:
|
||||
|
||||
mkdir build
|
||||
cd build
|
||||
cmake .. -DBUILD_WITH_QT6=ON
|
||||
make -j $(nproc)
|
||||
sudo make install
|
||||
|
||||
Strawberry is backwards compatible with Qt 5, to compile with Qt 5 use:
|
||||
|
||||
cmake .. -DBUILD_WITH_QT5=ON
|
||||
|
||||
To compile on Windows with Visual Studio 2019 or 2022, see https://github.com/strawberrymusicplayer/strawberry-msvc
|
||||
|
||||
### :penguin: Packaging status
|
||||
|
||||
[](https://repology.org/metapackage/strawberry/versions)
|
||||
[](https://repology.org/metapackage/strawberry/versions)
|
||||
|
||||
|
||||
@@ -2,18 +2,5 @@ find_program(LSB_RELEASE_EXEC lsb_release)
|
||||
find_program(DPKG_BUILDPACKAGE dpkg-buildpackage)
|
||||
|
||||
if (LSB_RELEASE_EXEC AND DPKG_BUILDPACKAGE)
|
||||
execute_process(COMMAND env LC_ALL=C date "+%a, %-d %b %Y %H:%M:%S %z" OUTPUT_VARIABLE DEB_DATE OUTPUT_STRIP_TRAILING_WHITESPACE)
|
||||
|
||||
execute_process(COMMAND /bin/sh "-c" "${LSB_RELEASE_EXEC} -cs"
|
||||
OUTPUT_VARIABLE DEB_CODENAME
|
||||
OUTPUT_STRIP_TRAILING_WHITESPACE
|
||||
)
|
||||
|
||||
if (DEB_CODENAME AND DEB_DATE)
|
||||
add_custom_target(deb
|
||||
WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}
|
||||
COMMAND ${DPKG_BUILDPACKAGE} -b -d -uc -us
|
||||
)
|
||||
endif()
|
||||
|
||||
add_custom_target(deb WORKING_DIRECTORY ${CMAKE_SOURCE_DIR} COMMAND ${DPKG_BUILDPACKAGE} -b -d -uc -us)
|
||||
endif()
|
||||
|
||||
@@ -21,10 +21,7 @@ if(MACDEPLOYQT_EXECUTABLE)
|
||||
COMMAND mkdir -p ${CMAKE_BINARY_DIR}/strawberry.app/Contents/{Frameworks,Resources}
|
||||
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
|
||||
-executable=${CMAKE_BINARY_DIR}/strawberry.app/Contents/PlugIns/gio-modules/libgiognutls.so
|
||||
#-executable=${CMAKE_BINARY_DIR}/strawberry.app/Contents/PlugIns/gst-plugin-scanner
|
||||
COMMAND ${MACDEPLOYQT_EXECUTABLE} strawberry.app -verbose=3 -executable=${CMAKE_BINARY_DIR}/strawberry.app/Contents/PlugIns/strawberry-tagreader
|
||||
WORKING_DIRECTORY ${CMAKE_BINARY_DIR}
|
||||
DEPENDS strawberry strawberry-tagreader copy_gstreamer_plugins macdeployqt
|
||||
)
|
||||
|
||||
@@ -12,10 +12,17 @@ if (LSB_RELEASE_EXEC AND RPMBUILD_EXEC)
|
||||
OUTPUT_VARIABLE DIST_RELEASE
|
||||
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"
|
||||
OUTPUT_VARIABLE DIST_VERSION
|
||||
OUTPUT_STRIP_TRAILING_WHITESPACE
|
||||
)
|
||||
if (${DIST_NAME} STREQUAL "openmandrivalinux")
|
||||
execute_process(COMMAND /bin/sh "-c" "${LSB_RELEASE_EXEC} -ds | tr '[:upper:]' '[:lower:]' | sed 's/\"//g' | sed 's/\\./0/g' | cut -d' ' -f3"
|
||||
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)
|
||||
|
||||
message(STATUS "Distro Name: ${DIST_NAME}")
|
||||
@@ -44,6 +51,8 @@ if (LSB_RELEASE_EXEC AND RPMBUILD_EXEC)
|
||||
set(RPM_DISTRO "el${DIST_VERSION}")
|
||||
elseif (${DIST_NAME} STREQUAL "mageia" AND DIST_RELEASE)
|
||||
set(RPM_DISTRO "mga${DIST_RELEASE}")
|
||||
elseif (${DIST_NAME} STREQUAL "openmandrivalinux" AND DIST_VERSION)
|
||||
set(RPM_DISTRO "omv${DIST_VERSION}")
|
||||
endif()
|
||||
|
||||
if(NOT RPM_DISTRO)
|
||||
|
||||
@@ -24,7 +24,7 @@ macro(summary_show)
|
||||
list(SORT summary_willbuild)
|
||||
list(SORT summary_willnotbuild)
|
||||
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_willnotbuild "The following components WILL NOT be built:")
|
||||
message("")
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
set(STRAWBERRY_VERSION_MAJOR 1)
|
||||
set(STRAWBERRY_VERSION_MINOR 0)
|
||||
set(STRAWBERRY_VERSION_PATCH 0)
|
||||
set(STRAWBERRY_VERSION_PATCH 17)
|
||||
#set(STRAWBERRY_VERSION_PRERELEASE rc1)
|
||||
|
||||
set(INCLUDE_GIT_REVISION OFF)
|
||||
@@ -14,10 +14,6 @@ set(STRAWBERRY_VERSION_RPM_R "1")
|
||||
set(STRAWBERRY_VERSION_PAC_V "${majorminorpatch}")
|
||||
set(STRAWBERRY_VERSION_PAC_R "1")
|
||||
|
||||
if(${STRAWBERRY_VERSION_PATCH} EQUAL "0")
|
||||
set(STRAWBERRY_VERSION_DISPLAY "${STRAWBERRY_VERSION_MAJOR}.${STRAWBERRY_VERSION_MINOR}")
|
||||
endif(${STRAWBERRY_VERSION_PATCH} EQUAL "0")
|
||||
|
||||
if(STRAWBERRY_VERSION_PRERELEASE)
|
||||
set(STRAWBERRY_VERSION_DISPLAY "${STRAWBERRY_VERSION_DISPLAY} ${STRAWBERRY_VERSION_PRERELEASE}")
|
||||
set(STRAWBERRY_VERSION_RPM_R "0.${STRAWBERRY_VERSION_PRERELEASE}")
|
||||
|
||||
@@ -1,21 +1,13 @@
|
||||
<RCC>
|
||||
<qresource prefix="/">
|
||||
<file>schema/schema.sql</file>
|
||||
<file>schema/schema-1.sql</file>
|
||||
<file>schema/schema-2.sql</file>
|
||||
<file>schema/schema-3.sql</file>
|
||||
<file>schema/schema-4.sql</file>
|
||||
<file>schema/schema-5.sql</file>
|
||||
<file>schema/schema-6.sql</file>
|
||||
<file>schema/schema-7.sql</file>
|
||||
<file>schema/schema-8.sql</file>
|
||||
<file>schema/schema-9.sql</file>
|
||||
<file>schema/schema-10.sql</file>
|
||||
<file>schema/schema-11.sql</file>
|
||||
<file>schema/schema-12.sql</file>
|
||||
<file>schema/schema-13.sql</file>
|
||||
<file>schema/schema-14.sql</file>
|
||||
<file>schema/schema-15.sql</file>
|
||||
<file>schema/schema-16.sql</file>
|
||||
<file>schema/device-schema.sql</file>
|
||||
<file>style/strawberry.css</file>
|
||||
<file>style/smartplaylistsearchterm.css</file>
|
||||
@@ -46,7 +38,6 @@
|
||||
<file>pictures/rainbowdash.png</file>
|
||||
<file>pictures/star-on.png</file>
|
||||
<file>pictures/star-off.png</file>
|
||||
<file>fonts/HumongousofEternitySt.ttf</file>
|
||||
<file>mood/sample.mood</file>
|
||||
<file>text/ghosts.txt</file>
|
||||
</qresource>
|
||||
|
||||
@@ -24,6 +24,7 @@
|
||||
<file>icons/128x128/document-open-folder.png</file>
|
||||
<file>icons/128x128/document-open.png</file>
|
||||
<file>icons/128x128/document-save.png</file>
|
||||
<file>icons/128x128/document-save-all.png</file>
|
||||
<file>icons/128x128/document-search.png</file>
|
||||
<file>icons/128x128/download.png</file>
|
||||
<file>icons/128x128/edit-clear-list.png</file>
|
||||
@@ -95,6 +96,7 @@
|
||||
<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/alsa.png</file>
|
||||
<file>icons/64x64/application-exit.png</file>
|
||||
@@ -119,6 +121,7 @@
|
||||
<file>icons/64x64/document-open-folder.png</file>
|
||||
<file>icons/64x64/document-open.png</file>
|
||||
<file>icons/64x64/document-save.png</file>
|
||||
<file>icons/64x64/document-save-all.png</file>
|
||||
<file>icons/64x64/document-search.png</file>
|
||||
<file>icons/64x64/download.png</file>
|
||||
<file>icons/64x64/edit-clear-list.png</file>
|
||||
@@ -191,6 +194,7 @@
|
||||
<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/alsa.png</file>
|
||||
<file>icons/48x48/application-exit.png</file>
|
||||
@@ -216,6 +220,7 @@
|
||||
<file>icons/48x48/document-open-remote.png</file>
|
||||
<file>icons/48x48/document-open.png</file>
|
||||
<file>icons/48x48/document-save.png</file>
|
||||
<file>icons/48x48/document-save-all.png</file>
|
||||
<file>icons/48x48/document-search.png</file>
|
||||
<file>icons/48x48/download.png</file>
|
||||
<file>icons/48x48/edit-clear-list.png</file>
|
||||
@@ -291,6 +296,7 @@
|
||||
<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/alsa.png</file>
|
||||
<file>icons/32x32/application-exit.png</file>
|
||||
@@ -316,6 +322,7 @@
|
||||
<file>icons/32x32/document-open-remote.png</file>
|
||||
<file>icons/32x32/document-open.png</file>
|
||||
<file>icons/32x32/document-save.png</file>
|
||||
<file>icons/32x32/document-save-all.png</file>
|
||||
<file>icons/32x32/document-search.png</file>
|
||||
<file>icons/32x32/download.png</file>
|
||||
<file>icons/32x32/edit-clear-list.png</file>
|
||||
@@ -391,6 +398,7 @@
|
||||
<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/alsa.png</file>
|
||||
<file>icons/22x22/application-exit.png</file>
|
||||
@@ -416,6 +424,7 @@
|
||||
<file>icons/22x22/document-open-remote.png</file>
|
||||
<file>icons/22x22/document-open.png</file>
|
||||
<file>icons/22x22/document-save.png</file>
|
||||
<file>icons/22x22/document-save-all.png</file>
|
||||
<file>icons/22x22/document-search.png</file>
|
||||
<file>icons/22x22/download.png</file>
|
||||
<file>icons/22x22/edit-clear-list.png</file>
|
||||
@@ -491,5 +500,6 @@
|
||||
<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>
|
||||
</RCC>
|
||||
|
||||
BIN
data/icons/128x128/document-save-all.png
Normal file
|
After Width: | Height: | Size: 9.3 KiB |
BIN
data/icons/128x128/musicbrainz.png
Normal file
|
After Width: | Height: | Size: 6.1 KiB |
BIN
data/icons/22x22/document-save-all.png
Normal file
|
After Width: | Height: | Size: 921 B |
BIN
data/icons/22x22/musicbrainz.png
Normal file
|
After Width: | Height: | Size: 1.0 KiB |
BIN
data/icons/32x32/document-save-all.png
Normal file
|
After Width: | Height: | Size: 1.3 KiB |
BIN
data/icons/32x32/musicbrainz.png
Normal file
|
After Width: | Height: | Size: 947 B |
BIN
data/icons/48x48/document-save-all.png
Normal file
|
After Width: | Height: | Size: 2.5 KiB |
BIN
data/icons/48x48/musicbrainz.png
Normal file
|
After Width: | Height: | Size: 1.3 KiB |
BIN
data/icons/64x64/document-save-all.png
Normal file
|
After Width: | Height: | Size: 3.6 KiB |
BIN
data/icons/64x64/musicbrainz.png
Normal file
|
After Width: | Height: | Size: 3.8 KiB |
BIN
data/icons/full/document-save-all.png
Normal file
|
After Width: | Height: | Size: 9.3 KiB |
BIN
data/icons/full/musicbrainz.png
Normal file
|
After Width: | Height: | Size: 13 KiB |
@@ -1,35 +1,35 @@
|
||||
CREATE TABLE device_%deviceid_directories (
|
||||
path TEXT NOT NULL DEFAULT '',
|
||||
path TEXT NOT NULL,
|
||||
subdirs INTEGER NOT NULL
|
||||
);
|
||||
|
||||
CREATE TABLE device_%deviceid_subdirectories (
|
||||
directory_id INTEGER NOT NULL,
|
||||
path TEXT NOT NULL DEFAULT '',
|
||||
path TEXT NOT NULL,
|
||||
mtime INTEGER NOT NULL
|
||||
);
|
||||
|
||||
CREATE TABLE device_%deviceid_songs (
|
||||
|
||||
title TEXT DEFAULT '',
|
||||
album TEXT DEFAULT '',
|
||||
artist TEXT DEFAULT '',
|
||||
albumartist TEXT DEFAULT '',
|
||||
title TEXT,
|
||||
album TEXT,
|
||||
artist TEXT,
|
||||
albumartist TEXT,
|
||||
track INTEGER NOT NULL DEFAULT -1,
|
||||
disc INTEGER NOT NULL DEFAULT -1,
|
||||
year INTEGER NOT NULL DEFAULT -1,
|
||||
originalyear INTEGER NOT NULL DEFAULT 0,
|
||||
genre TEXT DEFAULT '',
|
||||
genre TEXT,
|
||||
compilation INTEGER NOT NULL DEFAULT 0,
|
||||
composer TEXT DEFAULT '',
|
||||
performer TEXT DEFAULT '',
|
||||
grouping TEXT DEFAULT '',
|
||||
comment TEXT DEFAULT '',
|
||||
lyrics TEXT DEFAULT '',
|
||||
composer TEXT,
|
||||
performer TEXT,
|
||||
grouping TEXT,
|
||||
comment TEXT,
|
||||
lyrics TEXT,
|
||||
|
||||
artist_id TEXT DEFAULT '',
|
||||
album_id TEXT DEFAULT '',
|
||||
song_id TEXT DEFAULT '',
|
||||
artist_id TEXT,
|
||||
album_id TEXT,
|
||||
song_id TEXT,
|
||||
|
||||
beginning INTEGER NOT NULL DEFAULT 0,
|
||||
length INTEGER NOT NULL DEFAULT 0,
|
||||
@@ -40,14 +40,14 @@ CREATE TABLE device_%deviceid_songs (
|
||||
|
||||
source INTEGER NOT NULL DEFAULT 0,
|
||||
directory_id INTEGER NOT NULL DEFAULT -1,
|
||||
url TEXT NOT NULL DEFAULT '',
|
||||
url TEXT NOT NULL,
|
||||
filetype INTEGER NOT NULL DEFAULT 0,
|
||||
filesize INTEGER NOT NULL DEFAULT -1,
|
||||
mtime INTEGER NOT NULL DEFAULT -1,
|
||||
ctime INTEGER NOT NULL DEFAULT -1,
|
||||
unavailable INTEGER DEFAULT 0,
|
||||
|
||||
fingerprint TEXT DEFAULT '',
|
||||
fingerprint TEXT,
|
||||
|
||||
playcount INTEGER NOT NULL DEFAULT 0,
|
||||
skipcount INTEGER NOT NULL DEFAULT 0,
|
||||
@@ -59,15 +59,29 @@ CREATE TABLE device_%deviceid_songs (
|
||||
compilation_off INTEGER NOT NULL DEFAULT 0,
|
||||
compilation_effective INTEGER NOT NULL DEFAULT 0,
|
||||
|
||||
art_automatic TEXT DEFAULT '',
|
||||
art_manual TEXT DEFAULT '',
|
||||
art_automatic TEXT,
|
||||
art_manual TEXT,
|
||||
|
||||
effective_albumartist TEXT DEFAULT '',
|
||||
effective_albumartist TEXT,
|
||||
effective_originalyear INTEGER NOT NULL DEFAULT 0,
|
||||
|
||||
cue_path TEXT DEFAULT '',
|
||||
cue_path TEXT,
|
||||
|
||||
rating INTEGER DEFAULT -1
|
||||
rating INTEGER DEFAULT -1,
|
||||
|
||||
acoustid_id TEXT,
|
||||
acoustid_fingerprint TEXT,
|
||||
|
||||
musicbrainz_album_artist_id TEXT,
|
||||
musicbrainz_artist_id TEXT,
|
||||
musicbrainz_original_artist_id TEXT,
|
||||
musicbrainz_album_id TEXT,
|
||||
musicbrainz_original_album_id TEXT,
|
||||
musicbrainz_recording_id TEXT,
|
||||
musicbrainz_track_id TEXT,
|
||||
musicbrainz_disc_id TEXT,
|
||||
musicbrainz_release_group_id TEXT,
|
||||
musicbrainz_work_id TEXT
|
||||
|
||||
);
|
||||
|
||||
@@ -80,4 +94,4 @@ CREATE VIRTUAL TABLE device_%deviceid_fts USING fts5(
|
||||
tokenize = "unicode61 remove_diacritics 1"
|
||||
);
|
||||
|
||||
UPDATE devices SET schema_version=3 WHERE ROWID=%deviceid;
|
||||
UPDATE devices SET schema_version=4 WHERE ROWID=%deviceid;
|
||||
|
||||
@@ -1,3 +0,0 @@
|
||||
ALTER TABLE playlist_items ADD COLUMN internet_service TEXT;
|
||||
|
||||
UPDATE schema_version SET version=1;
|
||||
@@ -1,4 +1,4 @@
|
||||
ALTER TABLE %allsongstables ADD COLUMN fingerprint TEXT DEFAULT '';
|
||||
ALTER TABLE %allsongstables ADD COLUMN fingerprint TEXT;
|
||||
|
||||
ALTER TABLE %allsongstables ADD COLUMN lastseen INTEGER NOT NULL DEFAULT -1;
|
||||
|
||||
|
||||
@@ -1,8 +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 ''
|
||||
name TEXT NOT NULL,
|
||||
url TEXT NOT NULL,
|
||||
thumbnail_url TEXT
|
||||
);
|
||||
|
||||
UPDATE schema_version SET version=15;
|
||||
|
||||
217
data/schema/schema-16.sql
Normal file
@@ -0,0 +1,217 @@
|
||||
ALTER TABLE songs ADD COLUMN acoustid_id TEXT;
|
||||
|
||||
ALTER TABLE songs ADD COLUMN acoustid_fingerprint TEXT;
|
||||
|
||||
ALTER TABLE songs ADD COLUMN musicbrainz_album_artist_id TEXT;
|
||||
|
||||
ALTER TABLE songs ADD COLUMN musicbrainz_artist_id TEXT;
|
||||
|
||||
ALTER TABLE songs ADD COLUMN musicbrainz_original_artist_id TEXT;
|
||||
|
||||
ALTER TABLE songs ADD COLUMN musicbrainz_album_id TEXT;
|
||||
|
||||
ALTER TABLE songs ADD COLUMN musicbrainz_original_album_id TEXT;
|
||||
|
||||
ALTER TABLE songs ADD COLUMN musicbrainz_recording_id TEXT;
|
||||
|
||||
ALTER TABLE songs ADD COLUMN musicbrainz_track_id TEXT;
|
||||
|
||||
ALTER TABLE songs ADD COLUMN musicbrainz_disc_id TEXT;
|
||||
|
||||
ALTER TABLE songs ADD COLUMN musicbrainz_release_group_id TEXT;
|
||||
|
||||
ALTER TABLE songs ADD COLUMN musicbrainz_work_id TEXT;
|
||||
|
||||
ALTER TABLE subsonic_songs ADD COLUMN acoustid_id TEXT;
|
||||
|
||||
ALTER TABLE subsonic_songs ADD COLUMN acoustid_fingerprint TEXT;
|
||||
|
||||
ALTER TABLE subsonic_songs ADD COLUMN musicbrainz_album_artist_id TEXT;
|
||||
|
||||
ALTER TABLE subsonic_songs ADD COLUMN musicbrainz_artist_id TEXT;
|
||||
|
||||
ALTER TABLE subsonic_songs ADD COLUMN musicbrainz_original_artist_id TEXT;
|
||||
|
||||
ALTER TABLE subsonic_songs ADD COLUMN musicbrainz_album_id TEXT;
|
||||
|
||||
ALTER TABLE subsonic_songs ADD COLUMN musicbrainz_original_album_id TEXT;
|
||||
|
||||
ALTER TABLE subsonic_songs ADD COLUMN musicbrainz_recording_id TEXT;
|
||||
|
||||
ALTER TABLE subsonic_songs ADD COLUMN musicbrainz_track_id TEXT;
|
||||
|
||||
ALTER TABLE subsonic_songs ADD COLUMN musicbrainz_disc_id TEXT;
|
||||
|
||||
ALTER TABLE subsonic_songs ADD COLUMN musicbrainz_release_group_id TEXT;
|
||||
|
||||
ALTER TABLE subsonic_songs ADD COLUMN musicbrainz_work_id TEXT;
|
||||
|
||||
ALTER TABLE tidal_artists_songs ADD COLUMN acoustid_id TEXT;
|
||||
|
||||
ALTER TABLE tidal_artists_songs ADD COLUMN acoustid_fingerprint TEXT;
|
||||
|
||||
ALTER TABLE tidal_artists_songs ADD COLUMN musicbrainz_album_artist_id TEXT;
|
||||
|
||||
ALTER TABLE tidal_artists_songs ADD COLUMN musicbrainz_artist_id TEXT;
|
||||
|
||||
ALTER TABLE tidal_artists_songs ADD COLUMN musicbrainz_original_artist_id TEXT;
|
||||
|
||||
ALTER TABLE tidal_artists_songs ADD COLUMN musicbrainz_album_id TEXT;
|
||||
|
||||
ALTER TABLE tidal_artists_songs ADD COLUMN musicbrainz_original_album_id TEXT;
|
||||
|
||||
ALTER TABLE tidal_artists_songs ADD COLUMN musicbrainz_recording_id TEXT;
|
||||
|
||||
ALTER TABLE tidal_artists_songs ADD COLUMN musicbrainz_track_id TEXT;
|
||||
|
||||
ALTER TABLE tidal_artists_songs ADD COLUMN musicbrainz_disc_id TEXT;
|
||||
|
||||
ALTER TABLE tidal_artists_songs ADD COLUMN musicbrainz_release_group_id TEXT;
|
||||
|
||||
ALTER TABLE tidal_artists_songs ADD COLUMN musicbrainz_work_id TEXT;
|
||||
|
||||
ALTER TABLE tidal_albums_songs ADD COLUMN acoustid_id TEXT;
|
||||
|
||||
ALTER TABLE tidal_albums_songs ADD COLUMN acoustid_fingerprint TEXT;
|
||||
|
||||
ALTER TABLE tidal_albums_songs ADD COLUMN musicbrainz_album_artist_id TEXT;
|
||||
|
||||
ALTER TABLE tidal_albums_songs ADD COLUMN musicbrainz_artist_id TEXT;
|
||||
|
||||
ALTER TABLE tidal_albums_songs ADD COLUMN musicbrainz_original_artist_id TEXT;
|
||||
|
||||
ALTER TABLE tidal_albums_songs ADD COLUMN musicbrainz_album_id TEXT;
|
||||
|
||||
ALTER TABLE tidal_albums_songs ADD COLUMN musicbrainz_original_album_id TEXT;
|
||||
|
||||
ALTER TABLE tidal_albums_songs ADD COLUMN musicbrainz_recording_id TEXT;
|
||||
|
||||
ALTER TABLE tidal_albums_songs ADD COLUMN musicbrainz_track_id TEXT;
|
||||
|
||||
ALTER TABLE tidal_albums_songs ADD COLUMN musicbrainz_disc_id TEXT;
|
||||
|
||||
ALTER TABLE tidal_albums_songs ADD COLUMN musicbrainz_release_group_id TEXT;
|
||||
|
||||
ALTER TABLE tidal_albums_songs ADD COLUMN musicbrainz_work_id TEXT;
|
||||
|
||||
ALTER TABLE tidal_songs ADD COLUMN acoustid_id TEXT;
|
||||
|
||||
ALTER TABLE tidal_songs ADD COLUMN acoustid_fingerprint TEXT;
|
||||
|
||||
ALTER TABLE tidal_songs ADD COLUMN musicbrainz_album_artist_id TEXT;
|
||||
|
||||
ALTER TABLE tidal_songs ADD COLUMN musicbrainz_artist_id TEXT;
|
||||
|
||||
ALTER TABLE tidal_songs ADD COLUMN musicbrainz_original_artist_id TEXT;
|
||||
|
||||
ALTER TABLE tidal_songs ADD COLUMN musicbrainz_album_id TEXT;
|
||||
|
||||
ALTER TABLE tidal_songs ADD COLUMN musicbrainz_original_album_id TEXT;
|
||||
|
||||
ALTER TABLE tidal_songs ADD COLUMN musicbrainz_recording_id TEXT;
|
||||
|
||||
ALTER TABLE tidal_songs ADD COLUMN musicbrainz_track_id TEXT;
|
||||
|
||||
ALTER TABLE tidal_songs ADD COLUMN musicbrainz_disc_id TEXT;
|
||||
|
||||
ALTER TABLE tidal_songs ADD COLUMN musicbrainz_release_group_id TEXT;
|
||||
|
||||
ALTER TABLE tidal_songs ADD COLUMN musicbrainz_work_id TEXT;
|
||||
|
||||
ALTER TABLE qobuz_artists_songs ADD COLUMN acoustid_id TEXT;
|
||||
|
||||
ALTER TABLE qobuz_artists_songs ADD COLUMN acoustid_fingerprint TEXT;
|
||||
|
||||
ALTER TABLE qobuz_artists_songs ADD COLUMN musicbrainz_album_artist_id TEXT;
|
||||
|
||||
ALTER TABLE qobuz_artists_songs ADD COLUMN musicbrainz_artist_id TEXT;
|
||||
|
||||
ALTER TABLE qobuz_artists_songs ADD COLUMN musicbrainz_original_artist_id TEXT;
|
||||
|
||||
ALTER TABLE qobuz_artists_songs ADD COLUMN musicbrainz_album_id TEXT;
|
||||
|
||||
ALTER TABLE qobuz_artists_songs ADD COLUMN musicbrainz_original_album_id TEXT;
|
||||
|
||||
ALTER TABLE qobuz_artists_songs ADD COLUMN musicbrainz_recording_id TEXT;
|
||||
|
||||
ALTER TABLE qobuz_artists_songs ADD COLUMN musicbrainz_track_id TEXT;
|
||||
|
||||
ALTER TABLE qobuz_artists_songs ADD COLUMN musicbrainz_disc_id TEXT;
|
||||
|
||||
ALTER TABLE qobuz_artists_songs ADD COLUMN musicbrainz_release_group_id TEXT;
|
||||
|
||||
ALTER TABLE qobuz_artists_songs ADD COLUMN musicbrainz_work_id TEXT;
|
||||
|
||||
ALTER TABLE qobuz_albums_songs ADD COLUMN acoustid_id TEXT;
|
||||
|
||||
ALTER TABLE qobuz_albums_songs ADD COLUMN acoustid_fingerprint TEXT;
|
||||
|
||||
ALTER TABLE qobuz_albums_songs ADD COLUMN musicbrainz_album_artist_id TEXT;
|
||||
|
||||
ALTER TABLE qobuz_albums_songs ADD COLUMN musicbrainz_artist_id TEXT;
|
||||
|
||||
ALTER TABLE qobuz_albums_songs ADD COLUMN musicbrainz_original_artist_id TEXT;
|
||||
|
||||
ALTER TABLE qobuz_albums_songs ADD COLUMN musicbrainz_album_id TEXT;
|
||||
|
||||
ALTER TABLE qobuz_albums_songs ADD COLUMN musicbrainz_original_album_id TEXT;
|
||||
|
||||
ALTER TABLE qobuz_albums_songs ADD COLUMN musicbrainz_recording_id TEXT;
|
||||
|
||||
ALTER TABLE qobuz_albums_songs ADD COLUMN musicbrainz_track_id TEXT;
|
||||
|
||||
ALTER TABLE qobuz_albums_songs ADD COLUMN musicbrainz_disc_id TEXT;
|
||||
|
||||
ALTER TABLE qobuz_albums_songs ADD COLUMN musicbrainz_release_group_id TEXT;
|
||||
|
||||
ALTER TABLE qobuz_albums_songs ADD COLUMN musicbrainz_work_id TEXT;
|
||||
|
||||
ALTER TABLE qobuz_songs ADD COLUMN acoustid_id TEXT;
|
||||
|
||||
ALTER TABLE qobuz_songs ADD COLUMN acoustid_fingerprint TEXT;
|
||||
|
||||
ALTER TABLE qobuz_songs ADD COLUMN musicbrainz_album_artist_id TEXT;
|
||||
|
||||
ALTER TABLE qobuz_songs ADD COLUMN musicbrainz_artist_id TEXT;
|
||||
|
||||
ALTER TABLE qobuz_songs ADD COLUMN musicbrainz_original_artist_id TEXT;
|
||||
|
||||
ALTER TABLE qobuz_songs ADD COLUMN musicbrainz_album_id TEXT;
|
||||
|
||||
ALTER TABLE qobuz_songs ADD COLUMN musicbrainz_original_album_id TEXT;
|
||||
|
||||
ALTER TABLE qobuz_songs ADD COLUMN musicbrainz_recording_id TEXT;
|
||||
|
||||
ALTER TABLE qobuz_songs ADD COLUMN musicbrainz_track_id TEXT;
|
||||
|
||||
ALTER TABLE qobuz_songs ADD COLUMN musicbrainz_disc_id TEXT;
|
||||
|
||||
ALTER TABLE qobuz_songs ADD COLUMN musicbrainz_release_group_id TEXT;
|
||||
|
||||
ALTER TABLE qobuz_songs ADD COLUMN musicbrainz_work_id TEXT;
|
||||
|
||||
ALTER TABLE playlist_items ADD COLUMN acoustid_id TEXT;
|
||||
|
||||
ALTER TABLE playlist_items ADD COLUMN acoustid_fingerprint TEXT;
|
||||
|
||||
ALTER TABLE playlist_items ADD COLUMN musicbrainz_album_artist_id TEXT;
|
||||
|
||||
ALTER TABLE playlist_items ADD COLUMN musicbrainz_artist_id TEXT;
|
||||
|
||||
ALTER TABLE playlist_items ADD COLUMN musicbrainz_original_artist_id TEXT;
|
||||
|
||||
ALTER TABLE playlist_items ADD COLUMN musicbrainz_album_id TEXT;
|
||||
|
||||
ALTER TABLE playlist_items ADD COLUMN musicbrainz_original_album_id TEXT;
|
||||
|
||||
ALTER TABLE playlist_items ADD COLUMN musicbrainz_recording_id TEXT;
|
||||
|
||||
ALTER TABLE playlist_items ADD COLUMN musicbrainz_track_id TEXT;
|
||||
|
||||
ALTER TABLE playlist_items ADD COLUMN musicbrainz_disc_id TEXT;
|
||||
|
||||
ALTER TABLE playlist_items ADD COLUMN musicbrainz_release_group_id TEXT;
|
||||
|
||||
ALTER TABLE playlist_items ADD COLUMN musicbrainz_work_id TEXT;
|
||||
|
||||
UPDATE schema_version SET version=16;
|
||||
@@ -1,5 +0,0 @@
|
||||
ALTER TABLE songs ADD COLUMN lyrics TEXT;
|
||||
|
||||
ALTER TABLE playlist_items ADD COLUMN lyrics TEXT;
|
||||
|
||||
UPDATE schema_version SET version=2;
|
||||
@@ -1,65 +0,0 @@
|
||||
ALTER TABLE songs ADD COLUMN source INTEGER NOT NULL DEFAULT 0;
|
||||
|
||||
UPDATE songs SET source = 2 WHERE source = 0;
|
||||
|
||||
DROP TABLE playlist_items;
|
||||
|
||||
CREATE TABLE IF NOT EXISTS playlist_items (
|
||||
|
||||
playlist INTEGER NOT NULL,
|
||||
type INTEGER NOT NULL DEFAULT 0,
|
||||
collection_id INTEGER,
|
||||
url TEXT,
|
||||
|
||||
title TEXT NOT NULL,
|
||||
album TEXT NOT NULL,
|
||||
artist TEXT NOT NULL,
|
||||
albumartist TEXT NOT NULL,
|
||||
track INTEGER NOT NULL DEFAULT -1,
|
||||
disc INTEGER NOT NULL DEFAULT -1,
|
||||
year INTEGER NOT NULL DEFAULT -1,
|
||||
originalyear INTEGER NOT NULL DEFAULT 0,
|
||||
genre TEXT NOT NULL,
|
||||
compilation INTEGER NOT NULL DEFAULT -1,
|
||||
composer TEXT NOT NULL,
|
||||
performer TEXT NOT NULL,
|
||||
grouping TEXT NOT NULL,
|
||||
comment TEXT NOT NULL,
|
||||
lyrics TEXT NOT NULL,
|
||||
|
||||
beginning INTEGER NOT NULL DEFAULT 0,
|
||||
length INTEGER NOT NULL DEFAULT 0,
|
||||
|
||||
bitrate INTEGER NOT NULL DEFAULT 0,
|
||||
samplerate INTEGER NOT NULL DEFAULT 0,
|
||||
bitdepth INTEGER NOT NULL DEFAULT 0,
|
||||
|
||||
source INTEGER NOT NULL DEFAULT 0,
|
||||
directory_id INTEGER,
|
||||
filename TEXT,
|
||||
filetype INTEGER NOT NULL DEFAULT 0,
|
||||
filesize INTEGER,
|
||||
mtime INTEGER,
|
||||
ctime INTEGER,
|
||||
unavailable INTEGER DEFAULT 0,
|
||||
|
||||
playcount INTEGER NOT NULL DEFAULT 0,
|
||||
skipcount INTEGER NOT NULL DEFAULT 0,
|
||||
lastplayed INTEGER NOT NULL DEFAULT 0,
|
||||
|
||||
compilation_detected INTEGER DEFAULT 0,
|
||||
compilation_on INTEGER NOT NULL DEFAULT 0,
|
||||
compilation_off INTEGER NOT NULL DEFAULT 0,
|
||||
compilation_effective INTEGER NOT NULL DEFAULT 0,
|
||||
|
||||
art_automatic TEXT,
|
||||
art_manual TEXT,
|
||||
|
||||
effective_albumartist TEXT,
|
||||
effective_originalyear INTEGER NOT NULL DEFAULT 0,
|
||||
|
||||
cue_path TEXT
|
||||
|
||||
);
|
||||
|
||||
UPDATE schema_version SET version=3;
|
||||
@@ -1,205 +0,0 @@
|
||||
CREATE TABLE IF NOT EXISTS tidal_artists_songs (
|
||||
|
||||
title TEXT NOT NULL,
|
||||
album TEXT NOT NULL,
|
||||
artist TEXT NOT NULL,
|
||||
albumartist TEXT NOT NULL,
|
||||
track INTEGER NOT NULL DEFAULT -1,
|
||||
disc INTEGER NOT NULL DEFAULT -1,
|
||||
year INTEGER NOT NULL DEFAULT -1,
|
||||
originalyear INTEGER NOT NULL DEFAULT 0,
|
||||
genre TEXT NOT NULL,
|
||||
compilation INTEGER NOT NULL DEFAULT -1,
|
||||
composer TEXT NOT NULL,
|
||||
performer TEXT NOT NULL,
|
||||
grouping TEXT NOT NULL,
|
||||
comment TEXT NOT NULL,
|
||||
lyrics TEXT NOT NULL,
|
||||
|
||||
beginning INTEGER NOT NULL DEFAULT 0,
|
||||
length INTEGER NOT NULL DEFAULT 0,
|
||||
|
||||
bitrate INTEGER NOT NULL DEFAULT 0,
|
||||
samplerate INTEGER NOT NULL DEFAULT 0,
|
||||
bitdepth INTEGER NOT NULL DEFAULT 0,
|
||||
|
||||
source INTEGER NOT NULL DEFAULT 0,
|
||||
directory_id INTEGER NOT NULL,
|
||||
filename TEXT NOT NULL,
|
||||
filetype INTEGER NOT NULL DEFAULT 0,
|
||||
filesize INTEGER NOT NULL DEFAULT 0,
|
||||
mtime INTEGER NOT NULL DEFAULT 0,
|
||||
ctime INTEGER NOT NULL DEFAULT 0,
|
||||
unavailable INTEGER DEFAULT 0,
|
||||
|
||||
playcount INTEGER NOT NULL DEFAULT 0,
|
||||
skipcount INTEGER NOT NULL DEFAULT 0,
|
||||
lastplayed INTEGER NOT NULL DEFAULT 0,
|
||||
|
||||
compilation_detected INTEGER DEFAULT 0,
|
||||
compilation_on INTEGER NOT NULL DEFAULT 0,
|
||||
compilation_off INTEGER NOT NULL DEFAULT 0,
|
||||
compilation_effective INTEGER NOT NULL DEFAULT 0,
|
||||
|
||||
art_automatic TEXT,
|
||||
art_manual TEXT,
|
||||
|
||||
effective_albumartist TEXT,
|
||||
effective_originalyear INTEGER NOT NULL DEFAULT 0,
|
||||
|
||||
cue_path TEXT
|
||||
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS tidal_albums_songs (
|
||||
|
||||
title TEXT NOT NULL,
|
||||
album TEXT NOT NULL,
|
||||
artist TEXT NOT NULL,
|
||||
albumartist TEXT NOT NULL,
|
||||
track INTEGER NOT NULL DEFAULT -1,
|
||||
disc INTEGER NOT NULL DEFAULT -1,
|
||||
year INTEGER NOT NULL DEFAULT -1,
|
||||
originalyear INTEGER NOT NULL DEFAULT 0,
|
||||
genre TEXT NOT NULL,
|
||||
compilation INTEGER NOT NULL DEFAULT -1,
|
||||
composer TEXT NOT NULL,
|
||||
performer TEXT NOT NULL,
|
||||
grouping TEXT NOT NULL,
|
||||
comment TEXT NOT NULL,
|
||||
lyrics TEXT NOT NULL,
|
||||
|
||||
beginning INTEGER NOT NULL DEFAULT 0,
|
||||
length INTEGER NOT NULL DEFAULT 0,
|
||||
|
||||
bitrate INTEGER NOT NULL DEFAULT 0,
|
||||
samplerate INTEGER NOT NULL DEFAULT 0,
|
||||
bitdepth INTEGER NOT NULL DEFAULT 0,
|
||||
|
||||
source INTEGER NOT NULL DEFAULT 0,
|
||||
directory_id INTEGER NOT NULL,
|
||||
filename TEXT NOT NULL,
|
||||
filetype INTEGER NOT NULL DEFAULT 0,
|
||||
filesize INTEGER NOT NULL DEFAULT 0,
|
||||
mtime INTEGER NOT NULL DEFAULT 0,
|
||||
ctime INTEGER NOT NULL DEFAULT 0,
|
||||
unavailable INTEGER DEFAULT 0,
|
||||
|
||||
playcount INTEGER NOT NULL DEFAULT 0,
|
||||
skipcount INTEGER NOT NULL DEFAULT 0,
|
||||
lastplayed INTEGER NOT NULL DEFAULT 0,
|
||||
|
||||
compilation_detected INTEGER DEFAULT 0,
|
||||
compilation_on INTEGER NOT NULL DEFAULT 0,
|
||||
compilation_off INTEGER NOT NULL DEFAULT 0,
|
||||
compilation_effective INTEGER NOT NULL DEFAULT 0,
|
||||
|
||||
art_automatic TEXT,
|
||||
art_manual TEXT,
|
||||
|
||||
effective_albumartist TEXT,
|
||||
effective_originalyear INTEGER NOT NULL DEFAULT 0,
|
||||
|
||||
cue_path TEXT
|
||||
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS tidal_songs (
|
||||
|
||||
title TEXT NOT NULL,
|
||||
album TEXT NOT NULL,
|
||||
artist TEXT NOT NULL,
|
||||
albumartist TEXT NOT NULL,
|
||||
track INTEGER NOT NULL DEFAULT -1,
|
||||
disc INTEGER NOT NULL DEFAULT -1,
|
||||
year INTEGER NOT NULL DEFAULT -1,
|
||||
originalyear INTEGER NOT NULL DEFAULT 0,
|
||||
genre TEXT NOT NULL,
|
||||
compilation INTEGER NOT NULL DEFAULT -1,
|
||||
composer TEXT NOT NULL,
|
||||
performer TEXT NOT NULL,
|
||||
grouping TEXT NOT NULL,
|
||||
comment TEXT NOT NULL,
|
||||
lyrics TEXT NOT NULL,
|
||||
|
||||
beginning INTEGER NOT NULL DEFAULT 0,
|
||||
length INTEGER NOT NULL DEFAULT 0,
|
||||
|
||||
bitrate INTEGER NOT NULL DEFAULT 0,
|
||||
samplerate INTEGER NOT NULL DEFAULT 0,
|
||||
bitdepth INTEGER NOT NULL DEFAULT 0,
|
||||
|
||||
source INTEGER NOT NULL DEFAULT 0,
|
||||
directory_id INTEGER NOT NULL,
|
||||
filename TEXT NOT NULL,
|
||||
filetype INTEGER NOT NULL DEFAULT 0,
|
||||
filesize INTEGER NOT NULL DEFAULT 0,
|
||||
mtime INTEGER NOT NULL DEFAULT 0,
|
||||
ctime INTEGER NOT NULL DEFAULT 0,
|
||||
unavailable INTEGER DEFAULT 0,
|
||||
|
||||
playcount INTEGER NOT NULL DEFAULT 0,
|
||||
skipcount INTEGER NOT NULL DEFAULT 0,
|
||||
lastplayed INTEGER NOT NULL DEFAULT 0,
|
||||
|
||||
compilation_detected INTEGER DEFAULT 0,
|
||||
compilation_on INTEGER NOT NULL DEFAULT 0,
|
||||
compilation_off INTEGER NOT NULL DEFAULT 0,
|
||||
compilation_effective INTEGER NOT NULL DEFAULT 0,
|
||||
|
||||
art_automatic TEXT,
|
||||
art_manual TEXT,
|
||||
|
||||
effective_albumartist TEXT,
|
||||
effective_originalyear INTEGER NOT NULL DEFAULT 0,
|
||||
|
||||
cue_path TEXT
|
||||
|
||||
);
|
||||
|
||||
CREATE VIRTUAL TABLE IF NOT EXISTS tidal_artists_songs_fts USING fts3(
|
||||
|
||||
ftstitle,
|
||||
ftsalbum,
|
||||
ftsartist,
|
||||
ftsalbumartist,
|
||||
ftscomposer,
|
||||
ftsperformer,
|
||||
ftsgrouping,
|
||||
ftsgenre,
|
||||
ftscomment,
|
||||
tokenize=unicode
|
||||
|
||||
);
|
||||
|
||||
CREATE VIRTUAL TABLE IF NOT EXISTS tidal_albums_songs_fts USING fts3(
|
||||
|
||||
ftstitle,
|
||||
ftsalbum,
|
||||
ftsartist,
|
||||
ftsalbumartist,
|
||||
ftscomposer,
|
||||
ftsperformer,
|
||||
ftsgrouping,
|
||||
ftsgenre,
|
||||
ftscomment,
|
||||
tokenize=unicode
|
||||
|
||||
);
|
||||
|
||||
CREATE VIRTUAL TABLE IF NOT EXISTS tidal_songs_fts USING fts3(
|
||||
|
||||
ftstitle,
|
||||
ftsalbum,
|
||||
ftsartist,
|
||||
ftsalbumartist,
|
||||
ftscomposer,
|
||||
ftsperformer,
|
||||
ftsgrouping,
|
||||
ftsgenre,
|
||||
ftscomment,
|
||||
tokenize=unicode
|
||||
|
||||
);
|
||||
|
||||
UPDATE schema_version SET version=4;
|
||||
@@ -1,31 +0,0 @@
|
||||
ALTER TABLE songs ADD COLUMN artist_id INTEGER NOT NULL DEFAULT -1;
|
||||
|
||||
ALTER TABLE songs ADD COLUMN album_id INTEGER NOT NULL DEFAULT -1;
|
||||
|
||||
ALTER TABLE songs ADD COLUMN song_id INTEGER NOT NULL DEFAULT -1;
|
||||
|
||||
ALTER TABLE tidal_artists_songs ADD COLUMN artist_id INTEGER NOT NULL DEFAULT -1;
|
||||
|
||||
ALTER TABLE tidal_artists_songs ADD COLUMN album_id INTEGER NOT NULL DEFAULT -1;
|
||||
|
||||
ALTER TABLE tidal_artists_songs ADD COLUMN song_id INTEGER NOT NULL DEFAULT -1;
|
||||
|
||||
ALTER TABLE tidal_albums_songs ADD COLUMN artist_id INTEGER NOT NULL DEFAULT -1;
|
||||
|
||||
ALTER TABLE tidal_albums_songs ADD COLUMN album_id INTEGER NOT NULL DEFAULT -1;
|
||||
|
||||
ALTER TABLE tidal_albums_songs ADD COLUMN song_id INTEGER NOT NULL DEFAULT -1;
|
||||
|
||||
ALTER TABLE tidal_songs ADD COLUMN artist_id INTEGER NOT NULL DEFAULT -1;
|
||||
|
||||
ALTER TABLE tidal_songs ADD COLUMN album_id INTEGER NOT NULL DEFAULT -1;
|
||||
|
||||
ALTER TABLE tidal_songs ADD COLUMN song_id INTEGER NOT NULL DEFAULT -1;
|
||||
|
||||
ALTER TABLE playlist_items ADD COLUMN artist_id INTEGER NOT NULL DEFAULT -1;
|
||||
|
||||
ALTER TABLE playlist_items ADD COLUMN album_id INTEGER NOT NULL DEFAULT -1;
|
||||
|
||||
ALTER TABLE playlist_items ADD COLUMN song_id INTEGER NOT NULL DEFAULT -1;
|
||||
|
||||
UPDATE schema_version SET version=5;
|
||||
@@ -1,73 +0,0 @@
|
||||
CREATE TABLE IF NOT EXISTS subsonic_songs (
|
||||
|
||||
title TEXT NOT NULL,
|
||||
album TEXT NOT NULL,
|
||||
artist TEXT NOT NULL,
|
||||
albumartist TEXT NOT NULL,
|
||||
track INTEGER NOT NULL DEFAULT -1,
|
||||
disc INTEGER NOT NULL DEFAULT -1,
|
||||
year INTEGER NOT NULL DEFAULT -1,
|
||||
originalyear INTEGER NOT NULL DEFAULT 0,
|
||||
genre TEXT NOT NULL,
|
||||
compilation INTEGER NOT NULL DEFAULT -1,
|
||||
composer TEXT NOT NULL,
|
||||
performer TEXT NOT NULL,
|
||||
grouping TEXT NOT NULL,
|
||||
comment TEXT NOT NULL,
|
||||
lyrics TEXT NOT NULL,
|
||||
|
||||
artist_id INTEGER NOT NULL DEFAULT -1,
|
||||
album_id INTEGER NOT NULL DEFAULT -1,
|
||||
song_id INTEGER NOT NULL DEFAULT -1,
|
||||
|
||||
beginning INTEGER NOT NULL DEFAULT 0,
|
||||
length INTEGER NOT NULL DEFAULT 0,
|
||||
|
||||
bitrate INTEGER NOT NULL DEFAULT 0,
|
||||
samplerate INTEGER NOT NULL DEFAULT 0,
|
||||
bitdepth INTEGER NOT NULL DEFAULT 0,
|
||||
|
||||
source INTEGER NOT NULL DEFAULT 0,
|
||||
directory_id INTEGER NOT NULL,
|
||||
filename TEXT NOT NULL,
|
||||
filetype INTEGER NOT NULL DEFAULT 0,
|
||||
filesize INTEGER NOT NULL DEFAULT 0,
|
||||
mtime INTEGER NOT NULL DEFAULT 0,
|
||||
ctime INTEGER NOT NULL DEFAULT 0,
|
||||
unavailable INTEGER DEFAULT 0,
|
||||
|
||||
playcount INTEGER NOT NULL DEFAULT 0,
|
||||
skipcount INTEGER NOT NULL DEFAULT 0,
|
||||
lastplayed INTEGER NOT NULL DEFAULT 0,
|
||||
|
||||
compilation_detected INTEGER DEFAULT 0,
|
||||
compilation_on INTEGER NOT NULL DEFAULT 0,
|
||||
compilation_off INTEGER NOT NULL DEFAULT 0,
|
||||
compilation_effective INTEGER NOT NULL DEFAULT 0,
|
||||
|
||||
art_automatic TEXT,
|
||||
art_manual TEXT,
|
||||
|
||||
effective_albumartist TEXT,
|
||||
effective_originalyear INTEGER NOT NULL DEFAULT 0,
|
||||
|
||||
cue_path TEXT
|
||||
|
||||
);
|
||||
|
||||
CREATE VIRTUAL TABLE IF NOT EXISTS subsonic_songs_fts USING fts3(
|
||||
|
||||
ftstitle,
|
||||
ftsalbum,
|
||||
ftsartist,
|
||||
ftsalbumartist,
|
||||
ftscomposer,
|
||||
ftsperformer,
|
||||
ftsgrouping,
|
||||
ftsgenre,
|
||||
ftscomment,
|
||||
tokenize=unicode
|
||||
|
||||
);
|
||||
|
||||
UPDATE schema_version SET version=6;
|
||||
@@ -1,217 +0,0 @@
|
||||
CREATE TABLE IF NOT EXISTS qobuz_artists_songs (
|
||||
|
||||
title TEXT NOT NULL,
|
||||
album TEXT NOT NULL,
|
||||
artist TEXT NOT NULL,
|
||||
albumartist TEXT NOT NULL,
|
||||
track INTEGER NOT NULL DEFAULT -1,
|
||||
disc INTEGER NOT NULL DEFAULT -1,
|
||||
year INTEGER NOT NULL DEFAULT -1,
|
||||
originalyear INTEGER NOT NULL DEFAULT 0,
|
||||
genre TEXT NOT NULL,
|
||||
compilation INTEGER NOT NULL DEFAULT -1,
|
||||
composer TEXT NOT NULL,
|
||||
performer TEXT NOT NULL,
|
||||
grouping TEXT NOT NULL,
|
||||
comment TEXT NOT NULL,
|
||||
lyrics TEXT NOT NULL,
|
||||
|
||||
artist_id INTEGER NOT NULL DEFAULT -1,
|
||||
album_id INTEGER NOT NULL DEFAULT -1,
|
||||
song_id INTEGER NOT NULL DEFAULT -1,
|
||||
|
||||
beginning INTEGER NOT NULL DEFAULT 0,
|
||||
length INTEGER NOT NULL DEFAULT 0,
|
||||
|
||||
bitrate INTEGER NOT NULL DEFAULT 0,
|
||||
samplerate INTEGER NOT NULL DEFAULT 0,
|
||||
bitdepth INTEGER NOT NULL DEFAULT 0,
|
||||
|
||||
source INTEGER NOT NULL DEFAULT 0,
|
||||
directory_id INTEGER NOT NULL,
|
||||
filename TEXT NOT NULL,
|
||||
filetype INTEGER NOT NULL DEFAULT 0,
|
||||
filesize INTEGER NOT NULL DEFAULT 0,
|
||||
mtime INTEGER NOT NULL DEFAULT 0,
|
||||
ctime INTEGER NOT NULL DEFAULT 0,
|
||||
unavailable INTEGER DEFAULT 0,
|
||||
|
||||
playcount INTEGER NOT NULL DEFAULT 0,
|
||||
skipcount INTEGER NOT NULL DEFAULT 0,
|
||||
lastplayed INTEGER NOT NULL DEFAULT 0,
|
||||
|
||||
compilation_detected INTEGER DEFAULT 0,
|
||||
compilation_on INTEGER NOT NULL DEFAULT 0,
|
||||
compilation_off INTEGER NOT NULL DEFAULT 0,
|
||||
compilation_effective INTEGER NOT NULL DEFAULT 0,
|
||||
|
||||
art_automatic TEXT,
|
||||
art_manual TEXT,
|
||||
|
||||
effective_albumartist TEXT,
|
||||
effective_originalyear INTEGER NOT NULL DEFAULT 0,
|
||||
|
||||
cue_path TEXT
|
||||
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS qobuz_albums_songs (
|
||||
|
||||
title TEXT NOT NULL,
|
||||
album TEXT NOT NULL,
|
||||
artist TEXT NOT NULL,
|
||||
albumartist TEXT NOT NULL,
|
||||
track INTEGER NOT NULL DEFAULT -1,
|
||||
disc INTEGER NOT NULL DEFAULT -1,
|
||||
year INTEGER NOT NULL DEFAULT -1,
|
||||
originalyear INTEGER NOT NULL DEFAULT 0,
|
||||
genre TEXT NOT NULL,
|
||||
compilation INTEGER NOT NULL DEFAULT -1,
|
||||
composer TEXT NOT NULL,
|
||||
performer TEXT NOT NULL,
|
||||
grouping TEXT NOT NULL,
|
||||
comment TEXT NOT NULL,
|
||||
lyrics TEXT NOT NULL,
|
||||
|
||||
artist_id INTEGER NOT NULL DEFAULT -1,
|
||||
album_id INTEGER NOT NULL DEFAULT -1,
|
||||
song_id INTEGER NOT NULL DEFAULT -1,
|
||||
|
||||
beginning INTEGER NOT NULL DEFAULT 0,
|
||||
length INTEGER NOT NULL DEFAULT 0,
|
||||
|
||||
bitrate INTEGER NOT NULL DEFAULT 0,
|
||||
samplerate INTEGER NOT NULL DEFAULT 0,
|
||||
bitdepth INTEGER NOT NULL DEFAULT 0,
|
||||
|
||||
source INTEGER NOT NULL DEFAULT 0,
|
||||
directory_id INTEGER NOT NULL,
|
||||
filename TEXT NOT NULL,
|
||||
filetype INTEGER NOT NULL DEFAULT 0,
|
||||
filesize INTEGER NOT NULL DEFAULT 0,
|
||||
mtime INTEGER NOT NULL DEFAULT 0,
|
||||
ctime INTEGER NOT NULL DEFAULT 0,
|
||||
unavailable INTEGER DEFAULT 0,
|
||||
|
||||
playcount INTEGER NOT NULL DEFAULT 0,
|
||||
skipcount INTEGER NOT NULL DEFAULT 0,
|
||||
lastplayed INTEGER NOT NULL DEFAULT 0,
|
||||
|
||||
compilation_detected INTEGER DEFAULT 0,
|
||||
compilation_on INTEGER NOT NULL DEFAULT 0,
|
||||
compilation_off INTEGER NOT NULL DEFAULT 0,
|
||||
compilation_effective INTEGER NOT NULL DEFAULT 0,
|
||||
|
||||
art_automatic TEXT,
|
||||
art_manual TEXT,
|
||||
|
||||
effective_albumartist TEXT,
|
||||
effective_originalyear INTEGER NOT NULL DEFAULT 0,
|
||||
|
||||
cue_path TEXT
|
||||
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS qobuz_songs (
|
||||
|
||||
title TEXT NOT NULL,
|
||||
album TEXT NOT NULL,
|
||||
artist TEXT NOT NULL,
|
||||
albumartist TEXT NOT NULL,
|
||||
track INTEGER NOT NULL DEFAULT -1,
|
||||
disc INTEGER NOT NULL DEFAULT -1,
|
||||
year INTEGER NOT NULL DEFAULT -1,
|
||||
originalyear INTEGER NOT NULL DEFAULT 0,
|
||||
genre TEXT NOT NULL,
|
||||
compilation INTEGER NOT NULL DEFAULT -1,
|
||||
composer TEXT NOT NULL,
|
||||
performer TEXT NOT NULL,
|
||||
grouping TEXT NOT NULL,
|
||||
comment TEXT NOT NULL,
|
||||
lyrics TEXT NOT NULL,
|
||||
|
||||
artist_id INTEGER NOT NULL DEFAULT -1,
|
||||
album_id INTEGER NOT NULL DEFAULT -1,
|
||||
song_id INTEGER NOT NULL DEFAULT -1,
|
||||
|
||||
beginning INTEGER NOT NULL DEFAULT 0,
|
||||
length INTEGER NOT NULL DEFAULT 0,
|
||||
|
||||
bitrate INTEGER NOT NULL DEFAULT 0,
|
||||
samplerate INTEGER NOT NULL DEFAULT 0,
|
||||
bitdepth INTEGER NOT NULL DEFAULT 0,
|
||||
|
||||
source INTEGER NOT NULL DEFAULT 0,
|
||||
directory_id INTEGER NOT NULL,
|
||||
filename TEXT NOT NULL,
|
||||
filetype INTEGER NOT NULL DEFAULT 0,
|
||||
filesize INTEGER NOT NULL DEFAULT 0,
|
||||
mtime INTEGER NOT NULL DEFAULT 0,
|
||||
ctime INTEGER NOT NULL DEFAULT 0,
|
||||
unavailable INTEGER DEFAULT 0,
|
||||
|
||||
playcount INTEGER NOT NULL DEFAULT 0,
|
||||
skipcount INTEGER NOT NULL DEFAULT 0,
|
||||
lastplayed INTEGER NOT NULL DEFAULT 0,
|
||||
|
||||
compilation_detected INTEGER DEFAULT 0,
|
||||
compilation_on INTEGER NOT NULL DEFAULT 0,
|
||||
compilation_off INTEGER NOT NULL DEFAULT 0,
|
||||
compilation_effective INTEGER NOT NULL DEFAULT 0,
|
||||
|
||||
art_automatic TEXT,
|
||||
art_manual TEXT,
|
||||
|
||||
effective_albumartist TEXT,
|
||||
effective_originalyear INTEGER NOT NULL DEFAULT 0,
|
||||
|
||||
cue_path TEXT
|
||||
|
||||
);
|
||||
|
||||
CREATE VIRTUAL TABLE IF NOT EXISTS qobuz_artists_songs_fts USING fts3(
|
||||
|
||||
ftstitle,
|
||||
ftsalbum,
|
||||
ftsartist,
|
||||
ftsalbumartist,
|
||||
ftscomposer,
|
||||
ftsperformer,
|
||||
ftsgrouping,
|
||||
ftsgenre,
|
||||
ftscomment,
|
||||
tokenize=unicode
|
||||
|
||||
);
|
||||
|
||||
CREATE VIRTUAL TABLE IF NOT EXISTS qobuz_albums_songs_fts USING fts3(
|
||||
|
||||
ftstitle,
|
||||
ftsalbum,
|
||||
ftsartist,
|
||||
ftsalbumartist,
|
||||
ftscomposer,
|
||||
ftsperformer,
|
||||
ftsgrouping,
|
||||
ftsgenre,
|
||||
ftscomment,
|
||||
tokenize=unicode
|
||||
|
||||
);
|
||||
|
||||
CREATE VIRTUAL TABLE IF NOT EXISTS qobuz_songs_fts USING fts3(
|
||||
|
||||
ftstitle,
|
||||
ftsalbum,
|
||||
ftsartist,
|
||||
ftsalbumartist,
|
||||
ftscomposer,
|
||||
ftsperformer,
|
||||
ftsgrouping,
|
||||
ftsgenre,
|
||||
ftscomment,
|
||||
tokenize=unicode
|
||||
|
||||
);
|
||||
|
||||
UPDATE schema_version SET version=7;
|
||||
@@ -1,595 +0,0 @@
|
||||
ALTER TABLE songs RENAME TO songs_old;
|
||||
|
||||
ALTER TABLE playlist_items RENAME TO playlist_items_old;
|
||||
|
||||
ALTER TABLE tidal_artists_songs RENAME TO tidal_artists_songs_old;
|
||||
|
||||
ALTER TABLE tidal_albums_songs RENAME TO tidal_albums_songs_old;
|
||||
|
||||
ALTER TABLE tidal_songs RENAME TO tidal_songs_old;
|
||||
|
||||
ALTER TABLE qobuz_artists_songs RENAME TO qobuz_artists_songs_old;
|
||||
|
||||
ALTER TABLE qobuz_albums_songs RENAME TO qobuz_albums_songs_old;
|
||||
|
||||
ALTER TABLE qobuz_songs RENAME TO qobuz_songs_old;
|
||||
|
||||
ALTER TABLE subsonic_songs RENAME TO subsonic_songs_old;
|
||||
|
||||
DROP INDEX idx_filename;
|
||||
|
||||
CREATE TABLE songs (
|
||||
|
||||
title TEXT NOT NULL,
|
||||
album TEXT NOT NULL,
|
||||
artist TEXT NOT NULL,
|
||||
albumartist TEXT NOT NULL,
|
||||
track INTEGER NOT NULL DEFAULT -1,
|
||||
disc INTEGER NOT NULL DEFAULT -1,
|
||||
year INTEGER NOT NULL DEFAULT -1,
|
||||
originalyear INTEGER NOT NULL DEFAULT 0,
|
||||
genre TEXT NOT NULL,
|
||||
compilation INTEGER NOT NULL DEFAULT -1,
|
||||
composer TEXT NOT NULL,
|
||||
performer TEXT NOT NULL,
|
||||
grouping TEXT NOT NULL,
|
||||
comment TEXT NOT NULL,
|
||||
lyrics TEXT NOT NULL,
|
||||
|
||||
artist_id INTEGER NOT NULL DEFAULT -1,
|
||||
album_id TEXT NOT NULL,
|
||||
song_id INTEGER NOT NULL DEFAULT -1,
|
||||
|
||||
beginning INTEGER NOT NULL DEFAULT 0,
|
||||
length INTEGER NOT NULL DEFAULT 0,
|
||||
|
||||
bitrate INTEGER NOT NULL DEFAULT -1,
|
||||
samplerate INTEGER NOT NULL DEFAULT -1,
|
||||
bitdepth INTEGER NOT NULL DEFAULT -1,
|
||||
|
||||
source INTEGER NOT NULL DEFAULT 0,
|
||||
directory_id INTEGER NOT NULL DEFAULT -1,
|
||||
url TEXT NOT NULL,
|
||||
filetype INTEGER NOT NULL DEFAULT 0,
|
||||
filesize INTEGER NOT NULL DEFAULT -1,
|
||||
mtime INTEGER NOT NULL DEFAULT -1,
|
||||
ctime INTEGER NOT NULL DEFAULT -1,
|
||||
unavailable INTEGER DEFAULT 0,
|
||||
|
||||
playcount INTEGER NOT NULL DEFAULT 0,
|
||||
skipcount INTEGER NOT NULL DEFAULT 0,
|
||||
lastplayed INTEGER NOT NULL DEFAULT -1,
|
||||
|
||||
compilation_detected INTEGER DEFAULT 0,
|
||||
compilation_on INTEGER NOT NULL DEFAULT 0,
|
||||
compilation_off INTEGER NOT NULL DEFAULT 0,
|
||||
compilation_effective INTEGER NOT NULL DEFAULT 0,
|
||||
|
||||
art_automatic TEXT,
|
||||
art_manual TEXT,
|
||||
|
||||
effective_albumartist TEXT,
|
||||
effective_originalyear INTEGER NOT NULL DEFAULT 0,
|
||||
|
||||
cue_path TEXT
|
||||
|
||||
);
|
||||
|
||||
CREATE TABLE tidal_artists_songs (
|
||||
|
||||
title TEXT NOT NULL,
|
||||
album TEXT NOT NULL,
|
||||
artist TEXT NOT NULL,
|
||||
albumartist TEXT NOT NULL,
|
||||
track INTEGER NOT NULL DEFAULT -1,
|
||||
disc INTEGER NOT NULL DEFAULT -1,
|
||||
year INTEGER NOT NULL DEFAULT -1,
|
||||
originalyear INTEGER NOT NULL DEFAULT 0,
|
||||
genre TEXT NOT NULL,
|
||||
compilation INTEGER NOT NULL DEFAULT -1,
|
||||
composer TEXT NOT NULL,
|
||||
performer TEXT NOT NULL,
|
||||
grouping TEXT NOT NULL,
|
||||
comment TEXT NOT NULL,
|
||||
lyrics TEXT NOT NULL,
|
||||
|
||||
artist_id INTEGER NOT NULL DEFAULT -1,
|
||||
album_id TEXT NOT NULL,
|
||||
song_id INTEGER NOT NULL DEFAULT -1,
|
||||
|
||||
beginning INTEGER NOT NULL DEFAULT 0,
|
||||
length INTEGER NOT NULL DEFAULT 0,
|
||||
|
||||
bitrate INTEGER NOT NULL DEFAULT -1,
|
||||
samplerate INTEGER NOT NULL DEFAULT -1,
|
||||
bitdepth INTEGER NOT NULL DEFAULT -1,
|
||||
|
||||
source INTEGER NOT NULL DEFAULT 0,
|
||||
directory_id INTEGER NOT NULL DEFAULT -1,
|
||||
url TEXT NOT NULL,
|
||||
filetype INTEGER NOT NULL DEFAULT 0,
|
||||
filesize INTEGER NOT NULL DEFAULT -1,
|
||||
mtime INTEGER NOT NULL DEFAULT -1,
|
||||
ctime INTEGER NOT NULL DEFAULT -1,
|
||||
unavailable INTEGER DEFAULT 0,
|
||||
|
||||
playcount INTEGER NOT NULL DEFAULT 0,
|
||||
skipcount INTEGER NOT NULL DEFAULT 0,
|
||||
lastplayed INTEGER NOT NULL DEFAULT -1,
|
||||
|
||||
compilation_detected INTEGER DEFAULT 0,
|
||||
compilation_on INTEGER NOT NULL DEFAULT 0,
|
||||
compilation_off INTEGER NOT NULL DEFAULT 0,
|
||||
compilation_effective INTEGER NOT NULL DEFAULT 0,
|
||||
|
||||
art_automatic TEXT,
|
||||
art_manual TEXT,
|
||||
|
||||
effective_albumartist TEXT,
|
||||
effective_originalyear INTEGER NOT NULL DEFAULT 0,
|
||||
|
||||
cue_path TEXT
|
||||
|
||||
);
|
||||
|
||||
CREATE TABLE tidal_albums_songs (
|
||||
|
||||
title TEXT NOT NULL,
|
||||
album TEXT NOT NULL,
|
||||
artist TEXT NOT NULL,
|
||||
albumartist TEXT NOT NULL,
|
||||
track INTEGER NOT NULL DEFAULT -1,
|
||||
disc INTEGER NOT NULL DEFAULT -1,
|
||||
year INTEGER NOT NULL DEFAULT -1,
|
||||
originalyear INTEGER NOT NULL DEFAULT 0,
|
||||
genre TEXT NOT NULL,
|
||||
compilation INTEGER NOT NULL DEFAULT -1,
|
||||
composer TEXT NOT NULL,
|
||||
performer TEXT NOT NULL,
|
||||
grouping TEXT NOT NULL,
|
||||
comment TEXT NOT NULL,
|
||||
lyrics TEXT NOT NULL,
|
||||
|
||||
artist_id INTEGER NOT NULL DEFAULT -1,
|
||||
album_id TEXT NOT NULL,
|
||||
song_id INTEGER NOT NULL DEFAULT -1,
|
||||
|
||||
beginning INTEGER NOT NULL DEFAULT 0,
|
||||
length INTEGER NOT NULL DEFAULT 0,
|
||||
|
||||
bitrate INTEGER NOT NULL DEFAULT -1,
|
||||
samplerate INTEGER NOT NULL DEFAULT -1,
|
||||
bitdepth INTEGER NOT NULL DEFAULT -1,
|
||||
|
||||
source INTEGER NOT NULL DEFAULT 0,
|
||||
directory_id INTEGER NOT NULL DEFAULT -1,
|
||||
url TEXT NOT NULL,
|
||||
filetype INTEGER NOT NULL DEFAULT 0,
|
||||
filesize INTEGER NOT NULL DEFAULT -1,
|
||||
mtime INTEGER NOT NULL DEFAULT -1,
|
||||
ctime INTEGER NOT NULL DEFAULT -1,
|
||||
unavailable INTEGER DEFAULT 0,
|
||||
|
||||
playcount INTEGER NOT NULL DEFAULT 0,
|
||||
skipcount INTEGER NOT NULL DEFAULT 0,
|
||||
lastplayed INTEGER NOT NULL DEFAULT -1,
|
||||
|
||||
compilation_detected INTEGER DEFAULT 0,
|
||||
compilation_on INTEGER NOT NULL DEFAULT 0,
|
||||
compilation_off INTEGER NOT NULL DEFAULT 0,
|
||||
compilation_effective INTEGER NOT NULL DEFAULT 0,
|
||||
|
||||
art_automatic TEXT,
|
||||
art_manual TEXT,
|
||||
|
||||
effective_albumartist TEXT,
|
||||
effective_originalyear INTEGER NOT NULL DEFAULT 0,
|
||||
|
||||
cue_path TEXT
|
||||
|
||||
);
|
||||
|
||||
CREATE TABLE tidal_songs (
|
||||
|
||||
title TEXT NOT NULL,
|
||||
album TEXT NOT NULL,
|
||||
artist TEXT NOT NULL,
|
||||
albumartist TEXT NOT NULL,
|
||||
track INTEGER NOT NULL DEFAULT -1,
|
||||
disc INTEGER NOT NULL DEFAULT -1,
|
||||
year INTEGER NOT NULL DEFAULT -1,
|
||||
originalyear INTEGER NOT NULL DEFAULT 0,
|
||||
genre TEXT NOT NULL,
|
||||
compilation INTEGER NOT NULL DEFAULT -1,
|
||||
composer TEXT NOT NULL,
|
||||
performer TEXT NOT NULL,
|
||||
grouping TEXT NOT NULL,
|
||||
comment TEXT NOT NULL,
|
||||
lyrics TEXT NOT NULL,
|
||||
|
||||
artist_id INTEGER NOT NULL DEFAULT -1,
|
||||
album_id TEXT NOT NULL,
|
||||
song_id INTEGER NOT NULL DEFAULT -1,
|
||||
|
||||
beginning INTEGER NOT NULL DEFAULT 0,
|
||||
length INTEGER NOT NULL DEFAULT 0,
|
||||
|
||||
bitrate INTEGER NOT NULL DEFAULT -1,
|
||||
samplerate INTEGER NOT NULL DEFAULT -1,
|
||||
bitdepth INTEGER NOT NULL DEFAULT -1,
|
||||
|
||||
source INTEGER NOT NULL DEFAULT 0,
|
||||
directory_id INTEGER NOT NULL DEFAULT -1,
|
||||
url TEXT NOT NULL,
|
||||
filetype INTEGER NOT NULL DEFAULT 0,
|
||||
filesize INTEGER NOT NULL DEFAULT -1,
|
||||
mtime INTEGER NOT NULL DEFAULT -1,
|
||||
ctime INTEGER NOT NULL DEFAULT -1,
|
||||
unavailable INTEGER DEFAULT 0,
|
||||
|
||||
playcount INTEGER NOT NULL DEFAULT 0,
|
||||
skipcount INTEGER NOT NULL DEFAULT 0,
|
||||
lastplayed INTEGER NOT NULL DEFAULT -1,
|
||||
|
||||
compilation_detected INTEGER DEFAULT 0,
|
||||
compilation_on INTEGER NOT NULL DEFAULT 0,
|
||||
compilation_off INTEGER NOT NULL DEFAULT 0,
|
||||
compilation_effective INTEGER NOT NULL DEFAULT 0,
|
||||
|
||||
art_automatic TEXT,
|
||||
art_manual TEXT,
|
||||
|
||||
effective_albumartist TEXT,
|
||||
effective_originalyear INTEGER NOT NULL DEFAULT 0,
|
||||
|
||||
cue_path TEXT
|
||||
|
||||
);
|
||||
|
||||
CREATE TABLE subsonic_songs (
|
||||
|
||||
title TEXT NOT NULL,
|
||||
album TEXT NOT NULL,
|
||||
artist TEXT NOT NULL,
|
||||
albumartist TEXT NOT NULL,
|
||||
track INTEGER NOT NULL DEFAULT -1,
|
||||
disc INTEGER NOT NULL DEFAULT -1,
|
||||
year INTEGER NOT NULL DEFAULT -1,
|
||||
originalyear INTEGER NOT NULL DEFAULT 0,
|
||||
genre TEXT NOT NULL,
|
||||
compilation INTEGER NOT NULL DEFAULT -1,
|
||||
composer TEXT NOT NULL,
|
||||
performer TEXT NOT NULL,
|
||||
grouping TEXT NOT NULL,
|
||||
comment TEXT NOT NULL,
|
||||
lyrics TEXT NOT NULL,
|
||||
|
||||
artist_id INTEGER NOT NULL DEFAULT -1,
|
||||
album_id TEXT NOT NULL,
|
||||
song_id INTEGER NOT NULL DEFAULT -1,
|
||||
|
||||
beginning INTEGER NOT NULL DEFAULT 0,
|
||||
length INTEGER NOT NULL DEFAULT 0,
|
||||
|
||||
bitrate INTEGER NOT NULL DEFAULT -1,
|
||||
samplerate INTEGER NOT NULL DEFAULT -1,
|
||||
bitdepth INTEGER NOT NULL DEFAULT -1,
|
||||
|
||||
source INTEGER NOT NULL DEFAULT 0,
|
||||
directory_id INTEGER NOT NULL DEFAULT -1,
|
||||
url TEXT NOT NULL,
|
||||
filetype INTEGER NOT NULL DEFAULT 0,
|
||||
filesize INTEGER NOT NULL DEFAULT -1,
|
||||
mtime INTEGER NOT NULL DEFAULT -1,
|
||||
ctime INTEGER NOT NULL DEFAULT -1,
|
||||
unavailable INTEGER DEFAULT 0,
|
||||
|
||||
playcount INTEGER NOT NULL DEFAULT 0,
|
||||
skipcount INTEGER NOT NULL DEFAULT 0,
|
||||
lastplayed INTEGER NOT NULL DEFAULT -1,
|
||||
|
||||
compilation_detected INTEGER DEFAULT 0,
|
||||
compilation_on INTEGER NOT NULL DEFAULT 0,
|
||||
compilation_off INTEGER NOT NULL DEFAULT 0,
|
||||
compilation_effective INTEGER NOT NULL DEFAULT 0,
|
||||
|
||||
art_automatic TEXT,
|
||||
art_manual TEXT,
|
||||
|
||||
effective_albumartist TEXT,
|
||||
effective_originalyear INTEGER NOT NULL DEFAULT 0,
|
||||
|
||||
cue_path TEXT
|
||||
|
||||
);
|
||||
|
||||
CREATE TABLE qobuz_artists_songs (
|
||||
|
||||
title TEXT NOT NULL,
|
||||
album TEXT NOT NULL,
|
||||
artist TEXT NOT NULL,
|
||||
albumartist TEXT NOT NULL,
|
||||
track INTEGER NOT NULL DEFAULT -1,
|
||||
disc INTEGER NOT NULL DEFAULT -1,
|
||||
year INTEGER NOT NULL DEFAULT -1,
|
||||
originalyear INTEGER NOT NULL DEFAULT 0,
|
||||
genre TEXT NOT NULL,
|
||||
compilation INTEGER NOT NULL DEFAULT -1,
|
||||
composer TEXT NOT NULL,
|
||||
performer TEXT NOT NULL,
|
||||
grouping TEXT NOT NULL,
|
||||
comment TEXT NOT NULL,
|
||||
lyrics TEXT NOT NULL,
|
||||
|
||||
artist_id INTEGER NOT NULL DEFAULT -1,
|
||||
album_id TEXT NOT NULL,
|
||||
song_id INTEGER NOT NULL DEFAULT -1,
|
||||
|
||||
beginning INTEGER NOT NULL DEFAULT 0,
|
||||
length INTEGER NOT NULL DEFAULT 0,
|
||||
|
||||
bitrate INTEGER NOT NULL DEFAULT -1,
|
||||
samplerate INTEGER NOT NULL DEFAULT -1,
|
||||
bitdepth INTEGER NOT NULL DEFAULT -1,
|
||||
|
||||
source INTEGER NOT NULL DEFAULT 0,
|
||||
directory_id INTEGER NOT NULL DEFAULT -1,
|
||||
url TEXT NOT NULL,
|
||||
filetype INTEGER NOT NULL DEFAULT 0,
|
||||
filesize INTEGER NOT NULL DEFAULT -1,
|
||||
mtime INTEGER NOT NULL DEFAULT -1,
|
||||
ctime INTEGER NOT NULL DEFAULT -1,
|
||||
unavailable INTEGER DEFAULT 0,
|
||||
|
||||
playcount INTEGER NOT NULL DEFAULT 0,
|
||||
skipcount INTEGER NOT NULL DEFAULT 0,
|
||||
lastplayed INTEGER NOT NULL DEFAULT -1,
|
||||
|
||||
compilation_detected INTEGER DEFAULT 0,
|
||||
compilation_on INTEGER NOT NULL DEFAULT 0,
|
||||
compilation_off INTEGER NOT NULL DEFAULT 0,
|
||||
compilation_effective INTEGER NOT NULL DEFAULT 0,
|
||||
|
||||
art_automatic TEXT,
|
||||
art_manual TEXT,
|
||||
|
||||
effective_albumartist TEXT,
|
||||
effective_originalyear INTEGER NOT NULL DEFAULT 0,
|
||||
|
||||
cue_path TEXT
|
||||
|
||||
);
|
||||
|
||||
CREATE TABLE qobuz_albums_songs (
|
||||
|
||||
title TEXT NOT NULL,
|
||||
album TEXT NOT NULL,
|
||||
artist TEXT NOT NULL,
|
||||
albumartist TEXT NOT NULL,
|
||||
track INTEGER NOT NULL DEFAULT -1,
|
||||
disc INTEGER NOT NULL DEFAULT -1,
|
||||
year INTEGER NOT NULL DEFAULT -1,
|
||||
originalyear INTEGER NOT NULL DEFAULT 0,
|
||||
genre TEXT NOT NULL,
|
||||
compilation INTEGER NOT NULL DEFAULT -1,
|
||||
composer TEXT NOT NULL,
|
||||
performer TEXT NOT NULL,
|
||||
grouping TEXT NOT NULL,
|
||||
comment TEXT NOT NULL,
|
||||
lyrics TEXT NOT NULL,
|
||||
|
||||
artist_id INTEGER NOT NULL DEFAULT -1,
|
||||
album_id TEXT NOT NULL,
|
||||
song_id INTEGER NOT NULL DEFAULT -1,
|
||||
|
||||
beginning INTEGER NOT NULL DEFAULT 0,
|
||||
length INTEGER NOT NULL DEFAULT 0,
|
||||
|
||||
bitrate INTEGER NOT NULL DEFAULT -1,
|
||||
samplerate INTEGER NOT NULL DEFAULT -1,
|
||||
bitdepth INTEGER NOT NULL DEFAULT -1,
|
||||
|
||||
source INTEGER NOT NULL DEFAULT 0,
|
||||
directory_id INTEGER NOT NULL DEFAULT -1,
|
||||
url TEXT NOT NULL,
|
||||
filetype INTEGER NOT NULL DEFAULT 0,
|
||||
filesize INTEGER NOT NULL DEFAULT -1,
|
||||
mtime INTEGER NOT NULL DEFAULT -1,
|
||||
ctime INTEGER NOT NULL DEFAULT -1,
|
||||
unavailable INTEGER DEFAULT 0,
|
||||
|
||||
playcount INTEGER NOT NULL DEFAULT 0,
|
||||
skipcount INTEGER NOT NULL DEFAULT 0,
|
||||
lastplayed INTEGER NOT NULL DEFAULT -1,
|
||||
|
||||
compilation_detected INTEGER DEFAULT 0,
|
||||
compilation_on INTEGER NOT NULL DEFAULT 0,
|
||||
compilation_off INTEGER NOT NULL DEFAULT 0,
|
||||
compilation_effective INTEGER NOT NULL DEFAULT 0,
|
||||
|
||||
art_automatic TEXT,
|
||||
art_manual TEXT,
|
||||
|
||||
effective_albumartist TEXT,
|
||||
effective_originalyear INTEGER NOT NULL DEFAULT 0,
|
||||
|
||||
cue_path TEXT
|
||||
|
||||
);
|
||||
|
||||
CREATE TABLE qobuz_songs (
|
||||
|
||||
title TEXT NOT NULL,
|
||||
album TEXT NOT NULL,
|
||||
artist TEXT NOT NULL,
|
||||
albumartist TEXT NOT NULL,
|
||||
track INTEGER NOT NULL DEFAULT -1,
|
||||
disc INTEGER NOT NULL DEFAULT -1,
|
||||
year INTEGER NOT NULL DEFAULT -1,
|
||||
originalyear INTEGER NOT NULL DEFAULT 0,
|
||||
genre TEXT NOT NULL,
|
||||
compilation INTEGER NOT NULL DEFAULT -1,
|
||||
composer TEXT NOT NULL,
|
||||
performer TEXT NOT NULL,
|
||||
grouping TEXT NOT NULL,
|
||||
comment TEXT NOT NULL,
|
||||
lyrics TEXT NOT NULL,
|
||||
|
||||
artist_id INTEGER NOT NULL DEFAULT -1,
|
||||
album_id TEXT NOT NULL,
|
||||
song_id INTEGER NOT NULL DEFAULT -1,
|
||||
|
||||
beginning INTEGER NOT NULL DEFAULT 0,
|
||||
length INTEGER NOT NULL DEFAULT 0,
|
||||
|
||||
bitrate INTEGER NOT NULL DEFAULT -1,
|
||||
samplerate INTEGER NOT NULL DEFAULT -1,
|
||||
bitdepth INTEGER NOT NULL DEFAULT -1,
|
||||
|
||||
source INTEGER NOT NULL DEFAULT 0,
|
||||
directory_id INTEGER NOT NULL DEFAULT -1,
|
||||
url TEXT NOT NULL,
|
||||
filetype INTEGER NOT NULL DEFAULT 0,
|
||||
filesize INTEGER NOT NULL DEFAULT -1,
|
||||
mtime INTEGER NOT NULL DEFAULT -1,
|
||||
ctime INTEGER NOT NULL DEFAULT -1,
|
||||
unavailable INTEGER DEFAULT 0,
|
||||
|
||||
playcount INTEGER NOT NULL DEFAULT 0,
|
||||
skipcount INTEGER NOT NULL DEFAULT 0,
|
||||
lastplayed INTEGER NOT NULL DEFAULT -1,
|
||||
|
||||
compilation_detected INTEGER DEFAULT 0,
|
||||
compilation_on INTEGER NOT NULL DEFAULT 0,
|
||||
compilation_off INTEGER NOT NULL DEFAULT 0,
|
||||
compilation_effective INTEGER NOT NULL DEFAULT 0,
|
||||
|
||||
art_automatic TEXT,
|
||||
art_manual TEXT,
|
||||
|
||||
effective_albumartist TEXT,
|
||||
effective_originalyear INTEGER NOT NULL DEFAULT 0,
|
||||
|
||||
cue_path TEXT
|
||||
|
||||
);
|
||||
|
||||
CREATE TABLE playlist_items (
|
||||
|
||||
playlist INTEGER NOT NULL,
|
||||
type INTEGER NOT NULL DEFAULT 0,
|
||||
collection_id INTEGER,
|
||||
playlist_url TEXT,
|
||||
|
||||
title TEXT NOT NULL,
|
||||
album TEXT NOT NULL,
|
||||
artist TEXT NOT NULL,
|
||||
albumartist TEXT NOT NULL,
|
||||
track INTEGER NOT NULL DEFAULT -1,
|
||||
disc INTEGER NOT NULL DEFAULT -1,
|
||||
year INTEGER NOT NULL DEFAULT -1,
|
||||
originalyear INTEGER NOT NULL DEFAULT 0,
|
||||
genre TEXT NOT NULL,
|
||||
compilation INTEGER NOT NULL DEFAULT -1,
|
||||
composer TEXT NOT NULL,
|
||||
performer TEXT NOT NULL,
|
||||
grouping TEXT NOT NULL,
|
||||
comment TEXT NOT NULL,
|
||||
lyrics TEXT NOT NULL,
|
||||
|
||||
artist_id INTEGER NOT NULL DEFAULT -1,
|
||||
album_id TEXT NOT NULL,
|
||||
song_id INTEGER NOT NULL DEFAULT -1,
|
||||
|
||||
beginning INTEGER NOT NULL DEFAULT 0,
|
||||
length INTEGER NOT NULL DEFAULT 0,
|
||||
|
||||
bitrate INTEGER NOT NULL DEFAULT -1,
|
||||
samplerate INTEGER NOT NULL DEFAULT -1,
|
||||
bitdepth INTEGER NOT NULL DEFAULT -1,
|
||||
|
||||
source INTEGER NOT NULL DEFAULT 0,
|
||||
directory_id INTEGER,
|
||||
url TEXT,
|
||||
filetype INTEGER NOT NULL DEFAULT 0,
|
||||
filesize INTEGER,
|
||||
mtime INTEGER,
|
||||
ctime INTEGER,
|
||||
unavailable INTEGER DEFAULT 0,
|
||||
|
||||
playcount INTEGER NOT NULL DEFAULT 0,
|
||||
skipcount INTEGER NOT NULL DEFAULT 0,
|
||||
lastplayed INTEGER NOT NULL DEFAULT -1,
|
||||
|
||||
compilation_detected INTEGER DEFAULT 0,
|
||||
compilation_on INTEGER NOT NULL DEFAULT 0,
|
||||
compilation_off INTEGER NOT NULL DEFAULT 0,
|
||||
compilation_effective INTEGER NOT NULL DEFAULT 0,
|
||||
|
||||
art_automatic TEXT,
|
||||
art_manual TEXT,
|
||||
|
||||
effective_albumartist TEXT,
|
||||
effective_originalyear INTEGER NOT NULL DEFAULT 0,
|
||||
|
||||
cue_path TEXT
|
||||
|
||||
);
|
||||
|
||||
INSERT INTO songs (title, album, artist, albumartist, track, disc, year, originalyear, genre, compilation, composer, performer, grouping, comment, lyrics, artist_id, album_id, song_id, beginning, length, bitrate, samplerate, bitdepth, source, 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)
|
||||
SELECT title, album, artist, albumartist, track, disc, year, originalyear, genre, compilation, composer, performer, grouping, comment, lyrics, artist_id, album_id, song_id, beginning, length, bitrate, samplerate, bitdepth, source, directory_id, filename, 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
|
||||
FROM songs_old;
|
||||
|
||||
DROP TABLE songs_old;
|
||||
|
||||
INSERT INTO tidal_artists_songs (title, album, artist, albumartist, track, disc, year, originalyear, genre, compilation, composer, performer, grouping, comment, lyrics, artist_id, album_id, song_id, beginning, length, bitrate, samplerate, bitdepth, source, 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)
|
||||
SELECT title, album, artist, albumartist, track, disc, year, originalyear, genre, compilation, composer, performer, grouping, comment, lyrics, artist_id, album_id, song_id, beginning, length, bitrate, samplerate, bitdepth, source, directory_id, filename, 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
|
||||
FROM tidal_artists_songs_old;
|
||||
|
||||
DROP TABLE tidal_artists_songs_old;
|
||||
|
||||
INSERT INTO tidal_albums_songs (title, album, artist, albumartist, track, disc, year, originalyear, genre, compilation, composer, performer, grouping, comment, lyrics, artist_id, album_id, song_id, beginning, length, bitrate, samplerate, bitdepth, source, 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)
|
||||
SELECT title, album, artist, albumartist, track, disc, year, originalyear, genre, compilation, composer, performer, grouping, comment, lyrics, artist_id, album_id, song_id, beginning, length, bitrate, samplerate, bitdepth, source, directory_id, filename, 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
|
||||
FROM tidal_albums_songs_old;
|
||||
|
||||
DROP TABLE tidal_albums_songs_old;
|
||||
|
||||
INSERT INTO tidal_songs (title, album, artist, albumartist, track, disc, year, originalyear, genre, compilation, composer, performer, grouping, comment, lyrics, artist_id, album_id, song_id, beginning, length, bitrate, samplerate, bitdepth, source, 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)
|
||||
SELECT title, album, artist, albumartist, track, disc, year, originalyear, genre, compilation, composer, performer, grouping, comment, lyrics, artist_id, album_id, song_id, beginning, length, bitrate, samplerate, bitdepth, source, directory_id, filename, 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
|
||||
FROM tidal_songs_old;
|
||||
|
||||
DROP TABLE tidal_songs_old;
|
||||
|
||||
INSERT INTO qobuz_artists_songs (title, album, artist, albumartist, track, disc, year, originalyear, genre, compilation, composer, performer, grouping, comment, lyrics, artist_id, album_id, song_id, beginning, length, bitrate, samplerate, bitdepth, source, 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)
|
||||
SELECT title, album, artist, albumartist, track, disc, year, originalyear, genre, compilation, composer, performer, grouping, comment, lyrics, artist_id, album_id, song_id, beginning, length, bitrate, samplerate, bitdepth, source, directory_id, filename, 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
|
||||
FROM qobuz_artists_songs_old;
|
||||
|
||||
DROP TABLE qobuz_artists_songs_old;
|
||||
|
||||
INSERT INTO qobuz_albums_songs (title, album, artist, albumartist, track, disc, year, originalyear, genre, compilation, composer, performer, grouping, comment, lyrics, artist_id, album_id, song_id, beginning, length, bitrate, samplerate, bitdepth, source, 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)
|
||||
SELECT title, album, artist, albumartist, track, disc, year, originalyear, genre, compilation, composer, performer, grouping, comment, lyrics, artist_id, album_id, song_id, beginning, length, bitrate, samplerate, bitdepth, source, directory_id, filename, 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
|
||||
FROM qobuz_albums_songs_old;
|
||||
|
||||
DROP TABLE qobuz_albums_songs_old;
|
||||
|
||||
INSERT INTO qobuz_songs (title, album, artist, albumartist, track, disc, year, originalyear, genre, compilation, composer, performer, grouping, comment, lyrics, artist_id, album_id, song_id, beginning, length, bitrate, samplerate, bitdepth, source, 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)
|
||||
SELECT title, album, artist, albumartist, track, disc, year, originalyear, genre, compilation, composer, performer, grouping, comment, lyrics, artist_id, album_id, song_id, beginning, length, bitrate, samplerate, bitdepth, source, directory_id, filename, 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
|
||||
FROM qobuz_songs_old;
|
||||
|
||||
DROP TABLE qobuz_songs_old;
|
||||
|
||||
INSERT INTO subsonic_songs (title, album, artist, albumartist, track, disc, year, originalyear, genre, compilation, composer, performer, grouping, comment, lyrics, artist_id, album_id, song_id, beginning, length, bitrate, samplerate, bitdepth, source, 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)
|
||||
SELECT title, album, artist, albumartist, track, disc, year, originalyear, genre, compilation, composer, performer, grouping, comment, lyrics, artist_id, album_id, song_id, beginning, length, bitrate, samplerate, bitdepth, source, directory_id, filename, 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
|
||||
FROM subsonic_songs_old;
|
||||
|
||||
DROP TABLE subsonic_songs_old;
|
||||
|
||||
INSERT INTO playlist_items (playlist, type, collection_id, playlist_url, title, album, artist, albumartist, track, disc, year, originalyear, genre, compilation, composer, performer, grouping, comment, lyrics, artist_id, album_id, song_id, beginning, length, bitrate, samplerate, bitdepth, source, 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)
|
||||
SELECT playlist, type, collection_id, url, title, album, artist, albumartist, track, disc, year, originalyear, genre, compilation, composer, performer, grouping, comment, lyrics, artist_id, album_id, song_id, beginning, length, bitrate, samplerate, bitdepth, source, directory_id, filename, 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
|
||||
FROM playlist_items_old;
|
||||
|
||||
DROP TABLE playlist_items_old;
|
||||
|
||||
CREATE INDEX idx_url ON songs (url);
|
||||
|
||||
UPDATE schema_version SET version=8;
|
||||
@@ -1,41 +0,0 @@
|
||||
DROP TABLE %allsongstables_fts;
|
||||
|
||||
DROP TABLE playlist_items_fts_;
|
||||
|
||||
CREATE VIRTUAL TABLE %allsongstables_fts USING fts5(
|
||||
|
||||
ftstitle,
|
||||
ftsalbum,
|
||||
ftsartist,
|
||||
ftsalbumartist,
|
||||
ftscomposer,
|
||||
ftsperformer,
|
||||
ftsgrouping,
|
||||
ftsgenre,
|
||||
ftscomment,
|
||||
tokenize = "unicode61 remove_diacritics 1"
|
||||
|
||||
);
|
||||
|
||||
CREATE VIRTUAL TABLE playlist_items_fts_ USING fts5(
|
||||
|
||||
ftstitle,
|
||||
ftsalbum,
|
||||
ftsartist,
|
||||
ftsalbumartist,
|
||||
ftscomposer,
|
||||
ftsperformer,
|
||||
ftsgrouping,
|
||||
ftsgenre,
|
||||
ftscomment,
|
||||
tokenize = "unicode61 remove_diacritics 1"
|
||||
|
||||
);
|
||||
|
||||
INSERT INTO %allsongstables_fts (ROWID, ftstitle, ftsalbum, ftsartist, ftsalbumartist, ftscomposer, ftsperformer, ftsgrouping, ftsgenre, ftscomment)
|
||||
SELECT ROWID, title, album, artist, albumartist, composer, performer, grouping, genre, comment FROM %allsongstables;
|
||||
|
||||
INSERT INTO playlist_items_fts_ (ROWID, ftstitle, ftsalbum, ftsartist, ftsalbumartist, ftscomposer, ftsperformer, ftsgrouping, ftsgenre, ftscomment)
|
||||
SELECT ROWID, title, album, artist, albumartist, composer, performer, grouping, genre, comment FROM playlist_items;
|
||||
|
||||
UPDATE schema_version SET version=9;
|
||||
@@ -4,40 +4,40 @@ CREATE TABLE IF NOT EXISTS schema_version (
|
||||
|
||||
DELETE FROM schema_version;
|
||||
|
||||
INSERT INTO schema_version (version) VALUES (15);
|
||||
INSERT INTO schema_version (version) VALUES (16);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS directories (
|
||||
path TEXT NOT NULL DEFAULT '',
|
||||
path TEXT NOT NULL,
|
||||
subdirs INTEGER NOT NULL
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS subdirectories (
|
||||
directory_id INTEGER NOT NULL,
|
||||
path TEXT NOT NULL DEFAULT '',
|
||||
path TEXT NOT NULL,
|
||||
mtime INTEGER NOT NULL
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS songs (
|
||||
|
||||
title TEXT DEFAULT '',
|
||||
album TEXT DEFAULT '',
|
||||
artist TEXT DEFAULT '',
|
||||
albumartist TEXT DEFAULT '',
|
||||
title TEXT,
|
||||
album TEXT,
|
||||
artist TEXT,
|
||||
albumartist TEXT,
|
||||
track INTEGER NOT NULL DEFAULT -1,
|
||||
disc INTEGER NOT NULL DEFAULT -1,
|
||||
year INTEGER NOT NULL DEFAULT -1,
|
||||
originalyear INTEGER NOT NULL DEFAULT -1,
|
||||
genre TEXT DEFAULT '',
|
||||
genre TEXT,
|
||||
compilation INTEGER NOT NULL DEFAULT 0,
|
||||
composer TEXT DEFAULT '',
|
||||
performer TEXT DEFAULT '',
|
||||
grouping TEXT DEFAULT '',
|
||||
comment TEXT DEFAULT '',
|
||||
lyrics TEXT DEFAULT '',
|
||||
composer TEXT,
|
||||
performer TEXT,
|
||||
grouping TEXT,
|
||||
comment TEXT,
|
||||
lyrics TEXT,
|
||||
|
||||
artist_id TEXT DEFAULT '',
|
||||
album_id TEXT DEFAULT '',
|
||||
song_id TEXT DEFAULT '',
|
||||
artist_id TEXT,
|
||||
album_id TEXT,
|
||||
song_id TEXT,
|
||||
|
||||
beginning INTEGER NOT NULL DEFAULT 0,
|
||||
length INTEGER NOT NULL DEFAULT 0,
|
||||
@@ -48,14 +48,14 @@ CREATE TABLE IF NOT EXISTS songs (
|
||||
|
||||
source INTEGER NOT NULL DEFAULT 0,
|
||||
directory_id INTEGER NOT NULL DEFAULT -1,
|
||||
url TEXT NOT NULL DEFAULT '',
|
||||
url TEXT NOT NULL,
|
||||
filetype INTEGER NOT NULL DEFAULT 0,
|
||||
filesize INTEGER NOT NULL DEFAULT -1,
|
||||
mtime INTEGER NOT NULL DEFAULT -1,
|
||||
ctime INTEGER NOT NULL DEFAULT -1,
|
||||
unavailable INTEGER DEFAULT 0,
|
||||
|
||||
fingerprint TEXT DEFAULT '',
|
||||
fingerprint TEXT,
|
||||
|
||||
playcount INTEGER NOT NULL DEFAULT 0,
|
||||
skipcount INTEGER NOT NULL DEFAULT 0,
|
||||
@@ -67,39 +67,53 @@ CREATE TABLE IF NOT EXISTS songs (
|
||||
compilation_off INTEGER NOT NULL DEFAULT 0,
|
||||
compilation_effective INTEGER NOT NULL DEFAULT 0,
|
||||
|
||||
art_automatic TEXT DEFAULT '',
|
||||
art_manual TEXT DEFAULT '',
|
||||
art_automatic TEXT,
|
||||
art_manual TEXT,
|
||||
|
||||
effective_albumartist TEXT DEFAULT '',
|
||||
effective_albumartist TEXT,
|
||||
effective_originalyear INTEGER NOT NULL DEFAULT 0,
|
||||
|
||||
cue_path TEXT DEFAULT '',
|
||||
cue_path TEXT,
|
||||
|
||||
rating INTEGER DEFAULT -1
|
||||
rating INTEGER DEFAULT -1,
|
||||
|
||||
acoustid_id TEXT,
|
||||
acoustid_fingerprint TEXT,
|
||||
|
||||
musicbrainz_album_artist_id TEXT,
|
||||
musicbrainz_artist_id TEXT,
|
||||
musicbrainz_original_artist_id TEXT,
|
||||
musicbrainz_album_id TEXT,
|
||||
musicbrainz_original_album_id TEXT,
|
||||
musicbrainz_recording_id TEXT,
|
||||
musicbrainz_track_id TEXT,
|
||||
musicbrainz_disc_id TEXT,
|
||||
musicbrainz_release_group_id TEXT,
|
||||
musicbrainz_work_id TEXT
|
||||
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS subsonic_songs (
|
||||
|
||||
title TEXT DEFAULT '',
|
||||
album TEXT DEFAULT '',
|
||||
artist TEXT DEFAULT '',
|
||||
albumartist TEXT DEFAULT '',
|
||||
title TEXT,
|
||||
album TEXT,
|
||||
artist TEXT,
|
||||
albumartist TEXT,
|
||||
track INTEGER NOT NULL DEFAULT -1,
|
||||
disc INTEGER NOT NULL DEFAULT -1,
|
||||
year INTEGER NOT NULL DEFAULT -1,
|
||||
originalyear INTEGER NOT NULL DEFAULT -1,
|
||||
genre TEXT DEFAULT '',
|
||||
genre TEXT,
|
||||
compilation INTEGER NOT NULL DEFAULT 0,
|
||||
composer TEXT DEFAULT '',
|
||||
performer TEXT DEFAULT '',
|
||||
grouping TEXT DEFAULT '',
|
||||
comment TEXT DEFAULT '',
|
||||
lyrics TEXT DEFAULT '',
|
||||
composer TEXT,
|
||||
performer TEXT,
|
||||
grouping TEXT,
|
||||
comment TEXT,
|
||||
lyrics TEXT,
|
||||
|
||||
artist_id TEXT DEFAULT '',
|
||||
album_id TEXT DEFAULT '',
|
||||
song_id TEXT DEFAULT '',
|
||||
artist_id TEXT,
|
||||
album_id TEXT,
|
||||
song_id TEXT,
|
||||
|
||||
beginning INTEGER NOT NULL DEFAULT 0,
|
||||
length INTEGER NOT NULL DEFAULT 0,
|
||||
@@ -110,14 +124,14 @@ CREATE TABLE IF NOT EXISTS subsonic_songs (
|
||||
|
||||
source INTEGER NOT NULL DEFAULT 0,
|
||||
directory_id INTEGER NOT NULL DEFAULT -1,
|
||||
url TEXT NOT NULL DEFAULT '',
|
||||
url TEXT NOT NULL,
|
||||
filetype INTEGER NOT NULL DEFAULT 0,
|
||||
filesize INTEGER NOT NULL DEFAULT -1,
|
||||
mtime INTEGER NOT NULL DEFAULT -1,
|
||||
ctime INTEGER NOT NULL DEFAULT -1,
|
||||
unavailable INTEGER DEFAULT 0,
|
||||
|
||||
fingerprint TEXT DEFAULT '',
|
||||
fingerprint TEXT,
|
||||
|
||||
playcount INTEGER NOT NULL DEFAULT 0,
|
||||
skipcount INTEGER NOT NULL DEFAULT 0,
|
||||
@@ -129,39 +143,53 @@ CREATE TABLE IF NOT EXISTS subsonic_songs (
|
||||
compilation_off INTEGER NOT NULL DEFAULT 0,
|
||||
compilation_effective INTEGER NOT NULL DEFAULT 0,
|
||||
|
||||
art_automatic TEXT DEFAULT '',
|
||||
art_manual TEXT DEFAULT '',
|
||||
art_automatic TEXT,
|
||||
art_manual TEXT,
|
||||
|
||||
effective_albumartist TEXT DEFAULT '',
|
||||
effective_albumartist TEXT,
|
||||
effective_originalyear INTEGER NOT NULL DEFAULT 0,
|
||||
|
||||
cue_path TEXT DEFAULT '',
|
||||
cue_path TEXT,
|
||||
|
||||
rating INTEGER DEFAULT -1
|
||||
rating INTEGER DEFAULT -1,
|
||||
|
||||
acoustid_id TEXT,
|
||||
acoustid_fingerprint TEXT,
|
||||
|
||||
musicbrainz_album_artist_id TEXT,
|
||||
musicbrainz_artist_id TEXT,
|
||||
musicbrainz_original_artist_id TEXT,
|
||||
musicbrainz_album_id TEXT,
|
||||
musicbrainz_original_album_id TEXT,
|
||||
musicbrainz_recording_id TEXT,
|
||||
musicbrainz_track_id TEXT,
|
||||
musicbrainz_disc_id TEXT,
|
||||
musicbrainz_release_group_id TEXT,
|
||||
musicbrainz_work_id TEXT
|
||||
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS tidal_artists_songs (
|
||||
|
||||
title TEXT DEFAULT '',
|
||||
album TEXT DEFAULT '',
|
||||
artist TEXT DEFAULT '',
|
||||
albumartist TEXT DEFAULT '',
|
||||
title TEXT,
|
||||
album TEXT,
|
||||
artist TEXT,
|
||||
albumartist TEXT,
|
||||
track INTEGER NOT NULL DEFAULT -1,
|
||||
disc INTEGER NOT NULL DEFAULT -1,
|
||||
year INTEGER NOT NULL DEFAULT -1,
|
||||
originalyear INTEGER NOT NULL DEFAULT -1,
|
||||
genre TEXT DEFAULT '',
|
||||
genre TEXT,
|
||||
compilation INTEGER NOT NULL DEFAULT 0,
|
||||
composer TEXT DEFAULT '',
|
||||
performer TEXT DEFAULT '',
|
||||
grouping TEXT DEFAULT '',
|
||||
comment TEXT DEFAULT '',
|
||||
lyrics TEXT DEFAULT '',
|
||||
composer TEXT,
|
||||
performer TEXT,
|
||||
grouping TEXT,
|
||||
comment TEXT,
|
||||
lyrics TEXT,
|
||||
|
||||
artist_id TEXT DEFAULT '',
|
||||
album_id TEXT DEFAULT '',
|
||||
song_id TEXT DEFAULT '',
|
||||
artist_id TEXT,
|
||||
album_id TEXT,
|
||||
song_id TEXT,
|
||||
|
||||
beginning INTEGER NOT NULL DEFAULT 0,
|
||||
length INTEGER NOT NULL DEFAULT 0,
|
||||
@@ -172,14 +200,14 @@ CREATE TABLE IF NOT EXISTS tidal_artists_songs (
|
||||
|
||||
source INTEGER NOT NULL DEFAULT 0,
|
||||
directory_id INTEGER NOT NULL DEFAULT -1,
|
||||
url TEXT NOT NULL DEFAULT '',
|
||||
url TEXT NOT NULL,
|
||||
filetype INTEGER NOT NULL DEFAULT 0,
|
||||
filesize INTEGER NOT NULL DEFAULT -1,
|
||||
mtime INTEGER NOT NULL DEFAULT -1,
|
||||
ctime INTEGER NOT NULL DEFAULT -1,
|
||||
unavailable INTEGER DEFAULT 0,
|
||||
|
||||
fingerprint TEXT DEFAULT '',
|
||||
fingerprint TEXT,
|
||||
|
||||
playcount INTEGER NOT NULL DEFAULT 0,
|
||||
skipcount INTEGER NOT NULL DEFAULT 0,
|
||||
@@ -191,39 +219,53 @@ CREATE TABLE IF NOT EXISTS tidal_artists_songs (
|
||||
compilation_off INTEGER NOT NULL DEFAULT 0,
|
||||
compilation_effective INTEGER NOT NULL DEFAULT 0,
|
||||
|
||||
art_automatic TEXT DEFAULT '',
|
||||
art_manual TEXT DEFAULT '',
|
||||
art_automatic TEXT,
|
||||
art_manual TEXT,
|
||||
|
||||
effective_albumartist TEXT DEFAULT '',
|
||||
effective_albumartist TEXT,
|
||||
effective_originalyear INTEGER NOT NULL DEFAULT 0,
|
||||
|
||||
cue_path TEXT DEFAULT '',
|
||||
cue_path TEXT,
|
||||
|
||||
rating INTEGER DEFAULT -1
|
||||
rating INTEGER DEFAULT -1,
|
||||
|
||||
acoustid_id TEXT,
|
||||
acoustid_fingerprint TEXT,
|
||||
|
||||
musicbrainz_album_artist_id TEXT,
|
||||
musicbrainz_artist_id TEXT,
|
||||
musicbrainz_original_artist_id TEXT,
|
||||
musicbrainz_album_id TEXT,
|
||||
musicbrainz_original_album_id TEXT,
|
||||
musicbrainz_recording_id TEXT,
|
||||
musicbrainz_track_id TEXT,
|
||||
musicbrainz_disc_id TEXT,
|
||||
musicbrainz_release_group_id TEXT,
|
||||
musicbrainz_work_id TEXT
|
||||
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS tidal_albums_songs (
|
||||
|
||||
title TEXT DEFAULT '',
|
||||
album TEXT DEFAULT '',
|
||||
artist TEXT DEFAULT '',
|
||||
albumartist TEXT DEFAULT '',
|
||||
title TEXT,
|
||||
album TEXT,
|
||||
artist TEXT,
|
||||
albumartist TEXT,
|
||||
track INTEGER NOT NULL DEFAULT -1,
|
||||
disc INTEGER NOT NULL DEFAULT -1,
|
||||
year INTEGER NOT NULL DEFAULT -1,
|
||||
originalyear INTEGER NOT NULL DEFAULT -1,
|
||||
genre TEXT DEFAULT '',
|
||||
genre TEXT,
|
||||
compilation INTEGER NOT NULL DEFAULT 0,
|
||||
composer TEXT DEFAULT '',
|
||||
performer TEXT DEFAULT '',
|
||||
grouping TEXT DEFAULT '',
|
||||
comment TEXT DEFAULT '',
|
||||
lyrics TEXT DEFAULT '',
|
||||
composer TEXT,
|
||||
performer TEXT,
|
||||
grouping TEXT,
|
||||
comment TEXT,
|
||||
lyrics TEXT,
|
||||
|
||||
artist_id TEXT DEFAULT '',
|
||||
album_id TEXT DEFAULT '',
|
||||
song_id TEXT DEFAULT '',
|
||||
artist_id TEXT,
|
||||
album_id TEXT,
|
||||
song_id TEXT,
|
||||
|
||||
beginning INTEGER NOT NULL DEFAULT 0,
|
||||
length INTEGER NOT NULL DEFAULT 0,
|
||||
@@ -234,14 +276,14 @@ CREATE TABLE IF NOT EXISTS tidal_albums_songs (
|
||||
|
||||
source INTEGER NOT NULL DEFAULT 0,
|
||||
directory_id INTEGER NOT NULL DEFAULT -1,
|
||||
url TEXT NOT NULL DEFAULT '',
|
||||
url TEXT NOT NULL,
|
||||
filetype INTEGER NOT NULL DEFAULT 0,
|
||||
filesize INTEGER NOT NULL DEFAULT -1,
|
||||
mtime INTEGER NOT NULL DEFAULT -1,
|
||||
ctime INTEGER NOT NULL DEFAULT -1,
|
||||
unavailable INTEGER DEFAULT 0,
|
||||
|
||||
fingerprint TEXT DEFAULT '',
|
||||
fingerprint TEXT,
|
||||
|
||||
playcount INTEGER NOT NULL DEFAULT 0,
|
||||
skipcount INTEGER NOT NULL DEFAULT 0,
|
||||
@@ -253,39 +295,53 @@ CREATE TABLE IF NOT EXISTS tidal_albums_songs (
|
||||
compilation_off INTEGER NOT NULL DEFAULT 0,
|
||||
compilation_effective INTEGER NOT NULL DEFAULT 0,
|
||||
|
||||
art_automatic TEXT DEFAULT '',
|
||||
art_manual TEXT DEFAULT '',
|
||||
art_automatic TEXT,
|
||||
art_manual TEXT,
|
||||
|
||||
effective_albumartist TEXT DEFAULT '',
|
||||
effective_albumartist TEXT,
|
||||
effective_originalyear INTEGER NOT NULL DEFAULT 0,
|
||||
|
||||
cue_path TEXT DEFAULT '',
|
||||
cue_path TEXT,
|
||||
|
||||
rating INTEGER DEFAULT -1
|
||||
rating INTEGER DEFAULT -1,
|
||||
|
||||
acoustid_id TEXT,
|
||||
acoustid_fingerprint TEXT,
|
||||
|
||||
musicbrainz_album_artist_id TEXT,
|
||||
musicbrainz_artist_id TEXT,
|
||||
musicbrainz_original_artist_id TEXT,
|
||||
musicbrainz_album_id TEXT,
|
||||
musicbrainz_original_album_id TEXT,
|
||||
musicbrainz_recording_id TEXT,
|
||||
musicbrainz_track_id TEXT,
|
||||
musicbrainz_disc_id TEXT,
|
||||
musicbrainz_release_group_id TEXT,
|
||||
musicbrainz_work_id TEXT
|
||||
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS tidal_songs (
|
||||
|
||||
title TEXT DEFAULT '',
|
||||
album TEXT DEFAULT '',
|
||||
artist TEXT DEFAULT '',
|
||||
albumartist TEXT DEFAULT '',
|
||||
title TEXT,
|
||||
album TEXT,
|
||||
artist TEXT,
|
||||
albumartist TEXT,
|
||||
track INTEGER NOT NULL DEFAULT -1,
|
||||
disc INTEGER NOT NULL DEFAULT -1,
|
||||
year INTEGER NOT NULL DEFAULT -1,
|
||||
originalyear INTEGER NOT NULL DEFAULT -1,
|
||||
genre TEXT DEFAULT '',
|
||||
genre TEXT,
|
||||
compilation INTEGER NOT NULL DEFAULT 0,
|
||||
composer TEXT DEFAULT '',
|
||||
performer TEXT DEFAULT '',
|
||||
grouping TEXT DEFAULT '',
|
||||
comment TEXT DEFAULT '',
|
||||
lyrics TEXT DEFAULT '',
|
||||
composer TEXT,
|
||||
performer TEXT,
|
||||
grouping TEXT,
|
||||
comment TEXT,
|
||||
lyrics TEXT,
|
||||
|
||||
artist_id TEXT DEFAULT '',
|
||||
album_id TEXT DEFAULT '',
|
||||
song_id TEXT DEFAULT '',
|
||||
artist_id TEXT,
|
||||
album_id TEXT,
|
||||
song_id TEXT,
|
||||
|
||||
beginning INTEGER NOT NULL DEFAULT 0,
|
||||
length INTEGER NOT NULL DEFAULT 0,
|
||||
@@ -296,14 +352,14 @@ CREATE TABLE IF NOT EXISTS tidal_songs (
|
||||
|
||||
source INTEGER NOT NULL DEFAULT 0,
|
||||
directory_id INTEGER NOT NULL DEFAULT -1,
|
||||
url TEXT NOT NULL DEFAULT '',
|
||||
url TEXT NOT NULL,
|
||||
filetype INTEGER NOT NULL DEFAULT 0,
|
||||
filesize INTEGER NOT NULL DEFAULT -1,
|
||||
mtime INTEGER NOT NULL DEFAULT -1,
|
||||
ctime INTEGER NOT NULL DEFAULT -1,
|
||||
unavailable INTEGER DEFAULT 0,
|
||||
|
||||
fingerprint TEXT DEFAULT '',
|
||||
fingerprint TEXT,
|
||||
|
||||
playcount INTEGER NOT NULL DEFAULT 0,
|
||||
skipcount INTEGER NOT NULL DEFAULT 0,
|
||||
@@ -315,39 +371,53 @@ CREATE TABLE IF NOT EXISTS tidal_songs (
|
||||
compilation_off INTEGER NOT NULL DEFAULT 0,
|
||||
compilation_effective INTEGER NOT NULL DEFAULT 0,
|
||||
|
||||
art_automatic TEXT DEFAULT '',
|
||||
art_manual TEXT DEFAULT '',
|
||||
art_automatic TEXT,
|
||||
art_manual TEXT,
|
||||
|
||||
effective_albumartist TEXT DEFAULT '',
|
||||
effective_albumartist TEXT,
|
||||
effective_originalyear INTEGER NOT NULL DEFAULT 0,
|
||||
|
||||
cue_path TEXT DEFAULT '',
|
||||
cue_path TEXT,
|
||||
|
||||
rating INTEGER DEFAULT -1
|
||||
rating INTEGER DEFAULT -1,
|
||||
|
||||
acoustid_id TEXT,
|
||||
acoustid_fingerprint TEXT,
|
||||
|
||||
musicbrainz_album_artist_id TEXT,
|
||||
musicbrainz_artist_id TEXT,
|
||||
musicbrainz_original_artist_id TEXT,
|
||||
musicbrainz_album_id TEXT,
|
||||
musicbrainz_original_album_id TEXT,
|
||||
musicbrainz_recording_id TEXT,
|
||||
musicbrainz_track_id TEXT,
|
||||
musicbrainz_disc_id TEXT,
|
||||
musicbrainz_release_group_id TEXT,
|
||||
musicbrainz_work_id TEXT
|
||||
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS qobuz_artists_songs (
|
||||
|
||||
title TEXT DEFAULT '',
|
||||
album TEXT DEFAULT '',
|
||||
artist TEXT DEFAULT '',
|
||||
albumartist TEXT DEFAULT '',
|
||||
title TEXT,
|
||||
album TEXT,
|
||||
artist TEXT,
|
||||
albumartist TEXT,
|
||||
track INTEGER NOT NULL DEFAULT -1,
|
||||
disc INTEGER NOT NULL DEFAULT -1,
|
||||
year INTEGER NOT NULL DEFAULT -1,
|
||||
originalyear INTEGER NOT NULL DEFAULT -1,
|
||||
genre TEXT DEFAULT '',
|
||||
genre TEXT,
|
||||
compilation INTEGER NOT NULL DEFAULT 0,
|
||||
composer TEXT DEFAULT '',
|
||||
performer TEXT DEFAULT '',
|
||||
grouping TEXT DEFAULT '',
|
||||
comment TEXT DEFAULT '',
|
||||
lyrics TEXT DEFAULT '',
|
||||
composer TEXT,
|
||||
performer TEXT,
|
||||
grouping TEXT,
|
||||
comment TEXT,
|
||||
lyrics TEXT,
|
||||
|
||||
artist_id TEXT DEFAULT '',
|
||||
album_id TEXT DEFAULT '',
|
||||
song_id TEXT DEFAULT '',
|
||||
artist_id TEXT,
|
||||
album_id TEXT,
|
||||
song_id TEXT,
|
||||
|
||||
beginning INTEGER NOT NULL DEFAULT 0,
|
||||
length INTEGER NOT NULL DEFAULT 0,
|
||||
@@ -358,14 +428,14 @@ CREATE TABLE IF NOT EXISTS qobuz_artists_songs (
|
||||
|
||||
source INTEGER NOT NULL DEFAULT 0,
|
||||
directory_id INTEGER NOT NULL DEFAULT -1,
|
||||
url TEXT NOT NULL DEFAULT '',
|
||||
url TEXT NOT NULL,
|
||||
filetype INTEGER NOT NULL DEFAULT 0,
|
||||
filesize INTEGER NOT NULL DEFAULT -1,
|
||||
mtime INTEGER NOT NULL DEFAULT -1,
|
||||
ctime INTEGER NOT NULL DEFAULT -1,
|
||||
unavailable INTEGER DEFAULT 0,
|
||||
|
||||
fingerprint TEXT DEFAULT '',
|
||||
fingerprint TEXT,
|
||||
|
||||
playcount INTEGER NOT NULL DEFAULT 0,
|
||||
skipcount INTEGER NOT NULL DEFAULT 0,
|
||||
@@ -377,39 +447,53 @@ CREATE TABLE IF NOT EXISTS qobuz_artists_songs (
|
||||
compilation_off INTEGER NOT NULL DEFAULT 0,
|
||||
compilation_effective INTEGER NOT NULL DEFAULT 0,
|
||||
|
||||
art_automatic TEXT DEFAULT '',
|
||||
art_manual TEXT DEFAULT '',
|
||||
art_automatic TEXT,
|
||||
art_manual TEXT,
|
||||
|
||||
effective_albumartist TEXT DEFAULT '',
|
||||
effective_albumartist TEXT,
|
||||
effective_originalyear INTEGER NOT NULL DEFAULT 0,
|
||||
|
||||
cue_path TEXT DEFAULT '',
|
||||
cue_path TEXT,
|
||||
|
||||
rating INTEGER DEFAULT -1
|
||||
rating INTEGER DEFAULT -1,
|
||||
|
||||
acoustid_id TEXT,
|
||||
acoustid_fingerprint TEXT,
|
||||
|
||||
musicbrainz_album_artist_id TEXT,
|
||||
musicbrainz_artist_id TEXT,
|
||||
musicbrainz_original_artist_id TEXT,
|
||||
musicbrainz_album_id TEXT,
|
||||
musicbrainz_original_album_id TEXT,
|
||||
musicbrainz_recording_id TEXT,
|
||||
musicbrainz_track_id TEXT,
|
||||
musicbrainz_disc_id TEXT,
|
||||
musicbrainz_release_group_id TEXT,
|
||||
musicbrainz_work_id TEXT
|
||||
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS qobuz_albums_songs (
|
||||
|
||||
title TEXT DEFAULT '',
|
||||
album TEXT DEFAULT '',
|
||||
artist TEXT DEFAULT '',
|
||||
albumartist TEXT DEFAULT '',
|
||||
title TEXT,
|
||||
album TEXT,
|
||||
artist TEXT,
|
||||
albumartist TEXT,
|
||||
track INTEGER NOT NULL DEFAULT -1,
|
||||
disc INTEGER NOT NULL DEFAULT -1,
|
||||
year INTEGER NOT NULL DEFAULT -1,
|
||||
originalyear INTEGER NOT NULL DEFAULT -1,
|
||||
genre TEXT DEFAULT '',
|
||||
genre TEXT,
|
||||
compilation INTEGER NOT NULL DEFAULT 0,
|
||||
composer TEXT DEFAULT '',
|
||||
performer TEXT DEFAULT '',
|
||||
grouping TEXT DEFAULT '',
|
||||
comment TEXT DEFAULT '',
|
||||
lyrics TEXT DEFAULT '',
|
||||
composer TEXT,
|
||||
performer TEXT,
|
||||
grouping TEXT,
|
||||
comment TEXT,
|
||||
lyrics TEXT,
|
||||
|
||||
artist_id TEXT DEFAULT '',
|
||||
album_id TEXT DEFAULT '',
|
||||
song_id TEXT DEFAULT '',
|
||||
artist_id TEXT,
|
||||
album_id TEXT,
|
||||
song_id TEXT,
|
||||
|
||||
beginning INTEGER NOT NULL DEFAULT 0,
|
||||
length INTEGER NOT NULL DEFAULT 0,
|
||||
@@ -420,14 +504,14 @@ CREATE TABLE IF NOT EXISTS qobuz_albums_songs (
|
||||
|
||||
source INTEGER NOT NULL DEFAULT 0,
|
||||
directory_id INTEGER NOT NULL DEFAULT -1,
|
||||
url TEXT NOT NULL DEFAULT '',
|
||||
url TEXT NOT NULL,
|
||||
filetype INTEGER NOT NULL DEFAULT 0,
|
||||
filesize INTEGER NOT NULL DEFAULT -1,
|
||||
mtime INTEGER NOT NULL DEFAULT -1,
|
||||
ctime INTEGER NOT NULL DEFAULT -1,
|
||||
unavailable INTEGER DEFAULT 0,
|
||||
|
||||
fingerprint TEXT DEFAULT '',
|
||||
fingerprint TEXT,
|
||||
|
||||
playcount INTEGER NOT NULL DEFAULT 0,
|
||||
skipcount INTEGER NOT NULL DEFAULT 0,
|
||||
@@ -439,39 +523,53 @@ CREATE TABLE IF NOT EXISTS qobuz_albums_songs (
|
||||
compilation_off INTEGER NOT NULL DEFAULT 0,
|
||||
compilation_effective INTEGER NOT NULL DEFAULT 0,
|
||||
|
||||
art_automatic TEXT DEFAULT '',
|
||||
art_manual TEXT DEFAULT '',
|
||||
art_automatic TEXT,
|
||||
art_manual TEXT,
|
||||
|
||||
effective_albumartist TEXT DEFAULT '',
|
||||
effective_albumartist TEXT,
|
||||
effective_originalyear INTEGER NOT NULL DEFAULT 0,
|
||||
|
||||
cue_path TEXT DEFAULT '',
|
||||
cue_path TEXT,
|
||||
|
||||
rating INTEGER DEFAULT -1
|
||||
rating INTEGER DEFAULT -1,
|
||||
|
||||
acoustid_id TEXT,
|
||||
acoustid_fingerprint TEXT,
|
||||
|
||||
musicbrainz_album_artist_id TEXT,
|
||||
musicbrainz_artist_id TEXT,
|
||||
musicbrainz_original_artist_id TEXT,
|
||||
musicbrainz_album_id TEXT,
|
||||
musicbrainz_original_album_id TEXT,
|
||||
musicbrainz_recording_id TEXT,
|
||||
musicbrainz_track_id TEXT,
|
||||
musicbrainz_disc_id TEXT,
|
||||
musicbrainz_release_group_id TEXT,
|
||||
musicbrainz_work_id TEXT
|
||||
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS qobuz_songs (
|
||||
|
||||
title TEXT DEFAULT '',
|
||||
album TEXT DEFAULT '',
|
||||
artist TEXT DEFAULT '',
|
||||
albumartist TEXT DEFAULT '',
|
||||
title TEXT,
|
||||
album TEXT,
|
||||
artist TEXT,
|
||||
albumartist TEXT,
|
||||
track INTEGER NOT NULL DEFAULT -1,
|
||||
disc INTEGER NOT NULL DEFAULT -1,
|
||||
year INTEGER NOT NULL DEFAULT -1,
|
||||
originalyear INTEGER NOT NULL DEFAULT -1,
|
||||
genre TEXT DEFAULT '',
|
||||
genre TEXT,
|
||||
compilation INTEGER NOT NULL DEFAULT 0,
|
||||
composer TEXT DEFAULT '',
|
||||
performer TEXT DEFAULT '',
|
||||
grouping TEXT DEFAULT '',
|
||||
comment TEXT DEFAULT '',
|
||||
lyrics TEXT DEFAULT '',
|
||||
composer TEXT,
|
||||
performer TEXT,
|
||||
grouping TEXT,
|
||||
comment TEXT,
|
||||
lyrics TEXT,
|
||||
|
||||
artist_id TEXT DEFAULT '',
|
||||
album_id TEXT DEFAULT '',
|
||||
song_id TEXT DEFAULT '',
|
||||
artist_id TEXT,
|
||||
album_id TEXT,
|
||||
song_id TEXT,
|
||||
|
||||
beginning INTEGER NOT NULL DEFAULT 0,
|
||||
length INTEGER NOT NULL DEFAULT 0,
|
||||
@@ -482,14 +580,14 @@ CREATE TABLE IF NOT EXISTS qobuz_songs (
|
||||
|
||||
source INTEGER NOT NULL DEFAULT 0,
|
||||
directory_id INTEGER NOT NULL DEFAULT -1,
|
||||
url TEXT NOT NULL DEFAULT '',
|
||||
url TEXT NOT NULL,
|
||||
filetype INTEGER NOT NULL DEFAULT 0,
|
||||
filesize INTEGER NOT NULL DEFAULT -1,
|
||||
mtime INTEGER NOT NULL DEFAULT -1,
|
||||
ctime INTEGER NOT NULL DEFAULT -1,
|
||||
unavailable INTEGER DEFAULT 0,
|
||||
|
||||
fingerprint TEXT DEFAULT '',
|
||||
fingerprint TEXT,
|
||||
|
||||
playcount INTEGER NOT NULL DEFAULT 0,
|
||||
skipcount INTEGER NOT NULL DEFAULT 0,
|
||||
@@ -501,29 +599,43 @@ CREATE TABLE IF NOT EXISTS qobuz_songs (
|
||||
compilation_off INTEGER NOT NULL DEFAULT 0,
|
||||
compilation_effective INTEGER NOT NULL DEFAULT 0,
|
||||
|
||||
art_automatic TEXT DEFAULT '',
|
||||
art_manual TEXT DEFAULT '',
|
||||
art_automatic TEXT,
|
||||
art_manual TEXT,
|
||||
|
||||
effective_albumartist TEXT DEFAULT '',
|
||||
effective_albumartist TEXT,
|
||||
effective_originalyear INTEGER NOT NULL DEFAULT 0,
|
||||
|
||||
cue_path TEXT DEFAULT '',
|
||||
cue_path TEXT,
|
||||
|
||||
rating INTEGER DEFAULT -1
|
||||
rating INTEGER DEFAULT -1,
|
||||
|
||||
acoustid_id TEXT,
|
||||
acoustid_fingerprint TEXT,
|
||||
|
||||
musicbrainz_album_artist_id TEXT,
|
||||
musicbrainz_artist_id TEXT,
|
||||
musicbrainz_original_artist_id TEXT,
|
||||
musicbrainz_album_id TEXT,
|
||||
musicbrainz_original_album_id TEXT,
|
||||
musicbrainz_recording_id TEXT,
|
||||
musicbrainz_track_id TEXT,
|
||||
musicbrainz_disc_id TEXT,
|
||||
musicbrainz_release_group_id TEXT,
|
||||
musicbrainz_work_id TEXT
|
||||
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS playlists (
|
||||
|
||||
name TEXT NOT NULL DEFAULT '',
|
||||
name TEXT NOT NULL,
|
||||
last_played INTEGER NOT NULL DEFAULT -1,
|
||||
ui_order INTEGER NOT NULL DEFAULT 0,
|
||||
special_type TEXT DEFAULT '',
|
||||
ui_path TEXT DEFAULT '',
|
||||
special_type TEXT,
|
||||
ui_path TEXT,
|
||||
is_favorite INTEGER NOT NULL DEFAULT 0,
|
||||
|
||||
dynamic_playlist_type INTEGER,
|
||||
dynamic_playlist_backend TEXT DEFAULT '',
|
||||
dynamic_playlist_backend TEXT,
|
||||
dynamic_playlist_data BLOB
|
||||
|
||||
);
|
||||
@@ -533,27 +645,27 @@ CREATE TABLE IF NOT EXISTS playlist_items (
|
||||
playlist INTEGER NOT NULL,
|
||||
type INTEGER NOT NULL DEFAULT 0,
|
||||
collection_id INTEGER,
|
||||
playlist_url TEXT DEFAULT '',
|
||||
playlist_url TEXT,
|
||||
|
||||
title TEXT DEFAULT '',
|
||||
album TEXT DEFAULT '',
|
||||
artist TEXT DEFAULT '',
|
||||
albumartist TEXT DEFAULT '',
|
||||
title TEXT,
|
||||
album TEXT,
|
||||
artist TEXT,
|
||||
albumartist TEXT,
|
||||
track INTEGER,
|
||||
disc INTEGER,
|
||||
year INTEGER,
|
||||
originalyear INTEGER,
|
||||
genre TEXT DEFAULT '',
|
||||
genre TEXT,
|
||||
compilation INTEGER DEFAULT 0,
|
||||
composer TEXT DEFAULT '',
|
||||
performer TEXT DEFAULT '',
|
||||
grouping TEXT DEFAULT '',
|
||||
comment TEXT DEFAULT '',
|
||||
lyrics TEXT DEFAULT '',
|
||||
composer TEXT,
|
||||
performer TEXT,
|
||||
grouping TEXT,
|
||||
comment TEXT,
|
||||
lyrics TEXT,
|
||||
|
||||
artist_id TEXT DEFAULT '',
|
||||
album_id TEXT DEFAULT '',
|
||||
song_id TEXT DEFAULT '',
|
||||
artist_id TEXT,
|
||||
album_id TEXT,
|
||||
song_id TEXT,
|
||||
|
||||
beginning INTEGER,
|
||||
length INTEGER,
|
||||
@@ -564,14 +676,14 @@ CREATE TABLE IF NOT EXISTS playlist_items (
|
||||
|
||||
source INTEGER,
|
||||
directory_id INTEGER,
|
||||
url TEXT DEFAULT '',
|
||||
url TEXT NOT NULL,
|
||||
filetype INTEGER,
|
||||
filesize INTEGER,
|
||||
mtime INTEGER,
|
||||
ctime INTEGER,
|
||||
unavailable INTEGER DEFAULT 0,
|
||||
|
||||
fingerprint TEXT DEFAULT '',
|
||||
fingerprint TEXT,
|
||||
|
||||
playcount INTEGER DEFAULT 0,
|
||||
skipcount INTEGER DEFAULT 0,
|
||||
@@ -583,23 +695,37 @@ CREATE TABLE IF NOT EXISTS playlist_items (
|
||||
compilation_off INTEGER DEFAULT 0,
|
||||
compilation_effective INTEGER DEFAULT 0,
|
||||
|
||||
art_automatic TEXT DEFAULT '',
|
||||
art_manual TEXT DEFAULT '',
|
||||
art_automatic TEXT,
|
||||
art_manual TEXT,
|
||||
|
||||
effective_albumartist TEXT DEFAULT '',
|
||||
effective_albumartist TEXT,
|
||||
effective_originalyear INTEGER,
|
||||
|
||||
cue_path TEXT DEFAULT '',
|
||||
cue_path TEXT,
|
||||
|
||||
rating INTEGER DEFAULT -1
|
||||
rating INTEGER DEFAULT -1,
|
||||
|
||||
acoustid_id TEXT,
|
||||
acoustid_fingerprint TEXT,
|
||||
|
||||
musicbrainz_album_artist_id TEXT,
|
||||
musicbrainz_artist_id TEXT,
|
||||
musicbrainz_original_artist_id TEXT,
|
||||
musicbrainz_album_id TEXT,
|
||||
musicbrainz_original_album_id TEXT,
|
||||
musicbrainz_recording_id TEXT,
|
||||
musicbrainz_track_id TEXT,
|
||||
musicbrainz_disc_id TEXT,
|
||||
musicbrainz_release_group_id TEXT,
|
||||
musicbrainz_work_id TEXT
|
||||
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS devices (
|
||||
unique_id TEXT NOT NULL DEFAULT '',
|
||||
friendly_name TEXT DEFAULT '',
|
||||
unique_id TEXT NOT NULL,
|
||||
friendly_name TEXT,
|
||||
size INTEGER,
|
||||
icon TEXT DEFAULT '',
|
||||
icon TEXT,
|
||||
schema_version INTEGER NOT NULL DEFAULT 0,
|
||||
transcode_mode NOT NULL DEFAULT 3,
|
||||
transcode_format NOT NULL DEFAULT 5
|
||||
@@ -607,9 +733,9 @@ CREATE TABLE IF NOT EXISTS devices (
|
||||
|
||||
CREATE TABLE IF NOT EXISTS radio_channels (
|
||||
source INTEGER NOT NULL DEFAULT 0,
|
||||
name TEXT DEFAULT '',
|
||||
url TEXT DEFAULT '',
|
||||
thumbnail_url TEXT DEFAULT ''
|
||||
name TEXT,
|
||||
url TEXT NOT NULL,
|
||||
thumbnail_url TEXT
|
||||
);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_url ON songs (url);
|
||||
|
||||
@@ -19,47 +19,6 @@
|
||||
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 {
|
||||
background-color: %palette-base;
|
||||
}
|
||||
@@ -71,3 +30,45 @@ macos QMenu {
|
||||
#context-layout-scrollarea {
|
||||
background-color: %palette-base;
|
||||
}
|
||||
|
||||
#context-scrollarea {
|
||||
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;
|
||||
}
|
||||
|
||||
22
debian/CMakeLists.txt
vendored
Normal file
@@ -0,0 +1,22 @@
|
||||
if(LSB_RELEASE_EXEC AND DPKG_BUILDPACKAGE)
|
||||
|
||||
execute_process(COMMAND env LC_ALL=C date "+%a, %-d %b %Y %H:%M:%S %z" OUTPUT_VARIABLE DEB_DATE OUTPUT_STRIP_TRAILING_WHITESPACE)
|
||||
execute_process(COMMAND /bin/sh "-c" "${LSB_RELEASE_EXEC} -cs" OUTPUT_VARIABLE DEB_CODENAME OUTPUT_STRIP_TRAILING_WHITESPACE)
|
||||
|
||||
if(DEB_CODENAME AND DEB_DATE)
|
||||
|
||||
if(BUILD_WITH_QT5)
|
||||
set(DEBIAN_BUILD_DEPENDS_QT_PACKAGES qtbase5-dev,qtbase5-dev-tools,qttools5-dev,qttools5-dev-tools,libqt5x11extras5-dev)
|
||||
set(DEBIAN_DEPENDS_QT_PACKAGES libqt5sql5-sqlite)
|
||||
endif()
|
||||
if(BUILD_WITH_QT6)
|
||||
set(DEBIAN_BUILD_DEPENDS_QT_PACKAGES qt6-base-dev,qt6-base-dev-tools,qt6-tools-dev,qt6-tools-dev-tools,qt6-l10n-tools)
|
||||
set(DEBIAN_DEPENDS_QT_PACKAGES libqt6sql6-sqlite,qt6-qpa-plugins)
|
||||
endif()
|
||||
|
||||
configure_file(${CMAKE_CURRENT_SOURCE_DIR}/control.in ${CMAKE_CURRENT_SOURCE_DIR}/control @ONLY)
|
||||
configure_file(${CMAKE_CURRENT_SOURCE_DIR}/changelog.in ${CMAKE_CURRENT_SOURCE_DIR}/changelog)
|
||||
|
||||
endif()
|
||||
|
||||
endif()
|
||||
22
debian/control → debian/control.in
vendored
@@ -6,6 +6,7 @@ Build-Depends: debhelper (>= 11),
|
||||
make,
|
||||
cmake,
|
||||
gcc,
|
||||
g++,
|
||||
protobuf-compiler,
|
||||
libglib2.0-dev,
|
||||
libdbus-1-dev,
|
||||
@@ -16,11 +17,8 @@ Build-Depends: debhelper (>= 11),
|
||||
libasound2-dev,
|
||||
libpulse-dev,
|
||||
libtag1-dev,
|
||||
qtbase5-dev,
|
||||
qtbase5-private-dev,
|
||||
qtbase5-dev-tools,
|
||||
qttools5-dev,
|
||||
libqt5x11extras5-dev,
|
||||
libicu-dev,
|
||||
@DEBIAN_BUILD_DEPENDS_QT_PACKAGES@,
|
||||
libgstreamer1.0-dev,
|
||||
libgstreamer-plugins-base1.0-dev,
|
||||
libcdio-dev,
|
||||
@@ -28,20 +26,19 @@ Build-Depends: debhelper (>= 11),
|
||||
libmtp-dev,
|
||||
libchromaprint-dev,
|
||||
libfftw3-dev
|
||||
Standards-Version: 4.2.1
|
||||
Standards-Version: 4.6.1
|
||||
|
||||
Package: strawberry
|
||||
Architecture: any
|
||||
Depends: ${shlibs:Depends},
|
||||
${misc:Depends},
|
||||
libsqlite3-0,
|
||||
libqt5sql5-sqlite,
|
||||
@DEBIAN_DEPENDS_QT_PACKAGES@,
|
||||
gstreamer1.0-plugins-base,
|
||||
gstreamer1.0-plugins-good,
|
||||
gstreamer1.0-alsa,
|
||||
gstreamer1.0-pulseaudio
|
||||
Homepage: http://www.strawberrymusicplayer.org/
|
||||
Description: Audio player and music collection organizer
|
||||
Description: music player and music collection organizer
|
||||
Strawberry is a music player aimed at music collectors and audiophiles.
|
||||
.
|
||||
Features:
|
||||
@@ -49,14 +46,13 @@ Description: Audio player and music collection organizer
|
||||
- Supports WAV, FLAC, WavPack, Ogg Vorbis, Speex, MPC, TrueAudio, AIFF, MP4, MP3 and ASF
|
||||
- Audio CD playback
|
||||
- Native desktop notifications
|
||||
- Playlist management
|
||||
- Playlists in multiple formats
|
||||
- Playlist management and playlists in multiple formats
|
||||
- Smart and dynamic playlists
|
||||
- Advanced audio output and device configuration for bit-perfect playback on Linux
|
||||
- Edit tags on audio files
|
||||
- Automatically retrieve tags from MusicBrainz
|
||||
- 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
|
||||
- Support for multiple backends
|
||||
- Song lyrics from Lyrics.com, AudD, Genius, Musixmatch, ChartLyrics, lyrics.ovh and lololyrics.com
|
||||
- Audio analyzer
|
||||
- Audio equalizer
|
||||
- Transfer music to mass-storage USB players, MTP compatible devices and iPod Nano/Classic
|
||||
197
debian/copyright
vendored
@@ -1,26 +1,26 @@
|
||||
Format: https://www.debian.org/doc/packaging-manuals/copyright-format/1.0/
|
||||
Upstream-Name: strawberry
|
||||
Upstream-Contact: Jonas Kvinge <jonas@jkvinge.net>
|
||||
Source: https://www.strawberrymusicplayer.org/
|
||||
Source: https://github.com/strawberrymusicplayer/strawberry
|
||||
|
||||
Files: *
|
||||
Copyright: 2010-2015, David Sansome <me@davidsansome.com>
|
||||
2012-2014, 2017-2023 Jonas Kvinge <jonas@jkvinge.net>
|
||||
License: GPL-3+
|
||||
|
||||
Files: src/core/timeconstants.h
|
||||
Files: src/utilities/timeconstants.h
|
||||
ext/libstrawberry-common/core/logging.cpp
|
||||
ext/libstrawberry-common/core/logging.h
|
||||
ext/libstrawberry-common/core/messagehandler.cpp
|
||||
ext/libstrawberry-common/core/messagehandler.h
|
||||
Copyright: 2011, 2012, David Sansome <me@davidsansome.com>
|
||||
2018-2022, Jonas Kvinge <jonas@jkvinge.net>
|
||||
License: Apache-2.0
|
||||
|
||||
Files: src/core/main.h
|
||||
src/core/iconloader.cpp
|
||||
src/core/iconloader.h
|
||||
src/core/iconmapper.h
|
||||
src/config.h.in
|
||||
src/version.h.in
|
||||
src/context/contextview.cpp
|
||||
src/context/contextview.h
|
||||
src/context/contextalbum.cpp
|
||||
@@ -29,6 +29,8 @@ Files: src/core/main.h
|
||||
src/engine/enginetype.h
|
||||
src/engine/alsadevicefinder.cpp
|
||||
src/engine/alsadevicefinder.h
|
||||
src/engine/alsapcmdevicefinder.cpp
|
||||
src/engine/alsapcmdevicefinder.h
|
||||
src/engine/mmdevicefinder.cpp
|
||||
src/engine/mmdevicefinder.h
|
||||
src/engine/devicefinder.cpp
|
||||
@@ -69,11 +71,17 @@ Files: src/core/main.h
|
||||
src/covermanager/spotifycoverprovider.h
|
||||
src/covermanager/musixmatchcoverprovider.cpp
|
||||
src/covermanager/musixmatchcoverprovider.h
|
||||
src/globalshortcuts/globalshortcutsbackend-system.cpp
|
||||
src/globalshortcuts/globalshortcutsbackend-system.h
|
||||
src/globalshortcuts/globalshortcutsbackend-kde.cpp
|
||||
src/globalshortcuts/globalshortcutsbackend-kde.h
|
||||
src/globalshortcuts/globalshortcutsbackend-mate.cpp
|
||||
src/globalshortcuts/globalshortcutsbackend-mate.h
|
||||
src/globalshortcuts/globalshortcutsbackend-x11.cpp
|
||||
src/globalshortcuts/globalshortcutsbackend-x11.h
|
||||
src/globalshortcuts/globalshortcutsbackend-win.cpp
|
||||
src/globalshortcuts/globalshortcutsbackend-win.h
|
||||
src/globalshortcuts/globalshortcut.cpp
|
||||
src/globalshortcuts/globalshortcut.h
|
||||
src/globalshortcuts/globalshortcut-X11.cpp
|
||||
src/globalshortcuts/globalshortcut-x11.cpp
|
||||
src/globalshortcuts/globalshortcut-win.cpp
|
||||
src/globalshortcuts/keymapper_x11.h
|
||||
src/globalshortcuts/keymapper_win.h
|
||||
@@ -81,132 +89,21 @@ Files: src/core/main.h
|
||||
src/scrobbler/*
|
||||
src/subsonic/*
|
||||
src/tidal/*
|
||||
src/qobuz/*
|
||||
src/radios/*
|
||||
src/transcoder/transcoderoptionswavpack.cpp
|
||||
src/transcoder/transcoderoptionswavpack.h
|
||||
Copyright: 2012-2014, 2017-2020, Jonas Kvinge <jonas@jkvinge.net>
|
||||
License: GPL-3+
|
||||
|
||||
Files: src/core/main.cpp
|
||||
src/core/mainwindow.cpp
|
||||
src/core/mainwindow.h
|
||||
src/core/player.cpp
|
||||
src/core/player.h
|
||||
src/core/song.cpp
|
||||
src/core/song.h
|
||||
src/core/songloader.cpp
|
||||
src/core/songloader.h
|
||||
src/core/urlhandler.cpp
|
||||
src/core/urlhandler.h
|
||||
src/core/utilities.cpp
|
||||
src/core/utilities.h
|
||||
src/core/networkaccessmanager.cpp
|
||||
src/core/networkaccessmanager.h
|
||||
src/core/threadsafenetworkdiskcache.cpp
|
||||
src/core/threadsafenetworkdiskcache.h
|
||||
src/core/filesystemmusicstorage.cpp
|
||||
src/core/filesystemmusicstorage.h
|
||||
src/core/stylesheetloader.cpp
|
||||
src/core/stylesheetloader.h
|
||||
src/engine/gstenginepipeline.cpp
|
||||
src/engine/gstenginepipeline.h
|
||||
src/engine/vlcengine.cpp
|
||||
src/engine/vlcengine.h
|
||||
src/collection/collectionwatcher.cpp
|
||||
src/collection/collectionwatcher.h
|
||||
src/collection/collectionbackend.cpp
|
||||
src/collection/collectionbackend.h
|
||||
src/collection/collectionmodel.cpp
|
||||
src/collection/collectionmodel.h
|
||||
src/context/contextalbumsmodel.cpp
|
||||
src/context/contextalbumsview.cpp
|
||||
src/context/contextalbumsmodel.h
|
||||
src/context/contextalbumsview.h
|
||||
src/widgets/playingwidget.cpp
|
||||
src/widgets/playingwidget.h
|
||||
src/osd/osdbase.cpp
|
||||
src/osd/osdbase.h
|
||||
src/osd/osdpretty.cpp
|
||||
src/osd/osdpretty.h
|
||||
src/osd/osddbus.cpp
|
||||
src/osd/osddbus.h
|
||||
src/osd/osdmac.cpp
|
||||
src/osd/osdmac.h
|
||||
src/dialogs/about.cpp
|
||||
src/dialogs/about.h
|
||||
src/playlist/playlist.cpp
|
||||
src/playlist/playlist.h
|
||||
src/playlist/playlistitem.cpp
|
||||
src/playlist/playlistitem.h
|
||||
src/playlist/playlistdelegates.cpp
|
||||
src/playlist/playlistdelegates.h
|
||||
src/playlist/playlistbackend.cpp
|
||||
src/playlist/playlistbackend.h
|
||||
src/playlist/playlistview.cpp
|
||||
src/playlist/playlistview.h
|
||||
src/playlist/songplaylistitem.cpp
|
||||
src/playlist/songplaylistitem.h
|
||||
src/internet/internetplaylistitem.cpp
|
||||
src/internet/internetsearch.cpp
|
||||
src/internet/internetsearch.h
|
||||
src/internet/internetsearchview.cpp
|
||||
src/internet/internetsearchview.h
|
||||
src/internet/internetservices.cpp
|
||||
src/internet/internetservices.h
|
||||
src/internet/internetsongsview.cpp
|
||||
src/internet/internetsongsview.h
|
||||
src/internet/internetcollectionview.cpp
|
||||
src/internet/internetcollectionview.h
|
||||
ext/libstrawberry-tagreader/tagreader.cpp
|
||||
ext/libstrawberry-tagreader/tagreader.h
|
||||
src/device/devicemanager.cpp
|
||||
src/device/devicemanager.h
|
||||
src/device/deviceinfo.cpp
|
||||
src/device/deviceinfo.h
|
||||
src/device/deviceproperties.cpp
|
||||
src/device/deviceproperties.h
|
||||
src/device/deviceview.cpp
|
||||
src/device/deviceview.h
|
||||
src/device/connecteddevice.cpp
|
||||
src/device/connecteddevice.h
|
||||
src/device/mtpconnection.cpp
|
||||
src/device/mtpconnection.h
|
||||
src/device/mtpdevice.cpp
|
||||
src/device/mtpdevice.h
|
||||
src/globalshortcuts/globalshortcutsmanager.cpp
|
||||
src/globalshortcuts/globalshortcutsmanager.h
|
||||
src/settings/shortcutssettingspage.cpp
|
||||
src/settings/shortcutssettingspage.h
|
||||
src/settings/appearancesettingspage.cpp
|
||||
src/settings/appearancesettingspage.h
|
||||
src/organize/organize.cpp
|
||||
src/organize/organize.h
|
||||
src/organize/organizedialog.cpp
|
||||
src/organize/organizedialog.h
|
||||
src/organize/organizeerrordialog.cpp
|
||||
src/organize/organizeerrordialog.h
|
||||
src/transcoder/transcoder.cpp
|
||||
src/transcoder/transcoder.h
|
||||
src/musicbrainz/musicbrainzclient.cpp
|
||||
src/musicbrainz/musicbrainzclient.h
|
||||
src/covermanager/albumcoverloader.cpp
|
||||
src/covermanager/albumcoverloader.h
|
||||
src/covermanager/currentalbumcoverloader.cpp
|
||||
src/covermanager/currentalbumcoverloader.h
|
||||
src/covermanager/albumcoverchoicecontroller.cpp
|
||||
src/covermanager/albumcoverchoicecontroller.h
|
||||
src/covermanager/albumcoverfetchersearch.cpp
|
||||
src/covermanager/albumcoverfetchersearch.h
|
||||
src/covermanager/coverproviders.cpp
|
||||
src/covermanager/coverproviders.h
|
||||
src/covermanager/coverprovider.cpp
|
||||
src/covermanager/coverprovider.h
|
||||
Copyright: 2010, 2012-2014 David Sansome <me@davidsansome.com>
|
||||
2012-2014, 2017-2020 Jonas Kvinge <jonas@jkvinge.net>
|
||||
ext/libstrawberry-tagreader/tagreadertagparser.cpp
|
||||
ext/libstrawberry-tagreader/tagreadertagparser.h
|
||||
ext/macdeploycheck/*
|
||||
src/widgets/resizabletextedit.cpp
|
||||
src/widgets/resizabletextedit.h
|
||||
Copyright: 2012-2014, 2017-2023, Jonas Kvinge <jonas@jkvinge.net>
|
||||
License: GPL-3+
|
||||
|
||||
Files: src/engine/enginebase.cpp
|
||||
src/engine/enginebase.h
|
||||
Copyright: 2017, 2018, Jonas Kvinge <jonas@jkvinge.net>
|
||||
Copyright: 2017-2021, Jonas Kvinge <jonas@jkvinge.net>
|
||||
2010, David Sansome <me@davidsansome.com>
|
||||
2004, 2005, Max Howell, <max.howell@methylblue.com>
|
||||
2003, Mark Kretschmann <markey@web.de>
|
||||
@@ -214,7 +111,7 @@ License: GPL-3+
|
||||
|
||||
Files: src/engine/gstengine.cpp
|
||||
src/engine/gstengine.h
|
||||
Copyright: 2017, 2018, Jonas Kvinge <jonas@jkvinge.net>
|
||||
Copyright: 2017-2021, Jonas Kvinge <jonas@jkvinge.net>
|
||||
2006, Paul Cifarelli <paul@cifarelli.net>
|
||||
2005, Jakub Stachowski <qbast@go2.pl>
|
||||
2003-2005, Mark Kretschmann <markey@web.de>
|
||||
@@ -230,12 +127,7 @@ Files: src/core/application.cpp
|
||||
src/core/application.h
|
||||
Copyright: 2012, David Sansome <me@davidsansome.com>
|
||||
2012, 2014, John Maguire <john.maguire@gmail.com>
|
||||
2018, Jonas Kvinge <jonas@jkvinge.net>
|
||||
License: GPL-3+
|
||||
|
||||
Files: src/core/appearance.cpp
|
||||
src/core/appearance.h
|
||||
Copyright: 2012, Arnaud Bienner <arnaud.bienner@gmail.com>
|
||||
2018-2021, Jonas Kvinge <jonas@jkvinge.net>
|
||||
License: GPL-3+
|
||||
|
||||
Files: src/covermanager/discogscoverprovider.cpp
|
||||
@@ -244,13 +136,7 @@ Copyright: 2012, Martin Björklund <mbj4668@gmail.com>
|
||||
2016, Jonas Kvinge <jonas@jkvinge.net>
|
||||
License: GPL-3+
|
||||
|
||||
Files: ext/libstrawberry-common/core/arraysize.h
|
||||
ext/libstrawberry-common/core/scoped_nsautorelease_pool.h
|
||||
ext/libstrawberry-common/core/scoped_nsautorelease_pool.mm
|
||||
Copyright: 2010, 2011, 2014, The Chromium Authors.
|
||||
License: BSD-style
|
||||
|
||||
Files: ext/libstrawberry-common/core/lazy.h
|
||||
Files: src/core/lazy.h
|
||||
Copyright: 2016, John Maguire <john.maguire@gmail.com>
|
||||
License: GPL-3+
|
||||
|
||||
@@ -298,12 +184,16 @@ Copyright: 2011, Tyler Rhodes <tyler.s.rhodes@gmail.com>
|
||||
2015, Arun Narayanankutty <n.arun.lifescience@gmail.com>
|
||||
License: GPL-2+
|
||||
|
||||
Files: src/collection/savedgroupingmanager.h
|
||||
Files: src/collection/savedgroupingmanager.*
|
||||
Copyright: 2015, Nick Lanham <nick@afternight.org>
|
||||
2019-2021 Jonas Kvinge <jonas@jkvinge.net>
|
||||
License: GPL-3+
|
||||
|
||||
Files: src/core/scoped_cftyperef.h
|
||||
Files: src/core/arraysize.h
|
||||
src/core/scoped_cftyperef.h
|
||||
src/core/scoped_nsobject.h
|
||||
src/core/scoped_nsautorelease_pool.h
|
||||
src/core/scoped_nsautorelease_pool.mm
|
||||
Copyright: 2010, 2011, 2014, The Chromium Authors.
|
||||
License: BSD-style
|
||||
|
||||
@@ -322,7 +212,7 @@ Files: src/internet/localredirectserver.cpp
|
||||
src/internet/localredirectserver.h
|
||||
Copyright: 2012, 2014, John Maguire <john.maguire@gmail.com>
|
||||
2014, Krzysztof Sobiecki <sobkas@gmail.com>
|
||||
2018-2019, Jonas Kvinge <jonas@jkvinge.net>
|
||||
2018-2021, Jonas Kvinge <jonas@jkvinge.net>
|
||||
License: GPL-3+
|
||||
|
||||
Files: src/transcoder/transcoderoptionsopus.cpp
|
||||
@@ -337,12 +227,22 @@ Files: src/widgets/clickablelabel.cpp
|
||||
Copyright: 2010, 2011, Andrea Decorte <adecorte@gmail.com>
|
||||
License: GPL-3+
|
||||
|
||||
Files: src/widgets/sliderwidget.cpp
|
||||
src/widgets/sliderwidget.h
|
||||
Copyright: 2005, Gábor Lehel
|
||||
Files: src/widgets/sliderslider.cpp
|
||||
src/widgets/sliderslider.h
|
||||
src/widgets/prettyslider.cpp
|
||||
src/widgets/prettyslider.h
|
||||
src/widgets/volumeslider.cpp
|
||||
src/widgets/volumeslider.h
|
||||
Copyright: 2018-2023, Jonas Kvinge <jonas@jkvinge.net>
|
||||
2005, Gábor Lehel
|
||||
2003, Mark Kretschmann <markey@web.de>
|
||||
License: GPL-2+
|
||||
|
||||
Files: src/scrobbler/subsonicscrobbler.*
|
||||
Copyright: 2018-2021 Jonas Kvinge <jonas@jkvinge.net>
|
||||
2020 Pascal Below <spezifisch@below.fr>
|
||||
License: GPL-3+
|
||||
|
||||
Files: src/core/stylehelper.cpp
|
||||
src/core/stylehelper.h
|
||||
Copyright: 2016 The Qt Company Ltd.
|
||||
@@ -358,6 +258,7 @@ License: GPL-2+
|
||||
Files: src/widgets/qsearchfield_nonmac.cpp
|
||||
src/widgets/qsearchfield_mac.mm
|
||||
src/widgets/qsearchfield.h
|
||||
src/widgets/qocoa_mac.h
|
||||
Copyright: 2011, Mike McQuaid <mike@mikemcquaid.com>
|
||||
License: Expat
|
||||
|
||||
@@ -367,7 +268,7 @@ Copyright: 2010, Spotify AB
|
||||
License: BSD-3-clause
|
||||
|
||||
Files: 3rdparty/singleapplication/*
|
||||
Copyright: 2015-2018, Itay Grudev
|
||||
Copyright: 2015-2022, Itay Grudev
|
||||
License: MIT
|
||||
|
||||
|
||||
|
||||
6
debian/copyright-scan-patterns.yml
vendored
@@ -4,14 +4,14 @@ ignore:
|
||||
- Changelog
|
||||
- COPYING
|
||||
- CMakeLists.txt
|
||||
- Dockerfile
|
||||
- cmake_uninstall.cmake.in
|
||||
- .clang-format
|
||||
- .gitignore
|
||||
- .travis.yml
|
||||
- .github
|
||||
- /debian/
|
||||
- /cmake/
|
||||
- /data/
|
||||
- /dist/
|
||||
- /snap/
|
||||
|
||||
suffixes:
|
||||
- jpg
|
||||
|
||||
1
debian/rules
vendored
@@ -5,7 +5,6 @@
|
||||
|
||||
override_dh_auto_clean:
|
||||
rm -f dist/macos/Info.plist
|
||||
rm -f dist/unix/PKGBUILD
|
||||
rm -f dist/unix/strawberry.spec
|
||||
rm -f dist/scripts/maketarball.sh
|
||||
rm -f dist/windows/strawberry.nsi
|
||||
|
||||
4
dist/CMakeLists.txt
vendored
@@ -2,10 +2,6 @@ configure_file(${CMAKE_CURRENT_SOURCE_DIR}/scripts/maketarball.sh.in ${CMAKE_CUR
|
||||
if(RPM_DISTRO AND RPM_DATE)
|
||||
configure_file(${CMAKE_CURRENT_SOURCE_DIR}/unix/strawberry.spec.in ${CMAKE_CURRENT_SOURCE_DIR}/unix/strawberry.spec @ONLY)
|
||||
endif(RPM_DISTRO AND RPM_DATE)
|
||||
if(DEB_CODENAME AND DEB_DATE)
|
||||
configure_file(${CMAKE_SOURCE_DIR}/debian/changelog.in ${CMAKE_SOURCE_DIR}/debian/changelog)
|
||||
endif(DEB_CODENAME AND DEB_DATE)
|
||||
configure_file(${CMAKE_CURRENT_SOURCE_DIR}/unix/PKGBUILD.in ${CMAKE_CURRENT_SOURCE_DIR}/unix/PKGBUILD @ONLY)
|
||||
|
||||
if(APPLE)
|
||||
configure_file(${CMAKE_CURRENT_SOURCE_DIR}/macos/Info.plist.in ${CMAKE_CURRENT_SOURCE_DIR}/macos/Info.plist)
|
||||
|
||||
58
dist/macos/macgstcopy.sh
vendored
@@ -39,8 +39,12 @@ 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
|
||||
@@ -48,46 +52,46 @@ libgstaudioparsers.dylib
|
||||
libgstaudiorate.dylib
|
||||
libgstaudioresample.dylib
|
||||
libgstaudiotestsrc.dylib
|
||||
libgstaudiovisualizers.dylib
|
||||
libgstauparse.dylib
|
||||
libgstautoconvert.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
|
||||
libgstlevel.dylib
|
||||
libgstisomp4.dylib
|
||||
libgstlame.dylib
|
||||
libgstlibav.dylib
|
||||
libgstmpg123.dylib
|
||||
libgstmusepack.dylib
|
||||
libgstogg.dylib
|
||||
libgstopenmpt.dylib
|
||||
libgstopus.dylib
|
||||
libgstopusparse.dylib
|
||||
libgstosxaudio.dylib
|
||||
libgstplayback.dylib
|
||||
libgstrawparse.dylib
|
||||
libgstreplaygain.dylib
|
||||
libgstsoup.dylib
|
||||
libgstspectrum.dylib
|
||||
libgsttypefindfunctions.dylib
|
||||
libgstvolume.dylib
|
||||
libgstxingmux.dylib
|
||||
libgsttcp.dylib
|
||||
libgstudp.dylib
|
||||
libgstpbtypes.dylib
|
||||
libgstplayback.dylib
|
||||
libgstreplaygain.dylib
|
||||
libgstrtp.dylib
|
||||
libgstrtsp.dylib
|
||||
libgstflac.dylib
|
||||
libgstwavparse.dylib
|
||||
libgstfaad.dylib
|
||||
libgstogg.dylib
|
||||
libgstopus.dylib
|
||||
libgstasf.dylib
|
||||
libgstsoup.dylib
|
||||
libgstspectrum.dylib
|
||||
libgstspeex.dylib
|
||||
libgsttaglib.dylib
|
||||
libgsttcp.dylib
|
||||
libgsttypefindfunctions.dylib
|
||||
libgstudp.dylib
|
||||
libgstvolume.dylib
|
||||
libgstvorbis.dylib
|
||||
libgstisomp4.dylib
|
||||
libgstlibav.dylib
|
||||
libgstaiff.dylib
|
||||
libgstlame.dylib
|
||||
libgstopusparse.dylib
|
||||
libgstfaac.dylib
|
||||
libgstmusepack.dylib
|
||||
libgstwavpack.dylib
|
||||
libgstwavparse.dylib
|
||||
libgstxingmux.dylib;
|
||||
"
|
||||
|
||||
gst_plugins=$(echo "$gst_plugins" | tr '\n' ' ' | sed -e 's/^ //g' | sed -e 's/ / /g')
|
||||
|
||||
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"
|
||||
28
dist/scripts/maketarball.sh.in
vendored
@@ -11,10 +11,33 @@ if ! [ "$gitrev" = "ON" ]; then
|
||||
exclude_vcs="--exclude-vcs"
|
||||
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..."
|
||||
|
||||
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 \
|
||||
--exclude=".directory" \
|
||||
--exclude="*.tar" \
|
||||
@@ -26,6 +49,7 @@ tar -cJf $name-$version.tar.xz \
|
||||
--exclude="*.nsi" \
|
||||
--exclude="*.kdev4" \
|
||||
--exclude=".vscode" \
|
||||
--exclude=".idea" \
|
||||
--exclude="$root/.github" \
|
||||
--exclude="$root/.travis.yml" \
|
||||
--exclude="$root/.circleci" \
|
||||
@@ -33,11 +57,11 @@ tar -cJf $name-$version.tar.xz \
|
||||
--exclude="$root/CMakeLists.txt.user" \
|
||||
--exclude="$root/.clang-format" \
|
||||
--exclude="$root/build" \
|
||||
--exclude="$root/cmake-build-debug" \
|
||||
--exclude="$root/zanata.xml" \
|
||||
--exclude="$root/.zanata-cache" \
|
||||
--exclude="$root/debian/changelog" \
|
||||
--exclude="$root/dist/scripts/maketarball.sh" \
|
||||
--exclude="$root/dist/unix/PKGBUILD" \
|
||||
--exclude="$root/dist/macos/Info.plist" \
|
||||
--exclude="$root/dist/windows/windres.rc" \
|
||||
--exclude="$root/src/translations/translations.pot" \
|
||||
|
||||
56
dist/unix/PKGBUILD.in
vendored
@@ -1,56 +0,0 @@
|
||||
# Maintainer: Jonas Kvinge <jonas@jkvinge.net>
|
||||
pkgname=strawberry
|
||||
pkgver=@STRAWBERRY_VERSION_PAC_V@
|
||||
pkgrel=@STRAWBERRY_VERSION_PAC_R@
|
||||
pkgdesc="A music player aimed at audio enthusiasts and music collectors"
|
||||
arch=(x86_64)
|
||||
url="https://www.strawberrymusicplayer.org/"
|
||||
license=(GPL3)
|
||||
makedepends=(git cmake make gcc boost gettext qt5-tools)
|
||||
depends=(
|
||||
desktop-file-utils
|
||||
hicolor-icon-theme
|
||||
gnutls
|
||||
udisks2
|
||||
protobuf
|
||||
qt5-base
|
||||
qt5-x11extras
|
||||
sqlite3
|
||||
alsa-lib
|
||||
libpulse
|
||||
dbus
|
||||
taglib
|
||||
gstreamer
|
||||
gst-plugins-base
|
||||
gst-plugins-good
|
||||
vlc
|
||||
chromaprint
|
||||
libgpod
|
||||
libcdio
|
||||
libmtp
|
||||
fftw
|
||||
)
|
||||
optdepends=(
|
||||
gst-plugins-bad
|
||||
gst-plugins-ugly
|
||||
gst-libav
|
||||
)
|
||||
provides=(strawberry)
|
||||
conflicts=(strawberry)
|
||||
source=("strawberry-@STRAWBERRY_VERSION_PACKAGE@.tar.xz")
|
||||
sha256sums=('SKIP')
|
||||
|
||||
prepare() {
|
||||
mkdir -p build
|
||||
}
|
||||
|
||||
build() {
|
||||
cd build
|
||||
cmake ../${pkgname}-@STRAWBERRY_VERSION_PACKAGE@ -DCMAKE_INSTALL_PREFIX=/usr
|
||||
make -j$(nproc)
|
||||
}
|
||||
|
||||
package() {
|
||||
cd build
|
||||
make DESTDIR="${pkgdir}" install
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<component>
|
||||
<component type="desktop-application">
|
||||
<id>org.strawberrymusicplayer.strawberry</id>
|
||||
<launchable type="desktop-id">org.strawberrymusicplayer.strawberry.desktop</launchable>
|
||||
<metadata_license>CC0-1.0</metadata_license>
|
||||
@@ -13,6 +13,7 @@
|
||||
<url type="homepage">https://www.strawberrymusicplayer.org/</url>
|
||||
<url type="bugtracker">https://github.com/strawberrymusicplayer/strawberry/</url>
|
||||
<translation type="qt">strawberry</translation>
|
||||
<content_rating type="oars-1.1" />
|
||||
<description>
|
||||
<p>
|
||||
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.
|
||||
@@ -28,7 +29,7 @@
|
||||
<li>Edit tags on audio files</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>Song lyrics from AudD, Genius, Musixmatch, ChartLyrics, lyrics.ovh and lololyrics.com</li>
|
||||
<li>Song lyrics from Lyrics.com, AudD, Genius, Musixmatch, ChartLyrics, lyrics.ovh and lololyrics.com</li>
|
||||
<li>Support for multiple backends</li>
|
||||
<li>Audio analyzer and equalizer</li>
|
||||
<li>Transfer music to mass-storage USB players, MTP compatible devices and iPod Nano/Classic</li>
|
||||
@@ -48,4 +49,7 @@
|
||||
</screenshot>
|
||||
</screenshots>
|
||||
<update_contact>eclipseo@fedoraproject.org</update_contact>
|
||||
<releases>
|
||||
<release version="1.0.0" date="2021-10-16"/>
|
||||
</releases>
|
||||
</component>
|
||||
|
||||
@@ -9,18 +9,15 @@ TryExec=strawberry
|
||||
Icon=strawberry
|
||||
Terminal=false
|
||||
Categories=AudioVideo;Player;Qt;Audio;
|
||||
Keywords=Audio;Player;
|
||||
StartupNotify=false
|
||||
MimeType=x-content/audio-player;application/ogg;application/x-ogg;application/x-ogm-audio;audio/flac;audio/ogg;audio/vorbis;audio/aac;audio/mp4;audio/mpeg;audio/mpegurl;audio/vnd.rn-realaudio;audio/x-flac;audio/x-oggflac;audio/x-vorbis;audio/x-vorbis+ogg;audio/x-speex;audio/x-wav;audio/x-wavpack;audio/x-ape;audio/x-mp3;audio/x-mpeg;audio/x-mpegurl;audio/x-ms-wma;audio/x-musepack;audio/x-pn-realaudio;audio/x-scpls;video/x-ms-asf;x-scheme-handler/tidal;
|
||||
StartupWMClass=strawberry
|
||||
Actions=Play;Pause;Stop;StopAfterCurrent;Previous;Next;
|
||||
Actions=Play-Pause;Stop;StopAfterCurrent;Previous;Next;
|
||||
|
||||
[Desktop Action Play]
|
||||
Name=Play
|
||||
Exec=strawberry --play
|
||||
|
||||
[Desktop Action Pause]
|
||||
Name=Pause
|
||||
Exec=strawberry --pause
|
||||
[Desktop Action Play-Pause]
|
||||
Name=Play/Pause
|
||||
Exec=strawberry --play-pause
|
||||
|
||||
[Desktop Action Stop]
|
||||
Name=Stop
|
||||
|
||||
2
dist/unix/strawberry.1
vendored
@@ -29,7 +29,7 @@ Features:
|
||||
.br
|
||||
- Album cover art from Last.fm, Musicbrainz, Discogs, Musixmatch, Deezer, Tidal, Qobuz and Spotify
|
||||
.br
|
||||
- Song lyrics from AudD, Genius, Musixmatch, ChartLyrics, lyrics.ovh and lololyrics.com
|
||||
- Song lyrics from Lyrics.com, AudD, Genius, Musixmatch, ChartLyrics, lyrics.ovh and lololyrics.com
|
||||
.br
|
||||
- Support for multiple backends
|
||||
.br
|
||||
|
||||
12
dist/unix/strawberry.spec.in
vendored
@@ -1,6 +1,6 @@
|
||||
Name: strawberry
|
||||
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}
|
||||
%else
|
||||
Release: @STRAWBERRY_VERSION_RPM_R@.@RPM_DISTRO@
|
||||
@@ -49,6 +49,8 @@ BuildRequires: pkgconfig(sqlite3) >= 3.9
|
||||
BuildRequires: pkgconfig(taglib)
|
||||
%endif
|
||||
BuildRequires: pkgconfig(fftw3)
|
||||
BuildRequires: pkgconfig(icu-uc)
|
||||
BuildRequires: pkgconfig(icu-i18n)
|
||||
%if "@QT_VERSION_MAJOR@" == "5" && ( 0%{?fedora} || 0%{?rhel_version} || 0%{?centos} )
|
||||
BuildRequires: pkgconfig(Qt@QT_VERSION_MAJOR@Core)
|
||||
BuildRequires: pkgconfig(Qt@QT_VERSION_MAJOR@Gui)
|
||||
@@ -117,7 +119,7 @@ Features:
|
||||
- Edit tags on audio files
|
||||
- Automatically retrieve tags from MusicBrainz
|
||||
- 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 Lyrics.com, AudD, Genius, Musixmatch, ChartLyrics, lyrics.ovh and lololyrics.com
|
||||
- Support for multiple backends
|
||||
- Audio analyzer
|
||||
- Audio equalizer
|
||||
@@ -137,8 +139,8 @@ Features:
|
||||
%if 0%{?fedora} || 0%{?rhel_version} || 0%{?centos}
|
||||
export CXXFLAGS="-fPIC $RPM_OPT_FLAGS"
|
||||
%endif
|
||||
%{cmake} -DCMAKE_BUILD_TYPE:STRING=Release -DQT_VERSION_MAJOR=@QT_VERSION_MAJOR@
|
||||
%if 0%{?centos} || (0%{?mageia} && 0%{?mageia} <= 7)
|
||||
%{cmake} -DQT_VERSION_MAJOR=@QT_VERSION_MAJOR@
|
||||
%if 0%{?centos} || (0%{?mageia} && 0%{?mageia} <= 7) || "%{?_vendor}" == "openmandriva"
|
||||
%make_build
|
||||
%else
|
||||
%cmake_build
|
||||
@@ -148,7 +150,7 @@ Features:
|
||||
%if 0%{?centos}
|
||||
%make_install
|
||||
%else
|
||||
%if 0%{?mageia}
|
||||
%if 0%{?mageia} || "%{?_vendor}" == "openmandriva"
|
||||
%make_install -C build
|
||||
%else
|
||||
%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
|
||||
947
dist/windows/strawberry.nsi.in
vendored
@@ -1,4 +1,4 @@
|
||||
cmake_minimum_required(VERSION 3.0)
|
||||
cmake_minimum_required(VERSION 3.7)
|
||||
|
||||
set(SOURCES gstfastspectrum.cpp gstmoodbarplugin.cpp)
|
||||
|
||||
|
||||
@@ -33,24 +33,13 @@
|
||||
|
||||
#include "gstfastspectrum.h"
|
||||
|
||||
GST_DEBUG_CATEGORY_STATIC (gst_fastspectrum_debug);
|
||||
#define GST_CAT_DEFAULT gst_fastspectrum_debug
|
||||
GST_DEBUG_CATEGORY_STATIC(gst_fastspectrum_debug);
|
||||
|
||||
/* elementfactory information */
|
||||
#if G_BYTE_ORDER == G_LITTLE_ENDIAN
|
||||
# define FORMATS "{ S16LE, S24LE, S32LE, F32LE, F64LE }"
|
||||
#else
|
||||
# define FORMATS "{ S16BE, S24BE, S32BE, F32BE, F64BE }"
|
||||
#endif
|
||||
namespace {
|
||||
|
||||
#define ALLOWED_CAPS \
|
||||
GST_AUDIO_CAPS_MAKE (FORMATS) ", " \
|
||||
"layout = (string) interleaved, " \
|
||||
"channels = 1"
|
||||
|
||||
/* Spectrum properties */
|
||||
#define DEFAULT_INTERVAL (GST_SECOND / 10)
|
||||
#define DEFAULT_BANDS 128
|
||||
// Spectrum properties
|
||||
constexpr auto DEFAULT_INTERVAL = (GST_SECOND / 10);
|
||||
constexpr auto DEFAULT_BANDS = 128;
|
||||
|
||||
enum {
|
||||
PROP_0,
|
||||
@@ -58,93 +47,100 @@ enum {
|
||||
PROP_BANDS
|
||||
};
|
||||
|
||||
} // namespace
|
||||
|
||||
#define gst_fastspectrum_parent_class parent_class
|
||||
G_DEFINE_TYPE (GstFastSpectrum, gst_fastspectrum, GST_TYPE_AUDIO_FILTER)
|
||||
#pragma GCC diagnostic push
|
||||
#pragma GCC diagnostic ignored "-Wold-style-cast"
|
||||
G_DEFINE_TYPE(GstFastSpectrum, gst_fastspectrum, GST_TYPE_AUDIO_FILTER)
|
||||
#pragma GCC diagnostic pop
|
||||
|
||||
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_get_property (GObject * object, guint prop_id, GValue * value, GParamSpec * pspec);
|
||||
static gboolean gst_fastspectrum_start (GstBaseTransform * trans);
|
||||
static gboolean gst_fastspectrum_stop (GstBaseTransform * trans);
|
||||
static GstFlowReturn gst_fastspectrum_transform_ip (GstBaseTransform *trans, GstBuffer *buffer);
|
||||
static gboolean gst_fastspectrum_setup (GstAudioFilter * base, const GstAudioInfo * info);
|
||||
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_get_property(GObject *object, guint prop_id, GValue *value, GParamSpec *pspec);
|
||||
static gboolean gst_fastspectrum_start(GstBaseTransform *trans);
|
||||
static gboolean gst_fastspectrum_stop(GstBaseTransform *trans);
|
||||
static GstFlowReturn gst_fastspectrum_transform_ip(GstBaseTransform *trans, GstBuffer *buffer);
|
||||
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);
|
||||
GstElementClass *element_class = GST_ELEMENT_CLASS (klass);
|
||||
GstBaseTransformClass *trans_class = GST_BASE_TRANSFORM_CLASS (klass);
|
||||
GstAudioFilterClass *filter_class = GST_AUDIO_FILTER_CLASS (klass);
|
||||
GObjectClass *gobject_class = G_OBJECT_CLASS(klass);
|
||||
GstElementClass *element_class = GST_ELEMENT_CLASS(klass);
|
||||
GstBaseTransformClass *trans_class = GST_BASE_TRANSFORM_CLASS(klass);
|
||||
GstAudioFilterClass *filter_class = GST_AUDIO_FILTER_CLASS(klass);
|
||||
GstCaps *caps = nullptr;
|
||||
|
||||
gobject_class->set_property = gst_fastspectrum_set_property;
|
||||
gobject_class->get_property = gst_fastspectrum_get_property;
|
||||
gobject_class->finalize = gst_fastspectrum_finalize;
|
||||
|
||||
trans_class->start = GST_DEBUG_FUNCPTR (gst_fastspectrum_start);
|
||||
trans_class->stop = GST_DEBUG_FUNCPTR (gst_fastspectrum_stop);
|
||||
trans_class->transform_ip = GST_DEBUG_FUNCPTR (gst_fastspectrum_transform_ip);
|
||||
trans_class->start = GST_DEBUG_FUNCPTR(gst_fastspectrum_start);
|
||||
trans_class->stop = GST_DEBUG_FUNCPTR(gst_fastspectrum_stop);
|
||||
trans_class->transform_ip = GST_DEBUG_FUNCPTR(gst_fastspectrum_transform_ip);
|
||||
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_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_INTERVAL, g_param_spec_uint64("interval", "Interval", "Interval of time between message posts (in nanoseconds)", 1, G_MAXUINT64, DEFAULT_INTERVAL, static_cast<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, static_cast<GParamFlags>(G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)));
|
||||
|
||||
GST_DEBUG_CATEGORY_INIT (gst_fastspectrum_debug, "spectrum", 0,
|
||||
"audio spectrum analyser element");
|
||||
GST_DEBUG_CATEGORY_INIT(gst_fastspectrum_debug, "spectrum", 0, "audio spectrum analyser element");
|
||||
|
||||
gst_element_class_set_static_metadata (element_class, "Spectrum analyzer",
|
||||
"Filter/Analyzer/Audio",
|
||||
"Run an FFT on the audio signal, output spectrum data",
|
||||
"Erik Walthinsen <omega@cse.ogi.edu>, "
|
||||
"Stefan Kost <ensonic@users.sf.net>, "
|
||||
"Sebastian Dröge <sebastian.droege@collabora.co.uk>");
|
||||
gst_element_class_set_static_metadata(element_class, "Spectrum analyzer",
|
||||
"Filter/Analyzer/Audio",
|
||||
"Run an FFT on the audio signal, output spectrum data",
|
||||
"Erik Walthinsen <omega@cse.ogi.edu>, "
|
||||
"Stefan Kost <ensonic@users.sf.net>, "
|
||||
"Sebastian Dröge <sebastian.droege@collabora.co.uk>");
|
||||
|
||||
caps = gst_caps_from_string (ALLOWED_CAPS);
|
||||
gst_audio_filter_class_add_pad_templates (filter_class, caps);
|
||||
gst_caps_unref (caps);
|
||||
#if G_BYTE_ORDER == G_LITTLE_ENDIAN
|
||||
caps = gst_caps_from_string(GST_AUDIO_CAPS_MAKE("{ S16LE, S24LE, S32LE, F32LE, F64LE }") ", layout = (string) interleaved, channels = 1");
|
||||
#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;
|
||||
|
||||
}
|
||||
|
||||
static void gst_fastspectrum_init (GstFastSpectrum * spectrum) {
|
||||
static void gst_fastspectrum_init(GstFastSpectrum *spectrum) {
|
||||
|
||||
spectrum->interval = DEFAULT_INTERVAL;
|
||||
spectrum->bands = DEFAULT_BANDS;
|
||||
|
||||
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 nfft = 2 * bands - 2;
|
||||
|
||||
spectrum->input_ring_buffer = new 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_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->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);
|
||||
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;
|
||||
|
||||
}
|
||||
|
||||
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) {
|
||||
{
|
||||
QMutexLocker l(klass->fftw_lock);
|
||||
@@ -160,7 +156,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_fft = 0;
|
||||
@@ -169,147 +165,145 @@ 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_flush (spectrum);
|
||||
gst_fastspectrum_free_channel_data(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);
|
||||
|
||||
gst_fastspectrum_reset_state (spectrum);
|
||||
g_mutex_clear (&spectrum->lock);
|
||||
gst_fastspectrum_reset_state(spectrum);
|
||||
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);
|
||||
|
||||
switch (prop_id) {
|
||||
case PROP_INTERVAL:{
|
||||
guint64 interval = g_value_get_uint64 (value);
|
||||
g_mutex_lock (&filter->lock);
|
||||
case PROP_INTERVAL: {
|
||||
guint64 interval = g_value_get_uint64(value);
|
||||
g_mutex_lock(&filter->lock);
|
||||
if (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;
|
||||
}
|
||||
case PROP_BANDS:{
|
||||
guint bands = g_value_get_uint (value);
|
||||
g_mutex_lock (&filter->lock);
|
||||
case PROP_BANDS: {
|
||||
guint bands = g_value_get_uint(value);
|
||||
g_mutex_lock(&filter->lock);
|
||||
if (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;
|
||||
}
|
||||
default:
|
||||
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
|
||||
G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
|
||||
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);
|
||||
|
||||
switch (prop_id) {
|
||||
case PROP_INTERVAL:
|
||||
g_value_set_uint64 (value, filter->interval);
|
||||
g_value_set_uint64(value, filter->interval);
|
||||
break;
|
||||
case PROP_BANDS:
|
||||
g_value_set_uint (value, filter->bands);
|
||||
g_value_set_uint(value, filter->bands);
|
||||
break;
|
||||
default:
|
||||
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
|
||||
G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
|
||||
break;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
static gboolean gst_fastspectrum_start (GstBaseTransform * trans) {
|
||||
static gboolean gst_fastspectrum_start(GstBaseTransform *trans) {
|
||||
|
||||
GstFastSpectrum *spectrum = reinterpret_cast<GstFastSpectrum*>(trans);
|
||||
|
||||
gst_fastspectrum_reset_state (spectrum);
|
||||
gst_fastspectrum_reset_state(spectrum);
|
||||
|
||||
return TRUE;
|
||||
|
||||
}
|
||||
|
||||
static gboolean gst_fastspectrum_stop (GstBaseTransform * trans) {
|
||||
static gboolean gst_fastspectrum_stop(GstBaseTransform *trans) {
|
||||
|
||||
GstFastSpectrum *spectrum = reinterpret_cast<GstFastSpectrum*>(trans);
|
||||
|
||||
gst_fastspectrum_reset_state (spectrum);
|
||||
gst_fastspectrum_reset_state(spectrum);
|
||||
|
||||
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);
|
||||
|
||||
guint j = 0, ip = 0;
|
||||
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++];
|
||||
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);
|
||||
|
||||
guint j = 0, ip = 0;
|
||||
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++];
|
||||
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);
|
||||
guint ip = 0;
|
||||
|
||||
for (j = 0; j < len; j++) {
|
||||
for (guint j = 0; j < len; j++) {
|
||||
out[op] = in[ip++] / max_value;
|
||||
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 (j = 0; j < len; j++) {
|
||||
for (guint j = 0; j < len; j++) {
|
||||
#if G_BYTE_ORDER == G_BIG_ENDIAN
|
||||
gint32 value = GST_READ_UINT24_BE (_in);
|
||||
guint32 value = GST_READ_UINT24_BE(_in);
|
||||
#else
|
||||
gint32 value = GST_READ_UINT24_LE (_in);
|
||||
guint32 value = GST_READ_UINT24_LE(_in);
|
||||
#endif
|
||||
if (value & 0x00800000) {
|
||||
value |= 0xff000000;
|
||||
@@ -322,25 +316,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);
|
||||
guint ip = 0;
|
||||
|
||||
for (j = 0; j < len; j++) {
|
||||
for (guint j = 0; j < len; j++) {
|
||||
out[op] = in[ip++] / max_value;
|
||||
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);
|
||||
GstFastSpectrumInputData input_data = nullptr;
|
||||
|
||||
g_mutex_lock (&spectrum->lock);
|
||||
switch (GST_AUDIO_INFO_FORMAT (info)) {
|
||||
g_mutex_lock(&spectrum->lock);
|
||||
switch (GST_AUDIO_INFO_FORMAT(info)) {
|
||||
case GST_AUDIO_FORMAT_S16:
|
||||
input_data = input_data_mixed_int16_max;
|
||||
break;
|
||||
@@ -357,35 +351,33 @@ static gboolean gst_fastspectrum_setup (GstAudioFilter * base, const GstAudioInf
|
||||
input_data = input_data_mixed_double;
|
||||
break;
|
||||
default:
|
||||
g_assert_not_reached ();
|
||||
g_assert_not_reached();
|
||||
break;
|
||||
}
|
||||
spectrum->input_data = input_data;
|
||||
|
||||
gst_fastspectrum_reset_state (spectrum);
|
||||
g_mutex_unlock (&spectrum->lock);
|
||||
gst_fastspectrum_reset_state(spectrum);
|
||||
g_mutex_unlock(&spectrum->lock);
|
||||
|
||||
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 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];
|
||||
}
|
||||
|
||||
// Should be safe to execute the same plan multiple times in parallel.
|
||||
fftw_execute(spectrum->plan);
|
||||
|
||||
gdouble val = 0.0;
|
||||
/* Calculate magnitude in db */
|
||||
for (i = 0; i < bands; i++) {
|
||||
val = spectrum->fft_output[i][0] * spectrum->fft_output[i][0];
|
||||
// Calculate magnitude in db
|
||||
for (guint i = 0; i < bands; i++) {
|
||||
gdouble val = spectrum->fft_output[i][0] * spectrum->fft_output[i][0];
|
||||
val += spectrum->fft_output[i][1] * spectrum->fft_output[i][1];
|
||||
val /= nfft * nfft;
|
||||
spectrum->spect_magnitude[i] += val;
|
||||
@@ -393,79 +385,68 @@ 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);
|
||||
guint rate = GST_AUDIO_FILTER_RATE (spectrum);
|
||||
guint bps = GST_AUDIO_FILTER_BPS (spectrum);
|
||||
guint bpf = GST_AUDIO_FILTER_BPF (spectrum);
|
||||
double max_value = (1UL << ((bps << 3) - 1)) - 1;
|
||||
guint rate = GST_AUDIO_FILTER_RATE(spectrum);
|
||||
guint bps = GST_AUDIO_FILTER_BPS(spectrum);
|
||||
guint bpf = GST_AUDIO_FILTER_BPF(spectrum);
|
||||
double max_value = static_cast<double>((1UL << ((bps << 3) - 1)) - 1);
|
||||
guint bands = spectrum->bands;
|
||||
guint nfft = 2 * bands - 2;
|
||||
guint input_pos = 0;
|
||||
GstMapInfo map;
|
||||
const guint8 *data = nullptr;
|
||||
gsize size = 0;
|
||||
guint fft_todo = 0, msg_todo = 0, block_size = 0;
|
||||
gboolean have_full_interval = false;
|
||||
GstFastSpectrumInputData input_data = nullptr;
|
||||
|
||||
g_mutex_lock (&spectrum->lock);
|
||||
gst_buffer_map (buffer, &map, GST_MAP_READ);
|
||||
g_mutex_lock(&spectrum->lock);
|
||||
gst_buffer_map(buffer, &map, GST_MAP_READ);
|
||||
data = map.data;
|
||||
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)) {
|
||||
GST_DEBUG_OBJECT (spectrum, "Discontinuity detected -- flushing");
|
||||
gst_fastspectrum_flush (spectrum);
|
||||
if (GST_BUFFER_IS_DISCONT(buffer)) {
|
||||
GST_DEBUG_OBJECT(spectrum, "Discontinuity detected -- flushing");
|
||||
gst_fastspectrum_flush(spectrum);
|
||||
}
|
||||
|
||||
/* If we don't have a FFT context yet (or it was reset due to parameter
|
||||
* changes) get one and allocate memory for everything
|
||||
*/
|
||||
// If we don't have a FFT context yet (or it was reset due to parameter changes) get one and allocate memory for everything
|
||||
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
|
||||
* interval is in ns */
|
||||
spectrum->frames_per_interval = gst_util_uint64_scale (spectrum->interval, rate, GST_SECOND);
|
||||
// Number of sample frames we process before posting a message interval is in ns
|
||||
spectrum->frames_per_interval = gst_util_uint64_scale(spectrum->interval, rate, GST_SECOND);
|
||||
spectrum->frames_todo = spectrum->frames_per_interval;
|
||||
/* rounding error for frames_per_interval in ns,
|
||||
* aggregated it in accumulated_error */
|
||||
// Rounding error for frames_per_interval in ns, aggregated it in accumulated_error
|
||||
spectrum->error_per_interval = (spectrum->interval * rate) % GST_SECOND;
|
||||
if (spectrum->frames_per_interval == 0) {
|
||||
spectrum->frames_per_interval = 1;
|
||||
}
|
||||
|
||||
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));
|
||||
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));
|
||||
|
||||
spectrum->input_pos = 0;
|
||||
|
||||
gst_fastspectrum_flush (spectrum);
|
||||
gst_fastspectrum_flush(spectrum);
|
||||
}
|
||||
|
||||
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_data = spectrum->input_data;
|
||||
|
||||
while (size >= bpf) {
|
||||
/* run input_data for a chunk of data */
|
||||
fft_todo = nfft - (spectrum->num_frames % nfft);
|
||||
msg_todo = spectrum->frames_todo - spectrum->num_frames;
|
||||
GST_LOG_OBJECT (spectrum,
|
||||
"message frames todo: %u, fft frames todo: %u, input frames %"
|
||||
G_GSIZE_FORMAT, msg_todo, fft_todo, (size / bpf));
|
||||
block_size = msg_todo;
|
||||
// Run input_data for a chunk of data
|
||||
guint fft_todo = nfft - (spectrum->num_frames % nfft);
|
||||
guint msg_todo = spectrum->frames_todo - spectrum->num_frames;
|
||||
GST_LOG_OBJECT(spectrum, "message frames todo: %u, fft frames todo: %u, input frames %" G_GSIZE_FORMAT, msg_todo, fft_todo, (size / bpf));
|
||||
guint block_size = msg_todo;
|
||||
if (block_size > (size / bpf)) {
|
||||
block_size = (size / bpf);
|
||||
}
|
||||
@@ -473,7 +454,7 @@ static GstFlowReturn gst_fastspectrum_transform_ip (GstBaseTransform *trans, Gst
|
||||
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);
|
||||
|
||||
data += block_size * bpf;
|
||||
@@ -481,25 +462,19 @@ static GstFlowReturn gst_fastspectrum_transform_ip (GstBaseTransform *trans, Gst
|
||||
input_pos = (input_pos + block_size) % nfft;
|
||||
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,
|
||||
"size: %" G_GSIZE_FORMAT ", do-fft = %d, do-message = %d", size,
|
||||
(spectrum->num_frames % nfft == 0), have_full_interval);
|
||||
GST_LOG_OBJECT(spectrum, "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
|
||||
* the interval and we haven't run a FFT, then run an FFT */
|
||||
// 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
|
||||
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++;
|
||||
}
|
||||
|
||||
/* Do we have the FFTs for one interval? */
|
||||
// Do we have the FFTs for one interval?
|
||||
if (have_full_interval) {
|
||||
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));
|
||||
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));
|
||||
|
||||
spectrum->frames_todo = spectrum->frames_per_interval;
|
||||
if (spectrum->accumulated_error >= GST_SECOND) {
|
||||
@@ -511,17 +486,17 @@ static GstFlowReturn gst_fastspectrum_transform_ip (GstBaseTransform *trans, Gst
|
||||
if (spectrum->output_callback) {
|
||||
// Calculate average
|
||||
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
|
||||
memset(spectrum->spect_magnitude, 0, spectrum->bands * sizeof(double));
|
||||
}
|
||||
|
||||
if (GST_CLOCK_TIME_IS_VALID (spectrum->message_ts)) {
|
||||
spectrum->message_ts += gst_util_uint64_scale (spectrum->num_frames, GST_SECOND, rate);
|
||||
if (GST_CLOCK_TIME_IS_VALID(spectrum->message_ts)) {
|
||||
spectrum->message_ts += gst_util_uint64_scale(spectrum->num_frames, GST_SECOND, rate);
|
||||
}
|
||||
|
||||
spectrum->num_frames = 0;
|
||||
@@ -531,10 +506,10 @@ static GstFlowReturn gst_fastspectrum_transform_ip (GstBaseTransform *trans, Gst
|
||||
|
||||
spectrum->input_pos = input_pos;
|
||||
|
||||
gst_buffer_unmap (buffer, &map);
|
||||
g_mutex_unlock (&spectrum->lock);
|
||||
gst_buffer_unmap(buffer, &map);
|
||||
g_mutex_unlock(&spectrum->lock);
|
||||
|
||||
g_assert (size == 0);
|
||||
g_assert(size == 0);
|
||||
|
||||
return GST_FLOW_OK;
|
||||
|
||||
|
||||
@@ -38,38 +38,37 @@
|
||||
G_BEGIN_DECLS
|
||||
|
||||
#define GST_TYPE_FASTSPECTRUM (gst_fastspectrum_get_type())
|
||||
#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_FASTSPECTRUM_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST((klass), GST_TYPE_FASTSPECTRUM,GstFastSpectrumClass))
|
||||
#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_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))
|
||||
|
||||
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;
|
||||
using OutputCallback = std::function<void(double *magnitudes, int size)>;
|
||||
|
||||
struct GstFastSpectrum {
|
||||
GstAudioFilter parent;
|
||||
|
||||
/* properties */
|
||||
guint64 interval; /* how many nanoseconds between emits */
|
||||
guint64 frames_per_interval; /* how many frames per interval */
|
||||
// Properties
|
||||
guint64 interval; // How many nanoseconds between emits
|
||||
guint64 frames_per_interval; // How many frames per interval
|
||||
guint64 frames_todo;
|
||||
guint bands; /* number of spectrum bands */
|
||||
gboolean multi_channel; /* send separate channel results */
|
||||
guint bands; // Number of spectrum bands
|
||||
gboolean multi_channel; // Send separate channel results
|
||||
|
||||
guint64 num_frames; /* frame count (1 sample per channel)
|
||||
* since last emit */
|
||||
guint64 num_fft; /* number of FFTs since last emit */
|
||||
GstClockTime message_ts; /* starttime for next message */
|
||||
guint64 num_frames; // Frame count (1 sample per channel) since last emit
|
||||
guint64 num_fft; // Number of FFTs since last emit
|
||||
GstClockTime message_ts; // Starttime for next message
|
||||
|
||||
/* <private> */
|
||||
// <private>
|
||||
bool channel_data_initialized;
|
||||
double* input_ring_buffer;
|
||||
double* fft_input;
|
||||
fftw_complex* fft_output;
|
||||
double* spect_magnitude;
|
||||
double *input_ring_buffer;
|
||||
double *fft_input;
|
||||
fftw_complex *fft_output;
|
||||
double *spect_magnitude;
|
||||
fftw_plan plan;
|
||||
|
||||
guint input_pos;
|
||||
@@ -87,11 +86,11 @@ struct GstFastSpectrumClass {
|
||||
GstAudioFilterClass parent_class;
|
||||
|
||||
// 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
|
||||
|
||||
#endif // GST_MOODBAR_FASTSPECTRUM_H
|
||||
#endif // GST_MOODBAR_FASTSPECTRUM_H
|
||||
|
||||
@@ -30,7 +30,6 @@ static gboolean gst_moodbar_plugin_init(GstPlugin *plugin) {
|
||||
}
|
||||
|
||||
return TRUE;
|
||||
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
@@ -19,7 +19,7 @@
|
||||
#define GST_MOODBAR_PLUGIN_H
|
||||
|
||||
extern "C" {
|
||||
int gstfastspectrum_register_static();
|
||||
int gstfastspectrum_register_static();
|
||||
}
|
||||
|
||||
#endif // GST_MOODBAR_PLUGIN_H
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
cmake_minimum_required(VERSION 3.0)
|
||||
cmake_minimum_required(VERSION 3.7)
|
||||
|
||||
set(SOURCES
|
||||
core/logging.cpp
|
||||
|
||||
@@ -52,18 +52,17 @@
|
||||
namespace logging {
|
||||
|
||||
static Level sDefaultLevel = Level_Debug;
|
||||
static QMap<QString, Level>* sClassLevels = nullptr;
|
||||
static QMap<QString, Level> *sClassLevels = nullptr;
|
||||
static QIODevice *sNullDevice = nullptr;
|
||||
|
||||
//const char* kDefaultLogLevels = "*:3";
|
||||
const char *kDefaultLogLevels = "GstEnginePipeline:2,*:3";
|
||||
const char *kDefaultLogLevels = "*:3";
|
||||
|
||||
static const char *kMessageHandlerMagic = "__logging_message__";
|
||||
static const size_t kMessageHandlerMagicLength = strlen(kMessageHandlerMagic);
|
||||
static QtMessageHandler sOriginalMessageHandler = nullptr;
|
||||
|
||||
template <class T>
|
||||
static T CreateLogger(Level level, const QString& class_name, int line, const char* category);
|
||||
template<class T>
|
||||
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*) {
|
||||
|
||||
@@ -89,13 +88,13 @@ void GLog(const char *domain, int level, const char *message, void*) {
|
||||
|
||||
}
|
||||
|
||||
template <class T>
|
||||
template<class T>
|
||||
class DebugBase : public QDebug {
|
||||
public:
|
||||
DebugBase() : QDebug(sNullDevice) {}
|
||||
explicit DebugBase(QtMsgType t) : QDebug(t) {}
|
||||
T& space() { return static_cast<T&>(QDebug::space()); }
|
||||
T& noSpace() { return static_cast<T&>(QDebug::nospace()); }
|
||||
T &space() { return static_cast<T&>(QDebug::space()); }
|
||||
T &nospace() { return static_cast<T&>(QDebug::nospace()); }
|
||||
};
|
||||
|
||||
// Debug message will be stored in a buffer.
|
||||
@@ -112,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,
|
||||
// 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_;
|
||||
};
|
||||
@@ -126,8 +125,10 @@ class LoggedDebug : public DebugBase<LoggedDebug> {
|
||||
|
||||
static void MessageHandler(QtMsgType type, const QMessageLogContext&, const QString &message) {
|
||||
|
||||
if (strncmp(kMessageHandlerMagic, message.toLocal8Bit().data(), kMessageHandlerMagicLength) == 0) {
|
||||
fprintf(stderr, "%s\n", message.toLocal8Bit().data() + kMessageHandlerMagicLength);
|
||||
if (message.startsWith(kMessageHandlerMagic)) {
|
||||
QByteArray message_data = message.toUtf8();
|
||||
fprintf(type == QtCriticalMsg || type == QtFatalMsg ? stderr : stdout, "%s\n", message_data.constData() + kMessageHandlerMagicLength);
|
||||
fflush(type == QtCriticalMsg || type == QtFatalMsg ? stderr : stdout);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -146,12 +147,13 @@ static void MessageHandler(QtMsgType type, const QMessageLogContext&, const QStr
|
||||
break;
|
||||
}
|
||||
|
||||
for (const QString& line : message.split('\n')) {
|
||||
for (const QString &line : message.split('\n')) {
|
||||
BufferedDebug d = CreateLogger<BufferedDebug>(level, "unknown", -1, nullptr);
|
||||
d << line.toLocal8Bit().constData();
|
||||
if (d.buf_) {
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -174,6 +176,7 @@ void Init() {
|
||||
if (!sOriginalMessageHandler) {
|
||||
sOriginalMessageHandler = qInstallMessageHandler(MessageHandler);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
void SetLevels(const QString &levels) {
|
||||
@@ -213,9 +216,9 @@ static QString ParsePrettyFunction(const char *pretty_function) {
|
||||
|
||||
// Get the class name out of the function name.
|
||||
QString class_name = pretty_function;
|
||||
const int paren = class_name.indexOf('(');
|
||||
const qint64 paren = class_name.indexOf('(');
|
||||
if (paren != -1) {
|
||||
const int colons = class_name.lastIndexOf("::", paren);
|
||||
const qint64 colons = class_name.lastIndexOf("::", paren);
|
||||
if (colons != -1) {
|
||||
class_name = class_name.left(colons);
|
||||
}
|
||||
@@ -224,16 +227,17 @@ static QString ParsePrettyFunction(const char *pretty_function) {
|
||||
}
|
||||
}
|
||||
|
||||
const int space = class_name.lastIndexOf(' ');
|
||||
const qint64 space = class_name.lastIndexOf(' ');
|
||||
if (space != -1) {
|
||||
class_name = class_name.mid(space + 1);
|
||||
}
|
||||
|
||||
return class_name;
|
||||
|
||||
}
|
||||
|
||||
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
|
||||
const char *level_name = nullptr;
|
||||
@@ -270,11 +274,10 @@ static T CreateLogger(Level level, const QString &class_name, int line, const ch
|
||||
}
|
||||
|
||||
T ret(type);
|
||||
ret.nospace() << QDateTime::currentDateTime().toString("hh:mm:ss.zzz").toLatin1().constData()
|
||||
<< level_name
|
||||
<< function_line.leftJustified(32).toLatin1().constData();
|
||||
ret.nospace() << QDateTime::currentDateTime().toString("hh:mm:ss.zzz").toLatin1().constData() << level_name << function_line.leftJustified(32).toLatin1().constData();
|
||||
|
||||
return ret.space();
|
||||
|
||||
}
|
||||
|
||||
#ifdef Q_OS_UNIX
|
||||
@@ -304,6 +307,7 @@ QString LinuxDemangle(const QString &symbol) {
|
||||
}
|
||||
QString mangled_function = match.captured(1);
|
||||
return CXXDemangle(mangled_function);
|
||||
|
||||
}
|
||||
#endif // Q_OS_LINUX
|
||||
|
||||
@@ -311,11 +315,11 @@ QString LinuxDemangle(const QString &symbol) {
|
||||
QString DarwinDemangle(const QString &symbol);
|
||||
QString DarwinDemangle(const QString &symbol) {
|
||||
|
||||
#if QT_VERSION >= QT_VERSION_CHECK(5, 14, 0)
|
||||
# if QT_VERSION >= QT_VERSION_CHECK(5, 14, 0)
|
||||
QStringList split = symbol.split(' ', Qt::SkipEmptyParts);
|
||||
#else
|
||||
# else
|
||||
QStringList split = symbol.split(' ', QString::SkipEmptyParts);
|
||||
#endif
|
||||
# endif
|
||||
QString mangled_function = split[3];
|
||||
return CXXDemangle(mangled_function);
|
||||
|
||||
@@ -324,6 +328,7 @@ QString DarwinDemangle(const QString &symbol) {
|
||||
|
||||
QString DemangleSymbol(const QString &symbol);
|
||||
QString DemangleSymbol(const QString &symbol) {
|
||||
|
||||
#ifdef Q_OS_MACOS
|
||||
return DarwinDemangle(symbol);
|
||||
#elif defined(Q_OS_LINUX)
|
||||
@@ -331,9 +336,11 @@ QString DemangleSymbol(const QString &symbol) {
|
||||
#else
|
||||
return symbol;
|
||||
#endif
|
||||
|
||||
}
|
||||
|
||||
void DumpStackTrace() {
|
||||
|
||||
#ifdef HAVE_BACKTRACE
|
||||
void *callstack[128];
|
||||
int callstack_size = backtrace(reinterpret_cast<void**>(&callstack), sizeof(callstack));
|
||||
@@ -346,11 +353,11 @@ void DumpStackTrace() {
|
||||
#else
|
||||
qLog(Debug) << "FIXME: Implement printing stack traces on this platform";
|
||||
#endif
|
||||
|
||||
}
|
||||
|
||||
// These are the functions that create loggers for the rest of Clementine.
|
||||
// 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.
|
||||
// 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 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)
|
||||
|
||||
QDebug CreateLoggerInfo(int line, const char *pretty_function, const char *category) { return qCreateLogger(line, pretty_function, category, Info); }
|
||||
@@ -360,20 +367,20 @@ QDebug CreateLoggerError(int line, const char *pretty_function, const char *cate
|
||||
#ifdef QT_NO_WARNING_OUTPUT
|
||||
QNoDebug CreateLoggerWarning(int, const char*, const char*) { return QNoDebug(); }
|
||||
#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
|
||||
|
||||
#ifdef QT_NO_DEBUG_OUTPUT
|
||||
QNoDebug CreateLoggerDebug(int, const char*, const char*) { return QNoDebug(); }
|
||||
#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
|
||||
|
||||
} // namespace logging
|
||||
|
||||
namespace {
|
||||
|
||||
template <typename T>
|
||||
template<typename T>
|
||||
QString print_duration(T duration, const std::string &unit) {
|
||||
return QString("%1%2").arg(duration.count()).arg(unit.c_str());
|
||||
}
|
||||
@@ -384,4 +391,3 @@ QDebug operator<<(QDebug dbg, std::chrono::seconds secs) {
|
||||
dbg.nospace() << print_duration(secs, "s");
|
||||
return dbg.space();
|
||||
}
|
||||
|
||||
|
||||
@@ -68,28 +68,27 @@ enum Level {
|
||||
};
|
||||
|
||||
void Init();
|
||||
void SetLevels(const QString& levels);
|
||||
void SetLevels(const QString &levels);
|
||||
|
||||
void DumpStackTrace();
|
||||
|
||||
QDebug CreateLoggerInfo(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 CreateLoggerInfo(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);
|
||||
|
||||
#ifdef QT_NO_WARNING_OUTPUT
|
||||
QNoDebug CreateLoggerWarning(int, const char*, const char*);
|
||||
#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
|
||||
|
||||
#ifdef QT_NO_DEBUG_OUTPUT
|
||||
QNoDebug CreateLoggerDebug(int, const char*, const char*);
|
||||
#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
|
||||
|
||||
|
||||
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;
|
||||
|
||||
|
||||
@@ -98,8 +98,8 @@ void _MessageHandlerBase::DeviceReadyRead() {
|
||||
void _MessageHandlerBase::WriteMessage(const QByteArray &data) {
|
||||
|
||||
QDataStream s(device_);
|
||||
s << quint32(data.length());
|
||||
s.writeRawData(data.data(), data.length());
|
||||
s << static_cast<quint32>(data.length());
|
||||
s.writeRawData(data.data(), static_cast<int>(data.length()));
|
||||
|
||||
// Sorry.
|
||||
if (flush_abstract_socket_) {
|
||||
|
||||
@@ -35,9 +35,6 @@
|
||||
|
||||
class QIODevice;
|
||||
|
||||
#define QStringFromStdString(x) QString::fromUtf8((x).data(), (x).size())
|
||||
#define DataCommaSizeFromQString(x) (x).toUtf8().constData(), (x).toUtf8().length()
|
||||
|
||||
// Reads and writes uint32 length encoded protobufs to a socket.
|
||||
// This base QObject is separate from AbstractMessageHandler because moc can't handle templated classes.
|
||||
// Use AbstractMessageHandler instead.
|
||||
@@ -79,14 +76,14 @@ class _MessageHandlerBase : public QObject {
|
||||
|
||||
// Reads and writes uint32 length encoded MessageType messages to a socket.
|
||||
// You should subclass this and implement the MessageArrived(MessageType) method.
|
||||
template <typename MT>
|
||||
template<typename MT>
|
||||
class AbstractMessageHandler : public _MessageHandlerBase {
|
||||
public:
|
||||
AbstractMessageHandler(QIODevice *device, QObject *parent);
|
||||
~AbstractMessageHandler() override { AbstractMessageHandler::AbortAll(); }
|
||||
|
||||
typedef MT MessageType;
|
||||
typedef MessageReply<MT> ReplyType;
|
||||
using MessageType = MT;
|
||||
using ReplyType = MessageReply<MT>;
|
||||
|
||||
// Serialises the message and writes it to the socket.
|
||||
// This version MUST be called from the thread in which the AbstractMessageHandler was created.
|
||||
@@ -115,11 +112,11 @@ class AbstractMessageHandler : public _MessageHandlerBase {
|
||||
QMap<int, ReplyType*> pending_replies_;
|
||||
};
|
||||
|
||||
template <typename MT>
|
||||
template<typename MT>
|
||||
AbstractMessageHandler<MT>::AbstractMessageHandler(QIODevice *device, QObject *parent)
|
||||
: _MessageHandlerBase(device, parent) {}
|
||||
|
||||
template <typename MT>
|
||||
template<typename MT>
|
||||
void AbstractMessageHandler<MT>::SendMessage(const MessageType &message) {
|
||||
Q_ASSERT(QThread::currentThread() == thread());
|
||||
|
||||
@@ -127,7 +124,7 @@ void AbstractMessageHandler<MT>::SendMessage(const MessageType &message) {
|
||||
WriteMessage(QByteArray(data.data(), data.size()));
|
||||
}
|
||||
|
||||
template <typename MT>
|
||||
template<typename MT>
|
||||
void AbstractMessageHandler<MT>::SendMessageAsync(const MessageType &message) {
|
||||
std::string data = message.SerializeAsString();
|
||||
QMetaObject::invokeMethod(this, "WriteMessage", Qt::QueuedConnection, Q_ARG(QByteArray, QByteArray(data.data(), data.size())));
|
||||
|
||||
@@ -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.
|
||||
template <typename MessageType>
|
||||
template<typename MessageType>
|
||||
class MessageReply : public _MessageReplyBase {
|
||||
public:
|
||||
explicit MessageReply(const MessageType &request_message, QObject *parent = nullptr);
|
||||
|
||||
@@ -19,6 +19,9 @@
|
||||
#ifndef WORKERPOOL_H
|
||||
#define WORKERPOOL_H
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#include <cstdio>
|
||||
#include <cstddef>
|
||||
|
||||
#include <QtGlobal>
|
||||
@@ -56,6 +59,8 @@ class _WorkerPoolBase : public QObject {
|
||||
protected slots:
|
||||
virtual void DoStart() {}
|
||||
virtual void NewConnection() {}
|
||||
virtual void ProcessReadyReadStandardOutput() {}
|
||||
virtual void ProcessReadyReadStandardError() {}
|
||||
virtual void ProcessError(QProcess::ProcessError) {}
|
||||
virtual void SendQueuedMessages() {}
|
||||
};
|
||||
@@ -65,14 +70,14 @@ class _WorkerPoolBase : public QObject {
|
||||
// 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.
|
||||
// Instances of HandlerType are created in the WorkerPool's thread.
|
||||
template <typename HandlerType>
|
||||
template<typename HandlerType>
|
||||
class WorkerPool : public _WorkerPoolBase {
|
||||
public:
|
||||
explicit WorkerPool(QObject *parent = nullptr);
|
||||
~WorkerPool() override;
|
||||
|
||||
typedef typename HandlerType::MessageType MessageType;
|
||||
typedef typename HandlerType::ReplyType ReplyType;
|
||||
using MessageType = typename HandlerType::MessageType;
|
||||
using ReplyType = typename HandlerType::ReplyType;
|
||||
|
||||
// Sets the name of the worker executable. This is looked for first in the current directory, and then in $PATH.
|
||||
// You must call this before calling Start().
|
||||
@@ -98,6 +103,8 @@ class WorkerPool : public _WorkerPoolBase {
|
||||
// These are all reimplemented slots, they are called on the WorkerPool's thread.
|
||||
void DoStart() override;
|
||||
void NewConnection() override;
|
||||
void ProcessReadyReadStandardOutput() override;
|
||||
void ProcessReadyReadStandardError() override;
|
||||
void ProcessError(QProcess::ProcessError error) override;
|
||||
void SendQueuedMessages() override;
|
||||
|
||||
@@ -114,9 +121,9 @@ class WorkerPool : public _WorkerPoolBase {
|
||||
// Must only ever be called on my thread.
|
||||
void StartOneWorker(Worker *worker);
|
||||
|
||||
template <typename T>
|
||||
template<typename T>
|
||||
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) {
|
||||
return &(*it);
|
||||
}
|
||||
@@ -124,7 +131,7 @@ class WorkerPool : public _WorkerPoolBase {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
template<typename T>
|
||||
void DeleteQObjectPointerLater(T **p) {
|
||||
if (*p) {
|
||||
(*p)->deleteLater();
|
||||
@@ -151,17 +158,17 @@ class WorkerPool : public _WorkerPoolBase {
|
||||
QAtomicInt next_id_;
|
||||
|
||||
QMutex message_queue_mutex_;
|
||||
QQueue<ReplyType*> message_queue_;
|
||||
QQueue<ReplyType *> message_queue_;
|
||||
};
|
||||
|
||||
|
||||
template <typename HandlerType>
|
||||
template<typename HandlerType>
|
||||
WorkerPool<HandlerType>::WorkerPool(QObject *parent)
|
||||
: _WorkerPoolBase(parent),
|
||||
next_worker_(0),
|
||||
next_id_(0) {
|
||||
: _WorkerPoolBase(parent),
|
||||
worker_count_(1),
|
||||
next_worker_(0),
|
||||
next_id_(0) {
|
||||
|
||||
worker_count_ = qBound(1, QThread::idealThreadCount() / 2, 4);
|
||||
local_server_name_ = qApp->applicationName().toLower();
|
||||
|
||||
if (local_server_name_.isEmpty()) {
|
||||
@@ -170,12 +177,14 @@ WorkerPool<HandlerType>::WorkerPool(QObject *parent)
|
||||
|
||||
}
|
||||
|
||||
template <typename HandlerType>
|
||||
template<typename HandlerType>
|
||||
WorkerPool<HandlerType>::~WorkerPool() {
|
||||
|
||||
for (const Worker &worker : workers_) {
|
||||
if (worker.local_socket_ && worker.process_) {
|
||||
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.
|
||||
qLog(Debug) << "Closing worker socket";
|
||||
@@ -199,30 +208,30 @@ WorkerPool<HandlerType>::~WorkerPool() {
|
||||
|
||||
}
|
||||
|
||||
template <typename HandlerType>
|
||||
template<typename HandlerType>
|
||||
void WorkerPool<HandlerType>::SetWorkerCount(const int count) {
|
||||
Q_ASSERT(workers_.isEmpty());
|
||||
worker_count_ = count;
|
||||
}
|
||||
|
||||
template <typename HandlerType>
|
||||
template<typename HandlerType>
|
||||
void WorkerPool<HandlerType>::SetLocalServerName(const QString &local_server_name) {
|
||||
Q_ASSERT(workers_.isEmpty());
|
||||
local_server_name_ = local_server_name;
|
||||
}
|
||||
|
||||
template <typename HandlerType>
|
||||
template<typename HandlerType>
|
||||
void WorkerPool<HandlerType>::SetExecutableName(const QString &executable_name) {
|
||||
Q_ASSERT(workers_.isEmpty());
|
||||
executable_name_ = executable_name;
|
||||
}
|
||||
|
||||
template <typename HandlerType>
|
||||
template<typename HandlerType>
|
||||
void WorkerPool<HandlerType>::Start() {
|
||||
QMetaObject::invokeMethod(this, "DoStart");
|
||||
}
|
||||
|
||||
template <typename HandlerType>
|
||||
template<typename HandlerType>
|
||||
void WorkerPool<HandlerType>::DoStart() {
|
||||
|
||||
Q_ASSERT(workers_.isEmpty());
|
||||
@@ -234,6 +243,10 @@ void WorkerPool<HandlerType>::DoStart() {
|
||||
|
||||
QStringList search_path;
|
||||
search_path << QCoreApplication::applicationDirPath();
|
||||
#if defined(Q_OS_UNIX)
|
||||
search_path << "/usr/libexec";
|
||||
search_path << "/usr/local/libexec";
|
||||
#endif
|
||||
#if defined(Q_OS_MACOS) && defined(USE_BUNDLE)
|
||||
search_path << QCoreApplication::applicationDirPath() + "/" + USE_BUNDLE_DIR;
|
||||
#endif
|
||||
@@ -261,7 +274,7 @@ void WorkerPool<HandlerType>::DoStart() {
|
||||
|
||||
}
|
||||
|
||||
template <typename HandlerType>
|
||||
template<typename HandlerType>
|
||||
void WorkerPool<HandlerType>::StartOneWorker(Worker *worker) {
|
||||
|
||||
Q_ASSERT(QThread::currentThread() == thread());
|
||||
@@ -276,6 +289,8 @@ void WorkerPool<HandlerType>::StartOneWorker(Worker *worker) {
|
||||
|
||||
QObject::connect(worker->local_server_, &QLocalServer::newConnection, this, &WorkerPool::NewConnection);
|
||||
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
|
||||
forever {
|
||||
@@ -293,17 +308,16 @@ void WorkerPool<HandlerType>::StartOneWorker(Worker *worker) {
|
||||
|
||||
qLog(Debug) << "Starting worker" << worker << executable_path_ << worker->local_server_->fullServerName();
|
||||
|
||||
// Start the process
|
||||
#if defined(Q_OS_WIN32) && defined(QT_NO_DEBUG_OUTPUT) && !defined(ENABLE_WIN32_CONSOLE)
|
||||
#ifdef Q_OS_WIN32
|
||||
worker->process_->setProcessChannelMode(QProcess::SeparateChannels);
|
||||
#else
|
||||
worker->process_->setProcessChannelMode(QProcess::ForwardedChannels);
|
||||
#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() {
|
||||
|
||||
Q_ASSERT(QThread::currentThread() == thread());
|
||||
@@ -328,9 +342,10 @@ void WorkerPool<HandlerType>::NewConnection() {
|
||||
worker->handler_ = new HandlerType(worker->local_socket_, this);
|
||||
|
||||
SendQueuedMessages();
|
||||
|
||||
}
|
||||
|
||||
template <typename HandlerType>
|
||||
template<typename HandlerType>
|
||||
void WorkerPool<HandlerType>::ProcessError(QProcess::ProcessError error) {
|
||||
|
||||
Q_ASSERT(QThread::currentThread() == thread());
|
||||
@@ -358,6 +373,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>
|
||||
typename WorkerPool<HandlerType>::ReplyType*
|
||||
WorkerPool<HandlerType>::NewReply(MessageType *message) {
|
||||
@@ -388,7 +429,7 @@ WorkerPool<HandlerType>::SendMessageWithReply(MessageType *message) {
|
||||
|
||||
}
|
||||
|
||||
template <typename HandlerType>
|
||||
template<typename HandlerType>
|
||||
void WorkerPool<HandlerType>::SendQueuedMessages() {
|
||||
|
||||
QMutexLocker l(&message_queue_mutex_);
|
||||
@@ -410,7 +451,7 @@ void WorkerPool<HandlerType>::SendQueuedMessages() {
|
||||
|
||||
}
|
||||
|
||||
template <typename HandlerType>
|
||||
template<typename HandlerType>
|
||||
HandlerType *WorkerPool<HandlerType>::NextHandler() const {
|
||||
|
||||
for (int i = 0; i < workers_.count(); ++i) {
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
cmake_minimum_required(VERSION 3.0)
|
||||
cmake_minimum_required(VERSION 3.7)
|
||||
|
||||
set(MESSAGES tagreadermessages.proto)
|
||||
set(SOURCES tagreaderbase.cpp)
|
||||
|
||||
if(USE_TAGLIB AND TAGLIB_FOUND)
|
||||
list(APPEND SOURCES tagreadertaglib.cpp)
|
||||
list(APPEND SOURCES tagreadertaglib.cpp tagreadergme.cpp)
|
||||
endif()
|
||||
|
||||
if(USE_TAGPARSER AND TAGPARSER_FOUND)
|
||||
@@ -43,12 +43,21 @@ target_include_directories(libstrawberry-tagreader PRIVATE
|
||||
|
||||
target_link_libraries(libstrawberry-tagreader PRIVATE
|
||||
${GLIB_LIBRARIES}
|
||||
${PROTOBUF_LIBRARY}
|
||||
${Protobuf_LIBRARIES}
|
||||
${QtCore_LIBRARIES}
|
||||
${QtNetwork_LIBRARIES}
|
||||
${QtGui_LIBRARIES}
|
||||
libstrawberry-common
|
||||
)
|
||||
|
||||
if(WIN32 AND Protobuf_VERSION VERSION_GREATER_EQUAL 4.22.0)
|
||||
if (MSVC)
|
||||
target_link_libraries(libstrawberry-tagreader PRIVATE abseil_dll)
|
||||
else()
|
||||
target_link_libraries(libstrawberry-tagreader PRIVATE absl_log_internal_message absl_log_internal_check_op)
|
||||
endif()
|
||||
endif()
|
||||
|
||||
if(USE_TAGLIB AND TAGLIB_FOUND)
|
||||
target_include_directories(libstrawberry-tagreader SYSTEM PRIVATE ${TAGLIB_INCLUDE_DIRS})
|
||||
target_link_libraries(libstrawberry-tagreader PRIVATE ${TAGLIB_LIBRARIES})
|
||||
|
||||
@@ -19,9 +19,125 @@
|
||||
|
||||
#include <string>
|
||||
|
||||
#include <QByteArray>
|
||||
#include <QString>
|
||||
#include <QIODevice>
|
||||
#include <QFile>
|
||||
#include <QBuffer>
|
||||
#include <QImage>
|
||||
#include <QMimeDatabase>
|
||||
|
||||
#include "core/logging.h"
|
||||
#include "tagreaderbase.h"
|
||||
|
||||
const std::string TagReaderBase::kEmbeddedCover = "(embedded)";
|
||||
|
||||
TagReaderBase::TagReaderBase() = default;
|
||||
TagReaderBase::~TagReaderBase() = default;
|
||||
|
||||
float TagReaderBase::ConvertPOPMRating(const int POPM_rating) {
|
||||
|
||||
if (POPM_rating < 0x01) return 0.0F;
|
||||
else if (POPM_rating < 0x40) return 0.20F;
|
||||
else if (POPM_rating < 0x80) return 0.40F;
|
||||
else if (POPM_rating < 0xC0) return 0.60F;
|
||||
else if (POPM_rating < 0xFC) return 0.80F;
|
||||
|
||||
return 1.0F;
|
||||
|
||||
}
|
||||
|
||||
int TagReaderBase::ConvertToPOPMRating(const float rating) {
|
||||
|
||||
if (rating < 0.20) return 0x00;
|
||||
else if (rating < 0.40) return 0x01;
|
||||
else if (rating < 0.60) return 0x40;
|
||||
else if (rating < 0.80) return 0x80;
|
||||
else if (rating < 1.0) return 0xC0;
|
||||
|
||||
return 0xFF;
|
||||
|
||||
}
|
||||
|
||||
QByteArray TagReaderBase::LoadCoverDataFromRequest(const spb::tagreader::SaveFileRequest &request) {
|
||||
|
||||
if (!request.has_save_cover() || !request.save_cover()) {
|
||||
return QByteArray();
|
||||
}
|
||||
|
||||
const QString song_filename = QString::fromUtf8(request.filename().data(), request.filename().size());
|
||||
QString cover_filename;
|
||||
if (request.has_cover_filename()) {
|
||||
cover_filename = QString::fromUtf8(request.cover_filename().data(), request.cover_filename().size());
|
||||
}
|
||||
QByteArray cover_data;
|
||||
if (request.has_cover_data()) {
|
||||
cover_data = QByteArray(request.cover_data().data(), request.cover_data().size());
|
||||
}
|
||||
bool cover_is_jpeg = false;
|
||||
if (request.has_cover_is_jpeg()) {
|
||||
cover_is_jpeg = request.cover_is_jpeg();
|
||||
}
|
||||
|
||||
return LoadCoverDataFromRequest(song_filename, cover_filename, cover_data, cover_is_jpeg);
|
||||
|
||||
}
|
||||
|
||||
QByteArray TagReaderBase::LoadCoverDataFromRequest(const spb::tagreader::SaveEmbeddedArtRequest &request) {
|
||||
|
||||
const QString song_filename = QString::fromUtf8(request.filename().data(), request.filename().size());
|
||||
QString cover_filename;
|
||||
if (request.has_cover_filename()) {
|
||||
cover_filename = QString::fromUtf8(request.cover_filename().data(), request.cover_filename().size());
|
||||
}
|
||||
QByteArray cover_data;
|
||||
if (request.has_cover_data()) {
|
||||
cover_data = QByteArray(request.cover_data().data(), request.cover_data().size());
|
||||
}
|
||||
bool cover_is_jpeg = false;
|
||||
if (request.has_cover_is_jpeg()) {
|
||||
cover_is_jpeg = request.cover_is_jpeg();
|
||||
}
|
||||
|
||||
return LoadCoverDataFromRequest(song_filename, cover_filename, cover_data, cover_is_jpeg);
|
||||
|
||||
}
|
||||
|
||||
QByteArray TagReaderBase::LoadCoverDataFromRequest(const QString &song_filename, const QString &cover_filename, QByteArray cover_data, const bool cover_is_jpeg) {
|
||||
|
||||
if (!cover_data.isEmpty() && cover_is_jpeg) {
|
||||
qLog(Debug) << "Using cover from JPEG data for" << song_filename;
|
||||
return cover_data;
|
||||
}
|
||||
|
||||
if (cover_data.isEmpty() && !cover_filename.isEmpty()) {
|
||||
qLog(Debug) << "Loading cover from" << cover_filename << "for" << song_filename;
|
||||
QFile file(cover_filename);
|
||||
if (!file.open(QIODevice::ReadOnly)) {
|
||||
qLog(Error) << "Failed to open file" << cover_filename << "for reading:" << file.errorString();
|
||||
return QByteArray();
|
||||
}
|
||||
cover_data = file.readAll();
|
||||
file.close();
|
||||
}
|
||||
|
||||
if (!cover_data.isEmpty()) {
|
||||
if (QMimeDatabase().mimeTypeForData(cover_data).name() == "image/jpeg") {
|
||||
qLog(Debug) << "Using cover from JPEG data for" << song_filename;
|
||||
return cover_data;
|
||||
}
|
||||
// Convert image to JPEG.
|
||||
qLog(Debug) << "Converting cover to JPEG data for" << song_filename;
|
||||
QImage cover_image(cover_data);
|
||||
cover_data.clear();
|
||||
QBuffer buffer(&cover_data);
|
||||
if (buffer.open(QIODevice::WriteOnly)) {
|
||||
cover_image.save(&buffer, "JPEG");
|
||||
buffer.close();
|
||||
}
|
||||
return cover_data;
|
||||
}
|
||||
|
||||
return QByteArray();
|
||||
|
||||
}
|
||||
|
||||
@@ -38,11 +38,23 @@ class 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 bool ReadFile(const QString &filename, spb::tagreader::SongMetadata *song) const = 0;
|
||||
virtual bool SaveFile(const spb::tagreader::SaveFileRequest &request) const = 0;
|
||||
|
||||
virtual QByteArray LoadEmbeddedArt(const QString &filename) const = 0;
|
||||
virtual bool SaveEmbeddedArt(const QString &filename, const QByteArray &data) = 0;
|
||||
virtual bool SaveEmbeddedArt(const spb::tagreader::SaveEmbeddedArtRequest &request) const = 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;
|
||||
|
||||
static float ConvertPOPMRating(const int POPM_rating);
|
||||
static int ConvertToPOPMRating(const float rating);
|
||||
|
||||
static QByteArray LoadCoverDataFromRequest(const spb::tagreader::SaveFileRequest &request);
|
||||
static QByteArray LoadCoverDataFromRequest(const spb::tagreader::SaveEmbeddedArtRequest &request);
|
||||
|
||||
private:
|
||||
static QByteArray LoadCoverDataFromRequest(const QString &song_filename, const QString &cover_filename, QByteArray cover_data, const bool cover_is_jpeg);
|
||||
|
||||
protected:
|
||||
static const std::string kEmbeddedCover;
|
||||
|
||||
300
ext/libstrawberry-tagreader/tagreadergme.cpp
Normal file
@@ -0,0 +1,300 @@
|
||||
/*
|
||||
* Strawberry Music Player
|
||||
* Copyright 2022, Eoin O'Neill <eoinoneill1991@gmail.com>
|
||||
*
|
||||
* 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 "tagreadergme.h"
|
||||
|
||||
#include <tag.h>
|
||||
#include <apefile.h>
|
||||
|
||||
#include <QByteArray>
|
||||
#include <QString>
|
||||
#include <QChar>
|
||||
#include <QFileInfo>
|
||||
#include <QFile>
|
||||
#include <QTextStream>
|
||||
|
||||
#include "utilities/timeconstants.h"
|
||||
#include "core/logging.h"
|
||||
#include "core/messagehandler.h"
|
||||
#include "tagreaderbase.h"
|
||||
#include "tagreadertaglib.h"
|
||||
|
||||
bool GME::IsSupportedFormat(const QFileInfo &file_info) {
|
||||
return file_info.exists() && (file_info.completeSuffix().endsWith("spc", Qt::CaseInsensitive) || file_info.completeSuffix().endsWith("vgm"), Qt::CaseInsensitive);
|
||||
}
|
||||
|
||||
bool GME::ReadFile(const QFileInfo &file_info, spb::tagreader::SongMetadata *song_info) {
|
||||
|
||||
if (file_info.completeSuffix().endsWith("spc"), Qt::CaseInsensitive) {
|
||||
SPC::Read(file_info, song_info);
|
||||
return true;
|
||||
}
|
||||
if (file_info.completeSuffix().endsWith("vgm", Qt::CaseInsensitive)) {
|
||||
VGM::Read(file_info, song_info);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
|
||||
}
|
||||
|
||||
quint32 GME::UnpackBytes32(const char *const bytes, size_t length) {
|
||||
|
||||
Q_ASSERT(length <= 4 && length > 0);
|
||||
|
||||
quint32 value = 0;
|
||||
for (size_t i = 0; i < length; i++) {
|
||||
value |= static_cast<unsigned char>(bytes[i]) << (8 * i);
|
||||
}
|
||||
|
||||
return value;
|
||||
|
||||
}
|
||||
|
||||
void GME::SPC::Read(const QFileInfo &file_info, spb::tagreader::SongMetadata *song_info) {
|
||||
|
||||
QFile file(file_info.filePath());
|
||||
if (!file.open(QIODevice::ReadOnly)) return;
|
||||
|
||||
qLog(Debug) << "Reading tags from SPC file" << file_info.fileName();
|
||||
|
||||
// Check for header -- more reliable than file name alone.
|
||||
if (!file.read(33).startsWith(QString("SNES-SPC700").toLatin1())) return;
|
||||
|
||||
// First order of business -- get any tag values that exist within the core file information.
|
||||
// These only allow for a certain number of bytes per field,
|
||||
// so they will likely be overwritten either by the id666 standard or the APETAG format (as used by other players, such as foobar and winamp)
|
||||
//
|
||||
// Make sure to check id6 documentation before changing the read values!
|
||||
|
||||
file.seek(HAS_ID6_OFFSET);
|
||||
const QByteArray id6_status = file.read(1);
|
||||
const bool has_id6 = id6_status.length() >= 1 && id6_status[0] == static_cast<char>(xID6_STATUS::ON);
|
||||
|
||||
file.seek(SONG_TITLE_OFFSET);
|
||||
song_info->set_title(QString::fromLatin1(file.read(32)).toStdString());
|
||||
|
||||
file.seek(GAME_TITLE_OFFSET);
|
||||
song_info->set_album(QString::fromLatin1(file.read(32)).toStdString());
|
||||
|
||||
file.seek(ARTIST_OFFSET);
|
||||
song_info->set_artist(QString::fromLatin1(file.read(32)).toStdString());
|
||||
|
||||
file.seek(INTRO_LENGTH_OFFSET);
|
||||
QByteArray length_bytes = file.read(INTRO_LENGTH_SIZE);
|
||||
quint64 length_in_sec = 0;
|
||||
if (length_bytes.size() >= INTRO_LENGTH_SIZE) {
|
||||
length_in_sec = ConvertSPCStringToNum(length_bytes);
|
||||
|
||||
if (!length_in_sec || length_in_sec >= 0x1FFF) {
|
||||
// This means that parsing the length as a string failed, so get value LE.
|
||||
length_in_sec = length_bytes[0] | (length_bytes[1] << 8) | (length_bytes[2] << 16);
|
||||
}
|
||||
|
||||
if (length_in_sec < 0x1FFF) {
|
||||
song_info->set_length_nanosec(length_in_sec * kNsecPerSec);
|
||||
}
|
||||
}
|
||||
|
||||
file.seek(FADE_LENGTH_OFFSET);
|
||||
QByteArray fade_bytes = file.read(FADE_LENGTH_SIZE);
|
||||
if (fade_bytes.size() >= FADE_LENGTH_SIZE) {
|
||||
quint64 fade_length_in_ms = ConvertSPCStringToNum(fade_bytes);
|
||||
|
||||
if (fade_length_in_ms > 0x7FFF) {
|
||||
fade_length_in_ms = fade_bytes[0] | (fade_bytes[1] << 8) | (fade_bytes[2] << 16) | (fade_bytes[3] << 24);
|
||||
}
|
||||
}
|
||||
|
||||
// Check for XID6 data -- this is infrequently used, but being able to fill in data from this is ideal before trying to rely on APETAG values.
|
||||
// XID6 format follows EA's binary file format standard named "IFF"
|
||||
file.seek(XID6_OFFSET);
|
||||
if (has_id6 && file.read(4) == QString("xid6").toLatin1()) {
|
||||
QByteArray xid6_head_data = file.read(4);
|
||||
if (xid6_head_data.size() >= 4) {
|
||||
qint64 xid6_size = xid6_head_data[0] | (xid6_head_data[1] << 8) | (xid6_head_data[2] << 16) | xid6_head_data[3];
|
||||
// This should be the size remaining for entire ID6 block, but it seems that most files treat this as the size of the remaining header space...
|
||||
|
||||
qLog(Debug) << file_info.fileName() << "has ID6 tag.";
|
||||
|
||||
while ((file.pos()) + 4 < XID6_OFFSET + xid6_size) {
|
||||
QByteArray arr = file.read(4);
|
||||
if (arr.size() < 4) break;
|
||||
|
||||
qint8 id = arr[0];
|
||||
qint8 type = arr[1];
|
||||
Q_UNUSED(id);
|
||||
Q_UNUSED(type);
|
||||
qint16 length = arr[2] | (arr[3] << 8);
|
||||
|
||||
file.read(GetNextMemAddressAlign32bit(length));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Music Players that support SPC tend to support additional tagging data as
|
||||
// an APETAG entry at the bottom of the file instead of writing into the xid6 tagging space.
|
||||
// This is where a lot of the extra data for a file is stored, such as genre or replaygain data.
|
||||
// This data is currently supported by TagLib, so we will simply use that for the remaining values.
|
||||
TagLib::APE::File ape(file_info.filePath().toStdString().data());
|
||||
if (ape.hasAPETag()) {
|
||||
TagLib::Tag *tag = ape.tag();
|
||||
if (!tag) return;
|
||||
|
||||
TagReaderTagLib::TStringToStdString(tag->artist(), song_info->mutable_artist());
|
||||
TagReaderTagLib::TStringToStdString(tag->album(), song_info->mutable_album());
|
||||
TagReaderTagLib::TStringToStdString(tag->title(), song_info->mutable_title());
|
||||
TagReaderTagLib::TStringToStdString(tag->genre(), song_info->mutable_genre());
|
||||
song_info->set_track(tag->track());
|
||||
song_info->set_year(tag->year());
|
||||
}
|
||||
|
||||
song_info->set_valid(true);
|
||||
song_info->set_filetype(spb::tagreader::SongMetadata_FileType_SPC);
|
||||
|
||||
}
|
||||
|
||||
qint16 GME::SPC::GetNextMemAddressAlign32bit(qint16 input) {
|
||||
return ((input + 0x3) & ~0x3);
|
||||
// Plus 0x3 for rounding up (not down), AND NOT to flatten out on a 32 bit level.
|
||||
}
|
||||
|
||||
quint64 GME::SPC::ConvertSPCStringToNum(const QByteArray &arr) {
|
||||
|
||||
quint64 result = 0;
|
||||
for (auto it = arr.begin(); it != arr.end(); it++) {
|
||||
unsigned int num = *it - '0';
|
||||
if (num > 9) break;
|
||||
result = (result * 10) + num; // Shift Left and add.
|
||||
}
|
||||
|
||||
return result;
|
||||
|
||||
}
|
||||
|
||||
void GME::VGM::Read(const QFileInfo &file_info, spb::tagreader::SongMetadata *song_info) {
|
||||
|
||||
QFile file(file_info.filePath());
|
||||
if (!file.open(QIODevice::ReadOnly)) return;
|
||||
|
||||
qLog(Debug) << "Reading tags from VGM file" << file_info.fileName();
|
||||
|
||||
if (!file.read(4).startsWith(QString("Vgm ").toLatin1())) return;
|
||||
|
||||
file.seek(GD3_TAG_PTR);
|
||||
QByteArray gd3_head = file.read(4);
|
||||
if (gd3_head.size() < 4) return;
|
||||
|
||||
quint64 pt = GME::UnpackBytes32(gd3_head.constData(), gd3_head.size());
|
||||
|
||||
file.seek(SAMPLE_COUNT);
|
||||
QByteArray sample_count_bytes = file.read(4);
|
||||
file.seek(LOOP_SAMPLE_COUNT);
|
||||
QByteArray loop_count_bytes = file.read(4);
|
||||
quint64 length = 0;
|
||||
|
||||
if (!GetPlaybackLength(sample_count_bytes, loop_count_bytes, length)) return;
|
||||
|
||||
file.seek(GD3_TAG_PTR + pt);
|
||||
QByteArray gd3_version = file.read(4);
|
||||
|
||||
file.seek(file.pos() + 4);
|
||||
QByteArray gd3_length_bytes = file.read(4);
|
||||
quint32 gd3_length = GME::UnpackBytes32(gd3_length_bytes.constData(), gd3_length_bytes.size());
|
||||
|
||||
QByteArray gd3Data = file.read(gd3_length);
|
||||
QTextStream fileTagStream(gd3Data, QIODevice::ReadOnly);
|
||||
// Stored as 16 bit UTF string, two bytes per letter.
|
||||
#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
|
||||
fileTagStream.setEncoding(QStringConverter::Utf16);
|
||||
#else
|
||||
fileTagStream.setCodec("UTF-16");
|
||||
#endif
|
||||
QStringList strings = fileTagStream.readLine(0).split(QChar('\0'));
|
||||
if (strings.count() < 10) return;
|
||||
|
||||
// VGM standard dictates string tag data exist in specific order.
|
||||
// Order alternates between English and Japanese version of data.
|
||||
// Read GD3 tag standard for more details.
|
||||
song_info->set_title(strings[0].toStdString());
|
||||
song_info->set_album(strings[2].toStdString());
|
||||
song_info->set_artist(strings[6].toStdString());
|
||||
song_info->set_year(strings[8].left(4).toInt());
|
||||
song_info->set_length_nanosec(length * kNsecPerMsec);
|
||||
song_info->set_valid(true);
|
||||
song_info->set_filetype(spb::tagreader::SongMetadata_FileType_VGM);
|
||||
|
||||
}
|
||||
|
||||
bool GME::VGM::GetPlaybackLength(const QByteArray &sample_count_bytes, const QByteArray &loop_count_bytes, quint64 &out_length) {
|
||||
|
||||
if (sample_count_bytes.size() != 4) return false;
|
||||
if (loop_count_bytes.size() != 4) return false;
|
||||
|
||||
quint64 sample_count = GME::UnpackBytes32(sample_count_bytes.constData(), sample_count_bytes.size());
|
||||
|
||||
if (sample_count <= 0) return false;
|
||||
|
||||
quint64 loop_sample_count = GME::UnpackBytes32(loop_count_bytes.constData(), loop_count_bytes.size());
|
||||
|
||||
if (loop_sample_count <= 0) {
|
||||
out_length = sample_count * 1000 / SAMPLE_TIMEBASE;
|
||||
return true;
|
||||
}
|
||||
|
||||
quint64 intro_length_ms = (sample_count - loop_sample_count) * 1000 / SAMPLE_TIMEBASE;
|
||||
quint64 loop_length_ms = (loop_sample_count) * 1000 / SAMPLE_TIMEBASE;
|
||||
out_length = intro_length_ms + (loop_length_ms * 2) + GST_GME_LOOP_TIME_MS;
|
||||
|
||||
return true;
|
||||
|
||||
}
|
||||
|
||||
TagReaderGME::TagReaderGME() = default;
|
||||
TagReaderGME::~TagReaderGME() = default;
|
||||
|
||||
bool TagReaderGME::IsMediaFile(const QString &filename) const {
|
||||
QFileInfo fileinfo(filename);
|
||||
return GME::IsSupportedFormat(fileinfo);
|
||||
}
|
||||
|
||||
bool TagReaderGME::ReadFile(const QString &filename, spb::tagreader::SongMetadata *song) const {
|
||||
QFileInfo fileinfo(filename);
|
||||
return GME::ReadFile(fileinfo, song);
|
||||
}
|
||||
|
||||
bool TagReaderGME::SaveFile(const spb::tagreader::SaveFileRequest&) const {
|
||||
return false;
|
||||
}
|
||||
|
||||
QByteArray TagReaderGME::LoadEmbeddedArt(const QString&) const {
|
||||
return QByteArray();
|
||||
}
|
||||
|
||||
bool TagReaderGME::SaveEmbeddedArt(const spb::tagreader::SaveEmbeddedArtRequest&) const {
|
||||
return false;
|
||||
}
|
||||
|
||||
bool TagReaderGME::SaveSongPlaycountToFile(const QString&, const spb::tagreader::SongMetadata&) const {
|
||||
return false;
|
||||
}
|
||||
|
||||
bool TagReaderGME::SaveSongRatingToFile(const QString&, const spb::tagreader::SongMetadata&) const {
|
||||
return false;
|
||||
}
|
||||