Compare commits
397 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
fc07919a75 | ||
|
|
d7661edf67 | ||
|
|
1c91693294 | ||
|
|
3fc3cbc6d5 | ||
|
|
11b5895e69 | ||
|
|
b4c614edbf | ||
|
|
deddaed04a | ||
|
|
a155e503f4 | ||
|
|
c0663bc19f | ||
|
|
7ffa51b83d | ||
|
|
6d397b9988 | ||
|
|
571a7fa26b | ||
|
|
b3b5a38c3a | ||
|
|
a4b115f89b | ||
|
|
3d672bb145 | ||
|
|
15b656b753 | ||
|
|
f5785db163 | ||
|
|
1ff1bf3292 | ||
|
|
b062febea0 | ||
|
|
35301dc79e | ||
|
|
30c336726b | ||
|
|
3821680817 | ||
|
|
74242ea24f | ||
|
|
73c7024e11 | ||
|
|
bbdec92dc6 | ||
|
|
b4c289101c | ||
|
|
722d0797f6 | ||
|
|
deb27d5b55 | ||
|
|
9cc4ffdf6e | ||
|
|
c76d63d1d9 | ||
|
|
21bb4f33ad | ||
|
|
2897b881d6 | ||
|
|
e9b89d0929 | ||
|
|
e801254b2e | ||
|
|
6f49918ee9 | ||
|
|
0ae7c18f1f | ||
|
|
c9c3fb396a | ||
|
|
91db4f1934 | ||
|
|
9bbed6e95c | ||
|
|
748bc27b25 | ||
|
|
f42708e8bc | ||
|
|
160e4570a2 | ||
|
|
6272965143 | ||
|
|
1e9613bf7f | ||
|
|
a061dac298 | ||
|
|
914dee8571 | ||
|
|
47e2905edf | ||
|
|
ee6675aee0 | ||
|
|
62e0d9fe64 | ||
|
|
a174c142c1 | ||
|
|
95afc5fdec | ||
|
|
04d69f66c0 | ||
|
|
0347141edd | ||
|
|
1d6baae6e0 | ||
|
|
fb0f48f08a | ||
|
|
76e5e03d31 | ||
|
|
4cab743634 | ||
|
|
7c10ec97b7 | ||
|
|
8718a16889 | ||
|
|
75a0b924c3 | ||
|
|
83a90e0c05 | ||
|
|
e5eadd1315 | ||
|
|
f0142d90d4 | ||
|
|
cabd6e6e9d | ||
|
|
4804a05736 | ||
|
|
c258e5a3af | ||
|
|
e8492940a5 | ||
|
|
4bccb1ab47 | ||
|
|
b6d219e232 | ||
|
|
23ee17594d | ||
|
|
d9d39d8e25 | ||
|
|
224d5d46c1 | ||
|
|
09e0059930 | ||
|
|
0ddff2b087 | ||
|
|
2e6a29eacc | ||
|
|
ad2fb82aa9 | ||
|
|
a3f91c11e8 | ||
|
|
a50c978ce3 | ||
|
|
27d6f881cd | ||
|
|
944cd020af | ||
|
|
bbe5d64b99 | ||
|
|
f7b36ac4c7 | ||
|
|
1d555ca17e | ||
|
|
f91b6c3468 | ||
|
|
abe6eeb350 | ||
|
|
64f90a7912 | ||
|
|
5733966843 | ||
|
|
eb1344fcec | ||
|
|
c5fb29f00e | ||
|
|
63135b9c54 | ||
|
|
f7c666584e | ||
|
|
6834324de2 | ||
|
|
3a0d59e66f | ||
|
|
ffd2e2188a | ||
|
|
0e8d5bdc5d | ||
|
|
14806f6614 | ||
|
|
00ece83b9d | ||
|
|
8197ae2a2d | ||
|
|
5fe658bb16 | ||
|
|
617179f0c6 | ||
|
|
95ac85f642 | ||
|
|
ca8877ad47 | ||
|
|
6d8f31048c | ||
|
|
ac859eb576 | ||
|
|
2dfa171b6a | ||
|
|
6f72e3e2ea | ||
|
|
c2b73ae963 | ||
|
|
6c50077409 | ||
|
|
06746449a1 | ||
|
|
da7b8edf51 | ||
|
|
6e29b41f23 | ||
|
|
912bb069af | ||
|
|
6b2d7a67d8 | ||
|
|
dbb8ec0290 | ||
|
|
60b32760f2 | ||
|
|
73a40bcb49 | ||
|
|
0e258a5a32 | ||
|
|
7ca65c81d8 | ||
|
|
2ad1a60e59 | ||
|
|
cf17ff4478 | ||
|
|
fffc3aac68 | ||
|
|
295ac3c458 | ||
|
|
b6693a71f9 | ||
|
|
5b21118a8c | ||
|
|
0235b19801 | ||
|
|
7426399aa2 | ||
|
|
6861b0d668 | ||
|
|
c30fb0d38c | ||
|
|
e45521c6c0 | ||
|
|
d11fe8d4fc | ||
|
|
8e83e63e3d | ||
|
|
c8fd0ac4b3 | ||
|
|
d78419eb33 | ||
|
|
e44a3d013d | ||
|
|
aeb0d05017 | ||
|
|
62702e4b3d | ||
|
|
5146cdfa2f | ||
|
|
246e7018c3 | ||
|
|
24286dbe9d | ||
|
|
2f442dfbe1 | ||
|
|
b2fb01ee9c | ||
|
|
45e0a9a4ef | ||
|
|
675b7b4bf4 | ||
|
|
be7a35443e | ||
|
|
9918615fcd | ||
|
|
4b72ef77c1 | ||
|
|
e427c61fbb | ||
|
|
c78e8937d5 | ||
|
|
5d63f7a93d | ||
|
|
46551ada7f | ||
|
|
83f17a37b1 | ||
|
|
9e23e0a623 | ||
|
|
4a53d4f043 | ||
|
|
0fd61945c7 | ||
|
|
e3624eed30 | ||
|
|
1923c8be0c | ||
|
|
a3e37fbfe2 | ||
|
|
318c3bb422 | ||
|
|
db96e24028 | ||
|
|
cc2cce66df | ||
|
|
7afeabd288 | ||
|
|
eb43a812d6 | ||
|
|
12cbcdb6f4 | ||
|
|
a2e35e30dc | ||
|
|
b6ff7e6b47 | ||
|
|
6dba40c6bb | ||
|
|
896da46422 | ||
|
|
7c07f5eb2a | ||
|
|
298cff37de | ||
|
|
8add802fe9 | ||
|
|
6d080a0d59 | ||
|
|
f0ae1051ee | ||
|
|
1ad0ffeaa6 | ||
|
|
45b44d012d | ||
|
|
cb6cbb9ee5 | ||
|
|
013a0d9cb0 | ||
|
|
8abb3ea225 | ||
|
|
74a5233b5d | ||
|
|
cd695a4522 | ||
|
|
eec4b2cc0e | ||
|
|
616875d0d2 | ||
|
|
eb749bd76f | ||
|
|
7d1e404efd | ||
|
|
4a01a578d1 | ||
|
|
393a2e0ea0 | ||
|
|
8e21decb8d | ||
|
|
22bfbab832 | ||
|
|
28d0cc8795 | ||
|
|
51f2383a07 | ||
|
|
a6426c6eba | ||
|
|
0631da6c8e | ||
|
|
3960239cfe | ||
|
|
b16e48af6f | ||
|
|
4d3950565a | ||
|
|
8d24bc50c9 | ||
|
|
458efe9168 | ||
|
|
ae940a8681 | ||
|
|
d76b223b7c | ||
|
|
7243d5f7cb | ||
|
|
387f74f66a | ||
|
|
61ffb7d97a | ||
|
|
a553693f34 | ||
|
|
cdcfd64ec4 | ||
|
|
7f4302ba20 | ||
|
|
0d94f2d376 | ||
|
|
c1eb20a20b | ||
|
|
634db67685 | ||
|
|
86648258a3 | ||
|
|
287f0a3739 | ||
|
|
8f42df209a | ||
|
|
390fd64a74 | ||
|
|
48ee471def | ||
|
|
e862afcb78 | ||
|
|
872da05ff6 | ||
|
|
d09e2daf00 | ||
|
|
e2d5b44b0a | ||
|
|
52d42ef2a8 | ||
|
|
b6abc34461 | ||
|
|
ae6a50626d | ||
|
|
8f9dbfee2c | ||
|
|
c71ccda967 | ||
|
|
0f284f2e1d | ||
|
|
45ac80dd8c | ||
|
|
09cdba4b3d | ||
|
|
d94ee8863c | ||
|
|
cc8ced6430 | ||
|
|
cdb144e6a6 | ||
|
|
58fb8643c6 | ||
|
|
b81cfa8acb | ||
|
|
938ee20f1f | ||
|
|
d02dc54c1b | ||
|
|
8680a54ae4 | ||
|
|
30001be0ee | ||
|
|
4614cb5ec1 | ||
|
|
e390f3a399 | ||
|
|
e22d463d11 | ||
|
|
5877aa822c | ||
|
|
a2915913bb | ||
|
|
a8b40747b2 | ||
|
|
b63030d302 | ||
|
|
4457e416db | ||
|
|
c7c1a8ede1 | ||
|
|
fd1e9d7fb0 | ||
|
|
59a6d2317b | ||
|
|
531c171542 | ||
|
|
0c743452b0 | ||
|
|
4893d3da1f | ||
|
|
5cdc24bfb0 | ||
|
|
523cdca449 | ||
|
|
09537d73da | ||
|
|
99c0c0b3b1 | ||
|
|
45bc353341 | ||
|
|
b2fc41a911 | ||
|
|
ebefe8b6d2 | ||
|
|
9e3508134b | ||
|
|
3166ca2127 | ||
|
|
204508478f | ||
|
|
51ce674c97 | ||
|
|
6fcde9fe5f | ||
|
|
c688e3431d | ||
|
|
230376c7f3 | ||
|
|
91d0a2cd0c | ||
|
|
1ffd010e4a | ||
|
|
ae366e141f | ||
|
|
eb869c6e97 | ||
|
|
b559f7cd16 | ||
|
|
375d32f08e | ||
|
|
89d6b7cec0 | ||
|
|
fdf96e8342 | ||
|
|
c5d73d7b09 | ||
|
|
34b4cc2d9e | ||
|
|
40ed17e609 | ||
|
|
673d76af45 | ||
|
|
c13fb6f9d5 | ||
|
|
424b1e7d1f | ||
|
|
e80c9c4101 | ||
|
|
f75c3633e9 | ||
|
|
d535cd8276 | ||
|
|
c13f6ab2af | ||
|
|
d2a30bfb78 | ||
|
|
e5b17092b4 | ||
|
|
466cb4c78b | ||
|
|
9722e1ddc4 | ||
|
|
e3a9b0b943 | ||
|
|
d668a8aee2 | ||
|
|
75dc0aafcf | ||
|
|
41b94233c6 | ||
|
|
52cff01b9c | ||
|
|
372fc6603d | ||
|
|
9221797c9d | ||
|
|
7501131558 | ||
|
|
bba7be99a3 | ||
|
|
abb95534d0 | ||
|
|
0b7b4c12e4 | ||
|
|
4056f00169 | ||
|
|
10303cb9c0 | ||
|
|
e3587d369e | ||
|
|
2a048502cc | ||
|
|
cac01fbde9 | ||
|
|
2d2ce191ec | ||
|
|
6380cb8458 | ||
|
|
48e0e6af71 | ||
|
|
b756bccc7a | ||
|
|
ae8eed7a67 | ||
|
|
ae4882bec3 | ||
|
|
1379741859 | ||
|
|
b45c7ace78 | ||
|
|
3843d9f55b | ||
|
|
f3422cb2fe | ||
|
|
73692797dc | ||
|
|
31dd910289 | ||
|
|
da51580299 | ||
|
|
9db148b1ec | ||
|
|
091b1b8209 | ||
|
|
900a4071ed | ||
|
|
df4b2f7938 | ||
|
|
803f44d9bc | ||
|
|
4b67aee020 | ||
|
|
71dc47d6c9 | ||
|
|
9cb305fb0d | ||
|
|
1672130486 | ||
|
|
8e135e3e79 | ||
|
|
aa1a4f6ea5 | ||
|
|
ba34cf5258 | ||
|
|
647089d2a8 | ||
|
|
5211508eb4 | ||
|
|
e6f05ae465 | ||
|
|
a9193f9b76 | ||
|
|
e373a17cd3 | ||
|
|
ebab9b7e4a | ||
|
|
6de0399807 | ||
|
|
5cc7bb80f6 | ||
|
|
6e0bd9b3f8 | ||
|
|
d1943f72d3 | ||
|
|
81709873bd | ||
|
|
f6bb7cd8ed | ||
|
|
d1c19e431c | ||
|
|
9ab2dde8ab | ||
|
|
71559bb125 | ||
|
|
ae4c95262c | ||
|
|
dbbf07c9c1 | ||
|
|
ab8cd619d5 | ||
|
|
c30ad2819d | ||
|
|
311e91797a | ||
|
|
52be4df355 | ||
|
|
0364e81050 | ||
|
|
2d49b71bc9 | ||
|
|
a18a4bdf31 | ||
|
|
d3acbe8288 | ||
|
|
22afcbcbb6 | ||
|
|
495c6bc21c | ||
|
|
cfd1fe59f3 | ||
|
|
c46cf5bc84 | ||
|
|
a8742557bd | ||
|
|
3bea58cf56 | ||
|
|
5aaa5231b8 | ||
|
|
82d10dd7cb | ||
|
|
841065fb91 | ||
|
|
c7146ef138 | ||
|
|
08f32d1de6 | ||
|
|
4c3f86aa4d | ||
|
|
445cf22333 | ||
|
|
4919de647a | ||
|
|
77fae99528 | ||
|
|
10bd4b40b9 | ||
|
|
9d8e6bb253 | ||
|
|
49c71ecfad | ||
|
|
d18834b415 | ||
|
|
e52cda193e | ||
|
|
0d5edd35ea | ||
|
|
f3f51c3d9d | ||
|
|
1431916183 | ||
|
|
ae48008803 | ||
|
|
5664813931 | ||
|
|
3948af80b8 | ||
|
|
343d6f9aff | ||
|
|
51b379502f | ||
|
|
82142751de | ||
|
|
4e5755f218 | ||
|
|
2f4f29517e | ||
|
|
e8a073651f | ||
|
|
d23da7a612 | ||
|
|
1ae4938da3 | ||
|
|
b5e27d4d69 | ||
|
|
d74fc8d1ce | ||
|
|
c5d9a41967 | ||
|
|
7e3042c4f4 | ||
|
|
1291dadbd6 | ||
|
|
f645099a39 | ||
|
|
8f32038891 | ||
|
|
a6fb1dcdf9 | ||
|
|
f01b469f3f | ||
|
|
47884919c8 | ||
|
|
653a35496d | ||
|
|
9b14df6b27 | ||
|
|
09813f3c5a | ||
|
|
7d76b7e4c2 |
@@ -226,66 +226,6 @@ commands:
|
||||
gstreamer1-plugins-base-devel
|
||||
|
||||
|
||||
install_mageia_dependencies:
|
||||
description: Install Mageia dependencies
|
||||
steps:
|
||||
- run:
|
||||
name: Update packages
|
||||
command: urpmi.update --auto -a
|
||||
- run:
|
||||
name: Configure auto update
|
||||
command: urpmi --auto --auto-update
|
||||
- run:
|
||||
name: Install dependencies
|
||||
command: >
|
||||
urpmi --auto --force
|
||||
urpmi-debuginfo-install
|
||||
git
|
||||
tar
|
||||
rpmdevtools
|
||||
make
|
||||
cmake
|
||||
glibc
|
||||
binutils
|
||||
gcc-c++
|
||||
man
|
||||
gettext
|
||||
notification-daemon
|
||||
dbus-devel
|
||||
libgnutls-devel
|
||||
lib64boost-devel
|
||||
lib64protobuf-devel
|
||||
protobuf-compiler
|
||||
lib64sqlite3-devel
|
||||
lib64alsa2-devel
|
||||
lib64pulseaudio-devel
|
||||
lib64notify-devel
|
||||
lib64qt5core-devel
|
||||
lib64qt5gui-devel
|
||||
lib64qt5widgets-devel
|
||||
lib64qt5network-devel
|
||||
lib64qt5concurrent-devel
|
||||
lib64qt5sql-devel
|
||||
lib64qt5dbus-devel
|
||||
lib64qt5x11extras-devel
|
||||
lib64qt5help-devel
|
||||
libqt5test-devel
|
||||
lib64gstreamer1.0-devel
|
||||
lib64gstreamer-plugins-base1.0-devel
|
||||
lib64cdio-devel
|
||||
lib64gpod-devel
|
||||
lib64mtp-devel
|
||||
lib64raw1394-devel
|
||||
lib64chromaprint-devel
|
||||
libfftw-devel
|
||||
desktop-file-utils
|
||||
appstream-util
|
||||
libappstream-glib8
|
||||
hicolor-icon-theme
|
||||
qt5ct
|
||||
lib64mesaegl1
|
||||
|
||||
|
||||
install_debian_dependencies:
|
||||
description: Install Debian dependencies
|
||||
steps:
|
||||
@@ -294,6 +234,7 @@ commands:
|
||||
command: >
|
||||
apt-get update && apt-get install -y
|
||||
build-essential
|
||||
dh-make
|
||||
ssh
|
||||
git
|
||||
make
|
||||
@@ -342,6 +283,7 @@ commands:
|
||||
command: >
|
||||
apt-get update && apt-get install -y
|
||||
build-essential
|
||||
dh-make
|
||||
ssh
|
||||
git
|
||||
make
|
||||
@@ -392,24 +334,6 @@ jobs:
|
||||
- build_source
|
||||
|
||||
|
||||
build_opensuse_tumbleweed:
|
||||
docker:
|
||||
- image: opensuse/tumbleweed
|
||||
environment:
|
||||
RPM_BUILD_NCPUS: "2"
|
||||
steps:
|
||||
- run:
|
||||
name: Update packages
|
||||
command: zypper --non-interactive --gpg-auto-import-keys ref
|
||||
- run:
|
||||
name: Upgrade packages
|
||||
command: zypper --non-interactive --gpg-auto-import-keys dup
|
||||
- install_opensuse_dependencies
|
||||
- checkout
|
||||
- cmake
|
||||
- build_source
|
||||
- build_rpm
|
||||
|
||||
build_opensuse_lp151:
|
||||
docker:
|
||||
- image: opensuse/leap:15.1
|
||||
@@ -473,19 +397,6 @@ jobs:
|
||||
- build_rpm
|
||||
|
||||
|
||||
build_mageia_7:
|
||||
docker:
|
||||
- image: mageia:7
|
||||
environment:
|
||||
RPM_BUILD_NCPUS: "2"
|
||||
steps:
|
||||
- install_mageia_dependencies
|
||||
- checkout
|
||||
- cmake
|
||||
- build_source
|
||||
- build_rpm
|
||||
|
||||
|
||||
build_debian_buster:
|
||||
docker:
|
||||
- image: debian:buster
|
||||
@@ -543,10 +454,6 @@ workflows:
|
||||
only: /.*/
|
||||
|
||||
|
||||
- build_opensuse_tumbleweed:
|
||||
filters:
|
||||
tags:
|
||||
only: /.*/
|
||||
- build_opensuse_lp151:
|
||||
filters:
|
||||
tags:
|
||||
@@ -567,12 +474,6 @@ workflows:
|
||||
only: /.*/
|
||||
|
||||
|
||||
- build_mageia_7:
|
||||
filters:
|
||||
tags:
|
||||
only: /.*/
|
||||
|
||||
|
||||
- build_centos_8:
|
||||
filters:
|
||||
tags:
|
||||
|
||||
105
.clang-format
Normal file
@@ -0,0 +1,105 @@
|
||||
BasedOnStyle: Chromium
|
||||
Language: Cpp
|
||||
Standard: Cpp11
|
||||
AccessModifierOffset: -1
|
||||
AlignAfterOpenBracket: false
|
||||
AlignConsecutiveAssignments: false
|
||||
AlignConsecutiveDeclarations: false
|
||||
AlignConsecutiveMacros: true
|
||||
AlignEscapedNewlines: true
|
||||
AlignOperands: false
|
||||
AlignTrailingComments: true
|
||||
AllowAllArgumentsOnNextLine: false
|
||||
AllowAllConstructorInitializersOnNextLine: true
|
||||
AllowAllParametersOfDeclarationOnNextLine: false
|
||||
AllowShortBlocksOnASingleLine: false
|
||||
AllowShortCaseLabelsOnASingleLine: true
|
||||
AllowShortFunctionsOnASingleLine: true
|
||||
AllowShortIfStatementsOnASingleLine: true
|
||||
AllowShortLambdasOnASingleLine: true
|
||||
AllowShortLoopsOnASingleLine: true
|
||||
AlwaysBreakAfterReturnType: None
|
||||
AlwaysBreakBeforeMultilineStrings: false
|
||||
AlwaysBreakTemplateDeclarations: No
|
||||
BinPackArguments: true
|
||||
BinPackParameters: true
|
||||
BreakBeforeBraces: false
|
||||
BreakBeforeBinaryOperators: false
|
||||
BreakBeforeBraces: Stroustrup
|
||||
BreakBeforeTernaryOperators: false
|
||||
BreakConstructorInitializers: BeforeColon
|
||||
BreakInheritanceList: BeforeColon
|
||||
BreakStringLiterals: false
|
||||
ColumnLimit: 0
|
||||
CompactNamespaces: false
|
||||
ConstructorInitializerAllOnOneLineOrOnePerLine: false
|
||||
ConstructorInitializerIndentWidth: 4
|
||||
ContinuationIndentWidth: 2
|
||||
Cpp11BracedListStyle: false
|
||||
DeriveLineEnding: true
|
||||
DerivePointerAlignment: true
|
||||
DisableFormat: false
|
||||
ExperimentalAutoDetectBinPacking: true
|
||||
FixNamespaceComments: true
|
||||
IncludeBlocks: Preserve
|
||||
IndentCaseLabels: true
|
||||
IndentGotoLabels: true
|
||||
IndentPPDirectives: AfterHash
|
||||
IndentWidth: 2
|
||||
IndentWrappedFunctionNames: false
|
||||
KeepEmptyLinesAtTheStartOfBlocks: true
|
||||
MaxEmptyLinesToKeep: 100
|
||||
NamespaceIndentation: None
|
||||
ObjCBlockIndentWidth: 2
|
||||
ObjCSpaceAfterProperty: false
|
||||
ObjCSpaceBeforeProtocolList: false
|
||||
PenaltyBreakAssignment: 0
|
||||
PenaltyBreakBeforeFirstCallParameter: 0
|
||||
PenaltyBreakComment: 0
|
||||
PenaltyBreakFirstLessLess: 0
|
||||
PenaltyBreakString: 0
|
||||
PenaltyBreakTemplateDeclaration: 0
|
||||
PenaltyExcessCharacter: 0
|
||||
PenaltyReturnTypeOnItsOwnLine: 0
|
||||
PointerAlignment: Right
|
||||
ReflowComments: false
|
||||
SortIncludes: false
|
||||
SortUsingDeclarations: false
|
||||
SpaceAfterCStyleCast: false
|
||||
SpaceAfterLogicalNot: false
|
||||
SpaceAfterTemplateKeyword: false
|
||||
SpaceBeforeAssignmentOperators: true
|
||||
SpaceBeforeCpp11BracedList: true
|
||||
SpaceBeforeCtorInitializerColon: true
|
||||
SpaceBeforeInheritanceColon: true
|
||||
SpaceBeforeParens: ControlStatements
|
||||
SpaceBeforeRangeBasedForLoopColon: true
|
||||
SpaceBeforeSquareBrackets: false
|
||||
SpaceInEmptyBlock: false
|
||||
SpaceInEmptyParentheses: false
|
||||
SpacesBeforeTrailingComments: 2
|
||||
SpacesInAngles: false
|
||||
SpacesInCStyleCastParentheses: false
|
||||
SpacesInConditionalStatement: false
|
||||
SpacesInContainerLiterals: true
|
||||
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
|
||||
@@ -7,7 +7,12 @@ assignees: ''
|
||||
|
||||
---
|
||||
|
||||
For general technical questions and help with technical issues please use the forum on https://forum.strawberrymusicplayer.org/
|
||||
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/
|
||||
|
||||
**Describe the bug**
|
||||
A clear and concise description of what the bug is.
|
||||
305
.github/workflows/ccpp.yml
vendored
@@ -7,7 +7,7 @@ jobs:
|
||||
name: Create source tarball
|
||||
runs-on: ubuntu-latest
|
||||
container:
|
||||
image: opensuse/leap:15.1
|
||||
image: opensuse/leap:15.2
|
||||
steps:
|
||||
- uses: actions/checkout@v1.2.0
|
||||
- name: Update packages
|
||||
@@ -352,7 +352,6 @@ jobs:
|
||||
qt6-x11extras-devel
|
||||
qt6-base-common-devel
|
||||
qt6-sql-sqlite
|
||||
qt6-qt5compat-devel
|
||||
libcdio-devel
|
||||
libgpod-devel
|
||||
libmtp-devel
|
||||
@@ -367,7 +366,7 @@ jobs:
|
||||
- name: Configure CMake
|
||||
shell: bash
|
||||
working-directory: build
|
||||
run: cmake .. -DCMAKE_BUILD_TYPE=$BUILD_TYPE -DWITH_QT6=ON
|
||||
run: cmake .. -DCMAKE_BUILD_TYPE=$BUILD_TYPE -DBUILD_WITH_QT6=ON
|
||||
- name: Build
|
||||
working-directory: build
|
||||
run: cmake --build . --config $BUILD_TYPE
|
||||
@@ -451,6 +450,160 @@ jobs:
|
||||
run: rpmbuild -ba ../dist/unix/strawberry.spec
|
||||
|
||||
|
||||
build_fedora_33:
|
||||
name: Build Fedora 33
|
||||
runs-on: ubuntu-latest
|
||||
container:
|
||||
image: fedora:33
|
||||
env:
|
||||
RPM_BUILD_NCPUS: "2"
|
||||
steps:
|
||||
- uses: actions/checkout@v1.2.0
|
||||
|
||||
- name: Update packages
|
||||
run: yum update --assumeyes
|
||||
|
||||
- name: Upgrade packages
|
||||
run: yum upgrade --assumeyes
|
||||
|
||||
- name: Install Fedora dependencies
|
||||
run: >
|
||||
dnf install --assumeyes
|
||||
@development-tools
|
||||
redhat-lsb-core
|
||||
git
|
||||
glibc
|
||||
gcc-c++
|
||||
rpmdevtools
|
||||
make
|
||||
cmake
|
||||
pkgconfig
|
||||
glib
|
||||
man
|
||||
tar
|
||||
gettext
|
||||
openssh
|
||||
boost-devel
|
||||
dbus-devel
|
||||
protobuf-devel
|
||||
protobuf-compiler
|
||||
sqlite-devel
|
||||
alsa-lib-devel
|
||||
pulseaudio-libs-devel
|
||||
libnotify-devel
|
||||
gnutls-devel
|
||||
qt5-qtbase-devel
|
||||
qt5-qtx11extras-devel
|
||||
qt5-qttools-devel
|
||||
gstreamer1-devel
|
||||
gstreamer1-plugins-base-devel
|
||||
taglib-devel
|
||||
libcdio-devel
|
||||
libgpod-devel
|
||||
libmtp-devel
|
||||
libchromaprint-devel
|
||||
fftw-devel
|
||||
desktop-file-utils
|
||||
libappstream-glib
|
||||
hicolor-icon-theme
|
||||
- name: Create Build Environment
|
||||
shell: bash
|
||||
run: cmake -E make_directory build
|
||||
- name: Configure CMake
|
||||
shell: bash
|
||||
working-directory: build
|
||||
run: cmake .. -DCMAKE_BUILD_TYPE=$BUILD_TYPE
|
||||
- name: Create source tarball
|
||||
working-directory: build
|
||||
run: ../dist/scripts/maketarball.sh
|
||||
- name: Create RPM build sources directories
|
||||
working-directory: build
|
||||
run: mkdir -p ~/rpmbuild/SOURCES /usr/src/packages/SOURCES
|
||||
- name: Copy source tarball
|
||||
working-directory: build
|
||||
run: cp strawberry-*.tar.xz ~/rpmbuild/SOURCES/
|
||||
- name: Build RPM
|
||||
working-directory: build
|
||||
run: rpmbuild -ba ../dist/unix/strawberry.spec
|
||||
|
||||
|
||||
build_fedora_34:
|
||||
name: Build Fedora 34
|
||||
runs-on: ubuntu-latest
|
||||
container:
|
||||
image: fedora:34
|
||||
env:
|
||||
RPM_BUILD_NCPUS: "2"
|
||||
steps:
|
||||
- uses: actions/checkout@v1.2.0
|
||||
|
||||
- name: Update packages
|
||||
run: yum update --assumeyes
|
||||
|
||||
- name: Upgrade packages
|
||||
run: yum upgrade --assumeyes
|
||||
|
||||
- name: Install Fedora dependencies
|
||||
run: >
|
||||
dnf install --assumeyes
|
||||
@development-tools
|
||||
redhat-lsb-core
|
||||
git
|
||||
glibc
|
||||
gcc-c++
|
||||
rpmdevtools
|
||||
make
|
||||
cmake
|
||||
pkgconfig
|
||||
glib
|
||||
man
|
||||
tar
|
||||
gettext
|
||||
openssh
|
||||
boost-devel
|
||||
dbus-devel
|
||||
protobuf-devel
|
||||
protobuf-compiler
|
||||
sqlite-devel
|
||||
alsa-lib-devel
|
||||
pulseaudio-libs-devel
|
||||
libnotify-devel
|
||||
gnutls-devel
|
||||
qt5-qtbase-devel
|
||||
qt5-qtx11extras-devel
|
||||
qt5-qttools-devel
|
||||
gstreamer1-devel
|
||||
gstreamer1-plugins-base-devel
|
||||
taglib-devel
|
||||
libcdio-devel
|
||||
libgpod-devel
|
||||
libmtp-devel
|
||||
libchromaprint-devel
|
||||
fftw-devel
|
||||
desktop-file-utils
|
||||
libappstream-glib
|
||||
hicolor-icon-theme
|
||||
- name: Create Build Environment
|
||||
shell: bash
|
||||
run: cmake -E make_directory build
|
||||
- name: Configure CMake
|
||||
shell: bash
|
||||
working-directory: build
|
||||
run: cmake .. -DCMAKE_BUILD_TYPE=$BUILD_TYPE
|
||||
- name: Create source tarball
|
||||
working-directory: build
|
||||
run: ../dist/scripts/maketarball.sh
|
||||
- name: Create RPM build sources directories
|
||||
working-directory: build
|
||||
run: mkdir -p ~/rpmbuild/SOURCES /usr/src/packages/SOURCES
|
||||
- name: Copy source tarball
|
||||
working-directory: build
|
||||
run: cp strawberry-*.tar.xz ~/rpmbuild/SOURCES/
|
||||
- name: Build RPM
|
||||
working-directory: build
|
||||
run: rpmbuild -ba ../dist/unix/strawberry.spec
|
||||
|
||||
|
||||
build_centos_8:
|
||||
name: Build CentOS 8
|
||||
runs-on: ubuntu-latest
|
||||
@@ -541,89 +694,6 @@ jobs:
|
||||
run: rpmbuild -ba ../dist/unix/strawberry.spec
|
||||
|
||||
|
||||
build_mageia_7:
|
||||
name: Build Mageia 7
|
||||
runs-on: ubuntu-latest
|
||||
container:
|
||||
image: mageia:7
|
||||
steps:
|
||||
- uses: actions/checkout@v1.2.0
|
||||
|
||||
- name: Update packages
|
||||
run: urpmi.update --auto -a
|
||||
|
||||
- name: Configure auto update
|
||||
run: urpmi --auto --auto-update
|
||||
|
||||
- name: Install Mageia dependencies
|
||||
run: >
|
||||
urpmi --auto --force
|
||||
urpmi-debuginfo-install
|
||||
git
|
||||
tar
|
||||
rpmdevtools
|
||||
make
|
||||
cmake
|
||||
glibc
|
||||
binutils
|
||||
gcc-c++
|
||||
man
|
||||
gettext
|
||||
notification-daemon
|
||||
dbus-devel
|
||||
libgnutls-devel
|
||||
lib64boost-devel
|
||||
lib64protobuf-devel
|
||||
protobuf-compiler
|
||||
lib64sqlite3-devel
|
||||
lib64alsa2-devel
|
||||
lib64pulseaudio-devel
|
||||
lib64notify-devel
|
||||
lib64qt5core-devel
|
||||
lib64qt5gui-devel
|
||||
lib64qt5widgets-devel
|
||||
lib64qt5network-devel
|
||||
lib64qt5concurrent-devel
|
||||
lib64qt5sql-devel
|
||||
lib64qt5dbus-devel
|
||||
lib64qt5x11extras-devel
|
||||
lib64qt5help-devel
|
||||
libqt5test-devel
|
||||
lib64gstreamer1.0-devel
|
||||
lib64gstreamer-plugins-base1.0-devel
|
||||
lib64cdio-devel
|
||||
lib64gpod-devel
|
||||
lib64mtp-devel
|
||||
lib64raw1394-devel
|
||||
lib64chromaprint-devel
|
||||
libfftw-devel
|
||||
desktop-file-utils
|
||||
appstream-util
|
||||
libappstream-glib8
|
||||
hicolor-icon-theme
|
||||
qt5ct
|
||||
lib64mesaegl1
|
||||
- name: Create Build Environment
|
||||
shell: bash
|
||||
run: cmake -E make_directory build
|
||||
- name: Configure CMake
|
||||
shell: bash
|
||||
working-directory: build
|
||||
run: cmake .. -DCMAKE_BUILD_TYPE=$BUILD_TYPE
|
||||
- name: Create source tarball
|
||||
working-directory: build
|
||||
run: ../dist/scripts/maketarball.sh
|
||||
- name: Create RPM build sources directories
|
||||
working-directory: build
|
||||
run: mkdir -p ~/rpmbuild/SOURCES /usr/src/packages/SOURCES
|
||||
- name: Copy source tarball
|
||||
working-directory: build
|
||||
run: cp strawberry-*.tar.xz ~/rpmbuild/SOURCES/
|
||||
- name: Build RPM
|
||||
working-directory: build
|
||||
run: rpmbuild -ba ../dist/unix/strawberry.spec
|
||||
|
||||
|
||||
build_debian_buster:
|
||||
name: Build Debian Buster
|
||||
runs-on: ubuntu-latest
|
||||
@@ -635,6 +705,7 @@ jobs:
|
||||
run: >
|
||||
apt-get update && apt-get install -y
|
||||
build-essential
|
||||
dh-make
|
||||
ssh
|
||||
git
|
||||
make
|
||||
@@ -691,6 +762,7 @@ jobs:
|
||||
run: >
|
||||
apt-get update && apt-get install -y
|
||||
build-essential
|
||||
dh-make
|
||||
ssh
|
||||
git
|
||||
make
|
||||
@@ -749,6 +821,7 @@ jobs:
|
||||
run: >
|
||||
apt-get update && apt-get install -y
|
||||
build-essential
|
||||
dh-make
|
||||
ssh
|
||||
git
|
||||
make
|
||||
@@ -810,6 +883,7 @@ jobs:
|
||||
run: >
|
||||
apt-get update && apt-get install -y
|
||||
build-essential
|
||||
dh-make
|
||||
ssh
|
||||
git
|
||||
make
|
||||
@@ -871,6 +945,7 @@ jobs:
|
||||
run: >
|
||||
apt-get update && apt-get install -y
|
||||
build-essential
|
||||
dh-make
|
||||
ssh
|
||||
git
|
||||
make
|
||||
@@ -924,8 +999,10 @@ jobs:
|
||||
runs-on: macos-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v1.2.0
|
||||
- name: Unlink python
|
||||
run: brew unlink python@2
|
||||
#- name: Update
|
||||
# run: brew update
|
||||
#- name: Upgrade
|
||||
# run: brew upgrade
|
||||
- name: Install packages
|
||||
run: >
|
||||
brew install
|
||||
@@ -977,10 +1054,14 @@ jobs:
|
||||
working-directory: build
|
||||
shell: bash
|
||||
run: make install
|
||||
#- name: Create DMG
|
||||
# working-directory: build
|
||||
# shell: bash
|
||||
# run: make dmg
|
||||
- name: Create DMG
|
||||
working-directory: build
|
||||
shell: bash
|
||||
run: make dmg
|
||||
- uses: actions/upload-artifact@v2
|
||||
with:
|
||||
name: upload-macos
|
||||
path: build/strawberry-*.dmg
|
||||
|
||||
|
||||
build-windows:
|
||||
@@ -1019,10 +1100,6 @@ jobs:
|
||||
working-directory: build
|
||||
run: make -j2
|
||||
|
||||
- name: Strip executables
|
||||
working-directory: build
|
||||
run: /usr/src/strawberry-mxe/usr/bin/x86_64-w64-mingw32.shared-strip *.exe
|
||||
|
||||
- name: Create directories
|
||||
working-directory: build
|
||||
run: mkdir -p gio-modules platforms sqldrivers imageformats styles gstreamer-plugins nsisplugins
|
||||
@@ -1099,13 +1176,9 @@ jobs:
|
||||
/usr/src/strawberry-mxe/usr/x86_64-w64-mingw32.shared/bin/gstreamer-1.0/libgstlibav.dll
|
||||
${GITHUB_WORKSPACE}/build/gstreamer-plugins/
|
||||
|
||||
- name: Copy killproc.exe
|
||||
- name: Copy extra binaries
|
||||
working-directory: build
|
||||
run: cp /usr/src/strawberry-mxe/usr/x86_64-w64-mingw32.shared/bin/killproc.exe .
|
||||
|
||||
- name: Copy liborc-0.4-0.dll
|
||||
working-directory: build
|
||||
run: cp /usr/src/strawberry-mxe/usr/x86_64-w64-mingw32.shared/bin/liborc-0.4-0.dll .
|
||||
run: cp /usr/src/strawberry-mxe/usr/x86_64-w64-mingw32.shared/bin/{sqlite3.exe,killproc.exe} .
|
||||
|
||||
- name: Copy dependencies
|
||||
working-directory: build
|
||||
@@ -1121,6 +1194,10 @@ jobs:
|
||||
-F ./gstreamer-plugins
|
||||
-R /usr/src/strawberry-mxe/usr/x86_64-w64-mingw32.shared
|
||||
|
||||
- name: Strip binaries
|
||||
working-directory: build
|
||||
run: find . -type f \( -iname \*.dll -o -iname \*.exe \) -exec /usr/src/strawberry-mxe/usr/bin/x86_64-w64-mingw32.shared-strip {} \;
|
||||
|
||||
- name: Copy nsis files
|
||||
working-directory: build
|
||||
run: cp ${GITHUB_WORKSPACE}/dist/windows/*.nsi ${GITHUB_WORKSPACE}/dist/windows/*.nsh ${GITHUB_WORKSPACE}/dist/windows/*.ico .
|
||||
@@ -1128,3 +1205,27 @@ jobs:
|
||||
- name: Build Windows installer
|
||||
working-directory: build
|
||||
run: makensis strawberry.nsi
|
||||
|
||||
|
||||
upload-macos:
|
||||
name: Upload macOS DMG
|
||||
runs-on: ubuntu-latest
|
||||
if: github.ref == 'refs/heads/master'
|
||||
needs:
|
||||
- build-macos
|
||||
steps:
|
||||
- uses: actions/checkout@v1.2.0
|
||||
- uses: actions/download-artifact@v2
|
||||
with:
|
||||
path: uploads
|
||||
- name: Install SSH keys
|
||||
uses: shimataro/ssh-key-action@v2
|
||||
with:
|
||||
known_hosts: ${{ secrets.KNOWN_HOSTS2 }}
|
||||
key: ${{ secrets.SSH_KEY }}
|
||||
- name: rsync
|
||||
run: |
|
||||
set -x
|
||||
for i in $(find uploads -type f -name '*.dmg'); do
|
||||
rsync -e "ssh -p 50220 -o StrictHostKeyChecking=no" -va $i travis@echoes.jkvinge.net:/home/travis/builds/macos/catalina/
|
||||
done
|
||||
|
||||
59
.travis.yml
@@ -2,8 +2,6 @@ sudo: required
|
||||
language: C++
|
||||
os:
|
||||
- osx
|
||||
services:
|
||||
- docker
|
||||
compiler:
|
||||
- gcc
|
||||
|
||||
@@ -12,45 +10,36 @@ before_install:
|
||||
echo $DEPLOY_KEY_ENC | base64 --decode | openssl aes-256-cbc -K $encrypted_83a41ac424a6_key -iv $encrypted_83a41ac424a6_iv -out ~/.ssh/id_rsa -d ;
|
||||
chmod 600 ~/.ssh/id_rsa ;
|
||||
fi
|
||||
- if [[ "$TRAVIS_OS_NAME" == "linux" ]]; then
|
||||
docker build -f Dockerfile -t strawberry-build . || travis_terminate 1;
|
||||
docker run --name build -itd strawberry-build /bin/bash || travis_terminate 1;
|
||||
docker exec build git clone https://github.com/strawberrymusicplayer/strawberry;
|
||||
fi
|
||||
- if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then
|
||||
git fetch --unshallow || travis_terminate 1;
|
||||
git pull || travis_terminate 1;
|
||||
brew unlink python@2 || travis_terminate 1;
|
||||
brew install glib pkgconfig libffi protobuf protobuf-c qt gettext gnutls fftw sqlite chromaprint zlib taglib;
|
||||
brew install gstreamer gst-plugins-base gst-plugins-good gst-plugins-bad gst-plugins-ugly gst-libav;
|
||||
brew install libcdio libmtp;
|
||||
brew install create-dmg;
|
||||
brew cask install sparkle;
|
||||
sudo ln -s /usr/local/Caskroom/sparkle/$(ls /usr/local/Caskroom/sparkle | head -n1)/Sparkle.framework /Library/Frameworks/Sparkle.framework;
|
||||
sudo ln -s /usr/local/Caskroom/sparkle/$(ls /usr/local/Caskroom/sparkle | head -n1)/Sparkle.framework.dSYM /Library/Frameworks/Sparkle.framework.dSYM;
|
||||
export Qt5_DIR=/usr/local/opt/qt5/lib/cmake;
|
||||
export Qt5LinguistTools_DIR=/usr/local/opt/qt5/lib/cmake/Qt5LinguistTools;
|
||||
export PATH="/usr/local/opt/gettext/bin:$PATH";
|
||||
export PKG_CONFIG_PATH="/usr/local/opt/libffi/lib/pkgconfig/:/usr/local/opt/zlib/lib/pkgconfig:$PKG_CONFIG_PATH";
|
||||
ls /usr/local/lib/gstreamer-1.0;
|
||||
fi
|
||||
- git fetch --unshallow
|
||||
- git pull
|
||||
- brew update
|
||||
- travis_wait 120 brew upgrade || echo "Failed"
|
||||
- travis_wait 120 brew upgrade || echo "Failed"
|
||||
- brew install glib pkgconfig libffi protobuf protobuf-c qt gettext gnutls fftw sqlite chromaprint zlib taglib
|
||||
- brew install gstreamer gst-plugins-base gst-plugins-good gst-plugins-bad gst-plugins-ugly gst-libav
|
||||
- brew install libcdio libmtp
|
||||
- brew install create-dmg
|
||||
- brew cask install sparkle
|
||||
- sudo ln -s /usr/local/Caskroom/sparkle/$(ls /usr/local/Caskroom/sparkle | head -n1)/Sparkle.framework /Library/Frameworks/Sparkle.framework
|
||||
- sudo ln -s /usr/local/Caskroom/sparkle/$(ls /usr/local/Caskroom/sparkle | head -n1)/Sparkle.framework.dSYM /Library/Frameworks/Sparkle.framework.dSYM
|
||||
- export Qt5_DIR=/usr/local/opt/qt5/lib/cmake
|
||||
- export Qt5LinguistTools_DIR=/usr/local/opt/qt5/lib/cmake/Qt5LinguistTools
|
||||
- ls /usr/local/lib/gstreamer-1.0
|
||||
before_script:
|
||||
- if [[ "$TRAVIS_OS_NAME" == "linux" ]]; then docker exec build cmake -Hstrawberry -Bbuild ; fi
|
||||
- if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then mkdir build; cd build; cmake .. -DUSE_BUNDLE=ON ; fi
|
||||
- mkdir build
|
||||
- cd build
|
||||
- cmake .. -DUSE_BUNDLE=ON
|
||||
script:
|
||||
- if [[ "$TRAVIS_OS_NAME" == "linux" ]]; then docker exec build make -C build -j8 ; fi
|
||||
- if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then
|
||||
make -j8 || travis_terminate 1;
|
||||
make install || travis_terminate 1;
|
||||
make dmg;
|
||||
fi
|
||||
- make -j8
|
||||
- make install
|
||||
- make dmg
|
||||
after_success:
|
||||
- if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then ls -lh strawberry*.dmg; fi
|
||||
- ls -lh strawberry*.dmg
|
||||
- if [[ "$TRAVIS_OS_NAME" == "osx" ]] && [[ "$CC_FOR_BUILD" == "gcc" ]] && [ -f ~/.ssh/id_rsa ]; then
|
||||
if [[ "$TRAVIS_BRANCH" == "master" ]]; then
|
||||
rsync -e "ssh -o StrictHostKeyChecking=no" -va strawberry*.dmg travis@echoes.jkvinge.net:/home/travis/builds/macos;
|
||||
rsync -e "ssh -o StrictHostKeyChecking=no" -va strawberry*.dmg travis@echoes.jkvinge.net:/home/travis/builds/macos/highsierra/;
|
||||
elif [[ "$TRAVIS_BRANCH" == "macos" ]]; then
|
||||
rsync -e "ssh -o StrictHostKeyChecking=no" -va strawberry*.dmg travis@echoes.jkvinge.net:/home/travis/builds/macos;
|
||||
rsync -e "ssh -o StrictHostKeyChecking=no" -va strawberry*.dmg travis@echoes.jkvinge.net:/home/travis/builds/macos/highsierra/;
|
||||
fi
|
||||
fi
|
||||
|
||||
|
||||
13
3rdparty/singleapplication/CMakeLists.txt
vendored
@@ -10,17 +10,12 @@ endif()
|
||||
|
||||
set(SINGLEAPP-SOURCES singleapplication.cpp singleapplication_p.cpp)
|
||||
set(SINGLEAPP-MOC-HEADERS singleapplication.h singleapplication_p.h)
|
||||
if(WITH_QT6)
|
||||
if(BUILD_WITH_QT6)
|
||||
qt6_wrap_cpp(SINGLEAPP-SOURCES-MOC ${SINGLEAPP-MOC-HEADERS})
|
||||
else()
|
||||
qt5_wrap_cpp(SINGLEAPP-SOURCES-MOC ${SINGLEAPP-MOC-HEADERS})
|
||||
endif()
|
||||
add_library(singleapplication STATIC ${SINGLEAPP-SOURCES} ${SINGLEAPP-SOURCES-MOC})
|
||||
target_include_directories(singleapplication SYSTEM PRIVATE
|
||||
${QtCore_INCLUDE_DIRS}
|
||||
${QtWidgets_INCLUDE_DIRS}
|
||||
${QtNetwork_INCLUDE_DIRS}
|
||||
)
|
||||
target_include_directories(singleapplication PRIVATE
|
||||
${CMAKE_CURRENT_SOURCE_DIR}
|
||||
${CMAKE_CURRENT_BINARY_DIR}
|
||||
@@ -33,16 +28,12 @@ target_link_libraries(singleapplication PRIVATE
|
||||
|
||||
set(SINGLECOREAPP-SOURCES singlecoreapplication.cpp singlecoreapplication_p.cpp)
|
||||
set(SINGLECOREAPP-MOC-HEADERS singlecoreapplication.h singlecoreapplication_p.h)
|
||||
if(WITH_QT6)
|
||||
if(BUILD_WITH_QT6)
|
||||
qt6_wrap_cpp(SINGLECOREAPP-SOURCES-MOC ${SINGLECOREAPP-MOC-HEADERS})
|
||||
else()
|
||||
qt5_wrap_cpp(SINGLECOREAPP-SOURCES-MOC ${SINGLECOREAPP-MOC-HEADERS})
|
||||
endif()
|
||||
add_library(singlecoreapplication STATIC ${SINGLECOREAPP-SOURCES} ${SINGLECOREAPP-SOURCES-MOC})
|
||||
target_include_directories(singlecoreapplication SYSTEM PRIVATE
|
||||
${QtCore_INCLUDE_DIRS}
|
||||
${QtNetwork_INCLUDE_DIRS}
|
||||
)
|
||||
target_include_directories(singlecoreapplication PRIVATE
|
||||
${CMAKE_CURRENT_SOURCE_DIR}
|
||||
${CMAKE_CURRENT_BINARY_DIR}
|
||||
|
||||
186
3rdparty/singleapplication/singleapplication.cpp
vendored
@@ -1,6 +1,6 @@
|
||||
// The MIT License (MIT)
|
||||
//
|
||||
// Copyright (c) Itay Grudev 2015 - 2018
|
||||
// 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
|
||||
@@ -35,113 +35,131 @@
|
||||
#include <limits>
|
||||
|
||||
#include <QtGlobal>
|
||||
#include <QCoreApplication>
|
||||
#include <QApplication>
|
||||
#include <QThread>
|
||||
#include <QSharedMemory>
|
||||
#include <QLocalSocket>
|
||||
#include <QByteArray>
|
||||
#include <QElapsedTimer>
|
||||
#include <QtDebug>
|
||||
#if QT_VERSION >= QT_VERSION_CHECK(5, 10, 0)
|
||||
# include <QRandomGenerator>
|
||||
#else
|
||||
# include <QDateTime>
|
||||
#endif
|
||||
|
||||
#include "singleapplication.h"
|
||||
#include "singleapplication_p.h"
|
||||
|
||||
/**
|
||||
* @brief Constructor.
|
||||
* Checks and fires up LocalServer or closes the program if another instance already exists
|
||||
* @brief Constructor. Checks and fires up LocalServer or closes the program if another instance already exists
|
||||
* @param argc
|
||||
* @param argv
|
||||
* @param {bool} allowSecondaryInstances
|
||||
* @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)) {
|
||||
: app_t(argc, argv),
|
||||
d_ptr(new SingleApplicationPrivate(this)) {
|
||||
|
||||
Q_D(SingleApplication);
|
||||
|
||||
// Store the current mode of the program
|
||||
d->options = options;
|
||||
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;
|
||||
// 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);
|
||||
d->memory_ = new QSharedMemory(d->blockServerName_);
|
||||
|
||||
// Create a shared memory block
|
||||
if (d->memory->create(sizeof(InstancesInfo))) {
|
||||
if (d->memory_->create(sizeof(InstancesInfo))) {
|
||||
// Initialize the shared memory block
|
||||
d->memory->lock();
|
||||
if (!d->memory_->lock()) {
|
||||
qCritical() << "SingleApplication: Unable to lock memory block after create.";
|
||||
abortSafely();
|
||||
}
|
||||
d->initializeMemoryBlock();
|
||||
d->memory->unlock();
|
||||
}
|
||||
else {
|
||||
// Attempt to attach to the memory segment
|
||||
if (! d->memory->attach()) {
|
||||
qCritical() << "SingleApplication: Unable to attach to shared memory block.";
|
||||
qCritical() << d->memory->errorString();
|
||||
delete d;
|
||||
::exit(EXIT_FAILURE);
|
||||
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());
|
||||
InstancesInfo *inst = static_cast<InstancesInfo*>(d->memory_->data());
|
||||
QElapsedTimer time;
|
||||
time.start();
|
||||
|
||||
// Make sure the shared memory block is initialised and in consistent state
|
||||
while (true) {
|
||||
d->memory->lock();
|
||||
|
||||
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();
|
||||
}
|
||||
|
||||
d->memory->unlock();
|
||||
|
||||
// Random sleep here limits the probability of a collision between two racing apps
|
||||
#if QT_VERSION >= QT_VERSION_CHECK(5, 10, 0)
|
||||
QThread::sleep(QRandomGenerator::global()->bounded(8u, 18u));
|
||||
#else
|
||||
qsrand(QDateTime::currentMSecsSinceEpoch() % std::numeric_limits<uint>::max());
|
||||
QThread::sleep(8 + static_cast<unsigned long>(static_cast <float>(qrand()) / RAND_MAX * 10));
|
||||
#endif
|
||||
// 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 == false) {
|
||||
d->startPrimary();
|
||||
d->memory->unlock();
|
||||
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) {
|
||||
inst->secondary += 1;
|
||||
inst->checksum = d->blockChecksum();
|
||||
d->instanceNumber = inst->secondary;
|
||||
d->startSecondary();
|
||||
if (d->options & Mode::SecondaryNotification) {
|
||||
if (d->options_ & Mode::SecondaryNotification) {
|
||||
d->connectToPrimary(timeout, SingleApplicationPrivate::SecondaryInstance);
|
||||
}
|
||||
d->memory->unlock();
|
||||
if (!d->memory_->unlock()) {
|
||||
qDebug() << "SingleApplication: Unable to unlock memory after secondary start.";
|
||||
qDebug() << d->memory_->errorString();
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
d->memory->unlock();
|
||||
if (!d->memory_->unlock()) {
|
||||
qDebug() << "SingleApplication: Unable to unlock memory at end of execution.";
|
||||
qDebug() << d->memory_->errorString();
|
||||
}
|
||||
|
||||
d->connectToPrimary(timeout, SingleApplicationPrivate::NewInstance);
|
||||
|
||||
@@ -151,34 +169,73 @@ SingleApplication::SingleApplication(int &argc, char *argv[], bool allowSecondar
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Destructor
|
||||
*/
|
||||
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;
|
||||
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;
|
||||
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;
|
||||
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 successfuly, false otherwise.
|
||||
*/
|
||||
bool SingleApplication::sendMessage(QByteArray message, int timeout) {
|
||||
|
||||
Q_D(SingleApplication);
|
||||
@@ -187,11 +244,26 @@ bool SingleApplication::sendMessage(QByteArray message, int timeout) {
|
||||
if (isPrimary()) return false;
|
||||
|
||||
// Make sure the socket is connected
|
||||
d->connectToPrimary(timeout, SingleApplicationPrivate::Reconnect);
|
||||
if (!d->connectToPrimary(timeout, SingleApplicationPrivate::Reconnect))
|
||||
return false;
|
||||
|
||||
d->socket->write(message);
|
||||
bool dataWritten = d->socket->waitForBytesWritten(timeout);
|
||||
d->socket->flush();
|
||||
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);
|
||||
|
||||
}
|
||||
|
||||
41
3rdparty/singleapplication/singleapplication.h
vendored
@@ -1,6 +1,6 @@
|
||||
// The MIT License (MIT)
|
||||
//
|
||||
// Copyright (c) Itay Grudev 2015 - 2018
|
||||
// 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
|
||||
@@ -42,16 +42,15 @@
|
||||
class SingleApplicationPrivate;
|
||||
|
||||
/**
|
||||
* @brief The SingleApplication class handles multipe instances of the same
|
||||
* Application
|
||||
* @see QCoreApplication
|
||||
* @brief The SingleApplication class handles multipe instances of the same Application
|
||||
* @see QApplication
|
||||
*/
|
||||
class SingleApplication : public QApplication {
|
||||
Q_OBJECT
|
||||
|
||||
typedef QApplication app_t;
|
||||
|
||||
public:
|
||||
public:
|
||||
/**
|
||||
* @brief Mode of operation of SingleApplication.
|
||||
* Whether the block should be user-wide or system-wide and whether the
|
||||
@@ -63,11 +62,11 @@ public:
|
||||
* @enum
|
||||
*/
|
||||
enum Mode {
|
||||
User = 1 << 0,
|
||||
System = 1 << 1,
|
||||
SecondaryNotification = 1 << 2,
|
||||
ExcludeAppVersion = 1 << 3,
|
||||
ExcludeAppPath = 1 << 4
|
||||
User = 1 << 0,
|
||||
System = 1 << 1,
|
||||
SecondaryNotification = 1 << 2,
|
||||
ExcludeAppVersion = 1 << 3,
|
||||
ExcludeAppPath = 1 << 4
|
||||
};
|
||||
Q_DECLARE_FLAGS(Options, Mode)
|
||||
|
||||
@@ -91,7 +90,7 @@ public:
|
||||
* Usually 4*timeout would be the worst case (fail) scenario.
|
||||
* @see See the corresponding QAPPLICATION_CLASS constructor for reference
|
||||
*/
|
||||
explicit SingleApplication( int &argc, char *argv[], bool allowSecondary = false, Options options = Mode::User, int timeout = 1000 );
|
||||
explicit SingleApplication(int &argc, char *argv[], bool allowSecondary = false, Options options = Mode::User, int timeout = 1000);
|
||||
~SingleApplication() override;
|
||||
|
||||
/**
|
||||
@@ -118,6 +117,18 @@ public:
|
||||
*/
|
||||
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
|
||||
@@ -125,18 +136,18 @@ public:
|
||||
* @note sendMessage() will return false if invoked from the primary
|
||||
* instance.
|
||||
*/
|
||||
bool sendMessage( QByteArray message, int timeout = 1000 );
|
||||
bool sendMessage(QByteArray message, int timeout = 1000);
|
||||
|
||||
signals:
|
||||
void instanceStarted();
|
||||
void receivedMessage( quint32 instanceId, QByteArray message );
|
||||
void receivedMessage(quint32 instanceId, QByteArray message);
|
||||
|
||||
private:
|
||||
SingleApplicationPrivate *d_ptr;
|
||||
Q_DECLARE_PRIVATE(SingleApplication)
|
||||
|
||||
void abortSafely();
|
||||
};
|
||||
|
||||
Q_DECLARE_OPERATORS_FOR_FLAGS(SingleApplication::Options)
|
||||
|
||||
#endif // SINGLEAPPLICATION_H
|
||||
#endif // SINGLEAPPLICATION_H
|
||||
|
||||
355
3rdparty/singleapplication/singleapplication_p.cpp
vendored
@@ -1,6 +1,6 @@
|
||||
// The MIT License (MIT)
|
||||
//
|
||||
// Copyright (c) Itay Grudev 2015 - 2018
|
||||
// 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
|
||||
@@ -44,6 +44,8 @@
|
||||
# include <pwd.h>
|
||||
#endif
|
||||
|
||||
#include <QObject>
|
||||
#include <QThread>
|
||||
#include <QIODevice>
|
||||
#include <QSharedMemory>
|
||||
#include <QByteArray>
|
||||
@@ -52,6 +54,12 @@
|
||||
#include <QLocalServer>
|
||||
#include <QLocalSocket>
|
||||
#include <QDir>
|
||||
#include <QElapsedTimer>
|
||||
#if QT_VERSION >= QT_VERSION_CHECK(5, 10, 0)
|
||||
# include <QRandomGenerator>
|
||||
#else
|
||||
# include <QDateTime>
|
||||
#endif
|
||||
|
||||
#include "singleapplication.h"
|
||||
#include "singleapplication_p.h"
|
||||
@@ -61,33 +69,73 @@
|
||||
# include <lmcons.h>
|
||||
#endif
|
||||
|
||||
SingleApplicationPrivate::SingleApplicationPrivate(SingleApplication *_q_ptr)
|
||||
: q_ptr(_q_ptr),
|
||||
memory(nullptr),
|
||||
socket(nullptr),
|
||||
server(nullptr),
|
||||
instanceNumber(-1)
|
||||
{}
|
||||
SingleApplicationPrivate::SingleApplicationPrivate(SingleApplication *ptr)
|
||||
: q_ptr(ptr),
|
||||
memory_(nullptr),
|
||||
socket_(nullptr),
|
||||
server_(nullptr),
|
||||
instanceNumber_(-1) {}
|
||||
|
||||
SingleApplicationPrivate::~SingleApplicationPrivate() {
|
||||
|
||||
if (socket != nullptr) {
|
||||
socket->close();
|
||||
delete socket;
|
||||
if (socket_ != nullptr) {
|
||||
socket_->close();
|
||||
delete socket_;
|
||||
socket_ = nullptr;
|
||||
}
|
||||
|
||||
memory->lock();
|
||||
InstancesInfo* inst = static_cast<InstancesInfo*>(memory->data());
|
||||
if (server != nullptr) {
|
||||
server->close();
|
||||
delete server;
|
||||
inst->primary = false;
|
||||
inst->primaryPid = -1;
|
||||
inst->checksum = blockChecksum();
|
||||
}
|
||||
memory->unlock();
|
||||
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;
|
||||
delete memory_;
|
||||
memory_ = nullptr;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
QString SingleApplicationPrivate::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
|
||||
|
||||
}
|
||||
|
||||
@@ -99,11 +147,11 @@ void SingleApplicationPrivate::genBlockServerName() {
|
||||
appData.addData(SingleApplication::app_t::organizationName().toUtf8());
|
||||
appData.addData(SingleApplication::app_t::organizationDomain().toUtf8());
|
||||
|
||||
if (!(options & SingleApplication::Mode::ExcludeAppVersion)) {
|
||||
if (!(options_ & SingleApplication::Mode::ExcludeAppVersion)) {
|
||||
appData.addData(SingleApplication::app_t::applicationVersion().toUtf8());
|
||||
}
|
||||
|
||||
if (! (options & SingleApplication::Mode::ExcludeAppPath)) {
|
||||
if (!(options_ & SingleApplication::Mode::ExcludeAppPath)) {
|
||||
#ifdef Q_OS_WIN
|
||||
appData.addData(SingleApplication::app_t::applicationFilePath().toLower().toUtf8());
|
||||
#else
|
||||
@@ -112,42 +160,22 @@ void SingleApplicationPrivate::genBlockServerName() {
|
||||
}
|
||||
|
||||
// User level block requires a user specific data in the hash
|
||||
if (options & SingleApplication::Mode::User) {
|
||||
#ifdef Q_OS_UNIX
|
||||
QByteArray username;
|
||||
#if defined(HAVE_GETEUID) && defined(HAVE_GETPWUID)
|
||||
struct passwd *pw = getpwuid(geteuid());
|
||||
if (pw) {
|
||||
username = pw->pw_name;
|
||||
}
|
||||
#endif
|
||||
if (username.isEmpty()) username = qgetenv("USER");
|
||||
appData.addData(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)) {
|
||||
appData.addData(QString::fromWCharArray(username).toUtf8());
|
||||
}
|
||||
else {
|
||||
appData.addData(qgetenv("USERNAME"));
|
||||
}
|
||||
#endif
|
||||
if (options_ & SingleApplication::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("/", "_");
|
||||
blockServerName_ = appData.result().toBase64().replace("/", "_");
|
||||
|
||||
}
|
||||
|
||||
void SingleApplicationPrivate::initializeMemoryBlock() {
|
||||
|
||||
InstancesInfo* inst = static_cast<InstancesInfo*>(memory->data());
|
||||
InstancesInfo *inst = static_cast<InstancesInfo*>(memory_->data());
|
||||
inst->primary = false;
|
||||
inst->secondary = 0;
|
||||
inst->primaryPid = -1;
|
||||
inst->primaryUser[0] = '\0';
|
||||
inst->checksum = blockChecksum();
|
||||
|
||||
}
|
||||
@@ -156,133 +184,161 @@ void SingleApplicationPrivate::startPrimary() {
|
||||
|
||||
Q_Q(SingleApplication);
|
||||
|
||||
// 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
|
||||
// SingleApplication::Mode::User flag on User level or no restrictions
|
||||
if (options & SingleApplication::Mode::User) {
|
||||
server->setSocketOptions(QLocalServer::UserAccessOption);
|
||||
}
|
||||
else {
|
||||
server->setSocketOptions(QLocalServer::WorldAccessOption);
|
||||
}
|
||||
|
||||
server->listen(blockServerName);
|
||||
QObject::connect(server, &QLocalServer::newConnection, this, &SingleApplicationPrivate::slotConnectionEstablished);
|
||||
|
||||
// Reset the number of connections
|
||||
InstancesInfo* inst = static_cast <InstancesInfo*>(memory->data());
|
||||
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();
|
||||
|
||||
instanceNumber = 0;
|
||||
// Restrict access to the socket according to the SingleApplication::Mode::User flag on User level or no restrictions
|
||||
if (options_ & SingleApplication::Mode::User) {
|
||||
server_->setSocketOptions(QLocalServer::UserAccessOption);
|
||||
}
|
||||
else {
|
||||
server_->setSocketOptions(QLocalServer::WorldAccessOption);
|
||||
}
|
||||
|
||||
server_->listen(blockServerName_);
|
||||
QObject::connect(server_, &QLocalServer::newConnection, this, &SingleApplicationPrivate::slotConnectionEstablished);
|
||||
|
||||
}
|
||||
|
||||
void SingleApplicationPrivate::startSecondary() {}
|
||||
void SingleApplicationPrivate::startSecondary() {
|
||||
|
||||
void SingleApplicationPrivate::connectToPrimary(const int msecs, const ConnectionType connectionType) {
|
||||
InstancesInfo *inst = static_cast<InstancesInfo*>(memory_->data());
|
||||
|
||||
inst->secondary += 1;
|
||||
inst->checksum = blockChecksum();
|
||||
instanceNumber_ = inst->secondary;
|
||||
|
||||
}
|
||||
|
||||
bool SingleApplicationPrivate::connectToPrimary(int timeout, 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_ == nullptr) {
|
||||
socket_ = new QLocalSocket();
|
||||
}
|
||||
|
||||
// If already connected - we are done;
|
||||
if (socket->state() == QLocalSocket::ConnectedState)
|
||||
return;
|
||||
if (socket_->state() == QLocalSocket::ConnectedState) return true;
|
||||
|
||||
// If not connect
|
||||
if (socket->state() == QLocalSocket::UnconnectedState ||
|
||||
socket->state() == QLocalSocket::ClosingState) {
|
||||
socket->connectToServer(blockServerName);
|
||||
}
|
||||
if (socket_->state() != QLocalSocket::ConnectedState) {
|
||||
|
||||
// Wait for being connected
|
||||
if (socket->state() == QLocalSocket::ConnectingState) {
|
||||
socket->waitForConnected(msecs);
|
||||
forever {
|
||||
randomSleep();
|
||||
|
||||
if (socket_->state() != QLocalSocket::ConnectingState)
|
||||
socket_->connectToServer(blockServerName_);
|
||||
|
||||
if (socket_->state() == QLocalSocket::ConnectingState) {
|
||||
socket_->waitForConnected(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 SingleApplication protocol
|
||||
if (socket->state() == QLocalSocket::ConnectedState) {
|
||||
// Notify the parent that a new instance had been started;
|
||||
QByteArray initMsg;
|
||||
QDataStream writeStream(&initMsg, QIODevice::WriteOnly);
|
||||
QByteArray initMsg;
|
||||
QDataStream writeStream(&initMsg, QIODevice::WriteOnly);
|
||||
writeStream.setVersion(QDataStream::Qt_5_8);
|
||||
|
||||
writeStream.setVersion(QDataStream::Qt_5_6);
|
||||
writeStream << blockServerName_.toLatin1();
|
||||
writeStream << static_cast<quint8>(connectionType);
|
||||
writeStream << instanceNumber_;
|
||||
|
||||
writeStream << blockServerName.toLatin1();
|
||||
writeStream << static_cast<quint8>(connectionType);
|
||||
writeStream << instanceNumber;
|
||||
quint16 checksum = qChecksum(initMsg.constData(), static_cast<quint32>(initMsg.length()));
|
||||
writeStream << checksum;
|
||||
#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
|
||||
|
||||
// The header indicates the message length that follows
|
||||
QByteArray header;
|
||||
QDataStream headerStream(&header, QIODevice::WriteOnly);
|
||||
writeStream << checksum;
|
||||
|
||||
headerStream.setVersion(QDataStream::Qt_5_6);
|
||||
// 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());
|
||||
|
||||
headerStream << static_cast <quint64>(initMsg.length());
|
||||
socket_->write(header);
|
||||
socket_->write(initMsg);
|
||||
bool result = socket_->waitForBytesWritten(timeout - time.elapsed());
|
||||
socket_->flush();
|
||||
|
||||
socket->write(header);
|
||||
socket->write(initMsg);
|
||||
socket->flush();
|
||||
socket->waitForBytesWritten(msecs);
|
||||
}
|
||||
return result;
|
||||
|
||||
}
|
||||
|
||||
quint16 SingleApplicationPrivate::blockChecksum() {
|
||||
|
||||
return qChecksum(static_cast <const char *>(memory->data()), offsetof(InstancesInfo, checksum));
|
||||
#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 SingleApplicationPrivate::primaryPid() {
|
||||
|
||||
qint64 pid;
|
||||
|
||||
memory->lock();
|
||||
InstancesInfo* inst = static_cast<InstancesInfo*>(memory->data());
|
||||
pid = inst->primaryPid;
|
||||
memory->unlock();
|
||||
memory_->lock();
|
||||
InstancesInfo *inst = static_cast<InstancesInfo*>(memory_->data());
|
||||
qint64 pid = inst->primaryPid;
|
||||
memory_->unlock();
|
||||
|
||||
return pid;
|
||||
|
||||
}
|
||||
|
||||
QString SingleApplicationPrivate::primaryUser() {
|
||||
|
||||
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 SingleApplicationPrivate::slotConnectionEstablished() {
|
||||
|
||||
QLocalSocket *nextConnSocket = server->nextPendingConnection();
|
||||
connectionMap.insert(nextConnSocket, ConnectionInfo());
|
||||
QLocalSocket *nextConnSocket = server_->nextPendingConnection();
|
||||
connectionMap_.insert(nextConnSocket, ConnectionInfo());
|
||||
|
||||
QObject::connect(nextConnSocket, &QLocalSocket::aboutToClose,
|
||||
[nextConnSocket, this]() {
|
||||
auto &info = connectionMap[nextConnSocket];
|
||||
Q_EMIT this->slotClientConnectionClosed(nextConnSocket, info.instanceId);
|
||||
}
|
||||
);
|
||||
QObject::connect(nextConnSocket, &QLocalSocket::aboutToClose, [nextConnSocket, this]() {
|
||||
auto &info = connectionMap_[nextConnSocket];
|
||||
Q_EMIT this->slotClientConnectionClosed(nextConnSocket, info.instanceId);
|
||||
});
|
||||
|
||||
QObject::connect(nextConnSocket, &QLocalSocket::disconnected,
|
||||
[nextConnSocket, this](){
|
||||
connectionMap.remove(nextConnSocket);
|
||||
nextConnSocket->deleteLater();
|
||||
}
|
||||
);
|
||||
QObject::connect(nextConnSocket, &QLocalSocket::disconnected, [nextConnSocket, this]() {
|
||||
connectionMap_.remove(nextConnSocket);
|
||||
nextConnSocket->deleteLater();
|
||||
});
|
||||
|
||||
QObject::connect(nextConnSocket, &QLocalSocket::readyRead,
|
||||
[nextConnSocket, this]() {
|
||||
auto &info = connectionMap[nextConnSocket];
|
||||
switch(info.stage) {
|
||||
QObject::connect(nextConnSocket, &QLocalSocket::readyRead, [nextConnSocket, this]() {
|
||||
auto &info = connectionMap_[nextConnSocket];
|
||||
switch (info.stage) {
|
||||
case StageHeader:
|
||||
readInitMessageHeader(nextConnSocket);
|
||||
break;
|
||||
@@ -294,15 +350,14 @@ void SingleApplicationPrivate::slotConnectionEstablished() {
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
};
|
||||
}
|
||||
);
|
||||
};
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
void SingleApplicationPrivate::readInitMessageHeader(QLocalSocket *sock) {
|
||||
|
||||
if (!connectionMap.contains(sock)) {
|
||||
if (!connectionMap_.contains(sock)) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -311,13 +366,12 @@ void SingleApplicationPrivate::readInitMessageHeader(QLocalSocket *sock) {
|
||||
}
|
||||
|
||||
QDataStream headerStream(sock);
|
||||
|
||||
headerStream.setVersion(QDataStream::Qt_5_6);
|
||||
headerStream.setVersion(QDataStream::Qt_5_8);
|
||||
|
||||
// Read the header to know the message length
|
||||
quint64 msgLen = 0;
|
||||
headerStream >> msgLen;
|
||||
ConnectionInfo &info = connectionMap[sock];
|
||||
ConnectionInfo &info = connectionMap_[sock];
|
||||
info.stage = StageBody;
|
||||
info.msgLen = msgLen;
|
||||
|
||||
@@ -331,11 +385,11 @@ void SingleApplicationPrivate::readInitMessageBody(QLocalSocket *sock) {
|
||||
|
||||
Q_Q(SingleApplication);
|
||||
|
||||
if (!connectionMap.contains(sock)) {
|
||||
if (!connectionMap_.contains(sock)) {
|
||||
return;
|
||||
}
|
||||
|
||||
ConnectionInfo &info = connectionMap[sock];
|
||||
ConnectionInfo &info = connectionMap_[sock];
|
||||
if (sock->bytesAvailable() < static_cast<qint64>(info.msgLen)) {
|
||||
return;
|
||||
}
|
||||
@@ -343,8 +397,7 @@ void SingleApplicationPrivate::readInitMessageBody(QLocalSocket *sock) {
|
||||
// Read the message body
|
||||
QByteArray msgBytes = sock->read(info.msgLen);
|
||||
QDataStream readStream(msgBytes);
|
||||
|
||||
readStream.setVersion(QDataStream::Qt_5_6);
|
||||
readStream.setVersion(QDataStream::Qt_5_8);
|
||||
|
||||
// server name
|
||||
QByteArray latin1Name;
|
||||
@@ -354,7 +407,7 @@ void SingleApplicationPrivate::readInitMessageBody(QLocalSocket *sock) {
|
||||
ConnectionType connectionType = InvalidConnection;
|
||||
quint8 connTypeVal = InvalidConnection;
|
||||
readStream >> connTypeVal;
|
||||
connectionType = static_cast <ConnectionType>(connTypeVal);
|
||||
connectionType = static_cast<ConnectionType>(connTypeVal);
|
||||
|
||||
// instance id
|
||||
quint32 instanceId = 0;
|
||||
@@ -364,9 +417,13 @@ void SingleApplicationPrivate::readInitMessageBody(QLocalSocket *sock) {
|
||||
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;
|
||||
bool isValid = readStream.status() == QDataStream::Ok && QLatin1String(latin1Name) == blockServerName_ && msgChecksum == actualChecksum;
|
||||
|
||||
if (!isValid) {
|
||||
sock->close();
|
||||
@@ -376,7 +433,7 @@ void SingleApplicationPrivate::readInitMessageBody(QLocalSocket *sock) {
|
||||
info.instanceId = instanceId;
|
||||
info.stage = StageConnected;
|
||||
|
||||
if (connectionType == NewInstance || (connectionType == SecondaryInstance && options & SingleApplication::Mode::SecondaryNotification)) {
|
||||
if (connectionType == NewInstance || (connectionType == SecondaryInstance && options_ & SingleApplication::Mode::SecondaryNotification)) {
|
||||
Q_EMIT q->instanceStarted();
|
||||
}
|
||||
|
||||
@@ -395,7 +452,19 @@ void SingleApplicationPrivate::slotDataAvailable(QLocalSocket *dataSocket, const
|
||||
|
||||
void SingleApplicationPrivate::slotClientConnectionClosed(QLocalSocket *closedSocket, const quint32 instanceId) {
|
||||
|
||||
if (closedSocket->bytesAvailable() > 0)
|
||||
if (closedSocket->bytesAvailable() > 0) {
|
||||
Q_EMIT slotDataAvailable(closedSocket, instanceId);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
void SingleApplicationPrivate::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
|
||||
|
||||
}
|
||||
|
||||
26
3rdparty/singleapplication/singleapplication_p.h
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
|
||||
@@ -36,6 +36,7 @@
|
||||
|
||||
#include <QtGlobal>
|
||||
#include <QObject>
|
||||
#include <QString>
|
||||
#include <QMap>
|
||||
|
||||
#include "singleapplication.h"
|
||||
@@ -48,6 +49,7 @@ struct InstancesInfo {
|
||||
bool primary;
|
||||
quint32 secondary;
|
||||
qint64 primaryPid;
|
||||
char primaryUser[128];
|
||||
quint16 checksum;
|
||||
};
|
||||
|
||||
@@ -60,6 +62,7 @@ struct ConnectionInfo {
|
||||
|
||||
class SingleApplicationPrivate : public QObject {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
enum ConnectionType : quint8 {
|
||||
InvalidConnection = 0,
|
||||
@@ -74,27 +77,30 @@ class SingleApplicationPrivate : public QObject {
|
||||
};
|
||||
Q_DECLARE_PUBLIC(SingleApplication)
|
||||
|
||||
explicit SingleApplicationPrivate(SingleApplication *_q_ptr);
|
||||
explicit SingleApplicationPrivate(SingleApplication *ptr);
|
||||
~SingleApplicationPrivate() override;
|
||||
|
||||
QString getUsername();
|
||||
void genBlockServerName();
|
||||
void initializeMemoryBlock();
|
||||
void startPrimary();
|
||||
void startSecondary();
|
||||
void connectToPrimary(const int msecs, const ConnectionType connectionType);
|
||||
bool connectToPrimary(const int msecs, const ConnectionType connectionType);
|
||||
quint16 blockChecksum();
|
||||
qint64 primaryPid();
|
||||
QString primaryUser();
|
||||
void readInitMessageHeader(QLocalSocket *socket);
|
||||
void readInitMessageBody(QLocalSocket *socket);
|
||||
void randomSleep();
|
||||
|
||||
SingleApplication *q_ptr;
|
||||
QSharedMemory *memory;
|
||||
QLocalSocket *socket;
|
||||
QLocalServer *server;
|
||||
quint32 instanceNumber;
|
||||
QString blockServerName;
|
||||
SingleApplication::Options options;
|
||||
QMap<QLocalSocket*, ConnectionInfo> connectionMap;
|
||||
QSharedMemory *memory_;
|
||||
QLocalSocket *socket_;
|
||||
QLocalServer *server_;
|
||||
quint32 instanceNumber_;
|
||||
QString blockServerName_;
|
||||
SingleApplication::Options options_;
|
||||
QMap<QLocalSocket*, ConnectionInfo> connectionMap_;
|
||||
|
||||
public slots:
|
||||
void slotConnectionEstablished();
|
||||
|
||||
188
3rdparty/singleapplication/singlecoreapplication.cpp
vendored
@@ -1,6 +1,6 @@
|
||||
// The MIT License (MIT)
|
||||
//
|
||||
// Copyright (c) Itay Grudev 2015 - 2018
|
||||
// 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
|
||||
@@ -42,106 +42,124 @@
|
||||
#include <QByteArray>
|
||||
#include <QElapsedTimer>
|
||||
#include <QtDebug>
|
||||
#if QT_VERSION >= QT_VERSION_CHECK(5, 10, 0)
|
||||
# include <QRandomGenerator>
|
||||
#else
|
||||
# include <QDateTime>
|
||||
#endif
|
||||
|
||||
#include "singlecoreapplication.h"
|
||||
#include "singlecoreapplication_p.h"
|
||||
|
||||
/**
|
||||
* @brief Constructor. Checks and fires up LocalServer or closes the program
|
||||
* if another instance already exists
|
||||
* @brief Constructor. Checks and fires up LocalServer or closes the program if another instance already exists
|
||||
* @param argc
|
||||
* @param argv
|
||||
* @param {bool} allowSecondaryInstances
|
||||
* @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)) {
|
||||
: app_t(argc, argv),
|
||||
d_ptr(new SingleCoreApplicationPrivate(this)) {
|
||||
|
||||
Q_D(SingleCoreApplication);
|
||||
|
||||
// Store the current mode of the program
|
||||
d->options = options;
|
||||
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;
|
||||
// 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);
|
||||
d->memory_ = new QSharedMemory(d->blockServerName_);
|
||||
|
||||
// Create a shared memory block
|
||||
if (d->memory->create(sizeof(InstancesInfo))) {
|
||||
if (d->memory_->create(sizeof(InstancesInfo))) {
|
||||
// Initialize the shared memory block
|
||||
d->memory->lock();
|
||||
if (!d->memory_->lock()) {
|
||||
qCritical() << "SingleCoreApplication: Unable to lock memory block after create.";
|
||||
abortSafely();
|
||||
}
|
||||
d->initializeMemoryBlock();
|
||||
d->memory->unlock();
|
||||
}
|
||||
else {
|
||||
// Attempt to attach to the memory segment
|
||||
if (!d->memory->attach()) {
|
||||
qCritical() << "SingleCoreApplication: Unable to attach to shared memory block.";
|
||||
qCritical() << d->memory->errorString();
|
||||
delete d;
|
||||
::exit(EXIT_FAILURE);
|
||||
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());
|
||||
InstancesInfo *inst = static_cast<InstancesInfo*>(d->memory_->data());
|
||||
QElapsedTimer time;
|
||||
time.start();
|
||||
|
||||
// Make sure the shared memory block is initialised and in consistent state
|
||||
while (true) {
|
||||
d->memory->lock();
|
||||
|
||||
if(d->blockChecksum() == inst->checksum) break;
|
||||
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();
|
||||
}
|
||||
|
||||
d->memory->unlock();
|
||||
|
||||
// Random sleep here limits the probability of a collision between two racing apps
|
||||
#if QT_VERSION >= QT_VERSION_CHECK(5, 10, 0)
|
||||
QThread::sleep(QRandomGenerator::global()->bounded(8u, 18u));
|
||||
#else
|
||||
qsrand(QDateTime::currentMSecsSinceEpoch() % std::numeric_limits<uint>::max());
|
||||
QThread::sleep(8 + static_cast<unsigned long>(static_cast <float>(qrand()) / RAND_MAX * 10));
|
||||
#endif
|
||||
// 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 == false) {
|
||||
d->startPrimary();
|
||||
d->memory->unlock();
|
||||
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) {
|
||||
inst->secondary += 1;
|
||||
inst->checksum = d->blockChecksum();
|
||||
d->instanceNumber = inst->secondary;
|
||||
d->startSecondary();
|
||||
if(d->options & Mode::SecondaryNotification) {
|
||||
if (d->options_ & Mode::SecondaryNotification) {
|
||||
d->connectToPrimary(timeout, SingleCoreApplicationPrivate::SecondaryInstance);
|
||||
}
|
||||
d->memory->unlock();
|
||||
if (!d->memory_->unlock()) {
|
||||
qDebug() << "SingleCoreApplication: Unable to unlock memory after secondary start.";
|
||||
qDebug() << d->memory_->errorString();
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
d->memory->unlock();
|
||||
if (!d->memory_->unlock()) {
|
||||
qDebug() << "SingleCoreApplication: Unable to unlock memory at end of execution.";
|
||||
qDebug() << d->memory_->errorString();
|
||||
}
|
||||
|
||||
d->connectToPrimary(timeout, SingleCoreApplicationPrivate::NewInstance);
|
||||
|
||||
@@ -151,47 +169,101 @@ SingleCoreApplication::SingleCoreApplication(int &argc, char *argv[], bool allow
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Destructor
|
||||
*/
|
||||
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;
|
||||
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;
|
||||
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;
|
||||
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 successfuly, false otherwise.
|
||||
*/
|
||||
bool SingleCoreApplication::sendMessage(QByteArray message, int timeout) {
|
||||
|
||||
Q_D(SingleCoreApplication);
|
||||
|
||||
// Nobody to connect to
|
||||
if(isPrimary()) return false;
|
||||
if (isPrimary()) return false;
|
||||
|
||||
// Make sure the socket is connected
|
||||
d->connectToPrimary(timeout, SingleCoreApplicationPrivate::Reconnect);
|
||||
if (!d->connectToPrimary(timeout, SingleCoreApplicationPrivate::Reconnect))
|
||||
return false;
|
||||
|
||||
d->socket->write(message);
|
||||
bool dataWritten = d->socket->waitForBytesWritten(timeout);
|
||||
d->socket->flush();
|
||||
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);
|
||||
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
// The MIT License (MIT)
|
||||
//
|
||||
// Copyright (c) Itay Grudev 2015 - 2018
|
||||
// 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
|
||||
@@ -42,8 +42,7 @@
|
||||
class SingleCoreApplicationPrivate;
|
||||
|
||||
/**
|
||||
* @brief The SingleCoreApplication class handles multiple instances of the same
|
||||
* Application
|
||||
* @brief The SingleCoreApplication class handles multipe instances of the same Application
|
||||
* @see QCoreApplication
|
||||
*/
|
||||
class SingleCoreApplication : public QCoreApplication {
|
||||
@@ -51,7 +50,7 @@ class SingleCoreApplication : public QCoreApplication {
|
||||
|
||||
typedef QCoreApplication app_t;
|
||||
|
||||
public:
|
||||
public:
|
||||
/**
|
||||
* @brief Mode of operation of SingleCoreApplication.
|
||||
* Whether the block should be user-wide or system-wide and whether the
|
||||
@@ -63,11 +62,11 @@ public:
|
||||
* @enum
|
||||
*/
|
||||
enum Mode {
|
||||
User = 1 << 0,
|
||||
System = 1 << 1,
|
||||
SecondaryNotification = 1 << 2,
|
||||
ExcludeAppVersion = 1 << 3,
|
||||
ExcludeAppPath = 1 << 4
|
||||
User = 1 << 0,
|
||||
System = 1 << 1,
|
||||
SecondaryNotification = 1 << 2,
|
||||
ExcludeAppVersion = 1 << 3,
|
||||
ExcludeAppPath = 1 << 4
|
||||
};
|
||||
Q_DECLARE_FLAGS(Options, Mode)
|
||||
|
||||
@@ -89,9 +88,8 @@ public:
|
||||
* 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.
|
||||
* @see See the corresponding QAPPLICATION_CLASS constructor for reference
|
||||
*/
|
||||
explicit SingleCoreApplication( int &argc, char *argv[], bool allowSecondary = false, Options options = Mode::User, int timeout = 1000 );
|
||||
explicit SingleCoreApplication(int &argc, char *argv[], const bool allowSecondary = false, const Options options = Mode::User, const int timeout = 1000);
|
||||
~SingleCoreApplication() override;
|
||||
|
||||
/**
|
||||
@@ -118,6 +116,18 @@ public:
|
||||
*/
|
||||
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
|
||||
@@ -125,17 +135,18 @@ public:
|
||||
* @note sendMessage() will return false if invoked from the primary
|
||||
* instance.
|
||||
*/
|
||||
bool sendMessage( QByteArray message, int timeout = 1000 );
|
||||
bool sendMessage(QByteArray message, int timeout = 1000);
|
||||
|
||||
signals:
|
||||
void instanceStarted();
|
||||
void receivedMessage( quint32 instanceId, QByteArray message );
|
||||
void receivedMessage(quint32 instanceId, QByteArray message);
|
||||
|
||||
private:
|
||||
private:
|
||||
SingleCoreApplicationPrivate *d_ptr;
|
||||
Q_DECLARE_PRIVATE(SingleCoreApplication)
|
||||
void abortSafely();
|
||||
};
|
||||
|
||||
Q_DECLARE_OPERATORS_FOR_FLAGS(SingleCoreApplication::Options)
|
||||
|
||||
#endif // SINGLECOREAPPLICATION_H
|
||||
#endif // SINGLECOREAPPLICATION_H
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
// The MIT License (MIT)
|
||||
//
|
||||
// Copyright (c) Itay Grudev 2015 - 2018
|
||||
// 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
|
||||
@@ -44,6 +44,8 @@
|
||||
# include <pwd.h>
|
||||
#endif
|
||||
|
||||
#include <QObject>
|
||||
#include <QThread>
|
||||
#include <QIODevice>
|
||||
#include <QSharedMemory>
|
||||
#include <QByteArray>
|
||||
@@ -52,6 +54,12 @@
|
||||
#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"
|
||||
@@ -61,33 +69,73 @@
|
||||
# include <lmcons.h>
|
||||
#endif
|
||||
|
||||
SingleCoreApplicationPrivate::SingleCoreApplicationPrivate(SingleCoreApplication *_q_ptr)
|
||||
: q_ptr(_q_ptr),
|
||||
memory(nullptr),
|
||||
socket(nullptr),
|
||||
server(nullptr),
|
||||
instanceNumber(-1)
|
||||
{}
|
||||
SingleCoreApplicationPrivate::SingleCoreApplicationPrivate(SingleCoreApplication *ptr)
|
||||
: q_ptr(ptr),
|
||||
memory_(nullptr),
|
||||
socket_(nullptr),
|
||||
server_(nullptr),
|
||||
instanceNumber_(-1) {}
|
||||
|
||||
SingleCoreApplicationPrivate::~SingleCoreApplicationPrivate() {
|
||||
|
||||
if (socket != nullptr) {
|
||||
socket->close();
|
||||
delete socket;
|
||||
if (socket_ != nullptr) {
|
||||
socket_->close();
|
||||
delete socket_;
|
||||
socket_ = nullptr;
|
||||
}
|
||||
|
||||
memory->lock();
|
||||
InstancesInfo* inst = static_cast<InstancesInfo*>(memory->data());
|
||||
if (server != nullptr) {
|
||||
server->close();
|
||||
delete server;
|
||||
inst->primary = false;
|
||||
inst->primaryPid = -1;
|
||||
inst->checksum = blockChecksum();
|
||||
}
|
||||
memory->unlock();
|
||||
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;
|
||||
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
|
||||
|
||||
}
|
||||
|
||||
@@ -99,11 +147,11 @@ void SingleCoreApplicationPrivate::genBlockServerName() {
|
||||
appData.addData(SingleCoreApplication::app_t::organizationName().toUtf8());
|
||||
appData.addData(SingleCoreApplication::app_t::organizationDomain().toUtf8());
|
||||
|
||||
if (!(options & SingleCoreApplication::Mode::ExcludeAppVersion)) {
|
||||
if (!(options_ & SingleCoreApplication::Mode::ExcludeAppVersion)) {
|
||||
appData.addData(SingleCoreApplication::app_t::applicationVersion().toUtf8());
|
||||
}
|
||||
|
||||
if (!(options & SingleCoreApplication::Mode::ExcludeAppPath)) {
|
||||
if (!(options_ & SingleCoreApplication::Mode::ExcludeAppPath)) {
|
||||
#ifdef Q_OS_WIN
|
||||
appData.addData(SingleCoreApplication::app_t::applicationFilePath().toLower().toUtf8());
|
||||
#else
|
||||
@@ -112,42 +160,22 @@ void SingleCoreApplicationPrivate::genBlockServerName() {
|
||||
}
|
||||
|
||||
// User level block requires a user specific data in the hash
|
||||
if (options & SingleCoreApplication::Mode::User) {
|
||||
#ifdef Q_OS_UNIX
|
||||
QByteArray username;
|
||||
#if defined(HAVE_GETEUID) && defined(HAVE_GETPWUID)
|
||||
struct passwd *pw = getpwuid(geteuid());
|
||||
if (pw) {
|
||||
username = pw->pw_name;
|
||||
}
|
||||
#endif
|
||||
if (username.isEmpty()) username = qgetenv("USER");
|
||||
appData.addData(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)) {
|
||||
appData.addData(QString::fromWCharArray(username).toUtf8());
|
||||
}
|
||||
else {
|
||||
appData.addData(qgetenv("USERNAME"));
|
||||
}
|
||||
#endif
|
||||
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("/", "_");
|
||||
blockServerName_ = appData.result().toBase64().replace("/", "_");
|
||||
|
||||
}
|
||||
|
||||
void SingleCoreApplicationPrivate::initializeMemoryBlock() {
|
||||
|
||||
InstancesInfo* inst = static_cast<InstancesInfo*>(memory->data());
|
||||
InstancesInfo *inst = static_cast<InstancesInfo*>(memory_->data());
|
||||
inst->primary = false;
|
||||
inst->secondary = 0;
|
||||
inst->primaryPid = -1;
|
||||
inst->primaryUser[0] = '\0';
|
||||
inst->checksum = blockChecksum();
|
||||
|
||||
}
|
||||
@@ -156,133 +184,161 @@ void SingleCoreApplicationPrivate::startPrimary() {
|
||||
|
||||
Q_Q(SingleCoreApplication);
|
||||
|
||||
// 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);
|
||||
|
||||
// Reset the number of connections
|
||||
InstancesInfo* inst = static_cast <InstancesInfo*>(memory->data());
|
||||
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();
|
||||
|
||||
instanceNumber = 0;
|
||||
// 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() {}
|
||||
void SingleCoreApplicationPrivate::startSecondary() {
|
||||
|
||||
void SingleCoreApplicationPrivate::connectToPrimary(const int msecs, const ConnectionType connectionType) {
|
||||
InstancesInfo *inst = static_cast<InstancesInfo*>(memory_->data());
|
||||
|
||||
inst->secondary += 1;
|
||||
inst->checksum = blockChecksum();
|
||||
instanceNumber_ = inst->secondary;
|
||||
|
||||
}
|
||||
|
||||
bool SingleCoreApplicationPrivate::connectToPrimary(int timeout, 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_ == nullptr) {
|
||||
socket_ = new QLocalSocket();
|
||||
}
|
||||
|
||||
// If already connected - we are done;
|
||||
if (socket->state() == QLocalSocket::ConnectedState)
|
||||
return;
|
||||
if (socket_->state() == QLocalSocket::ConnectedState) return true;
|
||||
|
||||
// If not connect
|
||||
if (socket->state() == QLocalSocket::UnconnectedState ||
|
||||
socket->state() == QLocalSocket::ClosingState) {
|
||||
socket->connectToServer(blockServerName);
|
||||
}
|
||||
if (socket_->state() != QLocalSocket::ConnectedState) {
|
||||
|
||||
// Wait for being connected
|
||||
if (socket->state() == QLocalSocket::ConnectingState) {
|
||||
socket->waitForConnected(msecs);
|
||||
forever {
|
||||
randomSleep();
|
||||
|
||||
if (socket_->state() != QLocalSocket::ConnectingState)
|
||||
socket_->connectToServer(blockServerName_);
|
||||
|
||||
if (socket_->state() == QLocalSocket::ConnectingState) {
|
||||
socket_->waitForConnected(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
|
||||
if (socket->state() == QLocalSocket::ConnectedState) {
|
||||
// Notify the parent that a new instance had been started;
|
||||
QByteArray initMsg;
|
||||
QDataStream writeStream(&initMsg, QIODevice::WriteOnly);
|
||||
QByteArray initMsg;
|
||||
QDataStream writeStream(&initMsg, QIODevice::WriteOnly);
|
||||
writeStream.setVersion(QDataStream::Qt_5_8);
|
||||
|
||||
writeStream.setVersion(QDataStream::Qt_5_6);
|
||||
writeStream << blockServerName_.toLatin1();
|
||||
writeStream << static_cast<quint8>(connectionType);
|
||||
writeStream << instanceNumber_;
|
||||
|
||||
writeStream << blockServerName.toLatin1();
|
||||
writeStream << static_cast<quint8>(connectionType);
|
||||
writeStream << instanceNumber;
|
||||
quint16 checksum = qChecksum(initMsg.constData(), static_cast<quint32>(initMsg.length()));
|
||||
writeStream << checksum;
|
||||
#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
|
||||
|
||||
// The header indicates the message length that follows
|
||||
QByteArray header;
|
||||
QDataStream headerStream(&header, QIODevice::WriteOnly);
|
||||
writeStream << checksum;
|
||||
|
||||
headerStream.setVersion(QDataStream::Qt_5_6);
|
||||
// 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());
|
||||
|
||||
headerStream << static_cast <quint64>(initMsg.length());
|
||||
socket_->write(header);
|
||||
socket_->write(initMsg);
|
||||
bool result = socket_->waitForBytesWritten(timeout - time.elapsed());
|
||||
socket_->flush();
|
||||
|
||||
socket->write(header);
|
||||
socket->write(initMsg);
|
||||
socket->flush();
|
||||
socket->waitForBytesWritten(msecs);
|
||||
}
|
||||
return result;
|
||||
|
||||
}
|
||||
|
||||
quint16 SingleCoreApplicationPrivate::blockChecksum() {
|
||||
|
||||
return qChecksum(static_cast <const char*> (memory->data()), offsetof(InstancesInfo, checksum));
|
||||
#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() {
|
||||
|
||||
qint64 pid;
|
||||
|
||||
memory->lock();
|
||||
InstancesInfo* inst = static_cast<InstancesInfo*>(memory->data());
|
||||
pid = inst->primaryPid;
|
||||
memory->unlock();
|
||||
memory_->lock();
|
||||
InstancesInfo *inst = static_cast<InstancesInfo*>(memory_->data());
|
||||
qint64 pid = inst->primaryPid;
|
||||
memory_->unlock();
|
||||
|
||||
return pid;
|
||||
|
||||
}
|
||||
|
||||
QString SingleCoreApplicationPrivate::primaryUser() {
|
||||
|
||||
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());
|
||||
QLocalSocket *nextConnSocket = server_->nextPendingConnection();
|
||||
connectionMap_.insert(nextConnSocket, ConnectionInfo());
|
||||
|
||||
QObject::connect(nextConnSocket, &QLocalSocket::aboutToClose,
|
||||
[nextConnSocket, this]() {
|
||||
auto &info = connectionMap[nextConnSocket];
|
||||
Q_EMIT this->slotClientConnectionClosed(nextConnSocket, info.instanceId);
|
||||
}
|
||||
);
|
||||
QObject::connect(nextConnSocket, &QLocalSocket::aboutToClose, [nextConnSocket, this]() {
|
||||
auto &info = connectionMap_[nextConnSocket];
|
||||
Q_EMIT this->slotClientConnectionClosed(nextConnSocket, info.instanceId);
|
||||
});
|
||||
|
||||
QObject::connect(nextConnSocket, &QLocalSocket::disconnected,
|
||||
[nextConnSocket, this](){
|
||||
connectionMap.remove(nextConnSocket);
|
||||
nextConnSocket->deleteLater();
|
||||
}
|
||||
);
|
||||
QObject::connect(nextConnSocket, &QLocalSocket::disconnected, [nextConnSocket, this]() {
|
||||
connectionMap_.remove(nextConnSocket);
|
||||
nextConnSocket->deleteLater();
|
||||
});
|
||||
|
||||
QObject::connect(nextConnSocket, &QLocalSocket::readyRead,
|
||||
[nextConnSocket, this]() {
|
||||
auto &info = connectionMap[nextConnSocket];
|
||||
switch(info.stage) {
|
||||
QObject::connect(nextConnSocket, &QLocalSocket::readyRead, [nextConnSocket, this]() {
|
||||
auto &info = connectionMap_[nextConnSocket];
|
||||
switch (info.stage) {
|
||||
case StageHeader:
|
||||
readInitMessageHeader(nextConnSocket);
|
||||
break;
|
||||
@@ -294,15 +350,14 @@ void SingleCoreApplicationPrivate::slotConnectionEstablished() {
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
};
|
||||
}
|
||||
);
|
||||
};
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
void SingleCoreApplicationPrivate::readInitMessageHeader(QLocalSocket *sock) {
|
||||
|
||||
if (!connectionMap.contains(sock)) {
|
||||
if (!connectionMap_.contains(sock)) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -311,13 +366,12 @@ void SingleCoreApplicationPrivate::readInitMessageHeader(QLocalSocket *sock) {
|
||||
}
|
||||
|
||||
QDataStream headerStream(sock);
|
||||
|
||||
headerStream.setVersion(QDataStream::Qt_5_6);
|
||||
headerStream.setVersion(QDataStream::Qt_5_8);
|
||||
|
||||
// Read the header to know the message length
|
||||
quint64 msgLen = 0;
|
||||
headerStream >> msgLen;
|
||||
ConnectionInfo &info = connectionMap[sock];
|
||||
ConnectionInfo &info = connectionMap_[sock];
|
||||
info.stage = StageBody;
|
||||
info.msgLen = msgLen;
|
||||
|
||||
@@ -331,11 +385,11 @@ void SingleCoreApplicationPrivate::readInitMessageBody(QLocalSocket *sock) {
|
||||
|
||||
Q_Q(SingleCoreApplication);
|
||||
|
||||
if (!connectionMap.contains(sock)) {
|
||||
if (!connectionMap_.contains(sock)) {
|
||||
return;
|
||||
}
|
||||
|
||||
ConnectionInfo &info = connectionMap[sock];
|
||||
ConnectionInfo &info = connectionMap_[sock];
|
||||
if (sock->bytesAvailable() < static_cast<qint64>(info.msgLen)) {
|
||||
return;
|
||||
}
|
||||
@@ -343,8 +397,7 @@ void SingleCoreApplicationPrivate::readInitMessageBody(QLocalSocket *sock) {
|
||||
// Read the message body
|
||||
QByteArray msgBytes = sock->read(info.msgLen);
|
||||
QDataStream readStream(msgBytes);
|
||||
|
||||
readStream.setVersion(QDataStream::Qt_5_6);
|
||||
readStream.setVersion(QDataStream::Qt_5_8);
|
||||
|
||||
// server name
|
||||
QByteArray latin1Name;
|
||||
@@ -354,7 +407,7 @@ void SingleCoreApplicationPrivate::readInitMessageBody(QLocalSocket *sock) {
|
||||
ConnectionType connectionType = InvalidConnection;
|
||||
quint8 connTypeVal = InvalidConnection;
|
||||
readStream >> connTypeVal;
|
||||
connectionType = static_cast <ConnectionType>(connTypeVal);
|
||||
connectionType = static_cast<ConnectionType>(connTypeVal);
|
||||
|
||||
// instance id
|
||||
quint32 instanceId = 0;
|
||||
@@ -364,9 +417,13 @@ void SingleCoreApplicationPrivate::readInitMessageBody(QLocalSocket *sock) {
|
||||
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;
|
||||
bool isValid = readStream.status() == QDataStream::Ok && QLatin1String(latin1Name) == blockServerName_ && msgChecksum == actualChecksum;
|
||||
|
||||
if (!isValid) {
|
||||
sock->close();
|
||||
@@ -376,7 +433,7 @@ void SingleCoreApplicationPrivate::readInitMessageBody(QLocalSocket *sock) {
|
||||
info.instanceId = instanceId;
|
||||
info.stage = StageConnected;
|
||||
|
||||
if (connectionType == NewInstance || (connectionType == SecondaryInstance && options & SingleCoreApplication::Mode::SecondaryNotification)) {
|
||||
if (connectionType == NewInstance || (connectionType == SecondaryInstance && options_ & SingleCoreApplication::Mode::SecondaryNotification)) {
|
||||
Q_EMIT q->instanceStarted();
|
||||
}
|
||||
|
||||
@@ -395,7 +452,19 @@ void SingleCoreApplicationPrivate::slotDataAvailable(QLocalSocket *dataSocket, c
|
||||
|
||||
void SingleCoreApplicationPrivate::slotClientConnectionClosed(QLocalSocket *closedSocket, const quint32 instanceId) {
|
||||
|
||||
if (closedSocket->bytesAvailable() > 0)
|
||||
if (closedSocket->bytesAvailable() > 0) {
|
||||
Q_EMIT 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
|
||||
|
||||
}
|
||||
|
||||
@@ -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
|
||||
@@ -36,6 +36,7 @@
|
||||
|
||||
#include <QtGlobal>
|
||||
#include <QObject>
|
||||
#include <QString>
|
||||
#include <QMap>
|
||||
|
||||
#include "singlecoreapplication.h"
|
||||
@@ -48,6 +49,7 @@ struct InstancesInfo {
|
||||
bool primary;
|
||||
quint32 secondary;
|
||||
qint64 primaryPid;
|
||||
char primaryUser[128];
|
||||
quint16 checksum;
|
||||
};
|
||||
|
||||
@@ -60,6 +62,7 @@ struct ConnectionInfo {
|
||||
|
||||
class SingleCoreApplicationPrivate : public QObject {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
enum ConnectionType : quint8 {
|
||||
InvalidConnection = 0,
|
||||
@@ -74,27 +77,30 @@ class SingleCoreApplicationPrivate : public QObject {
|
||||
};
|
||||
Q_DECLARE_PUBLIC(SingleCoreApplication)
|
||||
|
||||
explicit SingleCoreApplicationPrivate(SingleCoreApplication *_q_ptr);
|
||||
explicit SingleCoreApplicationPrivate(SingleCoreApplication *ptr);
|
||||
~SingleCoreApplicationPrivate() override;
|
||||
|
||||
QString getUsername();
|
||||
void genBlockServerName();
|
||||
void initializeMemoryBlock();
|
||||
void startPrimary();
|
||||
void startSecondary();
|
||||
void connectToPrimary(const int msecs, const ConnectionType connectionType);
|
||||
bool connectToPrimary(const int msecs, const ConnectionType connectionType);
|
||||
quint16 blockChecksum();
|
||||
qint64 primaryPid();
|
||||
QString primaryUser();
|
||||
void readInitMessageHeader(QLocalSocket *socket);
|
||||
void readInitMessageBody(QLocalSocket *socket);
|
||||
void randomSleep();
|
||||
|
||||
SingleCoreApplication *q_ptr;
|
||||
QSharedMemory *memory;
|
||||
QLocalSocket *socket;
|
||||
QLocalServer *server;
|
||||
quint32 instanceNumber;
|
||||
QString blockServerName;
|
||||
SingleCoreApplication::Options options;
|
||||
QMap<QLocalSocket*, ConnectionInfo> connectionMap;
|
||||
QSharedMemory *memory_;
|
||||
QLocalSocket *socket_;
|
||||
QLocalServer *server_;
|
||||
quint32 instanceNumber_;
|
||||
QString blockServerName_;
|
||||
SingleCoreApplication::Options options_;
|
||||
QMap<QLocalSocket*, ConnectionInfo> connectionMap_;
|
||||
|
||||
public slots:
|
||||
void slotConnectionEstablished();
|
||||
|
||||
1
3rdparty/utf8-cpp/CMakeLists.txt
vendored
@@ -1,2 +1 @@
|
||||
cmake_minimum_required(VERSION 3.0)
|
||||
set(CMAKE_CXX_STANDARD 11)
|
||||
|
||||
140
CMakeLists.txt
@@ -13,7 +13,9 @@ include(cmake/OptionalSource.cmake)
|
||||
include(cmake/ParseArguments.cmake)
|
||||
include(cmake/Rpm.cmake)
|
||||
include(cmake/Deb.cmake)
|
||||
include(cmake/Dmg.cmake)
|
||||
if(APPLE)
|
||||
include(cmake/Dmg.cmake)
|
||||
endif()
|
||||
|
||||
set(CMAKE_MODULE_PATH ${CMAKE_SOURCE_DIR}/cmake)
|
||||
|
||||
@@ -27,12 +29,12 @@ if (${CMAKE_SYSTEM_NAME} STREQUAL "OpenBSD")
|
||||
set(OPENBSD ON)
|
||||
endif()
|
||||
|
||||
set(CMAKE_CXX_STANDARD 11)
|
||||
set(CMAKE_CXX_STANDARD 17)
|
||||
set(CMAKE_CXX_STANDARD_REQUIRED ON)
|
||||
|
||||
list(APPEND COMPILE_OPTIONS
|
||||
$<$<COMPILE_LANGUAGE:C>:--std=c99>
|
||||
$<$<COMPILE_LANGUAGE:CXX>:--std=c++11>
|
||||
$<$<COMPILE_LANGUAGE:CXX>:--std=c++17>
|
||||
-U__STRICT_ANSI__
|
||||
-Wall
|
||||
-Wextra
|
||||
@@ -91,6 +93,7 @@ find_package(Backtrace QUIET)
|
||||
if(Backtrace_FOUND)
|
||||
set(HAVE_BACKTRACE ON)
|
||||
endif()
|
||||
find_package(Iconv QUIET)
|
||||
find_package(GnuTLS REQUIRED)
|
||||
find_package(Protobuf REQUIRED)
|
||||
if (NOT Protobuf_PROTOC_EXECUTABLE)
|
||||
@@ -126,10 +129,37 @@ pkg_check_modules(LIBPULSE libpulse)
|
||||
pkg_check_modules(CHROMAPRINT libchromaprint)
|
||||
pkg_check_modules(LIBGPOD libgpod-1.0>=0.7.92)
|
||||
pkg_check_modules(LIBMTP libmtp>=1.0)
|
||||
pkg_check_modules(GDK_PIXBUF gdk-pixbuf-2.0)
|
||||
find_package(Gettext)
|
||||
find_package(FFTW3)
|
||||
|
||||
option(WITH_QT6 "Use Qt 6" OFF)
|
||||
if(NOT QT_DEFAULT_MAJOR_VERSION)
|
||||
set(QT_DEFAULT_MAJOR_VERSION 5)
|
||||
endif()
|
||||
set(QT_MAJOR_VERSION ${QT_DEFAULT_MAJOR_VERSION} CACHE STRING "Qt version to use (5 or 6), defaults to ${QT_DEFAULT_MAJOR_VERSION}")
|
||||
|
||||
option(BUILD_WITH_QT5 "Use Qt 5" OFF)
|
||||
option(BUILD_WITH_QT6 "Use Qt 6" OFF)
|
||||
|
||||
if(WITH_QT6)
|
||||
set(BUILD_WITH_QT6 ON)
|
||||
endif()
|
||||
|
||||
if(BUILD_WITH_QT5)
|
||||
set(QT_MAJOR_VERSION 5)
|
||||
elseif(BUILD_WITH_QT6)
|
||||
set(QT_MAJOR_VERSION 6)
|
||||
else()
|
||||
if(QT_MAJOR_VERSION EQUAL 5)
|
||||
set(BUILD_WITH_QT5 ON)
|
||||
elseif(QT_MAJOR_VERSION EQUAL 6)
|
||||
set(BUILD_WITH_QT6 ON)
|
||||
else()
|
||||
set(BUILD_WITH_QT5 ON)
|
||||
set(QT_MAJOR_VERSION 5)
|
||||
endif()
|
||||
endif()
|
||||
|
||||
set(QT_COMPONENTS Core Concurrent Widgets Network Sql)
|
||||
if(X11_FOUND)
|
||||
list(APPEND QT_COMPONENTS X11Extras)
|
||||
@@ -137,78 +167,35 @@ endif()
|
||||
if(DBUS_FOUND)
|
||||
list(APPEND QT_COMPONENTS DBus)
|
||||
endif()
|
||||
if(APPLE)
|
||||
list(APPEND QT_COMPONENTS MacExtras)
|
||||
endif()
|
||||
if(WIN32)
|
||||
list(APPEND QT_COMPONENTS WinExtras)
|
||||
endif()
|
||||
|
||||
if(WITH_QT6)
|
||||
list(APPEND QT_COMPONENTS Core5Compat)
|
||||
find_package(Qt6 REQUIRED COMPONENTS ${QT_COMPONENTS})
|
||||
set(QtCore_LIBRARIES Qt6::Core)
|
||||
set(QtConcurrent_LIBRARIES Qt6::Concurrent)
|
||||
set(QtWidgets_LIBRARIES Qt6::Widgets)
|
||||
set(QtNetwork_LIBRARIES Qt6::Network)
|
||||
set(QtSql_LIBRARIES Qt6::Sql)
|
||||
set(QT_LIBRARIES Qt6::Core Qt6::Concurrent Qt6::Widgets Qt6::Network Qt6::Sql Qt6::Core5Compat)
|
||||
if(Qt6DBus_FOUND)
|
||||
set(QtDBus_LIBRARIES Qt6::DBus)
|
||||
list(APPEND QT_LIBRARIES Qt6::DBus)
|
||||
get_target_property(QT_DBUSXML2CPP_EXECUTABLE Qt6::qdbusxml2cpp LOCATION)
|
||||
endif()
|
||||
if(Qt6X11Extras_FOUND)
|
||||
set(QtX11Extras_LIBRARIES Qt6::X11Extras)
|
||||
list(APPEND QT_LIBRARIES Qt6::X11Extras)
|
||||
endif()
|
||||
if(Qt6MacExtras_FOUND)
|
||||
set(QtMacExtras_LIBRARIES Qt6::MacExtras)
|
||||
list(APPEND QT_LIBRARIES Qt6::MacExtras)
|
||||
endif()
|
||||
if(Qt6WinExtras_FOUND)
|
||||
set(QtWinExtras_LIBRARIES Qt6::WinExtras)
|
||||
list(APPEND QT_LIBRARIES Qt6::WinExtras)
|
||||
endif()
|
||||
find_package(Qt6 QUIET COMPONENTS LinguistTools CONFIG)
|
||||
if (Qt6LinguistTools_FOUND)
|
||||
set(QT_LCONVERT_EXECUTABLE Qt6::lconvert)
|
||||
endif()
|
||||
else()
|
||||
set(QT_MIN_VERSION 5.8)
|
||||
find_package(Qt5 ${QT_MIN_VERSION} REQUIRED COMPONENTS ${QT_COMPONENTS})
|
||||
set(QtCore_LIBRARIES ${Qt5Core_LIBRARIES})
|
||||
set(QtConcurrent_LIBRARIES ${Qt5Concurrent_LIBRARIES})
|
||||
set(QtWidgets_LIBRARIES ${Qt5Widgets_LIBRARIES})
|
||||
set(QtNetwork_LIBRARIES ${Qt5Network_LIBRARIES})
|
||||
set(QtSql_LIBRARIES ${Qt5Sql_LIBRARIES})
|
||||
set(QT_LIBRARIES ${QtCore_LIBRARIES} ${QtConcurrent_LIBRARIES} ${QtWidgets_LIBRARIES} ${QtNetwork_LIBRARIES} ${QtSql_LIBRARIES})
|
||||
set(QT_INCLUDE_DIRS ${Qt5Core_INCLUDE_DIRS} ${Qt5Concurrent_INCLUDE_DIRS} ${Qt5Widgets_INCLUDE_DIRS} ${Qt5Network_INCLUDE_DIRS} ${Qt5Sql_INCLUDE_DIRS})
|
||||
if(Qt5DBus_FOUND)
|
||||
set(QtDBus_LIBRARIES ${Qt5DBus_LIBRARIES})
|
||||
list(APPEND QT_LIBRARIES ${Qt5DBus_LIBRARIES})
|
||||
list(APPEND QT_INCLUDE_DIRS ${Qt5DBus_INCLUDE_DIRS})
|
||||
get_target_property(QT_DBUSXML2CPP_EXECUTABLE Qt5::qdbusxml2cpp LOCATION)
|
||||
endif()
|
||||
if(Qt5X11Extras_FOUND)
|
||||
set(QtX11Extras_LIBRARIES ${Qt5X11Extras_LIBRARIES})
|
||||
list(APPEND QT_LIBRARIES ${Qt5X11Extras_LIBRARIES})
|
||||
list(APPEND QT_INCLUDE_DIRS ${Qt5X11Extras_INCLUDE_DIRS})
|
||||
endif()
|
||||
if(Qt5MacExtras_FOUND)
|
||||
set(QtMacExtras_LIBRARIES ${Qt5MacExtras_LIBRARIES})
|
||||
list(APPEND QT_LIBRARIES ${Qt5MacExtras_LIBRARIES})
|
||||
list(APPEND QT_INCLUDE_DIRS ${Qt5MacExtras_INCLUDE_DIRS})
|
||||
endif()
|
||||
if(Qt5WinExtras_FOUND)
|
||||
set(QtWinExtras_LIBRARIES ${Qt5WinExtras_LIBRARIES})
|
||||
list(APPEND QT_LIBRARIES ${Qt5WinExtras_LIBRARIES})
|
||||
list(APPEND QT_INCLUDE_DIRS ${Qt5WinExtras_INCLUDE_DIRS})
|
||||
endif()
|
||||
find_package(Qt5 ${QT_MIN_VERSION} QUIET COMPONENTS LinguistTools CONFIG)
|
||||
if (Qt5LinguistTools_FOUND)
|
||||
set(QT_LCONVERT_EXECUTABLE Qt5::lconvert)
|
||||
endif()
|
||||
find_package(Qt${QT_MAJOR_VERSION} REQUIRED COMPONENTS ${QT_COMPONENTS})
|
||||
|
||||
set(QtCore_LIBRARIES Qt${QT_MAJOR_VERSION}::Core)
|
||||
set(QtConcurrent_LIBRARIES Qt${QT_MAJOR_VERSION}::Concurrent)
|
||||
set(QtWidgets_LIBRARIES Qt${QT_MAJOR_VERSION}::Widgets)
|
||||
set(QtNetwork_LIBRARIES Qt${QT_MAJOR_VERSION}::Network)
|
||||
set(QtSql_LIBRARIES Qt${QT_MAJOR_VERSION}::Sql)
|
||||
set(QT_LIBRARIES Qt${QT_MAJOR_VERSION}::Core Qt${QT_MAJOR_VERSION}::Concurrent Qt${QT_MAJOR_VERSION}::Widgets Qt${QT_MAJOR_VERSION}::Network Qt${QT_MAJOR_VERSION}::Sql)
|
||||
if(Qt${QT_MAJOR_VERSION}DBus_FOUND)
|
||||
set(QtDBus_LIBRARIES Qt${QT_MAJOR_VERSION}::DBus)
|
||||
list(APPEND QT_LIBRARIES Qt${QT_MAJOR_VERSION}::DBus)
|
||||
get_target_property(QT_DBUSXML2CPP_EXECUTABLE Qt${QT_MAJOR_VERSION}::qdbusxml2cpp LOCATION)
|
||||
endif()
|
||||
if(Qt${QT_MAJOR_VERSION}X11Extras_FOUND)
|
||||
set(QtX11Extras_LIBRARIES Qt${QT_MAJOR_VERSION}::X11Extras)
|
||||
list(APPEND QT_LIBRARIES Qt${QT_MAJOR_VERSION}::X11Extras)
|
||||
endif()
|
||||
if(Qt${QT_MAJOR_VERSION}WinExtras_FOUND)
|
||||
set(QtWinExtras_LIBRARIES Qt${QT_MAJOR_VERSION}::WinExtras)
|
||||
list(APPEND QT_LIBRARIES Qt${QT_MAJOR_VERSION}::WinExtras)
|
||||
endif()
|
||||
|
||||
find_package(Qt${QT_MAJOR_VERSION} QUIET COMPONENTS LinguistTools CONFIG)
|
||||
if(Qt${QT_MAJOR_VERSION}LinguistTools_FOUND)
|
||||
set(QT_LCONVERT_EXECUTABLE Qt${QT_MAJOR_VERSION}::lconvert)
|
||||
endif()
|
||||
|
||||
if(X11_FOUND)
|
||||
@@ -261,7 +248,7 @@ if(APPLE)
|
||||
endif(APPLE)
|
||||
|
||||
if(NOT SPARKLE AND (APPLE OR WIN32))
|
||||
if(WITH_QT6)
|
||||
if(BUILD_WITH_QT6)
|
||||
pkg_check_modules(QTSPARKLE qtsparkle-qt6)
|
||||
else()
|
||||
pkg_check_modules(QTSPARKLE qtsparkle-qt5)
|
||||
@@ -335,6 +322,7 @@ optional_component(GIO ON "Devices: GIO device backend"
|
||||
|
||||
optional_component(LIBGPOD ON "Devices: iPod classic support"
|
||||
DEPENDS "libgpod" LIBGPOD_FOUND
|
||||
DEPENDS "gdk-pixbuf" GDK_PIXBUF_FOUND
|
||||
)
|
||||
|
||||
optional_component(LIBMTP ON "Devices: MTP support"
|
||||
@@ -346,7 +334,7 @@ optional_component(SPARKLE ON "Sparkle integration"
|
||||
DEPENDS "Sparkle" SPARKLE
|
||||
)
|
||||
|
||||
if(WITH_QT6)
|
||||
if(BUILD_WITH_QT6)
|
||||
optional_component(TRANSLATIONS ON "Translations"
|
||||
DEPENDS "gettext" GETTEXT_FOUND
|
||||
DEPENDS "Qt6LinguistTools" Qt6LinguistTools_FOUND
|
||||
@@ -362,6 +350,7 @@ 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(MOODBAR ON "Moodbar"
|
||||
DEPENDS "fftw3" FFTW3_FOUND
|
||||
@@ -386,9 +375,8 @@ endif(USE_BUNDLE AND NOT USE_BUNDLE_DIR)
|
||||
# Check that we have sqlite3 with FTS5
|
||||
|
||||
if(NOT CMAKE_CROSSCOMPILING)
|
||||
set(CMAKE_REQUIRED_FLAGS "--std=c++11")
|
||||
set(CMAKE_REQUIRED_FLAGS "--std=c++17")
|
||||
set(CMAKE_REQUIRED_LIBRARIES ${QtCore_LIBRARIES} ${QtSql_LIBRARIES})
|
||||
set(CMAKE_REQUIRED_INCLUDES ${QtCore_INCLUDE_DIRS} ${QtSql_INCLUDE_DIRS})
|
||||
check_cxx_source_runs("
|
||||
#include <QSqlDatabase>
|
||||
#include <QSqlQuery>
|
||||
|
||||
102
Changelog
@@ -2,9 +2,109 @@ Strawberry Music Player
|
||||
=======================
|
||||
ChangeLog
|
||||
|
||||
0.8.4:
|
||||
|
||||
Bugfixes:
|
||||
* Fix preventing session logout when window is maxmimized.
|
||||
* Fix empty space in organize window when copying songs/playlists to devices.
|
||||
* Fix crash when opening about dialog in a wayland session.
|
||||
* Fix stretched fancy/side tabbar style issue with adwaita style (Fedora/Gnome).
|
||||
* Fix centering star icon on playlist tabbar.
|
||||
* Fix network proxy settings for streaming.
|
||||
* Fix copy URL to clipboard to handle non-ASCII characters.
|
||||
* Fix HiDPI scaling for glow animation and drag over playlist.
|
||||
* Fix smart playlist search by filename.
|
||||
* Fix single letter collection nodes showing before dividers.
|
||||
|
||||
Enhancements:
|
||||
* Add support for native global shortcuts on KDE.
|
||||
* Add track progress in system tray icon as an option.
|
||||
* Only strip problematic characters in suggested filename when saving a playlist to file.
|
||||
* Change star/unstar playlist to doubleclick instead of singleclick.
|
||||
* Don't edit playlist name on doubleclick in playlists view.
|
||||
* Make context view top label text selectable.
|
||||
* Add setting to change Qt style.
|
||||
* Clear ID3v3 tags that are empty, and clear ID3v1 tags when setting ID3v3 tags.
|
||||
* Remove remaining uses of QTextCodec.
|
||||
* Remove Core5Compat dependency.
|
||||
|
||||
0.8.3:
|
||||
|
||||
Bugfixes:
|
||||
* Fixed updating playing widget song details in small cover mode.
|
||||
* Fixed file extension when transcoding songs.
|
||||
* Fixed updating album cover to collection in edit tag dialog when pressing save.
|
||||
* Fixed songs with empty artist in collection.
|
||||
* Fixed possible crashes with stream discovery.
|
||||
* Fixed setting engine state to null.
|
||||
* Fixed tagreader crash with empty APE tags.
|
||||
* Fixed a gstreamer memory leak.
|
||||
|
||||
Enhancements:
|
||||
* (Windows) Added WASAPI plugin.
|
||||
|
||||
0.8.2:
|
||||
|
||||
Bugfixes:
|
||||
* Fixed broken transition to next song for CUE files with certain audio formats (regression since version 0.6.13).
|
||||
* Fixed all collection divider keys showing on top with some language collate settings (regression in version 0.8.1).
|
||||
* Fixed SQL querying songs by song ID when song ID is a string.
|
||||
* Fixed saving album covers for LMS Subsonic servers.
|
||||
* Fixed reading song creation dates with LMS Subsonic servers.
|
||||
* Fixed saving initial settings.
|
||||
* 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:
|
||||
|
||||
Bugfixes:
|
||||
* Fixed engine selection in backend settings with Qt 6.
|
||||
* Fixed pixelated playlist source icon for currently playing song.
|
||||
* Fixed crash when deleting queued songs from playlist.
|
||||
* Fixed situations where songs could disappear or be shown multiple times with certain collection groupings.
|
||||
* Fixed initial sizes of playlist header columns.
|
||||
* Fixed Strawberry preventing logout.
|
||||
* Fixed incorrectly splitting of basename for moodbar and transcoding for filenames with several dots.
|
||||
* Fixed certain cases where "playing now" for scrobbler were sent twice.
|
||||
* Fixed album cover loaded twice for certain songs causing slugglish playing widget.
|
||||
* Fixed playing widget to draw text after album cover is fully shown.
|
||||
* Fixed crash when trying to copy a closed playlist to a device.
|
||||
* Fixed incorrect song source for CUE songs when added through the collection watcher.
|
||||
* Disable use of HTML in system tray tooltip on Cinnamon too.
|
||||
* Remove problematic '&' character from OSD messages.
|
||||
* (macOS) Fixed crash on exit when cover manager is open.
|
||||
* (macOS) Fixed graphical corruption.
|
||||
* (Windows) Fixed GStreamer registry problems.
|
||||
* (Windows) Register Tidal URL Scheme in Windows installer.
|
||||
|
||||
Enhancements:
|
||||
* Improved playlist autoscrolling.
|
||||
* Only allow playlist right click tag editing for editable songs.
|
||||
* Read song creation time from subsonic API.
|
||||
* Remember manually set compilation status for albums when songs are rescanned.
|
||||
* Added icons for edit tag playlist right click menu actions.
|
||||
* Maximize dialogs if they are already open when clicked again in the menu.
|
||||
* Added support for compilation tag to edit tag dialog.
|
||||
* Show song info and album cover in OSD on stop and pause.
|
||||
* Reshow OSD on song restart.
|
||||
* Always save initial settings.
|
||||
* Removed use of deprecated gstreamer "low-percent" (Minimum buffer fill setting).
|
||||
* Added buffer low and high watermark settings to backend settings.
|
||||
* Make use of newer version of the desktop notifications service when available.
|
||||
|
||||
New features:
|
||||
* Added setting for enabling scrobbling based on song source.
|
||||
* Added optional delete from disk in collection and playlist.
|
||||
* Added Last.fm import data wizard.
|
||||
* Added smart and dynamic playlists.
|
||||
* Added song ratings.
|
||||
* Added Qobuz streaming support.
|
||||
* Added Subsonic server side scrobbling support.
|
||||
* Load thumbnails from iPods to show under device collection.
|
||||
|
||||
0.7.2:
|
||||
|
||||
BugFixes:
|
||||
Bugfixes:
|
||||
* Fixed installation directory for translations.
|
||||
* Fixed collection sorting for non-ASCII characters.
|
||||
* Fixed closing connected devices on exit.
|
||||
|
||||
@@ -1,5 +0,0 @@
|
||||
from jonaski/opensuse:lp151
|
||||
|
||||
run mkdir -p /usr/src/app
|
||||
workdir /usr/src/app
|
||||
copy . /usr/src/app
|
||||
19
README.md
@@ -2,7 +2,7 @@
|
||||
[](https://paypal.me/jonaskvinge)
|
||||
=======================
|
||||
|
||||
Strawberry is a music player and music collection organizer. It is a fork of Clementine released in 2018 aimed at music collectors and audiophiles. It's written in C++ using the Qt 5 or 6 toolkit.
|
||||
Strawberry is a music player and music collection organizer. It is a fork of Clementine released in 2018 aimed at music collectors and audiophiles. It's written in C++ using the Qt toolkit.
|
||||
|
||||

|
||||
|
||||
@@ -35,7 +35,8 @@ You can also make a one-time payment through [paypal.me/jonaskvinge](https://pay
|
||||
* Supports WAV, FLAC, WavPack, Ogg FLAC, Ogg Vorbis, Ogg Opus, Ogg Speex, MPC, TrueAudio, AIFF, MP4, MP3, ASF and Monkey's Audio.
|
||||
* Audio CD playback
|
||||
* Native desktop notifications
|
||||
* Playlists in multiple formats
|
||||
* Playlist management
|
||||
* Smart and dynamic playlists
|
||||
* Advanced audio output and device configuration for bit-perfect playback on Linux
|
||||
* Edit tags on music files
|
||||
* Fetch tags from MusicBrainz
|
||||
@@ -46,7 +47,7 @@ You can also make a one-time payment through [paypal.me/jonaskvinge](https://pay
|
||||
* Audio equalizer
|
||||
* Transfer music to iPod, MTP or mass-storage USB player
|
||||
* Scrobbler with support for [Last.fm](https://www.last.fm/), [Libre.fm](https://libre.fm/) and [ListenBrainz](https://listenbrainz.org/)
|
||||
* Subsonic and Tidal streaming support
|
||||
* Subsonic, Tidal and Qobuz streaming support
|
||||
|
||||
|
||||
It has so far been tested to work on Linux, OpenBSD, FreeBSD, macOS and Windows.
|
||||
@@ -64,11 +65,11 @@ To build Strawberry from source you need the following installed on your system
|
||||
* [GLib](https://developer.gnome.org/glib/)
|
||||
* [Protobuf library and compiler](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 components X11Extras and DBus for Linux/BSD, MacExtras for macOS and WinExtras for Windows](https://www.qt.io/)
|
||||
* [Qt components X11Extras and D-Bus for Linux/BSD and WinExtras for Windows](https://www.qt.io/)
|
||||
* [SQLite 3.9 or newer with FTS5](https://www.sqlite.org)
|
||||
* [Chromaprint library](https://acoustid.org/chromaprint)
|
||||
* [ALSA library (linux)](https://www.alsa-project.org/)
|
||||
* [DBus (linux)](https://www.freedesktop.org/wiki/Software/dbus/)
|
||||
* [D-Bus (linux)](https://www.freedesktop.org/wiki/Software/dbus/)
|
||||
* [PulseAudio (linux optional)](https://www.freedesktop.org/wiki/Software/PulseAudio/?)
|
||||
* [GStreamer](https://gstreamer.freedesktop.org/) or [VLC](https://www.videolan.org)
|
||||
* [GnuTLS](https://www.gnutls.org/)
|
||||
@@ -83,8 +84,6 @@ Optional dependencies:
|
||||
Either GStreamer or VLC engine is required, but only GStreamer is fully implemented so far.
|
||||
You should also install the gstreamer plugins base and good, and optionally bad and ugly.
|
||||
|
||||
With Qt 6 we also depend on the Core5Compat module for QTextCodec.
|
||||
|
||||
### :wrench: Compiling from source
|
||||
|
||||
### Get the code:
|
||||
@@ -96,10 +95,12 @@ With Qt 6 we also depend on the Core5Compat module for QTextCodec.
|
||||
cd strawberry
|
||||
mkdir build && cd build
|
||||
cmake ..
|
||||
make -j4
|
||||
make -j$(nproc)
|
||||
sudo make install
|
||||
|
||||
To compile with Qt 6 use: cmake .. -DWITH_QT6=ON
|
||||
To compile with Qt 6 use:
|
||||
|
||||
cmake .. -DBUILD_WITH_QT6=ON
|
||||
|
||||
### :penguin: Packaging status
|
||||
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
execute_process(COMMAND ${CMAKE_SOURCE_DIR}/dist/macos/macversion.sh OUTPUT_VARIABLE MACOS_VERSION_PACKAGE OUTPUT_STRIP_TRAILING_WHITESPACE)
|
||||
|
||||
add_custom_target(dmg
|
||||
COMMAND /usr/local/opt/qt5/bin/macdeployqt strawberry.app
|
||||
COMMAND ${CMAKE_SOURCE_DIR}/dist/macos/macdeploy.py strawberry.app
|
||||
COMMAND create-dmg --volname strawberry --background "${CMAKE_SOURCE_DIR}/dist/macos/dmg_background.png" --app-drop-link 450 218 --icon strawberry.app 150 218 --window-size 600 450 strawberry-${STRAWBERRY_VERSION_PACKAGE}.dmg strawberry.app
|
||||
COMMAND create-dmg --volname strawberry --background "${CMAKE_SOURCE_DIR}/dist/macos/dmg_background.png" --app-drop-link 450 218 --icon strawberry.app 150 218 --window-size 600 450 strawberry-${STRAWBERRY_VERSION_PACKAGE}-${MACOS_VERSION_PACKAGE}.dmg strawberry.app
|
||||
WORKING_DIRECTORY ${CMAKE_BINARY_DIR}
|
||||
)
|
||||
|
||||
@@ -15,7 +15,7 @@ macro(optional_source TOGGLE)
|
||||
list(APPEND OTHER_SOURCES ${OPTIONAL_SOURCE_HEADERS})
|
||||
|
||||
set(_uic_sources)
|
||||
if(WITH_QT6)
|
||||
if(BUILD_WITH_QT6)
|
||||
qt6_wrap_ui(_uic_sources ${OPTIONAL_SOURCE_UI})
|
||||
else()
|
||||
qt5_wrap_ui(_uic_sources ${OPTIONAL_SOURCE_UI})
|
||||
|
||||
@@ -77,7 +77,7 @@ macro(add_po outfiles po_prefix)
|
||||
file(APPEND ${_qrc} "<file>${po_prefix}${_lang}.qm</file>")
|
||||
endforeach(_lang)
|
||||
file(APPEND ${_qrc} "</qresource></RCC>")
|
||||
if(WITH_QT6)
|
||||
if(BUILD_WITH_QT6)
|
||||
qt6_add_resources(${outfiles} ${_qrc})
|
||||
else()
|
||||
qt5_add_resources(${outfiles} ${_qrc})
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
set(STRAWBERRY_VERSION_MAJOR 0)
|
||||
set(STRAWBERRY_VERSION_MINOR 7)
|
||||
set(STRAWBERRY_VERSION_PATCH 2)
|
||||
set(STRAWBERRY_VERSION_MINOR 8)
|
||||
set(STRAWBERRY_VERSION_PATCH 4)
|
||||
#set(STRAWBERRY_VERSION_PRERELEASE rc1)
|
||||
|
||||
set(INCLUDE_GIT_REVISION OFF)
|
||||
|
||||
@@ -13,11 +13,13 @@
|
||||
<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/device-schema.sql</file>
|
||||
<file>style/strawberry.css</file>
|
||||
<file>html/playing-tooltip.html</file>
|
||||
<file>style/smartplaylistsearchterm.css</file>
|
||||
<file>html/oauthsuccess.html</file>
|
||||
<file>pictures/strawberry.png</file>
|
||||
<file>pictures/strawberry-grey.png</file>
|
||||
<file>pictures/strawberry-faded.png</file>
|
||||
<file>pictures/strawbs.png</file>
|
||||
<file>pictures/nomusic.png</file>
|
||||
@@ -40,6 +42,8 @@
|
||||
<file>pictures/osd_shadow_edge.png</file>
|
||||
<file>pictures/nyancat.png</file>
|
||||
<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>
|
||||
|
||||
@@ -1,41 +0,0 @@
|
||||
<table cellspacing="5" cellpadding="5">
|
||||
<tr>
|
||||
<td colspan="%columns">
|
||||
<center><h4>%appName</h4></center>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
%image
|
||||
<td>
|
||||
<table cellspacing="1" cellpadding="1">
|
||||
<tr>
|
||||
<td>
|
||||
<p align="right">%titleKey</p>
|
||||
</td>
|
||||
<td>%titleValue</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<p align="right">%artistKey</p>
|
||||
</td>
|
||||
<td>%artistValue</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<p align="right">%albumKey</p>
|
||||
</td>
|
||||
<td>%albumValue</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td colspan="2" height="20" />
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<p align="right">%lengthKey</p>
|
||||
</td>
|
||||
<td>%lengthValue</td>
|
||||
</tr>
|
||||
</table>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
@@ -72,6 +72,7 @@
|
||||
<file>icons/128x128/star-grey.png</file>
|
||||
<file>icons/128x128/star.png</file>
|
||||
<file>icons/128x128/strawberry.png</file>
|
||||
<file>icons/128x128/strawberry-grey.png</file>
|
||||
<file>icons/128x128/tools-wizard.png</file>
|
||||
<file>icons/128x128/view-choose.png</file>
|
||||
<file>icons/128x128/view-fullscreen.png</file>
|
||||
@@ -89,6 +90,8 @@
|
||||
<file>icons/128x128/love.png</file>
|
||||
<file>icons/128x128/subsonic.png</file>
|
||||
<file>icons/128x128/tidal.png</file>
|
||||
<file>icons/128x128/qobuz.png</file>
|
||||
<file>icons/128x128/multimedia-player-ipod-standard-black.png</file>
|
||||
<file>icons/64x64/albums.png</file>
|
||||
<file>icons/64x64/alsa.png</file>
|
||||
<file>icons/64x64/application-exit.png</file>
|
||||
@@ -162,6 +165,7 @@
|
||||
<file>icons/64x64/star-grey.png</file>
|
||||
<file>icons/64x64/star.png</file>
|
||||
<file>icons/64x64/strawberry.png</file>
|
||||
<file>icons/64x64/strawberry-grey.png</file>
|
||||
<file>icons/64x64/tools-wizard.png</file>
|
||||
<file>icons/64x64/view-choose.png</file>
|
||||
<file>icons/64x64/view-fullscreen.png</file>
|
||||
@@ -179,6 +183,8 @@
|
||||
<file>icons/64x64/love.png</file>
|
||||
<file>icons/64x64/subsonic.png</file>
|
||||
<file>icons/64x64/tidal.png</file>
|
||||
<file>icons/64x64/qobuz.png</file>
|
||||
<file>icons/64x64/multimedia-player-ipod-standard-black.png</file>
|
||||
<file>icons/48x48/albums.png</file>
|
||||
<file>icons/48x48/alsa.png</file>
|
||||
<file>icons/48x48/application-exit.png</file>
|
||||
@@ -256,6 +262,7 @@
|
||||
<file>icons/48x48/star-grey.png</file>
|
||||
<file>icons/48x48/star.png</file>
|
||||
<file>icons/48x48/strawberry.png</file>
|
||||
<file>icons/48x48/strawberry-grey.png</file>
|
||||
<file>icons/48x48/tools-wizard.png</file>
|
||||
<file>icons/48x48/view-choose.png</file>
|
||||
<file>icons/48x48/view-fullscreen.png</file>
|
||||
@@ -273,6 +280,8 @@
|
||||
<file>icons/48x48/love.png</file>
|
||||
<file>icons/48x48/subsonic.png</file>
|
||||
<file>icons/48x48/tidal.png</file>
|
||||
<file>icons/48x48/qobuz.png</file>
|
||||
<file>icons/48x48/multimedia-player-ipod-standard-black.png</file>
|
||||
<file>icons/32x32/albums.png</file>
|
||||
<file>icons/32x32/alsa.png</file>
|
||||
<file>icons/32x32/application-exit.png</file>
|
||||
@@ -350,6 +359,7 @@
|
||||
<file>icons/32x32/star-grey.png</file>
|
||||
<file>icons/32x32/star.png</file>
|
||||
<file>icons/32x32/strawberry.png</file>
|
||||
<file>icons/32x32/strawberry-grey.png</file>
|
||||
<file>icons/32x32/tools-wizard.png</file>
|
||||
<file>icons/32x32/view-choose.png</file>
|
||||
<file>icons/32x32/view-fullscreen.png</file>
|
||||
@@ -367,6 +377,8 @@
|
||||
<file>icons/32x32/love.png</file>
|
||||
<file>icons/32x32/subsonic.png</file>
|
||||
<file>icons/32x32/tidal.png</file>
|
||||
<file>icons/32x32/qobuz.png</file>
|
||||
<file>icons/32x32/multimedia-player-ipod-standard-black.png</file>
|
||||
<file>icons/22x22/albums.png</file>
|
||||
<file>icons/22x22/alsa.png</file>
|
||||
<file>icons/22x22/application-exit.png</file>
|
||||
@@ -444,6 +456,7 @@
|
||||
<file>icons/22x22/star-grey.png</file>
|
||||
<file>icons/22x22/star.png</file>
|
||||
<file>icons/22x22/strawberry.png</file>
|
||||
<file>icons/22x22/strawberry-grey.png</file>
|
||||
<file>icons/22x22/tools-wizard.png</file>
|
||||
<file>icons/22x22/view-choose.png</file>
|
||||
<file>icons/22x22/view-fullscreen.png</file>
|
||||
@@ -461,5 +474,7 @@
|
||||
<file>icons/22x22/love.png</file>
|
||||
<file>icons/22x22/subsonic.png</file>
|
||||
<file>icons/22x22/tidal.png</file>
|
||||
<file>icons/22x22/qobuz.png</file>
|
||||
<file>icons/22x22/multimedia-player-ipod-standard-black.png</file>
|
||||
</qresource>
|
||||
</RCC>
|
||||
|
||||
BIN
data/icons/128x128/multimedia-player-ipod-standard-black.png
Normal file
|
After Width: | Height: | Size: 8.9 KiB |
BIN
data/icons/128x128/qobuz.png
Normal file
|
After Width: | Height: | Size: 6.3 KiB |
BIN
data/icons/128x128/strawberry-grey.png
Normal file
|
After Width: | Height: | Size: 16 KiB |
BIN
data/icons/22x22/multimedia-player-ipod-standard-black.png
Normal file
|
After Width: | Height: | Size: 918 B |
BIN
data/icons/22x22/qobuz.png
Normal file
|
After Width: | Height: | Size: 964 B |
BIN
data/icons/22x22/strawberry-grey.png
Normal file
|
After Width: | Height: | Size: 1.2 KiB |
BIN
data/icons/32x32/multimedia-player-ipod-standard-black.png
Normal file
|
After Width: | Height: | Size: 1.4 KiB |
BIN
data/icons/32x32/qobuz.png
Normal file
|
After Width: | Height: | Size: 1.4 KiB |
BIN
data/icons/32x32/strawberry-grey.png
Normal file
|
After Width: | Height: | Size: 2.0 KiB |
BIN
data/icons/48x48/multimedia-player-ipod-standard-black.png
Normal file
|
After Width: | Height: | Size: 2.3 KiB |
BIN
data/icons/48x48/qobuz.png
Normal file
|
After Width: | Height: | Size: 2.2 KiB |
BIN
data/icons/48x48/strawberry-grey.png
Normal file
|
After Width: | Height: | Size: 3.8 KiB |
BIN
data/icons/64x64/multimedia-player-ipod-standard-black.png
Normal file
|
After Width: | Height: | Size: 3.1 KiB |
BIN
data/icons/64x64/qobuz.png
Normal file
|
After Width: | Height: | Size: 3.0 KiB |
BIN
data/icons/64x64/strawberry-grey.png
Normal file
|
After Width: | Height: | Size: 5.9 KiB |
BIN
data/icons/full/multimedia-player-ipod-standard-black.png
Normal file
|
After Width: | Height: | Size: 20 KiB |
BIN
data/icons/full/qobuz.png
Normal file
|
After Width: | Height: | Size: 59 KiB |
BIN
data/icons/full/strawberry-grey.png
Normal file
|
After Width: | Height: | Size: 352 KiB |
BIN
data/pictures/star-off.png
Normal file
|
After Width: | Height: | Size: 351 B |
BIN
data/pictures/star-on.png
Normal file
|
After Width: | Height: | Size: 284 B |
BIN
data/pictures/strawberry-grey.png
Normal file
|
After Width: | Height: | Size: 127 KiB |
@@ -62,7 +62,9 @@ CREATE TABLE device_%deviceid_songs (
|
||||
effective_albumartist TEXT,
|
||||
effective_originalyear INTEGER NOT NULL DEFAULT 0,
|
||||
|
||||
cue_path TEXT
|
||||
cue_path TEXT,
|
||||
|
||||
rating INTEGER DEFAULT -1
|
||||
|
||||
);
|
||||
|
||||
@@ -75,4 +77,4 @@ CREATE VIRTUAL TABLE device_%deviceid_fts USING fts5(
|
||||
tokenize = "unicode61 remove_diacritics 1"
|
||||
);
|
||||
|
||||
UPDATE devices SET schema_version=1 WHERE ROWID=%deviceid;
|
||||
UPDATE devices SET schema_version=2 WHERE ROWID=%deviceid;
|
||||
|
||||
231
data/schema/schema-13.sql
Normal file
@@ -0,0 +1,231 @@
|
||||
ALTER TABLE %allsongstables ADD COLUMN rating INTEGER DEFAULT -1;
|
||||
|
||||
ALTER TABLE playlists ADD COLUMN dynamic_playlist_type INTEGER;
|
||||
|
||||
ALTER TABLE playlists ADD COLUMN dynamic_playlist_backend TEXT;
|
||||
|
||||
ALTER TABLE playlists ADD COLUMN dynamic_playlist_data BLOB;
|
||||
|
||||
CREATE TABLE IF NOT EXISTS qobuz_artists_songs (
|
||||
|
||||
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,
|
||||
compilation INTEGER NOT NULL DEFAULT 0,
|
||||
composer TEXT,
|
||||
performer TEXT,
|
||||
grouping TEXT,
|
||||
comment TEXT,
|
||||
lyrics TEXT,
|
||||
|
||||
artist_id TEXT,
|
||||
album_id TEXT,
|
||||
song_id TEXT,
|
||||
|
||||
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,
|
||||
|
||||
rating INTEGER DEFAULT -1
|
||||
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS qobuz_albums_songs (
|
||||
|
||||
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,
|
||||
compilation INTEGER NOT NULL DEFAULT 0,
|
||||
composer TEXT,
|
||||
performer TEXT,
|
||||
grouping TEXT,
|
||||
comment TEXT,
|
||||
lyrics TEXT,
|
||||
|
||||
artist_id TEXT,
|
||||
album_id TEXT,
|
||||
song_id TEXT,
|
||||
|
||||
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,
|
||||
|
||||
rating INTEGER DEFAULT -1
|
||||
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS qobuz_songs (
|
||||
|
||||
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,
|
||||
compilation INTEGER NOT NULL DEFAULT 0,
|
||||
composer TEXT,
|
||||
performer TEXT,
|
||||
grouping TEXT,
|
||||
comment TEXT,
|
||||
lyrics TEXT,
|
||||
|
||||
artist_id TEXT,
|
||||
album_id TEXT,
|
||||
song_id TEXT,
|
||||
|
||||
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,
|
||||
|
||||
rating INTEGER DEFAULT -1
|
||||
|
||||
);
|
||||
|
||||
CREATE VIRTUAL TABLE IF NOT EXISTS qobuz_artists_songs_fts USING fts5(
|
||||
|
||||
ftstitle,
|
||||
ftsalbum,
|
||||
ftsartist,
|
||||
ftsalbumartist,
|
||||
ftscomposer,
|
||||
ftsperformer,
|
||||
ftsgrouping,
|
||||
ftsgenre,
|
||||
ftscomment,
|
||||
tokenize = "unicode61 remove_diacritics 1"
|
||||
|
||||
);
|
||||
|
||||
CREATE VIRTUAL TABLE IF NOT EXISTS qobuz_albums_songs_fts USING fts5(
|
||||
|
||||
ftstitle,
|
||||
ftsalbum,
|
||||
ftsartist,
|
||||
ftsalbumartist,
|
||||
ftscomposer,
|
||||
ftsperformer,
|
||||
ftsgrouping,
|
||||
ftsgenre,
|
||||
ftscomment,
|
||||
tokenize = "unicode61 remove_diacritics 1"
|
||||
|
||||
);
|
||||
|
||||
CREATE VIRTUAL TABLE IF NOT EXISTS qobuz_songs_fts USING fts5(
|
||||
|
||||
ftstitle,
|
||||
ftsalbum,
|
||||
ftsartist,
|
||||
ftsalbumartist,
|
||||
ftscomposer,
|
||||
ftsperformer,
|
||||
ftsgrouping,
|
||||
ftsgenre,
|
||||
ftscomment,
|
||||
tokenize = "unicode61 remove_diacritics 1"
|
||||
|
||||
);
|
||||
|
||||
UPDATE schema_version SET version=13;
|
||||
@@ -4,7 +4,7 @@ CREATE TABLE IF NOT EXISTS schema_version (
|
||||
|
||||
DELETE FROM schema_version;
|
||||
|
||||
INSERT INTO schema_version (version) VALUES (12);
|
||||
INSERT INTO schema_version (version) VALUES (13);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS directories (
|
||||
path TEXT NOT NULL,
|
||||
@@ -70,178 +70,9 @@ CREATE TABLE IF NOT EXISTS songs (
|
||||
effective_albumartist TEXT,
|
||||
effective_originalyear INTEGER NOT NULL DEFAULT 0,
|
||||
|
||||
cue_path TEXT
|
||||
cue_path TEXT,
|
||||
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS tidal_artists_songs (
|
||||
|
||||
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,
|
||||
compilation INTEGER NOT NULL DEFAULT 0,
|
||||
composer TEXT,
|
||||
performer TEXT,
|
||||
grouping TEXT,
|
||||
comment TEXT,
|
||||
lyrics TEXT,
|
||||
|
||||
artist_id TEXT,
|
||||
album_id TEXT,
|
||||
song_id TEXT,
|
||||
|
||||
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 IF NOT EXISTS tidal_albums_songs (
|
||||
|
||||
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,
|
||||
compilation INTEGER NOT NULL DEFAULT 0,
|
||||
composer TEXT,
|
||||
performer TEXT,
|
||||
grouping TEXT,
|
||||
comment TEXT,
|
||||
lyrics TEXT,
|
||||
|
||||
artist_id TEXT,
|
||||
album_id TEXT,
|
||||
song_id TEXT,
|
||||
|
||||
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 IF NOT EXISTS tidal_songs (
|
||||
|
||||
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,
|
||||
compilation INTEGER NOT NULL DEFAULT 0,
|
||||
composer TEXT,
|
||||
performer TEXT,
|
||||
grouping TEXT,
|
||||
comment TEXT,
|
||||
lyrics TEXT,
|
||||
|
||||
artist_id TEXT,
|
||||
album_id TEXT,
|
||||
song_id TEXT,
|
||||
|
||||
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
|
||||
rating INTEGER DEFAULT -1
|
||||
|
||||
);
|
||||
|
||||
@@ -298,7 +129,363 @@ CREATE TABLE IF NOT EXISTS subsonic_songs (
|
||||
effective_albumartist TEXT,
|
||||
effective_originalyear INTEGER NOT NULL DEFAULT 0,
|
||||
|
||||
cue_path TEXT
|
||||
cue_path TEXT,
|
||||
|
||||
rating INTEGER DEFAULT -1
|
||||
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS tidal_artists_songs (
|
||||
|
||||
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,
|
||||
compilation INTEGER NOT NULL DEFAULT 0,
|
||||
composer TEXT,
|
||||
performer TEXT,
|
||||
grouping TEXT,
|
||||
comment TEXT,
|
||||
lyrics TEXT,
|
||||
|
||||
artist_id TEXT,
|
||||
album_id TEXT,
|
||||
song_id TEXT,
|
||||
|
||||
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,
|
||||
|
||||
rating INTEGER DEFAULT -1
|
||||
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS tidal_albums_songs (
|
||||
|
||||
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,
|
||||
compilation INTEGER NOT NULL DEFAULT 0,
|
||||
composer TEXT,
|
||||
performer TEXT,
|
||||
grouping TEXT,
|
||||
comment TEXT,
|
||||
lyrics TEXT,
|
||||
|
||||
artist_id TEXT,
|
||||
album_id TEXT,
|
||||
song_id TEXT,
|
||||
|
||||
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,
|
||||
|
||||
rating INTEGER DEFAULT -1
|
||||
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS tidal_songs (
|
||||
|
||||
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,
|
||||
compilation INTEGER NOT NULL DEFAULT 0,
|
||||
composer TEXT,
|
||||
performer TEXT,
|
||||
grouping TEXT,
|
||||
comment TEXT,
|
||||
lyrics TEXT,
|
||||
|
||||
artist_id TEXT,
|
||||
album_id TEXT,
|
||||
song_id TEXT,
|
||||
|
||||
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,
|
||||
|
||||
rating INTEGER DEFAULT -1
|
||||
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS qobuz_artists_songs (
|
||||
|
||||
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,
|
||||
compilation INTEGER NOT NULL DEFAULT 0,
|
||||
composer TEXT,
|
||||
performer TEXT,
|
||||
grouping TEXT,
|
||||
comment TEXT,
|
||||
lyrics TEXT,
|
||||
|
||||
artist_id TEXT,
|
||||
album_id TEXT,
|
||||
song_id TEXT,
|
||||
|
||||
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,
|
||||
|
||||
rating INTEGER DEFAULT -1
|
||||
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS qobuz_albums_songs (
|
||||
|
||||
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,
|
||||
compilation INTEGER NOT NULL DEFAULT 0,
|
||||
composer TEXT,
|
||||
performer TEXT,
|
||||
grouping TEXT,
|
||||
comment TEXT,
|
||||
lyrics TEXT,
|
||||
|
||||
artist_id TEXT,
|
||||
album_id TEXT,
|
||||
song_id TEXT,
|
||||
|
||||
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,
|
||||
|
||||
rating INTEGER DEFAULT -1
|
||||
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS qobuz_songs (
|
||||
|
||||
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,
|
||||
compilation INTEGER NOT NULL DEFAULT 0,
|
||||
composer TEXT,
|
||||
performer TEXT,
|
||||
grouping TEXT,
|
||||
comment TEXT,
|
||||
lyrics TEXT,
|
||||
|
||||
artist_id TEXT,
|
||||
album_id TEXT,
|
||||
song_id TEXT,
|
||||
|
||||
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,
|
||||
|
||||
rating INTEGER DEFAULT -1
|
||||
|
||||
);
|
||||
|
||||
@@ -309,7 +496,11 @@ CREATE TABLE IF NOT EXISTS playlists (
|
||||
ui_order INTEGER NOT NULL DEFAULT 0,
|
||||
special_type TEXT,
|
||||
ui_path TEXT,
|
||||
is_favorite INTEGER NOT NULL DEFAULT 0
|
||||
is_favorite INTEGER NOT NULL DEFAULT 0,
|
||||
|
||||
dynamic_playlist_type INTEGER,
|
||||
dynamic_playlist_backend TEXT,
|
||||
dynamic_playlist_data BLOB
|
||||
|
||||
);
|
||||
|
||||
@@ -371,7 +562,9 @@ CREATE TABLE IF NOT EXISTS playlist_items (
|
||||
effective_albumartist TEXT,
|
||||
effective_originalyear INTEGER,
|
||||
|
||||
cue_path TEXT
|
||||
cue_path TEXT,
|
||||
|
||||
rating INTEGER DEFAULT -1
|
||||
|
||||
);
|
||||
|
||||
@@ -414,6 +607,21 @@ CREATE VIRTUAL TABLE IF NOT EXISTS songs_fts USING fts5(
|
||||
|
||||
);
|
||||
|
||||
CREATE VIRTUAL TABLE IF NOT EXISTS subsonic_songs_fts USING fts5(
|
||||
|
||||
ftstitle,
|
||||
ftsalbum,
|
||||
ftsartist,
|
||||
ftsalbumartist,
|
||||
ftscomposer,
|
||||
ftsperformer,
|
||||
ftsgrouping,
|
||||
ftsgenre,
|
||||
ftscomment,
|
||||
tokenize = "unicode61 remove_diacritics 1"
|
||||
|
||||
);
|
||||
|
||||
CREATE VIRTUAL TABLE IF NOT EXISTS tidal_artists_songs_fts USING fts5(
|
||||
|
||||
ftstitle,
|
||||
@@ -459,7 +667,7 @@ CREATE VIRTUAL TABLE IF NOT EXISTS tidal_songs_fts USING fts5(
|
||||
|
||||
);
|
||||
|
||||
CREATE VIRTUAL TABLE IF NOT EXISTS subsonic_songs_fts USING fts5(
|
||||
CREATE VIRTUAL TABLE IF NOT EXISTS qobuz_artists_songs_fts USING fts5(
|
||||
|
||||
ftstitle,
|
||||
ftsalbum,
|
||||
@@ -474,7 +682,22 @@ CREATE VIRTUAL TABLE IF NOT EXISTS subsonic_songs_fts USING fts5(
|
||||
|
||||
);
|
||||
|
||||
CREATE VIRTUAL TABLE IF NOT EXISTS playlist_items_fts_ USING fts5(
|
||||
CREATE VIRTUAL TABLE IF NOT EXISTS qobuz_albums_songs_fts USING fts5(
|
||||
|
||||
ftstitle,
|
||||
ftsalbum,
|
||||
ftsartist,
|
||||
ftsalbumartist,
|
||||
ftscomposer,
|
||||
ftsperformer,
|
||||
ftsgrouping,
|
||||
ftsgenre,
|
||||
ftscomment,
|
||||
tokenize = "unicode61 remove_diacritics 1"
|
||||
|
||||
);
|
||||
|
||||
CREATE VIRTUAL TABLE IF NOT EXISTS qobuz_songs_fts USING fts5(
|
||||
|
||||
ftstitle,
|
||||
ftsalbum,
|
||||
|
||||
42
data/style/smartplaylistsearchterm.css
Normal file
@@ -0,0 +1,42 @@
|
||||
#frame {
|
||||
border: 1px solid palette(mid);
|
||||
border-radius: 5px;
|
||||
}
|
||||
|
||||
#container {
|
||||
margin: 2px;
|
||||
}
|
||||
|
||||
#remove {
|
||||
border-top-left-radius: 0px;
|
||||
border-top-right-radius: 5px;
|
||||
border-bottom-left-radius: 0px;
|
||||
border-bottom-right-radius: 5px;
|
||||
border: 0px solid transparent;
|
||||
background: qlineargradient(x1:0, y1:0, x2:0, y2:1,
|
||||
stop:0 %light,
|
||||
stop:0.4 %light,
|
||||
stop:0.6 %dark,
|
||||
stop:1 %dark);
|
||||
|
||||
margin-left: 5px;
|
||||
padding: 0px 5px;
|
||||
}
|
||||
|
||||
#remove:hover {
|
||||
background: qlineargradient(x1:0, y1:0, x2:0, y2:1,
|
||||
stop:0 %light2,
|
||||
stop:0.4 %light2,
|
||||
stop:0.6 %base,
|
||||
stop:1 %base);
|
||||
border: 0px solid transparent;
|
||||
}
|
||||
|
||||
#remove:pressed {
|
||||
background: qlineargradient(x1:0, y1:0, x2:0, y2:1,
|
||||
stop:0 %base,
|
||||
stop:0.4 %base,
|
||||
stop:0.6 %light2,
|
||||
stop:1 %light2);
|
||||
border: 0px solid transparent;
|
||||
}
|
||||
1
debian/copyright
vendored
@@ -12,7 +12,6 @@ Files: src/core/timeconstants.h
|
||||
ext/libstrawberry-common/core/logging.h
|
||||
ext/libstrawberry-common/core/messagehandler.cpp
|
||||
ext/libstrawberry-common/core/messagehandler.h
|
||||
ext/libstrawberry-common/core/override.h
|
||||
Copyright: 2011, 2012, David Sansome <me@davidsansome.com>
|
||||
License: Apache-2.0
|
||||
|
||||
|
||||
126
dist/macos/macdeploy.py
vendored
@@ -21,7 +21,6 @@ import logging
|
||||
import os
|
||||
import re
|
||||
import subprocess
|
||||
import commands
|
||||
import sys
|
||||
import traceback
|
||||
|
||||
@@ -67,56 +66,57 @@ GSTREAMER_SEARCH_PATH = [
|
||||
|
||||
GSTREAMER_PLUGINS = [
|
||||
|
||||
'libgstapetag.so',
|
||||
'libgstapp.so',
|
||||
'libgstaudioconvert.so',
|
||||
'libgstaudiofx.so',
|
||||
'libgstaudiomixer.so',
|
||||
'libgstaudioparsers.so',
|
||||
'libgstaudiorate.so',
|
||||
'libgstaudioresample.so',
|
||||
'libgstaudiotestsrc.so',
|
||||
'libgstaudiovisualizers.so',
|
||||
'libgstauparse.so',
|
||||
'libgstautoconvert.so',
|
||||
'libgstautodetect.so',
|
||||
'libgstcoreelements.so',
|
||||
'libgstequalizer.so',
|
||||
'libgstgio.so',
|
||||
'libgsticydemux.so',
|
||||
'libgstid3demux.so',
|
||||
'libgstlevel.so',
|
||||
'libgstosxaudio.so',
|
||||
'libgstplayback.so',
|
||||
'libgstrawparse.so',
|
||||
'libgstrealmedia.so',
|
||||
'libgstreplaygain.so',
|
||||
'libgstsoup.so',
|
||||
'libgstspectrum.so',
|
||||
'libgsttypefindfunctions.so',
|
||||
'libgstvolume.so',
|
||||
'libgstxingmux.so',
|
||||
'libgsttcp.so',
|
||||
'libgstudp.so',
|
||||
'libgstpbtypes.so',
|
||||
'libgstrtp.so',
|
||||
'libgstrtsp.so',
|
||||
'libgstapetag.dylib',
|
||||
'libgstapp.dylib',
|
||||
'libgstaudioconvert.dylib',
|
||||
'libgstaudiofx.dylib',
|
||||
'libgstaudiomixer.dylib',
|
||||
'libgstaudioparsers.dylib',
|
||||
'libgstaudiorate.dylib',
|
||||
'libgstaudioresample.dylib',
|
||||
'libgstaudiotestsrc.dylib',
|
||||
'libgstaudiovisualizers.dylib',
|
||||
'libgstauparse.dylib',
|
||||
'libgstautoconvert.dylib',
|
||||
'libgstautodetect.dylib',
|
||||
'libgstcoreelements.dylib',
|
||||
'libgstequalizer.dylib',
|
||||
'libgstgio.dylib',
|
||||
'libgsticydemux.dylib',
|
||||
'libgstid3demux.dylib',
|
||||
'libgstlevel.dylib',
|
||||
'libgstosxaudio.dylib',
|
||||
'libgstplayback.dylib',
|
||||
'libgstrawparse.dylib',
|
||||
'libgstrealmedia.dylib',
|
||||
'libgstreplaygain.dylib',
|
||||
'libgstsoup.dylib',
|
||||
'libgstspectrum.dylib',
|
||||
'libgsttypefindfunctions.dylib',
|
||||
'libgstvolume.dylib',
|
||||
'libgstxingmux.dylib',
|
||||
'libgsttcp.dylib',
|
||||
'libgstudp.dylib',
|
||||
'libgstpbtypes.dylib',
|
||||
'libgstrtp.dylib',
|
||||
'libgstrtsp.dylib',
|
||||
|
||||
'libgstflac.so',
|
||||
'libgstwavparse.so',
|
||||
'libgstfaac.so',
|
||||
'libgstfaad.so',
|
||||
'libgstogg.so',
|
||||
'libgstopus.so',
|
||||
'libgstopusparse.so',
|
||||
'libgstasf.so',
|
||||
'libgstspeex.so',
|
||||
'libgsttaglib.so',
|
||||
'libgstvorbis.so',
|
||||
'libgstisomp4.so',
|
||||
'libgstlibav.so',
|
||||
'libgstaiff.so',
|
||||
'libgstlame.so',
|
||||
'libgstflac.dylib',
|
||||
'libgstwavparse.dylib',
|
||||
'libgstfaac.dylib',
|
||||
'libgstfaad.dylib',
|
||||
'libgstogg.dylib',
|
||||
'libgstopus.dylib',
|
||||
'libgstopusparse.dylib',
|
||||
'libgstasf.dylib',
|
||||
'libgstspeex.dylib',
|
||||
'libgsttaglib.dylib',
|
||||
'libgstvorbis.dylib',
|
||||
'libgstisomp4.dylib',
|
||||
'libgstlibav.dylib',
|
||||
'libgstaiff.dylib',
|
||||
'libgstlame.dylib',
|
||||
'libgstmusepack.dylib',
|
||||
|
||||
]
|
||||
|
||||
@@ -156,7 +156,7 @@ class CouldNotFindGstreamerPluginError(Error):
|
||||
pass
|
||||
|
||||
if len(sys.argv) < 2:
|
||||
print 'Usage: %s <bundle.app>' % sys.argv[0]
|
||||
print('Usage: %s <bundle.app>' % sys.argv[0])
|
||||
|
||||
bundle_dir = sys.argv[1]
|
||||
|
||||
@@ -176,20 +176,21 @@ fixed_frameworks = set()
|
||||
|
||||
|
||||
def GetBrokenLibraries(binary):
|
||||
#print "Checking libs for binary: %s" % binary
|
||||
output = subprocess.Popen([OTOOL, '-L', binary], stdout=subprocess.PIPE).communicate()[0]
|
||||
#print("Checking libs for binary: %s" % binary)
|
||||
output = subprocess.Popen([OTOOL, '-L', binary], stdout=subprocess.PIPE).communicate()[0].decode('utf-8')
|
||||
broken_libs = {'frameworks': [], 'libs': []}
|
||||
for line in [x.split(' ')[0].lstrip() for x in output.split('\n')[1:]]:
|
||||
#print "Checking line: %s" % line
|
||||
#print("Checking line: %s" % line)
|
||||
if not line: # skip empty lines
|
||||
continue
|
||||
if os.path.basename(binary) == os.path.basename(line):
|
||||
#print "mnope %s-%s" % (os.path.basename(binary), os.path.basename(line))
|
||||
#print("mnope %s-%s" % (os.path.basename(binary), os.path.basename(line)))
|
||||
continue
|
||||
if re.match(r'^\s*/System/', line):
|
||||
#print("system framework: %s" % line)
|
||||
continue # System framework
|
||||
elif re.match(r'^\s*/usr/lib/', line):
|
||||
#print "unix style system lib"
|
||||
#print("unix style system lib: %s" % line)
|
||||
continue # unix style system library
|
||||
elif re.match(r'^\s*@executable_path', line) or re.match(r'^\s*@rpath', line) or re.match(r'^\s*@loader_path', line):
|
||||
# Potentially already fixed library
|
||||
@@ -206,8 +207,9 @@ def GetBrokenLibraries(binary):
|
||||
if not os.path.exists(os.path.join(frameworks_dir, relative_path)):
|
||||
broken_libs['frameworks'].append(relative_path)
|
||||
else:
|
||||
print "GetBrokenLibraries Error: %s" % line
|
||||
print("GetBrokenLibraries Error: %s" % line)
|
||||
elif re.search(r'\w+\.framework', line):
|
||||
#print("framework: %s" % line)
|
||||
broken_libs['frameworks'].append(line)
|
||||
else:
|
||||
broken_libs['libs'].append(line)
|
||||
@@ -279,7 +281,7 @@ def FixLibrary(path):
|
||||
|
||||
abs_path = FindLibrary(path)
|
||||
if abs_path == "":
|
||||
print "Could not resolve %s, not fixing!" % path
|
||||
print("Could not resolve %s, not fixing!" % path)
|
||||
return
|
||||
|
||||
broken_libs = GetBrokenLibraries(abs_path)
|
||||
@@ -482,7 +484,7 @@ def main():
|
||||
try:
|
||||
FixPlugin('strawberry-tagreader', '.')
|
||||
except:
|
||||
print 'Failed to find blob: %s' % traceback.format_exc()
|
||||
print('Failed to find blob: %s' % traceback.format_exc())
|
||||
|
||||
for plugin in GSTREAMER_PLUGINS:
|
||||
FixPlugin(FindGstreamerPlugin(plugin), 'gstreamer')
|
||||
@@ -495,11 +497,11 @@ def main():
|
||||
#FixPlugin(FindQtPlugin(plugin), os.path.dirname(plugin))
|
||||
|
||||
if len(sys.argv) <= 2:
|
||||
print 'Would run %d commands:' % len(commands)
|
||||
print('Would run %d commands:' % len(commands))
|
||||
for command in commands:
|
||||
print ' '.join(command)
|
||||
print(' '.join(command))
|
||||
|
||||
#print 'OK?'
|
||||
#print('OK?')
|
||||
#raw_input()
|
||||
|
||||
for command in commands:
|
||||
|
||||
14
dist/macos/macversion.sh
vendored
Executable file
@@ -0,0 +1,14 @@
|
||||
#!/bin/sh
|
||||
|
||||
macos_version=$(sw_vers -productVersion| awk -F '[.]' '{print $2}')
|
||||
macos_codenames=(
|
||||
["13"]="highsierra"
|
||||
["14"]="mojave"
|
||||
["15"]="catalina"
|
||||
)
|
||||
|
||||
if [[ -n "${macos_codenames[$macos_version]}" ]]; then
|
||||
echo "${macos_codenames[$macos_version]}"
|
||||
else
|
||||
echo "unknown"
|
||||
fi
|
||||
112
dist/scripts/verify-icons.sh
vendored
@@ -16,85 +16,61 @@
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with Strawberry. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
sizes="128x128 64x64 48x48 32x32 22x22"
|
||||
sizes="full 128 64 48 32 22"
|
||||
|
||||
#
|
||||
|
||||
#for i in full/*
|
||||
#do
|
||||
# source=$i
|
||||
# file=`basename $i`
|
||||
|
||||
# id=`identify "$i"` || exit 1
|
||||
# if [ "$id" = "" ] ; then
|
||||
# echo "ERROR: Cannot determine format and geometry for image: \"$i\"."
|
||||
# continue
|
||||
# fi
|
||||
# g=`echo $id | awk '{print $3}'` || exit 1
|
||||
# if [ "$g" = "" ] ; then
|
||||
# echo "ERROR: Cannot determine geometry for image: \"$i\"."
|
||||
# continue
|
||||
# fi
|
||||
|
||||
# Geometry can be 563x144+0+0 or 75x98
|
||||
# we need to get rid of the plus (+) and the x characters:
|
||||
# w=`echo $g | sed 's/[^0-9]/ /g' | awk '{print $1}'` || exit 1
|
||||
# if [ "$w" = "" ] ; then
|
||||
# echo "ERROR: Cannot determine width for image: \"$x\"."
|
||||
# continue
|
||||
# fi
|
||||
# h=`echo $g | sed 's/[^0-9]/ /g' | awk '{print $2}'` || exit 1
|
||||
# if [ "$h" = "" ] ; then
|
||||
# echo "ERROR: Cannot determine height for image: \"$x\"."
|
||||
# continue
|
||||
# fi
|
||||
|
||||
# for x in $sizes
|
||||
# do
|
||||
|
||||
# dest="$x/$file"
|
||||
# if [ -f $dest ]; then
|
||||
# continue
|
||||
# fi
|
||||
|
||||
# x_w=$(echo $x | cut -d 'x' -f1)
|
||||
# x_h=$(echo $x | cut -d 'x' -f2)
|
||||
|
||||
# if [ "$w" -lt "$x_w" ] || [ "$h" -lt "$x_h" ]; then
|
||||
# continue
|
||||
# fi
|
||||
|
||||
#echo "convert -verbose -resize $x $source $dest"
|
||||
#convert -verbose -resize $x $source $dest
|
||||
|
||||
# done
|
||||
#done
|
||||
|
||||
|
||||
for i in $sizes
|
||||
for s in $sizes
|
||||
do
|
||||
for x in $i/*
|
||||
if [ "$s" = "full" ]; then
|
||||
dir=$s
|
||||
else
|
||||
dir=${s}x${s}
|
||||
fi
|
||||
if ! [ -d "$dir" ]; then
|
||||
echo "Missing $dir directory."
|
||||
continue
|
||||
fi
|
||||
for f in ${dir}/*
|
||||
do
|
||||
file=`basename $x`
|
||||
|
||||
file=`basename $f`
|
||||
|
||||
for y in $sizes
|
||||
do
|
||||
if [ "$y" = "$i" ]; then
|
||||
|
||||
if [ "$s" = "$y" ]; then
|
||||
continue
|
||||
fi
|
||||
if ! [ -f "$y/$file" ]; then
|
||||
echo "Warning: $y/$file does not exist, but $x exists."
|
||||
|
||||
if [ "$y" = "full" ]; then
|
||||
dir2=$y
|
||||
else
|
||||
dir2=${y}x${y}
|
||||
fi
|
||||
|
||||
if [ "$dir2" = "full" ]; then
|
||||
continue
|
||||
fi
|
||||
|
||||
if ! [ "$s" = "full" ] && [ $y -gt $s ]; then
|
||||
continue
|
||||
fi
|
||||
|
||||
if ! [ -f "${dir2}/$file" ]; then
|
||||
echo "Warning: ${dir2}/$file does not exist, but ${dir}/${file} exists."
|
||||
fi
|
||||
done
|
||||
|
||||
id=`identify "$x"` || exit 1
|
||||
if [ "$dir" = "full" ]; then
|
||||
continue
|
||||
fi
|
||||
|
||||
id=`identify "$f"` || exit 1
|
||||
if [ "$id" = "" ] ; then
|
||||
echo "ERROR: Cannot determine format and geometry for image: \"$x\"."
|
||||
echo "ERROR: Cannot determine format and geometry for image: \"$f\"."
|
||||
continue
|
||||
fi
|
||||
g=`echo $id | awk '{print $3}'` || exit 1
|
||||
if [ "$g" = "" ] ; then
|
||||
echo "ERROR: Cannot determine geometry for image: \"$x\"."
|
||||
echo "ERROR: Cannot determine geometry for image: \"$f\"."
|
||||
continue
|
||||
fi
|
||||
|
||||
@@ -102,17 +78,17 @@ do
|
||||
# we need to get rid of the plus (+) and the x characters:
|
||||
w=`echo $g | sed 's/[^0-9]/ /g' | awk '{print $1}'` || exit 1
|
||||
if [ "$w" = "" ] ; then
|
||||
echo "ERROR: Cannot determine width for image: \"$x\"."
|
||||
echo "ERROR: Cannot determine width for image: \"$f\"."
|
||||
continue
|
||||
fi
|
||||
h=`echo $g | sed 's/[^0-9]/ /g' | awk '{print $2}'` || exit 1
|
||||
if [ "$h" = "" ] ; then
|
||||
echo "ERROR: Cannot determine height for image: \"$x\"."
|
||||
echo "ERROR: Cannot determine height for image: \"$f\"."
|
||||
continue
|
||||
fi
|
||||
|
||||
if ! [ "${h}x${w}" = "$i" ]; then
|
||||
echo "Warning: $x is not $i, but ${h}x${w}!"
|
||||
if ! [ "${h}x${w}" = "$dir" ]; then
|
||||
echo "Warning: $f is not $dir, but ${h}x${w}!"
|
||||
fi
|
||||
|
||||
done
|
||||
|
||||
62
dist/unix/strawberry.spec.in
vendored
@@ -80,13 +80,6 @@ BuildRequires: pkgconfig(libvlc)
|
||||
Requires: libQt5Sql5-sqlite
|
||||
%endif
|
||||
|
||||
%if 0%{?suse_version} && 0%{?suse_version} < 1500
|
||||
Requires(post): update-desktop-files
|
||||
Requires(post): gtk3-tools
|
||||
Requires(postun): update-desktop-files
|
||||
Requires(postun): gtk3-tools
|
||||
%endif
|
||||
|
||||
%description
|
||||
Strawberry is a music player and music collection organizer.
|
||||
It is a fork of Clementine. The name is inspired by the band Strawbs.
|
||||
@@ -114,62 +107,37 @@ Features:
|
||||
%setup -qn %{name}-@STRAWBERRY_VERSION_PACKAGE@
|
||||
|
||||
%build
|
||||
%if 0%{?suse_version} || 0%{?mageia}
|
||||
%{cmake} ..
|
||||
%else
|
||||
mkdir -p %{_target_platform}
|
||||
pushd %{_target_platform}
|
||||
%{cmake} ..
|
||||
popd
|
||||
%if 0%{?fedora} || 0%{?rhel_version} || 0%{?centos}
|
||||
export CXXFLAGS="-fPIC $RPM_OPT_FLAGS"
|
||||
%endif
|
||||
|
||||
%if 0%{?suse_version} || 0%{?mageia}
|
||||
%if 0%{?suse_version} && 0%{?suse_version} < 1500
|
||||
make %{?_smp_mflags}
|
||||
%else
|
||||
%make_build
|
||||
%endif
|
||||
%{cmake} -DCMAKE_BUILD_TYPE:STRING=Release
|
||||
%if 0%{?centos} || 0%{?mageia}
|
||||
%make_build
|
||||
%else
|
||||
%make_build -C %{_target_platform}
|
||||
%cmake_build
|
||||
%endif
|
||||
|
||||
%install
|
||||
%if 0%{?suse_version}
|
||||
%cmake_install
|
||||
%if 0%{?centos}
|
||||
%make_install
|
||||
%else
|
||||
%if 0%{?mageia}
|
||||
%make_install -C build
|
||||
%else
|
||||
%make_install -C %{_target_platform}
|
||||
%endif
|
||||
%if 0%{?mageia}
|
||||
%make_install -C build
|
||||
%else
|
||||
%cmake_install
|
||||
%endif
|
||||
|
||||
%if 0%{?suse_version} && 0%{?suse_version} < 1500
|
||||
rm -f %{buildroot}%{_datadir}/metainfo/org.strawberrymusicplayer.strawberry.appdata.xml
|
||||
%endif
|
||||
|
||||
%if 0%{?suse_version}
|
||||
%suse_update_desktop_file org.strawberrymusicplayer.strawberry Qt AudioVideo Audio Player
|
||||
%endif
|
||||
|
||||
%if 0%{?suse_version} && 0%{?suse_version} < 1500
|
||||
%post
|
||||
%desktop_database_post
|
||||
%icon_theme_cache_post
|
||||
|
||||
%postun
|
||||
%desktop_database_postun
|
||||
%icon_theme_cache_postun
|
||||
%endif
|
||||
|
||||
%check
|
||||
desktop-file-validate %{buildroot}%{_datadir}/applications/org.strawberrymusicplayer.strawberry.desktop
|
||||
%if 0%{?fedora_version}
|
||||
appstream-util validate-relax --nonet %{buildroot}%{_metainfodir}/org.strawberrymusicplayer.strawberry.appdata.xml
|
||||
%else
|
||||
%if ! 0%{?suse_version} || ( 0%{?suse_version} && 0%{?suse_version} >= 1500 )
|
||||
appstream-util validate-relax --nonet %{buildroot}%{_datadir}/metainfo/org.strawberrymusicplayer.strawberry.appdata.xml
|
||||
%endif
|
||||
appstream-util validate-relax --nonet %{buildroot}%{_datadir}/metainfo/org.strawberrymusicplayer.strawberry.appdata.xml
|
||||
%endif
|
||||
|
||||
%files
|
||||
@@ -183,9 +151,7 @@ desktop-file-validate %{buildroot}%{_datadir}/applications/org.strawberrymusicpl
|
||||
%if 0%{?fedora_version}
|
||||
%{_metainfodir}/*.appdata.xml
|
||||
%else
|
||||
%if ! 0%{?suse_version} || ( 0%{?suse_version} && 0%{?suse_version} >= 1500 )
|
||||
%{_datadir}/metainfo/*.appdata.xml
|
||||
%endif
|
||||
%{_datadir}/metainfo/*.appdata.xml
|
||||
%endif
|
||||
%{_mandir}/man1/%{name}.1.*
|
||||
%{_mandir}/man1/%{name}-tagreader.1.*
|
||||
|
||||
125
dist/windows/strawberry.nsi.in
vendored
@@ -18,7 +18,7 @@
|
||||
!define debug
|
||||
!endif
|
||||
|
||||
!if "@WITH_QT6@" == "ON"
|
||||
!if "@BUILD_WITH_QT6@" == "ON"
|
||||
!define with_qt6
|
||||
!endif
|
||||
|
||||
@@ -174,7 +174,27 @@ Section "Strawberry" Strawberry
|
||||
File "strawberry.exe"
|
||||
File "strawberry-tagreader.exe"
|
||||
File "strawberry.ico"
|
||||
File "sqlite3.exe"
|
||||
|
||||
!ifdef arch_x86
|
||||
File "libgcc_s_sjlj-1.dll"
|
||||
File "libcrypto-1_1.dll"
|
||||
File "libssl-1_1.dll"
|
||||
!endif
|
||||
|
||||
!ifdef arch_x64
|
||||
File "libgcc_s_seh-1.dll"
|
||||
File "libcrypto-1_1-x64.dll"
|
||||
File "libssl-1_1-x64.dll"
|
||||
!endif
|
||||
|
||||
File "avcodec-58.dll"
|
||||
File "avfilter-7.dll"
|
||||
File "avformat-58.dll"
|
||||
File "avresample-4.dll"
|
||||
File "avutil-56.dll"
|
||||
File "libbrotlicommon.dll"
|
||||
File "libbrotlidec.dll"
|
||||
File "libbz2.dll"
|
||||
File "libcdio-19.dll"
|
||||
File "libchromaprint.dll"
|
||||
@@ -214,21 +234,31 @@ Section "Strawberry" Strawberry
|
||||
File "libnettle-8.dll"
|
||||
File "libogg-0.dll"
|
||||
File "libopus-0.dll"
|
||||
File "liborc-0.4-0.dll"
|
||||
File "libpcre-1.dll"
|
||||
File "libpcre2-16-0.dll"
|
||||
File "libpng16-16.dll"
|
||||
File "libprotobuf-23.dll"
|
||||
File "libprotobuf-25.dll"
|
||||
File "libpsl-5.dll"
|
||||
File "libsoup-2.4-1.dll"
|
||||
File "libspeex-1.dll"
|
||||
File "libsqlite3-0.dll"
|
||||
File "libssp-0.dll"
|
||||
File "libstdc++-6.dll"
|
||||
File "libtag.dll"
|
||||
File "libtasn1-6.dll"
|
||||
File "libunistring-2.dll"
|
||||
File "libvorbis-0.dll"
|
||||
File "libvorbisenc-2.dll"
|
||||
File "libwavpack-1.dll"
|
||||
File "libwinpthread-1.dll"
|
||||
File "libxml2-2.dll"
|
||||
File "libzstd.dll"
|
||||
File "postproc-55.dll"
|
||||
File "swresample-3.dll"
|
||||
File "swscale-5.dll"
|
||||
File "zlib1.dll"
|
||||
|
||||
!ifdef with_qt6
|
||||
File "Qt6Concurrent.dll"
|
||||
File "Qt6Core.dll"
|
||||
@@ -248,24 +278,15 @@ Section "Strawberry" Strawberry
|
||||
File "Qt5WinExtras.dll"
|
||||
File "libqtsparkle-qt5.dll"
|
||||
!endif
|
||||
File "zlib1.dll"
|
||||
File "libzstd.dll"
|
||||
File "libtasn1-6.dll"
|
||||
File "libbrotlicommon.dll"
|
||||
File "libbrotlidec.dll"
|
||||
File "libpsl-5.dll"
|
||||
File "liborc-0.4-0.dll"
|
||||
|
||||
!ifdef arch_x86
|
||||
File "libgcc_s_sjlj-1.dll"
|
||||
File "libcrypto-1_1.dll"
|
||||
File "libssl-1_1.dll"
|
||||
!endif
|
||||
|
||||
!ifdef arch_x64
|
||||
File "libgcc_s_seh-1.dll"
|
||||
File "libcrypto-1_1-x64.dll"
|
||||
File "libssl-1_1-x64.dll"
|
||||
!ifdef debug
|
||||
File "gdb.exe"
|
||||
File "libdl.dll"
|
||||
File "libexpat-1.dll"
|
||||
File "libmman.dll"
|
||||
File "libmpfr-6.dll"
|
||||
File "libreadline8.dll"
|
||||
File "libtermcap.dll"
|
||||
!endif
|
||||
|
||||
File "killproc.exe"
|
||||
@@ -350,7 +371,7 @@ Section "Gstreamer plugins" gstreamer-plugins
|
||||
File "/oname=libgsttypefindfunctions.dll" "gstreamer-plugins\libgsttypefindfunctions.dll"
|
||||
File "/oname=libgstgio.dll" "gstreamer-plugins\libgstgio.dll"
|
||||
File "/oname=libgstdirectsound.dll" "gstreamer-plugins\libgstdirectsound.dll"
|
||||
;File "/oname=libgstwasapi.dll" "gstreamer-plugins\libgstwasapi.dll"
|
||||
File "/oname=libgstwasapi.dll" "gstreamer-plugins\libgstwasapi.dll"
|
||||
File "/oname=libgstapetag.dll" "gstreamer-plugins\libgstapetag.dll"
|
||||
File "/oname=libgsticydemux.dll" "gstreamer-plugins\libgsticydemux.dll"
|
||||
File "/oname=libgstid3demux.dll" "gstreamer-plugins\libgstid3demux.dll"
|
||||
@@ -391,6 +412,12 @@ Section "Start menu items" startmenu
|
||||
|
||||
SectionEnd
|
||||
|
||||
Section "Tidal URL Scheme"
|
||||
WriteRegStr HKCR "tidal" "URL Protocol" ""
|
||||
WriteRegStr HKCR "tidal" "" "URL:tidal"
|
||||
WriteRegStr HKCR 'tidal\shell\open\command' '' '"${PRODUCT_INSTALL_DIR}\strawberry.exe" "%1"'
|
||||
SectionEnd
|
||||
|
||||
Section "Uninstaller"
|
||||
; Create uninstaller
|
||||
WriteUninstaller "$INSTDIR\Uninstall.exe"
|
||||
@@ -415,7 +442,27 @@ Section "Uninstall"
|
||||
Delete "$INSTDIR\strawberry.ico"
|
||||
Delete "$INSTDIR\strawberry.exe"
|
||||
Delete "$INSTDIR\strawberry-tagreader.exe"
|
||||
Delete "$INSTDIR\sqlite3.exe"
|
||||
|
||||
!ifdef arch_x86
|
||||
Delete "$INSTDIR\libgcc_s_sjlj-1.dll"
|
||||
Delete "$INSTDIR\libcrypto-1_1.dll"
|
||||
Delete "$INSTDIR\libssl-1_1.dll"
|
||||
!endif
|
||||
|
||||
!ifdef arch_x64
|
||||
Delete "$INSTDIR\libgcc_s_seh-1.dll"
|
||||
Delete "$INSTDIR\libcrypto-1_1-x64.dll"
|
||||
Delete "$INSTDIR\libssl-1_1-x64.dll"
|
||||
!endif
|
||||
|
||||
Delete "$INSTDIR\avcodec-58.dll"
|
||||
Delete "$INSTDIR\avfilter-7.dll"
|
||||
Delete "$INSTDIR\avformat-58.dll"
|
||||
Delete "$INSTDIR\avresample-4.dll"
|
||||
Delete "$INSTDIR\avutil-56.dll"
|
||||
Delete "$INSTDIR\libbrotlicommon.dll"
|
||||
Delete "$INSTDIR\libbrotlidec.dll"
|
||||
Delete "$INSTDIR\libbz2.dll"
|
||||
Delete "$INSTDIR\libcdio-19.dll"
|
||||
Delete "$INSTDIR\libchromaprint.dll"
|
||||
@@ -455,21 +502,29 @@ Section "Uninstall"
|
||||
Delete "$INSTDIR\libnettle-8.dll"
|
||||
Delete "$INSTDIR\libogg-0.dll"
|
||||
Delete "$INSTDIR\libopus-0.dll"
|
||||
Delete "$INSTDIR\liborc-0.4-0.dll"
|
||||
Delete "$INSTDIR\libpcre-1.dll"
|
||||
Delete "$INSTDIR\libpcre2-16-0.dll"
|
||||
Delete "$INSTDIR\libpng16-16.dll"
|
||||
Delete "$INSTDIR\libprotobuf-23.dll"
|
||||
Delete "$INSTDIR\libprotobuf-25.dll"
|
||||
Delete "$INSTDIR\libpsl-5.dll"
|
||||
Delete "$INSTDIR\libqtsparkle-qt5.dll"
|
||||
Delete "$INSTDIR\libqtsparkle-qt6.dll"
|
||||
Delete "$INSTDIR\libsoup-2.4-1.dll"
|
||||
Delete "$INSTDIR\libspeex-1.dll"
|
||||
Delete "$INSTDIR\libsqlite3-0.dll"
|
||||
Delete "$INSTDIR\libssp-0.dll"
|
||||
Delete "$INSTDIR\libstdc++-6.dll"
|
||||
Delete "$INSTDIR\libtag.dll"
|
||||
Delete "$INSTDIR\libtasn1-6.dll"
|
||||
Delete "$INSTDIR\libunistring-2.dll"
|
||||
Delete "$INSTDIR\libvorbis-0.dll"
|
||||
Delete "$INSTDIR\libvorbisenc-2.dll"
|
||||
Delete "$INSTDIR\libwavpack-1.dll"
|
||||
Delete "$INSTDIR\libwinpthread-1.dll"
|
||||
Delete "$INSTDIR\libxml2-2.dll"
|
||||
Delete "$INSTDIR\libzstd.dll"
|
||||
Delete "$INSTDIR\postproc-55.dll"
|
||||
Delete "$INSTDIR\Qt5Concurrent.dll"
|
||||
Delete "$INSTDIR\Qt5Core.dll"
|
||||
Delete "$INSTDIR\Qt5Gui.dll"
|
||||
@@ -477,7 +532,6 @@ Section "Uninstall"
|
||||
Delete "$INSTDIR\Qt5Sql.dll"
|
||||
Delete "$INSTDIR\Qt5Widgets.dll"
|
||||
Delete "$INSTDIR\Qt5WinExtras.dll"
|
||||
Delete "$INSTDIR\libqtsparkle-qt5.dll"
|
||||
Delete "$INSTDIR\Qt6Concurrent.dll"
|
||||
Delete "$INSTDIR\Qt6Core.dll"
|
||||
Delete "$INSTDIR\Qt6Gui.dll"
|
||||
@@ -485,25 +539,18 @@ Section "Uninstall"
|
||||
Delete "$INSTDIR\Qt6Sql.dll"
|
||||
Delete "$INSTDIR\Qt6Widgets.dll"
|
||||
Delete "$INSTDIR\Qt6WinExtras.dll"
|
||||
Delete "$INSTDIR\libqtsparkle-qt6.dll"
|
||||
Delete "$INSTDIR\swresample-3.dll"
|
||||
Delete "$INSTDIR\swscale-5.dll"
|
||||
Delete "$INSTDIR\zlib1.dll"
|
||||
Delete "$INSTDIR\libzstd.dll"
|
||||
Delete "$INSTDIR\libtasn1-6.dll"
|
||||
Delete "$INSTDIR\libbrotlicommon.dll"
|
||||
Delete "$INSTDIR\libbrotlidec.dll"
|
||||
Delete "$INSTDIR\libpsl-5.dll"
|
||||
Delete "$INSTDIR\liborc-0.4-0.dll"
|
||||
|
||||
!ifdef arch_x86
|
||||
Delete "$INSTDIR\libgcc_s_sjlj-1.dll"
|
||||
Delete "$INSTDIR\libcrypto-1_1.dll"
|
||||
Delete "$INSTDIR\libssl-1_1.dll"
|
||||
!endif
|
||||
|
||||
!ifdef arch_x64
|
||||
Delete "$INSTDIR\libgcc_s_seh-1.dll"
|
||||
Delete "$INSTDIR\libcrypto-1_1-x64.dll"
|
||||
Delete "$INSTDIR\libssl-1_1-x64.dll"
|
||||
!ifdef debug
|
||||
Delete "$INSTDIR\gdb.exe"
|
||||
Delete "$INSTDIR\libdl.dll"
|
||||
Delete "$INSTDIR\libexpat-1.dll"
|
||||
Delete "$INSTDIR\libmman.dll"
|
||||
Delete "$INSTDIR\libmpfr-6.dll"
|
||||
Delete "$INSTDIR\libreadline8.dll"
|
||||
Delete "$INSTDIR\libtermcap.dll"
|
||||
!endif
|
||||
|
||||
Delete "$INSTDIR\gio-modules\libgiognutls.dll"
|
||||
|
||||
@@ -9,7 +9,6 @@ link_directories(
|
||||
${GSTREAMER_BASE_LIBRARY_DIRS}
|
||||
${GSTREAMER_AUDIO_LIBRARY_DIRS}
|
||||
${FFTW3_LIBRARY_DIRS}
|
||||
${QtCore_LIBRARY_DIRS}
|
||||
)
|
||||
|
||||
add_library(gstmoodbar STATIC ${SOURCES})
|
||||
@@ -21,7 +20,6 @@ target_include_directories(gstmoodbar SYSTEM PRIVATE
|
||||
${GSTREAMER_BASE_INCLUDE_DIRS}
|
||||
${GSTREAMER_AUDIO_INCLUDE_DIRS}
|
||||
${FFTW3_INCLUDE_DIR}
|
||||
${QtCore_INCLUDE_DIRS}
|
||||
)
|
||||
|
||||
target_include_directories(gstmoodbar PRIVATE ${CMAKE_CURRENT_SOURCE_DIR})
|
||||
|
||||
@@ -115,7 +115,7 @@ static void gst_fastspectrum_init (GstFastSpectrum * spectrum) {
|
||||
spectrum->interval = DEFAULT_INTERVAL;
|
||||
spectrum->bands = DEFAULT_BANDS;
|
||||
|
||||
spectrum->channel_data_initialised = false;
|
||||
spectrum->channel_data_initialized = false;
|
||||
|
||||
g_mutex_init (&spectrum->lock);
|
||||
|
||||
@@ -137,14 +137,14 @@ static void gst_fastspectrum_alloc_channel_data (GstFastSpectrum * spectrum) {
|
||||
QMutexLocker l(klass->fftw_lock);
|
||||
spectrum->plan = fftw_plan_dft_r2c_1d(nfft, spectrum->fft_input, spectrum->fft_output, FFTW_ESTIMATE);
|
||||
}
|
||||
spectrum->channel_data_initialised = true;
|
||||
spectrum->channel_data_initialized = true;
|
||||
|
||||
}
|
||||
|
||||
static void gst_fastspectrum_free_channel_data (GstFastSpectrum * spectrum) {
|
||||
|
||||
GstFastSpectrumClass* klass = reinterpret_cast<GstFastSpectrumClass*>(G_OBJECT_GET_CLASS(spectrum));
|
||||
if (spectrum->channel_data_initialised) {
|
||||
if (spectrum->channel_data_initialized) {
|
||||
{
|
||||
QMutexLocker l(klass->fftw_lock);
|
||||
fftw_destroy_plan(spectrum->plan);
|
||||
@@ -154,7 +154,7 @@ static void gst_fastspectrum_free_channel_data (GstFastSpectrum * spectrum) {
|
||||
delete[] spectrum->input_ring_buffer;
|
||||
delete[] spectrum->spect_magnitude;
|
||||
|
||||
spectrum->channel_data_initialised = false;
|
||||
spectrum->channel_data_initialized = false;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -422,7 +422,7 @@ static GstFlowReturn gst_fastspectrum_transform_ip (GstBaseTransform *trans, Gst
|
||||
/* 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_initialised) {
|
||||
if (!spectrum->channel_data_initialized) {
|
||||
GST_DEBUG_OBJECT (spectrum, "allocating for bands %u", bands);
|
||||
|
||||
gst_fastspectrum_alloc_channel_data (spectrum);
|
||||
|
||||
@@ -65,7 +65,7 @@ struct GstFastSpectrum {
|
||||
GstClockTime message_ts; /* starttime for next message */
|
||||
|
||||
/* <private> */
|
||||
bool channel_data_initialised;
|
||||
bool channel_data_initialized;
|
||||
double* input_ring_buffer;
|
||||
double* fft_input;
|
||||
fftw_complex* fft_output;
|
||||
|
||||
@@ -20,7 +20,7 @@ if(APPLE)
|
||||
list(APPEND SOURCES core/scoped_nsautorelease_pool.mm)
|
||||
endif(APPLE)
|
||||
|
||||
if(WITH_QT6)
|
||||
if(BUILD_WITH_QT6)
|
||||
qt6_wrap_cpp(MOC ${HEADERS})
|
||||
else()
|
||||
qt5_wrap_cpp(MOC ${HEADERS})
|
||||
@@ -28,16 +28,12 @@ endif()
|
||||
|
||||
link_directories(
|
||||
${GLIB_LIBRARY_DIRS}
|
||||
${QtCore_LIBRARY_DIRS}
|
||||
${QtNetwork_LIBRARY_DIRS}
|
||||
)
|
||||
|
||||
add_library(libstrawberry-common STATIC ${SOURCES} ${MOC})
|
||||
|
||||
target_include_directories(libstrawberry-common SYSTEM PRIVATE
|
||||
${GLIB_INCLUDE_DIRS}
|
||||
${QtCore_INCLUDE_DIRS}
|
||||
${QtNetwork_INCLUDE_DIRS}
|
||||
)
|
||||
|
||||
target_include_directories(libstrawberry-common PRIVATE
|
||||
|
||||
@@ -1,123 +0,0 @@
|
||||
/* This file is part of Strawberry.
|
||||
Copyright 2012, David Sansome <me@davidsansome.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/>.
|
||||
*/
|
||||
|
||||
#ifndef CONCURRENTRUN_H
|
||||
#define CONCURRENTRUN_H
|
||||
|
||||
#include <functional>
|
||||
|
||||
#include <QFuture>
|
||||
#include <QRunnable>
|
||||
#include <QThreadPool>
|
||||
|
||||
/*
|
||||
The aim of ThreadFunctor classes and ConcurrentRun::Run() functions is to
|
||||
complete QtConcurrentRun, which lack support for using a particular
|
||||
QThreadPool, as it always uses QThreadPool::globalInstance().
|
||||
|
||||
This is problematic when we do not want to share the same thread pool over
|
||||
all the application, but want to keep the convenient QtConcurrent::run()
|
||||
functor syntax.
|
||||
With ConcurrentRun::Run(), time critical changes can be performed in their
|
||||
own pool, which is not empty by other actions (as it happens when using
|
||||
QtConcurrentRun::run()).
|
||||
|
||||
ThreadFunctor classes are used to store a functor and its arguments, and
|
||||
Run() functions are used for convenience: to directly create a new
|
||||
ThreadFunctor object and start it.
|
||||
*/
|
||||
|
||||
/*
|
||||
Base abstract classes ThreadFunctorBase and ThreadFunctor (for void and
|
||||
non-void result):
|
||||
*/
|
||||
template<typename ReturnType>
|
||||
class ThreadFunctorBase : public QFutureInterface<ReturnType>, public QRunnable {
|
||||
public:
|
||||
ThreadFunctorBase() {}
|
||||
|
||||
QFuture<ReturnType> Start(QThreadPool* thread_pool) {
|
||||
this->setRunnable(this);
|
||||
this->reportStarted();
|
||||
Q_ASSERT(thread_pool);
|
||||
QFuture<ReturnType> future = this->future();
|
||||
thread_pool->start(this, 0 /* priority: currently we do not support changing the priority. Might be added later if needed */);
|
||||
return future;
|
||||
}
|
||||
|
||||
void run() override = 0;
|
||||
};
|
||||
|
||||
template <typename ReturnType, typename... Args>
|
||||
class ThreadFunctor : public ThreadFunctorBase<ReturnType> {
|
||||
public:
|
||||
explicit ThreadFunctor(std::function<ReturnType (Args...)> function, Args... args)
|
||||
: function_(std::bind(function, args...)) {
|
||||
}
|
||||
|
||||
void run() override {
|
||||
this->reportResult(function_());
|
||||
this->reportFinished();
|
||||
}
|
||||
|
||||
private:
|
||||
std::function<ReturnType()> function_;
|
||||
};
|
||||
|
||||
// Partial specialisation for void return type.
|
||||
template <typename... Args>
|
||||
class ThreadFunctor <void, Args...> : public ThreadFunctorBase<void> {
|
||||
public:
|
||||
explicit ThreadFunctor(std::function<void (Args...)> function, Args... args)
|
||||
: function_(std::bind(function, args...)) {
|
||||
}
|
||||
|
||||
void run() override {
|
||||
function_();
|
||||
this->reportFinished();
|
||||
}
|
||||
|
||||
private:
|
||||
std::function<void()> function_;
|
||||
};
|
||||
|
||||
|
||||
/*
|
||||
Run functions
|
||||
*/
|
||||
namespace ConcurrentRun {
|
||||
|
||||
// Empty argument form.
|
||||
template <typename ReturnType>
|
||||
QFuture<ReturnType> Run(QThreadPool* threadpool, std::function<ReturnType ()> function) {
|
||||
return (new ThreadFunctor<ReturnType>(function))->Start(threadpool);
|
||||
}
|
||||
|
||||
// Function object with arguments form.
|
||||
template <typename ReturnType, typename... Args>
|
||||
QFuture<ReturnType> Run(QThreadPool* threadpool, std::function<ReturnType (Args...)> function, const Args&... args) {
|
||||
return (new ThreadFunctor<ReturnType, Args...>(function, args...))->Start(threadpool);
|
||||
}
|
||||
|
||||
// Support passing C function pointers instead of function objects.
|
||||
template <typename ReturnType, typename... Args>
|
||||
QFuture<ReturnType> Run(QThreadPool* threadpool, ReturnType (*function) (Args...), const Args&... args) {
|
||||
return Run(threadpool, std::function<ReturnType (Args...)>(function), args...);
|
||||
}
|
||||
}
|
||||
|
||||
#endif // CONCURRENTRUN_H
|
||||
@@ -21,7 +21,7 @@
|
||||
#include <functional>
|
||||
#include <memory>
|
||||
|
||||
// Helper for lazy initialisation of objects.
|
||||
// Helper for lazy initialization of objects.
|
||||
// Usage:
|
||||
// Lazy<Foo> my_lazy_object([]() { return new Foo; });
|
||||
|
||||
@@ -34,33 +34,32 @@ class Lazy {
|
||||
Lazy() : init_([]() { return new T; }) {}
|
||||
|
||||
T* get() const {
|
||||
CheckInitialised();
|
||||
CheckInitialized();
|
||||
return ptr_.get();
|
||||
}
|
||||
|
||||
typename std::add_lvalue_reference<T>::type operator*() const {
|
||||
CheckInitialised();
|
||||
CheckInitialized();
|
||||
return *ptr_;
|
||||
}
|
||||
|
||||
T* operator->() const { return get(); }
|
||||
|
||||
// Returns true if the object is not yet initialised.
|
||||
// Returns true if the object is not yet initialized.
|
||||
explicit operator bool() const { return ptr_; }
|
||||
|
||||
// Deletes the underlying object and will re-run the initialisation function
|
||||
// if the object is requested again.
|
||||
void reset() { ptr_.reset(nullptr); }
|
||||
// Deletes the underlying object and will re-run the initialization function if the object is requested again.
|
||||
void reset() { ptr_.reset(); }
|
||||
|
||||
private:
|
||||
void CheckInitialised() const {
|
||||
void CheckInitialized() const {
|
||||
if (!ptr_) {
|
||||
ptr_.reset(init_());
|
||||
ptr_.reset(init_(), [](T*obj) { obj->deleteLater(); });
|
||||
}
|
||||
}
|
||||
|
||||
const std::function<T*()> init_;
|
||||
mutable std::unique_ptr<T> ptr_;
|
||||
mutable std::shared_ptr<T> ptr_;
|
||||
};
|
||||
|
||||
#endif // LAZY_H
|
||||
|
||||
@@ -1,33 +0,0 @@
|
||||
/* This file is part of Strawberry.
|
||||
Copyright 2012, David Sansome <me@davidsansome.com>
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
#ifndef OVERRIDE_H
|
||||
#define OVERRIDE_H
|
||||
|
||||
// Defines the OVERRIDE macro as C++11's override control keyword if
|
||||
// it is available.
|
||||
|
||||
#ifndef __has_extension
|
||||
#define __has_extension(x) 0
|
||||
#endif
|
||||
|
||||
#if __has_extension(cxx_override_control) // Clang feature checking macro.
|
||||
# define OVERRIDE override
|
||||
#else
|
||||
# define OVERRIDE
|
||||
#endif
|
||||
|
||||
#endif // OVERRIDE_H
|
||||
@@ -1,7 +1,7 @@
|
||||
cmake_minimum_required(VERSION 3.0)
|
||||
|
||||
set(MESSAGES tagreadermessages.proto)
|
||||
set(SOURCES fmpsparser.cpp tagreader.cpp)
|
||||
set(SOURCES tagreader.cpp)
|
||||
|
||||
protobuf_generate_cpp(PROTO_SOURCES PROTO_HEADERS ${MESSAGES})
|
||||
|
||||
@@ -9,7 +9,6 @@ link_directories(
|
||||
${GLIB_LIBRARY_DIRS}
|
||||
${PROTOBUF_LIBRARY_DIRS}
|
||||
${TAGLIB_LIBRARY_DIRS}
|
||||
${Qt5Core_LIBRARY_DIRS}
|
||||
)
|
||||
|
||||
add_library(libstrawberry-tagreader STATIC ${PROTO_SOURCES} ${SOURCES})
|
||||
@@ -17,8 +16,6 @@ add_library(libstrawberry-tagreader STATIC ${PROTO_SOURCES} ${SOURCES})
|
||||
target_include_directories(libstrawberry-tagreader SYSTEM PRIVATE
|
||||
${GLIB_INCLUDE_DIRS}
|
||||
${PROTOBUF_INCLUDE_DIRS}
|
||||
${QtCore_INCLUDE_DIRS}
|
||||
${QtNetwork_INCLUDE_DIRS}
|
||||
)
|
||||
|
||||
target_include_directories(libstrawberry-tagreader PRIVATE
|
||||
@@ -38,7 +35,3 @@ target_link_libraries(libstrawberry-tagreader PRIVATE
|
||||
${QtNetwork_LIBRARIES}
|
||||
libstrawberry-common
|
||||
)
|
||||
|
||||
if(WITH_QT6)
|
||||
target_link_libraries(libstrawberry-tagreader PRIVATE Qt6::Core5Compat)
|
||||
endif()
|
||||
|
||||
@@ -1,132 +0,0 @@
|
||||
/* This file is part of Strawberry.
|
||||
Copyright 2010, David Sansome <me@davidsansome.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 "config.h"
|
||||
|
||||
#include <functional>
|
||||
|
||||
#include <QList>
|
||||
#include <QVariant>
|
||||
#include <QString>
|
||||
#include <QChar>
|
||||
#include <QRegularExpression>
|
||||
#include <QRegularExpressionMatch>
|
||||
|
||||
#include "fmpsparser.h"
|
||||
|
||||
using std::placeholders::_1;
|
||||
using std::placeholders::_2;
|
||||
|
||||
FMPSParser::FMPSParser() :
|
||||
// The float regex ends with (?:$|(?=::|;;)) to ensure it matches all the way
|
||||
// up to the end of the value. Without it, it would match a string that
|
||||
// starts with a number, like "123abc".
|
||||
float_re_("\\s*([+-]?\\d+(?:\\.\\d+)?)\\s*(?:$|(?=::|;;))"),
|
||||
|
||||
// Matches any character except unescaped slashes, colons and semicolons.
|
||||
string_re_("((?:[^\\\\;:]|(?:\\\\[\\\\:;]))+)(?:$|(?=::|;;))"),
|
||||
|
||||
// Used for replacing escaped characters.
|
||||
escape_re_("\\\\([\\\\:;])") {}
|
||||
|
||||
// Parses a list of things (of type T) that are separated by two consecutive
|
||||
// Separator characters. Each individual thing is parsed by the F function.
|
||||
// For example, to parse this data:
|
||||
// foo::bar::baz
|
||||
// Use:
|
||||
// QVariantList ret;
|
||||
// ParseContainer<':'>(data, ParseValue, &ret);
|
||||
// ret will then contain "foo", "bar", and "baz".
|
||||
// Returns the number of characters that were consumed from data.
|
||||
//
|
||||
// You can parse lists of lists by using different separator characters:
|
||||
// ParseContainer<';'>(data, ParseContainer<':'>, &ret);
|
||||
template <char Separator, typename F, typename T>
|
||||
static int ParseContainer(const QStringRef& data, F f, QList<T>* ret) {
|
||||
ret->clear();
|
||||
|
||||
T value;
|
||||
int pos = 0;
|
||||
while (pos < data.length()) {
|
||||
const int len = data.length() - pos;
|
||||
int matched_len = f(QStringRef(data.string(), data.position() + pos, len), &value);
|
||||
if (matched_len == -1 || matched_len > len)
|
||||
break;
|
||||
|
||||
ret->append(value);
|
||||
pos += matched_len;
|
||||
|
||||
// Expect two separators in a row
|
||||
if (pos + 2 <= data.length() && data.at(pos) == Separator && data.at(pos+1) == Separator) {
|
||||
pos += 2;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return pos;
|
||||
}
|
||||
|
||||
bool FMPSParser::Parse(const QString &data) {
|
||||
|
||||
result_ = Result();
|
||||
|
||||
// Only return success if we matched the whole string
|
||||
return ParseListList(data, &result_) == data.length();
|
||||
}
|
||||
|
||||
int FMPSParser::ParseValueRef(const QStringRef& data, QVariant* ret) const {
|
||||
// Try to match a float
|
||||
QRegularExpressionMatch re_match = float_re_.match(*data.string(), data.position());
|
||||
if (re_match.capturedStart() == data.position()) {
|
||||
*ret = re_match.captured(1).toDouble();
|
||||
return re_match.capturedLength();
|
||||
}
|
||||
|
||||
// Otherwise try to match a string
|
||||
re_match = string_re_.match(*data.string(), data.position());
|
||||
if (re_match.capturedStart() == data.position()) {
|
||||
// Replace escape sequences with their actual characters
|
||||
QString value = re_match.captured(1);
|
||||
value.replace(escape_re_, "\\1");
|
||||
*ret = value;
|
||||
return re_match.capturedLength();
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
// Parses an inner list - a list of values
|
||||
int FMPSParser::ParseListRef(const QStringRef &data, QVariantList *ret) const {
|
||||
return ParseContainer<':'>(data, std::bind(&FMPSParser::ParseValueRef, this, _1, _2), ret);
|
||||
}
|
||||
|
||||
// Parses an outer list - a list of lists
|
||||
int FMPSParser::ParseListListRef(const QStringRef &data, Result *ret) const {
|
||||
return ParseContainer<';'>(data, std::bind(&FMPSParser::ParseListRef, this, _1, _2), ret);
|
||||
}
|
||||
|
||||
// Convenience functions that take QStrings instead of QStringRefs. Use the QStringRef versions if possible, they're faster.
|
||||
int FMPSParser::ParseValue(const QString &data, QVariant *ret) const {
|
||||
return ParseValueRef(QStringRef(&data), ret);
|
||||
}
|
||||
int FMPSParser::ParseList(const QString &data, QVariantList *ret) const {
|
||||
return ParseListRef(QStringRef(&data), ret);
|
||||
}
|
||||
int FMPSParser::ParseListList(const QString &data, Result *ret) const {
|
||||
return ParseListListRef(QStringRef(&data), ret);
|
||||
}
|
||||
@@ -1,64 +0,0 @@
|
||||
/* This file is part of Strawberry.
|
||||
Copyright 2010, David Sansome <me@davidsansome.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/>.
|
||||
*/
|
||||
|
||||
#ifndef FMPSPARSER_H
|
||||
#define FMPSPARSER_H
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#include <QList>
|
||||
#include <QMetaType>
|
||||
#include <QString>
|
||||
#include <QRegularExpression>
|
||||
|
||||
class QVariant;
|
||||
|
||||
class FMPSParser {
|
||||
public:
|
||||
FMPSParser();
|
||||
|
||||
// A FMPS result is a list of lists of values (where a value is a string or
|
||||
// a float).
|
||||
typedef QList<QVariantList> Result;
|
||||
|
||||
// Parses a FMPS value and returns true on success.
|
||||
bool Parse(const QString &data);
|
||||
|
||||
// Gets the result of the last successful Parse.
|
||||
Result result() const { return result_; }
|
||||
|
||||
// Returns true if result() is empty.
|
||||
bool is_empty() const { return result().isEmpty() || result()[0].isEmpty(); }
|
||||
|
||||
// Internal functions, public for unit tests
|
||||
int ParseValue(const QString &data, QVariant *ret) const;
|
||||
int ParseValueRef(const QStringRef &data, QVariant *ret) const;
|
||||
|
||||
int ParseList(const QString &data, QVariantList *ret) const;
|
||||
int ParseListRef(const QStringRef &data, QVariantList *ret) const;
|
||||
|
||||
int ParseListList(const QString &data, Result *ret) const;
|
||||
int ParseListListRef(const QStringRef &data, Result *ret) const;
|
||||
|
||||
private:
|
||||
QRegularExpression float_re_;
|
||||
QRegularExpression string_re_;
|
||||
QRegularExpression escape_re_;
|
||||
Result result_;
|
||||
};
|
||||
|
||||
#endif // FMPSPARSER_H
|
||||
@@ -84,14 +84,11 @@
|
||||
#include <QVariant>
|
||||
#include <QString>
|
||||
#include <QUrl>
|
||||
#include <QTextCodec>
|
||||
#include <QVector>
|
||||
#include <QtDebug>
|
||||
|
||||
#include "core/logging.h"
|
||||
#include "core/messagehandler.h"
|
||||
|
||||
#include "fmpsparser.h"
|
||||
#include "core/timeconstants.h"
|
||||
|
||||
class FileRefFactory {
|
||||
@@ -140,6 +137,15 @@ TagReader::~TagReader() {
|
||||
delete factory_;
|
||||
}
|
||||
|
||||
bool TagReader::IsMediaFile(const QString &filename) const {
|
||||
|
||||
qLog(Debug) << "Checking for valid file" << filename;
|
||||
|
||||
std::unique_ptr<TagLib::FileRef> fileref(factory_->GetFileRef(filename));
|
||||
return !fileref->isNull() && fileref->tag();
|
||||
|
||||
}
|
||||
|
||||
pb::tagreader::SongMetadata_FileType TagReader::GuessFileType(TagLib::FileRef *fileref) const {
|
||||
|
||||
if (dynamic_cast<TagLib::RIFF::WAV::File*>(fileref->file())) return pb::tagreader::SongMetadata_FileType_WAV;
|
||||
@@ -200,10 +206,10 @@ void TagReader::ReadFile(const QString &filename, pb::tagreader::SongMetadata *s
|
||||
|
||||
TagLib::Tag *tag = fileref->tag();
|
||||
if (tag) {
|
||||
Decode(tag->title(), nullptr, song->mutable_title());
|
||||
Decode(tag->artist(), nullptr, song->mutable_artist()); // TPE1
|
||||
Decode(tag->album(), nullptr, song->mutable_album());
|
||||
Decode(tag->genre(), nullptr, song->mutable_genre());
|
||||
Decode(tag->title(), song->mutable_title());
|
||||
Decode(tag->artist(), song->mutable_artist()); // TPE1
|
||||
Decode(tag->album(), song->mutable_album());
|
||||
Decode(tag->genre(), song->mutable_genre());
|
||||
song->set_year(tag->year());
|
||||
song->set_track(tag->track());
|
||||
song->set_valid(true);
|
||||
@@ -216,7 +222,7 @@ void TagReader::ReadFile(const QString &filename, pb::tagreader::SongMetadata *s
|
||||
// Handle all the files which have VorbisComments (Ogg, OPUS, ...) in the same way;
|
||||
// apart, so we keep specific behavior for some formats by adding another "else if" block below.
|
||||
if (TagLib::Ogg::XiphComment *tag_ogg = dynamic_cast<TagLib::Ogg::XiphComment*>(fileref->file()->tag())) {
|
||||
ParseOggTag(tag_ogg->fieldListMap(), nullptr, &disc, &compilation, song);
|
||||
ParseOggTag(tag_ogg->fieldListMap(), &disc, &compilation, song);
|
||||
if (!tag_ogg->pictureList().isEmpty()) {
|
||||
song->set_art_automatic(kEmbeddedCover);
|
||||
}
|
||||
@@ -227,28 +233,28 @@ void TagReader::ReadFile(const QString &filename, pb::tagreader::SongMetadata *s
|
||||
song->set_bitdepth(file_flac->audioProperties()->bitsPerSample());
|
||||
|
||||
if (file_flac->xiphComment()) {
|
||||
ParseOggTag(file_flac->xiphComment()->fieldListMap(), nullptr, &disc, &compilation, song);
|
||||
ParseOggTag(file_flac->xiphComment()->fieldListMap(), &disc, &compilation, song);
|
||||
if (!file_flac->pictureList().isEmpty()) {
|
||||
song->set_art_automatic(kEmbeddedCover);
|
||||
}
|
||||
}
|
||||
if (tag) Decode(tag->comment(), nullptr, song->mutable_comment());
|
||||
if (tag) Decode(tag->comment(), song->mutable_comment());
|
||||
}
|
||||
|
||||
else if (TagLib::WavPack::File *file_wavpack = dynamic_cast<TagLib::WavPack::File *>(fileref->file())) {
|
||||
song->set_bitdepth(file_wavpack->audioProperties()->bitsPerSample());
|
||||
if (file_wavpack->tag()) {
|
||||
ParseAPETag(file_wavpack->APETag()->itemListMap(), nullptr, &disc, &compilation, song);
|
||||
if (file_wavpack->APETag()) {
|
||||
ParseAPETag(file_wavpack->APETag()->itemListMap(), &disc, &compilation, song);
|
||||
}
|
||||
if (tag) Decode(tag->comment(), nullptr, song->mutable_comment());
|
||||
if (tag) Decode(tag->comment(), song->mutable_comment());
|
||||
}
|
||||
|
||||
else if (TagLib::APE::File *file_ape = dynamic_cast<TagLib::APE::File*>(fileref->file())) {
|
||||
if (file_ape->tag()) {
|
||||
ParseAPETag(file_ape->APETag()->itemListMap(), nullptr, &disc, &compilation, song);
|
||||
if (file_ape->APETag()) {
|
||||
ParseAPETag(file_ape->APETag()->itemListMap(), &disc, &compilation, song);
|
||||
}
|
||||
song->set_bitdepth(file_ape->audioProperties()->bitsPerSample());
|
||||
if (tag) Decode(tag->comment(), nullptr, song->mutable_comment());
|
||||
if (tag) Decode(tag->comment(), song->mutable_comment());
|
||||
}
|
||||
|
||||
else if (TagLib::MPEG::File *file_mpeg = dynamic_cast<TagLib::MPEG::File*>(fileref->file())) {
|
||||
@@ -257,22 +263,22 @@ void TagReader::ReadFile(const QString &filename, pb::tagreader::SongMetadata *s
|
||||
const TagLib::ID3v2::FrameListMap &map = file_mpeg->ID3v2Tag()->frameListMap();
|
||||
|
||||
if (!map["TPOS"].isEmpty()) disc = TStringToQString(map["TPOS"].front()->toString()).trimmed();
|
||||
if (!map["TCOM"].isEmpty()) Decode(map["TCOM"].front()->toString(), nullptr, song->mutable_composer());
|
||||
if (!map["TCOM"].isEmpty()) Decode(map["TCOM"].front()->toString(), song->mutable_composer());
|
||||
|
||||
// content group
|
||||
if (!map["TIT1"].isEmpty()) Decode(map["TIT1"].front()->toString(), nullptr, song->mutable_grouping());
|
||||
if (!map["TIT1"].isEmpty()) Decode(map["TIT1"].front()->toString(), song->mutable_grouping());
|
||||
|
||||
// ID3v2: lead performer/soloist
|
||||
if (!map["TPE1"].isEmpty()) Decode(map["TPE1"].front()->toString(), nullptr, song->mutable_performer());
|
||||
if (!map["TPE1"].isEmpty()) Decode(map["TPE1"].front()->toString(), song->mutable_performer());
|
||||
|
||||
// original artist/performer
|
||||
if (!map["TOPE"].isEmpty()) Decode(map["TOPE"].front()->toString(), nullptr, song->mutable_performer());
|
||||
if (!map["TOPE"].isEmpty()) Decode(map["TOPE"].front()->toString(), song->mutable_performer());
|
||||
|
||||
// Skip TPE1 (which is the artist) here because we already fetched it
|
||||
|
||||
|
||||
// non-standard: Apple, Microsoft
|
||||
if (!map["TPE2"].isEmpty()) Decode(map["TPE2"].front()->toString(), nullptr, song->mutable_albumartist());
|
||||
if (!map["TPE2"].isEmpty()) Decode(map["TPE2"].front()->toString(), song->mutable_albumartist());
|
||||
|
||||
if (!map["TCMP"].isEmpty()) compilation = TStringToQString(map["TCMP"].front()->toString()).trimmed();
|
||||
|
||||
@@ -282,33 +288,24 @@ void TagReader::ReadFile(const QString &filename, pb::tagreader::SongMetadata *s
|
||||
}
|
||||
|
||||
if (!map["USLT"].isEmpty()) {
|
||||
Decode(map["USLT"].front()->toString(), nullptr, song->mutable_lyrics());
|
||||
Decode(map["USLT"].front()->toString(), song->mutable_lyrics());
|
||||
}
|
||||
else if (!map["SYLT"].isEmpty()) {
|
||||
Decode(map["SYLT"].front()->toString(), nullptr, song->mutable_lyrics());
|
||||
Decode(map["SYLT"].front()->toString(), song->mutable_lyrics());
|
||||
}
|
||||
|
||||
if (!map["APIC"].isEmpty()) song->set_art_automatic(kEmbeddedCover);
|
||||
|
||||
// Find a suitable comment tag. For now we ignore iTunNORM comments.
|
||||
for (uint i = 0; i < map["COMM"].size(); ++i) {
|
||||
for (uint i = 0 ; i < map["COMM"].size() ; ++i) {
|
||||
const TagLib::ID3v2::CommentsFrame *frame = dynamic_cast<const TagLib::ID3v2::CommentsFrame*>(map["COMM"][i]);
|
||||
|
||||
if (frame && TStringToQString(frame->description()) != "iTunNORM") {
|
||||
Decode(frame->text(), nullptr, song->mutable_comment());
|
||||
Decode(frame->text(), song->mutable_comment());
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Parse FMPS frames
|
||||
for (uint i = 0; i < map["TXXX"].size(); ++i) {
|
||||
const TagLib::ID3v2::UserTextIdentificationFrame *frame = dynamic_cast<const TagLib::ID3v2::UserTextIdentificationFrame*>(map["TXXX"][i]);
|
||||
|
||||
if (frame && frame->description().startsWith("FMPS_")) {
|
||||
ParseFMPSFrame(TStringToQString(frame->description()), TStringToQString(frame->fieldList()[1]), song);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -323,7 +320,7 @@ void TagReader::ReadFile(const QString &filename, pb::tagreader::SongMetadata *s
|
||||
if (mp4_tag->item("aART").isValid()) {
|
||||
TagLib::StringList album_artists = mp4_tag->item("aART").toStringList();
|
||||
if (!album_artists.isEmpty()) {
|
||||
Decode(album_artists.front(), nullptr, song->mutable_albumartist());
|
||||
Decode(album_artists.front(), song->mutable_albumartist());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -337,20 +334,20 @@ void TagReader::ReadFile(const QString &filename, pb::tagreader::SongMetadata *s
|
||||
}
|
||||
|
||||
if (mp4_tag->item("\251wrt").isValid()) {
|
||||
Decode(mp4_tag->item("\251wrt").toStringList().toString(", "), nullptr, song->mutable_composer());
|
||||
Decode(mp4_tag->item("\251wrt").toStringList().toString(", "), song->mutable_composer());
|
||||
}
|
||||
if (mp4_tag->item("\251grp").isValid()) {
|
||||
Decode(mp4_tag->item("\251grp").toStringList().toString(" "), nullptr, song->mutable_grouping());
|
||||
Decode(mp4_tag->item("\251grp").toStringList().toString(" "), song->mutable_grouping());
|
||||
}
|
||||
if (mp4_tag->item("\251lyr").isValid()) {
|
||||
Decode(mp4_tag->item("\251lyr").toStringList().toString(" "), nullptr, song->mutable_lyrics());
|
||||
Decode(mp4_tag->item("\251lyr").toStringList().toString(" "), song->mutable_lyrics());
|
||||
}
|
||||
|
||||
if (mp4_tag->item(kMP4_OriginalYear_ID).isValid()) {
|
||||
song->set_originalyear(TStringToQString(mp4_tag->item(kMP4_OriginalYear_ID).toStringList().toString('\n')).left(4).toInt());
|
||||
}
|
||||
|
||||
Decode(mp4_tag->comment(), nullptr, song->mutable_comment());
|
||||
Decode(mp4_tag->comment(), song->mutable_comment());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -359,7 +356,7 @@ void TagReader::ReadFile(const QString &filename, pb::tagreader::SongMetadata *s
|
||||
song->set_bitdepth(file_asf->audioProperties()->bitsPerSample());
|
||||
|
||||
if (file_asf->tag()) {
|
||||
Decode(file_asf->tag()->comment(), nullptr, song->mutable_comment());
|
||||
Decode(file_asf->tag()->comment(), song->mutable_comment());
|
||||
}
|
||||
|
||||
const TagLib::ASF::AttributeListMap &attributes_map = file_asf->tag()->attributeListMap();
|
||||
@@ -378,15 +375,15 @@ void TagReader::ReadFile(const QString &filename, pb::tagreader::SongMetadata *s
|
||||
}
|
||||
}
|
||||
|
||||
else if (TagLib::MPC::File* file_mpc = dynamic_cast<TagLib::MPC::File*>(fileref->file())) {
|
||||
if (file_mpc->tag()) {
|
||||
ParseAPETag(file_mpc->APETag()->itemListMap(), nullptr, &disc, &compilation, song);
|
||||
else if (TagLib::MPC::File *file_mpc = dynamic_cast<TagLib::MPC::File*>(fileref->file())) {
|
||||
if (file_mpc->APETag()) {
|
||||
ParseAPETag(file_mpc->APETag()->itemListMap(), &disc, &compilation, song);
|
||||
}
|
||||
if (tag) Decode(tag->comment(), nullptr, song->mutable_comment());
|
||||
if (tag) Decode(tag->comment(), song->mutable_comment());
|
||||
}
|
||||
|
||||
else if (tag) {
|
||||
Decode(tag->comment(), nullptr, song->mutable_comment());
|
||||
Decode(tag->comment(), song->mutable_comment());
|
||||
}
|
||||
|
||||
if (!disc.isEmpty()) {
|
||||
@@ -402,7 +399,7 @@ void TagReader::ReadFile(const QString &filename, pb::tagreader::SongMetadata *s
|
||||
|
||||
if (compilation.isEmpty()) {
|
||||
// well, it wasn't set, but if the artist is VA assume it's a compilation
|
||||
if (QStringFromStdString(song->artist()).toLower() == "various artists") {
|
||||
if (QStringFromStdString(song->artist()).toLower() == "various artists" || QStringFromStdString(song->albumartist()).toLower() == "various artists") {
|
||||
song->set_compilation(true);
|
||||
}
|
||||
}
|
||||
@@ -425,42 +422,27 @@ void TagReader::ReadFile(const QString &filename, pb::tagreader::SongMetadata *s
|
||||
|
||||
}
|
||||
|
||||
void TagReader::Decode(const TagLib::String &tag, const QTextCodec *codec, std::string *output) {
|
||||
|
||||
QString tmp;
|
||||
|
||||
if (codec && tag.isLatin1()) { // Never override UTF-8.
|
||||
const std::string fixed = QString::fromUtf8(tag.toCString(true)).toStdString();
|
||||
tmp = codec->toUnicode(fixed.c_str()).trimmed();
|
||||
}
|
||||
else {
|
||||
tmp = TStringToQString(tag).trimmed();
|
||||
}
|
||||
void TagReader::Decode(const TagLib::String &tag, std::string *output) {
|
||||
|
||||
QString tmp = TStringToQString(tag).trimmed();
|
||||
output->assign(DataCommaSizeFromQString(tmp));
|
||||
|
||||
}
|
||||
|
||||
void TagReader::Decode(const QString &tag, const QTextCodec *codec, std::string *output) {
|
||||
void TagReader::Decode(const QString &tag, std::string *output) {
|
||||
|
||||
if (!codec) {
|
||||
output->assign(DataCommaSizeFromQString(tag));
|
||||
}
|
||||
else {
|
||||
const QString decoded(codec->toUnicode(tag.toUtf8()));
|
||||
output->assign(DataCommaSizeFromQString(decoded));
|
||||
}
|
||||
output->assign(DataCommaSizeFromQString(tag));
|
||||
|
||||
}
|
||||
|
||||
void TagReader::ParseOggTag(const TagLib::Ogg::FieldListMap &map, const QTextCodec *codec, QString *disc, QString *compilation, pb::tagreader::SongMetadata *song) const {
|
||||
void TagReader::ParseOggTag(const TagLib::Ogg::FieldListMap &map, QString *disc, QString *compilation, pb::tagreader::SongMetadata *song) const {
|
||||
|
||||
if (!map["COMPOSER"].isEmpty()) Decode(map["COMPOSER"].front(), codec, song->mutable_composer());
|
||||
if (!map["PERFORMER"].isEmpty()) Decode(map["PERFORMER"].front(), codec, song->mutable_performer());
|
||||
if (!map["CONTENT GROUP"].isEmpty()) Decode(map["CONTENT GROUP"].front(), codec, song->mutable_grouping());
|
||||
if (!map["COMPOSER"].isEmpty()) Decode(map["COMPOSER"].front(), song->mutable_composer());
|
||||
if (!map["PERFORMER"].isEmpty()) Decode(map["PERFORMER"].front(), song->mutable_performer());
|
||||
if (!map["CONTENT GROUP"].isEmpty()) Decode(map["CONTENT GROUP"].front(), song->mutable_grouping());
|
||||
|
||||
if (!map["ALBUMARTIST"].isEmpty()) Decode(map["ALBUMARTIST"].front(), codec, song->mutable_albumartist());
|
||||
else if (!map["ALBUM ARTIST"].isEmpty()) Decode(map["ALBUM ARTIST"].front(), codec, song->mutable_albumartist());
|
||||
if (!map["ALBUMARTIST"].isEmpty()) Decode(map["ALBUMARTIST"].front(), song->mutable_albumartist());
|
||||
else if (!map["ALBUM ARTIST"].isEmpty()) Decode(map["ALBUM ARTIST"].front(), song->mutable_albumartist());
|
||||
|
||||
if (!map["ORIGINALDATE"].isEmpty()) song->set_originalyear(TStringToQString(map["ORIGINALDATE"].front()).left(4).toInt());
|
||||
else if (!map["ORIGINALYEAR"].isEmpty()) song->set_originalyear(TStringToQString(map["ORIGINALYEAR"].front()).toInt());
|
||||
@@ -472,20 +454,18 @@ void TagReader::ParseOggTag(const TagLib::Ogg::FieldListMap &map, const QTextCod
|
||||
|
||||
if (!map["FMPS_PLAYCOUNT"].isEmpty() && song->playcount() <= 0) song->set_playcount(TStringToQString( map["FMPS_PLAYCOUNT"].front() ).trimmed().toFloat());
|
||||
|
||||
if (!map["LYRICS"].isEmpty()) Decode(map["LYRICS"].front(), codec, song->mutable_lyrics());
|
||||
else if (!map["UNSYNCEDLYRICS"].isEmpty()) Decode(map["UNSYNCEDLYRICS"].front(), codec, song->mutable_lyrics());
|
||||
if (!map["LYRICS"].isEmpty()) Decode(map["LYRICS"].front(), song->mutable_lyrics());
|
||||
else if (!map["UNSYNCEDLYRICS"].isEmpty()) Decode(map["UNSYNCEDLYRICS"].front(), song->mutable_lyrics());
|
||||
|
||||
}
|
||||
|
||||
void TagReader::ParseAPETag(const TagLib::APE::ItemListMap &map, const QTextCodec *codec, QString *disc, QString *compilation, pb::tagreader::SongMetadata *song) const {
|
||||
|
||||
Q_UNUSED(codec);
|
||||
void TagReader::ParseAPETag(const TagLib::APE::ItemListMap &map, QString *disc, QString *compilation, pb::tagreader::SongMetadata *song) const {
|
||||
|
||||
TagLib::APE::ItemListMap::ConstIterator it = map.find("ALBUM ARTIST");
|
||||
if (it != map.end()) {
|
||||
TagLib::StringList album_artists = it->second.values();
|
||||
if (!album_artists.isEmpty()) {
|
||||
Decode(album_artists.front(), nullptr, song->mutable_albumartist());
|
||||
Decode(album_artists.front(), song->mutable_albumartist());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -499,19 +479,19 @@ void TagReader::ParseAPETag(const TagLib::APE::ItemListMap &map, const QTextCode
|
||||
}
|
||||
|
||||
if (map.contains("PERFORMER")) {
|
||||
Decode(map["PERFORMER"].values().toString(", "), nullptr, song->mutable_performer());
|
||||
Decode(map["PERFORMER"].values().toString(", "), song->mutable_performer());
|
||||
}
|
||||
|
||||
if (map.contains("COMPOSER")) {
|
||||
Decode(map["COMPOSER"].values().toString(", "), nullptr, song->mutable_composer());
|
||||
Decode(map["COMPOSER"].values().toString(", "), song->mutable_composer());
|
||||
}
|
||||
|
||||
if (map.contains("GROUPING")) {
|
||||
Decode(map["GROUPING"].values().toString(" "), nullptr, song->mutable_grouping());
|
||||
Decode(map["GROUPING"].values().toString(" "), song->mutable_grouping());
|
||||
}
|
||||
|
||||
if (map.contains("LYRICS")) {
|
||||
Decode(map["LYRICS"].toString(), nullptr, song->mutable_lyrics());
|
||||
Decode(map["LYRICS"].toString(), song->mutable_lyrics());
|
||||
}
|
||||
|
||||
if (map.contains("FMPS_PLAYCOUNT")) {
|
||||
@@ -523,40 +503,13 @@ void TagReader::ParseAPETag(const TagLib::APE::ItemListMap &map, const QTextCode
|
||||
|
||||
}
|
||||
|
||||
void TagReader::ParseFMPSFrame(const QString &name, const QString &value, pb::tagreader::SongMetadata *song) const {
|
||||
|
||||
qLog(Debug) << "Parsing FMPSFrame" << name << ", " << value;
|
||||
FMPSParser parser;
|
||||
|
||||
if (!parser.Parse(value) || parser.is_empty()) return;
|
||||
|
||||
QVariant var;
|
||||
|
||||
if (name == "FMPS_PlayCount") {
|
||||
var = parser.result()[0][0];
|
||||
if (var.type() == QVariant::Double) {
|
||||
song->set_playcount(var.toDouble());
|
||||
}
|
||||
}
|
||||
else if (name == "FMPS_PlayCount_User") {
|
||||
// Take a user playcount only if there's no playcount already set
|
||||
if (song->playcount() == 0 && parser.result()[0].count() >= 2) {
|
||||
var = parser.result()[0][1];
|
||||
if (var.type() == QVariant::Double) {
|
||||
song->set_playcount(var.toDouble());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
void TagReader::SetVorbisComments(TagLib::Ogg::XiphComment *vorbis_comments, const pb::tagreader::SongMetadata &song) const {
|
||||
|
||||
vorbis_comments->addField("COMPOSER", StdStringToTaglibString(song.composer()), true);
|
||||
vorbis_comments->addField("PERFORMER", StdStringToTaglibString(song.performer()), true);
|
||||
vorbis_comments->addField("CONTENT GROUP", StdStringToTaglibString(song.grouping()), true);
|
||||
vorbis_comments->addField("DISCNUMBER", QStringToTaglibString(song.disc() <= 0 -1 ? QString() : QString::number(song.disc())), true);
|
||||
vorbis_comments->addField("COMPILATION", StdStringToTaglibString(song.compilation() ? "1" : "0"), true);
|
||||
vorbis_comments->addField("DISCNUMBER", QStringToTaglibString(song.disc() <= 0 ? QString() : QString::number(song.disc())), true);
|
||||
vorbis_comments->addField("COMPILATION", QStringToTaglibString(song.compilation() ? "1" : QString()), true);
|
||||
|
||||
// Try to be coherent, the two forms are used but the first one is preferred
|
||||
|
||||
@@ -570,18 +523,22 @@ void TagReader::SetVorbisComments(TagLib::Ogg::XiphComment *vorbis_comments, con
|
||||
|
||||
bool TagReader::SaveFile(const QString &filename, const pb::tagreader::SongMetadata &song) const {
|
||||
|
||||
if (filename.isNull() || filename.isEmpty()) return false;
|
||||
if (filename.isEmpty()) return false;
|
||||
|
||||
qLog(Debug) << "Saving tags to" << filename;
|
||||
std::unique_ptr<TagLib::FileRef> fileref(factory_->GetFileRef(filename));;
|
||||
if (!fileref || fileref->isNull()) return false;
|
||||
|
||||
fileref->tag()->setTitle(StdStringToTaglibString(song.title()));
|
||||
fileref->tag()->setArtist(StdStringToTaglibString(song.artist()));
|
||||
fileref->tag()->setAlbum(StdStringToTaglibString(song.album()));
|
||||
fileref->tag()->setGenre(StdStringToTaglibString(song.genre()));
|
||||
fileref->tag()->setComment(StdStringToTaglibString(song.comment()));
|
||||
fileref->tag()->setYear(song.year());
|
||||
fileref->tag()->setTrack(song.track());
|
||||
fileref->tag()->setTitle(song.title().empty() ? TagLib::String() : StdStringToTaglibString(song.title()));
|
||||
fileref->tag()->setArtist(song.artist().empty() ? TagLib::String() : StdStringToTaglibString(song.artist()));
|
||||
fileref->tag()->setAlbum(song.album().empty() ? TagLib::String() : StdStringToTaglibString(song.album()));
|
||||
fileref->tag()->setGenre(song.genre().empty() ? TagLib::String() : StdStringToTaglibString(song.genre()));
|
||||
fileref->tag()->setComment(song.comment().empty() ? TagLib::String() : StdStringToTaglibString(song.comment()));
|
||||
fileref->tag()->setYear(song.year() <= 0 ? 0 : song.year());
|
||||
fileref->tag()->setTrack(song.track() <= 0 ? 0 : song.track());
|
||||
|
||||
bool saved = false;
|
||||
bool result = false;
|
||||
|
||||
if (TagLib::FLAC::File *file = dynamic_cast<TagLib::FLAC::File*>(fileref->file())) {
|
||||
TagLib::Ogg::XiphComment *tag = file->xiphComment();
|
||||
@@ -609,14 +566,16 @@ bool TagReader::SaveFile(const QString &filename, const pb::tagreader::SongMetad
|
||||
else if (TagLib::MPEG::File *file_mpeg = dynamic_cast<TagLib::MPEG::File*>(fileref->file())) {
|
||||
TagLib::ID3v2::Tag *tag = file_mpeg->ID3v2Tag(true);
|
||||
if (!tag) return false;
|
||||
SetTextFrame("TPOS", song.disc() <= 0 -1 ? QString() : QString::number(song.disc()), tag);
|
||||
SetTextFrame("TCOM", song.composer(), tag);
|
||||
SetTextFrame("TIT1", song.grouping(), tag);
|
||||
SetTextFrame("TOPE", song.performer(), tag);
|
||||
SetTextFrame("TPOS", song.disc() <= 0 ? QString() : QString::number(song.disc()), tag);
|
||||
SetTextFrame("TCOM", song.composer().empty() ? std::string() : song.composer(), tag);
|
||||
SetTextFrame("TIT1", song.grouping().empty() ? std::string() : song.grouping(), tag);
|
||||
SetTextFrame("TOPE", song.performer().empty() ? std::string() : song.performer(), tag);
|
||||
// Skip TPE1 (which is the artist) here because we already set it
|
||||
SetTextFrame("TPE2", song.albumartist(), tag);
|
||||
SetTextFrame("TCMP", std::string(song.compilation() ? "1" : "0"), tag);
|
||||
SetUnsyncLyricsFrame(song.lyrics(), tag);
|
||||
SetTextFrame("TPE2", song.albumartist().empty() ? std::string() : song.albumartist(), tag);
|
||||
SetTextFrame("TCMP", song.compilation() ? QString::number(1) : QString(), tag);
|
||||
SetUnsyncLyricsFrame(song.lyrics().empty() ? std::string() : song.lyrics(), tag);
|
||||
result = file_mpeg->save(TagLib::MPEG::File::ID3v2);
|
||||
saved = true;
|
||||
}
|
||||
|
||||
else if (TagLib::MP4::File *file_mp4 = dynamic_cast<TagLib::MP4::File*>(fileref->file())) {
|
||||
@@ -635,53 +594,29 @@ bool TagReader::SaveFile(const QString &filename, const pb::tagreader::SongMetad
|
||||
SetVorbisComments(tag, song);
|
||||
}
|
||||
|
||||
bool ret = fileref->save();
|
||||
if (!saved) {
|
||||
result = fileref->save();
|
||||
}
|
||||
|
||||
#ifdef Q_OS_LINUX
|
||||
if (ret) {
|
||||
if (result) {
|
||||
// Linux: inotify doesn't seem to notice the change to the file unless we change the timestamps as well. (this is what touch does)
|
||||
utimensat(0, QFile::encodeName(filename).constData(), nullptr, 0);
|
||||
}
|
||||
#endif // Q_OS_LINUX
|
||||
|
||||
return ret;
|
||||
return result;
|
||||
}
|
||||
|
||||
void TagReader::SaveAPETag(TagLib::APE::Tag *tag, const pb::tagreader::SongMetadata &song) const {
|
||||
|
||||
tag->setItem("album artist", TagLib::APE::Item("album artist", TagLib::StringList(song.albumartist().c_str())));
|
||||
tag->setItem("disc", TagLib::APE::Item("disc", TagLib::String::number(song.disc() <= 0 - 1 ? 0 : song.disc())));
|
||||
tag->addValue("disc", QStringToTaglibString(song.disc() <= 0 ? QString() : QString::number(song.disc())), true);
|
||||
tag->setItem("composer", TagLib::APE::Item("composer", TagLib::StringList(song.composer().c_str())));
|
||||
tag->setItem("grouping", TagLib::APE::Item("grouping", TagLib::StringList(song.grouping().c_str())));
|
||||
tag->setItem("performer", TagLib::APE::Item("performer", TagLib::StringList(song.performer().c_str())));
|
||||
tag->setItem("lyrics", TagLib::APE::Item("lyrics", TagLib::String(song.lyrics())));
|
||||
tag->setItem("compilation", TagLib::APE::Item("compilation", TagLib::StringList(song.compilation() ? "1" : "0")));
|
||||
|
||||
}
|
||||
|
||||
void TagReader::SetUserTextFrame(const QString &description, const QString &value, TagLib::ID3v2::Tag *tag) const {
|
||||
|
||||
const QByteArray descr_utf8(description.toUtf8());
|
||||
const QByteArray value_utf8(value.toUtf8());
|
||||
qLog(Debug) << "Setting FMPSFrame:" << description << ", " << value;
|
||||
SetUserTextFrame(std::string(descr_utf8.constData(), descr_utf8.length()), std::string(value_utf8.constData(), value_utf8.length()), tag);
|
||||
|
||||
}
|
||||
|
||||
void TagReader::SetUserTextFrame(const std::string &description, const std::string &value, TagLib::ID3v2::Tag *tag) const {
|
||||
|
||||
const TagLib::String t_description = StdStringToTaglibString(description);
|
||||
// Remove the frame if it already exists
|
||||
TagLib::ID3v2::UserTextIdentificationFrame *frame = TagLib::ID3v2::UserTextIdentificationFrame::find(tag, t_description);
|
||||
if (frame) {
|
||||
tag->removeFrame(frame);
|
||||
}
|
||||
|
||||
// Create and add a new frame
|
||||
frame = new TagLib::ID3v2::UserTextIdentificationFrame(TagLib::String::UTF8);
|
||||
|
||||
frame->setDescription(t_description);
|
||||
frame->setText(StdStringToTaglibString(value));
|
||||
tag->addFrame(frame);
|
||||
tag->addValue("compilation", QStringToTaglibString(song.compilation() ? QString::number(1) : QString()), true);
|
||||
|
||||
}
|
||||
|
||||
@@ -689,6 +624,7 @@ void TagReader::SetTextFrame(const char *id, const QString &value, TagLib::ID3v2
|
||||
|
||||
const QByteArray utf8(value.toUtf8());
|
||||
SetTextFrame(id, std::string(utf8.constData(), utf8.length()), tag);
|
||||
|
||||
}
|
||||
|
||||
void TagReader::SetTextFrame(const char *id, const std::string &value, TagLib::ID3v2::Tag *tag) const {
|
||||
@@ -702,6 +638,8 @@ void TagReader::SetTextFrame(const char *id, const std::string &value, TagLib::I
|
||||
tag->removeFrame(tag->frameListMap()[id_vector].front());
|
||||
}
|
||||
|
||||
if (value.empty()) return;
|
||||
|
||||
// If no frames stored create empty frame
|
||||
if (frames_buffer.isEmpty()) {
|
||||
TagLib::ID3v2::TextIdentificationFrame frame(id_vector, TagLib::String::UTF8);
|
||||
@@ -709,9 +647,9 @@ void TagReader::SetTextFrame(const char *id, const std::string &value, TagLib::I
|
||||
}
|
||||
|
||||
// Update and add the frames
|
||||
for (int lyrics_index = 0; lyrics_index < frames_buffer.size(); lyrics_index++) {
|
||||
TagLib::ID3v2::TextIdentificationFrame* frame = new TagLib::ID3v2::TextIdentificationFrame(frames_buffer.at(lyrics_index));
|
||||
if (lyrics_index == 0) {
|
||||
for (int i = 0 ; i < frames_buffer.size() ; ++i) {
|
||||
TagLib::ID3v2::TextIdentificationFrame *frame = new TagLib::ID3v2::TextIdentificationFrame(frames_buffer.at(i));
|
||||
if (i == 0) {
|
||||
frame->setText(StdStringToTaglibString(value));
|
||||
}
|
||||
// add frame takes ownership and clears the memory
|
||||
@@ -720,15 +658,6 @@ void TagReader::SetTextFrame(const char *id, const std::string &value, TagLib::I
|
||||
|
||||
}
|
||||
|
||||
bool TagReader::IsMediaFile(const QString &filename) const {
|
||||
|
||||
qLog(Debug) << "Checking for valid file" << filename;
|
||||
|
||||
std::unique_ptr<TagLib::FileRef> fileref(factory_->GetFileRef(filename));
|
||||
return !fileref->isNull() && fileref->tag();
|
||||
|
||||
}
|
||||
|
||||
QByteArray TagReader::LoadEmbeddedArt(const QString &filename) const {
|
||||
|
||||
if (filename.isEmpty()) return QByteArray();
|
||||
@@ -748,8 +677,7 @@ QByteArray TagReader::LoadEmbeddedArt(const QString &filename) const {
|
||||
if (flac_file && flac_file->xiphComment()) {
|
||||
TagLib::List<TagLib::FLAC::Picture*> pics = flac_file->pictureList();
|
||||
if (!pics.isEmpty()) {
|
||||
// Use the first picture in the file - this could be made cleverer and
|
||||
// pick the front cover if it's present.
|
||||
// Use the first picture in the file - this could be made cleverer and pick the front cover if it's present.
|
||||
|
||||
std::list<TagLib::FLAC::Picture*>::iterator it = pics.begin();
|
||||
TagLib::FLAC::Picture *picture = *it;
|
||||
@@ -854,7 +782,7 @@ QByteArray TagReader::LoadEmbeddedAPEArt(const TagLib::APE::ItemListMap &map) co
|
||||
|
||||
}
|
||||
|
||||
void TagReader::SetUnsyncLyricsFrame(const std::string& value, TagLib::ID3v2::Tag* tag) const {
|
||||
void TagReader::SetUnsyncLyricsFrame(const std::string &value, TagLib::ID3v2::Tag *tag) const {
|
||||
|
||||
TagLib::ByteVector id_vector("USLT");
|
||||
QVector<TagLib::ByteVector> frames_buffer;
|
||||
@@ -865,6 +793,8 @@ void TagReader::SetUnsyncLyricsFrame(const std::string& value, TagLib::ID3v2::Ta
|
||||
tag->removeFrame(tag->frameListMap()[id_vector].front());
|
||||
}
|
||||
|
||||
if (value.empty()) return;
|
||||
|
||||
// If no frames stored create empty frame
|
||||
if (frames_buffer.isEmpty()) {
|
||||
TagLib::ID3v2::UnsynchronizedLyricsFrame frame(TagLib::String::UTF8);
|
||||
@@ -873,9 +803,9 @@ void TagReader::SetUnsyncLyricsFrame(const std::string& value, TagLib::ID3v2::Ta
|
||||
}
|
||||
|
||||
// Update and add the frames
|
||||
for (int lyrics_index = 0; lyrics_index < frames_buffer.size(); lyrics_index++) {
|
||||
TagLib::ID3v2::UnsynchronizedLyricsFrame* frame = new TagLib::ID3v2::UnsynchronizedLyricsFrame(frames_buffer.at(lyrics_index));
|
||||
if (lyrics_index == 0) {
|
||||
for (int i = 0 ; i < frames_buffer.size() ; ++i) {
|
||||
TagLib::ID3v2::UnsynchronizedLyricsFrame *frame = new TagLib::ID3v2::UnsynchronizedLyricsFrame(frames_buffer.at(i));
|
||||
if (i == 0) {
|
||||
frame->setText(StdStringToTaglibString(value));
|
||||
}
|
||||
// add frame takes ownership and clears the memory
|
||||
|
||||
@@ -35,8 +35,6 @@
|
||||
|
||||
#include "tagreadermessages.pb.h"
|
||||
|
||||
class QTextCodec;
|
||||
|
||||
#ifndef USE_SYSTEM_TAGLIB
|
||||
using namespace Strawberry_TagLib;
|
||||
#endif
|
||||
@@ -52,28 +50,24 @@ class TagReader {
|
||||
explicit TagReader();
|
||||
~TagReader();
|
||||
|
||||
bool IsMediaFile(const QString &filename) const;
|
||||
pb::tagreader::SongMetadata_FileType GuessFileType(TagLib::FileRef *fileref) const;
|
||||
|
||||
void ReadFile(const QString &filename, pb::tagreader::SongMetadata *song) const;
|
||||
bool SaveFile(const QString &filename, const pb::tagreader::SongMetadata &song) const;
|
||||
|
||||
bool IsMediaFile(const QString &filename) const;
|
||||
QByteArray LoadEmbeddedArt(const QString &filename) const;
|
||||
QByteArray LoadEmbeddedAPEArt(const TagLib::APE::ItemListMap &map) const;
|
||||
|
||||
static void Decode(const TagLib::String& tag, const QTextCodec *codec, std::string *output);
|
||||
static void Decode(const QString &tag, const QTextCodec *codec, std::string *output);
|
||||
static void Decode(const TagLib::String &tag, std::string *output);
|
||||
static void Decode(const QString &tag, std::string *output);
|
||||
|
||||
void ParseOggTag(const TagLib::Ogg::FieldListMap &map, const QTextCodec *codec, QString *disc, QString *compilation, pb::tagreader::SongMetadata *song) const;
|
||||
void ParseAPETag(const TagLib::APE::ItemListMap &map, const QTextCodec *codec, QString *disc, QString *compilation, pb::tagreader::SongMetadata *song) const;
|
||||
void ParseFMPSFrame(const QString &name, const QString &value, pb::tagreader::SongMetadata *song) const;
|
||||
void ParseOggTag(const TagLib::Ogg::FieldListMap &map, QString *disc, QString *compilation, pb::tagreader::SongMetadata *song) const;
|
||||
void ParseAPETag(const TagLib::APE::ItemListMap &map, QString *disc, QString *compilation, pb::tagreader::SongMetadata *song) const;
|
||||
|
||||
void SetVorbisComments(TagLib::Ogg::XiphComment *vorbis_comments, const pb::tagreader::SongMetadata &song) const;
|
||||
void SaveAPETag(TagLib::APE::Tag *tag, const pb::tagreader::SongMetadata &song) const;
|
||||
|
||||
void SetUserTextFrame(const QString &description, const QString &value, TagLib::ID3v2::Tag *tag) const;
|
||||
void SetUserTextFrame(const std::string &description, const std::string& value, TagLib::ID3v2::Tag *tag) const;
|
||||
|
||||
void SetTextFrame(const char *id, const QString &value, TagLib::ID3v2::Tag *tag) const;
|
||||
void SetTextFrame(const char *id, const std::string &value, TagLib::ID3v2::Tag *tag) const;
|
||||
void SetUnsyncLyricsFrame(const std::string& value, TagLib::ID3v2::Tag* tag) const;
|
||||
|
||||
@@ -4,7 +4,7 @@ set(EXECUTABLE_OUTPUT_PATH ${CMAKE_BINARY_DIR})
|
||||
|
||||
set(SOURCES main.cpp tagreaderworker.cpp)
|
||||
|
||||
if(WITH_QT6)
|
||||
if(BUILD_WITH_QT6)
|
||||
qt6_wrap_cpp(MOC ${HEADERS})
|
||||
qt6_add_resources(QRC data/data.qrc)
|
||||
else()
|
||||
@@ -15,8 +15,6 @@ endif()
|
||||
link_directories(
|
||||
${GLIB_LIBRARY_DIRS}
|
||||
${TAGLIB_LIBRARY_DIRS}
|
||||
${QtCore_LIBRARY_DIRS}
|
||||
${QtNetwork_LIBRARY_DIRS}
|
||||
)
|
||||
|
||||
add_executable(strawberry-tagreader ${SOURCES} ${MOC} ${QRC})
|
||||
@@ -24,8 +22,6 @@ add_executable(strawberry-tagreader ${SOURCES} ${MOC} ${QRC})
|
||||
target_include_directories(strawberry-tagreader SYSTEM PRIVATE
|
||||
${GLIB_INCLUDE_DIRS}
|
||||
${PROTOBUF_INCLUDE_DIRS}
|
||||
${QtCore_INCLUDE_DIRS}
|
||||
${QtNetwork_INCLUDE_DIRS}
|
||||
)
|
||||
|
||||
target_include_directories(strawberry-tagreader PRIVATE
|
||||
|
||||
@@ -1,10 +1,9 @@
|
||||
name: strawberry
|
||||
version: '0.7.2+git'
|
||||
adopt-info: strawberry
|
||||
summary: music player and collection organizer
|
||||
description: |
|
||||
Strawberry is a music player and collection organizer.
|
||||
It is a fork of Clementine released in 2018 aimed at music collectors,
|
||||
audio enthusiasts and audiophiles
|
||||
It is a fork of Clementine released in 2018 aimed at music collectors and audiophiles
|
||||
|
||||
grade: stable
|
||||
confinement: strict
|
||||
@@ -43,7 +42,7 @@ parts:
|
||||
|
||||
alsa-lib:
|
||||
plugin: autotools
|
||||
source: https://www.alsa-project.org/files/pub/lib/alsa-lib-1.2.1.2.tar.bz2
|
||||
source: https://www.alsa-project.org/files/pub/lib/alsa-lib-1.2.3.tar.bz2
|
||||
configflags:
|
||||
- --prefix=/usr
|
||||
- --sysconfdir=/etc
|
||||
@@ -71,6 +70,9 @@ parts:
|
||||
after:
|
||||
- alsa-lib
|
||||
- desktop-qt5
|
||||
override-pull: |
|
||||
snapcraftctl pull
|
||||
snapcraftctl set-version "$(git describe --tags | sed 's/^v//' | cut -d "-" -f1-2)"
|
||||
override-build: |
|
||||
cmake ../src -DCMAKE_INSTALL_PREFIX=/usr
|
||||
make -j $(getconf _NPROCESSORS_ONLN)
|
||||
@@ -84,6 +86,7 @@ parts:
|
||||
- gcc
|
||||
- g++
|
||||
- protobuf-compiler
|
||||
- gettext
|
||||
- libglib2.0-dev
|
||||
- libgnutls28-dev
|
||||
- libdbus-1-dev
|
||||
@@ -96,6 +99,7 @@ parts:
|
||||
- qtbase5-dev
|
||||
- qtbase5-dev-tools
|
||||
- qtbase5-private-dev
|
||||
- qttools5-dev
|
||||
- libqt5x11extras5-dev
|
||||
- libgstreamer1.0-dev
|
||||
- libgstreamer-plugins-base1.0-dev
|
||||
@@ -154,6 +158,10 @@ parts:
|
||||
- gstreamer1.0-plugins-bad
|
||||
- gstreamer1.0-plugins-ugly
|
||||
- gstreamer1.0-libav
|
||||
- qt5-gtk-platformtheme
|
||||
- plasma-integration
|
||||
- kde-style-breeze
|
||||
- qtwayland5
|
||||
|
||||
apps:
|
||||
strawberry:
|
||||
@@ -174,7 +182,7 @@ apps:
|
||||
- x11
|
||||
- wayland
|
||||
- alsa
|
||||
- pulseaudio
|
||||
- audio-playback
|
||||
- removable-media
|
||||
- optical-drive
|
||||
- raw-usb
|
||||
|
||||
@@ -33,12 +33,10 @@ set(SOURCES
|
||||
core/thread.cpp
|
||||
core/urlhandler.cpp
|
||||
core/utilities.cpp
|
||||
core/scangiomodulepath.cpp
|
||||
core/iconloader.cpp
|
||||
core/qtsystemtrayicon.cpp
|
||||
core/standarditemiconloader.cpp
|
||||
core/systemtrayicon.cpp
|
||||
core/screensaver.cpp
|
||||
core/scopedtransaction.cpp
|
||||
core/translations.cpp
|
||||
|
||||
@@ -96,6 +94,7 @@ set(SOURCES
|
||||
playlist/playlistview.cpp
|
||||
playlist/songloaderinserter.cpp
|
||||
playlist/songplaylistitem.cpp
|
||||
playlist/dynamicplaylistcontrols.cpp
|
||||
|
||||
queue/queue.cpp
|
||||
queue/queueview.cpp
|
||||
@@ -111,6 +110,20 @@ set(SOURCES
|
||||
playlistparsers/xmlparser.cpp
|
||||
playlistparsers/xspfparser.cpp
|
||||
|
||||
smartplaylists/playlistgenerator.cpp
|
||||
smartplaylists/playlistgeneratorinserter.cpp
|
||||
smartplaylists/playlistquerygenerator.cpp
|
||||
smartplaylists/smartplaylistquerywizardplugin.cpp
|
||||
smartplaylists/smartplaylistsearch.cpp
|
||||
smartplaylists/smartplaylistsearchpreview.cpp
|
||||
smartplaylists/smartplaylistsearchterm.cpp
|
||||
smartplaylists/smartplaylistsearchtermwidget.cpp
|
||||
smartplaylists/smartplaylistsmodel.cpp
|
||||
smartplaylists/smartplaylistsviewcontainer.cpp
|
||||
smartplaylists/smartplaylistsview.cpp
|
||||
smartplaylists/smartplaylistwizard.cpp
|
||||
smartplaylists/smartplaylistwizardplugin.cpp
|
||||
|
||||
covermanager/albumcovermanager.cpp
|
||||
covermanager/albumcovermanagerlist.cpp
|
||||
covermanager/albumcoverloader.cpp
|
||||
@@ -169,6 +182,9 @@ set(SOURCES
|
||||
dialogs/trackselectiondialog.cpp
|
||||
dialogs/addstreamdialog.cpp
|
||||
dialogs/userpassdialog.cpp
|
||||
dialogs/deleteconfirmationdialog.cpp
|
||||
dialogs/lastfmimportdialog.cpp
|
||||
dialogs/snapdialog.cpp
|
||||
|
||||
widgets/autoexpandingtreeview.cpp
|
||||
widgets/busyindicator.cpp
|
||||
@@ -192,6 +208,7 @@ set(SOURCES
|
||||
widgets/tracksliderpopup.cpp
|
||||
widgets/tracksliderslider.cpp
|
||||
widgets/loginstatewidget.cpp
|
||||
widgets/ratingwidget.cpp
|
||||
|
||||
osd/osdbase.cpp
|
||||
osd/osdpretty.cpp
|
||||
@@ -222,6 +239,7 @@ set(SOURCES
|
||||
scrobbler/lastfmscrobbler.cpp
|
||||
scrobbler/librefmscrobbler.cpp
|
||||
scrobbler/listenbrainzscrobbler.cpp
|
||||
scrobbler/lastfmimport.cpp
|
||||
|
||||
organize/organize.cpp
|
||||
organize/organizeformat.cpp
|
||||
@@ -297,6 +315,7 @@ set(HEADERS
|
||||
playlist/playlistitemmimedata.h
|
||||
playlist/songloaderinserter.h
|
||||
playlist/songmimedata.h
|
||||
playlist/dynamicplaylistcontrols.h
|
||||
|
||||
queue/queue.h
|
||||
queue/queueview.h
|
||||
@@ -310,6 +329,18 @@ set(HEADERS
|
||||
playlistparsers/plsparser.h
|
||||
playlistparsers/xspfparser.h
|
||||
|
||||
smartplaylists/playlistgenerator.h
|
||||
smartplaylists/playlistgeneratorinserter.h
|
||||
smartplaylists/playlistgeneratormimedata.h
|
||||
smartplaylists/smartplaylistquerywizardplugin.h
|
||||
smartplaylists/smartplaylistsearchpreview.h
|
||||
smartplaylists/smartplaylistsearchtermwidget.h
|
||||
smartplaylists/smartplaylistsmodel.h
|
||||
smartplaylists/smartplaylistsviewcontainer.h
|
||||
smartplaylists/smartplaylistsview.h
|
||||
smartplaylists/smartplaylistwizard.h
|
||||
smartplaylists/smartplaylistwizardplugin.h
|
||||
|
||||
covermanager/albumcovermanager.h
|
||||
covermanager/albumcovermanagerlist.h
|
||||
covermanager/albumcoverloader.h
|
||||
@@ -367,6 +398,9 @@ set(HEADERS
|
||||
dialogs/trackselectiondialog.h
|
||||
dialogs/addstreamdialog.h
|
||||
dialogs/userpassdialog.h
|
||||
dialogs/deleteconfirmationdialog.h
|
||||
dialogs/lastfmimportdialog.h
|
||||
dialogs/snapdialog.h
|
||||
|
||||
widgets/autoexpandingtreeview.h
|
||||
widgets/busyindicator.h
|
||||
@@ -390,6 +424,7 @@ set(HEADERS
|
||||
widgets/tracksliderslider.h
|
||||
widgets/loginstatewidget.h
|
||||
widgets/qsearchfield.h
|
||||
widgets/ratingwidget.h
|
||||
|
||||
osd/osdbase.h
|
||||
osd/osdpretty.h
|
||||
@@ -418,6 +453,7 @@ set(HEADERS
|
||||
scrobbler/lastfmscrobbler.h
|
||||
scrobbler/librefmscrobbler.h
|
||||
scrobbler/listenbrainzscrobbler.h
|
||||
scrobbler/lastfmimport.h
|
||||
|
||||
organize/organize.h
|
||||
organize/organizedialog.h
|
||||
@@ -438,9 +474,17 @@ set(UI
|
||||
playlist/playlistlistcontainer.ui
|
||||
playlist/playlistsaveoptionsdialog.ui
|
||||
playlist/playlistsequence.ui
|
||||
playlist/dynamicplaylistcontrols.ui
|
||||
|
||||
queue/queueview.ui
|
||||
|
||||
smartplaylists/smartplaylistquerysearchpage.ui
|
||||
smartplaylists/smartplaylistquerysortpage.ui
|
||||
smartplaylists/smartplaylistsearchpreview.ui
|
||||
smartplaylists/smartplaylistsearchtermwidget.ui
|
||||
smartplaylists/smartplaylistsviewcontainer.ui
|
||||
smartplaylists/smartplaylistwizardfinishpage.ui
|
||||
|
||||
covermanager/albumcoverexport.ui
|
||||
covermanager/albumcovermanager.ui
|
||||
covermanager/albumcoversearcher.ui
|
||||
@@ -470,6 +514,8 @@ set(UI
|
||||
dialogs/trackselectiondialog.ui
|
||||
dialogs/addstreamdialog.ui
|
||||
dialogs/userpassdialog.ui
|
||||
dialogs/lastfmimportdialog.ui
|
||||
dialogs/snapdialog.ui
|
||||
|
||||
widgets/trackslider.ui
|
||||
widgets/fileview.ui
|
||||
@@ -513,8 +559,8 @@ if(HAVE_GLOBALSHORTCUTS)
|
||||
SOURCES globalshortcuts/globalshortcut-x11.cpp
|
||||
)
|
||||
optional_source(HAVE_DBUS
|
||||
SOURCES globalshortcuts/globalshortcutbackend-gsd.cpp
|
||||
HEADERS globalshortcuts/globalshortcutbackend-gsd.h
|
||||
SOURCES globalshortcuts/globalshortcutbackend-gsd.cpp globalshortcuts/globalshortcutbackend-kde.cpp
|
||||
HEADERS globalshortcuts/globalshortcutbackend-gsd.h globalshortcuts/globalshortcutbackend-kde.h
|
||||
)
|
||||
optional_source(WIN32
|
||||
SOURCES globalshortcuts/globalshortcut-win.cpp
|
||||
@@ -552,7 +598,7 @@ if(UNIX AND HAVE_DBUS)
|
||||
file(MAKE_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/dbus)
|
||||
|
||||
optional_source(HAVE_DBUS
|
||||
SOURCES core/mpris.cpp core/mpris2.cpp core/dbusscreensaver.cpp
|
||||
SOURCES core/mpris.cpp core/mpris2.cpp
|
||||
HEADERS core/mpris.h core/mpris2.h
|
||||
)
|
||||
optional_source(HAVE_UDISKS2
|
||||
@@ -560,7 +606,7 @@ if(UNIX AND HAVE_DBUS)
|
||||
HEADERS device/udisks2lister.h
|
||||
)
|
||||
|
||||
if (WITH_QT6)
|
||||
if (BUILD_WITH_QT6)
|
||||
|
||||
# MPRIS 2.0 DBUS interfaces
|
||||
qt6_add_dbus_adaptor(SOURCES
|
||||
@@ -588,6 +634,14 @@ if(UNIX AND HAVE_DBUS)
|
||||
dbus/org.gnome.SettingsDaemon.MediaKeys.xml
|
||||
dbus/gnomesettingsdaemon)
|
||||
|
||||
# org.kde.KGlobalAccel interface
|
||||
qt6_add_dbus_interface(SOURCES
|
||||
dbus/org.kde.KGlobalAccel.xml
|
||||
dbus/kglobalaccel)
|
||||
qt6_add_dbus_interface(SOURCES
|
||||
dbus/org.kde.KGlobalAccel.Component.xml
|
||||
dbus/kglobalaccelcomponent)
|
||||
|
||||
else()
|
||||
|
||||
# MPRIS 2.0 DBUS interfaces
|
||||
@@ -616,6 +670,14 @@ if(UNIX AND HAVE_DBUS)
|
||||
dbus/org.gnome.SettingsDaemon.MediaKeys.xml
|
||||
dbus/gnomesettingsdaemon)
|
||||
|
||||
# org.kde.KGlobalAccel interface
|
||||
qt5_add_dbus_interface(SOURCES
|
||||
dbus/org.kde.KGlobalAccel.xml
|
||||
dbus/kglobalaccel)
|
||||
qt5_add_dbus_interface(SOURCES
|
||||
dbus/org.kde.KGlobalAccel.Component.xml
|
||||
dbus/kglobalaccelcomponent)
|
||||
|
||||
endif()
|
||||
|
||||
# org.freedesktop.Avahi.Server interface
|
||||
@@ -657,7 +719,7 @@ if(UNIX AND HAVE_DBUS)
|
||||
PROPERTIES NO_NAMESPACE dbus/udisks2drive INCLUDE dbus/metatypes.h)
|
||||
set_source_files_properties(dbus/org.freedesktop.UDisks2.Job.xml
|
||||
PROPERTIES NO_NAMESPACE dbus/udisks2job INCLUDE dbus/metatypes.h)
|
||||
if(WITH_QT6)
|
||||
if(BUILD_WITH_QT6)
|
||||
qt6_add_dbus_interface(SOURCES
|
||||
dbus/org.freedesktop.DBus.ObjectManager.xml
|
||||
dbus/objectmanager)
|
||||
@@ -817,7 +879,6 @@ optional_source(APPLE
|
||||
core/mac_utilities.mm
|
||||
core/mac_startup.mm
|
||||
core/macsystemtrayicon.mm
|
||||
core/macscreensaver.cpp
|
||||
core/macfslistener.mm
|
||||
osd/osdmac.mm
|
||||
widgets/qsearchfield_mac.mm
|
||||
@@ -856,13 +917,17 @@ optional_source(HAVE_SUBSONIC
|
||||
subsonic/subsonicurlhandler.cpp
|
||||
subsonic/subsonicbaserequest.cpp
|
||||
subsonic/subsonicrequest.cpp
|
||||
subsonic/subsonicscrobblerequest.cpp
|
||||
settings/subsonicsettingspage.cpp
|
||||
scrobbler/subsonicscrobbler.cpp
|
||||
HEADERS
|
||||
subsonic/subsonicservice.h
|
||||
subsonic/subsonicurlhandler.h
|
||||
subsonic/subsonicbaserequest.h
|
||||
subsonic/subsonicrequest.h
|
||||
subsonic/subsonicscrobblerequest.h
|
||||
settings/subsonicsettingspage.h
|
||||
scrobbler/subsonicscrobbler.h
|
||||
UI
|
||||
settings/subsonicsettingspage.ui
|
||||
)
|
||||
@@ -890,6 +955,27 @@ optional_source(HAVE_TIDAL
|
||||
settings/tidalsettingspage.ui
|
||||
)
|
||||
|
||||
optional_source(HAVE_QOBUZ
|
||||
SOURCES
|
||||
qobuz/qobuzservice.cpp
|
||||
qobuz/qobuzurlhandler.cpp
|
||||
qobuz/qobuzbaserequest.cpp
|
||||
qobuz/qobuzrequest.cpp
|
||||
qobuz/qobuzstreamurlrequest.cpp
|
||||
qobuz/qobuzfavoriterequest.cpp
|
||||
settings/qobuzsettingspage.cpp
|
||||
HEADERS
|
||||
qobuz/qobuzservice.h
|
||||
qobuz/qobuzurlhandler.h
|
||||
qobuz/qobuzbaserequest.h
|
||||
qobuz/qobuzrequest.h
|
||||
qobuz/qobuzstreamurlrequest.h
|
||||
qobuz/qobuzfavoriterequest.h
|
||||
settings/qobuzsettingspage.h
|
||||
UI
|
||||
settings/qobuzsettingspage.ui
|
||||
)
|
||||
|
||||
# Moodbar
|
||||
optional_source(HAVE_MOODBAR
|
||||
SOURCES
|
||||
@@ -915,7 +1001,7 @@ optional_source(HAVE_MOODBAR
|
||||
configure_file(${CMAKE_CURRENT_SOURCE_DIR}/config.h.in ${CMAKE_CURRENT_BINARY_DIR}/config.h)
|
||||
configure_file(${CMAKE_CURRENT_SOURCE_DIR}/version.h.in ${CMAKE_CURRENT_BINARY_DIR}/version.h)
|
||||
|
||||
if(WITH_QT6)
|
||||
if(BUILD_WITH_QT6)
|
||||
qt6_wrap_cpp(MOC ${HEADERS})
|
||||
qt6_wrap_ui(UIC ${UI})
|
||||
qt6_add_resources(QRC ${RESOURCES})
|
||||
@@ -963,10 +1049,10 @@ link_directories(
|
||||
${GNUTLS_LIBRARY_DIRS}
|
||||
${SQLITE_LIBRARY_DIRS}
|
||||
${TAGLIB_LIBRARY_DIRS}
|
||||
${QT_LIBRARY_DIRS}
|
||||
${SINGLEAPPLICATION_LIBRARY_DIRS}
|
||||
${SINGLECOREAPPLICATION_LIBRARY_DIRS}
|
||||
${QTSPARKLE_LIBRARY_DIRS}
|
||||
${Iconv_LIBRARY_DIRS}
|
||||
)
|
||||
|
||||
if(HAVE_ALSA)
|
||||
@@ -1013,7 +1099,8 @@ if(HAVE_AUDIOCD)
|
||||
endif(HAVE_AUDIOCD)
|
||||
|
||||
if(HAVE_LIBGPOD)
|
||||
link_directories(${LIBGPOD_LIBRARIES})
|
||||
link_directories(${LIBGPOD_LIBRARY_DIRS})
|
||||
link_directories(${GDK_PIXBUF_LIBRARY_DIRS})
|
||||
endif(HAVE_LIBGPOD)
|
||||
|
||||
if(HAVE_LIBMTP)
|
||||
@@ -1036,7 +1123,6 @@ target_include_directories(strawberry_lib SYSTEM PUBLIC
|
||||
${GOBJECT_INCLUDE_DIRS}
|
||||
${GNUTLS_INCLUDE_DIRS}
|
||||
${SQLITE_INCLUDE_DIRS}
|
||||
${QT_INCLUDE_DIRS}
|
||||
)
|
||||
|
||||
target_include_directories(strawberry_lib PUBLIC
|
||||
@@ -1063,6 +1149,7 @@ target_link_libraries(strawberry_lib PUBLIC
|
||||
${SINGLEAPPLICATION_LIBRARIES}
|
||||
${SINGLECOREAPPLICATION_LIBRARIES}
|
||||
${QTSPARKLE_LIBRARIES}
|
||||
${Iconv_LIBRARY}
|
||||
libstrawberry-common
|
||||
libstrawberry-tagreader
|
||||
)
|
||||
@@ -1131,8 +1218,8 @@ if(HAVE_AUDIOCD)
|
||||
endif(HAVE_AUDIOCD)
|
||||
|
||||
if(HAVE_LIBGPOD)
|
||||
target_include_directories(strawberry_lib SYSTEM PRIVATE ${LIBGPOD_INCLUDE_DIRS})
|
||||
target_link_libraries(strawberry_lib PRIVATE ${LIBGPOD_LIBRARIES})
|
||||
target_include_directories(strawberry_lib SYSTEM PRIVATE ${LIBGPOD_INCLUDE_DIRS} ${GDK_PIXBUF_INCLUDE_DIRS})
|
||||
target_link_libraries(strawberry_lib PRIVATE ${LIBGPOD_LIBRARIES} ${GDK_PIXBUF_LIBRARIES})
|
||||
endif(HAVE_LIBGPOD)
|
||||
|
||||
if(HAVE_LIBMTP)
|
||||
|
||||
@@ -40,7 +40,7 @@
|
||||
|
||||
// INSTRUCTIONS Base2D
|
||||
// 1. do anything that depends on height() in init(), Base2D will call it before you are shown
|
||||
// 2. otherwise you can use the constructor to initialise things
|
||||
// 2. otherwise you can use the constructor to initialize things
|
||||
// 3. reimplement analyze(), and paint to canvas(), Base2D will update the widget when you return control to it
|
||||
// 4. if you want to manipulate the scope, reimplement transform()
|
||||
// 5. for convenience <vector> <qpixmap.h> <qwdiget.h> are pre-included
|
||||
|
||||
@@ -136,7 +136,7 @@ void AnalyzerContainer::ChangeAnalyzer(int id) {
|
||||
QObject *instance = analyzer_types_[id]->newInstance(Q_ARG(QWidget*, this));
|
||||
|
||||
if (!instance) {
|
||||
qLog(Warning) << "Couldn't initialise a new" << analyzer_types_[id]->className();
|
||||
qLog(Warning) << "Couldn't initialize a new" << analyzer_types_[id]->className();
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
@@ -92,7 +92,7 @@ class RainbowAnalyzer : public Analyzer::Base {
|
||||
}
|
||||
|
||||
private:
|
||||
// "constants" that get initialised in the constructor
|
||||
// "constants" that get initialized in the constructor
|
||||
float band_scale_[kRainbowBands];
|
||||
QPen colors_[kRainbowBands];
|
||||
|
||||
|
||||
@@ -40,6 +40,7 @@
|
||||
#include "collectionbackend.h"
|
||||
#include "collectionmodel.h"
|
||||
#include "playlist/playlistmanager.h"
|
||||
#include "scrobbler/lastfmimport.h"
|
||||
|
||||
const char *SCollection::kSongsTable = "songs";
|
||||
const char *SCollection::kDirsTable = "directories";
|
||||
@@ -106,10 +107,12 @@ void SCollection::Init() {
|
||||
connect(watcher_, SIGNAL(SubdirsDiscovered(SubdirectoryList)), backend_, SLOT(AddOrUpdateSubdirs(SubdirectoryList)));
|
||||
connect(watcher_, SIGNAL(SubdirsMTimeUpdated(SubdirectoryList)), backend_, SLOT(AddOrUpdateSubdirs(SubdirectoryList)));
|
||||
connect(watcher_, SIGNAL(CompilationsNeedUpdating()), backend_, SLOT(UpdateCompilations()));
|
||||
connect(backend_, SIGNAL(SongsStatisticsChanged(SongList)), SLOT(SongsStatisticsChanged(SongList)));
|
||||
connect(app_->playlist_manager(), SIGNAL(CurrentSongChanged(Song)), SLOT(CurrentSongChanged(Song)));
|
||||
connect(app_->player(), SIGNAL(Stopped()), SLOT(Stopped()));
|
||||
|
||||
connect(app_->lastfm_import(), SIGNAL(UpdateLastPlayed(QString, QString, QString, int)), backend_, SLOT(UpdateLastPlayed(QString, QString, QString, int)));
|
||||
connect(app_->lastfm_import(), SIGNAL(UpdatePlayCount(QString, QString, int)), backend_, SLOT(UpdatePlayCount(QString, QString, int)));
|
||||
|
||||
// This will start the watcher checking for updates
|
||||
backend_->LoadDirectoriesAsync();
|
||||
|
||||
@@ -179,7 +182,3 @@ void SCollection::CurrentSongChanged(const Song &song) { // FIXME
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
void SCollection::SongsStatisticsChanged(const SongList &songs) {
|
||||
Q_UNUSED(songs);
|
||||
}
|
||||
|
||||
@@ -79,7 +79,6 @@ class SCollection : public QObject {
|
||||
void IncrementalScan();
|
||||
|
||||
void CurrentSongChanged(const Song &song);
|
||||
void SongsStatisticsChanged(const SongList& songs);
|
||||
void Stopped();
|
||||
|
||||
signals:
|
||||
|
||||
@@ -42,8 +42,10 @@
|
||||
#include <QSqlDatabase>
|
||||
#include <QSqlQuery>
|
||||
|
||||
#include "core/logging.h"
|
||||
#include "core/database.h"
|
||||
#include "core/scopedtransaction.h"
|
||||
#include "smartplaylists/smartplaylistsearch.h"
|
||||
|
||||
#include "directory.h"
|
||||
#include "collectionbackend.h"
|
||||
@@ -108,15 +110,15 @@ void CollectionBackend::UpdateTotalAlbumCountAsync() {
|
||||
metaObject()->invokeMethod(this, "UpdateTotalAlbumCount", Qt::QueuedConnection);
|
||||
}
|
||||
|
||||
void CollectionBackend::IncrementPlayCountAsync(int id) {
|
||||
void CollectionBackend::IncrementPlayCountAsync(const int id) {
|
||||
metaObject()->invokeMethod(this, "IncrementPlayCount", Qt::QueuedConnection, Q_ARG(int, id));
|
||||
}
|
||||
|
||||
void CollectionBackend::IncrementSkipCountAsync(int id, float progress) {
|
||||
void CollectionBackend::IncrementSkipCountAsync(const int id, const float progress) {
|
||||
metaObject()->invokeMethod(this, "IncrementSkipCount", Qt::QueuedConnection, Q_ARG(int, id), Q_ARG(float, progress));
|
||||
}
|
||||
|
||||
void CollectionBackend::ResetStatisticsAsync(int id) {
|
||||
void CollectionBackend::ResetStatisticsAsync(const int id) {
|
||||
metaObject()->invokeMethod(this, "ResetStatistics", Qt::QueuedConnection, Q_ARG(int, id));
|
||||
}
|
||||
|
||||
@@ -133,7 +135,7 @@ void CollectionBackend::LoadDirectories() {
|
||||
|
||||
}
|
||||
|
||||
void CollectionBackend::ChangeDirPath(int id, const QString &old_path, const QString &new_path) {
|
||||
void CollectionBackend::ChangeDirPath(const int id, const QString &old_path, const QString &new_path) {
|
||||
|
||||
QMutexLocker l(db_->Mutex());
|
||||
QSqlDatabase db(db_->Connect());
|
||||
@@ -201,7 +203,7 @@ DirectoryList CollectionBackend::GetAllDirectories() {
|
||||
|
||||
}
|
||||
|
||||
SubdirectoryList CollectionBackend::SubdirsInDirectory(int id) {
|
||||
SubdirectoryList CollectionBackend::SubdirsInDirectory(const int id) {
|
||||
|
||||
QMutexLocker l(db_->Mutex());
|
||||
QSqlDatabase db = db_->Connect();
|
||||
@@ -209,7 +211,7 @@ SubdirectoryList CollectionBackend::SubdirsInDirectory(int id) {
|
||||
|
||||
}
|
||||
|
||||
SubdirectoryList CollectionBackend::SubdirsInDirectory(int id, QSqlDatabase &db) {
|
||||
SubdirectoryList CollectionBackend::SubdirsInDirectory(const int id, QSqlDatabase &db) {
|
||||
|
||||
QSqlQuery q(db);
|
||||
q.prepare(QString("SELECT path, mtime FROM %1 WHERE directory_id = :dir").arg(subdirs_table_));
|
||||
@@ -327,7 +329,7 @@ void CollectionBackend::RemoveDirectory(const Directory &dir) {
|
||||
|
||||
}
|
||||
|
||||
SongList CollectionBackend::FindSongsInDirectory(int id) {
|
||||
SongList CollectionBackend::FindSongsInDirectory(const int id) {
|
||||
|
||||
QMutexLocker l(db_->Mutex());
|
||||
QSqlDatabase db(db_->Connect());
|
||||
@@ -581,7 +583,7 @@ void CollectionBackend::DeleteSongs(const SongList &songs) {
|
||||
|
||||
}
|
||||
|
||||
void CollectionBackend::MarkSongsUnavailable(const SongList &songs, bool unavailable) {
|
||||
void CollectionBackend::MarkSongsUnavailable(const SongList &songs, const bool unavailable) {
|
||||
|
||||
QMutexLocker l(db_->Mutex());
|
||||
QSqlDatabase db(db_->Connect());
|
||||
@@ -700,7 +702,7 @@ SongList CollectionBackend::ExecCollectionQuery(CollectionQuery *query) {
|
||||
|
||||
}
|
||||
|
||||
Song CollectionBackend::GetSongById(int id) {
|
||||
Song CollectionBackend::GetSongById(const int id) {
|
||||
QMutexLocker l(db_->Mutex());
|
||||
QSqlDatabase db(db_->Connect());
|
||||
return GetSongById(id, db);
|
||||
@@ -749,7 +751,7 @@ SongList CollectionBackend::GetSongsByForeignId(const QStringList &ids, const QS
|
||||
|
||||
}
|
||||
|
||||
Song CollectionBackend::GetSongById(int id, QSqlDatabase &db) {
|
||||
Song CollectionBackend::GetSongById(const int id, QSqlDatabase &db) {
|
||||
SongList list = GetSongsById(QStringList() << QString::number(id), db);
|
||||
if (list.isEmpty()) return Song();
|
||||
return list.first();
|
||||
@@ -851,7 +853,11 @@ Song CollectionBackend::GetSongBySongId(const QString &song_id, QSqlDatabase &db
|
||||
|
||||
SongList CollectionBackend::GetSongsBySongId(const QStringList &song_ids, QSqlDatabase &db) {
|
||||
|
||||
QString in = song_ids.join(",");
|
||||
QStringList song_ids2;
|
||||
for (const QString &song_id : song_ids) {
|
||||
song_ids2 << "'" + song_id + "'";
|
||||
}
|
||||
QString in = song_ids2.join(",");
|
||||
|
||||
QSqlQuery q(db);
|
||||
q.prepare(QString("SELECT ROWID, " + Song::kColumnSpec + " FROM %1 WHERE SONG_ID IN (%2)").arg(songs_table_, in));
|
||||
@@ -999,10 +1005,8 @@ void CollectionBackend::UpdateCompilations(QSqlQuery &find_song, QSqlQuery &upda
|
||||
|
||||
CollectionBackend::AlbumList CollectionBackend::GetAlbums(const QString &artist, const bool compilation_required, const QueryOptions &opt) {
|
||||
|
||||
AlbumList ret;
|
||||
|
||||
CollectionQuery query(opt);
|
||||
query.SetColumnSpec("album, artist, albumartist, compilation, compilation_detected, art_automatic, art_manual, url");
|
||||
query.SetColumnSpec("url, artist, albumartist, album, compilation_effective, art_automatic, art_manual");
|
||||
query.SetOrderBy("album");
|
||||
|
||||
if (compilation_required) {
|
||||
@@ -1015,20 +1019,20 @@ CollectionBackend::AlbumList CollectionBackend::GetAlbums(const QString &artist,
|
||||
|
||||
{
|
||||
QMutexLocker l(db_->Mutex());
|
||||
if (!ExecQuery(&query)) return ret;
|
||||
if (!ExecQuery(&query)) return AlbumList();
|
||||
}
|
||||
|
||||
QString last_album;
|
||||
QString last_artist;
|
||||
QString last_album_artist;
|
||||
QMap<QString, Album> albums;
|
||||
while (query.Next()) {
|
||||
bool is_compilation = query.Value(3).toBool() | query.Value(4).toBool();
|
||||
bool is_compilation = query.Value(4).toBool();
|
||||
|
||||
Album info;
|
||||
info.artist = is_compilation ? QString() : query.Value(1).toString();
|
||||
info.album_artist = is_compilation ? QString() : query.Value(2).toString();
|
||||
info.album_name = query.Value(0).toString();
|
||||
info.first_url = QUrl::fromEncoded(query.Value(7).toByteArray());
|
||||
info.first_url = QUrl::fromEncoded(query.Value(0).toByteArray());
|
||||
if (!is_compilation) {
|
||||
info.artist = query.Value(1).toString();
|
||||
info.album_artist = query.Value(2).toString();
|
||||
}
|
||||
info.album_name = query.Value(3).toString();
|
||||
|
||||
QString art_automatic = query.Value(5).toString();
|
||||
if (art_automatic.contains(QRegularExpression("..+:.*"))) {
|
||||
@@ -1046,17 +1050,23 @@ CollectionBackend::AlbumList CollectionBackend::GetAlbums(const QString &artist,
|
||||
info.art_manual = QUrl::fromLocalFile(art_manual);
|
||||
}
|
||||
|
||||
if ((info.artist == last_artist || info.album_artist == last_album_artist) && info.album_name == last_album)
|
||||
continue;
|
||||
QString effective_albumartist = info.album_artist.isEmpty() ? info.artist : info.album_artist;
|
||||
QString key;
|
||||
if (!effective_albumartist.isEmpty()) {
|
||||
key.append(effective_albumartist);
|
||||
}
|
||||
if (!info.album_name.isEmpty()) {
|
||||
if (!key.isEmpty()) key.append("-");
|
||||
key.append(info.album_name);
|
||||
}
|
||||
|
||||
ret << info;
|
||||
if (key.isEmpty()) continue;
|
||||
|
||||
if (!albums.contains(key)) albums.insert(key, info);
|
||||
|
||||
last_album = info.album_name;
|
||||
last_artist = info.artist;
|
||||
last_album_artist = info.album_artist;
|
||||
}
|
||||
|
||||
return ret;
|
||||
return albums.values();
|
||||
|
||||
}
|
||||
|
||||
@@ -1163,7 +1173,7 @@ void CollectionBackend::UpdateManualAlbumArt(const QString &artist, const QStrin
|
||||
|
||||
}
|
||||
|
||||
void CollectionBackend::ForceCompilation(const QString &album, const QList<QString> &artists, bool on) {
|
||||
void CollectionBackend::ForceCompilation(const QString &album, const QList<QString> &artists, const bool on) {
|
||||
|
||||
QMutexLocker l(db_->Mutex());
|
||||
QSqlDatabase db(db_->Connect());
|
||||
@@ -1174,7 +1184,7 @@ void CollectionBackend::ForceCompilation(const QString &album, const QList<QStri
|
||||
CollectionQuery query;
|
||||
query.SetColumnSpec("ROWID, " + Song::kColumnSpec);
|
||||
query.AddWhere("album", album);
|
||||
if (!artist.isNull() && !artist.isEmpty()) query.AddWhere("artist", artist);
|
||||
if (!artist.isEmpty()) query.AddWhere("artist", artist);
|
||||
|
||||
if (!ExecQuery(&query)) return;
|
||||
|
||||
@@ -1219,7 +1229,7 @@ bool CollectionBackend::ExecQuery(CollectionQuery *q) {
|
||||
return !db_->CheckErrors(q->Exec(db_->Connect(), songs_table_, fts_table_));
|
||||
}
|
||||
|
||||
void CollectionBackend::IncrementPlayCount(int id) {
|
||||
void CollectionBackend::IncrementPlayCount(const int id) {
|
||||
|
||||
if (id == -1) return;
|
||||
|
||||
@@ -1238,7 +1248,7 @@ void CollectionBackend::IncrementPlayCount(int id) {
|
||||
|
||||
}
|
||||
|
||||
void CollectionBackend::IncrementSkipCount(int id, float progress) {
|
||||
void CollectionBackend::IncrementSkipCount(const int id, const float progress) {
|
||||
|
||||
Q_UNUSED(progress);
|
||||
|
||||
@@ -1258,7 +1268,7 @@ void CollectionBackend::IncrementSkipCount(int id, float progress) {
|
||||
|
||||
}
|
||||
|
||||
void CollectionBackend::ResetStatistics(int id) {
|
||||
void CollectionBackend::ResetStatistics(const int id) {
|
||||
|
||||
if (id == -1) return;
|
||||
|
||||
@@ -1297,3 +1307,152 @@ void CollectionBackend::DeleteAll() {
|
||||
emit DatabaseReset();
|
||||
|
||||
}
|
||||
|
||||
SongList CollectionBackend::FindSongs(const SmartPlaylistSearch &search) {
|
||||
|
||||
QMutexLocker l(db_->Mutex());
|
||||
QSqlDatabase db(db_->Connect());
|
||||
|
||||
// Build the query
|
||||
QString sql = search.ToSql(songs_table());
|
||||
|
||||
// Run the query
|
||||
SongList ret;
|
||||
QSqlQuery query(db);
|
||||
query.prepare(sql);
|
||||
query.exec();
|
||||
if (db_->CheckErrors(query)) return ret;
|
||||
|
||||
// Read the results
|
||||
while (query.next()) {
|
||||
Song song;
|
||||
song.InitFromQuery(query, true);
|
||||
ret << song;
|
||||
}
|
||||
return ret;
|
||||
|
||||
}
|
||||
|
||||
SongList CollectionBackend::GetAllSongs() {
|
||||
|
||||
// Get all the songs!
|
||||
return FindSongs(SmartPlaylistSearch(SmartPlaylistSearch::Type_All, SmartPlaylistSearch::TermList(), SmartPlaylistSearch::Sort_FieldAsc, SmartPlaylistSearchTerm::Field_Artist, -1));
|
||||
|
||||
}
|
||||
|
||||
SongList CollectionBackend::GetSongsBy(const QString &artist, const QString &album, const QString &title) {
|
||||
|
||||
QMutexLocker l(db_->Mutex());
|
||||
QSqlDatabase db(db_->Connect());
|
||||
|
||||
SongList songs;
|
||||
QSqlQuery q(db);
|
||||
if (album.isEmpty()) {
|
||||
q.prepare(QString("SELECT ROWID, " + Song::kColumnSpec + " FROM %1 WHERE artist = :artist COLLATE NOCASE AND title = :title COLLATE NOCASE").arg(songs_table_));
|
||||
}
|
||||
else {
|
||||
q.prepare(QString("SELECT ROWID, " + Song::kColumnSpec + " FROM %1 WHERE artist = :artist COLLATE NOCASE AND album = :album COLLATE NOCASE AND title = :title COLLATE NOCASE").arg(songs_table_));
|
||||
}
|
||||
q.bindValue(":artist", artist);
|
||||
if (!album.isEmpty()) q.bindValue(":album", album);
|
||||
q.bindValue(":title", title);
|
||||
q.exec();
|
||||
if (db_->CheckErrors(q)) return SongList();
|
||||
while (q.next()) {
|
||||
Song song(source_);
|
||||
song.InitFromQuery(q, true);
|
||||
songs << song;
|
||||
}
|
||||
|
||||
return songs;
|
||||
|
||||
}
|
||||
|
||||
void CollectionBackend::UpdateLastPlayed(const QString &artist, const QString &album, const QString &title, const int lastplayed) {
|
||||
|
||||
SongList songs = GetSongsBy(artist, album, title);
|
||||
if (songs.isEmpty()) {
|
||||
qLog(Debug) << "Could not find a matching song in the database for" << artist << album << title;
|
||||
return;
|
||||
}
|
||||
|
||||
QMutexLocker l(db_->Mutex());
|
||||
QSqlDatabase db(db_->Connect());
|
||||
|
||||
for (const Song &song : songs) {
|
||||
QSqlQuery q(db);
|
||||
q.prepare(QString("UPDATE %1 SET lastplayed = :lastplayed WHERE ROWID = :id").arg(songs_table_));
|
||||
q.bindValue(":lastplayed", lastplayed);
|
||||
q.bindValue(":id", song.id());
|
||||
q.exec();
|
||||
if (db_->CheckErrors(q)) continue;
|
||||
}
|
||||
|
||||
emit SongsStatisticsChanged(SongList() << songs);
|
||||
|
||||
}
|
||||
|
||||
void CollectionBackend::UpdatePlayCount(const QString &artist, const QString &title, const int playcount) {
|
||||
|
||||
SongList songs = GetSongsBy(artist, QString(), title);
|
||||
if (songs.isEmpty()) {
|
||||
qLog(Debug) << "Could not find a matching song in the database for" << artist << title;
|
||||
return;
|
||||
}
|
||||
|
||||
QMutexLocker l(db_->Mutex());
|
||||
QSqlDatabase db(db_->Connect());
|
||||
|
||||
for (const Song &song : songs) {
|
||||
QSqlQuery q(db);
|
||||
q.prepare(QString("UPDATE %1 SET playcount = :playcount WHERE ROWID = :id").arg(songs_table_));
|
||||
q.bindValue(":playcount", playcount);
|
||||
q.bindValue(":id", song.id());
|
||||
q.exec();
|
||||
if (db_->CheckErrors(q)) continue;
|
||||
}
|
||||
|
||||
emit SongsStatisticsChanged(SongList() << songs);
|
||||
|
||||
}
|
||||
|
||||
void CollectionBackend::UpdateSongRating(const int id, const float rating) {
|
||||
|
||||
if (id == -1) return;
|
||||
|
||||
QList<int> id_list;
|
||||
id_list << id;
|
||||
UpdateSongsRating(id_list, rating);
|
||||
|
||||
}
|
||||
|
||||
void CollectionBackend::UpdateSongsRating(const QList<int> &id_list, const float rating) {
|
||||
|
||||
if (id_list.isEmpty()) return;
|
||||
|
||||
QMutexLocker l(db_->Mutex());
|
||||
QSqlDatabase db(db_->Connect());
|
||||
|
||||
QStringList id_str_list;
|
||||
for (int i : id_list) {
|
||||
id_str_list << QString::number(i);
|
||||
}
|
||||
QString ids = id_str_list.join(",");
|
||||
QSqlQuery q(db);
|
||||
q.prepare(QString("UPDATE %1 SET rating = :rating WHERE ROWID IN (%2)").arg(songs_table_, ids));
|
||||
q.bindValue(":rating", rating);
|
||||
q.exec();
|
||||
if (db_->CheckErrors(q)) return;
|
||||
SongList new_song_list = GetSongsById(id_str_list, db);
|
||||
emit SongsRatingChanged(new_song_list);
|
||||
|
||||
}
|
||||
|
||||
void CollectionBackend::UpdateSongRatingAsync(const int id, const float rating) {
|
||||
metaObject()->invokeMethod(this, "UpdateSongRating", Qt::QueuedConnection, Q_ARG(int, id), Q_ARG(float, rating));
|
||||
}
|
||||
|
||||
void CollectionBackend::UpdateSongsRatingAsync(const QList<int>& ids, const float rating) {
|
||||
metaObject()->invokeMethod(this, "UpdateSongsRating", Qt::QueuedConnection, Q_ARG(QList<int>, ids), Q_ARG(float, rating));
|
||||
}
|
||||
|
||||
|
||||
@@ -40,6 +40,7 @@
|
||||
|
||||
class QThread;
|
||||
class Database;
|
||||
class SmartPlaylistSearch;
|
||||
|
||||
class CollectionBackendInterface : public QObject {
|
||||
Q_OBJECT
|
||||
@@ -80,10 +81,10 @@ class CollectionBackendInterface : public QObject {
|
||||
virtual void UpdateTotalArtistCountAsync() = 0;
|
||||
virtual void UpdateTotalAlbumCountAsync() = 0;
|
||||
|
||||
virtual SongList FindSongsInDirectory(int id) = 0;
|
||||
virtual SubdirectoryList SubdirsInDirectory(int id) = 0;
|
||||
virtual SongList FindSongsInDirectory(const int id) = 0;
|
||||
virtual SubdirectoryList SubdirsInDirectory(const int id) = 0;
|
||||
virtual DirectoryList GetAllDirectories() = 0;
|
||||
virtual void ChangeDirPath(int id, const QString &old_path, const QString &new_path) = 0;
|
||||
virtual void ChangeDirPath(const int id, const QString &old_path, const QString &new_path) = 0;
|
||||
|
||||
virtual QStringList GetAllArtists(const QueryOptions &opt = QueryOptions()) = 0;
|
||||
virtual QStringList GetAllArtistsWithAlbums(const QueryOptions &opt = QueryOptions()) = 0;
|
||||
@@ -99,7 +100,7 @@ class CollectionBackendInterface : public QObject {
|
||||
virtual void UpdateManualAlbumArtAsync(const QString &artist, const QString &albumartist, const QString &album, const QUrl &cover_url) = 0;
|
||||
virtual Album GetAlbumArt(const QString &artist, const QString &albumartist, const QString &album) = 0;
|
||||
|
||||
virtual Song GetSongById(int id) = 0;
|
||||
virtual Song GetSongById(const int id) = 0;
|
||||
|
||||
// Returns all sections of a song with the given filename. If there's just one section the resulting list will have it's size equal to 1.
|
||||
virtual SongList GetSongsByUrl(const QUrl &url) = 0;
|
||||
@@ -182,10 +183,16 @@ class CollectionBackend : public CollectionBackendInterface {
|
||||
Song GetSongBySongId(const QString &song_id);
|
||||
SongList GetSongsBySongId(const QStringList &song_ids);
|
||||
|
||||
SongList GetAllSongs();
|
||||
SongList FindSongs(const SmartPlaylistSearch &search);
|
||||
|
||||
Song::Source Source() const;
|
||||
|
||||
void AddOrUpdateSongsAsync(const SongList &songs);
|
||||
|
||||
void UpdateSongRatingAsync(const int id, const float rating);
|
||||
void UpdateSongsRatingAsync(const QList<int> &ids, const float rating);
|
||||
|
||||
public slots:
|
||||
void Exit();
|
||||
void LoadDirectories();
|
||||
@@ -195,7 +202,7 @@ class CollectionBackend : public CollectionBackendInterface {
|
||||
void AddOrUpdateSongs(const SongList &songs);
|
||||
void UpdateMTimesOnly(const SongList &songs);
|
||||
void DeleteSongs(const SongList &songs);
|
||||
void MarkSongsUnavailable(const SongList &songs, bool unavailable = true);
|
||||
void MarkSongsUnavailable(const SongList &songs, const bool unavailable = true);
|
||||
void AddOrUpdateSubdirs(const SubdirectoryList &subdirs);
|
||||
void UpdateCompilations();
|
||||
void UpdateManualAlbumArt(const QString &artist, const QString &albumartist, const QString &album, const QUrl &cover_url);
|
||||
@@ -205,19 +212,27 @@ class CollectionBackend : public CollectionBackendInterface {
|
||||
void ResetStatistics(const int id);
|
||||
void SongPathChanged(const Song &song, const QFileInfo &new_file);
|
||||
|
||||
signals:
|
||||
void DirectoryDiscovered(const Directory &dir, const SubdirectoryList &subdirs);
|
||||
void DirectoryDeleted(const Directory &dir);
|
||||
SongList GetSongsBy(const QString &artist, const QString &album, const QString &title);
|
||||
void UpdateLastPlayed(const QString &artist, const QString &album, const QString &title, const int lastplayed);
|
||||
void UpdatePlayCount(const QString &artist, const QString &title, const int playcount);
|
||||
|
||||
void SongsDiscovered(const SongList &songs);
|
||||
void SongsDeleted(const SongList &songs);
|
||||
void SongsStatisticsChanged(const SongList& songs);
|
||||
void UpdateSongRating(const int id, const float rating);
|
||||
void UpdateSongsRating(const QList<int> &id_list, const float rating);
|
||||
|
||||
signals:
|
||||
void DirectoryDiscovered(Directory, SubdirectoryList);
|
||||
void DirectoryDeleted(Directory);
|
||||
|
||||
void SongsDiscovered(SongList);
|
||||
void SongsDeleted(SongList);
|
||||
void SongsStatisticsChanged(SongList);
|
||||
|
||||
void DatabaseReset();
|
||||
|
||||
void TotalSongCountUpdated(const int total);
|
||||
void TotalArtistCountUpdated(const int total);
|
||||
void TotalAlbumCountUpdated(const int total);
|
||||
void TotalSongCountUpdated(int);
|
||||
void TotalArtistCountUpdated(int);
|
||||
void TotalAlbumCountUpdated(int);
|
||||
void SongsRatingChanged(SongList);
|
||||
|
||||
void ExitFinished();
|
||||
|
||||
|
||||
@@ -163,6 +163,17 @@ QString CollectionFilterWidget::group_by() {
|
||||
|
||||
}
|
||||
|
||||
QString CollectionFilterWidget::group_by_version() {
|
||||
|
||||
if (settings_prefix_.isEmpty()) {
|
||||
return QString("group_by_version");
|
||||
}
|
||||
else {
|
||||
return QString("%1_group_by_version").arg(settings_prefix_);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
QString CollectionFilterWidget::group_by(const int number) { return group_by() + QString::number(number); }
|
||||
|
||||
void CollectionFilterWidget::UpdateGroupByActions() {
|
||||
@@ -213,14 +224,26 @@ QActionGroup *CollectionFilterWidget::CreateGroupByActions(QObject *parent) {
|
||||
// read saved groupings
|
||||
QSettings s;
|
||||
s.beginGroup(CollectionModel::kSavedGroupingsSettingsGroup);
|
||||
QStringList saved = s.childKeys();
|
||||
for (int i = 0; i < saved.size(); ++i) {
|
||||
QByteArray bytes = s.value(saved.at(i)).toByteArray();
|
||||
QDataStream ds(&bytes, QIODevice::ReadOnly);
|
||||
CollectionModel::Grouping g;
|
||||
ds >> g;
|
||||
ret->addAction(CreateGroupByAction(saved.at(i), parent, g));
|
||||
int version = s.value("version").toInt();
|
||||
if (version == 1) {
|
||||
QStringList saved = s.childKeys();
|
||||
for (int i = 0; i < saved.size(); ++i) {
|
||||
if (saved.at(i) == "version") continue;
|
||||
QByteArray bytes = s.value(saved.at(i)).toByteArray();
|
||||
QDataStream ds(&bytes, QIODevice::ReadOnly);
|
||||
CollectionModel::Grouping g;
|
||||
ds >> g;
|
||||
ret->addAction(CreateGroupByAction(saved.at(i), parent, g));
|
||||
}
|
||||
}
|
||||
else {
|
||||
QStringList saved = s.childKeys();
|
||||
for (int i = 0; i < saved.size(); ++i) {
|
||||
if (saved.at(i) == "version") continue;
|
||||
s.remove(saved.at(i));
|
||||
}
|
||||
}
|
||||
s.endGroup();
|
||||
|
||||
QAction *sep2 = new QAction(parent);
|
||||
sep2->setSeparator(true);
|
||||
@@ -301,10 +324,18 @@ void CollectionFilterWidget::SetCollectionModel(CollectionModel *model) {
|
||||
if (!settings_group_.isEmpty()) {
|
||||
QSettings s;
|
||||
s.beginGroup(settings_group_);
|
||||
model_->SetGroupBy(CollectionModel::Grouping(
|
||||
CollectionModel::GroupBy(s.value(group_by(1), int(CollectionModel::GroupBy_AlbumArtist)).toInt()),
|
||||
CollectionModel::GroupBy(s.value(group_by(2), int(CollectionModel::GroupBy_AlbumDisc)).toInt()),
|
||||
CollectionModel::GroupBy(s.value(group_by(3), int(CollectionModel::GroupBy_None)).toInt())));
|
||||
int version = 0;
|
||||
if (s.contains(group_by_version())) version = s.value(group_by_version(), 0).toInt();
|
||||
if (version == 1) {
|
||||
model_->SetGroupBy(CollectionModel::Grouping(
|
||||
CollectionModel::GroupBy(s.value(group_by(1), int(CollectionModel::GroupBy_AlbumArtist)).toInt()),
|
||||
CollectionModel::GroupBy(s.value(group_by(2), int(CollectionModel::GroupBy_AlbumDisc)).toInt()),
|
||||
CollectionModel::GroupBy(s.value(group_by(3), int(CollectionModel::GroupBy_None)).toInt())));
|
||||
}
|
||||
else {
|
||||
model_->SetGroupBy(CollectionModel::Grouping(CollectionModel::GroupBy_AlbumArtist, CollectionModel::GroupBy_AlbumDisc, CollectionModel::GroupBy_None));
|
||||
}
|
||||
s.endGroup();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -327,9 +358,11 @@ void CollectionFilterWidget::GroupingChanged(const CollectionModel::Grouping &g)
|
||||
// Save the settings
|
||||
QSettings s;
|
||||
s.beginGroup(settings_group_);
|
||||
s.setValue(group_by_version(), 1);
|
||||
s.setValue(group_by(1), int(g[0]));
|
||||
s.setValue(group_by(2), int(g[1]));
|
||||
s.setValue(group_by(3), int(g[2]));
|
||||
s.endGroup();
|
||||
}
|
||||
|
||||
// Now make sure the correct action is checked
|
||||
|
||||
@@ -77,6 +77,7 @@ class CollectionFilterWidget : public QWidget {
|
||||
void SetCollectionModel(CollectionModel *model);
|
||||
|
||||
QString group_by();
|
||||
QString group_by_version();
|
||||
QString group_by(const int number);
|
||||
|
||||
void ReloadSettings();
|
||||
|
||||
@@ -85,25 +85,27 @@ class CollectionModel : public SimpleTreeModel<CollectionItem> {
|
||||
// These values get saved in QSettings - don't change them
|
||||
enum GroupBy {
|
||||
GroupBy_None = 0,
|
||||
GroupBy_Artist = 1,
|
||||
GroupBy_Album = 2,
|
||||
GroupBy_YearAlbum = 3,
|
||||
GroupBy_Year = 4,
|
||||
GroupBy_Composer = 5,
|
||||
GroupBy_Genre = 6,
|
||||
GroupBy_AlbumArtist = 7,
|
||||
GroupBy_FileType = 8,
|
||||
GroupBy_Performer = 9,
|
||||
GroupBy_Grouping = 10,
|
||||
GroupBy_Bitrate = 11,
|
||||
GroupBy_Disc = 12,
|
||||
GroupBy_OriginalYearAlbum = 13,
|
||||
GroupBy_OriginalYear = 14,
|
||||
GroupBy_Samplerate = 15,
|
||||
GroupBy_Bitdepth = 16,
|
||||
GroupBy_AlbumArtist = 1,
|
||||
GroupBy_Artist = 2,
|
||||
GroupBy_Album = 3,
|
||||
GroupBy_AlbumDisc = 4,
|
||||
GroupBy_YearAlbum = 5,
|
||||
GroupBy_YearAlbumDisc = 6,
|
||||
GroupBy_OriginalYearAlbum = 7,
|
||||
GroupBy_OriginalYearAlbumDisc = 8,
|
||||
GroupBy_Disc = 9,
|
||||
GroupBy_Year = 10,
|
||||
GroupBy_OriginalYear = 11,
|
||||
GroupBy_Genre = 12,
|
||||
GroupBy_Composer = 13,
|
||||
GroupBy_Performer = 14,
|
||||
GroupBy_Grouping = 15,
|
||||
GroupBy_FileType = 16,
|
||||
GroupBy_Format = 17,
|
||||
GroupBy_AlbumDisc = 18,
|
||||
GroupBy_YearAlbumDisc = 19
|
||||
GroupBy_Samplerate = 18,
|
||||
GroupBy_Bitdepth = 19,
|
||||
GroupBy_Bitrate = 20,
|
||||
GroupByCount = 21,
|
||||
};
|
||||
|
||||
struct Grouping {
|
||||
@@ -170,6 +172,7 @@ class CollectionModel : public SimpleTreeModel<CollectionItem> {
|
||||
static QString PrettyYearAlbum(const int year, const QString &album);
|
||||
static QString PrettyAlbumDisc(const QString &album, const int disc);
|
||||
static QString PrettyYearAlbumDisc(const int year, const QString &album, const int disc);
|
||||
static QString PrettyDisc(const int disc);
|
||||
static QString SortText(QString text);
|
||||
static QString SortTextForNumber(const int number);
|
||||
static QString SortTextForArtist(QString artist);
|
||||
@@ -179,7 +182,18 @@ class CollectionModel : public SimpleTreeModel<CollectionItem> {
|
||||
|
||||
quint64 icon_cache_disk_size() { return sIconCache->cacheSize(); }
|
||||
|
||||
static bool IsAlbumGrouping(const GroupBy group_by) { return group_by == GroupBy_Album || group_by == GroupBy_YearAlbum || group_by == GroupBy_OriginalYearAlbum || group_by == GroupBy_AlbumDisc || group_by == GroupBy_YearAlbumDisc; }
|
||||
static bool IsArtistGroupBy(const GroupBy group_by) {
|
||||
return group_by == CollectionModel::GroupBy_Artist || group_by == CollectionModel::GroupBy_AlbumArtist;
|
||||
}
|
||||
static bool IsAlbumGroupBy(const GroupBy group_by) { return group_by == GroupBy_Album || group_by == GroupBy_YearAlbum || group_by == GroupBy_AlbumDisc || group_by == GroupBy_YearAlbumDisc || group_by == GroupBy_OriginalYearAlbum || group_by == GroupBy_OriginalYearAlbumDisc; }
|
||||
|
||||
void set_use_lazy_loading(const bool value) { use_lazy_loading_ = value; }
|
||||
|
||||
QMap<QString, CollectionItem*> container_nodes(const int i) { return container_nodes_[i]; }
|
||||
QList<CollectionItem*> song_nodes() const { return song_nodes_.values(); }
|
||||
int divider_nodes_count() const { return divider_nodes_.count(); }
|
||||
|
||||
void ExpandAll(CollectionItem *item = nullptr) const;
|
||||
|
||||
signals:
|
||||
void TotalSongCountUpdated(const int count);
|
||||
@@ -248,6 +262,7 @@ class CollectionModel : public SimpleTreeModel<CollectionItem> {
|
||||
QString DividerDisplayText(const GroupBy type, const QString &key) const;
|
||||
|
||||
// Helpers
|
||||
static bool IsCompilationArtistNode(const CollectionItem *node) { return node == node->parent->compilation_artist_node_; }
|
||||
QString AlbumIconPixmapCacheKey(const QModelIndex &idx) const;
|
||||
QVariant AlbumIcon(const QModelIndex &idx);
|
||||
QVariant data(const CollectionItem *item, const int role) const;
|
||||
@@ -288,6 +303,7 @@ class CollectionModel : public SimpleTreeModel<CollectionItem> {
|
||||
bool use_pretty_covers_;
|
||||
bool show_dividers_;
|
||||
bool use_disk_cache_;
|
||||
bool use_lazy_loading_;
|
||||
|
||||
AlbumCoverLoaderOptions cover_loader_options_;
|
||||
|
||||
|
||||
@@ -70,6 +70,6 @@ Song CollectionPlaylistItem::Metadata() const {
|
||||
void CollectionPlaylistItem::SetArtManual(const QUrl &cover_url) {
|
||||
|
||||
song_.set_art_manual(cover_url);
|
||||
temp_metadata_.set_art_manual(cover_url);
|
||||
if (HasTemporaryMetadata()) temp_metadata_.set_art_manual(cover_url);
|
||||
|
||||
}
|
||||
|
||||
@@ -21,6 +21,7 @@
|
||||
#include "config.h"
|
||||
|
||||
#include <QtGlobal>
|
||||
#include <QMetaType>
|
||||
#include <QDateTime>
|
||||
#include <QVariant>
|
||||
#include <QString>
|
||||
@@ -133,9 +134,23 @@ void CollectionQuery::AddWhere(const QString &column, const QVariant &value, con
|
||||
}
|
||||
else {
|
||||
// Do integers inline - sqlite seems to get confused when you pass integers to bound parameters
|
||||
#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
|
||||
if (value.metaType().id() == QMetaType::Int) {
|
||||
#else
|
||||
if (value.type() == QVariant::Int) {
|
||||
#endif
|
||||
where_clauses_ << QString("%1 %2 %3").arg(column, op, value.toString());
|
||||
}
|
||||
else if (
|
||||
#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
|
||||
value.metaType().id() == QMetaType::QString
|
||||
#else
|
||||
value.type() == QVariant::String
|
||||
#endif
|
||||
&& value.toString().isNull()) {
|
||||
where_clauses_ << QString("%1 %2 ?").arg(column, op);
|
||||
bound_values_ << QString("");
|
||||
}
|
||||
else {
|
||||
where_clauses_ << QString("%1 %2 ?").arg(column, op);
|
||||
bound_values_ << value;
|
||||
|
||||
@@ -48,6 +48,8 @@
|
||||
#include "core/iconloader.h"
|
||||
#include "core/mimedata.h"
|
||||
#include "core/utilities.h"
|
||||
#include "core/musicstorage.h"
|
||||
#include "core/deletefiles.h"
|
||||
#include "collection.h"
|
||||
#include "collectionbackend.h"
|
||||
#include "collectiondirectorymodel.h"
|
||||
@@ -61,7 +63,9 @@
|
||||
# include "device/devicestatefiltermodel.h"
|
||||
#endif
|
||||
#include "dialogs/edittagdialog.h"
|
||||
#include "dialogs/deleteconfirmationdialog.h"
|
||||
#include "organize/organizedialog.h"
|
||||
#include "organize/organizeerrordialog.h"
|
||||
#include "settings/collectionsettingspage.h"
|
||||
|
||||
CollectionView::CollectionView(QWidget *parent)
|
||||
@@ -73,7 +77,23 @@ CollectionView::CollectionView(QWidget *parent)
|
||||
total_album_count_(-1),
|
||||
nomusic_(":/pictures/nomusic.png"),
|
||||
context_menu_(nullptr),
|
||||
is_in_keyboard_search_(false)
|
||||
action_load_(nullptr),
|
||||
action_add_to_playlist_(nullptr),
|
||||
action_add_to_playlist_enqueue_(nullptr),
|
||||
action_add_to_playlist_enqueue_next_(nullptr),
|
||||
action_open_in_new_playlist_(nullptr),
|
||||
action_organize_(nullptr),
|
||||
#ifndef Q_OS_WIN
|
||||
action_copy_to_device_(nullptr),
|
||||
#endif
|
||||
action_edit_track_(nullptr),
|
||||
action_edit_tracks_(nullptr),
|
||||
action_rescan_songs_(nullptr),
|
||||
action_show_in_browser_(nullptr),
|
||||
action_show_in_various_(nullptr),
|
||||
action_no_show_in_various_(nullptr),
|
||||
is_in_keyboard_search_(false),
|
||||
delete_files_(false)
|
||||
{
|
||||
|
||||
setItemDelegate(new CollectionItemDelegate(this));
|
||||
@@ -211,6 +231,8 @@ void CollectionView::ReloadSettings() {
|
||||
app_->collection_model()->set_show_dividers(settings.value("show_dividers", true).toBool());
|
||||
}
|
||||
|
||||
delete_files_ = settings.value("delete_files", false).toBool();
|
||||
|
||||
settings.endGroup();
|
||||
|
||||
}
|
||||
@@ -225,7 +247,7 @@ void CollectionView::SetApplication(Application *app) {
|
||||
|
||||
void CollectionView::SetFilter(CollectionFilterWidget *filter) { filter_ = filter; }
|
||||
|
||||
void CollectionView::TotalSongCountUpdated(int count) {
|
||||
void CollectionView::TotalSongCountUpdated(const int count) {
|
||||
|
||||
int old = total_song_count_;
|
||||
total_song_count_ = count;
|
||||
@@ -240,7 +262,7 @@ void CollectionView::TotalSongCountUpdated(int count) {
|
||||
|
||||
}
|
||||
|
||||
void CollectionView::TotalArtistCountUpdated(int count) {
|
||||
void CollectionView::TotalArtistCountUpdated(const int count) {
|
||||
|
||||
int old = total_artist_count_;
|
||||
total_artist_count_ = count;
|
||||
@@ -255,7 +277,7 @@ void CollectionView::TotalArtistCountUpdated(int count) {
|
||||
|
||||
}
|
||||
|
||||
void CollectionView::TotalAlbumCountUpdated(int count) {
|
||||
void CollectionView::TotalAlbumCountUpdated(const int count) {
|
||||
|
||||
int old = total_album_count_;
|
||||
total_album_count_ = count;
|
||||
@@ -316,41 +338,41 @@ void CollectionView::contextMenuEvent(QContextMenuEvent *e) {
|
||||
|
||||
if (!context_menu_) {
|
||||
context_menu_ = new QMenu(this);
|
||||
add_to_playlist_ = context_menu_->addAction(IconLoader::Load("media-playback-start"), tr("Append to current playlist"), this, SLOT(AddToPlaylist()));
|
||||
load_ = context_menu_->addAction(IconLoader::Load("media-playback-start"), tr("Replace current playlist"), this, SLOT(Load()));
|
||||
open_in_new_playlist_ = context_menu_->addAction(IconLoader::Load("document-new"), tr("Open in new playlist"), this, SLOT(OpenInNewPlaylist()));
|
||||
action_add_to_playlist_ = context_menu_->addAction(IconLoader::Load("media-playback-start"), tr("Append to current playlist"), this, SLOT(AddToPlaylist()));
|
||||
action_load_ = context_menu_->addAction(IconLoader::Load("media-playback-start"), tr("Replace current playlist"), this, SLOT(Load()));
|
||||
action_open_in_new_playlist_ = context_menu_->addAction(IconLoader::Load("document-new"), tr("Open in new playlist"), this, SLOT(OpenInNewPlaylist()));
|
||||
|
||||
context_menu_->addSeparator();
|
||||
add_to_playlist_enqueue_ = context_menu_->addAction(IconLoader::Load("go-next"), tr("Queue track"), this, SLOT(AddToPlaylistEnqueue()));
|
||||
add_to_playlist_enqueue_next_ = context_menu_->addAction(IconLoader::Load("go-next"), tr("Queue to play next"), this, SLOT(AddToPlaylistEnqueueNext()));
|
||||
action_add_to_playlist_enqueue_ = context_menu_->addAction(IconLoader::Load("go-next"), tr("Queue track"), this, SLOT(AddToPlaylistEnqueue()));
|
||||
action_add_to_playlist_enqueue_next_ = context_menu_->addAction(IconLoader::Load("go-next"), tr("Queue to play next"), this, SLOT(AddToPlaylistEnqueueNext()));
|
||||
|
||||
context_menu_->addSeparator();
|
||||
organize_ = context_menu_->addAction(IconLoader::Load("edit-copy"), tr("Organize files..."), this, SLOT(Organize()));
|
||||
action_organize_ = context_menu_->addAction(IconLoader::Load("edit-copy"), tr("Organize files..."), this, SLOT(Organize()));
|
||||
#ifndef Q_OS_WIN
|
||||
copy_to_device_ = context_menu_->addAction(IconLoader::Load("device"), tr("Copy to device..."), this, SLOT(CopyToDevice()));
|
||||
action_copy_to_device_ = context_menu_->addAction(IconLoader::Load("device"), tr("Copy to device..."), this, SLOT(CopyToDevice()));
|
||||
#endif
|
||||
//delete_ = context_menu_->addAction(IconLoader::Load("edit-delete"), tr("Delete from disk..."), this, SLOT(Delete()));
|
||||
action_delete_files_ = context_menu_->addAction(IconLoader::Load("edit-delete"), tr("Delete from disk..."), this, SLOT(Delete()));
|
||||
|
||||
context_menu_->addSeparator();
|
||||
edit_track_ = context_menu_->addAction(IconLoader::Load("edit-rename"), tr("Edit track information..."), this, SLOT(EditTracks()));
|
||||
edit_tracks_ = context_menu_->addAction(IconLoader::Load("edit-rename"), tr("Edit tracks information..."), this, SLOT(EditTracks()));
|
||||
show_in_browser_ = context_menu_->addAction(IconLoader::Load("document-open-folder"), tr("Show in file browser..."), this, SLOT(ShowInBrowser()));
|
||||
action_edit_track_ = context_menu_->addAction(IconLoader::Load("edit-rename"), tr("Edit track information..."), this, SLOT(EditTracks()));
|
||||
action_edit_tracks_ = context_menu_->addAction(IconLoader::Load("edit-rename"), tr("Edit tracks information..."), this, SLOT(EditTracks()));
|
||||
action_show_in_browser_ = context_menu_->addAction(IconLoader::Load("document-open-folder"), tr("Show in file browser..."), this, SLOT(ShowInBrowser()));
|
||||
|
||||
context_menu_->addSeparator();
|
||||
|
||||
rescan_songs_ = context_menu_->addAction(tr("Rescan song(s)"), this, SLOT(RescanSongs()));
|
||||
action_rescan_songs_ = context_menu_->addAction(tr("Rescan song(s)"), this, SLOT(RescanSongs()));
|
||||
|
||||
context_menu_->addSeparator();
|
||||
show_in_various_ = context_menu_->addAction( tr("Show in various artists"), this, SLOT(ShowInVarious()));
|
||||
no_show_in_various_ = context_menu_->addAction( tr("Don't show in various artists"), this, SLOT(NoShowInVarious()));
|
||||
action_show_in_various_ = context_menu_->addAction( tr("Show in various artists"), this, SLOT(ShowInVarious()));
|
||||
action_no_show_in_various_ = context_menu_->addAction( tr("Don't show in various artists"), this, SLOT(NoShowInVarious()));
|
||||
|
||||
context_menu_->addSeparator();
|
||||
|
||||
context_menu_->addMenu(filter_->menu());
|
||||
|
||||
#ifndef Q_OS_WIN
|
||||
copy_to_device_->setDisabled(app_->device_manager()->connected_devices_model()->rowCount() == 0);
|
||||
connect(app_->device_manager()->connected_devices_model(), SIGNAL(IsEmptyChanged(bool)), copy_to_device_, SLOT(setDisabled(bool)));
|
||||
action_copy_to_device_->setDisabled(app_->device_manager()->connected_devices_model()->rowCount() == 0);
|
||||
connect(app_->device_manager()->connected_devices_model(), SIGNAL(IsEmptyChanged(bool)), action_copy_to_device_, SLOT(setDisabled(bool)));
|
||||
#endif
|
||||
|
||||
}
|
||||
@@ -376,34 +398,45 @@ void CollectionView::contextMenuEvent(QContextMenuEvent *e) {
|
||||
const bool regular_elements_only = songs_selected == regular_elements && regular_elements > 0;
|
||||
|
||||
// in all modes
|
||||
load_->setEnabled(songs_selected > 0);
|
||||
add_to_playlist_->setEnabled(songs_selected > 0);
|
||||
open_in_new_playlist_->setEnabled(songs_selected > 0);
|
||||
add_to_playlist_enqueue_->setEnabled(songs_selected > 0);
|
||||
action_load_->setEnabled(songs_selected > 0);
|
||||
action_add_to_playlist_->setEnabled(songs_selected > 0);
|
||||
action_open_in_new_playlist_->setEnabled(songs_selected > 0);
|
||||
action_add_to_playlist_enqueue_->setEnabled(songs_selected > 0);
|
||||
|
||||
// if neither edit_track not edit_tracks are available, we show disabled edit_track element
|
||||
edit_track_->setVisible(regular_editable == 1);
|
||||
edit_track_->setEnabled(regular_editable == 1);
|
||||
edit_tracks_->setVisible(regular_editable > 1);
|
||||
edit_tracks_->setEnabled(regular_editable > 1);
|
||||
action_edit_track_->setVisible(regular_editable == 1);
|
||||
action_edit_track_->setEnabled(regular_editable == 1);
|
||||
action_edit_tracks_->setVisible(regular_editable > 1);
|
||||
action_edit_tracks_->setEnabled(regular_editable > 1);
|
||||
|
||||
rescan_songs_->setVisible(regular_editable > 0);
|
||||
rescan_songs_->setEnabled(regular_editable > 0);
|
||||
action_rescan_songs_->setVisible(regular_editable > 0);
|
||||
action_rescan_songs_->setEnabled(regular_editable > 0);
|
||||
|
||||
organize_->setVisible(regular_elements == regular_editable);
|
||||
action_organize_->setVisible(regular_elements == regular_editable);
|
||||
#ifndef Q_OS_WIN
|
||||
copy_to_device_->setVisible(regular_elements == regular_editable);
|
||||
action_copy_to_device_->setVisible(regular_elements == regular_editable);
|
||||
#endif
|
||||
//delete_->setVisible(regular_elements_only);
|
||||
show_in_various_->setVisible(regular_elements_only);
|
||||
no_show_in_various_->setVisible(regular_elements_only);
|
||||
|
||||
#if QT_VERSION >= QT_VERSION_CHECK(5, 15, 0)
|
||||
action_delete_files_->setVisible(regular_elements == regular_editable && delete_files_);
|
||||
#else
|
||||
action_delete_files_->setVisible(false);
|
||||
#endif
|
||||
|
||||
action_show_in_various_->setVisible(regular_elements_only);
|
||||
action_no_show_in_various_->setVisible(regular_elements_only);
|
||||
|
||||
// only when all selected items are editable
|
||||
organize_->setEnabled(regular_elements == regular_editable);
|
||||
action_organize_->setEnabled(regular_elements == regular_editable);
|
||||
#ifndef Q_OS_WIN
|
||||
copy_to_device_->setEnabled(regular_elements == regular_editable);
|
||||
action_copy_to_device_->setEnabled(regular_elements == regular_editable);
|
||||
#endif
|
||||
|
||||
#if QT_VERSION >= QT_VERSION_CHECK(5, 15, 0)
|
||||
action_delete_files_->setEnabled(regular_elements == regular_editable && delete_files_);
|
||||
#else
|
||||
action_delete_files_->setEnabled(false);
|
||||
#endif
|
||||
//delete_->setEnabled(regular_elements == regular_editable);
|
||||
|
||||
context_menu_->popup(e->globalPos());
|
||||
|
||||
@@ -413,7 +446,7 @@ void CollectionView::ShowInVarious() { ShowInVarious(true); }
|
||||
|
||||
void CollectionView::NoShowInVarious() { ShowInVarious(false); }
|
||||
|
||||
void CollectionView::ShowInVarious(bool on) {
|
||||
void CollectionView::ShowInVarious(const bool on) {
|
||||
|
||||
if (!context_menu_index_.isValid()) return;
|
||||
|
||||
@@ -421,7 +454,7 @@ void CollectionView::ShowInVarious(bool on) {
|
||||
// We put through "Various Artists" changes one album at a time,
|
||||
// to make sure the old album node gets removed (due to all children removed), before the new one gets added
|
||||
QMultiMap<QString, QString> albums;
|
||||
for (const Song& song : GetSelectedSongs()) {
|
||||
for (const Song &song : GetSelectedSongs()) {
|
||||
if (albums.find(song.album(), song.artist()) == albums.end())
|
||||
albums.insert(song.album(), song.artist());
|
||||
}
|
||||
@@ -440,7 +473,7 @@ void CollectionView::ShowInVarious(bool on) {
|
||||
if (other_artists.count() > 0) {
|
||||
if (QMessageBox::question(this,
|
||||
tr("There are other songs in this album"),
|
||||
tr("Would you like to move the other songs in this album to Various Artists as well?"),
|
||||
tr("Would you like to move the other songs on this album to Various Artists as well?"),
|
||||
QMessageBox::Yes | QMessageBox::No,
|
||||
QMessageBox::Yes) == QMessageBox::Yes) {
|
||||
for (const QString &s : other_artists) {
|
||||
@@ -619,3 +652,33 @@ int CollectionView::TotalArtists() {
|
||||
int CollectionView::TotalAlbums() {
|
||||
return total_album_count_;
|
||||
}
|
||||
|
||||
void CollectionView::Delete() {
|
||||
|
||||
if (!delete_files_) return;
|
||||
|
||||
SongList selected_songs = GetSelectedSongs();
|
||||
QStringList files;
|
||||
for (const Song &song : selected_songs) {
|
||||
files << song.url().toString();
|
||||
}
|
||||
if (DeleteConfirmationDialog::warning(files) != QDialogButtonBox::Yes) return;
|
||||
|
||||
// We can cheat and always take the storage of the first directory, since they'll all be FilesystemMusicStorage in a collection and deleting doesn't check the actual directory.
|
||||
std::shared_ptr<MusicStorage> storage = app_->collection_model()->directory_model()->index(0, 0).data(MusicStorage::Role_Storage).value<std::shared_ptr<MusicStorage>>();
|
||||
|
||||
DeleteFiles *delete_files = new DeleteFiles(app_->task_manager(), storage, true);
|
||||
connect(delete_files, SIGNAL(Finished(SongList)), SLOT(DeleteFinished(SongList)));
|
||||
delete_files->Start(selected_songs);
|
||||
|
||||
}
|
||||
|
||||
void CollectionView::DeleteFilesFinished(const SongList &songs_with_errors) {
|
||||
|
||||
if (songs_with_errors.isEmpty()) return;
|
||||
|
||||
OrganizeErrorDialog *dialog = new OrganizeErrorDialog(this);
|
||||
dialog->Show(OrganizeErrorDialog::Type_Delete, songs_with_errors);
|
||||
// It deletes itself when the user closes it
|
||||
|
||||
}
|
||||
|
||||
@@ -70,9 +70,9 @@ class CollectionView : public AutoExpandingTreeView {
|
||||
int TotalAlbums();
|
||||
|
||||
public slots:
|
||||
void TotalSongCountUpdated(int count);
|
||||
void TotalArtistCountUpdated(int count);
|
||||
void TotalAlbumCountUpdated(int count);
|
||||
void TotalSongCountUpdated(const int count);
|
||||
void TotalArtistCountUpdated(const int count);
|
||||
void TotalAlbumCountUpdated(const int count);
|
||||
void ReloadSettings();
|
||||
|
||||
void FilterReturnPressed();
|
||||
@@ -88,7 +88,7 @@ class CollectionView : public AutoExpandingTreeView {
|
||||
void TotalSongCountUpdated_();
|
||||
void TotalArtistCountUpdated_();
|
||||
void TotalAlbumCountUpdated_();
|
||||
void Error(const QString &message);
|
||||
void Error(QString);
|
||||
|
||||
protected:
|
||||
// QWidget
|
||||
@@ -109,10 +109,12 @@ class CollectionView : public AutoExpandingTreeView {
|
||||
void ShowInBrowser();
|
||||
void ShowInVarious();
|
||||
void NoShowInVarious();
|
||||
void Delete();
|
||||
void DeleteFilesFinished(const SongList &songs_with_errors);
|
||||
|
||||
private:
|
||||
void RecheckIsEmpty();
|
||||
void ShowInVarious(bool on);
|
||||
void ShowInVarious(const bool on);
|
||||
bool RestoreLevelFocus(const QModelIndex &parent = QModelIndex());
|
||||
void SaveContainerPath(const QModelIndex &child);
|
||||
|
||||
@@ -128,27 +130,28 @@ class CollectionView : public AutoExpandingTreeView {
|
||||
|
||||
QMenu *context_menu_;
|
||||
QModelIndex context_menu_index_;
|
||||
QAction *load_;
|
||||
QAction *add_to_playlist_;
|
||||
QAction *add_to_playlist_enqueue_;
|
||||
QAction *add_to_playlist_enqueue_next_;
|
||||
QAction *open_in_new_playlist_;
|
||||
QAction *organize_;
|
||||
QAction *action_load_;
|
||||
QAction *action_add_to_playlist_;
|
||||
QAction *action_add_to_playlist_enqueue_;
|
||||
QAction *action_add_to_playlist_enqueue_next_;
|
||||
QAction *action_open_in_new_playlist_;
|
||||
QAction *action_organize_;
|
||||
#ifndef Q_OS_WIN
|
||||
QAction *copy_to_device_;
|
||||
QAction *action_copy_to_device_;
|
||||
#endif
|
||||
QAction *delete_;
|
||||
QAction *edit_track_;
|
||||
QAction *edit_tracks_;
|
||||
QAction *rescan_songs_;
|
||||
QAction *show_in_browser_;
|
||||
QAction *show_in_various_;
|
||||
QAction *no_show_in_various_;
|
||||
QAction *action_edit_track_;
|
||||
QAction *action_edit_tracks_;
|
||||
QAction *action_rescan_songs_;
|
||||
QAction *action_show_in_browser_;
|
||||
QAction *action_show_in_various_;
|
||||
QAction *action_no_show_in_various_;
|
||||
QAction *action_delete_files_;
|
||||
|
||||
std::unique_ptr<OrganizeDialog> organize_dialog_;
|
||||
std::unique_ptr<EditTagDialog> edit_tag_dialog_;
|
||||
|
||||
bool is_in_keyboard_search_;
|
||||
bool delete_files_;
|
||||
|
||||
// Save focus
|
||||
Song last_selected_song_;
|
||||
|
||||
@@ -65,7 +65,7 @@ static const char *kNoMediaFile = ".nomedia";
|
||||
static const char *kNoMusicFile = ".nomusic";
|
||||
}
|
||||
|
||||
QStringList CollectionWatcher::sValidImages;
|
||||
QStringList CollectionWatcher::sValidImages = QStringList() << "jpg" << "png" << "gif" << "jpeg";
|
||||
|
||||
CollectionWatcher::CollectionWatcher(Song::Source source, QObject *parent)
|
||||
: QObject(parent),
|
||||
@@ -90,10 +90,6 @@ CollectionWatcher::CollectionWatcher(Song::Source source, QObject *parent)
|
||||
rescan_timer_->setInterval(1000);
|
||||
rescan_timer_->setSingleShot(true);
|
||||
|
||||
if (sValidImages.isEmpty()) {
|
||||
sValidImages << "jpg" << "png" << "gif" << "jpeg";
|
||||
}
|
||||
|
||||
ReloadSettings();
|
||||
|
||||
connect(rescan_timer_, SIGNAL(timeout()), SLOT(RescanPathsNow()));
|
||||
@@ -573,7 +569,8 @@ SongList CollectionWatcher::ScanNewFile(const QString &file, const QString &path
|
||||
// Also, watch out for incorrect media files.
|
||||
// Playlist parser for CUEs considers every entry in sheet valid and we don't want invalid media getting into collection!
|
||||
QString file_nfd = file.normalized(QString::NormalizationForm_D);
|
||||
for (const Song &cue_song : cue_parser_->Load(&cue, matching_cue, path)) {
|
||||
for (Song &cue_song : cue_parser_->Load(&cue, matching_cue, path)) {
|
||||
cue_song.set_source(source_);
|
||||
if (cue_song.url().toLocalFile().normalized(QString::NormalizationForm_D) == file_nfd) {
|
||||
if (TagReaderClient::Instance()->IsMediaFileBlocking(file)) {
|
||||
song_list << cue_song;
|
||||
@@ -812,7 +809,6 @@ void CollectionWatcher::ReloadSettings() {
|
||||
scan_on_startup_ = s.value("startup_scan", true).toBool();
|
||||
monitor_ = s.value("monitor", true).toBool();
|
||||
mark_songs_unavailable_ = s.value("mark_songs_unavailable", false).toBool();
|
||||
live_scanning_ = s.value("live_scanning", false).toBool();
|
||||
QStringList filters = s.value("cover_art_patterns", QStringList() << "front" << "cover").toStringList();
|
||||
s.endGroup();
|
||||
|
||||
|
||||
@@ -74,44 +74,45 @@ SavedGroupingManager::~SavedGroupingManager() {
|
||||
QString SavedGroupingManager::GroupByToString(const CollectionModel::GroupBy &g) {
|
||||
|
||||
switch (g) {
|
||||
case CollectionModel::GroupBy_None: {
|
||||
case CollectionModel::GroupBy_None:
|
||||
case CollectionModel::GroupByCount: {
|
||||
return tr("None");
|
||||
}
|
||||
case CollectionModel::GroupBy_Artist: {
|
||||
return tr("Artist");
|
||||
}
|
||||
case CollectionModel::GroupBy_AlbumArtist: {
|
||||
return tr("Album artist");
|
||||
}
|
||||
case CollectionModel::GroupBy_Artist: {
|
||||
return tr("Artist");
|
||||
}
|
||||
case CollectionModel::GroupBy_Album: {
|
||||
return tr("Album");
|
||||
}
|
||||
case CollectionModel::GroupBy_AlbumDisc: {
|
||||
return tr("Album - Disc");
|
||||
}
|
||||
case CollectionModel::GroupBy_Disc: {
|
||||
return tr("Disc");
|
||||
}
|
||||
case CollectionModel::GroupBy_Format: {
|
||||
return tr("Format");
|
||||
}
|
||||
case CollectionModel::GroupBy_Genre: {
|
||||
return tr("Genre");
|
||||
}
|
||||
case CollectionModel::GroupBy_Year: {
|
||||
return tr("Year");
|
||||
}
|
||||
case CollectionModel::GroupBy_YearAlbum: {
|
||||
return tr("Year - Album");
|
||||
}
|
||||
case CollectionModel::GroupBy_YearAlbumDisc: {
|
||||
return tr("Year - Album - Disc");
|
||||
}
|
||||
case CollectionModel::GroupBy_OriginalYearAlbum: {
|
||||
return tr("Original year - Album");
|
||||
}
|
||||
case CollectionModel::GroupBy_OriginalYearAlbumDisc: {
|
||||
return tr("Original year - Album - Disc");
|
||||
}
|
||||
case CollectionModel::GroupBy_Disc: {
|
||||
return tr("Disc");
|
||||
}
|
||||
case CollectionModel::GroupBy_Year: {
|
||||
return tr("Year");
|
||||
}
|
||||
case CollectionModel::GroupBy_OriginalYear: {
|
||||
return tr("Original year");
|
||||
}
|
||||
case CollectionModel::GroupBy_OriginalYearAlbum: {
|
||||
return tr("Original year - Album");
|
||||
case CollectionModel::GroupBy_Genre: {
|
||||
return tr("Genre");
|
||||
}
|
||||
case CollectionModel::GroupBy_Composer: {
|
||||
return tr("Composer");
|
||||
@@ -125,6 +126,9 @@ QString SavedGroupingManager::GroupByToString(const CollectionModel::GroupBy &g)
|
||||
case CollectionModel::GroupBy_FileType: {
|
||||
return tr("File type");
|
||||
}
|
||||
case CollectionModel::GroupBy_Format: {
|
||||
return tr("Format");
|
||||
}
|
||||
case CollectionModel::GroupBy_Samplerate: {
|
||||
return tr("Sample rate");
|
||||
}
|
||||
@@ -134,9 +138,10 @@ QString SavedGroupingManager::GroupByToString(const CollectionModel::GroupBy &g)
|
||||
case CollectionModel::GroupBy_Bitrate: {
|
||||
return tr("Bitrate");
|
||||
}
|
||||
default: { return tr("Unknown"); }
|
||||
}
|
||||
|
||||
return tr("Unknown");
|
||||
|
||||
}
|
||||
|
||||
void SavedGroupingManager::UpdateModel() {
|
||||
@@ -144,21 +149,33 @@ void SavedGroupingManager::UpdateModel() {
|
||||
model_->setRowCount(0); // don't use clear, it deletes headers
|
||||
QSettings s;
|
||||
s.beginGroup(CollectionModel::kSavedGroupingsSettingsGroup);
|
||||
QStringList saved = s.childKeys();
|
||||
for (int i = 0; i < saved.size(); ++i) {
|
||||
QByteArray bytes = s.value(saved.at(i)).toByteArray();
|
||||
QDataStream ds(&bytes, QIODevice::ReadOnly);
|
||||
CollectionModel::Grouping g;
|
||||
ds >> g;
|
||||
int version = s.value("version").toInt();
|
||||
if (version == 1) {
|
||||
QStringList saved = s.childKeys();
|
||||
for (int i = 0; i < saved.size(); ++i) {
|
||||
if (saved.at(i) == "version") continue;
|
||||
QByteArray bytes = s.value(saved.at(i)).toByteArray();
|
||||
QDataStream ds(&bytes, QIODevice::ReadOnly);
|
||||
CollectionModel::Grouping g;
|
||||
ds >> g;
|
||||
|
||||
QList<QStandardItem*> list;
|
||||
list << new QStandardItem(saved.at(i))
|
||||
<< new QStandardItem(GroupByToString(g.first))
|
||||
<< new QStandardItem(GroupByToString(g.second))
|
||||
<< new QStandardItem(GroupByToString(g.third));
|
||||
QList<QStandardItem*> list;
|
||||
list << new QStandardItem(saved.at(i))
|
||||
<< new QStandardItem(GroupByToString(g.first))
|
||||
<< new QStandardItem(GroupByToString(g.second))
|
||||
<< new QStandardItem(GroupByToString(g.third));
|
||||
|
||||
model_->appendRow(list);
|
||||
model_->appendRow(list);
|
||||
}
|
||||
}
|
||||
else {
|
||||
QStringList saved = s.childKeys();
|
||||
for (int i = 0; i < saved.size(); ++i) {
|
||||
if (saved.at(i) == "version") continue;
|
||||
s.remove(saved.at(i));
|
||||
}
|
||||
}
|
||||
s.endGroup();
|
||||
|
||||
}
|
||||
|
||||
@@ -173,6 +190,7 @@ void SavedGroupingManager::Remove() {
|
||||
s.remove(model_->item(index.row(), 0)->text());
|
||||
}
|
||||
}
|
||||
s.endGroup();
|
||||
}
|
||||
UpdateModel();
|
||||
filter_->UpdateGroupByActions();
|
||||
|
||||
@@ -46,6 +46,7 @@
|
||||
|
||||
#cmakedefine HAVE_SUBSONIC
|
||||
#cmakedefine HAVE_TIDAL
|
||||
#cmakedefine HAVE_QOBUZ
|
||||
|
||||
#cmakedefine HAVE_MOODBAR
|
||||
|
||||
|
||||
@@ -28,6 +28,7 @@
|
||||
#include <QtGlobal>
|
||||
#include <QMutex>
|
||||
#include <QMimeData>
|
||||
#include <QMetaType>
|
||||
#include <QVariant>
|
||||
#include <QList>
|
||||
#include <QSet>
|
||||
@@ -444,8 +445,13 @@ bool ContextAlbumsModel::CompareItems(const CollectionItem *a, const CollectionI
|
||||
QVariant left(data(a, ContextAlbumsModel::Role_SortText));
|
||||
QVariant right(data(b, ContextAlbumsModel::Role_SortText));
|
||||
|
||||
if (left.type() == QVariant::Int) return left.toInt() < right.toInt();
|
||||
return left.toString() < right.toString();
|
||||
#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
|
||||
if (left.metaType().id() == QMetaType::Int)
|
||||
#else
|
||||
if (left.type() == QVariant::Int)
|
||||
#endif
|
||||
return left.toInt() < right.toInt();
|
||||
else return left.toString() < right.toString();
|
||||
|
||||
}
|
||||
|
||||
|
||||
@@ -397,7 +397,7 @@ void ContextAlbumsView::EditTracks() {
|
||||
void ContextAlbumsView::CopyToDevice() {
|
||||
#ifndef Q_OS_WIN
|
||||
if (!organize_dialog_)
|
||||
organize_dialog_.reset(new OrganizeDialog(app_->task_manager()));
|
||||
organize_dialog_.reset(new OrganizeDialog(app_->task_manager(), nullptr, this));
|
||||
|
||||
organize_dialog_->SetDestinationModel(app_->device_manager()->connected_devices_model(), true);
|
||||
organize_dialog_->SetCopy(true);
|
||||
|
||||
@@ -151,6 +151,7 @@ ContextView::ContextView(QWidget *parent) :
|
||||
label_top_->setWordWrap(true);
|
||||
label_top_->setMinimumHeight(50);
|
||||
label_top_->setContentsMargins(0, 0, 32, 0);
|
||||
label_top_->setTextInteractionFlags(Qt::TextSelectableByMouse);
|
||||
|
||||
layout_scrollarea_->setObjectName("context-layout-scrollarea");
|
||||
layout_scrollarea_->setContentsMargins(15, 15, 15, 15);
|
||||
@@ -365,8 +366,8 @@ void ContextView::ReloadSettings() {
|
||||
title_fmt_ = s.value(ContextSettingsPage::kSettingsTitleFmt, "%title% - %artist%").toString();
|
||||
summary_fmt_ = s.value(ContextSettingsPage::kSettingsSummaryFmt, "%album%").toString();
|
||||
action_show_album_->setChecked(s.value(ContextSettingsPage::kSettingsGroupEnable[ContextSettingsPage::ContextSettingsOrder::ALBUM], true).toBool());
|
||||
action_show_data_->setChecked(s.value(ContextSettingsPage::kSettingsGroupEnable[ContextSettingsPage::ContextSettingsOrder::TECHNICAL_DATA], true).toBool());
|
||||
action_show_output_->setChecked(s.value(ContextSettingsPage::kSettingsGroupEnable[ContextSettingsPage::ContextSettingsOrder::ENGINE_AND_DEVICE], true).toBool());
|
||||
action_show_data_->setChecked(s.value(ContextSettingsPage::kSettingsGroupEnable[ContextSettingsPage::ContextSettingsOrder::TECHNICAL_DATA], false).toBool());
|
||||
action_show_output_->setChecked(s.value(ContextSettingsPage::kSettingsGroupEnable[ContextSettingsPage::ContextSettingsOrder::ENGINE_AND_DEVICE], false).toBool());
|
||||
action_show_albums_->setChecked(s.value(ContextSettingsPage::kSettingsGroupEnable[ContextSettingsPage::ContextSettingsOrder::ALBUMS_BY_ARTIST], false).toBool());
|
||||
action_show_lyrics_->setChecked(s.value(ContextSettingsPage::kSettingsGroupEnable[ContextSettingsPage::ContextSettingsOrder::SONG_LYRICS], true).toBool());
|
||||
action_search_lyrics_->setChecked(s.value(ContextSettingsPage::kSettingsGroupEnable[ContextSettingsPage::ContextSettingsOrder::SEARCH_LYRICS], true).toBool());
|
||||
|
||||
@@ -56,7 +56,6 @@
|
||||
#include "covermanager/discogscoverprovider.h"
|
||||
#include "covermanager/musicbrainzcoverprovider.h"
|
||||
#include "covermanager/deezercoverprovider.h"
|
||||
#include "covermanager/qobuzcoverprovider.h"
|
||||
#include "covermanager/musixmatchcoverprovider.h"
|
||||
#include "covermanager/spotifycoverprovider.h"
|
||||
|
||||
@@ -69,6 +68,7 @@
|
||||
#include "lyrics/chartlyricsprovider.h"
|
||||
|
||||
#include "scrobbler/audioscrobbler.h"
|
||||
#include "scrobbler/lastfmimport.h"
|
||||
|
||||
#include "internet/internetservices.h"
|
||||
|
||||
@@ -81,6 +81,11 @@
|
||||
# include "covermanager/tidalcoverprovider.h"
|
||||
#endif
|
||||
|
||||
#ifdef HAVE_QOBUZ
|
||||
# include "qobuz/qobuzservice.h"
|
||||
# include "covermanager/qobuzcoverprovider.h"
|
||||
#endif
|
||||
|
||||
#ifdef HAVE_MOODBAR
|
||||
# include "moodbar/moodbarcontroller.h"
|
||||
# include "moodbar/moodbarloader.h"
|
||||
@@ -122,11 +127,13 @@ class ApplicationImpl {
|
||||
cover_providers->AddProvider(new MusicbrainzCoverProvider(app, app));
|
||||
cover_providers->AddProvider(new DiscogsCoverProvider(app, app));
|
||||
cover_providers->AddProvider(new DeezerCoverProvider(app, app));
|
||||
cover_providers->AddProvider(new QobuzCoverProvider(app, app));
|
||||
cover_providers->AddProvider(new MusixmatchCoverProvider(app, app));
|
||||
cover_providers->AddProvider(new SpotifyCoverProvider(app, app));
|
||||
#ifdef HAVE_TIDAL
|
||||
cover_providers->AddProvider(new TidalCoverProvider(app, app));
|
||||
#endif
|
||||
#ifdef HAVE_QOBUZ
|
||||
cover_providers->AddProvider(new QobuzCoverProvider(app, app));
|
||||
#endif
|
||||
cover_providers->ReloadSettings();
|
||||
return cover_providers;
|
||||
@@ -156,10 +163,14 @@ class ApplicationImpl {
|
||||
#endif
|
||||
#ifdef HAVE_TIDAL
|
||||
internet_services->AddService(new TidalService(app, internet_services));
|
||||
#endif
|
||||
#ifdef HAVE_QOBUZ
|
||||
internet_services->AddService(new QobuzService(app, internet_services));
|
||||
#endif
|
||||
return internet_services;
|
||||
}),
|
||||
scrobbler_([=]() { return new AudioScrobbler(app, app); }),
|
||||
lastfm_import_([=]() { return new LastFMImport(app); }),
|
||||
|
||||
#ifdef HAVE_MOODBAR
|
||||
moodbar_loader_([=]() { return new MoodbarLoader(app, app); }),
|
||||
@@ -187,6 +198,7 @@ class ApplicationImpl {
|
||||
Lazy<LyricsProviders> lyrics_providers_;
|
||||
Lazy<InternetServices> internet_services_;
|
||||
Lazy<AudioScrobbler> scrobbler_;
|
||||
Lazy<LastFMImport> lastfm_import_;
|
||||
#ifdef HAVE_MOODBAR
|
||||
Lazy<MoodbarLoader> moodbar_loader_;
|
||||
Lazy<MoodbarController> moodbar_controller_;
|
||||
@@ -315,6 +327,7 @@ PlaylistBackend *Application::playlist_backend() const { return p_->playlist_bac
|
||||
PlaylistManager *Application::playlist_manager() const { return p_->playlist_manager_.get(); }
|
||||
InternetServices *Application::internet_services() const { return p_->internet_services_.get(); }
|
||||
AudioScrobbler *Application::scrobbler() const { return p_->scrobbler_.get(); }
|
||||
LastFMImport *Application::lastfm_import() const { return p_->lastfm_import_.get(); }
|
||||
#ifdef HAVE_MOODBAR
|
||||
MoodbarController *Application::moodbar_controller() const { return p_->moodbar_controller_.get(); }
|
||||
MoodbarLoader *Application::moodbar_loader() const { return p_->moodbar_loader_.get(); }
|
||||
|
||||
@@ -56,6 +56,7 @@ class CurrentAlbumCoverLoader;
|
||||
class CoverProviders;
|
||||
class LyricsProviders;
|
||||
class AudioScrobbler;
|
||||
class LastFMImport;
|
||||
class InternetServices;
|
||||
#ifdef HAVE_MOODBAR
|
||||
class MoodbarController;
|
||||
@@ -93,6 +94,7 @@ class Application : public QObject {
|
||||
LyricsProviders *lyrics_providers() const;
|
||||
|
||||
AudioScrobbler *scrobbler() const;
|
||||
LastFMImport *lastfm_import() const;
|
||||
|
||||
InternetServices *internet_services() const;
|
||||
|
||||
|
||||
@@ -54,7 +54,7 @@
|
||||
#include "scopedtransaction.h"
|
||||
|
||||
const char *Database::kDatabaseFilename = "strawberry.db";
|
||||
const int Database::kSchemaVersion = 12;
|
||||
const int Database::kSchemaVersion = 13;
|
||||
const char *Database::kMagicAllSongsTables = "%allsongstables";
|
||||
|
||||
int Database::sNextConnectionId = 1;
|
||||
@@ -63,7 +63,9 @@ QMutex Database::sNextConnectionIdMutex;
|
||||
Database::Database(Application *app, QObject *parent, const QString &database_name) :
|
||||
QObject(parent),
|
||||
app_(app),
|
||||
#if QT_VERSION < QT_VERSION_CHECK(5, 14, 0)
|
||||
mutex_(QMutex::Recursive),
|
||||
#endif
|
||||
injected_database_name_(database_name),
|
||||
query_hash_(0),
|
||||
startup_schema_version_(-1),
|
||||
@@ -130,6 +132,7 @@ QSqlDatabase Database::Connect() {
|
||||
if (db.isOpen()) {
|
||||
return db;
|
||||
}
|
||||
db.setConnectOptions("QSQLITE_BUSY_TIMEOUT=30000");
|
||||
//qLog(Debug) << "Opened database with connection id" << connection_id;
|
||||
|
||||
if (!injected_database_name_.isNull())
|
||||
@@ -200,7 +203,7 @@ QSqlDatabase Database::Connect() {
|
||||
UpdateMainSchema(&db);
|
||||
}
|
||||
|
||||
// We might have to initialise the schema in some attached databases now, if they were deleted and don't match up with the main schema version.
|
||||
// We might have to initialize the schema in some attached databases now, if they were deleted and don't match up with the main schema version.
|
||||
for (const QString &key : attached_databases_.keys()) {
|
||||
if (attached_databases_[key].is_temporary_ && attached_databases_[key].schema_.isEmpty())
|
||||
continue;
|
||||
@@ -516,11 +519,13 @@ void Database::DoBackup() {
|
||||
|
||||
QSqlDatabase db(this->Connect());
|
||||
|
||||
if (!db.isOpen()) return;
|
||||
|
||||
// Before we overwrite anything, make sure the database is not corrupt
|
||||
QMutexLocker l(&mutex_);
|
||||
const bool ok = IntegrityCheck(db);
|
||||
|
||||
if (ok) {
|
||||
const bool ok = IntegrityCheck(db);
|
||||
if (ok && SchemaVersion(&db) == kSchemaVersion) {
|
||||
BackupFile(db.databaseName());
|
||||
}
|
||||
|
||||
|
||||
@@ -34,6 +34,9 @@
|
||||
#include <QSqlQuery>
|
||||
#include <QString>
|
||||
#include <QStringList>
|
||||
#if QT_VERSION >= QT_VERSION_CHECK(5, 14, 0)
|
||||
# include <QRecursiveMutex>
|
||||
#endif
|
||||
|
||||
class QThread;
|
||||
class Application;
|
||||
@@ -63,7 +66,12 @@ class Database : public QObject {
|
||||
QSqlDatabase Connect();
|
||||
void Close();
|
||||
bool CheckErrors(const QSqlQuery &query);
|
||||
|
||||
#if QT_VERSION >= QT_VERSION_CHECK(5, 14, 0)
|
||||
QRecursiveMutex *Mutex() { return &mutex_; }
|
||||
#else
|
||||
QMutex *Mutex() { return &mutex_; }
|
||||
#endif
|
||||
|
||||
void RecreateAttachedDb(const QString &database_name);
|
||||
void ExecSchemaCommands(QSqlDatabase &db, const QString &schema, int schema_version, bool in_transaction = false);
|
||||
@@ -106,7 +114,11 @@ class Database : public QObject {
|
||||
|
||||
QString directory_;
|
||||
QMutex connect_mutex_;
|
||||
#if QT_VERSION >= QT_VERSION_CHECK(5, 14, 0)
|
||||
QRecursiveMutex mutex_;
|
||||
#else
|
||||
QMutex mutex_;
|
||||
#endif
|
||||
|
||||
// This ID makes the QSqlDatabase name unique to the object as well as the thread
|
||||
int connection_id_;
|
||||
|
||||
@@ -1,49 +0,0 @@
|
||||
/*
|
||||
* Strawberry Music Player
|
||||
* This file was part of Clementine.
|
||||
* Copyright 2010, David Sansome <me@davidsansome.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 "config.h"
|
||||
|
||||
#include <QCoreApplication>
|
||||
#include <QObject>
|
||||
#include <QDBusInterface>
|
||||
#include <QDBusReply>
|
||||
#include <QString>
|
||||
|
||||
#include "dbusscreensaver.h"
|
||||
|
||||
DBusScreensaver::DBusScreensaver(const QString &service, const QString &path, const QString &interface)
|
||||
: service_(service), path_(path), interface_(interface) {}
|
||||
|
||||
void DBusScreensaver::Inhibit() {
|
||||
|
||||
QDBusInterface gnome_screensaver("org.gnome.ScreenSaver", "/", "org.gnome.ScreenSaver");
|
||||
QDBusReply<quint32> reply = gnome_screensaver.call("Inhibit", QCoreApplication::applicationName(), QObject::tr("Visualizations"));
|
||||
if (reply.isValid()) {
|
||||
cookie_ = reply.value();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
void DBusScreensaver::Uninhibit() {
|
||||
|
||||
QDBusInterface gnome_screensaver("org.gnome.ScreenSaver", "/", "org.gnome.ScreenSaver");
|
||||
gnome_screensaver.call("UnInhibit", cookie_);
|
||||
|
||||
}
|
||||
@@ -34,10 +34,11 @@
|
||||
|
||||
const int DeleteFiles::kBatchSize = 50;
|
||||
|
||||
DeleteFiles::DeleteFiles(TaskManager *task_manager, std::shared_ptr<MusicStorage> storage)
|
||||
DeleteFiles::DeleteFiles(TaskManager *task_manager, std::shared_ptr<MusicStorage> storage, const bool use_trash)
|
||||
: thread_(nullptr),
|
||||
task_manager_(task_manager),
|
||||
storage_(storage),
|
||||
use_trash_(use_trash),
|
||||
started_(false),
|
||||
task_id_(0),
|
||||
progress_(0) {
|
||||
@@ -112,6 +113,7 @@ void DeleteFiles::ProcessSomeFiles() {
|
||||
|
||||
MusicStorage::DeleteJob job;
|
||||
job.metadata_ = song;
|
||||
job.use_trash_ = use_trash_;
|
||||
|
||||
if (!storage_->DeleteFromStorage(job)) {
|
||||
songs_with_errors_ << song;
|
||||
|
||||
@@ -38,7 +38,7 @@ class DeleteFiles : public QObject {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit DeleteFiles(TaskManager *task_manager, std::shared_ptr<MusicStorage> storage);
|
||||
explicit DeleteFiles(TaskManager *task_manager, std::shared_ptr<MusicStorage> storage, const bool use_trash);
|
||||
~DeleteFiles() override;
|
||||
|
||||
static const int kBatchSize;
|
||||
@@ -59,6 +59,7 @@ signals:
|
||||
std::shared_ptr<MusicStorage> storage_;
|
||||
|
||||
SongList songs_;
|
||||
bool use_trash_;
|
||||
|
||||
bool started_;
|
||||
|
||||
|
||||