Compare commits
251 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
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
|
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:
|
install_debian_dependencies:
|
||||||
description: Install Debian dependencies
|
description: Install Debian dependencies
|
||||||
steps:
|
steps:
|
||||||
@@ -294,6 +234,7 @@ commands:
|
|||||||
command: >
|
command: >
|
||||||
apt-get update && apt-get install -y
|
apt-get update && apt-get install -y
|
||||||
build-essential
|
build-essential
|
||||||
|
dh-make
|
||||||
ssh
|
ssh
|
||||||
git
|
git
|
||||||
make
|
make
|
||||||
@@ -342,6 +283,7 @@ commands:
|
|||||||
command: >
|
command: >
|
||||||
apt-get update && apt-get install -y
|
apt-get update && apt-get install -y
|
||||||
build-essential
|
build-essential
|
||||||
|
dh-make
|
||||||
ssh
|
ssh
|
||||||
git
|
git
|
||||||
make
|
make
|
||||||
@@ -392,24 +334,6 @@ jobs:
|
|||||||
- build_source
|
- 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:
|
build_opensuse_lp151:
|
||||||
docker:
|
docker:
|
||||||
- image: opensuse/leap:15.1
|
- image: opensuse/leap:15.1
|
||||||
@@ -473,19 +397,6 @@ jobs:
|
|||||||
- build_rpm
|
- 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:
|
build_debian_buster:
|
||||||
docker:
|
docker:
|
||||||
- image: debian:buster
|
- image: debian:buster
|
||||||
@@ -543,10 +454,6 @@ workflows:
|
|||||||
only: /.*/
|
only: /.*/
|
||||||
|
|
||||||
|
|
||||||
- build_opensuse_tumbleweed:
|
|
||||||
filters:
|
|
||||||
tags:
|
|
||||||
only: /.*/
|
|
||||||
- build_opensuse_lp151:
|
- build_opensuse_lp151:
|
||||||
filters:
|
filters:
|
||||||
tags:
|
tags:
|
||||||
@@ -567,12 +474,6 @@ workflows:
|
|||||||
only: /.*/
|
only: /.*/
|
||||||
|
|
||||||
|
|
||||||
- build_mageia_7:
|
|
||||||
filters:
|
|
||||||
tags:
|
|
||||||
only: /.*/
|
|
||||||
|
|
||||||
|
|
||||||
- build_centos_8:
|
- build_centos_8:
|
||||||
filters:
|
filters:
|
||||||
tags:
|
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**
|
**Describe the bug**
|
||||||
A clear and concise description of what the bug is.
|
A clear and concise description of what the bug is.
|
||||||
321
.github/workflows/ccpp.yml
vendored
@@ -367,7 +367,7 @@ jobs:
|
|||||||
- name: Configure CMake
|
- name: Configure CMake
|
||||||
shell: bash
|
shell: bash
|
||||||
working-directory: build
|
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
|
- name: Build
|
||||||
working-directory: build
|
working-directory: build
|
||||||
run: cmake --build . --config $BUILD_TYPE
|
run: cmake --build . --config $BUILD_TYPE
|
||||||
@@ -451,6 +451,160 @@ jobs:
|
|||||||
run: rpmbuild -ba ../dist/unix/strawberry.spec
|
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:
|
build_centos_8:
|
||||||
name: Build CentOS 8
|
name: Build CentOS 8
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
@@ -541,89 +695,6 @@ jobs:
|
|||||||
run: rpmbuild -ba ../dist/unix/strawberry.spec
|
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:
|
build_debian_buster:
|
||||||
name: Build Debian Buster
|
name: Build Debian Buster
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
@@ -635,6 +706,7 @@ jobs:
|
|||||||
run: >
|
run: >
|
||||||
apt-get update && apt-get install -y
|
apt-get update && apt-get install -y
|
||||||
build-essential
|
build-essential
|
||||||
|
dh-make
|
||||||
ssh
|
ssh
|
||||||
git
|
git
|
||||||
make
|
make
|
||||||
@@ -691,6 +763,7 @@ jobs:
|
|||||||
run: >
|
run: >
|
||||||
apt-get update && apt-get install -y
|
apt-get update && apt-get install -y
|
||||||
build-essential
|
build-essential
|
||||||
|
dh-make
|
||||||
ssh
|
ssh
|
||||||
git
|
git
|
||||||
make
|
make
|
||||||
@@ -749,6 +822,7 @@ jobs:
|
|||||||
run: >
|
run: >
|
||||||
apt-get update && apt-get install -y
|
apt-get update && apt-get install -y
|
||||||
build-essential
|
build-essential
|
||||||
|
dh-make
|
||||||
ssh
|
ssh
|
||||||
git
|
git
|
||||||
make
|
make
|
||||||
@@ -810,6 +884,7 @@ jobs:
|
|||||||
run: >
|
run: >
|
||||||
apt-get update && apt-get install -y
|
apt-get update && apt-get install -y
|
||||||
build-essential
|
build-essential
|
||||||
|
dh-make
|
||||||
ssh
|
ssh
|
||||||
git
|
git
|
||||||
make
|
make
|
||||||
@@ -871,6 +946,7 @@ jobs:
|
|||||||
run: >
|
run: >
|
||||||
apt-get update && apt-get install -y
|
apt-get update && apt-get install -y
|
||||||
build-essential
|
build-essential
|
||||||
|
dh-make
|
||||||
ssh
|
ssh
|
||||||
git
|
git
|
||||||
make
|
make
|
||||||
@@ -924,8 +1000,10 @@ jobs:
|
|||||||
runs-on: macos-latest
|
runs-on: macos-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v1.2.0
|
- uses: actions/checkout@v1.2.0
|
||||||
- name: Unlink python
|
- name: Update
|
||||||
run: brew unlink python@2
|
run: brew update
|
||||||
|
- name: Upgrade
|
||||||
|
run: brew upgrade
|
||||||
- name: Install packages
|
- name: Install packages
|
||||||
run: >
|
run: >
|
||||||
brew install
|
brew install
|
||||||
@@ -977,10 +1055,14 @@ jobs:
|
|||||||
working-directory: build
|
working-directory: build
|
||||||
shell: bash
|
shell: bash
|
||||||
run: make install
|
run: make install
|
||||||
#- name: Create DMG
|
- name: Create DMG
|
||||||
# working-directory: build
|
working-directory: build
|
||||||
# shell: bash
|
shell: bash
|
||||||
# run: make dmg
|
run: make dmg
|
||||||
|
- uses: actions/upload-artifact@v2
|
||||||
|
with:
|
||||||
|
name: upload-macos
|
||||||
|
path: build/strawberry-*.dmg
|
||||||
|
|
||||||
|
|
||||||
build-windows:
|
build-windows:
|
||||||
@@ -1019,10 +1101,6 @@ jobs:
|
|||||||
working-directory: build
|
working-directory: build
|
||||||
run: make -j2
|
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
|
- name: Create directories
|
||||||
working-directory: build
|
working-directory: build
|
||||||
run: mkdir -p gio-modules platforms sqldrivers imageformats styles gstreamer-plugins nsisplugins
|
run: mkdir -p gio-modules platforms sqldrivers imageformats styles gstreamer-plugins nsisplugins
|
||||||
@@ -1099,13 +1177,9 @@ jobs:
|
|||||||
/usr/src/strawberry-mxe/usr/x86_64-w64-mingw32.shared/bin/gstreamer-1.0/libgstlibav.dll
|
/usr/src/strawberry-mxe/usr/x86_64-w64-mingw32.shared/bin/gstreamer-1.0/libgstlibav.dll
|
||||||
${GITHUB_WORKSPACE}/build/gstreamer-plugins/
|
${GITHUB_WORKSPACE}/build/gstreamer-plugins/
|
||||||
|
|
||||||
- name: Copy killproc.exe
|
- name: Copy extra binaries
|
||||||
working-directory: build
|
working-directory: build
|
||||||
run: cp /usr/src/strawberry-mxe/usr/x86_64-w64-mingw32.shared/bin/killproc.exe .
|
run: cp /usr/src/strawberry-mxe/usr/x86_64-w64-mingw32.shared/bin/{sqlite3.exe,killproc.exe,liborc-0.4-0.dll} .
|
||||||
|
|
||||||
- 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 .
|
|
||||||
|
|
||||||
- name: Copy dependencies
|
- name: Copy dependencies
|
||||||
working-directory: build
|
working-directory: build
|
||||||
@@ -1121,6 +1195,10 @@ jobs:
|
|||||||
-F ./gstreamer-plugins
|
-F ./gstreamer-plugins
|
||||||
-R /usr/src/strawberry-mxe/usr/x86_64-w64-mingw32.shared
|
-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
|
- name: Copy nsis files
|
||||||
working-directory: build
|
working-directory: build
|
||||||
run: cp ${GITHUB_WORKSPACE}/dist/windows/*.nsi ${GITHUB_WORKSPACE}/dist/windows/*.nsh ${GITHUB_WORKSPACE}/dist/windows/*.ico .
|
run: cp ${GITHUB_WORKSPACE}/dist/windows/*.nsi ${GITHUB_WORKSPACE}/dist/windows/*.nsh ${GITHUB_WORKSPACE}/dist/windows/*.ico .
|
||||||
@@ -1128,3 +1206,46 @@ jobs:
|
|||||||
- name: Build Windows installer
|
- name: Build Windows installer
|
||||||
working-directory: build
|
working-directory: build
|
||||||
run: makensis strawberry.nsi
|
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
|
||||||
|
|
||||||
|
|
||||||
|
build_snap:
|
||||||
|
name: Build Snap
|
||||||
|
runs-on: ubuntu-18.04
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v1.2.0
|
||||||
|
- uses: snapcore/action-build@v1
|
||||||
|
id: snapcraft
|
||||||
|
- uses: snapcore/action-publish@v1
|
||||||
|
if: github.ref == 'refs/heads/master'
|
||||||
|
with:
|
||||||
|
store_login: ${{ secrets.SNAP_STORE_LOGIN }}
|
||||||
|
snap: ${{ steps.snapcraft.outputs.snap }}
|
||||||
|
release: beta
|
||||||
|
- uses: actions/upload-artifact@v2
|
||||||
|
with:
|
||||||
|
name: release_snap
|
||||||
|
path: ${{ steps.snapcraft.outputs.snap }}
|
||||||
|
|||||||
59
.travis.yml
@@ -2,8 +2,6 @@ sudo: required
|
|||||||
language: C++
|
language: C++
|
||||||
os:
|
os:
|
||||||
- osx
|
- osx
|
||||||
services:
|
|
||||||
- docker
|
|
||||||
compiler:
|
compiler:
|
||||||
- gcc
|
- 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 ;
|
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 ;
|
chmod 600 ~/.ssh/id_rsa ;
|
||||||
fi
|
fi
|
||||||
- if [[ "$TRAVIS_OS_NAME" == "linux" ]]; then
|
- git fetch --unshallow
|
||||||
docker build -f Dockerfile -t strawberry-build . || travis_terminate 1;
|
- git pull
|
||||||
docker run --name build -itd strawberry-build /bin/bash || travis_terminate 1;
|
- brew update
|
||||||
docker exec build git clone https://github.com/strawberrymusicplayer/strawberry;
|
- travis_wait 120 brew upgrade || echo "Failed"
|
||||||
fi
|
- travis_wait 120 brew upgrade || echo "Failed"
|
||||||
- if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then
|
- brew install glib pkgconfig libffi protobuf protobuf-c qt gettext gnutls fftw sqlite chromaprint zlib taglib
|
||||||
git fetch --unshallow || travis_terminate 1;
|
- brew install gstreamer gst-plugins-base gst-plugins-good gst-plugins-bad gst-plugins-ugly gst-libav
|
||||||
git pull || travis_terminate 1;
|
- brew install libcdio libmtp
|
||||||
brew unlink python@2 || travis_terminate 1;
|
- brew install create-dmg
|
||||||
brew install glib pkgconfig libffi protobuf protobuf-c qt gettext gnutls fftw sqlite chromaprint zlib taglib;
|
- brew cask install sparkle
|
||||||
brew install gstreamer gst-plugins-base gst-plugins-good gst-plugins-bad gst-plugins-ugly gst-libav;
|
- sudo ln -s /usr/local/Caskroom/sparkle/$(ls /usr/local/Caskroom/sparkle | head -n1)/Sparkle.framework /Library/Frameworks/Sparkle.framework
|
||||||
brew install libcdio libmtp;
|
- sudo ln -s /usr/local/Caskroom/sparkle/$(ls /usr/local/Caskroom/sparkle | head -n1)/Sparkle.framework.dSYM /Library/Frameworks/Sparkle.framework.dSYM
|
||||||
brew install create-dmg;
|
- export Qt5_DIR=/usr/local/opt/qt5/lib/cmake
|
||||||
brew cask install sparkle;
|
- export Qt5LinguistTools_DIR=/usr/local/opt/qt5/lib/cmake/Qt5LinguistTools
|
||||||
sudo ln -s /usr/local/Caskroom/sparkle/$(ls /usr/local/Caskroom/sparkle | head -n1)/Sparkle.framework /Library/Frameworks/Sparkle.framework;
|
- ls /usr/local/lib/gstreamer-1.0
|
||||||
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
|
|
||||||
before_script:
|
before_script:
|
||||||
- if [[ "$TRAVIS_OS_NAME" == "linux" ]]; then docker exec build cmake -Hstrawberry -Bbuild ; fi
|
- mkdir build
|
||||||
- if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then mkdir build; cd build; cmake .. -DUSE_BUNDLE=ON ; fi
|
- cd build
|
||||||
|
- cmake .. -DUSE_BUNDLE=ON
|
||||||
script:
|
script:
|
||||||
- if [[ "$TRAVIS_OS_NAME" == "linux" ]]; then docker exec build make -C build -j8 ; fi
|
- make -j8
|
||||||
- if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then
|
- make install
|
||||||
make -j8 || travis_terminate 1;
|
- make dmg
|
||||||
make install || travis_terminate 1;
|
|
||||||
make dmg;
|
|
||||||
fi
|
|
||||||
after_success:
|
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_OS_NAME" == "osx" ]] && [[ "$CC_FOR_BUILD" == "gcc" ]] && [ -f ~/.ssh/id_rsa ]; then
|
||||||
if [[ "$TRAVIS_BRANCH" == "master" ]]; 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
|
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
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
|||||||
4
3rdparty/singleapplication/CMakeLists.txt
vendored
@@ -10,7 +10,7 @@ endif()
|
|||||||
|
|
||||||
set(SINGLEAPP-SOURCES singleapplication.cpp singleapplication_p.cpp)
|
set(SINGLEAPP-SOURCES singleapplication.cpp singleapplication_p.cpp)
|
||||||
set(SINGLEAPP-MOC-HEADERS singleapplication.h singleapplication_p.h)
|
set(SINGLEAPP-MOC-HEADERS singleapplication.h singleapplication_p.h)
|
||||||
if(WITH_QT6)
|
if(BUILD_WITH_QT6)
|
||||||
qt6_wrap_cpp(SINGLEAPP-SOURCES-MOC ${SINGLEAPP-MOC-HEADERS})
|
qt6_wrap_cpp(SINGLEAPP-SOURCES-MOC ${SINGLEAPP-MOC-HEADERS})
|
||||||
else()
|
else()
|
||||||
qt5_wrap_cpp(SINGLEAPP-SOURCES-MOC ${SINGLEAPP-MOC-HEADERS})
|
qt5_wrap_cpp(SINGLEAPP-SOURCES-MOC ${SINGLEAPP-MOC-HEADERS})
|
||||||
@@ -33,7 +33,7 @@ target_link_libraries(singleapplication PRIVATE
|
|||||||
|
|
||||||
set(SINGLECOREAPP-SOURCES singlecoreapplication.cpp singlecoreapplication_p.cpp)
|
set(SINGLECOREAPP-SOURCES singlecoreapplication.cpp singlecoreapplication_p.cpp)
|
||||||
set(SINGLECOREAPP-MOC-HEADERS singlecoreapplication.h singlecoreapplication_p.h)
|
set(SINGLECOREAPP-MOC-HEADERS singlecoreapplication.h singlecoreapplication_p.h)
|
||||||
if(WITH_QT6)
|
if(BUILD_WITH_QT6)
|
||||||
qt6_wrap_cpp(SINGLECOREAPP-SOURCES-MOC ${SINGLECOREAPP-MOC-HEADERS})
|
qt6_wrap_cpp(SINGLECOREAPP-SOURCES-MOC ${SINGLECOREAPP-MOC-HEADERS})
|
||||||
else()
|
else()
|
||||||
qt5_wrap_cpp(SINGLECOREAPP-SOURCES-MOC ${SINGLECOREAPP-MOC-HEADERS})
|
qt5_wrap_cpp(SINGLECOREAPP-SOURCES-MOC ${SINGLECOREAPP-MOC-HEADERS})
|
||||||
|
|||||||
186
3rdparty/singleapplication/singleapplication.cpp
vendored
@@ -1,6 +1,6 @@
|
|||||||
// The MIT License (MIT)
|
// 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
|
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
// of this software and associated documentation files (the "Software"), to deal
|
// of this software and associated documentation files (the "Software"), to deal
|
||||||
@@ -35,113 +35,131 @@
|
|||||||
#include <limits>
|
#include <limits>
|
||||||
|
|
||||||
#include <QtGlobal>
|
#include <QtGlobal>
|
||||||
#include <QCoreApplication>
|
#include <QApplication>
|
||||||
#include <QThread>
|
#include <QThread>
|
||||||
#include <QSharedMemory>
|
#include <QSharedMemory>
|
||||||
#include <QLocalSocket>
|
#include <QLocalSocket>
|
||||||
#include <QByteArray>
|
#include <QByteArray>
|
||||||
#include <QElapsedTimer>
|
#include <QElapsedTimer>
|
||||||
#include <QtDebug>
|
#include <QtDebug>
|
||||||
#if QT_VERSION >= QT_VERSION_CHECK(5, 10, 0)
|
|
||||||
# include <QRandomGenerator>
|
|
||||||
#else
|
|
||||||
# include <QDateTime>
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#include "singleapplication.h"
|
#include "singleapplication.h"
|
||||||
#include "singleapplication_p.h"
|
#include "singleapplication_p.h"
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Constructor.
|
* @brief Constructor. Checks and fires up LocalServer or closes the program if another instance already exists
|
||||||
* Checks and fires up LocalServer or closes the program if another instance already exists
|
|
||||||
* @param argc
|
* @param argc
|
||||||
* @param argv
|
* @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)
|
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);
|
Q_D(SingleApplication);
|
||||||
|
|
||||||
// Store the current mode of the program
|
// 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
|
// Generating an application ID used for identifying the shared memory block and QLocalServer
|
||||||
d->genBlockServerName();
|
d->genBlockServerName();
|
||||||
|
|
||||||
|
// To mitigate QSharedMemory issues with large amount of processes attempting to attach at the same time
|
||||||
|
d->randomSleep();
|
||||||
|
|
||||||
#ifdef Q_OS_UNIX
|
#ifdef Q_OS_UNIX
|
||||||
// By explicitly attaching it and then deleting it we make sure that the
|
// By explicitly attaching it and then deleting it we make sure that the memory is deleted even after the process has crashed on Unix.
|
||||||
// memory is deleted even after the process has crashed on Unix.
|
d->memory_ = new QSharedMemory(d->blockServerName_);
|
||||||
d->memory = new QSharedMemory(d->blockServerName);
|
d->memory_->attach();
|
||||||
d->memory->attach();
|
delete d->memory_;
|
||||||
delete d->memory;
|
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
// Guarantee thread safe behaviour with a shared memory block.
|
// 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
|
// Create a shared memory block
|
||||||
if (d->memory->create(sizeof(InstancesInfo))) {
|
if (d->memory_->create(sizeof(InstancesInfo))) {
|
||||||
// Initialize the shared memory block
|
// 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->initializeMemoryBlock();
|
||||||
d->memory->unlock();
|
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
// Attempt to attach to the memory segment
|
if (d->memory_->error() == QSharedMemory::AlreadyExists) {
|
||||||
if (! d->memory->attach()) {
|
// Attempt to attach to the memory segment
|
||||||
qCritical() << "SingleApplication: Unable to attach to shared memory block.";
|
if (!d->memory_->attach()) {
|
||||||
qCritical() << d->memory->errorString();
|
qCritical() << "SingleApplication: Unable to attach to shared memory block.";
|
||||||
delete d;
|
abortSafely();
|
||||||
::exit(EXIT_FAILURE);
|
}
|
||||||
|
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;
|
QElapsedTimer time;
|
||||||
time.start();
|
time.start();
|
||||||
|
|
||||||
// Make sure the shared memory block is initialised and in consistent state
|
// Make sure the shared memory block is initialised and in consistent state
|
||||||
while (true) {
|
forever {
|
||||||
d->memory->lock();
|
// If the shared memory block's checksum is valid continue
|
||||||
|
|
||||||
if (d->blockChecksum() == inst->checksum) break;
|
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) {
|
if (time.elapsed() > 5000) {
|
||||||
qWarning() << "SingleApplication: Shared memory block has been in an inconsistent state from more than 5s. Assuming primary instance failure.";
|
qWarning() << "SingleApplication: Shared memory block has been in an inconsistent state from more than 5s. Assuming primary instance failure.";
|
||||||
d->initializeMemoryBlock();
|
d->initializeMemoryBlock();
|
||||||
}
|
}
|
||||||
|
|
||||||
d->memory->unlock();
|
// 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
|
||||||
// Random sleep here limits the probability of a collision between two racing apps
|
if (!d->memory_->unlock()) {
|
||||||
#if QT_VERSION >= QT_VERSION_CHECK(5, 10, 0)
|
qDebug() << "SingleApplication: Unable to unlock memory for random wait.";
|
||||||
QThread::sleep(QRandomGenerator::global()->bounded(8u, 18u));
|
qDebug() << d->memory_->errorString();
|
||||||
#else
|
}
|
||||||
qsrand(QDateTime::currentMSecsSinceEpoch() % std::numeric_limits<uint>::max());
|
d->randomSleep();
|
||||||
QThread::sleep(8 + static_cast<unsigned long>(static_cast <float>(qrand()) / RAND_MAX * 10));
|
if (!d->memory_->lock()) {
|
||||||
#endif
|
qCritical() << "SingleApplication: Unable to lock memory after random wait.";
|
||||||
|
abortSafely();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (inst->primary == false) {
|
if (inst->primary == false) {
|
||||||
d->startPrimary();
|
d->startPrimary();
|
||||||
d->memory->unlock();
|
if (!d->memory_->unlock()) {
|
||||||
|
qDebug() << "SingleApplication: Unable to unlock memory after primary start.";
|
||||||
|
qDebug() << d->memory_->errorString();
|
||||||
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check if another instance can be started
|
// Check if another instance can be started
|
||||||
if (allowSecondary) {
|
if (allowSecondary) {
|
||||||
inst->secondary += 1;
|
|
||||||
inst->checksum = d->blockChecksum();
|
|
||||||
d->instanceNumber = inst->secondary;
|
|
||||||
d->startSecondary();
|
d->startSecondary();
|
||||||
if (d->options & Mode::SecondaryNotification) {
|
if (d->options_ & Mode::SecondaryNotification) {
|
||||||
d->connectToPrimary(timeout, SingleApplicationPrivate::SecondaryInstance);
|
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;
|
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);
|
d->connectToPrimary(timeout, SingleApplicationPrivate::NewInstance);
|
||||||
|
|
||||||
@@ -151,34 +169,73 @@ SingleApplication::SingleApplication(int &argc, char *argv[], bool allowSecondar
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Destructor
|
|
||||||
*/
|
|
||||||
SingleApplication::~SingleApplication() {
|
SingleApplication::~SingleApplication() {
|
||||||
Q_D(SingleApplication);
|
Q_D(SingleApplication);
|
||||||
delete d;
|
delete d;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks if the current application instance is primary.
|
||||||
|
* @return Returns true if the instance is primary, false otherwise.
|
||||||
|
*/
|
||||||
bool SingleApplication::isPrimary() {
|
bool SingleApplication::isPrimary() {
|
||||||
Q_D(SingleApplication);
|
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() {
|
bool SingleApplication::isSecondary() {
|
||||||
Q_D(SingleApplication);
|
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() {
|
quint32 SingleApplication::instanceId() {
|
||||||
Q_D(SingleApplication);
|
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() {
|
qint64 SingleApplication::primaryPid() {
|
||||||
Q_D(SingleApplication);
|
Q_D(SingleApplication);
|
||||||
return d->primaryPid();
|
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) {
|
bool SingleApplication::sendMessage(QByteArray message, int timeout) {
|
||||||
|
|
||||||
Q_D(SingleApplication);
|
Q_D(SingleApplication);
|
||||||
@@ -187,11 +244,26 @@ bool SingleApplication::sendMessage(QByteArray message, int timeout) {
|
|||||||
if (isPrimary()) return false;
|
if (isPrimary()) return false;
|
||||||
|
|
||||||
// Make sure the socket is connected
|
// Make sure the socket is connected
|
||||||
d->connectToPrimary(timeout, SingleApplicationPrivate::Reconnect);
|
if (!d->connectToPrimary(timeout, SingleApplicationPrivate::Reconnect))
|
||||||
|
return false;
|
||||||
|
|
||||||
d->socket->write(message);
|
d->socket_->write(message);
|
||||||
bool dataWritten = d->socket->waitForBytesWritten(timeout);
|
const bool dataWritten = d->socket_->waitForBytesWritten(timeout);
|
||||||
d->socket->flush();
|
d->socket_->flush();
|
||||||
return dataWritten;
|
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)
|
// 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
|
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
// of this software and associated documentation files (the "Software"), to deal
|
// of this software and associated documentation files (the "Software"), to deal
|
||||||
@@ -42,16 +42,15 @@
|
|||||||
class SingleApplicationPrivate;
|
class SingleApplicationPrivate;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief The SingleApplication class handles multipe instances of the same
|
* @brief The SingleApplication class handles multipe instances of the same Application
|
||||||
* Application
|
* @see QApplication
|
||||||
* @see QCoreApplication
|
|
||||||
*/
|
*/
|
||||||
class SingleApplication : public QApplication {
|
class SingleApplication : public QApplication {
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
|
|
||||||
typedef QApplication app_t;
|
typedef QApplication app_t;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
/**
|
/**
|
||||||
* @brief Mode of operation of SingleApplication.
|
* @brief Mode of operation of SingleApplication.
|
||||||
* Whether the block should be user-wide or system-wide and whether the
|
* Whether the block should be user-wide or system-wide and whether the
|
||||||
@@ -63,11 +62,11 @@ public:
|
|||||||
* @enum
|
* @enum
|
||||||
*/
|
*/
|
||||||
enum Mode {
|
enum Mode {
|
||||||
User = 1 << 0,
|
User = 1 << 0,
|
||||||
System = 1 << 1,
|
System = 1 << 1,
|
||||||
SecondaryNotification = 1 << 2,
|
SecondaryNotification = 1 << 2,
|
||||||
ExcludeAppVersion = 1 << 3,
|
ExcludeAppVersion = 1 << 3,
|
||||||
ExcludeAppPath = 1 << 4
|
ExcludeAppPath = 1 << 4
|
||||||
};
|
};
|
||||||
Q_DECLARE_FLAGS(Options, Mode)
|
Q_DECLARE_FLAGS(Options, Mode)
|
||||||
|
|
||||||
@@ -91,7 +90,7 @@ public:
|
|||||||
* Usually 4*timeout would be the worst case (fail) scenario.
|
* Usually 4*timeout would be the worst case (fail) scenario.
|
||||||
* @see See the corresponding QAPPLICATION_CLASS constructor for reference
|
* @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;
|
~SingleApplication() override;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -118,6 +117,18 @@ public:
|
|||||||
*/
|
*/
|
||||||
qint64 primaryPid();
|
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.
|
* @brief Sends a message to the primary instance. Returns true on success.
|
||||||
* @param {int} timeout - Timeout for connecting
|
* @param {int} timeout - Timeout for connecting
|
||||||
@@ -125,18 +136,18 @@ public:
|
|||||||
* @note sendMessage() will return false if invoked from the primary
|
* @note sendMessage() will return false if invoked from the primary
|
||||||
* instance.
|
* instance.
|
||||||
*/
|
*/
|
||||||
bool sendMessage( QByteArray message, int timeout = 1000 );
|
bool sendMessage(QByteArray message, int timeout = 1000);
|
||||||
|
|
||||||
signals:
|
signals:
|
||||||
void instanceStarted();
|
void instanceStarted();
|
||||||
void receivedMessage( quint32 instanceId, QByteArray message );
|
void receivedMessage(quint32 instanceId, QByteArray message);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
SingleApplicationPrivate *d_ptr;
|
SingleApplicationPrivate *d_ptr;
|
||||||
Q_DECLARE_PRIVATE(SingleApplication)
|
Q_DECLARE_PRIVATE(SingleApplication)
|
||||||
|
void abortSafely();
|
||||||
};
|
};
|
||||||
|
|
||||||
Q_DECLARE_OPERATORS_FOR_FLAGS(SingleApplication::Options)
|
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)
|
// 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
|
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
// of this software and associated documentation files (the "Software"), to deal
|
// of this software and associated documentation files (the "Software"), to deal
|
||||||
@@ -44,6 +44,8 @@
|
|||||||
# include <pwd.h>
|
# include <pwd.h>
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
#include <QObject>
|
||||||
|
#include <QThread>
|
||||||
#include <QIODevice>
|
#include <QIODevice>
|
||||||
#include <QSharedMemory>
|
#include <QSharedMemory>
|
||||||
#include <QByteArray>
|
#include <QByteArray>
|
||||||
@@ -52,6 +54,12 @@
|
|||||||
#include <QLocalServer>
|
#include <QLocalServer>
|
||||||
#include <QLocalSocket>
|
#include <QLocalSocket>
|
||||||
#include <QDir>
|
#include <QDir>
|
||||||
|
#include <QElapsedTimer>
|
||||||
|
#if QT_VERSION >= QT_VERSION_CHECK(5, 10, 0)
|
||||||
|
# include <QRandomGenerator>
|
||||||
|
#else
|
||||||
|
# include <QDateTime>
|
||||||
|
#endif
|
||||||
|
|
||||||
#include "singleapplication.h"
|
#include "singleapplication.h"
|
||||||
#include "singleapplication_p.h"
|
#include "singleapplication_p.h"
|
||||||
@@ -61,33 +69,73 @@
|
|||||||
# include <lmcons.h>
|
# include <lmcons.h>
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
SingleApplicationPrivate::SingleApplicationPrivate(SingleApplication *_q_ptr)
|
SingleApplicationPrivate::SingleApplicationPrivate(SingleApplication *ptr)
|
||||||
: q_ptr(_q_ptr),
|
: q_ptr(ptr),
|
||||||
memory(nullptr),
|
memory_(nullptr),
|
||||||
socket(nullptr),
|
socket_(nullptr),
|
||||||
server(nullptr),
|
server_(nullptr),
|
||||||
instanceNumber(-1)
|
instanceNumber_(-1) {}
|
||||||
{}
|
|
||||||
|
|
||||||
SingleApplicationPrivate::~SingleApplicationPrivate() {
|
SingleApplicationPrivate::~SingleApplicationPrivate() {
|
||||||
|
|
||||||
if (socket != nullptr) {
|
if (socket_ != nullptr) {
|
||||||
socket->close();
|
socket_->close();
|
||||||
delete socket;
|
delete socket_;
|
||||||
|
socket_ = nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
memory->lock();
|
if (memory_ != nullptr) {
|
||||||
InstancesInfo* inst = static_cast<InstancesInfo*>(memory->data());
|
memory_->lock();
|
||||||
if (server != nullptr) {
|
InstancesInfo *inst = static_cast<InstancesInfo*>(memory_->data());
|
||||||
server->close();
|
if (server_ != nullptr) {
|
||||||
delete server;
|
server_->close();
|
||||||
inst->primary = false;
|
delete server_;
|
||||||
inst->primaryPid = -1;
|
inst->primary = false;
|
||||||
inst->checksum = blockChecksum();
|
inst->primaryPid = -1;
|
||||||
}
|
inst->primaryUser[0] = '\0';
|
||||||
memory->unlock();
|
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::organizationName().toUtf8());
|
||||||
appData.addData(SingleApplication::app_t::organizationDomain().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());
|
appData.addData(SingleApplication::app_t::applicationVersion().toUtf8());
|
||||||
}
|
}
|
||||||
|
|
||||||
if (! (options & SingleApplication::Mode::ExcludeAppPath)) {
|
if (!(options_ & SingleApplication::Mode::ExcludeAppPath)) {
|
||||||
#ifdef Q_OS_WIN
|
#ifdef Q_OS_WIN
|
||||||
appData.addData(SingleApplication::app_t::applicationFilePath().toLower().toUtf8());
|
appData.addData(SingleApplication::app_t::applicationFilePath().toLower().toUtf8());
|
||||||
#else
|
#else
|
||||||
@@ -112,42 +160,22 @@ void SingleApplicationPrivate::genBlockServerName() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// User level block requires a user specific data in the hash
|
// User level block requires a user specific data in the hash
|
||||||
if (options & SingleApplication::Mode::User) {
|
if (options_ & SingleApplication::Mode::User) {
|
||||||
#ifdef Q_OS_UNIX
|
appData.addData(getUsername().toUtf8());
|
||||||
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
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Replace the backslash in RFC 2045 Base64 [a-zA-Z0-9+/=] to comply with server naming requirements.
|
// 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() {
|
void SingleApplicationPrivate::initializeMemoryBlock() {
|
||||||
|
|
||||||
InstancesInfo* inst = static_cast<InstancesInfo*>(memory->data());
|
InstancesInfo *inst = static_cast<InstancesInfo*>(memory_->data());
|
||||||
inst->primary = false;
|
inst->primary = false;
|
||||||
inst->secondary = 0;
|
inst->secondary = 0;
|
||||||
inst->primaryPid = -1;
|
inst->primaryPid = -1;
|
||||||
|
inst->primaryUser[0] = '\0';
|
||||||
inst->checksum = blockChecksum();
|
inst->checksum = blockChecksum();
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -156,133 +184,161 @@ void SingleApplicationPrivate::startPrimary() {
|
|||||||
|
|
||||||
Q_Q(SingleApplication);
|
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
|
// Reset the number of connections
|
||||||
InstancesInfo* inst = static_cast <InstancesInfo*>(memory->data());
|
InstancesInfo *inst = static_cast<InstancesInfo*>(memory_->data());
|
||||||
|
|
||||||
inst->primary = true;
|
inst->primary = true;
|
||||||
inst->primaryPid = q->applicationPid();
|
inst->primaryPid = q->applicationPid();
|
||||||
|
qstrncpy(inst->primaryUser, getUsername().toUtf8().data(), sizeof(inst->primaryUser));
|
||||||
inst->checksum = blockChecksum();
|
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.
|
// Connect to the Local Server of the Primary Instance if not already connected.
|
||||||
if (socket == nullptr) {
|
if (socket_ == nullptr) {
|
||||||
socket = new QLocalSocket();
|
socket_ = new QLocalSocket();
|
||||||
}
|
}
|
||||||
|
|
||||||
// If already connected - we are done;
|
if (socket_->state() == QLocalSocket::ConnectedState) return true;
|
||||||
if (socket->state() == QLocalSocket::ConnectedState)
|
|
||||||
return;
|
|
||||||
|
|
||||||
// If not connect
|
if (socket_->state() != QLocalSocket::ConnectedState) {
|
||||||
if (socket->state() == QLocalSocket::UnconnectedState ||
|
|
||||||
socket->state() == QLocalSocket::ClosingState) {
|
|
||||||
socket->connectToServer(blockServerName);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Wait for being connected
|
forever {
|
||||||
if (socket->state() == QLocalSocket::ConnectingState) {
|
randomSleep();
|
||||||
socket->waitForConnected(msecs);
|
|
||||||
|
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
|
// Initialisation message according to the SingleApplication protocol
|
||||||
if (socket->state() == QLocalSocket::ConnectedState) {
|
QByteArray initMsg;
|
||||||
// Notify the parent that a new instance had been started;
|
QDataStream writeStream(&initMsg, QIODevice::WriteOnly);
|
||||||
QByteArray initMsg;
|
writeStream.setVersion(QDataStream::Qt_5_8);
|
||||||
QDataStream writeStream(&initMsg, QIODevice::WriteOnly);
|
|
||||||
|
|
||||||
writeStream.setVersion(QDataStream::Qt_5_6);
|
writeStream << blockServerName_.toLatin1();
|
||||||
|
writeStream << static_cast<quint8>(connectionType);
|
||||||
|
writeStream << instanceNumber_;
|
||||||
|
|
||||||
writeStream << blockServerName.toLatin1();
|
#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
|
||||||
writeStream << static_cast<quint8>(connectionType);
|
quint16 checksum = qChecksum(QByteArray(initMsg, static_cast<quint32>(initMsg.length())));
|
||||||
writeStream << instanceNumber;
|
#else
|
||||||
quint16 checksum = qChecksum(initMsg.constData(), static_cast<quint32>(initMsg.length()));
|
quint16 checksum = qChecksum(initMsg.constData(), static_cast<quint32>(initMsg.length()));
|
||||||
writeStream << checksum;
|
#endif
|
||||||
|
|
||||||
// The header indicates the message length that follows
|
writeStream << checksum;
|
||||||
QByteArray header;
|
|
||||||
QDataStream headerStream(&header, QIODevice::WriteOnly);
|
|
||||||
|
|
||||||
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);
|
return result;
|
||||||
socket->write(initMsg);
|
|
||||||
socket->flush();
|
|
||||||
socket->waitForBytesWritten(msecs);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
quint16 SingleApplicationPrivate::blockChecksum() {
|
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 SingleApplicationPrivate::primaryPid() {
|
||||||
|
|
||||||
qint64 pid;
|
memory_->lock();
|
||||||
|
InstancesInfo *inst = static_cast<InstancesInfo*>(memory_->data());
|
||||||
memory->lock();
|
qint64 pid = inst->primaryPid;
|
||||||
InstancesInfo* inst = static_cast<InstancesInfo*>(memory->data());
|
memory_->unlock();
|
||||||
pid = inst->primaryPid;
|
|
||||||
memory->unlock();
|
|
||||||
|
|
||||||
return pid;
|
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
|
* @brief Executed when a connection has been made to the LocalServer
|
||||||
*/
|
*/
|
||||||
void SingleApplicationPrivate::slotConnectionEstablished() {
|
void SingleApplicationPrivate::slotConnectionEstablished() {
|
||||||
|
|
||||||
QLocalSocket *nextConnSocket = server->nextPendingConnection();
|
QLocalSocket *nextConnSocket = server_->nextPendingConnection();
|
||||||
connectionMap.insert(nextConnSocket, ConnectionInfo());
|
connectionMap_.insert(nextConnSocket, ConnectionInfo());
|
||||||
|
|
||||||
QObject::connect(nextConnSocket, &QLocalSocket::aboutToClose,
|
QObject::connect(nextConnSocket, &QLocalSocket::aboutToClose, [nextConnSocket, this]() {
|
||||||
[nextConnSocket, this]() {
|
auto &info = connectionMap_[nextConnSocket];
|
||||||
auto &info = connectionMap[nextConnSocket];
|
Q_EMIT this->slotClientConnectionClosed(nextConnSocket, info.instanceId);
|
||||||
Q_EMIT this->slotClientConnectionClosed(nextConnSocket, info.instanceId);
|
});
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
QObject::connect(nextConnSocket, &QLocalSocket::disconnected,
|
QObject::connect(nextConnSocket, &QLocalSocket::disconnected, [nextConnSocket, this]() {
|
||||||
[nextConnSocket, this](){
|
connectionMap_.remove(nextConnSocket);
|
||||||
connectionMap.remove(nextConnSocket);
|
nextConnSocket->deleteLater();
|
||||||
nextConnSocket->deleteLater();
|
});
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
QObject::connect(nextConnSocket, &QLocalSocket::readyRead,
|
QObject::connect(nextConnSocket, &QLocalSocket::readyRead, [nextConnSocket, this]() {
|
||||||
[nextConnSocket, this]() {
|
auto &info = connectionMap_[nextConnSocket];
|
||||||
auto &info = connectionMap[nextConnSocket];
|
switch (info.stage) {
|
||||||
switch(info.stage) {
|
|
||||||
case StageHeader:
|
case StageHeader:
|
||||||
readInitMessageHeader(nextConnSocket);
|
readInitMessageHeader(nextConnSocket);
|
||||||
break;
|
break;
|
||||||
@@ -294,15 +350,14 @@ void SingleApplicationPrivate::slotConnectionEstablished() {
|
|||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
break;
|
break;
|
||||||
};
|
};
|
||||||
}
|
});
|
||||||
);
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void SingleApplicationPrivate::readInitMessageHeader(QLocalSocket *sock) {
|
void SingleApplicationPrivate::readInitMessageHeader(QLocalSocket *sock) {
|
||||||
|
|
||||||
if (!connectionMap.contains(sock)) {
|
if (!connectionMap_.contains(sock)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -311,13 +366,12 @@ void SingleApplicationPrivate::readInitMessageHeader(QLocalSocket *sock) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
QDataStream headerStream(sock);
|
QDataStream headerStream(sock);
|
||||||
|
headerStream.setVersion(QDataStream::Qt_5_8);
|
||||||
headerStream.setVersion(QDataStream::Qt_5_6);
|
|
||||||
|
|
||||||
// Read the header to know the message length
|
// Read the header to know the message length
|
||||||
quint64 msgLen = 0;
|
quint64 msgLen = 0;
|
||||||
headerStream >> msgLen;
|
headerStream >> msgLen;
|
||||||
ConnectionInfo &info = connectionMap[sock];
|
ConnectionInfo &info = connectionMap_[sock];
|
||||||
info.stage = StageBody;
|
info.stage = StageBody;
|
||||||
info.msgLen = msgLen;
|
info.msgLen = msgLen;
|
||||||
|
|
||||||
@@ -331,11 +385,11 @@ void SingleApplicationPrivate::readInitMessageBody(QLocalSocket *sock) {
|
|||||||
|
|
||||||
Q_Q(SingleApplication);
|
Q_Q(SingleApplication);
|
||||||
|
|
||||||
if (!connectionMap.contains(sock)) {
|
if (!connectionMap_.contains(sock)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
ConnectionInfo &info = connectionMap[sock];
|
ConnectionInfo &info = connectionMap_[sock];
|
||||||
if (sock->bytesAvailable() < static_cast<qint64>(info.msgLen)) {
|
if (sock->bytesAvailable() < static_cast<qint64>(info.msgLen)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -343,8 +397,7 @@ void SingleApplicationPrivate::readInitMessageBody(QLocalSocket *sock) {
|
|||||||
// Read the message body
|
// Read the message body
|
||||||
QByteArray msgBytes = sock->read(info.msgLen);
|
QByteArray msgBytes = sock->read(info.msgLen);
|
||||||
QDataStream readStream(msgBytes);
|
QDataStream readStream(msgBytes);
|
||||||
|
readStream.setVersion(QDataStream::Qt_5_8);
|
||||||
readStream.setVersion(QDataStream::Qt_5_6);
|
|
||||||
|
|
||||||
// server name
|
// server name
|
||||||
QByteArray latin1Name;
|
QByteArray latin1Name;
|
||||||
@@ -354,7 +407,7 @@ void SingleApplicationPrivate::readInitMessageBody(QLocalSocket *sock) {
|
|||||||
ConnectionType connectionType = InvalidConnection;
|
ConnectionType connectionType = InvalidConnection;
|
||||||
quint8 connTypeVal = InvalidConnection;
|
quint8 connTypeVal = InvalidConnection;
|
||||||
readStream >> connTypeVal;
|
readStream >> connTypeVal;
|
||||||
connectionType = static_cast <ConnectionType>(connTypeVal);
|
connectionType = static_cast<ConnectionType>(connTypeVal);
|
||||||
|
|
||||||
// instance id
|
// instance id
|
||||||
quint32 instanceId = 0;
|
quint32 instanceId = 0;
|
||||||
@@ -364,9 +417,13 @@ void SingleApplicationPrivate::readInitMessageBody(QLocalSocket *sock) {
|
|||||||
quint16 msgChecksum = 0;
|
quint16 msgChecksum = 0;
|
||||||
readStream >> msgChecksum;
|
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)));
|
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) {
|
if (!isValid) {
|
||||||
sock->close();
|
sock->close();
|
||||||
@@ -376,7 +433,7 @@ void SingleApplicationPrivate::readInitMessageBody(QLocalSocket *sock) {
|
|||||||
info.instanceId = instanceId;
|
info.instanceId = instanceId;
|
||||||
info.stage = StageConnected;
|
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();
|
Q_EMIT q->instanceStarted();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -395,7 +452,19 @@ void SingleApplicationPrivate::slotDataAvailable(QLocalSocket *dataSocket, const
|
|||||||
|
|
||||||
void SingleApplicationPrivate::slotClientConnectionClosed(QLocalSocket *closedSocket, const quint32 instanceId) {
|
void SingleApplicationPrivate::slotClientConnectionClosed(QLocalSocket *closedSocket, const quint32 instanceId) {
|
||||||
|
|
||||||
if (closedSocket->bytesAvailable() > 0)
|
if (closedSocket->bytesAvailable() > 0) {
|
||||||
Q_EMIT slotDataAvailable(closedSocket, instanceId);
|
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)
|
// The MIT License (MIT)
|
||||||
//
|
//
|
||||||
// Copyright (c) Itay Grudev 2015 - 2016
|
// Copyright (c) Itay Grudev 2015 - 2020
|
||||||
//
|
//
|
||||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
// of this software and associated documentation files (the "Software"), to deal
|
// of this software and associated documentation files (the "Software"), to deal
|
||||||
@@ -36,6 +36,7 @@
|
|||||||
|
|
||||||
#include <QtGlobal>
|
#include <QtGlobal>
|
||||||
#include <QObject>
|
#include <QObject>
|
||||||
|
#include <QString>
|
||||||
#include <QMap>
|
#include <QMap>
|
||||||
|
|
||||||
#include "singleapplication.h"
|
#include "singleapplication.h"
|
||||||
@@ -48,6 +49,7 @@ struct InstancesInfo {
|
|||||||
bool primary;
|
bool primary;
|
||||||
quint32 secondary;
|
quint32 secondary;
|
||||||
qint64 primaryPid;
|
qint64 primaryPid;
|
||||||
|
char primaryUser[128];
|
||||||
quint16 checksum;
|
quint16 checksum;
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -60,6 +62,7 @@ struct ConnectionInfo {
|
|||||||
|
|
||||||
class SingleApplicationPrivate : public QObject {
|
class SingleApplicationPrivate : public QObject {
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
|
|
||||||
public:
|
public:
|
||||||
enum ConnectionType : quint8 {
|
enum ConnectionType : quint8 {
|
||||||
InvalidConnection = 0,
|
InvalidConnection = 0,
|
||||||
@@ -74,27 +77,30 @@ class SingleApplicationPrivate : public QObject {
|
|||||||
};
|
};
|
||||||
Q_DECLARE_PUBLIC(SingleApplication)
|
Q_DECLARE_PUBLIC(SingleApplication)
|
||||||
|
|
||||||
explicit SingleApplicationPrivate(SingleApplication *_q_ptr);
|
explicit SingleApplicationPrivate(SingleApplication *ptr);
|
||||||
~SingleApplicationPrivate() override;
|
~SingleApplicationPrivate() override;
|
||||||
|
|
||||||
|
QString getUsername();
|
||||||
void genBlockServerName();
|
void genBlockServerName();
|
||||||
void initializeMemoryBlock();
|
void initializeMemoryBlock();
|
||||||
void startPrimary();
|
void startPrimary();
|
||||||
void startSecondary();
|
void startSecondary();
|
||||||
void connectToPrimary(const int msecs, const ConnectionType connectionType);
|
bool connectToPrimary(const int msecs, const ConnectionType connectionType);
|
||||||
quint16 blockChecksum();
|
quint16 blockChecksum();
|
||||||
qint64 primaryPid();
|
qint64 primaryPid();
|
||||||
|
QString primaryUser();
|
||||||
void readInitMessageHeader(QLocalSocket *socket);
|
void readInitMessageHeader(QLocalSocket *socket);
|
||||||
void readInitMessageBody(QLocalSocket *socket);
|
void readInitMessageBody(QLocalSocket *socket);
|
||||||
|
void randomSleep();
|
||||||
|
|
||||||
SingleApplication *q_ptr;
|
SingleApplication *q_ptr;
|
||||||
QSharedMemory *memory;
|
QSharedMemory *memory_;
|
||||||
QLocalSocket *socket;
|
QLocalSocket *socket_;
|
||||||
QLocalServer *server;
|
QLocalServer *server_;
|
||||||
quint32 instanceNumber;
|
quint32 instanceNumber_;
|
||||||
QString blockServerName;
|
QString blockServerName_;
|
||||||
SingleApplication::Options options;
|
SingleApplication::Options options_;
|
||||||
QMap<QLocalSocket*, ConnectionInfo> connectionMap;
|
QMap<QLocalSocket*, ConnectionInfo> connectionMap_;
|
||||||
|
|
||||||
public slots:
|
public slots:
|
||||||
void slotConnectionEstablished();
|
void slotConnectionEstablished();
|
||||||
|
|||||||
188
3rdparty/singleapplication/singlecoreapplication.cpp
vendored
@@ -1,6 +1,6 @@
|
|||||||
// The MIT License (MIT)
|
// 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
|
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
// of this software and associated documentation files (the "Software"), to deal
|
// of this software and associated documentation files (the "Software"), to deal
|
||||||
@@ -42,106 +42,124 @@
|
|||||||
#include <QByteArray>
|
#include <QByteArray>
|
||||||
#include <QElapsedTimer>
|
#include <QElapsedTimer>
|
||||||
#include <QtDebug>
|
#include <QtDebug>
|
||||||
#if QT_VERSION >= QT_VERSION_CHECK(5, 10, 0)
|
|
||||||
# include <QRandomGenerator>
|
|
||||||
#else
|
|
||||||
# include <QDateTime>
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#include "singlecoreapplication.h"
|
#include "singlecoreapplication.h"
|
||||||
#include "singlecoreapplication_p.h"
|
#include "singlecoreapplication_p.h"
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Constructor. Checks and fires up LocalServer or closes the program
|
* @brief Constructor. Checks and fires up LocalServer or closes the program if another instance already exists
|
||||||
* if another instance already exists
|
|
||||||
* @param argc
|
* @param argc
|
||||||
* @param argv
|
* @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)
|
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);
|
Q_D(SingleCoreApplication);
|
||||||
|
|
||||||
// Store the current mode of the program
|
// 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
|
// Generating an application ID used for identifying the shared memory block and QLocalServer
|
||||||
d->genBlockServerName();
|
d->genBlockServerName();
|
||||||
|
|
||||||
|
// To mitigate QSharedMemory issues with large amount of processes attempting to attach at the same time
|
||||||
|
d->randomSleep();
|
||||||
|
|
||||||
#ifdef Q_OS_UNIX
|
#ifdef Q_OS_UNIX
|
||||||
// By explicitly attaching it and then deleting it we make sure that the
|
// By explicitly attaching it and then deleting it we make sure that the memory is deleted even after the process has crashed on Unix.
|
||||||
// memory is deleted even after the process has crashed on Unix.
|
d->memory_ = new QSharedMemory(d->blockServerName_);
|
||||||
d->memory = new QSharedMemory(d->blockServerName);
|
d->memory_->attach();
|
||||||
d->memory->attach();
|
delete d->memory_;
|
||||||
delete d->memory;
|
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
// Guarantee thread safe behaviour with a shared memory block.
|
// 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
|
// Create a shared memory block
|
||||||
if (d->memory->create(sizeof(InstancesInfo))) {
|
if (d->memory_->create(sizeof(InstancesInfo))) {
|
||||||
// Initialize the shared memory block
|
// 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->initializeMemoryBlock();
|
||||||
d->memory->unlock();
|
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
// Attempt to attach to the memory segment
|
if (d->memory_->error() == QSharedMemory::AlreadyExists) {
|
||||||
if (!d->memory->attach()) {
|
// Attempt to attach to the memory segment
|
||||||
qCritical() << "SingleCoreApplication: Unable to attach to shared memory block.";
|
if (!d->memory_->attach()) {
|
||||||
qCritical() << d->memory->errorString();
|
qCritical() << "SingleCoreApplication: Unable to attach to shared memory block.";
|
||||||
delete d;
|
abortSafely();
|
||||||
::exit(EXIT_FAILURE);
|
}
|
||||||
|
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;
|
QElapsedTimer time;
|
||||||
time.start();
|
time.start();
|
||||||
|
|
||||||
// Make sure the shared memory block is initialised and in consistent state
|
// Make sure the shared memory block is initialised and in consistent state
|
||||||
while (true) {
|
forever {
|
||||||
d->memory->lock();
|
// If the shared memory block's checksum is valid continue
|
||||||
|
if (d->blockChecksum() == inst->checksum) break;
|
||||||
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) {
|
if (time.elapsed() > 5000) {
|
||||||
qWarning() << "SingleCoreApplication: Shared memory block has been in an inconsistent state from more than 5s. Assuming primary instance failure.";
|
qWarning() << "SingleCoreApplication: Shared memory block has been in an inconsistent state from more than 5s. Assuming primary instance failure.";
|
||||||
d->initializeMemoryBlock();
|
d->initializeMemoryBlock();
|
||||||
}
|
}
|
||||||
|
|
||||||
d->memory->unlock();
|
// 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
|
||||||
// Random sleep here limits the probability of a collision between two racing apps
|
if (!d->memory_->unlock()) {
|
||||||
#if QT_VERSION >= QT_VERSION_CHECK(5, 10, 0)
|
qDebug() << "SingleCoreApplication: Unable to unlock memory for random wait.";
|
||||||
QThread::sleep(QRandomGenerator::global()->bounded(8u, 18u));
|
qDebug() << d->memory_->errorString();
|
||||||
#else
|
}
|
||||||
qsrand(QDateTime::currentMSecsSinceEpoch() % std::numeric_limits<uint>::max());
|
d->randomSleep();
|
||||||
QThread::sleep(8 + static_cast<unsigned long>(static_cast <float>(qrand()) / RAND_MAX * 10));
|
if (!d->memory_->lock()) {
|
||||||
#endif
|
qCritical() << "SingleCoreApplication: Unable to lock memory after random wait.";
|
||||||
|
abortSafely();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (inst->primary == false) {
|
if (inst->primary == false) {
|
||||||
d->startPrimary();
|
d->startPrimary();
|
||||||
d->memory->unlock();
|
if (!d->memory_->unlock()) {
|
||||||
|
qDebug() << "SingleCoreApplication: Unable to unlock memory after primary start.";
|
||||||
|
qDebug() << d->memory_->errorString();
|
||||||
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check if another instance can be started
|
// Check if another instance can be started
|
||||||
if (allowSecondary) {
|
if (allowSecondary) {
|
||||||
inst->secondary += 1;
|
|
||||||
inst->checksum = d->blockChecksum();
|
|
||||||
d->instanceNumber = inst->secondary;
|
|
||||||
d->startSecondary();
|
d->startSecondary();
|
||||||
if(d->options & Mode::SecondaryNotification) {
|
if (d->options_ & Mode::SecondaryNotification) {
|
||||||
d->connectToPrimary(timeout, SingleCoreApplicationPrivate::SecondaryInstance);
|
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;
|
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);
|
d->connectToPrimary(timeout, SingleCoreApplicationPrivate::NewInstance);
|
||||||
|
|
||||||
@@ -151,47 +169,101 @@ SingleCoreApplication::SingleCoreApplication(int &argc, char *argv[], bool allow
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Destructor
|
|
||||||
*/
|
|
||||||
SingleCoreApplication::~SingleCoreApplication() {
|
SingleCoreApplication::~SingleCoreApplication() {
|
||||||
Q_D(SingleCoreApplication);
|
Q_D(SingleCoreApplication);
|
||||||
delete d;
|
delete d;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks if the current application instance is primary.
|
||||||
|
* @return Returns true if the instance is primary, false otherwise.
|
||||||
|
*/
|
||||||
bool SingleCoreApplication::isPrimary() {
|
bool SingleCoreApplication::isPrimary() {
|
||||||
Q_D(SingleCoreApplication);
|
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() {
|
bool SingleCoreApplication::isSecondary() {
|
||||||
Q_D(SingleCoreApplication);
|
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() {
|
quint32 SingleCoreApplication::instanceId() {
|
||||||
Q_D(SingleCoreApplication);
|
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() {
|
qint64 SingleCoreApplication::primaryPid() {
|
||||||
Q_D(SingleCoreApplication);
|
Q_D(SingleCoreApplication);
|
||||||
return d->primaryPid();
|
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) {
|
bool SingleCoreApplication::sendMessage(QByteArray message, int timeout) {
|
||||||
|
|
||||||
Q_D(SingleCoreApplication);
|
Q_D(SingleCoreApplication);
|
||||||
|
|
||||||
// Nobody to connect to
|
// Nobody to connect to
|
||||||
if(isPrimary()) return false;
|
if (isPrimary()) return false;
|
||||||
|
|
||||||
// Make sure the socket is connected
|
// Make sure the socket is connected
|
||||||
d->connectToPrimary(timeout, SingleCoreApplicationPrivate::Reconnect);
|
if (!d->connectToPrimary(timeout, SingleCoreApplicationPrivate::Reconnect))
|
||||||
|
return false;
|
||||||
|
|
||||||
d->socket->write(message);
|
d->socket_->write(message);
|
||||||
bool dataWritten = d->socket->waitForBytesWritten(timeout);
|
const bool dataWritten = d->socket_->waitForBytesWritten(timeout);
|
||||||
d->socket->flush();
|
d->socket_->flush();
|
||||||
return dataWritten;
|
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)
|
// 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
|
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
// of this software and associated documentation files (the "Software"), to deal
|
// of this software and associated documentation files (the "Software"), to deal
|
||||||
@@ -42,8 +42,7 @@
|
|||||||
class SingleCoreApplicationPrivate;
|
class SingleCoreApplicationPrivate;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief The SingleCoreApplication class handles multiple instances of the same
|
* @brief The SingleCoreApplication class handles multipe instances of the same Application
|
||||||
* Application
|
|
||||||
* @see QCoreApplication
|
* @see QCoreApplication
|
||||||
*/
|
*/
|
||||||
class SingleCoreApplication : public QCoreApplication {
|
class SingleCoreApplication : public QCoreApplication {
|
||||||
@@ -51,7 +50,7 @@ class SingleCoreApplication : public QCoreApplication {
|
|||||||
|
|
||||||
typedef QCoreApplication app_t;
|
typedef QCoreApplication app_t;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
/**
|
/**
|
||||||
* @brief Mode of operation of SingleCoreApplication.
|
* @brief Mode of operation of SingleCoreApplication.
|
||||||
* Whether the block should be user-wide or system-wide and whether the
|
* Whether the block should be user-wide or system-wide and whether the
|
||||||
@@ -63,11 +62,11 @@ public:
|
|||||||
* @enum
|
* @enum
|
||||||
*/
|
*/
|
||||||
enum Mode {
|
enum Mode {
|
||||||
User = 1 << 0,
|
User = 1 << 0,
|
||||||
System = 1 << 1,
|
System = 1 << 1,
|
||||||
SecondaryNotification = 1 << 2,
|
SecondaryNotification = 1 << 2,
|
||||||
ExcludeAppVersion = 1 << 3,
|
ExcludeAppVersion = 1 << 3,
|
||||||
ExcludeAppPath = 1 << 4
|
ExcludeAppPath = 1 << 4
|
||||||
};
|
};
|
||||||
Q_DECLARE_FLAGS(Options, Mode)
|
Q_DECLARE_FLAGS(Options, Mode)
|
||||||
|
|
||||||
@@ -89,9 +88,8 @@ public:
|
|||||||
* operations. It does not guarantee that the SingleCoreApplication
|
* operations. It does not guarantee that the SingleCoreApplication
|
||||||
* initialisation will be completed in given time, though is a good hint.
|
* initialisation will be completed in given time, though is a good hint.
|
||||||
* Usually 4*timeout would be the worst case (fail) scenario.
|
* Usually 4*timeout would be the worst case (fail) scenario.
|
||||||
* @see See the corresponding QAPPLICATION_CLASS constructor for reference
|
|
||||||
*/
|
*/
|
||||||
explicit 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;
|
~SingleCoreApplication() override;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -118,6 +116,18 @@ public:
|
|||||||
*/
|
*/
|
||||||
qint64 primaryPid();
|
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.
|
* @brief Sends a message to the primary instance. Returns true on success.
|
||||||
* @param {int} timeout - Timeout for connecting
|
* @param {int} timeout - Timeout for connecting
|
||||||
@@ -125,17 +135,18 @@ public:
|
|||||||
* @note sendMessage() will return false if invoked from the primary
|
* @note sendMessage() will return false if invoked from the primary
|
||||||
* instance.
|
* instance.
|
||||||
*/
|
*/
|
||||||
bool sendMessage( QByteArray message, int timeout = 1000 );
|
bool sendMessage(QByteArray message, int timeout = 1000);
|
||||||
|
|
||||||
signals:
|
signals:
|
||||||
void instanceStarted();
|
void instanceStarted();
|
||||||
void receivedMessage( quint32 instanceId, QByteArray message );
|
void receivedMessage(quint32 instanceId, QByteArray message);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
SingleCoreApplicationPrivate *d_ptr;
|
SingleCoreApplicationPrivate *d_ptr;
|
||||||
Q_DECLARE_PRIVATE(SingleCoreApplication)
|
Q_DECLARE_PRIVATE(SingleCoreApplication)
|
||||||
|
void abortSafely();
|
||||||
};
|
};
|
||||||
|
|
||||||
Q_DECLARE_OPERATORS_FOR_FLAGS(SingleCoreApplication::Options)
|
Q_DECLARE_OPERATORS_FOR_FLAGS(SingleCoreApplication::Options)
|
||||||
|
|
||||||
#endif // SINGLECOREAPPLICATION_H
|
#endif // SINGLECOREAPPLICATION_H
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
// The MIT License (MIT)
|
// 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
|
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
// of this software and associated documentation files (the "Software"), to deal
|
// of this software and associated documentation files (the "Software"), to deal
|
||||||
@@ -44,6 +44,8 @@
|
|||||||
# include <pwd.h>
|
# include <pwd.h>
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
#include <QObject>
|
||||||
|
#include <QThread>
|
||||||
#include <QIODevice>
|
#include <QIODevice>
|
||||||
#include <QSharedMemory>
|
#include <QSharedMemory>
|
||||||
#include <QByteArray>
|
#include <QByteArray>
|
||||||
@@ -52,6 +54,12 @@
|
|||||||
#include <QLocalServer>
|
#include <QLocalServer>
|
||||||
#include <QLocalSocket>
|
#include <QLocalSocket>
|
||||||
#include <QDir>
|
#include <QDir>
|
||||||
|
#include <QElapsedTimer>
|
||||||
|
#if QT_VERSION >= QT_VERSION_CHECK(5, 10, 0)
|
||||||
|
# include <QRandomGenerator>
|
||||||
|
#else
|
||||||
|
# include <QDateTime>
|
||||||
|
#endif
|
||||||
|
|
||||||
#include "singlecoreapplication.h"
|
#include "singlecoreapplication.h"
|
||||||
#include "singlecoreapplication_p.h"
|
#include "singlecoreapplication_p.h"
|
||||||
@@ -61,33 +69,73 @@
|
|||||||
# include <lmcons.h>
|
# include <lmcons.h>
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
SingleCoreApplicationPrivate::SingleCoreApplicationPrivate(SingleCoreApplication *_q_ptr)
|
SingleCoreApplicationPrivate::SingleCoreApplicationPrivate(SingleCoreApplication *ptr)
|
||||||
: q_ptr(_q_ptr),
|
: q_ptr(ptr),
|
||||||
memory(nullptr),
|
memory_(nullptr),
|
||||||
socket(nullptr),
|
socket_(nullptr),
|
||||||
server(nullptr),
|
server_(nullptr),
|
||||||
instanceNumber(-1)
|
instanceNumber_(-1) {}
|
||||||
{}
|
|
||||||
|
|
||||||
SingleCoreApplicationPrivate::~SingleCoreApplicationPrivate() {
|
SingleCoreApplicationPrivate::~SingleCoreApplicationPrivate() {
|
||||||
|
|
||||||
if (socket != nullptr) {
|
if (socket_ != nullptr) {
|
||||||
socket->close();
|
socket_->close();
|
||||||
delete socket;
|
delete socket_;
|
||||||
|
socket_ = nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
memory->lock();
|
if (memory_ != nullptr) {
|
||||||
InstancesInfo* inst = static_cast<InstancesInfo*>(memory->data());
|
memory_->lock();
|
||||||
if (server != nullptr) {
|
InstancesInfo *inst = static_cast<InstancesInfo*>(memory_->data());
|
||||||
server->close();
|
if (server_ != nullptr) {
|
||||||
delete server;
|
server_->close();
|
||||||
inst->primary = false;
|
delete server_;
|
||||||
inst->primaryPid = -1;
|
inst->primary = false;
|
||||||
inst->checksum = blockChecksum();
|
inst->primaryPid = -1;
|
||||||
}
|
inst->primaryUser[0] = '\0';
|
||||||
memory->unlock();
|
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::organizationName().toUtf8());
|
||||||
appData.addData(SingleCoreApplication::app_t::organizationDomain().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());
|
appData.addData(SingleCoreApplication::app_t::applicationVersion().toUtf8());
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!(options & SingleCoreApplication::Mode::ExcludeAppPath)) {
|
if (!(options_ & SingleCoreApplication::Mode::ExcludeAppPath)) {
|
||||||
#ifdef Q_OS_WIN
|
#ifdef Q_OS_WIN
|
||||||
appData.addData(SingleCoreApplication::app_t::applicationFilePath().toLower().toUtf8());
|
appData.addData(SingleCoreApplication::app_t::applicationFilePath().toLower().toUtf8());
|
||||||
#else
|
#else
|
||||||
@@ -112,42 +160,22 @@ void SingleCoreApplicationPrivate::genBlockServerName() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// User level block requires a user specific data in the hash
|
// User level block requires a user specific data in the hash
|
||||||
if (options & SingleCoreApplication::Mode::User) {
|
if (options_ & SingleCoreApplication::Mode::User) {
|
||||||
#ifdef Q_OS_UNIX
|
appData.addData(getUsername().toUtf8());
|
||||||
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
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Replace the backslash in RFC 2045 Base64 [a-zA-Z0-9+/=] to comply with server naming requirements.
|
// 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() {
|
void SingleCoreApplicationPrivate::initializeMemoryBlock() {
|
||||||
|
|
||||||
InstancesInfo* inst = static_cast<InstancesInfo*>(memory->data());
|
InstancesInfo *inst = static_cast<InstancesInfo*>(memory_->data());
|
||||||
inst->primary = false;
|
inst->primary = false;
|
||||||
inst->secondary = 0;
|
inst->secondary = 0;
|
||||||
inst->primaryPid = -1;
|
inst->primaryPid = -1;
|
||||||
|
inst->primaryUser[0] = '\0';
|
||||||
inst->checksum = blockChecksum();
|
inst->checksum = blockChecksum();
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -156,133 +184,161 @@ void SingleCoreApplicationPrivate::startPrimary() {
|
|||||||
|
|
||||||
Q_Q(SingleCoreApplication);
|
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
|
// Reset the number of connections
|
||||||
InstancesInfo* inst = static_cast <InstancesInfo*>(memory->data());
|
InstancesInfo *inst = static_cast<InstancesInfo*>(memory_->data());
|
||||||
|
|
||||||
inst->primary = true;
|
inst->primary = true;
|
||||||
inst->primaryPid = q->applicationPid();
|
inst->primaryPid = q->applicationPid();
|
||||||
|
qstrncpy(inst->primaryUser, getUsername().toUtf8().data(), sizeof(inst->primaryUser));
|
||||||
inst->checksum = blockChecksum();
|
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.
|
// Connect to the Local Server of the Primary Instance if not already connected.
|
||||||
if (socket == nullptr) {
|
if (socket_ == nullptr) {
|
||||||
socket = new QLocalSocket();
|
socket_ = new QLocalSocket();
|
||||||
}
|
}
|
||||||
|
|
||||||
// If already connected - we are done;
|
if (socket_->state() == QLocalSocket::ConnectedState) return true;
|
||||||
if (socket->state() == QLocalSocket::ConnectedState)
|
|
||||||
return;
|
|
||||||
|
|
||||||
// If not connect
|
if (socket_->state() != QLocalSocket::ConnectedState) {
|
||||||
if (socket->state() == QLocalSocket::UnconnectedState ||
|
|
||||||
socket->state() == QLocalSocket::ClosingState) {
|
|
||||||
socket->connectToServer(blockServerName);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Wait for being connected
|
forever {
|
||||||
if (socket->state() == QLocalSocket::ConnectingState) {
|
randomSleep();
|
||||||
socket->waitForConnected(msecs);
|
|
||||||
|
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
|
// Initialisation message according to the SingleCoreApplication protocol
|
||||||
if (socket->state() == QLocalSocket::ConnectedState) {
|
QByteArray initMsg;
|
||||||
// Notify the parent that a new instance had been started;
|
QDataStream writeStream(&initMsg, QIODevice::WriteOnly);
|
||||||
QByteArray initMsg;
|
writeStream.setVersion(QDataStream::Qt_5_8);
|
||||||
QDataStream writeStream(&initMsg, QIODevice::WriteOnly);
|
|
||||||
|
|
||||||
writeStream.setVersion(QDataStream::Qt_5_6);
|
writeStream << blockServerName_.toLatin1();
|
||||||
|
writeStream << static_cast<quint8>(connectionType);
|
||||||
|
writeStream << instanceNumber_;
|
||||||
|
|
||||||
writeStream << blockServerName.toLatin1();
|
#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
|
||||||
writeStream << static_cast<quint8>(connectionType);
|
quint16 checksum = qChecksum(QByteArray(initMsg, static_cast<quint32>(initMsg.length())));
|
||||||
writeStream << instanceNumber;
|
#else
|
||||||
quint16 checksum = qChecksum(initMsg.constData(), static_cast<quint32>(initMsg.length()));
|
quint16 checksum = qChecksum(initMsg.constData(), static_cast<quint32>(initMsg.length()));
|
||||||
writeStream << checksum;
|
#endif
|
||||||
|
|
||||||
// The header indicates the message length that follows
|
writeStream << checksum;
|
||||||
QByteArray header;
|
|
||||||
QDataStream headerStream(&header, QIODevice::WriteOnly);
|
|
||||||
|
|
||||||
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);
|
return result;
|
||||||
socket->write(initMsg);
|
|
||||||
socket->flush();
|
|
||||||
socket->waitForBytesWritten(msecs);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
quint16 SingleCoreApplicationPrivate::blockChecksum() {
|
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 SingleCoreApplicationPrivate::primaryPid() {
|
||||||
|
|
||||||
qint64 pid;
|
memory_->lock();
|
||||||
|
InstancesInfo *inst = static_cast<InstancesInfo*>(memory_->data());
|
||||||
memory->lock();
|
qint64 pid = inst->primaryPid;
|
||||||
InstancesInfo* inst = static_cast<InstancesInfo*>(memory->data());
|
memory_->unlock();
|
||||||
pid = inst->primaryPid;
|
|
||||||
memory->unlock();
|
|
||||||
|
|
||||||
return pid;
|
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
|
* @brief Executed when a connection has been made to the LocalServer
|
||||||
*/
|
*/
|
||||||
void SingleCoreApplicationPrivate::slotConnectionEstablished() {
|
void SingleCoreApplicationPrivate::slotConnectionEstablished() {
|
||||||
|
|
||||||
QLocalSocket *nextConnSocket = server->nextPendingConnection();
|
QLocalSocket *nextConnSocket = server_->nextPendingConnection();
|
||||||
connectionMap.insert(nextConnSocket, ConnectionInfo());
|
connectionMap_.insert(nextConnSocket, ConnectionInfo());
|
||||||
|
|
||||||
QObject::connect(nextConnSocket, &QLocalSocket::aboutToClose,
|
QObject::connect(nextConnSocket, &QLocalSocket::aboutToClose, [nextConnSocket, this]() {
|
||||||
[nextConnSocket, this]() {
|
auto &info = connectionMap_[nextConnSocket];
|
||||||
auto &info = connectionMap[nextConnSocket];
|
Q_EMIT this->slotClientConnectionClosed(nextConnSocket, info.instanceId);
|
||||||
Q_EMIT this->slotClientConnectionClosed(nextConnSocket, info.instanceId);
|
});
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
QObject::connect(nextConnSocket, &QLocalSocket::disconnected,
|
QObject::connect(nextConnSocket, &QLocalSocket::disconnected, [nextConnSocket, this]() {
|
||||||
[nextConnSocket, this](){
|
connectionMap_.remove(nextConnSocket);
|
||||||
connectionMap.remove(nextConnSocket);
|
nextConnSocket->deleteLater();
|
||||||
nextConnSocket->deleteLater();
|
});
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
QObject::connect(nextConnSocket, &QLocalSocket::readyRead,
|
QObject::connect(nextConnSocket, &QLocalSocket::readyRead, [nextConnSocket, this]() {
|
||||||
[nextConnSocket, this]() {
|
auto &info = connectionMap_[nextConnSocket];
|
||||||
auto &info = connectionMap[nextConnSocket];
|
switch (info.stage) {
|
||||||
switch(info.stage) {
|
|
||||||
case StageHeader:
|
case StageHeader:
|
||||||
readInitMessageHeader(nextConnSocket);
|
readInitMessageHeader(nextConnSocket);
|
||||||
break;
|
break;
|
||||||
@@ -294,15 +350,14 @@ void SingleCoreApplicationPrivate::slotConnectionEstablished() {
|
|||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
break;
|
break;
|
||||||
};
|
};
|
||||||
}
|
});
|
||||||
);
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void SingleCoreApplicationPrivate::readInitMessageHeader(QLocalSocket *sock) {
|
void SingleCoreApplicationPrivate::readInitMessageHeader(QLocalSocket *sock) {
|
||||||
|
|
||||||
if (!connectionMap.contains(sock)) {
|
if (!connectionMap_.contains(sock)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -311,13 +366,12 @@ void SingleCoreApplicationPrivate::readInitMessageHeader(QLocalSocket *sock) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
QDataStream headerStream(sock);
|
QDataStream headerStream(sock);
|
||||||
|
headerStream.setVersion(QDataStream::Qt_5_8);
|
||||||
headerStream.setVersion(QDataStream::Qt_5_6);
|
|
||||||
|
|
||||||
// Read the header to know the message length
|
// Read the header to know the message length
|
||||||
quint64 msgLen = 0;
|
quint64 msgLen = 0;
|
||||||
headerStream >> msgLen;
|
headerStream >> msgLen;
|
||||||
ConnectionInfo &info = connectionMap[sock];
|
ConnectionInfo &info = connectionMap_[sock];
|
||||||
info.stage = StageBody;
|
info.stage = StageBody;
|
||||||
info.msgLen = msgLen;
|
info.msgLen = msgLen;
|
||||||
|
|
||||||
@@ -331,11 +385,11 @@ void SingleCoreApplicationPrivate::readInitMessageBody(QLocalSocket *sock) {
|
|||||||
|
|
||||||
Q_Q(SingleCoreApplication);
|
Q_Q(SingleCoreApplication);
|
||||||
|
|
||||||
if (!connectionMap.contains(sock)) {
|
if (!connectionMap_.contains(sock)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
ConnectionInfo &info = connectionMap[sock];
|
ConnectionInfo &info = connectionMap_[sock];
|
||||||
if (sock->bytesAvailable() < static_cast<qint64>(info.msgLen)) {
|
if (sock->bytesAvailable() < static_cast<qint64>(info.msgLen)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -343,8 +397,7 @@ void SingleCoreApplicationPrivate::readInitMessageBody(QLocalSocket *sock) {
|
|||||||
// Read the message body
|
// Read the message body
|
||||||
QByteArray msgBytes = sock->read(info.msgLen);
|
QByteArray msgBytes = sock->read(info.msgLen);
|
||||||
QDataStream readStream(msgBytes);
|
QDataStream readStream(msgBytes);
|
||||||
|
readStream.setVersion(QDataStream::Qt_5_8);
|
||||||
readStream.setVersion(QDataStream::Qt_5_6);
|
|
||||||
|
|
||||||
// server name
|
// server name
|
||||||
QByteArray latin1Name;
|
QByteArray latin1Name;
|
||||||
@@ -354,7 +407,7 @@ void SingleCoreApplicationPrivate::readInitMessageBody(QLocalSocket *sock) {
|
|||||||
ConnectionType connectionType = InvalidConnection;
|
ConnectionType connectionType = InvalidConnection;
|
||||||
quint8 connTypeVal = InvalidConnection;
|
quint8 connTypeVal = InvalidConnection;
|
||||||
readStream >> connTypeVal;
|
readStream >> connTypeVal;
|
||||||
connectionType = static_cast <ConnectionType>(connTypeVal);
|
connectionType = static_cast<ConnectionType>(connTypeVal);
|
||||||
|
|
||||||
// instance id
|
// instance id
|
||||||
quint32 instanceId = 0;
|
quint32 instanceId = 0;
|
||||||
@@ -364,9 +417,13 @@ void SingleCoreApplicationPrivate::readInitMessageBody(QLocalSocket *sock) {
|
|||||||
quint16 msgChecksum = 0;
|
quint16 msgChecksum = 0;
|
||||||
readStream >> msgChecksum;
|
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)));
|
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) {
|
if (!isValid) {
|
||||||
sock->close();
|
sock->close();
|
||||||
@@ -376,7 +433,7 @@ void SingleCoreApplicationPrivate::readInitMessageBody(QLocalSocket *sock) {
|
|||||||
info.instanceId = instanceId;
|
info.instanceId = instanceId;
|
||||||
info.stage = StageConnected;
|
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();
|
Q_EMIT q->instanceStarted();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -395,7 +452,19 @@ void SingleCoreApplicationPrivate::slotDataAvailable(QLocalSocket *dataSocket, c
|
|||||||
|
|
||||||
void SingleCoreApplicationPrivate::slotClientConnectionClosed(QLocalSocket *closedSocket, const quint32 instanceId) {
|
void SingleCoreApplicationPrivate::slotClientConnectionClosed(QLocalSocket *closedSocket, const quint32 instanceId) {
|
||||||
|
|
||||||
if (closedSocket->bytesAvailable() > 0)
|
if (closedSocket->bytesAvailable() > 0) {
|
||||||
Q_EMIT slotDataAvailable(closedSocket, instanceId);
|
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)
|
// The MIT License (MIT)
|
||||||
//
|
//
|
||||||
// Copyright (c) Itay Grudev 2015 - 2016
|
// Copyright (c) Itay Grudev 2015 - 2020
|
||||||
//
|
//
|
||||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
// of this software and associated documentation files (the "Software"), to deal
|
// of this software and associated documentation files (the "Software"), to deal
|
||||||
@@ -36,6 +36,7 @@
|
|||||||
|
|
||||||
#include <QtGlobal>
|
#include <QtGlobal>
|
||||||
#include <QObject>
|
#include <QObject>
|
||||||
|
#include <QString>
|
||||||
#include <QMap>
|
#include <QMap>
|
||||||
|
|
||||||
#include "singlecoreapplication.h"
|
#include "singlecoreapplication.h"
|
||||||
@@ -48,6 +49,7 @@ struct InstancesInfo {
|
|||||||
bool primary;
|
bool primary;
|
||||||
quint32 secondary;
|
quint32 secondary;
|
||||||
qint64 primaryPid;
|
qint64 primaryPid;
|
||||||
|
char primaryUser[128];
|
||||||
quint16 checksum;
|
quint16 checksum;
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -60,6 +62,7 @@ struct ConnectionInfo {
|
|||||||
|
|
||||||
class SingleCoreApplicationPrivate : public QObject {
|
class SingleCoreApplicationPrivate : public QObject {
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
|
|
||||||
public:
|
public:
|
||||||
enum ConnectionType : quint8 {
|
enum ConnectionType : quint8 {
|
||||||
InvalidConnection = 0,
|
InvalidConnection = 0,
|
||||||
@@ -74,27 +77,30 @@ class SingleCoreApplicationPrivate : public QObject {
|
|||||||
};
|
};
|
||||||
Q_DECLARE_PUBLIC(SingleCoreApplication)
|
Q_DECLARE_PUBLIC(SingleCoreApplication)
|
||||||
|
|
||||||
explicit SingleCoreApplicationPrivate(SingleCoreApplication *_q_ptr);
|
explicit SingleCoreApplicationPrivate(SingleCoreApplication *ptr);
|
||||||
~SingleCoreApplicationPrivate() override;
|
~SingleCoreApplicationPrivate() override;
|
||||||
|
|
||||||
|
QString getUsername();
|
||||||
void genBlockServerName();
|
void genBlockServerName();
|
||||||
void initializeMemoryBlock();
|
void initializeMemoryBlock();
|
||||||
void startPrimary();
|
void startPrimary();
|
||||||
void startSecondary();
|
void startSecondary();
|
||||||
void connectToPrimary(const int msecs, const ConnectionType connectionType);
|
bool connectToPrimary(const int msecs, const ConnectionType connectionType);
|
||||||
quint16 blockChecksum();
|
quint16 blockChecksum();
|
||||||
qint64 primaryPid();
|
qint64 primaryPid();
|
||||||
|
QString primaryUser();
|
||||||
void readInitMessageHeader(QLocalSocket *socket);
|
void readInitMessageHeader(QLocalSocket *socket);
|
||||||
void readInitMessageBody(QLocalSocket *socket);
|
void readInitMessageBody(QLocalSocket *socket);
|
||||||
|
void randomSleep();
|
||||||
|
|
||||||
SingleCoreApplication *q_ptr;
|
SingleCoreApplication *q_ptr;
|
||||||
QSharedMemory *memory;
|
QSharedMemory *memory_;
|
||||||
QLocalSocket *socket;
|
QLocalSocket *socket_;
|
||||||
QLocalServer *server;
|
QLocalServer *server_;
|
||||||
quint32 instanceNumber;
|
quint32 instanceNumber_;
|
||||||
QString blockServerName;
|
QString blockServerName_;
|
||||||
SingleCoreApplication::Options options;
|
SingleCoreApplication::Options options_;
|
||||||
QMap<QLocalSocket*, ConnectionInfo> connectionMap;
|
QMap<QLocalSocket*, ConnectionInfo> connectionMap_;
|
||||||
|
|
||||||
public slots:
|
public slots:
|
||||||
void slotConnectionEstablished();
|
void slotConnectionEstablished();
|
||||||
|
|||||||
@@ -13,7 +13,9 @@ include(cmake/OptionalSource.cmake)
|
|||||||
include(cmake/ParseArguments.cmake)
|
include(cmake/ParseArguments.cmake)
|
||||||
include(cmake/Rpm.cmake)
|
include(cmake/Rpm.cmake)
|
||||||
include(cmake/Deb.cmake)
|
include(cmake/Deb.cmake)
|
||||||
include(cmake/Dmg.cmake)
|
if(APPLE)
|
||||||
|
include(cmake/Dmg.cmake)
|
||||||
|
endif()
|
||||||
|
|
||||||
set(CMAKE_MODULE_PATH ${CMAKE_SOURCE_DIR}/cmake)
|
set(CMAKE_MODULE_PATH ${CMAKE_SOURCE_DIR}/cmake)
|
||||||
|
|
||||||
@@ -91,6 +93,7 @@ find_package(Backtrace QUIET)
|
|||||||
if(Backtrace_FOUND)
|
if(Backtrace_FOUND)
|
||||||
set(HAVE_BACKTRACE ON)
|
set(HAVE_BACKTRACE ON)
|
||||||
endif()
|
endif()
|
||||||
|
find_package(Iconv QUIET)
|
||||||
find_package(GnuTLS REQUIRED)
|
find_package(GnuTLS REQUIRED)
|
||||||
find_package(Protobuf REQUIRED)
|
find_package(Protobuf REQUIRED)
|
||||||
if (NOT Protobuf_PROTOC_EXECUTABLE)
|
if (NOT Protobuf_PROTOC_EXECUTABLE)
|
||||||
@@ -126,10 +129,21 @@ pkg_check_modules(LIBPULSE libpulse)
|
|||||||
pkg_check_modules(CHROMAPRINT libchromaprint)
|
pkg_check_modules(CHROMAPRINT libchromaprint)
|
||||||
pkg_check_modules(LIBGPOD libgpod-1.0>=0.7.92)
|
pkg_check_modules(LIBGPOD libgpod-1.0>=0.7.92)
|
||||||
pkg_check_modules(LIBMTP libmtp>=1.0)
|
pkg_check_modules(LIBMTP libmtp>=1.0)
|
||||||
|
pkg_check_modules(GDK_PIXBUF gdk-pixbuf-2.0)
|
||||||
find_package(Gettext)
|
find_package(Gettext)
|
||||||
find_package(FFTW3)
|
find_package(FFTW3)
|
||||||
|
|
||||||
option(WITH_QT6 "Use Qt 6" OFF)
|
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(NOT BUILD_WITH_QT5 AND NOT BUILD_WITH_QT6)
|
||||||
|
set(BUILD_WITH_QT5 ON)
|
||||||
|
endif()
|
||||||
|
|
||||||
set(QT_COMPONENTS Core Concurrent Widgets Network Sql)
|
set(QT_COMPONENTS Core Concurrent Widgets Network Sql)
|
||||||
if(X11_FOUND)
|
if(X11_FOUND)
|
||||||
list(APPEND QT_COMPONENTS X11Extras)
|
list(APPEND QT_COMPONENTS X11Extras)
|
||||||
@@ -144,7 +158,7 @@ if(WIN32)
|
|||||||
list(APPEND QT_COMPONENTS WinExtras)
|
list(APPEND QT_COMPONENTS WinExtras)
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
if(WITH_QT6)
|
if(BUILD_WITH_QT6)
|
||||||
list(APPEND QT_COMPONENTS Core5Compat)
|
list(APPEND QT_COMPONENTS Core5Compat)
|
||||||
find_package(Qt6 REQUIRED COMPONENTS ${QT_COMPONENTS})
|
find_package(Qt6 REQUIRED COMPONENTS ${QT_COMPONENTS})
|
||||||
set(QtCore_LIBRARIES Qt6::Core)
|
set(QtCore_LIBRARIES Qt6::Core)
|
||||||
@@ -174,7 +188,7 @@ if(WITH_QT6)
|
|||||||
if (Qt6LinguistTools_FOUND)
|
if (Qt6LinguistTools_FOUND)
|
||||||
set(QT_LCONVERT_EXECUTABLE Qt6::lconvert)
|
set(QT_LCONVERT_EXECUTABLE Qt6::lconvert)
|
||||||
endif()
|
endif()
|
||||||
else()
|
elseif(BUILD_WITH_QT5)
|
||||||
set(QT_MIN_VERSION 5.8)
|
set(QT_MIN_VERSION 5.8)
|
||||||
find_package(Qt5 ${QT_MIN_VERSION} REQUIRED COMPONENTS ${QT_COMPONENTS})
|
find_package(Qt5 ${QT_MIN_VERSION} REQUIRED COMPONENTS ${QT_COMPONENTS})
|
||||||
set(QtCore_LIBRARIES ${Qt5Core_LIBRARIES})
|
set(QtCore_LIBRARIES ${Qt5Core_LIBRARIES})
|
||||||
@@ -209,6 +223,8 @@ else()
|
|||||||
if (Qt5LinguistTools_FOUND)
|
if (Qt5LinguistTools_FOUND)
|
||||||
set(QT_LCONVERT_EXECUTABLE Qt5::lconvert)
|
set(QT_LCONVERT_EXECUTABLE Qt5::lconvert)
|
||||||
endif()
|
endif()
|
||||||
|
else()
|
||||||
|
message(FATAL_ERROR "Set BUILD_WITH_QT5 or BUILD_WITH_QT6")
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
if(X11_FOUND)
|
if(X11_FOUND)
|
||||||
@@ -261,7 +277,7 @@ if(APPLE)
|
|||||||
endif(APPLE)
|
endif(APPLE)
|
||||||
|
|
||||||
if(NOT SPARKLE AND (APPLE OR WIN32))
|
if(NOT SPARKLE AND (APPLE OR WIN32))
|
||||||
if(WITH_QT6)
|
if(BUILD_WITH_QT6)
|
||||||
pkg_check_modules(QTSPARKLE qtsparkle-qt6)
|
pkg_check_modules(QTSPARKLE qtsparkle-qt6)
|
||||||
else()
|
else()
|
||||||
pkg_check_modules(QTSPARKLE qtsparkle-qt5)
|
pkg_check_modules(QTSPARKLE qtsparkle-qt5)
|
||||||
@@ -335,6 +351,7 @@ optional_component(GIO ON "Devices: GIO device backend"
|
|||||||
|
|
||||||
optional_component(LIBGPOD ON "Devices: iPod classic support"
|
optional_component(LIBGPOD ON "Devices: iPod classic support"
|
||||||
DEPENDS "libgpod" LIBGPOD_FOUND
|
DEPENDS "libgpod" LIBGPOD_FOUND
|
||||||
|
DEPENDS "gdk-pixbuf" GDK_PIXBUF_FOUND
|
||||||
)
|
)
|
||||||
|
|
||||||
optional_component(LIBMTP ON "Devices: MTP support"
|
optional_component(LIBMTP ON "Devices: MTP support"
|
||||||
@@ -346,7 +363,7 @@ optional_component(SPARKLE ON "Sparkle integration"
|
|||||||
DEPENDS "Sparkle" SPARKLE
|
DEPENDS "Sparkle" SPARKLE
|
||||||
)
|
)
|
||||||
|
|
||||||
if(WITH_QT6)
|
if(BUILD_WITH_QT6)
|
||||||
optional_component(TRANSLATIONS ON "Translations"
|
optional_component(TRANSLATIONS ON "Translations"
|
||||||
DEPENDS "gettext" GETTEXT_FOUND
|
DEPENDS "gettext" GETTEXT_FOUND
|
||||||
DEPENDS "Qt6LinguistTools" Qt6LinguistTools_FOUND
|
DEPENDS "Qt6LinguistTools" Qt6LinguistTools_FOUND
|
||||||
@@ -362,6 +379,7 @@ option(INSTALL_TRANSLATIONS "Install translations" OFF)
|
|||||||
|
|
||||||
optional_component(SUBSONIC ON "Subsonic support")
|
optional_component(SUBSONIC ON "Subsonic support")
|
||||||
optional_component(TIDAL ON "Tidal support")
|
optional_component(TIDAL ON "Tidal support")
|
||||||
|
optional_component(QOBUZ ON "Qobuz support")
|
||||||
|
|
||||||
optional_component(MOODBAR ON "Moodbar"
|
optional_component(MOODBAR ON "Moodbar"
|
||||||
DEPENDS "fftw3" FFTW3_FOUND
|
DEPENDS "fftw3" FFTW3_FOUND
|
||||||
|
|||||||
61
Changelog
@@ -2,9 +2,68 @@ Strawberry Music Player
|
|||||||
=======================
|
=======================
|
||||||
ChangeLog
|
ChangeLog
|
||||||
|
|
||||||
|
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:
|
0.7.2:
|
||||||
|
|
||||||
BugFixes:
|
Bugfixes:
|
||||||
* Fixed installation directory for translations.
|
* Fixed installation directory for translations.
|
||||||
* Fixed collection sorting for non-ASCII characters.
|
* Fixed collection sorting for non-ASCII characters.
|
||||||
* Fixed closing connected devices on exit.
|
* Fixed closing connected devices on exit.
|
||||||
|
|||||||
@@ -1,5 +0,0 @@
|
|||||||
from jonaski/opensuse:lp151
|
|
||||||
|
|
||||||
run mkdir -p /usr/src/app
|
|
||||||
workdir /usr/src/app
|
|
||||||
copy . /usr/src/app
|
|
||||||
13
README.md
@@ -2,7 +2,7 @@
|
|||||||
[](https://paypal.me/jonaskvinge)
|
[](https://paypal.me/jonaskvinge)
|
||||||
=======================
|
=======================
|
||||||
|
|
||||||
Strawberry is a music player and music collection organizer. It is a fork of Clementine released in 2018 aimed at music collectors and audiophiles. It's written in C++ using the Qt 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.
|
* 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
|
* Audio CD playback
|
||||||
* Native desktop notifications
|
* 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
|
* Advanced audio output and device configuration for bit-perfect playback on Linux
|
||||||
* Edit tags on music files
|
* Edit tags on music files
|
||||||
* Fetch tags from MusicBrainz
|
* Fetch tags from MusicBrainz
|
||||||
@@ -46,7 +47,7 @@ You can also make a one-time payment through [paypal.me/jonaskvinge](https://pay
|
|||||||
* Audio equalizer
|
* Audio equalizer
|
||||||
* Transfer music to iPod, MTP or mass-storage USB player
|
* 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/)
|
* 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.
|
It has so far been tested to work on Linux, OpenBSD, FreeBSD, macOS and Windows.
|
||||||
@@ -96,10 +97,12 @@ With Qt 6 we also depend on the Core5Compat module for QTextCodec.
|
|||||||
cd strawberry
|
cd strawberry
|
||||||
mkdir build && cd build
|
mkdir build && cd build
|
||||||
cmake ..
|
cmake ..
|
||||||
make -j4
|
make -j$(nproc)
|
||||||
sudo make install
|
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
|
### :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
|
add_custom_target(dmg
|
||||||
COMMAND /usr/local/opt/qt5/bin/macdeployqt strawberry.app
|
COMMAND /usr/local/opt/qt5/bin/macdeployqt strawberry.app
|
||||||
COMMAND ${CMAKE_SOURCE_DIR}/dist/macos/macdeploy.py 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}
|
WORKING_DIRECTORY ${CMAKE_BINARY_DIR}
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ macro(optional_source TOGGLE)
|
|||||||
list(APPEND OTHER_SOURCES ${OPTIONAL_SOURCE_HEADERS})
|
list(APPEND OTHER_SOURCES ${OPTIONAL_SOURCE_HEADERS})
|
||||||
|
|
||||||
set(_uic_sources)
|
set(_uic_sources)
|
||||||
if(WITH_QT6)
|
if(BUILD_WITH_QT6)
|
||||||
qt6_wrap_ui(_uic_sources ${OPTIONAL_SOURCE_UI})
|
qt6_wrap_ui(_uic_sources ${OPTIONAL_SOURCE_UI})
|
||||||
else()
|
else()
|
||||||
qt5_wrap_ui(_uic_sources ${OPTIONAL_SOURCE_UI})
|
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>")
|
file(APPEND ${_qrc} "<file>${po_prefix}${_lang}.qm</file>")
|
||||||
endforeach(_lang)
|
endforeach(_lang)
|
||||||
file(APPEND ${_qrc} "</qresource></RCC>")
|
file(APPEND ${_qrc} "</qresource></RCC>")
|
||||||
if(WITH_QT6)
|
if(BUILD_WITH_QT6)
|
||||||
qt6_add_resources(${outfiles} ${_qrc})
|
qt6_add_resources(${outfiles} ${_qrc})
|
||||||
else()
|
else()
|
||||||
qt5_add_resources(${outfiles} ${_qrc})
|
qt5_add_resources(${outfiles} ${_qrc})
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
set(STRAWBERRY_VERSION_MAJOR 0)
|
set(STRAWBERRY_VERSION_MAJOR 0)
|
||||||
set(STRAWBERRY_VERSION_MINOR 7)
|
set(STRAWBERRY_VERSION_MINOR 8)
|
||||||
set(STRAWBERRY_VERSION_PATCH 2)
|
set(STRAWBERRY_VERSION_PATCH 2)
|
||||||
#set(STRAWBERRY_VERSION_PRERELEASE rc1)
|
#set(STRAWBERRY_VERSION_PRERELEASE rc1)
|
||||||
|
|
||||||
|
|||||||
@@ -13,9 +13,10 @@
|
|||||||
<file>schema/schema-10.sql</file>
|
<file>schema/schema-10.sql</file>
|
||||||
<file>schema/schema-11.sql</file>
|
<file>schema/schema-11.sql</file>
|
||||||
<file>schema/schema-12.sql</file>
|
<file>schema/schema-12.sql</file>
|
||||||
|
<file>schema/schema-13.sql</file>
|
||||||
<file>schema/device-schema.sql</file>
|
<file>schema/device-schema.sql</file>
|
||||||
<file>style/strawberry.css</file>
|
<file>style/strawberry.css</file>
|
||||||
<file>html/playing-tooltip.html</file>
|
<file>style/smartplaylistsearchterm.css</file>
|
||||||
<file>html/oauthsuccess.html</file>
|
<file>html/oauthsuccess.html</file>
|
||||||
<file>pictures/strawberry.png</file>
|
<file>pictures/strawberry.png</file>
|
||||||
<file>pictures/strawberry-faded.png</file>
|
<file>pictures/strawberry-faded.png</file>
|
||||||
@@ -40,6 +41,8 @@
|
|||||||
<file>pictures/osd_shadow_edge.png</file>
|
<file>pictures/osd_shadow_edge.png</file>
|
||||||
<file>pictures/nyancat.png</file>
|
<file>pictures/nyancat.png</file>
|
||||||
<file>pictures/rainbowdash.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>fonts/HumongousofEternitySt.ttf</file>
|
||||||
<file>mood/sample.mood</file>
|
<file>mood/sample.mood</file>
|
||||||
<file>text/ghosts.txt</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>
|
|
||||||
@@ -89,6 +89,8 @@
|
|||||||
<file>icons/128x128/love.png</file>
|
<file>icons/128x128/love.png</file>
|
||||||
<file>icons/128x128/subsonic.png</file>
|
<file>icons/128x128/subsonic.png</file>
|
||||||
<file>icons/128x128/tidal.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/albums.png</file>
|
||||||
<file>icons/64x64/alsa.png</file>
|
<file>icons/64x64/alsa.png</file>
|
||||||
<file>icons/64x64/application-exit.png</file>
|
<file>icons/64x64/application-exit.png</file>
|
||||||
@@ -179,6 +181,8 @@
|
|||||||
<file>icons/64x64/love.png</file>
|
<file>icons/64x64/love.png</file>
|
||||||
<file>icons/64x64/subsonic.png</file>
|
<file>icons/64x64/subsonic.png</file>
|
||||||
<file>icons/64x64/tidal.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/albums.png</file>
|
||||||
<file>icons/48x48/alsa.png</file>
|
<file>icons/48x48/alsa.png</file>
|
||||||
<file>icons/48x48/application-exit.png</file>
|
<file>icons/48x48/application-exit.png</file>
|
||||||
@@ -273,6 +277,8 @@
|
|||||||
<file>icons/48x48/love.png</file>
|
<file>icons/48x48/love.png</file>
|
||||||
<file>icons/48x48/subsonic.png</file>
|
<file>icons/48x48/subsonic.png</file>
|
||||||
<file>icons/48x48/tidal.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/albums.png</file>
|
||||||
<file>icons/32x32/alsa.png</file>
|
<file>icons/32x32/alsa.png</file>
|
||||||
<file>icons/32x32/application-exit.png</file>
|
<file>icons/32x32/application-exit.png</file>
|
||||||
@@ -367,6 +373,8 @@
|
|||||||
<file>icons/32x32/love.png</file>
|
<file>icons/32x32/love.png</file>
|
||||||
<file>icons/32x32/subsonic.png</file>
|
<file>icons/32x32/subsonic.png</file>
|
||||||
<file>icons/32x32/tidal.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/albums.png</file>
|
||||||
<file>icons/22x22/alsa.png</file>
|
<file>icons/22x22/alsa.png</file>
|
||||||
<file>icons/22x22/application-exit.png</file>
|
<file>icons/22x22/application-exit.png</file>
|
||||||
@@ -461,5 +469,7 @@
|
|||||||
<file>icons/22x22/love.png</file>
|
<file>icons/22x22/love.png</file>
|
||||||
<file>icons/22x22/subsonic.png</file>
|
<file>icons/22x22/subsonic.png</file>
|
||||||
<file>icons/22x22/tidal.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>
|
</qresource>
|
||||||
</RCC>
|
</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/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/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/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/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/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/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 |
@@ -62,7 +62,9 @@ CREATE TABLE device_%deviceid_songs (
|
|||||||
effective_albumartist TEXT,
|
effective_albumartist TEXT,
|
||||||
effective_originalyear INTEGER NOT NULL DEFAULT 0,
|
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"
|
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;
|
DELETE FROM schema_version;
|
||||||
|
|
||||||
INSERT INTO schema_version (version) VALUES (12);
|
INSERT INTO schema_version (version) VALUES (13);
|
||||||
|
|
||||||
CREATE TABLE IF NOT EXISTS directories (
|
CREATE TABLE IF NOT EXISTS directories (
|
||||||
path TEXT NOT NULL,
|
path TEXT NOT NULL,
|
||||||
@@ -70,178 +70,9 @@ CREATE TABLE IF NOT EXISTS songs (
|
|||||||
effective_albumartist TEXT,
|
effective_albumartist TEXT,
|
||||||
effective_originalyear INTEGER NOT NULL DEFAULT 0,
|
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
|
|
||||||
|
|
||||||
);
|
|
||||||
|
|
||||||
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
|
|
||||||
|
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -298,7 +129,363 @@ CREATE TABLE IF NOT EXISTS subsonic_songs (
|
|||||||
effective_albumartist TEXT,
|
effective_albumartist TEXT,
|
||||||
effective_originalyear INTEGER NOT NULL DEFAULT 0,
|
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,
|
ui_order INTEGER NOT NULL DEFAULT 0,
|
||||||
special_type TEXT,
|
special_type TEXT,
|
||||||
ui_path 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_albumartist TEXT,
|
||||||
effective_originalyear INTEGER,
|
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(
|
CREATE VIRTUAL TABLE IF NOT EXISTS tidal_artists_songs_fts USING fts5(
|
||||||
|
|
||||||
ftstitle,
|
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,
|
ftstitle,
|
||||||
ftsalbum,
|
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,
|
ftstitle,
|
||||||
ftsalbum,
|
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/logging.h
|
||||||
ext/libstrawberry-common/core/messagehandler.cpp
|
ext/libstrawberry-common/core/messagehandler.cpp
|
||||||
ext/libstrawberry-common/core/messagehandler.h
|
ext/libstrawberry-common/core/messagehandler.h
|
||||||
ext/libstrawberry-common/core/override.h
|
|
||||||
Copyright: 2011, 2012, David Sansome <me@davidsansome.com>
|
Copyright: 2011, 2012, David Sansome <me@davidsansome.com>
|
||||||
License: Apache-2.0
|
License: Apache-2.0
|
||||||
|
|
||||||
|
|||||||
1
dist/CMakeLists.txt
vendored
@@ -7,6 +7,7 @@ endif (APPLE)
|
|||||||
|
|
||||||
if (WIN32)
|
if (WIN32)
|
||||||
configure_file(${CMAKE_CURRENT_SOURCE_DIR}/windows/strawberry.nsi.in ${CMAKE_CURRENT_SOURCE_DIR}/windows/strawberry.nsi @ONLY)
|
configure_file(${CMAKE_CURRENT_SOURCE_DIR}/windows/strawberry.nsi.in ${CMAKE_CURRENT_SOURCE_DIR}/windows/strawberry.nsi @ONLY)
|
||||||
|
configure_file(${CMAKE_CURRENT_SOURCE_DIR}/windows/strawberry-wasapi.nsi.in ${CMAKE_CURRENT_SOURCE_DIR}/windows/strawberry-wasapi.nsi @ONLY)
|
||||||
endif (WIN32)
|
endif (WIN32)
|
||||||
|
|
||||||
if (UNIX AND NOT APPLE)
|
if (UNIX AND NOT APPLE)
|
||||||
|
|||||||
126
dist/macos/macdeploy.py
vendored
@@ -21,7 +21,6 @@ import logging
|
|||||||
import os
|
import os
|
||||||
import re
|
import re
|
||||||
import subprocess
|
import subprocess
|
||||||
import commands
|
|
||||||
import sys
|
import sys
|
||||||
import traceback
|
import traceback
|
||||||
|
|
||||||
@@ -67,56 +66,57 @@ GSTREAMER_SEARCH_PATH = [
|
|||||||
|
|
||||||
GSTREAMER_PLUGINS = [
|
GSTREAMER_PLUGINS = [
|
||||||
|
|
||||||
'libgstapetag.so',
|
'libgstapetag.dylib',
|
||||||
'libgstapp.so',
|
'libgstapp.dylib',
|
||||||
'libgstaudioconvert.so',
|
'libgstaudioconvert.dylib',
|
||||||
'libgstaudiofx.so',
|
'libgstaudiofx.dylib',
|
||||||
'libgstaudiomixer.so',
|
'libgstaudiomixer.dylib',
|
||||||
'libgstaudioparsers.so',
|
'libgstaudioparsers.dylib',
|
||||||
'libgstaudiorate.so',
|
'libgstaudiorate.dylib',
|
||||||
'libgstaudioresample.so',
|
'libgstaudioresample.dylib',
|
||||||
'libgstaudiotestsrc.so',
|
'libgstaudiotestsrc.dylib',
|
||||||
'libgstaudiovisualizers.so',
|
'libgstaudiovisualizers.dylib',
|
||||||
'libgstauparse.so',
|
'libgstauparse.dylib',
|
||||||
'libgstautoconvert.so',
|
'libgstautoconvert.dylib',
|
||||||
'libgstautodetect.so',
|
'libgstautodetect.dylib',
|
||||||
'libgstcoreelements.so',
|
'libgstcoreelements.dylib',
|
||||||
'libgstequalizer.so',
|
'libgstequalizer.dylib',
|
||||||
'libgstgio.so',
|
'libgstgio.dylib',
|
||||||
'libgsticydemux.so',
|
'libgsticydemux.dylib',
|
||||||
'libgstid3demux.so',
|
'libgstid3demux.dylib',
|
||||||
'libgstlevel.so',
|
'libgstlevel.dylib',
|
||||||
'libgstosxaudio.so',
|
'libgstosxaudio.dylib',
|
||||||
'libgstplayback.so',
|
'libgstplayback.dylib',
|
||||||
'libgstrawparse.so',
|
'libgstrawparse.dylib',
|
||||||
'libgstrealmedia.so',
|
'libgstrealmedia.dylib',
|
||||||
'libgstreplaygain.so',
|
'libgstreplaygain.dylib',
|
||||||
'libgstsoup.so',
|
'libgstsoup.dylib',
|
||||||
'libgstspectrum.so',
|
'libgstspectrum.dylib',
|
||||||
'libgsttypefindfunctions.so',
|
'libgsttypefindfunctions.dylib',
|
||||||
'libgstvolume.so',
|
'libgstvolume.dylib',
|
||||||
'libgstxingmux.so',
|
'libgstxingmux.dylib',
|
||||||
'libgsttcp.so',
|
'libgsttcp.dylib',
|
||||||
'libgstudp.so',
|
'libgstudp.dylib',
|
||||||
'libgstpbtypes.so',
|
'libgstpbtypes.dylib',
|
||||||
'libgstrtp.so',
|
'libgstrtp.dylib',
|
||||||
'libgstrtsp.so',
|
'libgstrtsp.dylib',
|
||||||
|
|
||||||
'libgstflac.so',
|
'libgstflac.dylib',
|
||||||
'libgstwavparse.so',
|
'libgstwavparse.dylib',
|
||||||
'libgstfaac.so',
|
'libgstfaac.dylib',
|
||||||
'libgstfaad.so',
|
'libgstfaad.dylib',
|
||||||
'libgstogg.so',
|
'libgstogg.dylib',
|
||||||
'libgstopus.so',
|
'libgstopus.dylib',
|
||||||
'libgstopusparse.so',
|
'libgstopusparse.dylib',
|
||||||
'libgstasf.so',
|
'libgstasf.dylib',
|
||||||
'libgstspeex.so',
|
'libgstspeex.dylib',
|
||||||
'libgsttaglib.so',
|
'libgsttaglib.dylib',
|
||||||
'libgstvorbis.so',
|
'libgstvorbis.dylib',
|
||||||
'libgstisomp4.so',
|
'libgstisomp4.dylib',
|
||||||
'libgstlibav.so',
|
'libgstlibav.dylib',
|
||||||
'libgstaiff.so',
|
'libgstaiff.dylib',
|
||||||
'libgstlame.so',
|
'libgstlame.dylib',
|
||||||
|
'libgstmusepack.dylib',
|
||||||
|
|
||||||
]
|
]
|
||||||
|
|
||||||
@@ -156,7 +156,7 @@ class CouldNotFindGstreamerPluginError(Error):
|
|||||||
pass
|
pass
|
||||||
|
|
||||||
if len(sys.argv) < 2:
|
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]
|
bundle_dir = sys.argv[1]
|
||||||
|
|
||||||
@@ -176,20 +176,21 @@ fixed_frameworks = set()
|
|||||||
|
|
||||||
|
|
||||||
def GetBrokenLibraries(binary):
|
def GetBrokenLibraries(binary):
|
||||||
#print "Checking libs for binary: %s" % binary
|
#print("Checking libs for binary: %s" % binary)
|
||||||
output = subprocess.Popen([OTOOL, '-L', binary], stdout=subprocess.PIPE).communicate()[0]
|
output = subprocess.Popen([OTOOL, '-L', binary], stdout=subprocess.PIPE).communicate()[0].decode('utf-8')
|
||||||
broken_libs = {'frameworks': [], 'libs': []}
|
broken_libs = {'frameworks': [], 'libs': []}
|
||||||
for line in [x.split(' ')[0].lstrip() for x in output.split('\n')[1:]]:
|
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
|
if not line: # skip empty lines
|
||||||
continue
|
continue
|
||||||
if os.path.basename(binary) == os.path.basename(line):
|
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
|
continue
|
||||||
if re.match(r'^\s*/System/', line):
|
if re.match(r'^\s*/System/', line):
|
||||||
|
#print("system framework: %s" % line)
|
||||||
continue # System framework
|
continue # System framework
|
||||||
elif re.match(r'^\s*/usr/lib/', line):
|
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
|
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):
|
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
|
# Potentially already fixed library
|
||||||
@@ -206,8 +207,9 @@ def GetBrokenLibraries(binary):
|
|||||||
if not os.path.exists(os.path.join(frameworks_dir, relative_path)):
|
if not os.path.exists(os.path.join(frameworks_dir, relative_path)):
|
||||||
broken_libs['frameworks'].append(relative_path)
|
broken_libs['frameworks'].append(relative_path)
|
||||||
else:
|
else:
|
||||||
print "GetBrokenLibraries Error: %s" % line
|
print("GetBrokenLibraries Error: %s" % line)
|
||||||
elif re.search(r'\w+\.framework', line):
|
elif re.search(r'\w+\.framework', line):
|
||||||
|
#print("framework: %s" % line)
|
||||||
broken_libs['frameworks'].append(line)
|
broken_libs['frameworks'].append(line)
|
||||||
else:
|
else:
|
||||||
broken_libs['libs'].append(line)
|
broken_libs['libs'].append(line)
|
||||||
@@ -279,7 +281,7 @@ def FixLibrary(path):
|
|||||||
|
|
||||||
abs_path = FindLibrary(path)
|
abs_path = FindLibrary(path)
|
||||||
if abs_path == "":
|
if abs_path == "":
|
||||||
print "Could not resolve %s, not fixing!" % path
|
print("Could not resolve %s, not fixing!" % path)
|
||||||
return
|
return
|
||||||
|
|
||||||
broken_libs = GetBrokenLibraries(abs_path)
|
broken_libs = GetBrokenLibraries(abs_path)
|
||||||
@@ -482,7 +484,7 @@ def main():
|
|||||||
try:
|
try:
|
||||||
FixPlugin('strawberry-tagreader', '.')
|
FixPlugin('strawberry-tagreader', '.')
|
||||||
except:
|
except:
|
||||||
print 'Failed to find blob: %s' % traceback.format_exc()
|
print('Failed to find blob: %s' % traceback.format_exc())
|
||||||
|
|
||||||
for plugin in GSTREAMER_PLUGINS:
|
for plugin in GSTREAMER_PLUGINS:
|
||||||
FixPlugin(FindGstreamerPlugin(plugin), 'gstreamer')
|
FixPlugin(FindGstreamerPlugin(plugin), 'gstreamer')
|
||||||
@@ -495,11 +497,11 @@ def main():
|
|||||||
#FixPlugin(FindQtPlugin(plugin), os.path.dirname(plugin))
|
#FixPlugin(FindQtPlugin(plugin), os.path.dirname(plugin))
|
||||||
|
|
||||||
if len(sys.argv) <= 2:
|
if len(sys.argv) <= 2:
|
||||||
print 'Would run %d commands:' % len(commands)
|
print('Would run %d commands:' % len(commands))
|
||||||
for command in commands:
|
for command in commands:
|
||||||
print ' '.join(command)
|
print(' '.join(command))
|
||||||
|
|
||||||
#print 'OK?'
|
#print('OK?')
|
||||||
#raw_input()
|
#raw_input()
|
||||||
|
|
||||||
for command in commands:
|
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
|
# You should have received a copy of the GNU General Public License
|
||||||
# along with Strawberry. If not, see <http://www.gnu.org/licenses/>.
|
# 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 s in $sizes
|
||||||
|
|
||||||
#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
|
|
||||||
do
|
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
|
do
|
||||||
file=`basename $x`
|
file=`basename $f`
|
||||||
|
|
||||||
for y in $sizes
|
for y in $sizes
|
||||||
do
|
do
|
||||||
if [ "$y" = "$i" ]; then
|
|
||||||
|
if [ "$s" = "$y" ]; then
|
||||||
continue
|
continue
|
||||||
fi
|
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
|
fi
|
||||||
done
|
done
|
||||||
|
|
||||||
id=`identify "$x"` || exit 1
|
if [ "$dir" = "full" ]; then
|
||||||
|
continue
|
||||||
|
fi
|
||||||
|
|
||||||
|
id=`identify "$f"` || exit 1
|
||||||
if [ "$id" = "" ] ; then
|
if [ "$id" = "" ] ; then
|
||||||
echo "ERROR: Cannot determine format and geometry for image: \"$x\"."
|
echo "ERROR: Cannot determine format and geometry for image: \"$f\"."
|
||||||
continue
|
continue
|
||||||
fi
|
fi
|
||||||
g=`echo $id | awk '{print $3}'` || exit 1
|
g=`echo $id | awk '{print $3}'` || exit 1
|
||||||
if [ "$g" = "" ] ; then
|
if [ "$g" = "" ] ; then
|
||||||
echo "ERROR: Cannot determine geometry for image: \"$x\"."
|
echo "ERROR: Cannot determine geometry for image: \"$f\"."
|
||||||
continue
|
continue
|
||||||
fi
|
fi
|
||||||
|
|
||||||
@@ -102,17 +78,17 @@ do
|
|||||||
# we need to get rid of the plus (+) and the x characters:
|
# 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
|
w=`echo $g | sed 's/[^0-9]/ /g' | awk '{print $1}'` || exit 1
|
||||||
if [ "$w" = "" ] ; then
|
if [ "$w" = "" ] ; then
|
||||||
echo "ERROR: Cannot determine width for image: \"$x\"."
|
echo "ERROR: Cannot determine width for image: \"$f\"."
|
||||||
continue
|
continue
|
||||||
fi
|
fi
|
||||||
h=`echo $g | sed 's/[^0-9]/ /g' | awk '{print $2}'` || exit 1
|
h=`echo $g | sed 's/[^0-9]/ /g' | awk '{print $2}'` || exit 1
|
||||||
if [ "$h" = "" ] ; then
|
if [ "$h" = "" ] ; then
|
||||||
echo "ERROR: Cannot determine height for image: \"$x\"."
|
echo "ERROR: Cannot determine height for image: \"$f\"."
|
||||||
continue
|
continue
|
||||||
fi
|
fi
|
||||||
|
|
||||||
if ! [ "${h}x${w}" = "$i" ]; then
|
if ! [ "${h}x${w}" = "$dir" ]; then
|
||||||
echo "Warning: $x is not $i, but ${h}x${w}!"
|
echo "Warning: $f is not $dir, but ${h}x${w}!"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
done
|
done
|
||||||
|
|||||||
61
dist/unix/strawberry.spec.in
vendored
@@ -80,13 +80,6 @@ BuildRequires: pkgconfig(libvlc)
|
|||||||
Requires: libQt5Sql5-sqlite
|
Requires: libQt5Sql5-sqlite
|
||||||
%endif
|
%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
|
%description
|
||||||
Strawberry is a music player and music collection organizer.
|
Strawberry is a music player and music collection organizer.
|
||||||
It is a fork of Clementine. The name is inspired by the band Strawbs.
|
It is a fork of Clementine. The name is inspired by the band Strawbs.
|
||||||
@@ -114,62 +107,34 @@ Features:
|
|||||||
%setup -qn %{name}-@STRAWBERRY_VERSION_PACKAGE@
|
%setup -qn %{name}-@STRAWBERRY_VERSION_PACKAGE@
|
||||||
|
|
||||||
%build
|
%build
|
||||||
%if 0%{?suse_version} || 0%{?mageia}
|
%{cmake} -DCMAKE_BUILD_TYPE:STRING=Release
|
||||||
%{cmake} ..
|
%if 0%{?centos} || 0%{?mageia}
|
||||||
|
%make_build
|
||||||
%else
|
%else
|
||||||
mkdir -p %{_target_platform}
|
%cmake_build
|
||||||
pushd %{_target_platform}
|
|
||||||
%{cmake} ..
|
|
||||||
popd
|
|
||||||
%endif
|
|
||||||
|
|
||||||
%if 0%{?suse_version} || 0%{?mageia}
|
|
||||||
%if 0%{?suse_version} && 0%{?suse_version} < 1500
|
|
||||||
make %{?_smp_mflags}
|
|
||||||
%else
|
|
||||||
%make_build
|
|
||||||
%endif
|
|
||||||
%else
|
|
||||||
%make_build -C %{_target_platform}
|
|
||||||
%endif
|
%endif
|
||||||
|
|
||||||
%install
|
%install
|
||||||
%if 0%{?suse_version}
|
%if 0%{?centos}
|
||||||
%cmake_install
|
%make_install
|
||||||
%else
|
%else
|
||||||
%if 0%{?mageia}
|
%if 0%{?mageia}
|
||||||
%make_install -C build
|
%make_install -C build
|
||||||
%else
|
%else
|
||||||
%make_install -C %{_target_platform}
|
%cmake_install
|
||||||
%endif
|
|
||||||
%endif
|
%endif
|
||||||
|
|
||||||
%if 0%{?suse_version} && 0%{?suse_version} < 1500
|
|
||||||
rm -f %{buildroot}%{_datadir}/metainfo/org.strawberrymusicplayer.strawberry.appdata.xml
|
|
||||||
%endif
|
%endif
|
||||||
|
|
||||||
%if 0%{?suse_version}
|
%if 0%{?suse_version}
|
||||||
%suse_update_desktop_file org.strawberrymusicplayer.strawberry Qt AudioVideo Audio Player
|
%suse_update_desktop_file org.strawberrymusicplayer.strawberry Qt AudioVideo Audio Player
|
||||||
%endif
|
%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
|
%check
|
||||||
desktop-file-validate %{buildroot}%{_datadir}/applications/org.strawberrymusicplayer.strawberry.desktop
|
desktop-file-validate %{buildroot}%{_datadir}/applications/org.strawberrymusicplayer.strawberry.desktop
|
||||||
%if 0%{?fedora_version}
|
%if 0%{?fedora_version}
|
||||||
appstream-util validate-relax --nonet %{buildroot}%{_metainfodir}/org.strawberrymusicplayer.strawberry.appdata.xml
|
appstream-util validate-relax --nonet %{buildroot}%{_metainfodir}/org.strawberrymusicplayer.strawberry.appdata.xml
|
||||||
%else
|
%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
|
||||||
appstream-util validate-relax --nonet %{buildroot}%{_datadir}/metainfo/org.strawberrymusicplayer.strawberry.appdata.xml
|
|
||||||
%endif
|
|
||||||
%endif
|
%endif
|
||||||
|
|
||||||
%files
|
%files
|
||||||
@@ -183,9 +148,7 @@ desktop-file-validate %{buildroot}%{_datadir}/applications/org.strawberrymusicpl
|
|||||||
%if 0%{?fedora_version}
|
%if 0%{?fedora_version}
|
||||||
%{_metainfodir}/*.appdata.xml
|
%{_metainfodir}/*.appdata.xml
|
||||||
%else
|
%else
|
||||||
%if ! 0%{?suse_version} || ( 0%{?suse_version} && 0%{?suse_version} >= 1500 )
|
%{_datadir}/metainfo/*.appdata.xml
|
||||||
%{_datadir}/metainfo/*.appdata.xml
|
|
||||||
%endif
|
|
||||||
%endif
|
%endif
|
||||||
%{_mandir}/man1/%{name}.1.*
|
%{_mandir}/man1/%{name}.1.*
|
||||||
%{_mandir}/man1/%{name}-tagreader.1.*
|
%{_mandir}/man1/%{name}-tagreader.1.*
|
||||||
|
|||||||
182
dist/windows/strawberry-wasapi.nsi.in
vendored
Normal file
@@ -0,0 +1,182 @@
|
|||||||
|
!if "@ARCH@" == x86
|
||||||
|
!define arch_x86
|
||||||
|
!endif
|
||||||
|
|
||||||
|
!if "@ARCH@" == i686-w64-mingw32.shared
|
||||||
|
!define arch_x86
|
||||||
|
!endif
|
||||||
|
|
||||||
|
!if "@ARCH@" == x86_64
|
||||||
|
!define arch_x64
|
||||||
|
!endif
|
||||||
|
|
||||||
|
!if "@ARCH@" == x86_64-w64-mingw32.shared
|
||||||
|
!define arch_x64
|
||||||
|
!endif
|
||||||
|
|
||||||
|
!if "@CMAKE_BUILD_TYPE@" == Debug
|
||||||
|
!define debug
|
||||||
|
!endif
|
||||||
|
|
||||||
|
!if "@BUILD_WITH_QT6@" == "ON"
|
||||||
|
!define with_qt6
|
||||||
|
!endif
|
||||||
|
|
||||||
|
!ifdef debug
|
||||||
|
!define PRODUCT_NAME "Strawberry Music Player Debug WASAPI plugin"
|
||||||
|
!define PRODUCT_NAME_SHORT "StrawberryWASAPI"
|
||||||
|
!define PRODUCT_UNINST_KEY "Software\Microsoft\Windows\CurrentVersion\Uninstall\${PRODUCT_NAME_SHORT}Debug"
|
||||||
|
!ifdef arch_x86
|
||||||
|
!define PRODUCT_INSTALL_DIR "$PROGRAMFILES\Strawberry Music Player Debug"
|
||||||
|
!endif
|
||||||
|
!ifdef arch_x64
|
||||||
|
!define PRODUCT_INSTALL_DIR "$PROGRAMFILES64\Strawberry Music Player Debug"
|
||||||
|
!endif
|
||||||
|
!else
|
||||||
|
!define PRODUCT_NAME "Strawberry Music Player WASAPI plugin"
|
||||||
|
!define PRODUCT_NAME_SHORT "StrawberryWASAPI"
|
||||||
|
!define PRODUCT_UNINST_KEY "Software\Microsoft\Windows\CurrentVersion\Uninstall\${PRODUCT_NAME_SHORT}"
|
||||||
|
!ifdef arch_x86
|
||||||
|
!define PRODUCT_INSTALL_DIR "$PROGRAMFILES\Strawberry Music Player"
|
||||||
|
!endif
|
||||||
|
!ifdef arch_x64
|
||||||
|
!define PRODUCT_INSTALL_DIR "$PROGRAMFILES64\Strawberry Music Player"
|
||||||
|
!endif
|
||||||
|
!endif
|
||||||
|
|
||||||
|
!define PRODUCT_VERSION_MAJOR @STRAWBERRY_VERSION_MAJOR@
|
||||||
|
!define PRODUCT_VERSION_MINOR @STRAWBERRY_VERSION_MINOR@
|
||||||
|
!define PRODUCT_VERSION_PATCH @STRAWBERRY_VERSION_PATCH@
|
||||||
|
!define PRODUCT_DISPLAY_VERSION "@STRAWBERRY_VERSION_PACKAGE@"
|
||||||
|
!define PRODUCT_DISPLAY_VERSION_SHORT "@STRAWBERRY_VERSION_PACKAGE@"
|
||||||
|
|
||||||
|
!define PRODUCT_PUBLISHER "Jonas Kvinge"
|
||||||
|
!define PRODUCT_WEB_SITE "https://www.strawberrymusicplayer.org/"
|
||||||
|
|
||||||
|
!define PRODUCT_UNINST_ROOT_KEY "HKLM"
|
||||||
|
|
||||||
|
SetCompressor /SOLID lzma
|
||||||
|
|
||||||
|
!addplugindir nsisplugins
|
||||||
|
!include "MUI2.nsh"
|
||||||
|
!include "FileAssociation.nsh"
|
||||||
|
!include "Capabilities.nsh"
|
||||||
|
!include LogicLib.nsh
|
||||||
|
!include x64.nsh
|
||||||
|
|
||||||
|
!define MUI_ICON "strawberry.ico"
|
||||||
|
|
||||||
|
!define MUI_COMPONENTSPAGE_SMALLDESC
|
||||||
|
|
||||||
|
; Installer pages
|
||||||
|
!insertmacro MUI_PAGE_WELCOME
|
||||||
|
!insertmacro MUI_PAGE_DIRECTORY
|
||||||
|
!insertmacro MUI_PAGE_INSTFILES
|
||||||
|
!insertmacro MUI_PAGE_FINISH
|
||||||
|
|
||||||
|
; Uninstaller pages
|
||||||
|
!insertmacro MUI_UNPAGE_CONFIRM
|
||||||
|
!insertmacro MUI_UNPAGE_INSTFILES
|
||||||
|
!insertmacro MUI_UNPAGE_FINISH
|
||||||
|
|
||||||
|
!insertmacro MUI_LANGUAGE "English" ;first language is the default language
|
||||||
|
|
||||||
|
Name "${PRODUCT_NAME}"
|
||||||
|
!ifdef arch_x86
|
||||||
|
!ifdef debug
|
||||||
|
!ifdef with_qt6
|
||||||
|
OutFile "${PRODUCT_NAME_SHORT}Setup-${PRODUCT_DISPLAY_VERSION}-Qt6-Debug-x86.exe"
|
||||||
|
!else
|
||||||
|
OutFile "${PRODUCT_NAME_SHORT}Setup-${PRODUCT_DISPLAY_VERSION}-Debug-x86.exe"
|
||||||
|
!endif
|
||||||
|
!else
|
||||||
|
!ifdef with_qt6
|
||||||
|
OutFile "${PRODUCT_NAME_SHORT}Setup-${PRODUCT_DISPLAY_VERSION}-Qt6-x86.exe"
|
||||||
|
!else
|
||||||
|
OutFile "${PRODUCT_NAME_SHORT}Setup-${PRODUCT_DISPLAY_VERSION}-x86.exe"
|
||||||
|
!endif
|
||||||
|
!endif
|
||||||
|
!endif
|
||||||
|
|
||||||
|
!ifdef arch_x64
|
||||||
|
!ifdef debug
|
||||||
|
!ifdef with_qt6
|
||||||
|
OutFile "${PRODUCT_NAME_SHORT}Setup-${PRODUCT_DISPLAY_VERSION}-Qt6-Debug-x64.exe"
|
||||||
|
!else
|
||||||
|
OutFile "${PRODUCT_NAME_SHORT}Setup-${PRODUCT_DISPLAY_VERSION}-Debug-x64.exe"
|
||||||
|
!endif
|
||||||
|
!else
|
||||||
|
!ifdef with_qt6
|
||||||
|
OutFile "${PRODUCT_NAME_SHORT}Setup-${PRODUCT_DISPLAY_VERSION}-Qt6-x64.exe"
|
||||||
|
!else
|
||||||
|
OutFile "${PRODUCT_NAME_SHORT}Setup-${PRODUCT_DISPLAY_VERSION}-x64.exe"
|
||||||
|
!endif
|
||||||
|
!endif
|
||||||
|
!endif
|
||||||
|
|
||||||
|
InstallDir "${PRODUCT_INSTALL_DIR}"
|
||||||
|
|
||||||
|
; Get the path where Strawberry was installed previously and set it as default path
|
||||||
|
InstallDirRegKey ${PRODUCT_UNINST_ROOT_KEY} ${PRODUCT_UNINST_KEY} "UninstallString"
|
||||||
|
|
||||||
|
ShowInstDetails show
|
||||||
|
ShowUnInstDetails show
|
||||||
|
RequestExecutionLevel admin
|
||||||
|
|
||||||
|
; Check for previous installation, and call the uninstaller if any
|
||||||
|
Function CheckPreviousInstall
|
||||||
|
|
||||||
|
ReadRegStr $R0 ${PRODUCT_UNINST_ROOT_KEY} ${PRODUCT_UNINST_KEY} "UninstallString"
|
||||||
|
StrCmp $R0 "" done
|
||||||
|
|
||||||
|
MessageBox MB_OKCANCEL|MB_ICONEXCLAMATION \
|
||||||
|
"${PRODUCT_NAME} is already installed. $\n$\nClick `OK` to remove the \
|
||||||
|
previous version or `Cancel` to cancel this upgrade." \
|
||||||
|
IDOK uninst
|
||||||
|
Abort
|
||||||
|
; Run the uninstaller
|
||||||
|
uninst:
|
||||||
|
ClearErrors
|
||||||
|
ExecWait '$R0' ; Do not copy the uninstaller to a temp file
|
||||||
|
|
||||||
|
done:
|
||||||
|
|
||||||
|
FunctionEnd
|
||||||
|
|
||||||
|
Function .onInit
|
||||||
|
|
||||||
|
!insertmacro MUI_LANGDLL_DISPLAY
|
||||||
|
|
||||||
|
Call CheckPreviousInstall
|
||||||
|
|
||||||
|
FunctionEnd
|
||||||
|
|
||||||
|
Section "Gstreamer wasapi plugin" gstreamer-wasapi-plugin
|
||||||
|
SetOutPath "$INSTDIR\gstreamer-plugins"
|
||||||
|
File "/oname=libgstwasapi.dll" "gstreamer-plugins\libgstwasapi.dll"
|
||||||
|
SectionEnd
|
||||||
|
|
||||||
|
Section "Uninstaller"
|
||||||
|
; Create uninstaller
|
||||||
|
WriteUninstaller "$INSTDIR\Uninstall-WASAPI.exe"
|
||||||
|
|
||||||
|
WriteRegStr ${PRODUCT_UNINST_ROOT_KEY} "${PRODUCT_UNINST_KEY}" "DisplayName" "${PRODUCT_NAME}"
|
||||||
|
WriteRegStr ${PRODUCT_UNINST_ROOT_KEY} "${PRODUCT_UNINST_KEY}" "UninstallString" "$INSTDIR\Uninstall-WASAPI.exe"
|
||||||
|
WriteRegStr ${PRODUCT_UNINST_ROOT_KEY} "${PRODUCT_UNINST_KEY}" "DisplayIcon" "$INSTDIR\strawberry.ico"
|
||||||
|
WriteRegStr ${PRODUCT_UNINST_ROOT_KEY} "${PRODUCT_UNINST_KEY}" "DisplayVersion" "${PRODUCT_DISPLAY_VERSION}"
|
||||||
|
WriteRegDWORD ${PRODUCT_UNINST_ROOT_KEY} "${PRODUCT_UNINST_KEY}" "VersionMajor" "${PRODUCT_VERSION_MAJOR}"
|
||||||
|
WriteRegDWORD ${PRODUCT_UNINST_ROOT_KEY} "${PRODUCT_UNINST_KEY}" "VersionMinor" "${PRODUCT_VERSION_MINOR}"
|
||||||
|
WriteRegStr ${PRODUCT_UNINST_ROOT_KEY} "${PRODUCT_UNINST_KEY}" "URLInfoAbout" "${PRODUCT_WEB_SITE}"
|
||||||
|
WriteRegStr ${PRODUCT_UNINST_ROOT_KEY} "${PRODUCT_UNINST_KEY}" "Publisher" "${PRODUCT_PUBLISHER}"
|
||||||
|
|
||||||
|
SectionEnd
|
||||||
|
|
||||||
|
Section "Uninstall"
|
||||||
|
|
||||||
|
Delete "$INSTDIR\gstreamer-plugins\libgstwasapi.dll"
|
||||||
|
Delete "$INSTDIR\Uninstall-WASAPI.exe"
|
||||||
|
|
||||||
|
; Remove the entry from 'installed programs list'
|
||||||
|
DeleteRegKey ${PRODUCT_UNINST_ROOT_KEY} "${PRODUCT_UNINST_KEY}"
|
||||||
|
|
||||||
|
SectionEnd
|
||||||
111
dist/windows/strawberry.nsi.in
vendored
@@ -18,7 +18,7 @@
|
|||||||
!define debug
|
!define debug
|
||||||
!endif
|
!endif
|
||||||
|
|
||||||
!if "@WITH_QT6@" == "ON"
|
!if "@BUILD_WITH_QT6@" == "ON"
|
||||||
!define with_qt6
|
!define with_qt6
|
||||||
!endif
|
!endif
|
||||||
|
|
||||||
@@ -174,7 +174,27 @@ Section "Strawberry" Strawberry
|
|||||||
File "strawberry.exe"
|
File "strawberry.exe"
|
||||||
File "strawberry-tagreader.exe"
|
File "strawberry-tagreader.exe"
|
||||||
File "strawberry.ico"
|
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 "libbz2.dll"
|
||||||
File "libcdio-19.dll"
|
File "libcdio-19.dll"
|
||||||
File "libchromaprint.dll"
|
File "libchromaprint.dll"
|
||||||
@@ -214,21 +234,31 @@ Section "Strawberry" Strawberry
|
|||||||
File "libnettle-8.dll"
|
File "libnettle-8.dll"
|
||||||
File "libogg-0.dll"
|
File "libogg-0.dll"
|
||||||
File "libopus-0.dll"
|
File "libopus-0.dll"
|
||||||
|
File "liborc-0.4-0.dll"
|
||||||
File "libpcre-1.dll"
|
File "libpcre-1.dll"
|
||||||
File "libpcre2-16-0.dll"
|
File "libpcre2-16-0.dll"
|
||||||
File "libpng16-16.dll"
|
File "libpng16-16.dll"
|
||||||
File "libprotobuf-23.dll"
|
File "libprotobuf-24.dll"
|
||||||
|
File "libpsl-5.dll"
|
||||||
File "libsoup-2.4-1.dll"
|
File "libsoup-2.4-1.dll"
|
||||||
File "libspeex-1.dll"
|
File "libspeex-1.dll"
|
||||||
File "libsqlite3-0.dll"
|
File "libsqlite3-0.dll"
|
||||||
|
File "libssp-0.dll"
|
||||||
File "libstdc++-6.dll"
|
File "libstdc++-6.dll"
|
||||||
File "libtag.dll"
|
File "libtag.dll"
|
||||||
|
File "libtasn1-6.dll"
|
||||||
File "libunistring-2.dll"
|
File "libunistring-2.dll"
|
||||||
File "libvorbis-0.dll"
|
File "libvorbis-0.dll"
|
||||||
File "libvorbisenc-2.dll"
|
File "libvorbisenc-2.dll"
|
||||||
File "libwavpack-1.dll"
|
File "libwavpack-1.dll"
|
||||||
File "libwinpthread-1.dll"
|
File "libwinpthread-1.dll"
|
||||||
File "libxml2-2.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
|
!ifdef with_qt6
|
||||||
File "Qt6Concurrent.dll"
|
File "Qt6Concurrent.dll"
|
||||||
File "Qt6Core.dll"
|
File "Qt6Core.dll"
|
||||||
@@ -248,25 +278,6 @@ Section "Strawberry" Strawberry
|
|||||||
File "Qt5WinExtras.dll"
|
File "Qt5WinExtras.dll"
|
||||||
File "libqtsparkle-qt5.dll"
|
File "libqtsparkle-qt5.dll"
|
||||||
!endif
|
!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"
|
|
||||||
!endif
|
|
||||||
|
|
||||||
File "killproc.exe"
|
File "killproc.exe"
|
||||||
|
|
||||||
@@ -391,6 +402,12 @@ Section "Start menu items" startmenu
|
|||||||
|
|
||||||
SectionEnd
|
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"
|
Section "Uninstaller"
|
||||||
; Create uninstaller
|
; Create uninstaller
|
||||||
WriteUninstaller "$INSTDIR\Uninstall.exe"
|
WriteUninstaller "$INSTDIR\Uninstall.exe"
|
||||||
@@ -415,7 +432,27 @@ Section "Uninstall"
|
|||||||
Delete "$INSTDIR\strawberry.ico"
|
Delete "$INSTDIR\strawberry.ico"
|
||||||
Delete "$INSTDIR\strawberry.exe"
|
Delete "$INSTDIR\strawberry.exe"
|
||||||
Delete "$INSTDIR\strawberry-tagreader.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\libbz2.dll"
|
||||||
Delete "$INSTDIR\libcdio-19.dll"
|
Delete "$INSTDIR\libcdio-19.dll"
|
||||||
Delete "$INSTDIR\libchromaprint.dll"
|
Delete "$INSTDIR\libchromaprint.dll"
|
||||||
@@ -455,21 +492,29 @@ Section "Uninstall"
|
|||||||
Delete "$INSTDIR\libnettle-8.dll"
|
Delete "$INSTDIR\libnettle-8.dll"
|
||||||
Delete "$INSTDIR\libogg-0.dll"
|
Delete "$INSTDIR\libogg-0.dll"
|
||||||
Delete "$INSTDIR\libopus-0.dll"
|
Delete "$INSTDIR\libopus-0.dll"
|
||||||
|
Delete "$INSTDIR\liborc-0.4-0.dll"
|
||||||
Delete "$INSTDIR\libpcre-1.dll"
|
Delete "$INSTDIR\libpcre-1.dll"
|
||||||
Delete "$INSTDIR\libpcre2-16-0.dll"
|
Delete "$INSTDIR\libpcre2-16-0.dll"
|
||||||
Delete "$INSTDIR\libpng16-16.dll"
|
Delete "$INSTDIR\libpng16-16.dll"
|
||||||
Delete "$INSTDIR\libprotobuf-23.dll"
|
Delete "$INSTDIR\libprotobuf-24.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\libsoup-2.4-1.dll"
|
||||||
Delete "$INSTDIR\libspeex-1.dll"
|
Delete "$INSTDIR\libspeex-1.dll"
|
||||||
Delete "$INSTDIR\libsqlite3-0.dll"
|
Delete "$INSTDIR\libsqlite3-0.dll"
|
||||||
|
Delete "$INSTDIR\libssp-0.dll"
|
||||||
Delete "$INSTDIR\libstdc++-6.dll"
|
Delete "$INSTDIR\libstdc++-6.dll"
|
||||||
Delete "$INSTDIR\libtag.dll"
|
Delete "$INSTDIR\libtag.dll"
|
||||||
|
Delete "$INSTDIR\libtasn1-6.dll"
|
||||||
Delete "$INSTDIR\libunistring-2.dll"
|
Delete "$INSTDIR\libunistring-2.dll"
|
||||||
Delete "$INSTDIR\libvorbis-0.dll"
|
Delete "$INSTDIR\libvorbis-0.dll"
|
||||||
Delete "$INSTDIR\libvorbisenc-2.dll"
|
Delete "$INSTDIR\libvorbisenc-2.dll"
|
||||||
Delete "$INSTDIR\libwavpack-1.dll"
|
Delete "$INSTDIR\libwavpack-1.dll"
|
||||||
Delete "$INSTDIR\libwinpthread-1.dll"
|
Delete "$INSTDIR\libwinpthread-1.dll"
|
||||||
Delete "$INSTDIR\libxml2-2.dll"
|
Delete "$INSTDIR\libxml2-2.dll"
|
||||||
|
Delete "$INSTDIR\libzstd.dll"
|
||||||
|
Delete "$INSTDIR\postproc-55.dll"
|
||||||
Delete "$INSTDIR\Qt5Concurrent.dll"
|
Delete "$INSTDIR\Qt5Concurrent.dll"
|
||||||
Delete "$INSTDIR\Qt5Core.dll"
|
Delete "$INSTDIR\Qt5Core.dll"
|
||||||
Delete "$INSTDIR\Qt5Gui.dll"
|
Delete "$INSTDIR\Qt5Gui.dll"
|
||||||
@@ -477,7 +522,6 @@ Section "Uninstall"
|
|||||||
Delete "$INSTDIR\Qt5Sql.dll"
|
Delete "$INSTDIR\Qt5Sql.dll"
|
||||||
Delete "$INSTDIR\Qt5Widgets.dll"
|
Delete "$INSTDIR\Qt5Widgets.dll"
|
||||||
Delete "$INSTDIR\Qt5WinExtras.dll"
|
Delete "$INSTDIR\Qt5WinExtras.dll"
|
||||||
Delete "$INSTDIR\libqtsparkle-qt5.dll"
|
|
||||||
Delete "$INSTDIR\Qt6Concurrent.dll"
|
Delete "$INSTDIR\Qt6Concurrent.dll"
|
||||||
Delete "$INSTDIR\Qt6Core.dll"
|
Delete "$INSTDIR\Qt6Core.dll"
|
||||||
Delete "$INSTDIR\Qt6Gui.dll"
|
Delete "$INSTDIR\Qt6Gui.dll"
|
||||||
@@ -485,26 +529,9 @@ Section "Uninstall"
|
|||||||
Delete "$INSTDIR\Qt6Sql.dll"
|
Delete "$INSTDIR\Qt6Sql.dll"
|
||||||
Delete "$INSTDIR\Qt6Widgets.dll"
|
Delete "$INSTDIR\Qt6Widgets.dll"
|
||||||
Delete "$INSTDIR\Qt6WinExtras.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\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"
|
|
||||||
!endif
|
|
||||||
|
|
||||||
Delete "$INSTDIR\gio-modules\libgiognutls.dll"
|
Delete "$INSTDIR\gio-modules\libgiognutls.dll"
|
||||||
Delete "$INSTDIR\platforms\qwindows.dll"
|
Delete "$INSTDIR\platforms\qwindows.dll"
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ if(APPLE)
|
|||||||
list(APPEND SOURCES core/scoped_nsautorelease_pool.mm)
|
list(APPEND SOURCES core/scoped_nsautorelease_pool.mm)
|
||||||
endif(APPLE)
|
endif(APPLE)
|
||||||
|
|
||||||
if(WITH_QT6)
|
if(BUILD_WITH_QT6)
|
||||||
qt6_wrap_cpp(MOC ${HEADERS})
|
qt6_wrap_cpp(MOC ${HEADERS})
|
||||||
else()
|
else()
|
||||||
qt5_wrap_cpp(MOC ${HEADERS})
|
qt5_wrap_cpp(MOC ${HEADERS})
|
||||||
|
|||||||
@@ -21,7 +21,7 @@
|
|||||||
#include <functional>
|
#include <functional>
|
||||||
#include <memory>
|
#include <memory>
|
||||||
|
|
||||||
// Helper for lazy initialisation of objects.
|
// Helper for lazy initialization of objects.
|
||||||
// Usage:
|
// Usage:
|
||||||
// Lazy<Foo> my_lazy_object([]() { return new Foo; });
|
// Lazy<Foo> my_lazy_object([]() { return new Foo; });
|
||||||
|
|
||||||
@@ -48,19 +48,18 @@ class Lazy {
|
|||||||
// Returns true if the object is not yet initialised.
|
// Returns true if the object is not yet initialised.
|
||||||
explicit operator bool() const { return ptr_; }
|
explicit operator bool() const { return ptr_; }
|
||||||
|
|
||||||
// Deletes the underlying object and will re-run the initialisation function
|
// Deletes the underlying object and will re-run the initialisation function if the object is requested again.
|
||||||
// if the object is requested again.
|
void reset() { ptr_.reset(); }
|
||||||
void reset() { ptr_.reset(nullptr); }
|
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void CheckInitialised() const {
|
void CheckInitialised() const {
|
||||||
if (!ptr_) {
|
if (!ptr_) {
|
||||||
ptr_.reset(init_());
|
ptr_.reset(init_(), [](T*obj) { obj->deleteLater(); });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const std::function<T*()> init_;
|
const std::function<T*()> init_;
|
||||||
mutable std::unique_ptr<T> ptr_;
|
mutable std::shared_ptr<T> ptr_;
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif // LAZY_H
|
#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)
|
cmake_minimum_required(VERSION 3.0)
|
||||||
|
|
||||||
set(MESSAGES tagreadermessages.proto)
|
set(MESSAGES tagreadermessages.proto)
|
||||||
set(SOURCES fmpsparser.cpp tagreader.cpp)
|
set(SOURCES tagreader.cpp)
|
||||||
|
|
||||||
protobuf_generate_cpp(PROTO_SOURCES PROTO_HEADERS ${MESSAGES})
|
protobuf_generate_cpp(PROTO_SOURCES PROTO_HEADERS ${MESSAGES})
|
||||||
|
|
||||||
@@ -39,6 +39,6 @@ target_link_libraries(libstrawberry-tagreader PRIVATE
|
|||||||
libstrawberry-common
|
libstrawberry-common
|
||||||
)
|
)
|
||||||
|
|
||||||
if(WITH_QT6)
|
if(BUILD_WITH_QT6)
|
||||||
target_link_libraries(libstrawberry-tagreader PRIVATE Qt6::Core5Compat)
|
target_link_libraries(libstrawberry-tagreader PRIVATE Qt6::Core5Compat)
|
||||||
endif()
|
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
|
|
||||||
@@ -90,8 +90,6 @@
|
|||||||
|
|
||||||
#include "core/logging.h"
|
#include "core/logging.h"
|
||||||
#include "core/messagehandler.h"
|
#include "core/messagehandler.h"
|
||||||
|
|
||||||
#include "fmpsparser.h"
|
|
||||||
#include "core/timeconstants.h"
|
#include "core/timeconstants.h"
|
||||||
|
|
||||||
class FileRefFactory {
|
class FileRefFactory {
|
||||||
@@ -300,15 +298,6 @@ void TagReader::ReadFile(const QString &filename, pb::tagreader::SongMetadata *s
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -402,7 +391,7 @@ void TagReader::ReadFile(const QString &filename, pb::tagreader::SongMetadata *s
|
|||||||
|
|
||||||
if (compilation.isEmpty()) {
|
if (compilation.isEmpty()) {
|
||||||
// well, it wasn't set, but if the artist is VA assume it's a compilation
|
// 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);
|
song->set_compilation(true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -523,33 +512,6 @@ 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 {
|
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("COMPOSER", StdStringToTaglibString(song.composer()), true);
|
||||||
@@ -570,7 +532,8 @@ void TagReader::SetVorbisComments(TagLib::Ogg::XiphComment *vorbis_comments, con
|
|||||||
|
|
||||||
bool TagReader::SaveFile(const QString &filename, const pb::tagreader::SongMetadata &song) const {
|
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;
|
qLog(Debug) << "Saving tags to" << filename;
|
||||||
std::unique_ptr<TagLib::FileRef> fileref(factory_->GetFileRef(filename));;
|
std::unique_ptr<TagLib::FileRef> fileref(factory_->GetFileRef(filename));;
|
||||||
if (!fileref || fileref->isNull()) return false;
|
if (!fileref || fileref->isNull()) return false;
|
||||||
|
|||||||
@@ -66,7 +66,6 @@ class TagReader {
|
|||||||
|
|
||||||
void ParseOggTag(const TagLib::Ogg::FieldListMap &map, const QTextCodec *codec, QString *disc, QString *compilation, pb::tagreader::SongMetadata *song) const;
|
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 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 SetVorbisComments(TagLib::Ogg::XiphComment *vorbis_comments, const 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 SaveAPETag(TagLib::APE::Tag *tag, const pb::tagreader::SongMetadata &song) const;
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ set(EXECUTABLE_OUTPUT_PATH ${CMAKE_BINARY_DIR})
|
|||||||
|
|
||||||
set(SOURCES main.cpp tagreaderworker.cpp)
|
set(SOURCES main.cpp tagreaderworker.cpp)
|
||||||
|
|
||||||
if(WITH_QT6)
|
if(BUILD_WITH_QT6)
|
||||||
qt6_wrap_cpp(MOC ${HEADERS})
|
qt6_wrap_cpp(MOC ${HEADERS})
|
||||||
qt6_add_resources(QRC data/data.qrc)
|
qt6_add_resources(QRC data/data.qrc)
|
||||||
else()
|
else()
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
name: strawberry
|
name: strawberry
|
||||||
version: '0.7.2+git'
|
version: '0.8.2+git'
|
||||||
summary: music player and collection organizer
|
summary: music player and collection organizer
|
||||||
description: |
|
description: |
|
||||||
Strawberry is a music player and collection organizer.
|
Strawberry is a music player and collection organizer.
|
||||||
|
|||||||
@@ -33,12 +33,10 @@ set(SOURCES
|
|||||||
core/thread.cpp
|
core/thread.cpp
|
||||||
core/urlhandler.cpp
|
core/urlhandler.cpp
|
||||||
core/utilities.cpp
|
core/utilities.cpp
|
||||||
core/scangiomodulepath.cpp
|
|
||||||
core/iconloader.cpp
|
core/iconloader.cpp
|
||||||
core/qtsystemtrayicon.cpp
|
core/qtsystemtrayicon.cpp
|
||||||
core/standarditemiconloader.cpp
|
core/standarditemiconloader.cpp
|
||||||
core/systemtrayicon.cpp
|
core/systemtrayicon.cpp
|
||||||
core/screensaver.cpp
|
|
||||||
core/scopedtransaction.cpp
|
core/scopedtransaction.cpp
|
||||||
core/translations.cpp
|
core/translations.cpp
|
||||||
|
|
||||||
@@ -96,6 +94,7 @@ set(SOURCES
|
|||||||
playlist/playlistview.cpp
|
playlist/playlistview.cpp
|
||||||
playlist/songloaderinserter.cpp
|
playlist/songloaderinserter.cpp
|
||||||
playlist/songplaylistitem.cpp
|
playlist/songplaylistitem.cpp
|
||||||
|
playlist/dynamicplaylistcontrols.cpp
|
||||||
|
|
||||||
queue/queue.cpp
|
queue/queue.cpp
|
||||||
queue/queueview.cpp
|
queue/queueview.cpp
|
||||||
@@ -111,6 +110,20 @@ set(SOURCES
|
|||||||
playlistparsers/xmlparser.cpp
|
playlistparsers/xmlparser.cpp
|
||||||
playlistparsers/xspfparser.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/albumcovermanager.cpp
|
||||||
covermanager/albumcovermanagerlist.cpp
|
covermanager/albumcovermanagerlist.cpp
|
||||||
covermanager/albumcoverloader.cpp
|
covermanager/albumcoverloader.cpp
|
||||||
@@ -169,6 +182,8 @@ set(SOURCES
|
|||||||
dialogs/trackselectiondialog.cpp
|
dialogs/trackselectiondialog.cpp
|
||||||
dialogs/addstreamdialog.cpp
|
dialogs/addstreamdialog.cpp
|
||||||
dialogs/userpassdialog.cpp
|
dialogs/userpassdialog.cpp
|
||||||
|
dialogs/deleteconfirmationdialog.cpp
|
||||||
|
dialogs/lastfmimportdialog.cpp
|
||||||
|
|
||||||
widgets/autoexpandingtreeview.cpp
|
widgets/autoexpandingtreeview.cpp
|
||||||
widgets/busyindicator.cpp
|
widgets/busyindicator.cpp
|
||||||
@@ -192,6 +207,7 @@ set(SOURCES
|
|||||||
widgets/tracksliderpopup.cpp
|
widgets/tracksliderpopup.cpp
|
||||||
widgets/tracksliderslider.cpp
|
widgets/tracksliderslider.cpp
|
||||||
widgets/loginstatewidget.cpp
|
widgets/loginstatewidget.cpp
|
||||||
|
widgets/ratingwidget.cpp
|
||||||
|
|
||||||
osd/osdbase.cpp
|
osd/osdbase.cpp
|
||||||
osd/osdpretty.cpp
|
osd/osdpretty.cpp
|
||||||
@@ -222,6 +238,7 @@ set(SOURCES
|
|||||||
scrobbler/lastfmscrobbler.cpp
|
scrobbler/lastfmscrobbler.cpp
|
||||||
scrobbler/librefmscrobbler.cpp
|
scrobbler/librefmscrobbler.cpp
|
||||||
scrobbler/listenbrainzscrobbler.cpp
|
scrobbler/listenbrainzscrobbler.cpp
|
||||||
|
scrobbler/lastfmimport.cpp
|
||||||
|
|
||||||
organize/organize.cpp
|
organize/organize.cpp
|
||||||
organize/organizeformat.cpp
|
organize/organizeformat.cpp
|
||||||
@@ -297,6 +314,7 @@ set(HEADERS
|
|||||||
playlist/playlistitemmimedata.h
|
playlist/playlistitemmimedata.h
|
||||||
playlist/songloaderinserter.h
|
playlist/songloaderinserter.h
|
||||||
playlist/songmimedata.h
|
playlist/songmimedata.h
|
||||||
|
playlist/dynamicplaylistcontrols.h
|
||||||
|
|
||||||
queue/queue.h
|
queue/queue.h
|
||||||
queue/queueview.h
|
queue/queueview.h
|
||||||
@@ -310,6 +328,18 @@ set(HEADERS
|
|||||||
playlistparsers/plsparser.h
|
playlistparsers/plsparser.h
|
||||||
playlistparsers/xspfparser.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/albumcovermanager.h
|
||||||
covermanager/albumcovermanagerlist.h
|
covermanager/albumcovermanagerlist.h
|
||||||
covermanager/albumcoverloader.h
|
covermanager/albumcoverloader.h
|
||||||
@@ -367,6 +397,8 @@ set(HEADERS
|
|||||||
dialogs/trackselectiondialog.h
|
dialogs/trackselectiondialog.h
|
||||||
dialogs/addstreamdialog.h
|
dialogs/addstreamdialog.h
|
||||||
dialogs/userpassdialog.h
|
dialogs/userpassdialog.h
|
||||||
|
dialogs/deleteconfirmationdialog.h
|
||||||
|
dialogs/lastfmimportdialog.h
|
||||||
|
|
||||||
widgets/autoexpandingtreeview.h
|
widgets/autoexpandingtreeview.h
|
||||||
widgets/busyindicator.h
|
widgets/busyindicator.h
|
||||||
@@ -390,6 +422,7 @@ set(HEADERS
|
|||||||
widgets/tracksliderslider.h
|
widgets/tracksliderslider.h
|
||||||
widgets/loginstatewidget.h
|
widgets/loginstatewidget.h
|
||||||
widgets/qsearchfield.h
|
widgets/qsearchfield.h
|
||||||
|
widgets/ratingwidget.h
|
||||||
|
|
||||||
osd/osdbase.h
|
osd/osdbase.h
|
||||||
osd/osdpretty.h
|
osd/osdpretty.h
|
||||||
@@ -418,6 +451,7 @@ set(HEADERS
|
|||||||
scrobbler/lastfmscrobbler.h
|
scrobbler/lastfmscrobbler.h
|
||||||
scrobbler/librefmscrobbler.h
|
scrobbler/librefmscrobbler.h
|
||||||
scrobbler/listenbrainzscrobbler.h
|
scrobbler/listenbrainzscrobbler.h
|
||||||
|
scrobbler/lastfmimport.h
|
||||||
|
|
||||||
organize/organize.h
|
organize/organize.h
|
||||||
organize/organizedialog.h
|
organize/organizedialog.h
|
||||||
@@ -438,9 +472,17 @@ set(UI
|
|||||||
playlist/playlistlistcontainer.ui
|
playlist/playlistlistcontainer.ui
|
||||||
playlist/playlistsaveoptionsdialog.ui
|
playlist/playlistsaveoptionsdialog.ui
|
||||||
playlist/playlistsequence.ui
|
playlist/playlistsequence.ui
|
||||||
|
playlist/dynamicplaylistcontrols.ui
|
||||||
|
|
||||||
queue/queueview.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/albumcoverexport.ui
|
||||||
covermanager/albumcovermanager.ui
|
covermanager/albumcovermanager.ui
|
||||||
covermanager/albumcoversearcher.ui
|
covermanager/albumcoversearcher.ui
|
||||||
@@ -470,6 +512,7 @@ set(UI
|
|||||||
dialogs/trackselectiondialog.ui
|
dialogs/trackselectiondialog.ui
|
||||||
dialogs/addstreamdialog.ui
|
dialogs/addstreamdialog.ui
|
||||||
dialogs/userpassdialog.ui
|
dialogs/userpassdialog.ui
|
||||||
|
dialogs/lastfmimportdialog.ui
|
||||||
|
|
||||||
widgets/trackslider.ui
|
widgets/trackslider.ui
|
||||||
widgets/fileview.ui
|
widgets/fileview.ui
|
||||||
@@ -552,7 +595,7 @@ if(UNIX AND HAVE_DBUS)
|
|||||||
file(MAKE_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/dbus)
|
file(MAKE_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/dbus)
|
||||||
|
|
||||||
optional_source(HAVE_DBUS
|
optional_source(HAVE_DBUS
|
||||||
SOURCES core/mpris.cpp core/mpris2.cpp core/dbusscreensaver.cpp
|
SOURCES core/mpris.cpp core/mpris2.cpp
|
||||||
HEADERS core/mpris.h core/mpris2.h
|
HEADERS core/mpris.h core/mpris2.h
|
||||||
)
|
)
|
||||||
optional_source(HAVE_UDISKS2
|
optional_source(HAVE_UDISKS2
|
||||||
@@ -560,7 +603,7 @@ if(UNIX AND HAVE_DBUS)
|
|||||||
HEADERS device/udisks2lister.h
|
HEADERS device/udisks2lister.h
|
||||||
)
|
)
|
||||||
|
|
||||||
if (WITH_QT6)
|
if (BUILD_WITH_QT6)
|
||||||
|
|
||||||
# MPRIS 2.0 DBUS interfaces
|
# MPRIS 2.0 DBUS interfaces
|
||||||
qt6_add_dbus_adaptor(SOURCES
|
qt6_add_dbus_adaptor(SOURCES
|
||||||
@@ -657,7 +700,7 @@ if(UNIX AND HAVE_DBUS)
|
|||||||
PROPERTIES NO_NAMESPACE dbus/udisks2drive INCLUDE dbus/metatypes.h)
|
PROPERTIES NO_NAMESPACE dbus/udisks2drive INCLUDE dbus/metatypes.h)
|
||||||
set_source_files_properties(dbus/org.freedesktop.UDisks2.Job.xml
|
set_source_files_properties(dbus/org.freedesktop.UDisks2.Job.xml
|
||||||
PROPERTIES NO_NAMESPACE dbus/udisks2job INCLUDE dbus/metatypes.h)
|
PROPERTIES NO_NAMESPACE dbus/udisks2job INCLUDE dbus/metatypes.h)
|
||||||
if(WITH_QT6)
|
if(BUILD_WITH_QT6)
|
||||||
qt6_add_dbus_interface(SOURCES
|
qt6_add_dbus_interface(SOURCES
|
||||||
dbus/org.freedesktop.DBus.ObjectManager.xml
|
dbus/org.freedesktop.DBus.ObjectManager.xml
|
||||||
dbus/objectmanager)
|
dbus/objectmanager)
|
||||||
@@ -817,7 +860,6 @@ optional_source(APPLE
|
|||||||
core/mac_utilities.mm
|
core/mac_utilities.mm
|
||||||
core/mac_startup.mm
|
core/mac_startup.mm
|
||||||
core/macsystemtrayicon.mm
|
core/macsystemtrayicon.mm
|
||||||
core/macscreensaver.cpp
|
|
||||||
core/macfslistener.mm
|
core/macfslistener.mm
|
||||||
osd/osdmac.mm
|
osd/osdmac.mm
|
||||||
widgets/qsearchfield_mac.mm
|
widgets/qsearchfield_mac.mm
|
||||||
@@ -856,13 +898,17 @@ optional_source(HAVE_SUBSONIC
|
|||||||
subsonic/subsonicurlhandler.cpp
|
subsonic/subsonicurlhandler.cpp
|
||||||
subsonic/subsonicbaserequest.cpp
|
subsonic/subsonicbaserequest.cpp
|
||||||
subsonic/subsonicrequest.cpp
|
subsonic/subsonicrequest.cpp
|
||||||
|
subsonic/subsonicscrobblerequest.cpp
|
||||||
settings/subsonicsettingspage.cpp
|
settings/subsonicsettingspage.cpp
|
||||||
|
scrobbler/subsonicscrobbler.cpp
|
||||||
HEADERS
|
HEADERS
|
||||||
subsonic/subsonicservice.h
|
subsonic/subsonicservice.h
|
||||||
subsonic/subsonicurlhandler.h
|
subsonic/subsonicurlhandler.h
|
||||||
subsonic/subsonicbaserequest.h
|
subsonic/subsonicbaserequest.h
|
||||||
subsonic/subsonicrequest.h
|
subsonic/subsonicrequest.h
|
||||||
|
subsonic/subsonicscrobblerequest.h
|
||||||
settings/subsonicsettingspage.h
|
settings/subsonicsettingspage.h
|
||||||
|
scrobbler/subsonicscrobbler.h
|
||||||
UI
|
UI
|
||||||
settings/subsonicsettingspage.ui
|
settings/subsonicsettingspage.ui
|
||||||
)
|
)
|
||||||
@@ -890,6 +936,27 @@ optional_source(HAVE_TIDAL
|
|||||||
settings/tidalsettingspage.ui
|
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
|
# Moodbar
|
||||||
optional_source(HAVE_MOODBAR
|
optional_source(HAVE_MOODBAR
|
||||||
SOURCES
|
SOURCES
|
||||||
@@ -915,7 +982,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}/config.h.in ${CMAKE_CURRENT_BINARY_DIR}/config.h)
|
||||||
configure_file(${CMAKE_CURRENT_SOURCE_DIR}/version.h.in ${CMAKE_CURRENT_BINARY_DIR}/version.h)
|
configure_file(${CMAKE_CURRENT_SOURCE_DIR}/version.h.in ${CMAKE_CURRENT_BINARY_DIR}/version.h)
|
||||||
|
|
||||||
if(WITH_QT6)
|
if(BUILD_WITH_QT6)
|
||||||
qt6_wrap_cpp(MOC ${HEADERS})
|
qt6_wrap_cpp(MOC ${HEADERS})
|
||||||
qt6_wrap_ui(UIC ${UI})
|
qt6_wrap_ui(UIC ${UI})
|
||||||
qt6_add_resources(QRC ${RESOURCES})
|
qt6_add_resources(QRC ${RESOURCES})
|
||||||
@@ -967,6 +1034,7 @@ link_directories(
|
|||||||
${SINGLEAPPLICATION_LIBRARY_DIRS}
|
${SINGLEAPPLICATION_LIBRARY_DIRS}
|
||||||
${SINGLECOREAPPLICATION_LIBRARY_DIRS}
|
${SINGLECOREAPPLICATION_LIBRARY_DIRS}
|
||||||
${QTSPARKLE_LIBRARY_DIRS}
|
${QTSPARKLE_LIBRARY_DIRS}
|
||||||
|
${Iconv_LIBRARY_DIRS}
|
||||||
)
|
)
|
||||||
|
|
||||||
if(HAVE_ALSA)
|
if(HAVE_ALSA)
|
||||||
@@ -1013,7 +1081,8 @@ if(HAVE_AUDIOCD)
|
|||||||
endif(HAVE_AUDIOCD)
|
endif(HAVE_AUDIOCD)
|
||||||
|
|
||||||
if(HAVE_LIBGPOD)
|
if(HAVE_LIBGPOD)
|
||||||
link_directories(${LIBGPOD_LIBRARIES})
|
link_directories(${LIBGPOD_LIBRARY_DIRS})
|
||||||
|
link_directories(${GDK_PIXBUF_LIBRARY_DIRS})
|
||||||
endif(HAVE_LIBGPOD)
|
endif(HAVE_LIBGPOD)
|
||||||
|
|
||||||
if(HAVE_LIBMTP)
|
if(HAVE_LIBMTP)
|
||||||
@@ -1063,6 +1132,7 @@ target_link_libraries(strawberry_lib PUBLIC
|
|||||||
${SINGLEAPPLICATION_LIBRARIES}
|
${SINGLEAPPLICATION_LIBRARIES}
|
||||||
${SINGLECOREAPPLICATION_LIBRARIES}
|
${SINGLECOREAPPLICATION_LIBRARIES}
|
||||||
${QTSPARKLE_LIBRARIES}
|
${QTSPARKLE_LIBRARIES}
|
||||||
|
${Iconv_LIBRARY}
|
||||||
libstrawberry-common
|
libstrawberry-common
|
||||||
libstrawberry-tagreader
|
libstrawberry-tagreader
|
||||||
)
|
)
|
||||||
@@ -1131,8 +1201,8 @@ if(HAVE_AUDIOCD)
|
|||||||
endif(HAVE_AUDIOCD)
|
endif(HAVE_AUDIOCD)
|
||||||
|
|
||||||
if(HAVE_LIBGPOD)
|
if(HAVE_LIBGPOD)
|
||||||
target_include_directories(strawberry_lib SYSTEM PRIVATE ${LIBGPOD_INCLUDE_DIRS})
|
target_include_directories(strawberry_lib SYSTEM PRIVATE ${LIBGPOD_INCLUDE_DIRS} ${GDK_PIXBUF_INCLUDE_DIRS})
|
||||||
target_link_libraries(strawberry_lib PRIVATE ${LIBGPOD_LIBRARIES})
|
target_link_libraries(strawberry_lib PRIVATE ${LIBGPOD_LIBRARIES} ${GDK_PIXBUF_LIBRARIES})
|
||||||
endif(HAVE_LIBGPOD)
|
endif(HAVE_LIBGPOD)
|
||||||
|
|
||||||
if(HAVE_LIBMTP)
|
if(HAVE_LIBMTP)
|
||||||
|
|||||||
@@ -40,6 +40,7 @@
|
|||||||
#include "collectionbackend.h"
|
#include "collectionbackend.h"
|
||||||
#include "collectionmodel.h"
|
#include "collectionmodel.h"
|
||||||
#include "playlist/playlistmanager.h"
|
#include "playlist/playlistmanager.h"
|
||||||
|
#include "scrobbler/lastfmimport.h"
|
||||||
|
|
||||||
const char *SCollection::kSongsTable = "songs";
|
const char *SCollection::kSongsTable = "songs";
|
||||||
const char *SCollection::kDirsTable = "directories";
|
const char *SCollection::kDirsTable = "directories";
|
||||||
@@ -106,10 +107,12 @@ void SCollection::Init() {
|
|||||||
connect(watcher_, SIGNAL(SubdirsDiscovered(SubdirectoryList)), backend_, SLOT(AddOrUpdateSubdirs(SubdirectoryList)));
|
connect(watcher_, SIGNAL(SubdirsDiscovered(SubdirectoryList)), backend_, SLOT(AddOrUpdateSubdirs(SubdirectoryList)));
|
||||||
connect(watcher_, SIGNAL(SubdirsMTimeUpdated(SubdirectoryList)), backend_, SLOT(AddOrUpdateSubdirs(SubdirectoryList)));
|
connect(watcher_, SIGNAL(SubdirsMTimeUpdated(SubdirectoryList)), backend_, SLOT(AddOrUpdateSubdirs(SubdirectoryList)));
|
||||||
connect(watcher_, SIGNAL(CompilationsNeedUpdating()), backend_, SLOT(UpdateCompilations()));
|
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_->playlist_manager(), SIGNAL(CurrentSongChanged(Song)), SLOT(CurrentSongChanged(Song)));
|
||||||
connect(app_->player(), SIGNAL(Stopped()), SLOT(Stopped()));
|
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
|
// This will start the watcher checking for updates
|
||||||
backend_->LoadDirectoriesAsync();
|
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 IncrementalScan();
|
||||||
|
|
||||||
void CurrentSongChanged(const Song &song);
|
void CurrentSongChanged(const Song &song);
|
||||||
void SongsStatisticsChanged(const SongList& songs);
|
|
||||||
void Stopped();
|
void Stopped();
|
||||||
|
|
||||||
signals:
|
signals:
|
||||||
|
|||||||
@@ -42,8 +42,10 @@
|
|||||||
#include <QSqlDatabase>
|
#include <QSqlDatabase>
|
||||||
#include <QSqlQuery>
|
#include <QSqlQuery>
|
||||||
|
|
||||||
|
#include "core/logging.h"
|
||||||
#include "core/database.h"
|
#include "core/database.h"
|
||||||
#include "core/scopedtransaction.h"
|
#include "core/scopedtransaction.h"
|
||||||
|
#include "smartplaylists/smartplaylistsearch.h"
|
||||||
|
|
||||||
#include "directory.h"
|
#include "directory.h"
|
||||||
#include "collectionbackend.h"
|
#include "collectionbackend.h"
|
||||||
@@ -108,15 +110,15 @@ void CollectionBackend::UpdateTotalAlbumCountAsync() {
|
|||||||
metaObject()->invokeMethod(this, "UpdateTotalAlbumCount", Qt::QueuedConnection);
|
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));
|
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));
|
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));
|
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());
|
QMutexLocker l(db_->Mutex());
|
||||||
QSqlDatabase db(db_->Connect());
|
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());
|
QMutexLocker l(db_->Mutex());
|
||||||
QSqlDatabase db = db_->Connect();
|
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);
|
QSqlQuery q(db);
|
||||||
q.prepare(QString("SELECT path, mtime FROM %1 WHERE directory_id = :dir").arg(subdirs_table_));
|
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());
|
QMutexLocker l(db_->Mutex());
|
||||||
QSqlDatabase db(db_->Connect());
|
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());
|
QMutexLocker l(db_->Mutex());
|
||||||
QSqlDatabase db(db_->Connect());
|
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());
|
QMutexLocker l(db_->Mutex());
|
||||||
QSqlDatabase db(db_->Connect());
|
QSqlDatabase db(db_->Connect());
|
||||||
return GetSongById(id, db);
|
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);
|
SongList list = GetSongsById(QStringList() << QString::number(id), db);
|
||||||
if (list.isEmpty()) return Song();
|
if (list.isEmpty()) return Song();
|
||||||
return list.first();
|
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) {
|
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);
|
QSqlQuery q(db);
|
||||||
q.prepare(QString("SELECT ROWID, " + Song::kColumnSpec + " FROM %1 WHERE SONG_ID IN (%2)").arg(songs_table_, in));
|
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) {
|
CollectionBackend::AlbumList CollectionBackend::GetAlbums(const QString &artist, const bool compilation_required, const QueryOptions &opt) {
|
||||||
|
|
||||||
AlbumList ret;
|
|
||||||
|
|
||||||
CollectionQuery query(opt);
|
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");
|
query.SetOrderBy("album");
|
||||||
|
|
||||||
if (compilation_required) {
|
if (compilation_required) {
|
||||||
@@ -1015,20 +1019,20 @@ CollectionBackend::AlbumList CollectionBackend::GetAlbums(const QString &artist,
|
|||||||
|
|
||||||
{
|
{
|
||||||
QMutexLocker l(db_->Mutex());
|
QMutexLocker l(db_->Mutex());
|
||||||
if (!ExecQuery(&query)) return ret;
|
if (!ExecQuery(&query)) return AlbumList();
|
||||||
}
|
}
|
||||||
|
|
||||||
QString last_album;
|
QMap<QString, Album> albums;
|
||||||
QString last_artist;
|
|
||||||
QString last_album_artist;
|
|
||||||
while (query.Next()) {
|
while (query.Next()) {
|
||||||
bool is_compilation = query.Value(3).toBool() | query.Value(4).toBool();
|
bool is_compilation = query.Value(4).toBool();
|
||||||
|
|
||||||
Album info;
|
Album info;
|
||||||
info.artist = is_compilation ? QString() : query.Value(1).toString();
|
info.first_url = QUrl::fromEncoded(query.Value(0).toByteArray());
|
||||||
info.album_artist = is_compilation ? QString() : query.Value(2).toString();
|
if (!is_compilation) {
|
||||||
info.album_name = query.Value(0).toString();
|
info.artist = query.Value(1).toString();
|
||||||
info.first_url = QUrl::fromEncoded(query.Value(7).toByteArray());
|
info.album_artist = query.Value(2).toString();
|
||||||
|
}
|
||||||
|
info.album_name = query.Value(3).toString();
|
||||||
|
|
||||||
QString art_automatic = query.Value(5).toString();
|
QString art_automatic = query.Value(5).toString();
|
||||||
if (art_automatic.contains(QRegularExpression("..+:.*"))) {
|
if (art_automatic.contains(QRegularExpression("..+:.*"))) {
|
||||||
@@ -1046,17 +1050,23 @@ CollectionBackend::AlbumList CollectionBackend::GetAlbums(const QString &artist,
|
|||||||
info.art_manual = QUrl::fromLocalFile(art_manual);
|
info.art_manual = QUrl::fromLocalFile(art_manual);
|
||||||
}
|
}
|
||||||
|
|
||||||
if ((info.artist == last_artist || info.album_artist == last_album_artist) && info.album_name == last_album)
|
QString effective_albumartist = info.album_artist.isEmpty() ? info.artist : info.album_artist;
|
||||||
continue;
|
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());
|
QMutexLocker l(db_->Mutex());
|
||||||
QSqlDatabase db(db_->Connect());
|
QSqlDatabase db(db_->Connect());
|
||||||
@@ -1174,7 +1184,7 @@ void CollectionBackend::ForceCompilation(const QString &album, const QList<QStri
|
|||||||
CollectionQuery query;
|
CollectionQuery query;
|
||||||
query.SetColumnSpec("ROWID, " + Song::kColumnSpec);
|
query.SetColumnSpec("ROWID, " + Song::kColumnSpec);
|
||||||
query.AddWhere("album", album);
|
query.AddWhere("album", album);
|
||||||
if (!artist.isNull() && !artist.isEmpty()) query.AddWhere("artist", artist);
|
if (!artist.isEmpty()) query.AddWhere("artist", artist);
|
||||||
|
|
||||||
if (!ExecQuery(&query)) return;
|
if (!ExecQuery(&query)) return;
|
||||||
|
|
||||||
@@ -1219,7 +1229,7 @@ bool CollectionBackend::ExecQuery(CollectionQuery *q) {
|
|||||||
return !db_->CheckErrors(q->Exec(db_->Connect(), songs_table_, fts_table_));
|
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;
|
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);
|
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;
|
if (id == -1) return;
|
||||||
|
|
||||||
@@ -1297,3 +1307,152 @@ void CollectionBackend::DeleteAll() {
|
|||||||
emit DatabaseReset();
|
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 QThread;
|
||||||
class Database;
|
class Database;
|
||||||
|
class SmartPlaylistSearch;
|
||||||
|
|
||||||
class CollectionBackendInterface : public QObject {
|
class CollectionBackendInterface : public QObject {
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
@@ -80,10 +81,10 @@ class CollectionBackendInterface : public QObject {
|
|||||||
virtual void UpdateTotalArtistCountAsync() = 0;
|
virtual void UpdateTotalArtistCountAsync() = 0;
|
||||||
virtual void UpdateTotalAlbumCountAsync() = 0;
|
virtual void UpdateTotalAlbumCountAsync() = 0;
|
||||||
|
|
||||||
virtual SongList FindSongsInDirectory(int id) = 0;
|
virtual SongList FindSongsInDirectory(const int id) = 0;
|
||||||
virtual SubdirectoryList SubdirsInDirectory(int id) = 0;
|
virtual SubdirectoryList SubdirsInDirectory(const int id) = 0;
|
||||||
virtual DirectoryList GetAllDirectories() = 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 GetAllArtists(const QueryOptions &opt = QueryOptions()) = 0;
|
||||||
virtual QStringList GetAllArtistsWithAlbums(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 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 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.
|
// 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;
|
virtual SongList GetSongsByUrl(const QUrl &url) = 0;
|
||||||
@@ -182,10 +183,16 @@ class CollectionBackend : public CollectionBackendInterface {
|
|||||||
Song GetSongBySongId(const QString &song_id);
|
Song GetSongBySongId(const QString &song_id);
|
||||||
SongList GetSongsBySongId(const QStringList &song_ids);
|
SongList GetSongsBySongId(const QStringList &song_ids);
|
||||||
|
|
||||||
|
SongList GetAllSongs();
|
||||||
|
SongList FindSongs(const SmartPlaylistSearch &search);
|
||||||
|
|
||||||
Song::Source Source() const;
|
Song::Source Source() const;
|
||||||
|
|
||||||
void AddOrUpdateSongsAsync(const SongList &songs);
|
void AddOrUpdateSongsAsync(const SongList &songs);
|
||||||
|
|
||||||
|
void UpdateSongRatingAsync(const int id, const float rating);
|
||||||
|
void UpdateSongsRatingAsync(const QList<int> &ids, const float rating);
|
||||||
|
|
||||||
public slots:
|
public slots:
|
||||||
void Exit();
|
void Exit();
|
||||||
void LoadDirectories();
|
void LoadDirectories();
|
||||||
@@ -195,7 +202,7 @@ class CollectionBackend : public CollectionBackendInterface {
|
|||||||
void AddOrUpdateSongs(const SongList &songs);
|
void AddOrUpdateSongs(const SongList &songs);
|
||||||
void UpdateMTimesOnly(const SongList &songs);
|
void UpdateMTimesOnly(const SongList &songs);
|
||||||
void DeleteSongs(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 AddOrUpdateSubdirs(const SubdirectoryList &subdirs);
|
||||||
void UpdateCompilations();
|
void UpdateCompilations();
|
||||||
void UpdateManualAlbumArt(const QString &artist, const QString &albumartist, const QString &album, const QUrl &cover_url);
|
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 ResetStatistics(const int id);
|
||||||
void SongPathChanged(const Song &song, const QFileInfo &new_file);
|
void SongPathChanged(const Song &song, const QFileInfo &new_file);
|
||||||
|
|
||||||
signals:
|
SongList GetSongsBy(const QString &artist, const QString &album, const QString &title);
|
||||||
void DirectoryDiscovered(const Directory &dir, const SubdirectoryList &subdirs);
|
void UpdateLastPlayed(const QString &artist, const QString &album, const QString &title, const int lastplayed);
|
||||||
void DirectoryDeleted(const Directory &dir);
|
void UpdatePlayCount(const QString &artist, const QString &title, const int playcount);
|
||||||
|
|
||||||
void SongsDiscovered(const SongList &songs);
|
void UpdateSongRating(const int id, const float rating);
|
||||||
void SongsDeleted(const SongList &songs);
|
void UpdateSongsRating(const QList<int> &id_list, const float rating);
|
||||||
void SongsStatisticsChanged(const SongList& songs);
|
|
||||||
|
signals:
|
||||||
|
void DirectoryDiscovered(Directory, SubdirectoryList);
|
||||||
|
void DirectoryDeleted(Directory);
|
||||||
|
|
||||||
|
void SongsDiscovered(SongList);
|
||||||
|
void SongsDeleted(SongList);
|
||||||
|
void SongsStatisticsChanged(SongList);
|
||||||
|
|
||||||
void DatabaseReset();
|
void DatabaseReset();
|
||||||
|
|
||||||
void TotalSongCountUpdated(const int total);
|
void TotalSongCountUpdated(int);
|
||||||
void TotalArtistCountUpdated(const int total);
|
void TotalArtistCountUpdated(int);
|
||||||
void TotalAlbumCountUpdated(const int total);
|
void TotalAlbumCountUpdated(int);
|
||||||
|
void SongsRatingChanged(SongList);
|
||||||
|
|
||||||
void ExitFinished();
|
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); }
|
QString CollectionFilterWidget::group_by(const int number) { return group_by() + QString::number(number); }
|
||||||
|
|
||||||
void CollectionFilterWidget::UpdateGroupByActions() {
|
void CollectionFilterWidget::UpdateGroupByActions() {
|
||||||
@@ -213,14 +224,26 @@ QActionGroup *CollectionFilterWidget::CreateGroupByActions(QObject *parent) {
|
|||||||
// read saved groupings
|
// read saved groupings
|
||||||
QSettings s;
|
QSettings s;
|
||||||
s.beginGroup(CollectionModel::kSavedGroupingsSettingsGroup);
|
s.beginGroup(CollectionModel::kSavedGroupingsSettingsGroup);
|
||||||
QStringList saved = s.childKeys();
|
int version = s.value("version").toInt();
|
||||||
for (int i = 0; i < saved.size(); ++i) {
|
if (version == 1) {
|
||||||
QByteArray bytes = s.value(saved.at(i)).toByteArray();
|
QStringList saved = s.childKeys();
|
||||||
QDataStream ds(&bytes, QIODevice::ReadOnly);
|
for (int i = 0; i < saved.size(); ++i) {
|
||||||
CollectionModel::Grouping g;
|
if (saved.at(i) == "version") continue;
|
||||||
ds >> g;
|
QByteArray bytes = s.value(saved.at(i)).toByteArray();
|
||||||
ret->addAction(CreateGroupByAction(saved.at(i), parent, g));
|
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);
|
QAction *sep2 = new QAction(parent);
|
||||||
sep2->setSeparator(true);
|
sep2->setSeparator(true);
|
||||||
@@ -301,10 +324,18 @@ void CollectionFilterWidget::SetCollectionModel(CollectionModel *model) {
|
|||||||
if (!settings_group_.isEmpty()) {
|
if (!settings_group_.isEmpty()) {
|
||||||
QSettings s;
|
QSettings s;
|
||||||
s.beginGroup(settings_group_);
|
s.beginGroup(settings_group_);
|
||||||
model_->SetGroupBy(CollectionModel::Grouping(
|
int version = 0;
|
||||||
CollectionModel::GroupBy(s.value(group_by(1), int(CollectionModel::GroupBy_AlbumArtist)).toInt()),
|
if (s.contains(group_by_version())) version = s.value(group_by_version(), 0).toInt();
|
||||||
CollectionModel::GroupBy(s.value(group_by(2), int(CollectionModel::GroupBy_AlbumDisc)).toInt()),
|
if (version == 1) {
|
||||||
CollectionModel::GroupBy(s.value(group_by(3), int(CollectionModel::GroupBy_None)).toInt())));
|
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
|
// Save the settings
|
||||||
QSettings s;
|
QSettings s;
|
||||||
s.beginGroup(settings_group_);
|
s.beginGroup(settings_group_);
|
||||||
|
s.setValue(group_by_version(), 1);
|
||||||
s.setValue(group_by(1), int(g[0]));
|
s.setValue(group_by(1), int(g[0]));
|
||||||
s.setValue(group_by(2), int(g[1]));
|
s.setValue(group_by(2), int(g[1]));
|
||||||
s.setValue(group_by(3), int(g[2]));
|
s.setValue(group_by(3), int(g[2]));
|
||||||
|
s.endGroup();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Now make sure the correct action is checked
|
// Now make sure the correct action is checked
|
||||||
|
|||||||
@@ -77,6 +77,7 @@ class CollectionFilterWidget : public QWidget {
|
|||||||
void SetCollectionModel(CollectionModel *model);
|
void SetCollectionModel(CollectionModel *model);
|
||||||
|
|
||||||
QString group_by();
|
QString group_by();
|
||||||
|
QString group_by_version();
|
||||||
QString group_by(const int number);
|
QString group_by(const int number);
|
||||||
|
|
||||||
void ReloadSettings();
|
void ReloadSettings();
|
||||||
|
|||||||
@@ -85,25 +85,27 @@ class CollectionModel : public SimpleTreeModel<CollectionItem> {
|
|||||||
// These values get saved in QSettings - don't change them
|
// These values get saved in QSettings - don't change them
|
||||||
enum GroupBy {
|
enum GroupBy {
|
||||||
GroupBy_None = 0,
|
GroupBy_None = 0,
|
||||||
GroupBy_Artist = 1,
|
GroupBy_AlbumArtist = 1,
|
||||||
GroupBy_Album = 2,
|
GroupBy_Artist = 2,
|
||||||
GroupBy_YearAlbum = 3,
|
GroupBy_Album = 3,
|
||||||
GroupBy_Year = 4,
|
GroupBy_AlbumDisc = 4,
|
||||||
GroupBy_Composer = 5,
|
GroupBy_YearAlbum = 5,
|
||||||
GroupBy_Genre = 6,
|
GroupBy_YearAlbumDisc = 6,
|
||||||
GroupBy_AlbumArtist = 7,
|
GroupBy_OriginalYearAlbum = 7,
|
||||||
GroupBy_FileType = 8,
|
GroupBy_OriginalYearAlbumDisc = 8,
|
||||||
GroupBy_Performer = 9,
|
GroupBy_Disc = 9,
|
||||||
GroupBy_Grouping = 10,
|
GroupBy_Year = 10,
|
||||||
GroupBy_Bitrate = 11,
|
GroupBy_OriginalYear = 11,
|
||||||
GroupBy_Disc = 12,
|
GroupBy_Genre = 12,
|
||||||
GroupBy_OriginalYearAlbum = 13,
|
GroupBy_Composer = 13,
|
||||||
GroupBy_OriginalYear = 14,
|
GroupBy_Performer = 14,
|
||||||
GroupBy_Samplerate = 15,
|
GroupBy_Grouping = 15,
|
||||||
GroupBy_Bitdepth = 16,
|
GroupBy_FileType = 16,
|
||||||
GroupBy_Format = 17,
|
GroupBy_Format = 17,
|
||||||
GroupBy_AlbumDisc = 18,
|
GroupBy_Samplerate = 18,
|
||||||
GroupBy_YearAlbumDisc = 19
|
GroupBy_Bitdepth = 19,
|
||||||
|
GroupBy_Bitrate = 20,
|
||||||
|
GroupByCount = 21,
|
||||||
};
|
};
|
||||||
|
|
||||||
struct Grouping {
|
struct Grouping {
|
||||||
@@ -170,6 +172,7 @@ class CollectionModel : public SimpleTreeModel<CollectionItem> {
|
|||||||
static QString PrettyYearAlbum(const int year, const QString &album);
|
static QString PrettyYearAlbum(const int year, const QString &album);
|
||||||
static QString PrettyAlbumDisc(const QString &album, const int disc);
|
static QString PrettyAlbumDisc(const QString &album, const int disc);
|
||||||
static QString PrettyYearAlbumDisc(const int year, 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 SortText(QString text);
|
||||||
static QString SortTextForNumber(const int number);
|
static QString SortTextForNumber(const int number);
|
||||||
static QString SortTextForArtist(QString artist);
|
static QString SortTextForArtist(QString artist);
|
||||||
@@ -179,7 +182,18 @@ class CollectionModel : public SimpleTreeModel<CollectionItem> {
|
|||||||
|
|
||||||
quint64 icon_cache_disk_size() { return sIconCache->cacheSize(); }
|
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:
|
signals:
|
||||||
void TotalSongCountUpdated(const int count);
|
void TotalSongCountUpdated(const int count);
|
||||||
@@ -248,6 +262,7 @@ class CollectionModel : public SimpleTreeModel<CollectionItem> {
|
|||||||
QString DividerDisplayText(const GroupBy type, const QString &key) const;
|
QString DividerDisplayText(const GroupBy type, const QString &key) const;
|
||||||
|
|
||||||
// Helpers
|
// Helpers
|
||||||
|
static bool IsCompilationArtistNode(const CollectionItem *node) { return node == node->parent->compilation_artist_node_; }
|
||||||
QString AlbumIconPixmapCacheKey(const QModelIndex &idx) const;
|
QString AlbumIconPixmapCacheKey(const QModelIndex &idx) const;
|
||||||
QVariant AlbumIcon(const QModelIndex &idx);
|
QVariant AlbumIcon(const QModelIndex &idx);
|
||||||
QVariant data(const CollectionItem *item, const int role) const;
|
QVariant data(const CollectionItem *item, const int role) const;
|
||||||
@@ -288,6 +303,7 @@ class CollectionModel : public SimpleTreeModel<CollectionItem> {
|
|||||||
bool use_pretty_covers_;
|
bool use_pretty_covers_;
|
||||||
bool show_dividers_;
|
bool show_dividers_;
|
||||||
bool use_disk_cache_;
|
bool use_disk_cache_;
|
||||||
|
bool use_lazy_loading_;
|
||||||
|
|
||||||
AlbumCoverLoaderOptions cover_loader_options_;
|
AlbumCoverLoaderOptions cover_loader_options_;
|
||||||
|
|
||||||
|
|||||||
@@ -70,6 +70,6 @@ Song CollectionPlaylistItem::Metadata() const {
|
|||||||
void CollectionPlaylistItem::SetArtManual(const QUrl &cover_url) {
|
void CollectionPlaylistItem::SetArtManual(const QUrl &cover_url) {
|
||||||
|
|
||||||
song_.set_art_manual(cover_url);
|
song_.set_art_manual(cover_url);
|
||||||
temp_metadata_.set_art_manual(cover_url);
|
if (HasTemporaryMetadata()) temp_metadata_.set_art_manual(cover_url);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -48,6 +48,8 @@
|
|||||||
#include "core/iconloader.h"
|
#include "core/iconloader.h"
|
||||||
#include "core/mimedata.h"
|
#include "core/mimedata.h"
|
||||||
#include "core/utilities.h"
|
#include "core/utilities.h"
|
||||||
|
#include "core/musicstorage.h"
|
||||||
|
#include "core/deletefiles.h"
|
||||||
#include "collection.h"
|
#include "collection.h"
|
||||||
#include "collectionbackend.h"
|
#include "collectionbackend.h"
|
||||||
#include "collectiondirectorymodel.h"
|
#include "collectiondirectorymodel.h"
|
||||||
@@ -61,7 +63,9 @@
|
|||||||
# include "device/devicestatefiltermodel.h"
|
# include "device/devicestatefiltermodel.h"
|
||||||
#endif
|
#endif
|
||||||
#include "dialogs/edittagdialog.h"
|
#include "dialogs/edittagdialog.h"
|
||||||
|
#include "dialogs/deleteconfirmationdialog.h"
|
||||||
#include "organize/organizedialog.h"
|
#include "organize/organizedialog.h"
|
||||||
|
#include "organize/organizeerrordialog.h"
|
||||||
#include "settings/collectionsettingspage.h"
|
#include "settings/collectionsettingspage.h"
|
||||||
|
|
||||||
CollectionView::CollectionView(QWidget *parent)
|
CollectionView::CollectionView(QWidget *parent)
|
||||||
@@ -73,7 +77,23 @@ CollectionView::CollectionView(QWidget *parent)
|
|||||||
total_album_count_(-1),
|
total_album_count_(-1),
|
||||||
nomusic_(":/pictures/nomusic.png"),
|
nomusic_(":/pictures/nomusic.png"),
|
||||||
context_menu_(nullptr),
|
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));
|
setItemDelegate(new CollectionItemDelegate(this));
|
||||||
@@ -211,6 +231,8 @@ void CollectionView::ReloadSettings() {
|
|||||||
app_->collection_model()->set_show_dividers(settings.value("show_dividers", true).toBool());
|
app_->collection_model()->set_show_dividers(settings.value("show_dividers", true).toBool());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
delete_files_ = settings.value("delete_files", false).toBool();
|
||||||
|
|
||||||
settings.endGroup();
|
settings.endGroup();
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -225,7 +247,7 @@ void CollectionView::SetApplication(Application *app) {
|
|||||||
|
|
||||||
void CollectionView::SetFilter(CollectionFilterWidget *filter) { filter_ = filter; }
|
void CollectionView::SetFilter(CollectionFilterWidget *filter) { filter_ = filter; }
|
||||||
|
|
||||||
void CollectionView::TotalSongCountUpdated(int count) {
|
void CollectionView::TotalSongCountUpdated(const int count) {
|
||||||
|
|
||||||
int old = total_song_count_;
|
int old = total_song_count_;
|
||||||
total_song_count_ = 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_;
|
int old = total_artist_count_;
|
||||||
total_artist_count_ = 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_;
|
int old = total_album_count_;
|
||||||
total_album_count_ = count;
|
total_album_count_ = count;
|
||||||
@@ -316,41 +338,41 @@ void CollectionView::contextMenuEvent(QContextMenuEvent *e) {
|
|||||||
|
|
||||||
if (!context_menu_) {
|
if (!context_menu_) {
|
||||||
context_menu_ = new QMenu(this);
|
context_menu_ = new QMenu(this);
|
||||||
add_to_playlist_ = context_menu_->addAction(IconLoader::Load("media-playback-start"), tr("Append to current playlist"), this, SLOT(AddToPlaylist()));
|
action_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()));
|
action_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_open_in_new_playlist_ = context_menu_->addAction(IconLoader::Load("document-new"), tr("Open in new playlist"), this, SLOT(OpenInNewPlaylist()));
|
||||||
|
|
||||||
context_menu_->addSeparator();
|
context_menu_->addSeparator();
|
||||||
add_to_playlist_enqueue_ = context_menu_->addAction(IconLoader::Load("go-next"), tr("Queue track"), this, SLOT(AddToPlaylistEnqueue()));
|
action_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_next_ = context_menu_->addAction(IconLoader::Load("go-next"), tr("Queue to play next"), this, SLOT(AddToPlaylistEnqueueNext()));
|
||||||
|
|
||||||
context_menu_->addSeparator();
|
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
|
#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
|
#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();
|
context_menu_->addSeparator();
|
||||||
edit_track_ = context_menu_->addAction(IconLoader::Load("edit-rename"), tr("Edit track information..."), this, SLOT(EditTracks()));
|
action_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()));
|
action_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_show_in_browser_ = context_menu_->addAction(IconLoader::Load("document-open-folder"), tr("Show in file browser..."), this, SLOT(ShowInBrowser()));
|
||||||
|
|
||||||
context_menu_->addSeparator();
|
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();
|
context_menu_->addSeparator();
|
||||||
show_in_various_ = context_menu_->addAction( tr("Show in various artists"), this, SLOT(ShowInVarious()));
|
action_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_no_show_in_various_ = context_menu_->addAction( tr("Don't show in various artists"), this, SLOT(NoShowInVarious()));
|
||||||
|
|
||||||
context_menu_->addSeparator();
|
context_menu_->addSeparator();
|
||||||
|
|
||||||
context_menu_->addMenu(filter_->menu());
|
context_menu_->addMenu(filter_->menu());
|
||||||
|
|
||||||
#ifndef Q_OS_WIN
|
#ifndef Q_OS_WIN
|
||||||
copy_to_device_->setDisabled(app_->device_manager()->connected_devices_model()->rowCount() == 0);
|
action_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)));
|
connect(app_->device_manager()->connected_devices_model(), SIGNAL(IsEmptyChanged(bool)), action_copy_to_device_, SLOT(setDisabled(bool)));
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -376,34 +398,45 @@ void CollectionView::contextMenuEvent(QContextMenuEvent *e) {
|
|||||||
const bool regular_elements_only = songs_selected == regular_elements && regular_elements > 0;
|
const bool regular_elements_only = songs_selected == regular_elements && regular_elements > 0;
|
||||||
|
|
||||||
// in all modes
|
// in all modes
|
||||||
load_->setEnabled(songs_selected > 0);
|
action_load_->setEnabled(songs_selected > 0);
|
||||||
add_to_playlist_->setEnabled(songs_selected > 0);
|
action_add_to_playlist_->setEnabled(songs_selected > 0);
|
||||||
open_in_new_playlist_->setEnabled(songs_selected > 0);
|
action_open_in_new_playlist_->setEnabled(songs_selected > 0);
|
||||||
add_to_playlist_enqueue_->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
|
// if neither edit_track not edit_tracks are available, we show disabled edit_track element
|
||||||
edit_track_->setVisible(regular_editable == 1);
|
action_edit_track_->setVisible(regular_editable == 1);
|
||||||
edit_track_->setEnabled(regular_editable == 1);
|
action_edit_track_->setEnabled(regular_editable == 1);
|
||||||
edit_tracks_->setVisible(regular_editable > 1);
|
action_edit_tracks_->setVisible(regular_editable > 1);
|
||||||
edit_tracks_->setEnabled(regular_editable > 1);
|
action_edit_tracks_->setEnabled(regular_editable > 1);
|
||||||
|
|
||||||
rescan_songs_->setVisible(regular_editable > 0);
|
action_rescan_songs_->setVisible(regular_editable > 0);
|
||||||
rescan_songs_->setEnabled(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
|
#ifndef Q_OS_WIN
|
||||||
copy_to_device_->setVisible(regular_elements == regular_editable);
|
action_copy_to_device_->setVisible(regular_elements == regular_editable);
|
||||||
#endif
|
#endif
|
||||||
//delete_->setVisible(regular_elements_only);
|
|
||||||
show_in_various_->setVisible(regular_elements_only);
|
#if QT_VERSION >= QT_VERSION_CHECK(5, 15, 0)
|
||||||
no_show_in_various_->setVisible(regular_elements_only);
|
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
|
// only when all selected items are editable
|
||||||
organize_->setEnabled(regular_elements == regular_editable);
|
action_organize_->setEnabled(regular_elements == regular_editable);
|
||||||
#ifndef Q_OS_WIN
|
#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
|
#endif
|
||||||
//delete_->setEnabled(regular_elements == regular_editable);
|
|
||||||
|
|
||||||
context_menu_->popup(e->globalPos());
|
context_menu_->popup(e->globalPos());
|
||||||
|
|
||||||
@@ -413,7 +446,7 @@ void CollectionView::ShowInVarious() { ShowInVarious(true); }
|
|||||||
|
|
||||||
void CollectionView::NoShowInVarious() { ShowInVarious(false); }
|
void CollectionView::NoShowInVarious() { ShowInVarious(false); }
|
||||||
|
|
||||||
void CollectionView::ShowInVarious(bool on) {
|
void CollectionView::ShowInVarious(const bool on) {
|
||||||
|
|
||||||
if (!context_menu_index_.isValid()) return;
|
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,
|
// 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
|
// to make sure the old album node gets removed (due to all children removed), before the new one gets added
|
||||||
QMultiMap<QString, QString> albums;
|
QMultiMap<QString, QString> albums;
|
||||||
for (const Song& song : GetSelectedSongs()) {
|
for (const Song &song : GetSelectedSongs()) {
|
||||||
if (albums.find(song.album(), song.artist()) == albums.end())
|
if (albums.find(song.album(), song.artist()) == albums.end())
|
||||||
albums.insert(song.album(), song.artist());
|
albums.insert(song.album(), song.artist());
|
||||||
}
|
}
|
||||||
@@ -440,7 +473,7 @@ void CollectionView::ShowInVarious(bool on) {
|
|||||||
if (other_artists.count() > 0) {
|
if (other_artists.count() > 0) {
|
||||||
if (QMessageBox::question(this,
|
if (QMessageBox::question(this,
|
||||||
tr("There are other songs in this album"),
|
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::No,
|
||||||
QMessageBox::Yes) == QMessageBox::Yes) {
|
QMessageBox::Yes) == QMessageBox::Yes) {
|
||||||
for (const QString &s : other_artists) {
|
for (const QString &s : other_artists) {
|
||||||
@@ -619,3 +652,33 @@ int CollectionView::TotalArtists() {
|
|||||||
int CollectionView::TotalAlbums() {
|
int CollectionView::TotalAlbums() {
|
||||||
return total_album_count_;
|
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();
|
int TotalAlbums();
|
||||||
|
|
||||||
public slots:
|
public slots:
|
||||||
void TotalSongCountUpdated(int count);
|
void TotalSongCountUpdated(const int count);
|
||||||
void TotalArtistCountUpdated(int count);
|
void TotalArtistCountUpdated(const int count);
|
||||||
void TotalAlbumCountUpdated(int count);
|
void TotalAlbumCountUpdated(const int count);
|
||||||
void ReloadSettings();
|
void ReloadSettings();
|
||||||
|
|
||||||
void FilterReturnPressed();
|
void FilterReturnPressed();
|
||||||
@@ -88,7 +88,7 @@ class CollectionView : public AutoExpandingTreeView {
|
|||||||
void TotalSongCountUpdated_();
|
void TotalSongCountUpdated_();
|
||||||
void TotalArtistCountUpdated_();
|
void TotalArtistCountUpdated_();
|
||||||
void TotalAlbumCountUpdated_();
|
void TotalAlbumCountUpdated_();
|
||||||
void Error(const QString &message);
|
void Error(QString);
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
// QWidget
|
// QWidget
|
||||||
@@ -109,10 +109,12 @@ class CollectionView : public AutoExpandingTreeView {
|
|||||||
void ShowInBrowser();
|
void ShowInBrowser();
|
||||||
void ShowInVarious();
|
void ShowInVarious();
|
||||||
void NoShowInVarious();
|
void NoShowInVarious();
|
||||||
|
void Delete();
|
||||||
|
void DeleteFilesFinished(const SongList &songs_with_errors);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void RecheckIsEmpty();
|
void RecheckIsEmpty();
|
||||||
void ShowInVarious(bool on);
|
void ShowInVarious(const bool on);
|
||||||
bool RestoreLevelFocus(const QModelIndex &parent = QModelIndex());
|
bool RestoreLevelFocus(const QModelIndex &parent = QModelIndex());
|
||||||
void SaveContainerPath(const QModelIndex &child);
|
void SaveContainerPath(const QModelIndex &child);
|
||||||
|
|
||||||
@@ -128,27 +130,28 @@ class CollectionView : public AutoExpandingTreeView {
|
|||||||
|
|
||||||
QMenu *context_menu_;
|
QMenu *context_menu_;
|
||||||
QModelIndex context_menu_index_;
|
QModelIndex context_menu_index_;
|
||||||
QAction *load_;
|
QAction *action_load_;
|
||||||
QAction *add_to_playlist_;
|
QAction *action_add_to_playlist_;
|
||||||
QAction *add_to_playlist_enqueue_;
|
QAction *action_add_to_playlist_enqueue_;
|
||||||
QAction *add_to_playlist_enqueue_next_;
|
QAction *action_add_to_playlist_enqueue_next_;
|
||||||
QAction *open_in_new_playlist_;
|
QAction *action_open_in_new_playlist_;
|
||||||
QAction *organize_;
|
QAction *action_organize_;
|
||||||
#ifndef Q_OS_WIN
|
#ifndef Q_OS_WIN
|
||||||
QAction *copy_to_device_;
|
QAction *action_copy_to_device_;
|
||||||
#endif
|
#endif
|
||||||
QAction *delete_;
|
QAction *action_edit_track_;
|
||||||
QAction *edit_track_;
|
QAction *action_edit_tracks_;
|
||||||
QAction *edit_tracks_;
|
QAction *action_rescan_songs_;
|
||||||
QAction *rescan_songs_;
|
QAction *action_show_in_browser_;
|
||||||
QAction *show_in_browser_;
|
QAction *action_show_in_various_;
|
||||||
QAction *show_in_various_;
|
QAction *action_no_show_in_various_;
|
||||||
QAction *no_show_in_various_;
|
QAction *action_delete_files_;
|
||||||
|
|
||||||
std::unique_ptr<OrganizeDialog> organize_dialog_;
|
std::unique_ptr<OrganizeDialog> organize_dialog_;
|
||||||
std::unique_ptr<EditTagDialog> edit_tag_dialog_;
|
std::unique_ptr<EditTagDialog> edit_tag_dialog_;
|
||||||
|
|
||||||
bool is_in_keyboard_search_;
|
bool is_in_keyboard_search_;
|
||||||
|
bool delete_files_;
|
||||||
|
|
||||||
// Save focus
|
// Save focus
|
||||||
Song last_selected_song_;
|
Song last_selected_song_;
|
||||||
|
|||||||
@@ -65,7 +65,7 @@ static const char *kNoMediaFile = ".nomedia";
|
|||||||
static const char *kNoMusicFile = ".nomusic";
|
static const char *kNoMusicFile = ".nomusic";
|
||||||
}
|
}
|
||||||
|
|
||||||
QStringList CollectionWatcher::sValidImages;
|
QStringList CollectionWatcher::sValidImages = QStringList() << "jpg" << "png" << "gif" << "jpeg";
|
||||||
|
|
||||||
CollectionWatcher::CollectionWatcher(Song::Source source, QObject *parent)
|
CollectionWatcher::CollectionWatcher(Song::Source source, QObject *parent)
|
||||||
: QObject(parent),
|
: QObject(parent),
|
||||||
@@ -90,10 +90,6 @@ CollectionWatcher::CollectionWatcher(Song::Source source, QObject *parent)
|
|||||||
rescan_timer_->setInterval(1000);
|
rescan_timer_->setInterval(1000);
|
||||||
rescan_timer_->setSingleShot(true);
|
rescan_timer_->setSingleShot(true);
|
||||||
|
|
||||||
if (sValidImages.isEmpty()) {
|
|
||||||
sValidImages << "jpg" << "png" << "gif" << "jpeg";
|
|
||||||
}
|
|
||||||
|
|
||||||
ReloadSettings();
|
ReloadSettings();
|
||||||
|
|
||||||
connect(rescan_timer_, SIGNAL(timeout()), SLOT(RescanPathsNow()));
|
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.
|
// 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!
|
// 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);
|
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 (cue_song.url().toLocalFile().normalized(QString::NormalizationForm_D) == file_nfd) {
|
||||||
if (TagReaderClient::Instance()->IsMediaFileBlocking(file)) {
|
if (TagReaderClient::Instance()->IsMediaFileBlocking(file)) {
|
||||||
song_list << cue_song;
|
song_list << cue_song;
|
||||||
@@ -812,7 +809,6 @@ void CollectionWatcher::ReloadSettings() {
|
|||||||
scan_on_startup_ = s.value("startup_scan", true).toBool();
|
scan_on_startup_ = s.value("startup_scan", true).toBool();
|
||||||
monitor_ = s.value("monitor", true).toBool();
|
monitor_ = s.value("monitor", true).toBool();
|
||||||
mark_songs_unavailable_ = s.value("mark_songs_unavailable", false).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();
|
QStringList filters = s.value("cover_art_patterns", QStringList() << "front" << "cover").toStringList();
|
||||||
s.endGroup();
|
s.endGroup();
|
||||||
|
|
||||||
|
|||||||
@@ -74,44 +74,45 @@ SavedGroupingManager::~SavedGroupingManager() {
|
|||||||
QString SavedGroupingManager::GroupByToString(const CollectionModel::GroupBy &g) {
|
QString SavedGroupingManager::GroupByToString(const CollectionModel::GroupBy &g) {
|
||||||
|
|
||||||
switch (g) {
|
switch (g) {
|
||||||
case CollectionModel::GroupBy_None: {
|
case CollectionModel::GroupBy_None:
|
||||||
|
case CollectionModel::GroupByCount: {
|
||||||
return tr("None");
|
return tr("None");
|
||||||
}
|
}
|
||||||
case CollectionModel::GroupBy_Artist: {
|
|
||||||
return tr("Artist");
|
|
||||||
}
|
|
||||||
case CollectionModel::GroupBy_AlbumArtist: {
|
case CollectionModel::GroupBy_AlbumArtist: {
|
||||||
return tr("Album artist");
|
return tr("Album artist");
|
||||||
}
|
}
|
||||||
|
case CollectionModel::GroupBy_Artist: {
|
||||||
|
return tr("Artist");
|
||||||
|
}
|
||||||
case CollectionModel::GroupBy_Album: {
|
case CollectionModel::GroupBy_Album: {
|
||||||
return tr("Album");
|
return tr("Album");
|
||||||
}
|
}
|
||||||
case CollectionModel::GroupBy_AlbumDisc: {
|
case CollectionModel::GroupBy_AlbumDisc: {
|
||||||
return tr("Album - Disc");
|
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: {
|
case CollectionModel::GroupBy_YearAlbum: {
|
||||||
return tr("Year - Album");
|
return tr("Year - Album");
|
||||||
}
|
}
|
||||||
case CollectionModel::GroupBy_YearAlbumDisc: {
|
case CollectionModel::GroupBy_YearAlbumDisc: {
|
||||||
return tr("Year - Album - Disc");
|
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: {
|
case CollectionModel::GroupBy_OriginalYear: {
|
||||||
return tr("Original year");
|
return tr("Original year");
|
||||||
}
|
}
|
||||||
case CollectionModel::GroupBy_OriginalYearAlbum: {
|
case CollectionModel::GroupBy_Genre: {
|
||||||
return tr("Original year - Album");
|
return tr("Genre");
|
||||||
}
|
}
|
||||||
case CollectionModel::GroupBy_Composer: {
|
case CollectionModel::GroupBy_Composer: {
|
||||||
return tr("Composer");
|
return tr("Composer");
|
||||||
@@ -125,6 +126,9 @@ QString SavedGroupingManager::GroupByToString(const CollectionModel::GroupBy &g)
|
|||||||
case CollectionModel::GroupBy_FileType: {
|
case CollectionModel::GroupBy_FileType: {
|
||||||
return tr("File type");
|
return tr("File type");
|
||||||
}
|
}
|
||||||
|
case CollectionModel::GroupBy_Format: {
|
||||||
|
return tr("Format");
|
||||||
|
}
|
||||||
case CollectionModel::GroupBy_Samplerate: {
|
case CollectionModel::GroupBy_Samplerate: {
|
||||||
return tr("Sample rate");
|
return tr("Sample rate");
|
||||||
}
|
}
|
||||||
@@ -134,9 +138,10 @@ QString SavedGroupingManager::GroupByToString(const CollectionModel::GroupBy &g)
|
|||||||
case CollectionModel::GroupBy_Bitrate: {
|
case CollectionModel::GroupBy_Bitrate: {
|
||||||
return tr("Bitrate");
|
return tr("Bitrate");
|
||||||
}
|
}
|
||||||
default: { return tr("Unknown"); }
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return tr("Unknown");
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void SavedGroupingManager::UpdateModel() {
|
void SavedGroupingManager::UpdateModel() {
|
||||||
@@ -144,21 +149,33 @@ void SavedGroupingManager::UpdateModel() {
|
|||||||
model_->setRowCount(0); // don't use clear, it deletes headers
|
model_->setRowCount(0); // don't use clear, it deletes headers
|
||||||
QSettings s;
|
QSettings s;
|
||||||
s.beginGroup(CollectionModel::kSavedGroupingsSettingsGroup);
|
s.beginGroup(CollectionModel::kSavedGroupingsSettingsGroup);
|
||||||
QStringList saved = s.childKeys();
|
int version = s.value("version").toInt();
|
||||||
for (int i = 0; i < saved.size(); ++i) {
|
if (version == 1) {
|
||||||
QByteArray bytes = s.value(saved.at(i)).toByteArray();
|
QStringList saved = s.childKeys();
|
||||||
QDataStream ds(&bytes, QIODevice::ReadOnly);
|
for (int i = 0; i < saved.size(); ++i) {
|
||||||
CollectionModel::Grouping g;
|
if (saved.at(i) == "version") continue;
|
||||||
ds >> g;
|
QByteArray bytes = s.value(saved.at(i)).toByteArray();
|
||||||
|
QDataStream ds(&bytes, QIODevice::ReadOnly);
|
||||||
|
CollectionModel::Grouping g;
|
||||||
|
ds >> g;
|
||||||
|
|
||||||
QList<QStandardItem*> list;
|
QList<QStandardItem*> list;
|
||||||
list << new QStandardItem(saved.at(i))
|
list << new QStandardItem(saved.at(i))
|
||||||
<< new QStandardItem(GroupByToString(g.first))
|
<< new QStandardItem(GroupByToString(g.first))
|
||||||
<< new QStandardItem(GroupByToString(g.second))
|
<< new QStandardItem(GroupByToString(g.second))
|
||||||
<< new QStandardItem(GroupByToString(g.third));
|
<< 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.remove(model_->item(index.row(), 0)->text());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
s.endGroup();
|
||||||
}
|
}
|
||||||
UpdateModel();
|
UpdateModel();
|
||||||
filter_->UpdateGroupByActions();
|
filter_->UpdateGroupByActions();
|
||||||
|
|||||||
@@ -46,6 +46,7 @@
|
|||||||
|
|
||||||
#cmakedefine HAVE_SUBSONIC
|
#cmakedefine HAVE_SUBSONIC
|
||||||
#cmakedefine HAVE_TIDAL
|
#cmakedefine HAVE_TIDAL
|
||||||
|
#cmakedefine HAVE_QOBUZ
|
||||||
|
|
||||||
#cmakedefine HAVE_MOODBAR
|
#cmakedefine HAVE_MOODBAR
|
||||||
|
|
||||||
|
|||||||
@@ -365,8 +365,8 @@ void ContextView::ReloadSettings() {
|
|||||||
title_fmt_ = s.value(ContextSettingsPage::kSettingsTitleFmt, "%title% - %artist%").toString();
|
title_fmt_ = s.value(ContextSettingsPage::kSettingsTitleFmt, "%title% - %artist%").toString();
|
||||||
summary_fmt_ = s.value(ContextSettingsPage::kSettingsSummaryFmt, "%album%").toString();
|
summary_fmt_ = s.value(ContextSettingsPage::kSettingsSummaryFmt, "%album%").toString();
|
||||||
action_show_album_->setChecked(s.value(ContextSettingsPage::kSettingsGroupEnable[ContextSettingsPage::ContextSettingsOrder::ALBUM], true).toBool());
|
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_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], true).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_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_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());
|
action_search_lyrics_->setChecked(s.value(ContextSettingsPage::kSettingsGroupEnable[ContextSettingsPage::ContextSettingsOrder::SEARCH_LYRICS], true).toBool());
|
||||||
|
|||||||
@@ -56,7 +56,6 @@
|
|||||||
#include "covermanager/discogscoverprovider.h"
|
#include "covermanager/discogscoverprovider.h"
|
||||||
#include "covermanager/musicbrainzcoverprovider.h"
|
#include "covermanager/musicbrainzcoverprovider.h"
|
||||||
#include "covermanager/deezercoverprovider.h"
|
#include "covermanager/deezercoverprovider.h"
|
||||||
#include "covermanager/qobuzcoverprovider.h"
|
|
||||||
#include "covermanager/musixmatchcoverprovider.h"
|
#include "covermanager/musixmatchcoverprovider.h"
|
||||||
#include "covermanager/spotifycoverprovider.h"
|
#include "covermanager/spotifycoverprovider.h"
|
||||||
|
|
||||||
@@ -69,6 +68,7 @@
|
|||||||
#include "lyrics/chartlyricsprovider.h"
|
#include "lyrics/chartlyricsprovider.h"
|
||||||
|
|
||||||
#include "scrobbler/audioscrobbler.h"
|
#include "scrobbler/audioscrobbler.h"
|
||||||
|
#include "scrobbler/lastfmimport.h"
|
||||||
|
|
||||||
#include "internet/internetservices.h"
|
#include "internet/internetservices.h"
|
||||||
|
|
||||||
@@ -81,6 +81,11 @@
|
|||||||
# include "covermanager/tidalcoverprovider.h"
|
# include "covermanager/tidalcoverprovider.h"
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
#ifdef HAVE_QOBUZ
|
||||||
|
# include "qobuz/qobuzservice.h"
|
||||||
|
# include "covermanager/qobuzcoverprovider.h"
|
||||||
|
#endif
|
||||||
|
|
||||||
#ifdef HAVE_MOODBAR
|
#ifdef HAVE_MOODBAR
|
||||||
# include "moodbar/moodbarcontroller.h"
|
# include "moodbar/moodbarcontroller.h"
|
||||||
# include "moodbar/moodbarloader.h"
|
# include "moodbar/moodbarloader.h"
|
||||||
@@ -122,11 +127,13 @@ class ApplicationImpl {
|
|||||||
cover_providers->AddProvider(new MusicbrainzCoverProvider(app, app));
|
cover_providers->AddProvider(new MusicbrainzCoverProvider(app, app));
|
||||||
cover_providers->AddProvider(new DiscogsCoverProvider(app, app));
|
cover_providers->AddProvider(new DiscogsCoverProvider(app, app));
|
||||||
cover_providers->AddProvider(new DeezerCoverProvider(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 MusixmatchCoverProvider(app, app));
|
||||||
cover_providers->AddProvider(new SpotifyCoverProvider(app, app));
|
cover_providers->AddProvider(new SpotifyCoverProvider(app, app));
|
||||||
#ifdef HAVE_TIDAL
|
#ifdef HAVE_TIDAL
|
||||||
cover_providers->AddProvider(new TidalCoverProvider(app, app));
|
cover_providers->AddProvider(new TidalCoverProvider(app, app));
|
||||||
|
#endif
|
||||||
|
#ifdef HAVE_QOBUZ
|
||||||
|
cover_providers->AddProvider(new QobuzCoverProvider(app, app));
|
||||||
#endif
|
#endif
|
||||||
cover_providers->ReloadSettings();
|
cover_providers->ReloadSettings();
|
||||||
return cover_providers;
|
return cover_providers;
|
||||||
@@ -156,10 +163,14 @@ class ApplicationImpl {
|
|||||||
#endif
|
#endif
|
||||||
#ifdef HAVE_TIDAL
|
#ifdef HAVE_TIDAL
|
||||||
internet_services->AddService(new TidalService(app, internet_services));
|
internet_services->AddService(new TidalService(app, internet_services));
|
||||||
|
#endif
|
||||||
|
#ifdef HAVE_QOBUZ
|
||||||
|
internet_services->AddService(new QobuzService(app, internet_services));
|
||||||
#endif
|
#endif
|
||||||
return internet_services;
|
return internet_services;
|
||||||
}),
|
}),
|
||||||
scrobbler_([=]() { return new AudioScrobbler(app, app); }),
|
scrobbler_([=]() { return new AudioScrobbler(app, app); }),
|
||||||
|
lastfm_import_([=]() { return new LastFMImport(app); }),
|
||||||
|
|
||||||
#ifdef HAVE_MOODBAR
|
#ifdef HAVE_MOODBAR
|
||||||
moodbar_loader_([=]() { return new MoodbarLoader(app, app); }),
|
moodbar_loader_([=]() { return new MoodbarLoader(app, app); }),
|
||||||
@@ -187,6 +198,7 @@ class ApplicationImpl {
|
|||||||
Lazy<LyricsProviders> lyrics_providers_;
|
Lazy<LyricsProviders> lyrics_providers_;
|
||||||
Lazy<InternetServices> internet_services_;
|
Lazy<InternetServices> internet_services_;
|
||||||
Lazy<AudioScrobbler> scrobbler_;
|
Lazy<AudioScrobbler> scrobbler_;
|
||||||
|
Lazy<LastFMImport> lastfm_import_;
|
||||||
#ifdef HAVE_MOODBAR
|
#ifdef HAVE_MOODBAR
|
||||||
Lazy<MoodbarLoader> moodbar_loader_;
|
Lazy<MoodbarLoader> moodbar_loader_;
|
||||||
Lazy<MoodbarController> moodbar_controller_;
|
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(); }
|
PlaylistManager *Application::playlist_manager() const { return p_->playlist_manager_.get(); }
|
||||||
InternetServices *Application::internet_services() const { return p_->internet_services_.get(); }
|
InternetServices *Application::internet_services() const { return p_->internet_services_.get(); }
|
||||||
AudioScrobbler *Application::scrobbler() const { return p_->scrobbler_.get(); }
|
AudioScrobbler *Application::scrobbler() const { return p_->scrobbler_.get(); }
|
||||||
|
LastFMImport *Application::lastfm_import() const { return p_->lastfm_import_.get(); }
|
||||||
#ifdef HAVE_MOODBAR
|
#ifdef HAVE_MOODBAR
|
||||||
MoodbarController *Application::moodbar_controller() const { return p_->moodbar_controller_.get(); }
|
MoodbarController *Application::moodbar_controller() const { return p_->moodbar_controller_.get(); }
|
||||||
MoodbarLoader *Application::moodbar_loader() const { return p_->moodbar_loader_.get(); }
|
MoodbarLoader *Application::moodbar_loader() const { return p_->moodbar_loader_.get(); }
|
||||||
|
|||||||
@@ -56,6 +56,7 @@ class CurrentAlbumCoverLoader;
|
|||||||
class CoverProviders;
|
class CoverProviders;
|
||||||
class LyricsProviders;
|
class LyricsProviders;
|
||||||
class AudioScrobbler;
|
class AudioScrobbler;
|
||||||
|
class LastFMImport;
|
||||||
class InternetServices;
|
class InternetServices;
|
||||||
#ifdef HAVE_MOODBAR
|
#ifdef HAVE_MOODBAR
|
||||||
class MoodbarController;
|
class MoodbarController;
|
||||||
@@ -93,6 +94,7 @@ class Application : public QObject {
|
|||||||
LyricsProviders *lyrics_providers() const;
|
LyricsProviders *lyrics_providers() const;
|
||||||
|
|
||||||
AudioScrobbler *scrobbler() const;
|
AudioScrobbler *scrobbler() const;
|
||||||
|
LastFMImport *lastfm_import() const;
|
||||||
|
|
||||||
InternetServices *internet_services() const;
|
InternetServices *internet_services() const;
|
||||||
|
|
||||||
|
|||||||
@@ -54,7 +54,7 @@
|
|||||||
#include "scopedtransaction.h"
|
#include "scopedtransaction.h"
|
||||||
|
|
||||||
const char *Database::kDatabaseFilename = "strawberry.db";
|
const char *Database::kDatabaseFilename = "strawberry.db";
|
||||||
const int Database::kSchemaVersion = 12;
|
const int Database::kSchemaVersion = 13;
|
||||||
const char *Database::kMagicAllSongsTables = "%allsongstables";
|
const char *Database::kMagicAllSongsTables = "%allsongstables";
|
||||||
|
|
||||||
int Database::sNextConnectionId = 1;
|
int Database::sNextConnectionId = 1;
|
||||||
@@ -63,7 +63,9 @@ QMutex Database::sNextConnectionIdMutex;
|
|||||||
Database::Database(Application *app, QObject *parent, const QString &database_name) :
|
Database::Database(Application *app, QObject *parent, const QString &database_name) :
|
||||||
QObject(parent),
|
QObject(parent),
|
||||||
app_(app),
|
app_(app),
|
||||||
|
#if QT_VERSION < QT_VERSION_CHECK(5, 14, 0)
|
||||||
mutex_(QMutex::Recursive),
|
mutex_(QMutex::Recursive),
|
||||||
|
#endif
|
||||||
injected_database_name_(database_name),
|
injected_database_name_(database_name),
|
||||||
query_hash_(0),
|
query_hash_(0),
|
||||||
startup_schema_version_(-1),
|
startup_schema_version_(-1),
|
||||||
|
|||||||
@@ -34,6 +34,9 @@
|
|||||||
#include <QSqlQuery>
|
#include <QSqlQuery>
|
||||||
#include <QString>
|
#include <QString>
|
||||||
#include <QStringList>
|
#include <QStringList>
|
||||||
|
#if QT_VERSION >= QT_VERSION_CHECK(5, 14, 0)
|
||||||
|
# include <QRecursiveMutex>
|
||||||
|
#endif
|
||||||
|
|
||||||
class QThread;
|
class QThread;
|
||||||
class Application;
|
class Application;
|
||||||
@@ -63,7 +66,12 @@ class Database : public QObject {
|
|||||||
QSqlDatabase Connect();
|
QSqlDatabase Connect();
|
||||||
void Close();
|
void Close();
|
||||||
bool CheckErrors(const QSqlQuery &query);
|
bool CheckErrors(const QSqlQuery &query);
|
||||||
|
|
||||||
|
#if QT_VERSION >= QT_VERSION_CHECK(5, 14, 0)
|
||||||
|
QRecursiveMutex *Mutex() { return &mutex_; }
|
||||||
|
#else
|
||||||
QMutex *Mutex() { return &mutex_; }
|
QMutex *Mutex() { return &mutex_; }
|
||||||
|
#endif
|
||||||
|
|
||||||
void RecreateAttachedDb(const QString &database_name);
|
void RecreateAttachedDb(const QString &database_name);
|
||||||
void ExecSchemaCommands(QSqlDatabase &db, const QString &schema, int schema_version, bool in_transaction = false);
|
void ExecSchemaCommands(QSqlDatabase &db, const QString &schema, int schema_version, bool in_transaction = false);
|
||||||
@@ -106,7 +114,11 @@ class Database : public QObject {
|
|||||||
|
|
||||||
QString directory_;
|
QString directory_;
|
||||||
QMutex connect_mutex_;
|
QMutex connect_mutex_;
|
||||||
|
#if QT_VERSION >= QT_VERSION_CHECK(5, 14, 0)
|
||||||
|
QRecursiveMutex mutex_;
|
||||||
|
#else
|
||||||
QMutex mutex_;
|
QMutex mutex_;
|
||||||
|
#endif
|
||||||
|
|
||||||
// This ID makes the QSqlDatabase name unique to the object as well as the thread
|
// This ID makes the QSqlDatabase name unique to the object as well as the thread
|
||||||
int connection_id_;
|
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;
|
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),
|
: thread_(nullptr),
|
||||||
task_manager_(task_manager),
|
task_manager_(task_manager),
|
||||||
storage_(storage),
|
storage_(storage),
|
||||||
|
use_trash_(use_trash),
|
||||||
started_(false),
|
started_(false),
|
||||||
task_id_(0),
|
task_id_(0),
|
||||||
progress_(0) {
|
progress_(0) {
|
||||||
@@ -112,6 +113,7 @@ void DeleteFiles::ProcessSomeFiles() {
|
|||||||
|
|
||||||
MusicStorage::DeleteJob job;
|
MusicStorage::DeleteJob job;
|
||||||
job.metadata_ = song;
|
job.metadata_ = song;
|
||||||
|
job.use_trash_ = use_trash_;
|
||||||
|
|
||||||
if (!storage_->DeleteFromStorage(job)) {
|
if (!storage_->DeleteFromStorage(job)) {
|
||||||
songs_with_errors_ << song;
|
songs_with_errors_ << song;
|
||||||
|
|||||||
@@ -38,7 +38,7 @@ class DeleteFiles : public QObject {
|
|||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
|
|
||||||
public:
|
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;
|
~DeleteFiles() override;
|
||||||
|
|
||||||
static const int kBatchSize;
|
static const int kBatchSize;
|
||||||
@@ -59,6 +59,7 @@ signals:
|
|||||||
std::shared_ptr<MusicStorage> storage_;
|
std::shared_ptr<MusicStorage> storage_;
|
||||||
|
|
||||||
SongList songs_;
|
SongList songs_;
|
||||||
|
bool use_trash_;
|
||||||
|
|
||||||
bool started_;
|
bool started_;
|
||||||
|
|
||||||
|
|||||||
@@ -108,6 +108,17 @@ bool FilesystemMusicStorage::DeleteFromStorage(const DeleteJob &job) {
|
|||||||
QString path = job.metadata_.url().toLocalFile();
|
QString path = job.metadata_.url().toLocalFile();
|
||||||
QFileInfo fileInfo(path);
|
QFileInfo fileInfo(path);
|
||||||
|
|
||||||
|
if (job.use_trash_) {
|
||||||
|
#if QT_VERSION >= QT_VERSION_CHECK(5, 15, 0)
|
||||||
|
if (fileInfo.isDir())
|
||||||
|
return Utilities::MoveToTrashRecursive(path);
|
||||||
|
else
|
||||||
|
return QFile::moveToTrash(path);
|
||||||
|
#else
|
||||||
|
return false;
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
if (fileInfo.isDir())
|
if (fileInfo.isDir())
|
||||||
return Utilities::RemoveRecursive(path);
|
return Utilities::RemoveRecursive(path);
|
||||||
else
|
else
|
||||||
|
|||||||
@@ -38,10 +38,12 @@ bool IconLoader::custom_icons_ = false;
|
|||||||
|
|
||||||
void IconLoader::Init() {
|
void IconLoader::Init() {
|
||||||
|
|
||||||
|
#if !defined(Q_OS_MACOS) && !defined(Q_OS_WIN)
|
||||||
QSettings s;
|
QSettings s;
|
||||||
s.beginGroup(AppearanceSettingsPage::kSettingsGroup);
|
s.beginGroup(AppearanceSettingsPage::kSettingsGroup);
|
||||||
system_icons_ = s.value("system_icons", false).toBool();
|
system_icons_ = s.value("system_icons", false).toBool();
|
||||||
s.endGroup();
|
s.endGroup();
|
||||||
|
#endif
|
||||||
|
|
||||||
QDir dir;
|
QDir dir;
|
||||||
if (dir.exists(QStandardPaths::writableLocation(QStandardPaths::AppLocalDataLocation) + "/icons")) {
|
if (dir.exists(QStandardPaths::writableLocation(QStandardPaths::AppLocalDataLocation) + "/icons")) {
|
||||||
|
|||||||
@@ -1,47 +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 "macscreensaver.h"
|
|
||||||
|
|
||||||
#include <QtDebug>
|
|
||||||
|
|
||||||
#include "core/utilities.h"
|
|
||||||
#include "core/mac_utilities.h"
|
|
||||||
|
|
||||||
// kIOPMAssertionTypePreventUserIdleDisplaySleep from Lion.
|
|
||||||
#define kLionDisplayAssertion CFSTR("PreventUserIdleDisplaySleep")
|
|
||||||
|
|
||||||
MacScreensaver::MacScreensaver() : assertion_id_(0) {}
|
|
||||||
|
|
||||||
void MacScreensaver::Inhibit() {
|
|
||||||
CFStringRef assertion_type = (Utilities::GetMacOsVersion() >= 7) ? kLionDisplayAssertion : kIOPMAssertionTypeNoDisplaySleep;
|
|
||||||
|
|
||||||
IOPMAssertionCreateWithName(
|
|
||||||
assertion_type,
|
|
||||||
kIOPMAssertionLevelOn,
|
|
||||||
CFSTR("Showing full-screen Strawberry visualisations"),
|
|
||||||
&assertion_id_);
|
|
||||||
}
|
|
||||||
|
|
||||||
void MacScreensaver::Uninhibit() {
|
|
||||||
IOPMAssertionRelease(assertion_id_);
|
|
||||||
}
|
|
||||||
@@ -91,6 +91,8 @@
|
|||||||
#include "database.h"
|
#include "database.h"
|
||||||
#include "player.h"
|
#include "player.h"
|
||||||
#include "appearance.h"
|
#include "appearance.h"
|
||||||
|
#include "filesystemmusicstorage.h"
|
||||||
|
#include "deletefiles.h"
|
||||||
#include "engine/enginetype.h"
|
#include "engine/enginetype.h"
|
||||||
#include "engine/enginebase.h"
|
#include "engine/enginebase.h"
|
||||||
#include "engine/engine_fwd.h"
|
#include "engine/engine_fwd.h"
|
||||||
@@ -100,6 +102,8 @@
|
|||||||
#include "dialogs/trackselectiondialog.h"
|
#include "dialogs/trackselectiondialog.h"
|
||||||
#include "dialogs/edittagdialog.h"
|
#include "dialogs/edittagdialog.h"
|
||||||
#include "dialogs/addstreamdialog.h"
|
#include "dialogs/addstreamdialog.h"
|
||||||
|
#include "dialogs/deleteconfirmationdialog.h"
|
||||||
|
#include "dialogs/lastfmimportdialog.h"
|
||||||
#include "organize/organizedialog.h"
|
#include "organize/organizedialog.h"
|
||||||
#include "widgets/fancytabwidget.h"
|
#include "widgets/fancytabwidget.h"
|
||||||
#include "widgets/playingwidget.h"
|
#include "widgets/playingwidget.h"
|
||||||
@@ -152,11 +156,15 @@
|
|||||||
#include "settings/playlistsettingspage.h"
|
#include "settings/playlistsettingspage.h"
|
||||||
#ifdef HAVE_SUBSONIC
|
#ifdef HAVE_SUBSONIC
|
||||||
# include "settings/subsonicsettingspage.h"
|
# include "settings/subsonicsettingspage.h"
|
||||||
|
# include "scrobbler/subsonicscrobbler.h"
|
||||||
#endif
|
#endif
|
||||||
#ifdef HAVE_TIDAL
|
#ifdef HAVE_TIDAL
|
||||||
# include "tidal/tidalservice.h"
|
# include "tidal/tidalservice.h"
|
||||||
# include "settings/tidalsettingspage.h"
|
# include "settings/tidalsettingspage.h"
|
||||||
#endif
|
#endif
|
||||||
|
#ifdef HAVE_QOBUZ
|
||||||
|
# include "settings/qobuzsettingspage.h"
|
||||||
|
#endif
|
||||||
|
|
||||||
#include "internet/internetservices.h"
|
#include "internet/internetservices.h"
|
||||||
#include "internet/internetservice.h"
|
#include "internet/internetservice.h"
|
||||||
@@ -166,6 +174,7 @@
|
|||||||
#include "internet/internetsearchview.h"
|
#include "internet/internetsearchview.h"
|
||||||
|
|
||||||
#include "scrobbler/audioscrobbler.h"
|
#include "scrobbler/audioscrobbler.h"
|
||||||
|
#include "scrobbler/lastfmimport.h"
|
||||||
|
|
||||||
#if defined(HAVE_GSTREAMER) && defined(HAVE_CHROMAPRINT)
|
#if defined(HAVE_GSTREAMER) && defined(HAVE_CHROMAPRINT)
|
||||||
# include "musicbrainz/tagfetcher.h"
|
# include "musicbrainz/tagfetcher.h"
|
||||||
@@ -180,6 +189,9 @@
|
|||||||
# include "moodbar/moodbarproxystyle.h"
|
# include "moodbar/moodbarproxystyle.h"
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
#include "smartplaylists/smartplaylistsviewcontainer.h"
|
||||||
|
#include "smartplaylists/smartplaylistsview.h"
|
||||||
|
|
||||||
#ifdef Q_OS_WIN
|
#ifdef Q_OS_WIN
|
||||||
# include "windows7thumbbar.h"
|
# include "windows7thumbbar.h"
|
||||||
#endif
|
#endif
|
||||||
@@ -209,6 +221,10 @@ MainWindow::MainWindow(Application *app, SystemTrayIcon *tray_icon, OSDBase *osd
|
|||||||
app_(app),
|
app_(app),
|
||||||
tray_icon_(tray_icon),
|
tray_icon_(tray_icon),
|
||||||
osd_(osd),
|
osd_(osd),
|
||||||
|
console_([=]() {
|
||||||
|
Console *console = new Console(app);
|
||||||
|
return console;
|
||||||
|
}),
|
||||||
edit_tag_dialog_(std::bind(&MainWindow::CreateEditTagDialog, this)),
|
edit_tag_dialog_(std::bind(&MainWindow::CreateEditTagDialog, this)),
|
||||||
album_cover_choice_controller_(new AlbumCoverChoiceController(this)),
|
album_cover_choice_controller_(new AlbumCoverChoiceController(this)),
|
||||||
#ifdef HAVE_GLOBALSHORTCUTS
|
#ifdef HAVE_GLOBALSHORTCUTS
|
||||||
@@ -248,12 +264,17 @@ MainWindow::MainWindow(Application *app, SystemTrayIcon *tray_icon, OSDBase *osd
|
|||||||
connect(add_stream_dialog, SIGNAL(accepted()), this, SLOT(AddStreamAccepted()));
|
connect(add_stream_dialog, SIGNAL(accepted()), this, SLOT(AddStreamAccepted()));
|
||||||
return add_stream_dialog;
|
return add_stream_dialog;
|
||||||
}),
|
}),
|
||||||
|
smartplaylists_view_(new SmartPlaylistsViewContainer(app, this)),
|
||||||
#ifdef HAVE_SUBSONIC
|
#ifdef HAVE_SUBSONIC
|
||||||
subsonic_view_(new InternetSongsView(app_, app->internet_services()->ServiceBySource(Song::Source_Subsonic), SubsonicSettingsPage::kSettingsGroup, SettingsDialog::Page_Subsonic, this)),
|
subsonic_view_(new InternetSongsView(app_, app->internet_services()->ServiceBySource(Song::Source_Subsonic), SubsonicSettingsPage::kSettingsGroup, SettingsDialog::Page_Subsonic, this)),
|
||||||
#endif
|
#endif
|
||||||
#ifdef HAVE_TIDAL
|
#ifdef HAVE_TIDAL
|
||||||
tidal_view_(new InternetTabsView(app_, app->internet_services()->ServiceBySource(Song::Source_Tidal), TidalSettingsPage::kSettingsGroup, SettingsDialog::Page_Tidal, this)),
|
tidal_view_(new InternetTabsView(app_, app->internet_services()->ServiceBySource(Song::Source_Tidal), TidalSettingsPage::kSettingsGroup, SettingsDialog::Page_Tidal, this)),
|
||||||
#endif
|
#endif
|
||||||
|
#ifdef HAVE_QOBUZ
|
||||||
|
qobuz_view_(new InternetTabsView(app_, app->internet_services()->ServiceBySource(Song::Source_Qobuz), QobuzSettingsPage::kSettingsGroup, SettingsDialog::Page_Qobuz, this)),
|
||||||
|
#endif
|
||||||
|
lastfm_import_dialog_(new LastFMImportDialog(app_->lastfm_import(), this)),
|
||||||
collection_show_all_(nullptr),
|
collection_show_all_(nullptr),
|
||||||
collection_show_duplicates_(nullptr),
|
collection_show_duplicates_(nullptr),
|
||||||
collection_show_untagged_(nullptr),
|
collection_show_untagged_(nullptr),
|
||||||
@@ -261,15 +282,16 @@ MainWindow::MainWindow(Application *app, SystemTrayIcon *tray_icon, OSDBase *osd
|
|||||||
playlist_play_pause_(nullptr),
|
playlist_play_pause_(nullptr),
|
||||||
playlist_stop_after_(nullptr),
|
playlist_stop_after_(nullptr),
|
||||||
playlist_undoredo_(nullptr),
|
playlist_undoredo_(nullptr),
|
||||||
playlist_organize_(nullptr),
|
playlist_copy_url_(nullptr),
|
||||||
playlist_show_in_collection_(nullptr),
|
playlist_show_in_collection_(nullptr),
|
||||||
playlist_copy_to_collection_(nullptr),
|
playlist_copy_to_collection_(nullptr),
|
||||||
playlist_move_to_collection_(nullptr),
|
playlist_move_to_collection_(nullptr),
|
||||||
|
playlist_open_in_browser_(nullptr),
|
||||||
|
playlist_organize_(nullptr),
|
||||||
#ifndef Q_OS_WIN
|
#ifndef Q_OS_WIN
|
||||||
playlist_copy_to_device_(nullptr),
|
playlist_copy_to_device_(nullptr),
|
||||||
#endif
|
#endif
|
||||||
playlist_open_in_browser_(nullptr),
|
playlist_delete_(nullptr),
|
||||||
playlist_copy_url_(nullptr),
|
|
||||||
playlist_queue_(nullptr),
|
playlist_queue_(nullptr),
|
||||||
playlist_queue_play_next_(nullptr),
|
playlist_queue_play_next_(nullptr),
|
||||||
playlist_skip_(nullptr),
|
playlist_skip_(nullptr),
|
||||||
@@ -279,14 +301,18 @@ MainWindow::MainWindow(Application *app, SystemTrayIcon *tray_icon, OSDBase *osd
|
|||||||
collection_sort_model_(new QSortFilterProxyModel(this)),
|
collection_sort_model_(new QSortFilterProxyModel(this)),
|
||||||
track_position_timer_(new QTimer(this)),
|
track_position_timer_(new QTimer(this)),
|
||||||
track_slider_timer_(new QTimer(this)),
|
track_slider_timer_(new QTimer(this)),
|
||||||
initialized_(false),
|
keep_running_(false),
|
||||||
was_maximized_(true),
|
|
||||||
was_minimized_(false),
|
|
||||||
playing_widget_(true),
|
playing_widget_(true),
|
||||||
doubleclick_addmode_(BehaviourSettingsPage::AddBehaviour_Append),
|
doubleclick_addmode_(BehaviourSettingsPage::AddBehaviour_Append),
|
||||||
doubleclick_playmode_(BehaviourSettingsPage::PlayBehaviour_Never),
|
doubleclick_playmode_(BehaviourSettingsPage::PlayBehaviour_Never),
|
||||||
|
doubleclick_playlist_addmode_(BehaviourSettingsPage::PlaylistAddBehaviour_Play),
|
||||||
menu_playmode_(BehaviourSettingsPage::PlayBehaviour_Never),
|
menu_playmode_(BehaviourSettingsPage::PlayBehaviour_Never),
|
||||||
exit_count_(0)
|
initialized_(false),
|
||||||
|
was_maximized_(true),
|
||||||
|
was_minimized_(false),
|
||||||
|
hidden_(false),
|
||||||
|
exit_count_(0),
|
||||||
|
delete_files_(false)
|
||||||
{
|
{
|
||||||
|
|
||||||
qLog(Debug) << "Starting";
|
qLog(Debug) << "Starting";
|
||||||
@@ -309,9 +335,10 @@ MainWindow::MainWindow(Application *app, SystemTrayIcon *tray_icon, OSDBase *osd
|
|||||||
// Add tabs to the fancy tab widget
|
// Add tabs to the fancy tab widget
|
||||||
ui_->tabs->AddTab(context_view_, "context", IconLoader::Load("strawberry"), tr("Context"));
|
ui_->tabs->AddTab(context_view_, "context", IconLoader::Load("strawberry"), tr("Context"));
|
||||||
ui_->tabs->AddTab(collection_view_, "collection", IconLoader::Load("library-music"), tr("Collection"));
|
ui_->tabs->AddTab(collection_view_, "collection", IconLoader::Load("library-music"), tr("Collection"));
|
||||||
ui_->tabs->AddTab(file_view_, "files", IconLoader::Load("document-open"), tr("Files"));
|
|
||||||
ui_->tabs->AddTab(playlist_list_, "playlists", IconLoader::Load("view-media-playlist"), tr("Playlists"));
|
|
||||||
ui_->tabs->AddTab(queue_view_, "queue", IconLoader::Load("footsteps"), tr("Queue"));
|
ui_->tabs->AddTab(queue_view_, "queue", IconLoader::Load("footsteps"), tr("Queue"));
|
||||||
|
ui_->tabs->AddTab(playlist_list_, "playlists", IconLoader::Load("view-media-playlist"), tr("Playlists"));
|
||||||
|
ui_->tabs->AddTab(smartplaylists_view_, "smartplaylists", IconLoader::Load("view-media-playlist"), tr("Smart playlists"));
|
||||||
|
ui_->tabs->AddTab(file_view_, "files", IconLoader::Load("document-open"), tr("Files"));
|
||||||
#ifndef Q_OS_WIN
|
#ifndef Q_OS_WIN
|
||||||
ui_->tabs->AddTab(device_view_, "devices", IconLoader::Load("device"), tr("Devices"));
|
ui_->tabs->AddTab(device_view_, "devices", IconLoader::Load("device"), tr("Devices"));
|
||||||
#endif
|
#endif
|
||||||
@@ -321,6 +348,9 @@ MainWindow::MainWindow(Application *app, SystemTrayIcon *tray_icon, OSDBase *osd
|
|||||||
#ifdef HAVE_TIDAL
|
#ifdef HAVE_TIDAL
|
||||||
ui_->tabs->AddTab(tidal_view_, "tidal", IconLoader::Load("tidal"), tr("Tidal"));
|
ui_->tabs->AddTab(tidal_view_, "tidal", IconLoader::Load("tidal"), tr("Tidal"));
|
||||||
#endif
|
#endif
|
||||||
|
#ifdef HAVE_QOBUZ
|
||||||
|
ui_->tabs->AddTab(qobuz_view_, "qobuz", IconLoader::Load("qobuz"), tr("Qobuz"));
|
||||||
|
#endif
|
||||||
|
|
||||||
// Add the playing widget to the fancy tab widget
|
// Add the playing widget to the fancy tab widget
|
||||||
ui_->tabs->addBottomWidget(ui_->widget_playing);
|
ui_->tabs->addBottomWidget(ui_->widget_playing);
|
||||||
@@ -356,7 +386,7 @@ MainWindow::MainWindow(Application *app, SystemTrayIcon *tray_icon, OSDBase *osd
|
|||||||
|
|
||||||
ui_->playlist->SetManager(app_->playlist_manager());
|
ui_->playlist->SetManager(app_->playlist_manager());
|
||||||
|
|
||||||
ui_->playlist->view()->SetApplication(app_);
|
ui_->playlist->view()->Init(app_);
|
||||||
|
|
||||||
collection_view_->view()->setModel(collection_sort_model_);
|
collection_view_->view()->setModel(collection_sort_model_);
|
||||||
collection_view_->view()->SetApplication(app_);
|
collection_view_->view()->SetApplication(app_);
|
||||||
@@ -407,11 +437,14 @@ MainWindow::MainWindow(Application *app, SystemTrayIcon *tray_icon, OSDBase *osd
|
|||||||
|
|
||||||
ui_->action_cover_manager->setIcon(IconLoader::Load("document-download"));
|
ui_->action_cover_manager->setIcon(IconLoader::Load("document-download"));
|
||||||
ui_->action_edit_track->setIcon(IconLoader::Load("edit-rename"));
|
ui_->action_edit_track->setIcon(IconLoader::Load("edit-rename"));
|
||||||
|
ui_->action_edit_value->setIcon(IconLoader::Load("edit-rename"));
|
||||||
|
ui_->action_selection_set_value->setIcon(IconLoader::Load("edit-rename"));
|
||||||
ui_->action_equalizer->setIcon(IconLoader::Load("equalizer"));
|
ui_->action_equalizer->setIcon(IconLoader::Load("equalizer"));
|
||||||
ui_->action_transcoder->setIcon(IconLoader::Load("tools-wizard"));
|
ui_->action_transcoder->setIcon(IconLoader::Load("tools-wizard"));
|
||||||
ui_->action_update_collection->setIcon(IconLoader::Load("view-refresh"));
|
ui_->action_update_collection->setIcon(IconLoader::Load("view-refresh"));
|
||||||
ui_->action_full_collection_scan->setIcon(IconLoader::Load("view-refresh"));
|
ui_->action_full_collection_scan->setIcon(IconLoader::Load("view-refresh"));
|
||||||
ui_->action_settings->setIcon(IconLoader::Load("configure"));
|
ui_->action_settings->setIcon(IconLoader::Load("configure"));
|
||||||
|
ui_->action_import_data_from_last_fm->setIcon(IconLoader::Load("scrobble"));
|
||||||
|
|
||||||
// Scrobble
|
// Scrobble
|
||||||
|
|
||||||
@@ -452,6 +485,7 @@ MainWindow::MainWindow(Application *app, SystemTrayIcon *tray_icon, OSDBase *osd
|
|||||||
connect(ui_->action_auto_complete_tags, SIGNAL(triggered()), SLOT(AutoCompleteTags()));
|
connect(ui_->action_auto_complete_tags, SIGNAL(triggered()), SLOT(AutoCompleteTags()));
|
||||||
#endif
|
#endif
|
||||||
connect(ui_->action_settings, SIGNAL(triggered()), SLOT(OpenSettingsDialog()));
|
connect(ui_->action_settings, SIGNAL(triggered()), SLOT(OpenSettingsDialog()));
|
||||||
|
connect(ui_->action_import_data_from_last_fm, SIGNAL(triggered()), lastfm_import_dialog_, SLOT(show()));
|
||||||
connect(ui_->action_toggle_show_sidebar, SIGNAL(toggled(bool)), SLOT(ToggleSidebar(bool)));
|
connect(ui_->action_toggle_show_sidebar, SIGNAL(toggled(bool)), SLOT(ToggleSidebar(bool)));
|
||||||
connect(ui_->action_about_strawberry, SIGNAL(triggered()), SLOT(ShowAboutDialog()));
|
connect(ui_->action_about_strawberry, SIGNAL(triggered()), SLOT(ShowAboutDialog()));
|
||||||
connect(ui_->action_about_qt, SIGNAL(triggered()), qApp, SLOT(aboutQt()));
|
connect(ui_->action_about_qt, SIGNAL(triggered()), qApp, SLOT(aboutQt()));
|
||||||
@@ -462,7 +496,7 @@ MainWindow::MainWindow(Application *app, SystemTrayIcon *tray_icon, OSDBase *osd
|
|||||||
connect(ui_->action_add_folder, SIGNAL(triggered()), SLOT(AddFolder()));
|
connect(ui_->action_add_folder, SIGNAL(triggered()), SLOT(AddFolder()));
|
||||||
connect(ui_->action_add_stream, SIGNAL(triggered()), SLOT(AddStream()));
|
connect(ui_->action_add_stream, SIGNAL(triggered()), SLOT(AddStream()));
|
||||||
connect(ui_->action_cover_manager, SIGNAL(triggered()), SLOT(ShowCoverManager()));
|
connect(ui_->action_cover_manager, SIGNAL(triggered()), SLOT(ShowCoverManager()));
|
||||||
connect(ui_->action_equalizer, SIGNAL(triggered()), equalizer_.get(), SLOT(show()));
|
connect(ui_->action_equalizer, SIGNAL(triggered()), SLOT(ShowEqualizer()));
|
||||||
#if defined(HAVE_GSTREAMER)
|
#if defined(HAVE_GSTREAMER)
|
||||||
connect(ui_->action_transcoder, SIGNAL(triggered()), SLOT(ShowTranscodeDialog()));
|
connect(ui_->action_transcoder, SIGNAL(triggered()), SLOT(ShowTranscodeDialog()));
|
||||||
#else
|
#else
|
||||||
@@ -523,13 +557,13 @@ MainWindow::MainWindow(Application *app, SystemTrayIcon *tray_icon, OSDBase *osd
|
|||||||
connect(app_->player(), SIGNAL(Stopped()), SLOT(MediaStopped()));
|
connect(app_->player(), SIGNAL(Stopped()), SLOT(MediaStopped()));
|
||||||
connect(app_->player(), SIGNAL(Seeked(qlonglong)), SLOT(Seeked(qlonglong)));
|
connect(app_->player(), SIGNAL(Seeked(qlonglong)), SLOT(Seeked(qlonglong)));
|
||||||
connect(app_->player(), SIGNAL(TrackSkipped(PlaylistItemPtr)), SLOT(TrackSkipped(PlaylistItemPtr)));
|
connect(app_->player(), SIGNAL(TrackSkipped(PlaylistItemPtr)), SLOT(TrackSkipped(PlaylistItemPtr)));
|
||||||
connect(this, SIGNAL(IntroPointReached()), app_->player(), SLOT(IntroPointReached()));
|
|
||||||
connect(app_->player(), SIGNAL(VolumeChanged(int)), SLOT(VolumeChanged(int)));
|
connect(app_->player(), SIGNAL(VolumeChanged(int)), SLOT(VolumeChanged(int)));
|
||||||
|
|
||||||
connect(app_->player(), SIGNAL(Paused()), ui_->playlist, SLOT(ActivePaused()));
|
connect(app_->player(), SIGNAL(Paused()), ui_->playlist, SLOT(ActivePaused()));
|
||||||
connect(app_->player(), SIGNAL(Playing()), ui_->playlist, SLOT(ActivePlaying()));
|
connect(app_->player(), SIGNAL(Playing()), ui_->playlist, SLOT(ActivePlaying()));
|
||||||
connect(app_->player(), SIGNAL(Stopped()), ui_->playlist, SLOT(ActiveStopped()));
|
connect(app_->player(), SIGNAL(Stopped()), ui_->playlist, SLOT(ActiveStopped()));
|
||||||
|
|
||||||
|
connect(app_->playlist_manager(), SIGNAL(CurrentSongChanged(Song)), osd_, SLOT(SongChanged(Song)));
|
||||||
connect(app_->player(), SIGNAL(Paused()), osd_, SLOT(Paused()));
|
connect(app_->player(), SIGNAL(Paused()), osd_, SLOT(Paused()));
|
||||||
connect(app_->player(), SIGNAL(Resumed()), osd_, SLOT(Resumed()));
|
connect(app_->player(), SIGNAL(Resumed()), osd_, SLOT(Resumed()));
|
||||||
connect(app_->player(), SIGNAL(Stopped()), osd_, SLOT(Stopped()));
|
connect(app_->player(), SIGNAL(Stopped()), osd_, SLOT(Stopped()));
|
||||||
@@ -537,18 +571,17 @@ MainWindow::MainWindow(Application *app, SystemTrayIcon *tray_icon, OSDBase *osd
|
|||||||
connect(app_->player(), SIGNAL(VolumeChanged(int)), osd_, SLOT(VolumeChanged(int)));
|
connect(app_->player(), SIGNAL(VolumeChanged(int)), osd_, SLOT(VolumeChanged(int)));
|
||||||
connect(app_->player(), SIGNAL(VolumeChanged(int)), ui_->volume, SLOT(setValue(int)));
|
connect(app_->player(), SIGNAL(VolumeChanged(int)), ui_->volume, SLOT(setValue(int)));
|
||||||
connect(app_->player(), SIGNAL(ForceShowOSD(Song, bool)), SLOT(ForceShowOSD(Song, bool)));
|
connect(app_->player(), SIGNAL(ForceShowOSD(Song, bool)), SLOT(ForceShowOSD(Song, bool)));
|
||||||
connect(app_->player(), SIGNAL(SendNowPlaying()), SLOT(SendNowPlaying()));
|
|
||||||
|
|
||||||
connect(app_->playlist_manager(), SIGNAL(CurrentSongChanged(Song)), SLOT(SongChanged(Song)));
|
connect(app_->playlist_manager(), SIGNAL(CurrentSongChanged(Song)), SLOT(SongChanged(Song)));
|
||||||
connect(app_->playlist_manager(), SIGNAL(CurrentSongChanged(Song)), app_->player(), SLOT(CurrentMetadataChanged(Song)));
|
connect(app_->playlist_manager(), SIGNAL(CurrentSongChanged(Song)), app_->player(), SLOT(CurrentMetadataChanged(Song)));
|
||||||
connect(app_->playlist_manager(), SIGNAL(EditingFinished(QModelIndex)), SLOT(PlaylistEditFinished(QModelIndex)));
|
connect(app_->playlist_manager(), SIGNAL(EditingFinished(QModelIndex)), SLOT(PlaylistEditFinished(QModelIndex)));
|
||||||
connect(app_->playlist_manager(), SIGNAL(Error(QString)), SLOT(ShowErrorDialog(QString)));
|
connect(app_->playlist_manager(), SIGNAL(Error(QString)), SLOT(ShowErrorDialog(QString)));
|
||||||
connect(app_->playlist_manager(), SIGNAL(SummaryTextChanged(QString)), ui_->playlist_summary, SLOT(setText(QString)));
|
connect(app_->playlist_manager(), SIGNAL(SummaryTextChanged(QString)), ui_->playlist_summary, SLOT(setText(QString)));
|
||||||
connect(app_->playlist_manager(), SIGNAL(PlayRequested(QModelIndex)), SLOT(PlayIndex(QModelIndex)));
|
connect(app_->playlist_manager(), SIGNAL(PlayRequested(QModelIndex, Playlist::AutoScroll)), SLOT(PlayIndex(QModelIndex, Playlist::AutoScroll)));
|
||||||
|
|
||||||
connect(ui_->playlist->view(), SIGNAL(doubleClicked(QModelIndex)), SLOT(PlaylistDoubleClick(QModelIndex)));
|
connect(ui_->playlist->view(), SIGNAL(doubleClicked(QModelIndex)), SLOT(PlaylistDoubleClick(QModelIndex)));
|
||||||
connect(ui_->playlist->view(), SIGNAL(PlayItem(QModelIndex)), SLOT(PlayIndex(QModelIndex)));
|
connect(ui_->playlist->view(), SIGNAL(PlayItem(QModelIndex, Playlist::AutoScroll)), SLOT(PlayIndex(QModelIndex, Playlist::AutoScroll)));
|
||||||
connect(ui_->playlist->view(), SIGNAL(PlayPause()), app_->player(), SLOT(PlayPause()));
|
connect(ui_->playlist->view(), SIGNAL(PlayPause(Playlist::AutoScroll)), app_->player(), SLOT(PlayPause(Playlist::AutoScroll)));
|
||||||
connect(ui_->playlist->view(), SIGNAL(RightClicked(QPoint, QModelIndex)), SLOT(PlaylistRightClick(QPoint, QModelIndex)));
|
connect(ui_->playlist->view(), SIGNAL(RightClicked(QPoint, QModelIndex)), SLOT(PlaylistRightClick(QPoint, QModelIndex)));
|
||||||
connect(ui_->playlist->view(), SIGNAL(SeekForward()), app_->player(), SLOT(SeekForward()));
|
connect(ui_->playlist->view(), SIGNAL(SeekForward()), app_->player(), SLOT(SeekForward()));
|
||||||
connect(ui_->playlist->view(), SIGNAL(SeekBackward()), app_->player(), SLOT(SeekBackward()));
|
connect(ui_->playlist->view(), SIGNAL(SeekBackward()), app_->player(), SLOT(SeekBackward()));
|
||||||
@@ -604,7 +637,7 @@ MainWindow::MainWindow(Application *app, SystemTrayIcon *tray_icon, OSDBase *osd
|
|||||||
|
|
||||||
QAction *collection_config_action = new QAction(IconLoader::Load("configure"), tr("Configure collection..."), this);
|
QAction *collection_config_action = new QAction(IconLoader::Load("configure"), tr("Configure collection..."), this);
|
||||||
connect(collection_config_action, SIGNAL(triggered()), SLOT(ShowCollectionConfig()));
|
connect(collection_config_action, SIGNAL(triggered()), SLOT(ShowCollectionConfig()));
|
||||||
collection_view_->filter()->SetSettingsGroup(kSettingsGroup);
|
collection_view_->filter()->SetSettingsGroup(CollectionSettingsPage::kSettingsGroup);
|
||||||
collection_view_->filter()->SetCollectionModel(app_->collection()->model());
|
collection_view_->filter()->SetCollectionModel(app_->collection()->model());
|
||||||
|
|
||||||
QAction *separator = new QAction(this);
|
QAction *separator = new QAction(this);
|
||||||
@@ -629,6 +662,13 @@ MainWindow::MainWindow(Application *app, SystemTrayIcon *tray_icon, OSDBase *osd
|
|||||||
connect(this, SIGNAL(AuthorizationUrlReceived(QUrl)), tidalservice, SLOT(AuthorizationUrlReceived(QUrl)));
|
connect(this, SIGNAL(AuthorizationUrlReceived(QUrl)), tidalservice, SLOT(AuthorizationUrlReceived(QUrl)));
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
#ifdef HAVE_QOBUZ
|
||||||
|
connect(qobuz_view_->artists_collection_view(), SIGNAL(AddToPlaylistSignal(QMimeData*)), SLOT(AddToPlaylist(QMimeData*)));
|
||||||
|
connect(qobuz_view_->albums_collection_view(), SIGNAL(AddToPlaylistSignal(QMimeData*)), SLOT(AddToPlaylist(QMimeData*)));
|
||||||
|
connect(qobuz_view_->songs_collection_view(), SIGNAL(AddToPlaylistSignal(QMimeData*)), SLOT(AddToPlaylist(QMimeData*)));
|
||||||
|
connect(qobuz_view_->search_view(), SIGNAL(AddToPlaylist(QMimeData*)), SLOT(AddToPlaylist(QMimeData*)));
|
||||||
|
#endif
|
||||||
|
|
||||||
// Playlist menu
|
// Playlist menu
|
||||||
connect(playlist_menu_, SIGNAL(aboutToHide()), SLOT(PlaylistMenuHidden()));
|
connect(playlist_menu_, SIGNAL(aboutToHide()), SLOT(PlaylistMenuHidden()));
|
||||||
playlist_play_pause_ = playlist_menu_->addAction(tr("Play"), this, SLOT(PlaylistPlay()));
|
playlist_play_pause_ = playlist_menu_->addAction(tr("Play"), this, SLOT(PlaylistPlay()));
|
||||||
@@ -659,16 +699,16 @@ MainWindow::MainWindow(Application *app, SystemTrayIcon *tray_icon, OSDBase *osd
|
|||||||
playlist_menu_->addAction(ui_->action_add_files_to_transcoder);
|
playlist_menu_->addAction(ui_->action_add_files_to_transcoder);
|
||||||
#endif
|
#endif
|
||||||
playlist_menu_->addSeparator();
|
playlist_menu_->addSeparator();
|
||||||
|
playlist_copy_url_ = playlist_menu_->addAction(IconLoader::Load("edit-copy"), tr("Copy URL(s)..."), this, SLOT(PlaylistCopyUrl()));
|
||||||
|
playlist_show_in_collection_ = playlist_menu_->addAction(IconLoader::Load("edit-find"), tr("Show in collection..."), this, SLOT(ShowInCollection()));
|
||||||
|
playlist_open_in_browser_ = playlist_menu_->addAction(IconLoader::Load("document-open-folder"), tr("Show in file browser..."), this, SLOT(PlaylistOpenInBrowser()));
|
||||||
|
playlist_organize_ = playlist_menu_->addAction(IconLoader::Load("edit-copy"), tr("Organize files..."), this, SLOT(PlaylistMoveToCollection()));
|
||||||
|
playlist_copy_to_collection_ = playlist_menu_->addAction(IconLoader::Load("edit-copy"), tr("Copy to collection..."), this, SLOT(PlaylistCopyToCollection()));
|
||||||
|
playlist_move_to_collection_ = playlist_menu_->addAction(IconLoader::Load("go-jump"), tr("Move to collection..."), this, SLOT(PlaylistMoveToCollection()));
|
||||||
#if defined(HAVE_GSTREAMER) && !defined(Q_OS_WIN)
|
#if defined(HAVE_GSTREAMER) && !defined(Q_OS_WIN)
|
||||||
playlist_copy_to_device_ = playlist_menu_->addAction(IconLoader::Load("device"), tr("Copy to device..."), this, SLOT(PlaylistCopyToDevice()));
|
playlist_copy_to_device_ = playlist_menu_->addAction(IconLoader::Load("device"), tr("Copy to device..."), this, SLOT(PlaylistCopyToDevice()));
|
||||||
#endif
|
#endif
|
||||||
playlist_copy_to_collection_ = playlist_menu_->addAction(IconLoader::Load("edit-copy"), tr("Copy to collection..."), this, SLOT(PlaylistCopyToCollection()));
|
playlist_delete_ = playlist_menu_->addAction(IconLoader::Load("edit-delete"), tr("Delete from disk..."), this, SLOT(PlaylistDelete()));
|
||||||
playlist_move_to_collection_ = playlist_menu_->addAction(IconLoader::Load("go-jump"), tr("Move to collection..."), this, SLOT(PlaylistMoveToCollection()));
|
|
||||||
playlist_organize_ = playlist_menu_->addAction(IconLoader::Load("edit-copy"), tr("Organize files..."), this, SLOT(PlaylistMoveToCollection()));
|
|
||||||
playlist_open_in_browser_ = playlist_menu_->addAction(IconLoader::Load("document-open-folder"), tr("Show in file browser..."), this, SLOT(PlaylistOpenInBrowser()));
|
|
||||||
playlist_open_in_browser_->setVisible(false);
|
|
||||||
playlist_show_in_collection_ = playlist_menu_->addAction(IconLoader::Load("edit-find"), tr("Show in collection..."), this, SLOT(ShowInCollection()));
|
|
||||||
playlist_copy_url_ = playlist_menu_->addAction(IconLoader::Load("edit-copy"), tr("Copy URL(s)..."), this, SLOT(PlaylistCopyUrl()));
|
|
||||||
playlist_menu_->addSeparator();
|
playlist_menu_->addSeparator();
|
||||||
playlistitem_actions_separator_ = playlist_menu_->addSeparator();
|
playlistitem_actions_separator_ = playlist_menu_->addSeparator();
|
||||||
playlist_menu_->addAction(ui_->action_clear_playlist);
|
playlist_menu_->addAction(ui_->action_clear_playlist);
|
||||||
@@ -814,10 +854,19 @@ MainWindow::MainWindow(Application *app, SystemTrayIcon *tray_icon, OSDBase *osd
|
|||||||
connect(app_->playlist_manager()->sequence(), SIGNAL(RepeatModeChanged(PlaylistSequence::RepeatMode)), osd_, SLOT(RepeatModeChanged(PlaylistSequence::RepeatMode)));
|
connect(app_->playlist_manager()->sequence(), SIGNAL(RepeatModeChanged(PlaylistSequence::RepeatMode)), osd_, SLOT(RepeatModeChanged(PlaylistSequence::RepeatMode)));
|
||||||
connect(app_->playlist_manager()->sequence(), SIGNAL(ShuffleModeChanged(PlaylistSequence::ShuffleMode)), osd_, SLOT(ShuffleModeChanged(PlaylistSequence::ShuffleMode)));
|
connect(app_->playlist_manager()->sequence(), SIGNAL(ShuffleModeChanged(PlaylistSequence::ShuffleMode)), osd_, SLOT(ShuffleModeChanged(PlaylistSequence::ShuffleMode)));
|
||||||
|
|
||||||
|
// Smart playlists
|
||||||
|
connect(smartplaylists_view_, SIGNAL(AddToPlaylist(QMimeData*)), SLOT(AddToPlaylist(QMimeData*)));
|
||||||
|
|
||||||
ScrobbleButtonVisibilityChanged(app_->scrobbler()->ScrobbleButton());
|
ScrobbleButtonVisibilityChanged(app_->scrobbler()->ScrobbleButton());
|
||||||
LoveButtonVisibilityChanged(app_->scrobbler()->LoveButton());
|
LoveButtonVisibilityChanged(app_->scrobbler()->LoveButton());
|
||||||
ScrobblingEnabledChanged(app_->scrobbler()->IsEnabled());
|
ScrobblingEnabledChanged(app_->scrobbler()->IsEnabled());
|
||||||
|
|
||||||
|
// Last.fm ImportData
|
||||||
|
connect(app_->lastfm_import(), SIGNAL(Finished()), lastfm_import_dialog_, SLOT(Finished()));
|
||||||
|
connect(app_->lastfm_import(), SIGNAL(FinishedWithError(QString)), lastfm_import_dialog_, SLOT(FinishedWithError(QString)));
|
||||||
|
connect(app_->lastfm_import(), SIGNAL(UpdateTotal(int, int)), lastfm_import_dialog_, SLOT(UpdateTotal(int, int)));
|
||||||
|
connect(app_->lastfm_import(), SIGNAL(UpdateProgress(int, int)), lastfm_import_dialog_, SLOT(UpdateProgress(int, int)));
|
||||||
|
|
||||||
// Load settings
|
// Load settings
|
||||||
qLog(Debug) << "Loading settings";
|
qLog(Debug) << "Loading settings";
|
||||||
settings_.beginGroup(kSettingsGroup);
|
settings_.beginGroup(kSettingsGroup);
|
||||||
@@ -828,8 +877,8 @@ MainWindow::MainWindow(Application *app, SystemTrayIcon *tray_icon, OSDBase *osd
|
|||||||
restoreGeometry(settings_.value("geometry").toByteArray());
|
restoreGeometry(settings_.value("geometry").toByteArray());
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!ui_->splitter->restoreState(settings_.value("splitter_state").toByteArray())) {
|
if (!settings_.contains("splitter_state") || !ui_->splitter->restoreState(settings_.value("splitter_state").toByteArray())) {
|
||||||
ui_->splitter->setSizes(QList<int>() << 250 << width() - 250);
|
ui_->splitter->setSizes(QList<int>() << 20 << (width() - 20));
|
||||||
}
|
}
|
||||||
|
|
||||||
ui_->tabs->setCurrentIndex(settings_.value("current_tab", 1).toInt());
|
ui_->tabs->setCurrentIndex(settings_.value("current_tab", 1).toInt());
|
||||||
@@ -889,11 +938,13 @@ MainWindow::MainWindow(Application *app, SystemTrayIcon *tray_icon, OSDBase *osd
|
|||||||
if (was_minimized_) setWindowState(windowState() | Qt::WindowMinimized);
|
if (was_minimized_) setWindowState(windowState() | Qt::WindowMinimized);
|
||||||
|
|
||||||
if (!QSystemTrayIcon::isSystemTrayAvailable() || !tray_icon_ || !tray_icon_->IsVisible()) {
|
if (!QSystemTrayIcon::isSystemTrayAvailable() || !tray_icon_ || !tray_icon_->IsVisible()) {
|
||||||
|
hidden_ = false;
|
||||||
settings_.setValue("hidden", false);
|
settings_.setValue("hidden", false);
|
||||||
show();
|
show();
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
setVisible(!settings_.value("hidden", false).toBool());
|
hidden_ = settings_.value("hidden", false).toBool();
|
||||||
|
setVisible(!hidden_);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@@ -905,7 +956,7 @@ MainWindow::MainWindow(Application *app, SystemTrayIcon *tray_icon, OSDBase *osd
|
|||||||
ui_->action_toggle_show_sidebar->setChecked(show_sidebar);
|
ui_->action_toggle_show_sidebar->setChecked(show_sidebar);
|
||||||
|
|
||||||
QShortcut *close_window_shortcut = new QShortcut(this);
|
QShortcut *close_window_shortcut = new QShortcut(this);
|
||||||
close_window_shortcut->setKey(Qt::CTRL + Qt::Key_W);
|
close_window_shortcut->setKey(Qt::CTRL | Qt::Key_W);
|
||||||
connect(close_window_shortcut, SIGNAL(activated()), SLOT(SetHiddenInTray()));
|
connect(close_window_shortcut, SIGNAL(activated()), SLOT(SetHiddenInTray()));
|
||||||
|
|
||||||
CheckFullRescanRevisions();
|
CheckFullRescanRevisions();
|
||||||
@@ -957,12 +1008,13 @@ void MainWindow::ReloadSettings() {
|
|||||||
#endif
|
#endif
|
||||||
|
|
||||||
s.beginGroup(BehaviourSettingsPage::kSettingsGroup);
|
s.beginGroup(BehaviourSettingsPage::kSettingsGroup);
|
||||||
|
keep_running_ = s.value("keeprunning", false).toBool();
|
||||||
playing_widget_ = s.value("playing_widget", true).toBool();
|
playing_widget_ = s.value("playing_widget", true).toBool();
|
||||||
if (playing_widget_ != ui_->widget_playing->IsEnabled()) TabSwitched();
|
if (playing_widget_ != ui_->widget_playing->IsEnabled()) TabSwitched();
|
||||||
doubleclick_addmode_ = BehaviourSettingsPage::AddBehaviour(s.value("doubleclick_addmode", BehaviourSettingsPage::AddBehaviour_Append).toInt());
|
doubleclick_addmode_ = BehaviourSettingsPage::AddBehaviour(s.value("doubleclick_addmode", BehaviourSettingsPage::AddBehaviour_Append).toInt());
|
||||||
doubleclick_playmode_ = BehaviourSettingsPage::PlayBehaviour(s.value("doubleclick_playmode", BehaviourSettingsPage::PlayBehaviour_IfStopped).toInt());
|
doubleclick_playmode_ = BehaviourSettingsPage::PlayBehaviour(s.value("doubleclick_playmode", BehaviourSettingsPage::PlayBehaviour_Never).toInt());
|
||||||
doubleclick_playlist_addmode_ = BehaviourSettingsPage::PlaylistAddBehaviour(s.value("doubleclick_playlist_addmode", BehaviourSettingsPage::PlaylistAddBehaviour_Play).toInt());
|
doubleclick_playlist_addmode_ = BehaviourSettingsPage::PlaylistAddBehaviour(s.value("doubleclick_playlist_addmode", BehaviourSettingsPage::PlayBehaviour_Never).toInt());
|
||||||
menu_playmode_ = BehaviourSettingsPage::PlayBehaviour(s.value("menu_playmode", BehaviourSettingsPage::PlayBehaviour_IfStopped).toInt());
|
menu_playmode_ = BehaviourSettingsPage::PlayBehaviour(s.value("menu_playmode", BehaviourSettingsPage::PlayBehaviour_Never).toInt());
|
||||||
s.endGroup();
|
s.endGroup();
|
||||||
|
|
||||||
s.beginGroup(AppearanceSettingsPage::kSettingsGroup);
|
s.beginGroup(AppearanceSettingsPage::kSettingsGroup);
|
||||||
@@ -990,6 +1042,10 @@ void MainWindow::ReloadSettings() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
s.beginGroup(PlaylistSettingsPage::kSettingsGroup);
|
||||||
|
delete_files_ = s.value("delete_files", false).toBool();
|
||||||
|
s.endGroup();
|
||||||
|
|
||||||
osd_->ReloadSettings();
|
osd_->ReloadSettings();
|
||||||
|
|
||||||
album_cover_choice_controller_->search_cover_auto_action()->setChecked(settings_.value("search_for_cover_auto", true).toBool());
|
album_cover_choice_controller_->search_cover_auto_action()->setChecked(settings_.value("search_for_cover_auto", true).toBool());
|
||||||
@@ -1002,6 +1058,7 @@ void MainWindow::ReloadSettings() {
|
|||||||
ui_->tabs->EnableTab(subsonic_view_);
|
ui_->tabs->EnableTab(subsonic_view_);
|
||||||
else
|
else
|
||||||
ui_->tabs->DisableTab(subsonic_view_);
|
ui_->tabs->DisableTab(subsonic_view_);
|
||||||
|
app_->scrobbler()->Service<SubsonicScrobbler>()->ReloadSettings();
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#ifdef HAVE_TIDAL
|
#ifdef HAVE_TIDAL
|
||||||
@@ -1014,6 +1071,16 @@ void MainWindow::ReloadSettings() {
|
|||||||
ui_->tabs->DisableTab(tidal_view_);
|
ui_->tabs->DisableTab(tidal_view_);
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
#ifdef HAVE_QOBUZ
|
||||||
|
s.beginGroup(QobuzSettingsPage::kSettingsGroup);
|
||||||
|
bool enable_qobuz = s.value("enabled", false).toBool();
|
||||||
|
s.endGroup();
|
||||||
|
if (enable_qobuz)
|
||||||
|
ui_->tabs->EnableTab(qobuz_view_);
|
||||||
|
else
|
||||||
|
ui_->tabs->DisableTab(qobuz_view_);
|
||||||
|
#endif
|
||||||
|
|
||||||
ui_->tabs->ReloadSettings();
|
ui_->tabs->ReloadSettings();
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -1036,6 +1103,7 @@ void MainWindow::ReloadAllSettings() {
|
|||||||
file_view_->ReloadSettings();
|
file_view_->ReloadSettings();
|
||||||
queue_view_->ReloadSettings();
|
queue_view_->ReloadSettings();
|
||||||
playlist_list_->ReloadSettings();
|
playlist_list_->ReloadSettings();
|
||||||
|
smartplaylists_view_->ReloadSettings();
|
||||||
app_->cover_providers()->ReloadSettings();
|
app_->cover_providers()->ReloadSettings();
|
||||||
app_->lyrics_providers()->ReloadSettings();
|
app_->lyrics_providers()->ReloadSettings();
|
||||||
#ifdef HAVE_SUBSONIC
|
#ifdef HAVE_SUBSONIC
|
||||||
@@ -1044,6 +1112,9 @@ void MainWindow::ReloadAllSettings() {
|
|||||||
#ifdef HAVE_TIDAL
|
#ifdef HAVE_TIDAL
|
||||||
tidal_view_->ReloadSettings();
|
tidal_view_->ReloadSettings();
|
||||||
#endif
|
#endif
|
||||||
|
#ifdef HAVE_QOBUZ
|
||||||
|
qobuz_view_->ReloadSettings();
|
||||||
|
#endif
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1058,7 +1129,6 @@ void MainWindow::SaveSettings() {
|
|||||||
SaveGeometry();
|
SaveGeometry();
|
||||||
SavePlaybackStatus();
|
SavePlaybackStatus();
|
||||||
ui_->tabs->SaveSettings(kSettingsGroup);
|
ui_->tabs->SaveSettings(kSettingsGroup);
|
||||||
ui_->playlist->view()->SaveGeometry();
|
|
||||||
ui_->playlist->view()->SaveSettings();
|
ui_->playlist->view()->SaveSettings();
|
||||||
app_->scrobbler()->WriteCache();
|
app_->scrobbler()->WriteCache();
|
||||||
|
|
||||||
@@ -1073,6 +1143,9 @@ void MainWindow::Exit() {
|
|||||||
|
|
||||||
SaveSettings();
|
SaveSettings();
|
||||||
|
|
||||||
|
// Make sure Settings dialog is destroyed first.
|
||||||
|
settings_dialog_.reset();
|
||||||
|
|
||||||
if (exit_count_ > 1) {
|
if (exit_count_ > 1) {
|
||||||
qApp->quit();
|
qApp->quit();
|
||||||
}
|
}
|
||||||
@@ -1188,20 +1261,15 @@ void MainWindow::MediaPlaying() {
|
|||||||
track_position_timer_->start();
|
track_position_timer_->start();
|
||||||
track_slider_timer_->start();
|
track_slider_timer_->start();
|
||||||
UpdateTrackPosition();
|
UpdateTrackPosition();
|
||||||
SendNowPlaying();
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void MainWindow::SendNowPlaying() {
|
void MainWindow::SendNowPlaying() {
|
||||||
|
|
||||||
PlaylistItemPtr item(app_->player()->GetCurrentItem());
|
|
||||||
if (!item) return;
|
|
||||||
|
|
||||||
// Send now playing to scrobble services
|
// Send now playing to scrobble services
|
||||||
Playlist *playlist = app_->playlist_manager()->active();
|
Playlist *playlist = app_->playlist_manager()->active();
|
||||||
if (app_->scrobbler()->IsEnabled() && playlist && !playlist->nowplaying() && item->Metadata().is_metadata_good()) {
|
if (app_->scrobbler()->IsEnabled() && playlist && playlist->current_item() && playlist->current_item()->Metadata().is_metadata_good()) {
|
||||||
app_->scrobbler()->UpdateNowPlaying(item->Metadata());
|
app_->scrobbler()->UpdateNowPlaying(playlist->current_item()->Metadata());
|
||||||
playlist->set_nowplaying(true);
|
|
||||||
ui_->action_love->setEnabled(true);
|
ui_->action_love->setEnabled(true);
|
||||||
ui_->button_love->setEnabled(true);
|
ui_->button_love->setEnabled(true);
|
||||||
if (tray_icon_) tray_icon_->LoveStateChanged(true);
|
if (tray_icon_) tray_icon_->LoveStateChanged(true);
|
||||||
@@ -1216,11 +1284,15 @@ void MainWindow::VolumeChanged(const int volume) {
|
|||||||
|
|
||||||
void MainWindow::SongChanged(const Song &song) {
|
void MainWindow::SongChanged(const Song &song) {
|
||||||
|
|
||||||
|
qLog(Debug) << "Song changed to" << song.artist() << song.album() << song.title();
|
||||||
|
|
||||||
song_playing_ = song;
|
song_playing_ = song;
|
||||||
song_ = song;
|
song_ = song;
|
||||||
setWindowTitle(song.PrettyTitleWithArtist());
|
setWindowTitle(song.PrettyTitleWithArtist());
|
||||||
if (tray_icon_) tray_icon_->SetProgress(0);
|
if (tray_icon_) tray_icon_->SetProgress(0);
|
||||||
|
|
||||||
|
SendNowPlaying();
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void MainWindow::TrackSkipped(PlaylistItemPtr item) {
|
void MainWindow::TrackSkipped(PlaylistItemPtr item) {
|
||||||
@@ -1273,6 +1345,7 @@ void MainWindow::SaveGeometry() {
|
|||||||
|
|
||||||
settings_.setValue("maximized", isMaximized());
|
settings_.setValue("maximized", isMaximized());
|
||||||
settings_.setValue("minimized", isMinimized());
|
settings_.setValue("minimized", isMinimized());
|
||||||
|
settings_.setValue("hidden", hidden_);
|
||||||
settings_.setValue("geometry", saveGeometry());
|
settings_.setValue("geometry", saveGeometry());
|
||||||
settings_.setValue("splitter_state", ui_->splitter->saveState());
|
settings_.setValue("splitter_state", ui_->splitter->saveState());
|
||||||
|
|
||||||
@@ -1356,41 +1429,41 @@ void MainWindow::ResumePlaybackSeek(const int playback_position) {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void MainWindow::PlayIndex(const QModelIndex &index) {
|
void MainWindow::PlayIndex(const QModelIndex &idx, Playlist::AutoScroll autoscroll) {
|
||||||
|
|
||||||
if (!index.isValid()) return;
|
if (!idx.isValid()) return;
|
||||||
|
|
||||||
int row = index.row();
|
int row = idx.row();
|
||||||
if (index.model() == app_->playlist_manager()->current()->proxy()) {
|
if (idx.model() == app_->playlist_manager()->current()->proxy()) {
|
||||||
// The index was in the proxy model (might've been filtered), so we need to get the actual row in the source model.
|
// The index was in the proxy model (might've been filtered), so we need to get the actual row in the source model.
|
||||||
row = app_->playlist_manager()->current()->proxy()->mapToSource(index).row();
|
row = app_->playlist_manager()->current()->proxy()->mapToSource(idx).row();
|
||||||
}
|
}
|
||||||
|
|
||||||
app_->playlist_manager()->SetActiveToCurrent();
|
app_->playlist_manager()->SetActiveToCurrent();
|
||||||
app_->player()->PlayAt(row, Engine::Manual, true);
|
app_->player()->PlayAt(row, Engine::Manual, autoscroll, true);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void MainWindow::PlaylistDoubleClick(const QModelIndex &index) {
|
void MainWindow::PlaylistDoubleClick(const QModelIndex &idx) {
|
||||||
|
|
||||||
if (!index.isValid()) return;
|
if (!idx.isValid()) return;
|
||||||
|
|
||||||
int row = index.row();
|
int row = idx.row();
|
||||||
if (index.model() == app_->playlist_manager()->current()->proxy()) {
|
if (idx.model() == app_->playlist_manager()->current()->proxy()) {
|
||||||
// The index was in the proxy model (might've been filtered), so we need to get the actual row in the source model.
|
// The index was in the proxy model (might've been filtered), so we need to get the actual row in the source model.
|
||||||
row = app_->playlist_manager()->current()->proxy()->mapToSource(index).row();
|
row = app_->playlist_manager()->current()->proxy()->mapToSource(idx).row();
|
||||||
}
|
}
|
||||||
|
|
||||||
switch (doubleclick_playlist_addmode_) {
|
switch (doubleclick_playlist_addmode_) {
|
||||||
case BehaviourSettingsPage::PlaylistAddBehaviour_Play:
|
case BehaviourSettingsPage::PlaylistAddBehaviour_Play:
|
||||||
app_->playlist_manager()->SetActiveToCurrent();
|
app_->playlist_manager()->SetActiveToCurrent();
|
||||||
app_->player()->PlayAt(row, Engine::Manual, true);
|
app_->player()->PlayAt(row, Engine::Manual, Playlist::AutoScroll_Never, true, true);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case BehaviourSettingsPage::PlaylistAddBehaviour_Enqueue:
|
case BehaviourSettingsPage::PlaylistAddBehaviour_Enqueue:
|
||||||
app_->playlist_manager()->current()->queue()->ToggleTracks(QModelIndexList() << index);
|
app_->playlist_manager()->current()->queue()->ToggleTracks(QModelIndexList() << idx);
|
||||||
if (app_->player()->GetState() != Engine::Playing) {
|
if (app_->player()->GetState() != Engine::Playing) {
|
||||||
app_->player()->PlayAt(app_->playlist_manager()->current()->queue()->TakeNext(), Engine::Manual, true);
|
app_->player()->PlayAt(app_->playlist_manager()->current()->queue()->TakeNext(), Engine::Manual, Playlist::AutoScroll_Never, true);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@@ -1403,7 +1476,7 @@ void MainWindow::VolumeWheelEvent(const int delta) {
|
|||||||
|
|
||||||
void MainWindow::ToggleShowHide() {
|
void MainWindow::ToggleShowHide() {
|
||||||
|
|
||||||
if (settings_.value("hidden").toBool()) {
|
if (hidden_) {
|
||||||
show();
|
show();
|
||||||
SetHiddenInTray(false);
|
SetHiddenInTray(false);
|
||||||
}
|
}
|
||||||
@@ -1434,15 +1507,18 @@ void MainWindow::StopAfterCurrent() {
|
|||||||
emit StopAfterToggled(app_->playlist_manager()->active()->stop_after_current());
|
emit StopAfterToggled(app_->playlist_manager()->active()->stop_after_current());
|
||||||
}
|
}
|
||||||
|
|
||||||
void MainWindow::closeEvent(QCloseEvent *event) {
|
void MainWindow::showEvent(QShowEvent *e) {
|
||||||
|
|
||||||
QSettings settings;
|
hidden_ = false;
|
||||||
settings.beginGroup(BehaviourSettingsPage::kSettingsGroup);
|
|
||||||
bool keep_running = settings.value("keeprunning", false).toBool();
|
|
||||||
settings.endGroup();
|
|
||||||
|
|
||||||
if (keep_running && event->spontaneous() && QSystemTrayIcon::isSystemTrayAvailable()) {
|
QMainWindow::showEvent(e);
|
||||||
event->ignore();
|
|
||||||
|
}
|
||||||
|
|
||||||
|
void MainWindow::closeEvent(QCloseEvent *e) {
|
||||||
|
|
||||||
|
if (!hidden_ && keep_running_ && e->spontaneous() && QSystemTrayIcon::isSystemTrayAvailable()) {
|
||||||
|
e->ignore();
|
||||||
SetHiddenInTray(true);
|
SetHiddenInTray(true);
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
@@ -1453,7 +1529,7 @@ void MainWindow::closeEvent(QCloseEvent *event) {
|
|||||||
|
|
||||||
void MainWindow::SetHiddenInTray(const bool hidden) {
|
void MainWindow::SetHiddenInTray(const bool hidden) {
|
||||||
|
|
||||||
settings_.setValue("hidden", hidden);
|
hidden_ = hidden;
|
||||||
|
|
||||||
// Some window managers don't remember maximized state between calls to hide() and show(), so we have to remember it ourself.
|
// Some window managers don't remember maximized state between calls to hide() and show(), so we have to remember it ourself.
|
||||||
if (hidden) {
|
if (hidden) {
|
||||||
@@ -1473,7 +1549,7 @@ void MainWindow::FilePathChanged(const QString &path) {
|
|||||||
settings_.setValue("file_path", path);
|
settings_.setValue("file_path", path);
|
||||||
}
|
}
|
||||||
|
|
||||||
void MainWindow::Seeked(qlonglong microseconds) {
|
void MainWindow::Seeked(const qlonglong microseconds) {
|
||||||
|
|
||||||
const int position = microseconds / kUsecPerSec;
|
const int position = microseconds / kUsecPerSec;
|
||||||
const int length = app_->player()->GetCurrentItem()->Metadata().length_nanosec() / kNsecPerSec;
|
const int length = app_->player()->GetCurrentItem()->Metadata().length_nanosec() / kNsecPerSec;
|
||||||
@@ -1496,7 +1572,7 @@ void MainWindow::UpdateTrackPosition() {
|
|||||||
// Send Scrobble
|
// Send Scrobble
|
||||||
if (app_->scrobbler()->IsEnabled() && item->Metadata().is_metadata_good()) {
|
if (app_->scrobbler()->IsEnabled() && item->Metadata().is_metadata_good()) {
|
||||||
Playlist *playlist = app_->playlist_manager()->active();
|
Playlist *playlist = app_->playlist_manager()->active();
|
||||||
if (playlist && playlist->nowplaying() && !playlist->scrobbled()) {
|
if (playlist && !playlist->scrobbled()) {
|
||||||
const int scrobble_point = (playlist->scrobble_point_nanosec() / kNsecPerSec);
|
const int scrobble_point = (playlist->scrobble_point_nanosec() / kNsecPerSec);
|
||||||
if (position >= scrobble_point) {
|
if (position >= scrobble_point) {
|
||||||
app_->scrobbler()->Scrobble(item->Metadata(), scrobble_point);
|
app_->scrobbler()->Scrobble(item->Metadata(), scrobble_point);
|
||||||
@@ -1519,7 +1595,7 @@ void MainWindow::UpdateTrackSliderPosition() {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void MainWindow::ApplyAddBehaviour(BehaviourSettingsPage::AddBehaviour b, MimeData *mimedata) const {
|
void MainWindow::ApplyAddBehaviour(const BehaviourSettingsPage::AddBehaviour b, MimeData *mimedata) const {
|
||||||
|
|
||||||
switch (b) {
|
switch (b) {
|
||||||
case BehaviourSettingsPage::AddBehaviour_Append:
|
case BehaviourSettingsPage::AddBehaviour_Append:
|
||||||
@@ -1543,7 +1619,7 @@ void MainWindow::ApplyAddBehaviour(BehaviourSettingsPage::AddBehaviour b, MimeDa
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void MainWindow::ApplyPlayBehaviour(BehaviourSettingsPage::PlayBehaviour b, MimeData *mimedata) const {
|
void MainWindow::ApplyPlayBehaviour(const BehaviourSettingsPage::PlayBehaviour b, MimeData *mimedata) const {
|
||||||
|
|
||||||
switch (b) {
|
switch (b) {
|
||||||
case BehaviourSettingsPage::PlayBehaviour_Always:
|
case BehaviourSettingsPage::PlayBehaviour_Always:
|
||||||
@@ -1733,6 +1809,7 @@ void MainWindow::PlaylistRightClick(const QPoint &global_pos, const QModelIndex
|
|||||||
playlist_copy_to_device_->setVisible(false);
|
playlist_copy_to_device_->setVisible(false);
|
||||||
#endif
|
#endif
|
||||||
playlist_organize_->setVisible(false);
|
playlist_organize_->setVisible(false);
|
||||||
|
playlist_delete_->setVisible(false);
|
||||||
|
|
||||||
playlist_copy_url_->setVisible(selected > 0);
|
playlist_copy_url_->setVisible(selected > 0);
|
||||||
|
|
||||||
@@ -1805,6 +1882,10 @@ void MainWindow::PlaylistRightClick(const QPoint &global_pos, const QModelIndex
|
|||||||
playlist_copy_to_device_->setVisible(editable > 0);
|
playlist_copy_to_device_->setVisible(editable > 0);
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
#if QT_VERSION >= QT_VERSION_CHECK(5, 15, 0)
|
||||||
|
playlist_delete_->setVisible(delete_files_ && editable > 0);
|
||||||
|
#endif
|
||||||
|
|
||||||
// Remove old item actions, if any.
|
// Remove old item actions, if any.
|
||||||
for (QAction *action : playlistitem_actions_) {
|
for (QAction *action : playlistitem_actions_) {
|
||||||
playlist_menu_->removeAction(action);
|
playlist_menu_->removeAction(action);
|
||||||
@@ -1857,10 +1938,10 @@ void MainWindow::PlaylistRightClick(const QPoint &global_pos, const QModelIndex
|
|||||||
void MainWindow::PlaylistPlay() {
|
void MainWindow::PlaylistPlay() {
|
||||||
|
|
||||||
if (app_->playlist_manager()->current()->current_row() == playlist_menu_index_.row()) {
|
if (app_->playlist_manager()->current()->current_row() == playlist_menu_index_.row()) {
|
||||||
app_->player()->PlayPause();
|
app_->player()->PlayPause(Playlist::AutoScroll_Never);
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
PlayIndex(playlist_menu_index_);
|
PlayIndex(playlist_menu_index_, Playlist::AutoScroll_Never);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -1913,6 +1994,7 @@ void MainWindow::EditTracks() {
|
|||||||
|
|
||||||
edit_tag_dialog_->SetSongs(songs, items);
|
edit_tag_dialog_->SetSongs(songs, items);
|
||||||
edit_tag_dialog_->show();
|
edit_tag_dialog_->show();
|
||||||
|
edit_tag_dialog_->raise();
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1959,10 +2041,10 @@ void MainWindow::RenumberTracks() {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void MainWindow::SongSaveComplete(TagReaderReply *reply, const QPersistentModelIndex &index) {
|
void MainWindow::SongSaveComplete(TagReaderReply *reply, const QPersistentModelIndex &idx) {
|
||||||
|
|
||||||
if (reply->is_successful() && index.isValid()) {
|
if (reply->is_successful() && idx.isValid()) {
|
||||||
app_->playlist_manager()->current()->ReloadItems(QList<int>()<< index.row());
|
app_->playlist_manager()->current()->ReloadItems(QList<int>()<< idx.row());
|
||||||
}
|
}
|
||||||
reply->deleteLater();
|
reply->deleteLater();
|
||||||
|
|
||||||
@@ -2064,7 +2146,10 @@ void MainWindow::AddCDTracks() {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void MainWindow::AddStream() { add_stream_dialog_->show(); }
|
void MainWindow::AddStream() {
|
||||||
|
add_stream_dialog_->show();
|
||||||
|
add_stream_dialog_->raise();
|
||||||
|
}
|
||||||
|
|
||||||
void MainWindow::AddStreamAccepted() {
|
void MainWindow::AddStreamAccepted() {
|
||||||
|
|
||||||
@@ -2119,8 +2204,8 @@ void MainWindow::PlaylistClearCurrent() {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void MainWindow::PlaylistEditFinished(const QModelIndex &index) {
|
void MainWindow::PlaylistEditFinished(const QModelIndex &idx) {
|
||||||
if (index == playlist_menu_index_) SelectionSetValue();
|
if (idx == playlist_menu_index_) SelectionSetValue();
|
||||||
}
|
}
|
||||||
|
|
||||||
void MainWindow::CommandlineOptionsReceived(const quint32 instanceId, const QByteArray &string_options) {
|
void MainWindow::CommandlineOptionsReceived(const quint32 instanceId, const QByteArray &string_options) {
|
||||||
@@ -2134,9 +2219,11 @@ void MainWindow::CommandlineOptionsReceived(const quint32 instanceId, const QByt
|
|||||||
raise();
|
raise();
|
||||||
show();
|
show();
|
||||||
activateWindow();
|
activateWindow();
|
||||||
|
hidden_ = false;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
CommandlineOptionsReceived(options);
|
CommandlineOptionsReceived(options);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void MainWindow::CommandlineOptionsReceived(const CommandlineOptions &options) {
|
void MainWindow::CommandlineOptionsReceived(const CommandlineOptions &options) {
|
||||||
@@ -2148,7 +2235,7 @@ void MainWindow::CommandlineOptionsReceived(const CommandlineOptions &options) {
|
|||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case CommandlineOptions::Player_PlayPause:
|
case CommandlineOptions::Player_PlayPause:
|
||||||
app_->player()->PlayPause();
|
app_->player()->PlayPause(Playlist::AutoScroll_Maybe);
|
||||||
break;
|
break;
|
||||||
case CommandlineOptions::Player_Pause:
|
case CommandlineOptions::Player_Pause:
|
||||||
app_->player()->Pause();
|
app_->player()->Pause();
|
||||||
@@ -2223,7 +2310,7 @@ void MainWindow::CommandlineOptionsReceived(const CommandlineOptions &options) {
|
|||||||
app_->player()->SeekTo(app_->player()->engine()->position_nanosec() / kNsecPerSec + options.seek_by());
|
app_->player()->SeekTo(app_->player()->engine()->position_nanosec() / kNsecPerSec + options.seek_by());
|
||||||
}
|
}
|
||||||
|
|
||||||
if (options.play_track_at() != -1) app_->player()->PlayAt(options.play_track_at(), Engine::Manual, true);
|
if (options.play_track_at() != -1) app_->player()->PlayAt(options.play_track_at(), Engine::Manual, Playlist::AutoScroll_Maybe, true);
|
||||||
|
|
||||||
if (options.show_osd()) app_->player()->ShowOSD();
|
if (options.show_osd()) app_->player()->ShowOSD();
|
||||||
|
|
||||||
@@ -2327,6 +2414,7 @@ void MainWindow::CopyFilesToCollection(const QList<QUrl> &urls) {
|
|||||||
organize_dialog_->SetUrls(urls);
|
organize_dialog_->SetUrls(urls);
|
||||||
organize_dialog_->SetCopy(true);
|
organize_dialog_->SetCopy(true);
|
||||||
organize_dialog_->show();
|
organize_dialog_->show();
|
||||||
|
organize_dialog_->raise();
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2336,6 +2424,7 @@ void MainWindow::MoveFilesToCollection(const QList<QUrl> &urls) {
|
|||||||
organize_dialog_->SetUrls(urls);
|
organize_dialog_->SetUrls(urls);
|
||||||
organize_dialog_->SetCopy(false);
|
organize_dialog_->SetCopy(false);
|
||||||
organize_dialog_->show();
|
organize_dialog_->show();
|
||||||
|
organize_dialog_->raise();
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2344,8 +2433,10 @@ void MainWindow::CopyFilesToDevice(const QList<QUrl> &urls) {
|
|||||||
#if defined(HAVE_GSTREAMER) && !defined(Q_OS_WIN)
|
#if defined(HAVE_GSTREAMER) && !defined(Q_OS_WIN)
|
||||||
organize_dialog_->SetDestinationModel(app_->device_manager()->connected_devices_model(), true);
|
organize_dialog_->SetDestinationModel(app_->device_manager()->connected_devices_model(), true);
|
||||||
organize_dialog_->SetCopy(true);
|
organize_dialog_->SetCopy(true);
|
||||||
if (organize_dialog_->SetUrls(urls))
|
if (organize_dialog_->SetUrls(urls)) {
|
||||||
organize_dialog_->show();
|
organize_dialog_->show();
|
||||||
|
organize_dialog_->raise();
|
||||||
|
}
|
||||||
else {
|
else {
|
||||||
QMessageBox::warning(this, tr("Error"), tr("None of the selected songs were suitable for copying to a device"));
|
QMessageBox::warning(this, tr("Error"), tr("None of the selected songs were suitable for copying to a device"));
|
||||||
}
|
}
|
||||||
@@ -2368,6 +2459,7 @@ void MainWindow::EditFileTags(const QList<QUrl> &urls) {
|
|||||||
|
|
||||||
edit_tag_dialog_->SetSongs(songs);
|
edit_tag_dialog_->SetSongs(songs);
|
||||||
edit_tag_dialog_->show();
|
edit_tag_dialog_->show();
|
||||||
|
edit_tag_dialog_->raise();
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2397,6 +2489,7 @@ void MainWindow::PlaylistOrganizeSelected(const bool copy) {
|
|||||||
organize_dialog_->SetSongs(songs);
|
organize_dialog_->SetSongs(songs);
|
||||||
organize_dialog_->SetCopy(copy);
|
organize_dialog_->SetCopy(copy);
|
||||||
organize_dialog_->show();
|
organize_dialog_->show();
|
||||||
|
organize_dialog_->raise();
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2485,8 +2578,10 @@ void MainWindow::PlaylistCopyToDevice() {
|
|||||||
|
|
||||||
organize_dialog_->SetDestinationModel(app_->device_manager()->connected_devices_model(), true);
|
organize_dialog_->SetDestinationModel(app_->device_manager()->connected_devices_model(), true);
|
||||||
organize_dialog_->SetCopy(true);
|
organize_dialog_->SetCopy(true);
|
||||||
if (organize_dialog_->SetSongs(songs))
|
if (organize_dialog_->SetSongs(songs)) {
|
||||||
organize_dialog_->show();
|
organize_dialog_->show();
|
||||||
|
organize_dialog_->raise();
|
||||||
|
}
|
||||||
else {
|
else {
|
||||||
QMessageBox::warning(this, tr("Error"), tr("None of the selected songs were suitable for copying to a device"));
|
QMessageBox::warning(this, tr("Error"), tr("None of the selected songs were suitable for copying to a device"));
|
||||||
}
|
}
|
||||||
@@ -2512,6 +2607,14 @@ void MainWindow::ChangeCollectionQueryMode(QAction *action) {
|
|||||||
void MainWindow::ShowCoverManager() {
|
void MainWindow::ShowCoverManager() {
|
||||||
|
|
||||||
cover_manager_->show();
|
cover_manager_->show();
|
||||||
|
cover_manager_->raise();
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
void MainWindow::ShowEqualizer() {
|
||||||
|
|
||||||
|
equalizer_->show();
|
||||||
|
equalizer_->raise();
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2534,6 +2637,7 @@ SettingsDialog *MainWindow::CreateSettingsDialog() {
|
|||||||
void MainWindow::OpenSettingsDialog() {
|
void MainWindow::OpenSettingsDialog() {
|
||||||
|
|
||||||
settings_dialog_->show();
|
settings_dialog_->show();
|
||||||
|
settings_dialog_->raise();
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2553,6 +2657,7 @@ EditTagDialog *MainWindow::CreateEditTagDialog() {
|
|||||||
void MainWindow::ShowAboutDialog() {
|
void MainWindow::ShowAboutDialog() {
|
||||||
|
|
||||||
about_dialog_->show();
|
about_dialog_->show();
|
||||||
|
about_dialog_->raise();
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2560,6 +2665,7 @@ void MainWindow::ShowTranscodeDialog() {
|
|||||||
|
|
||||||
#ifdef HAVE_GSTREAMER
|
#ifdef HAVE_GSTREAMER
|
||||||
transcode_dialog_->show();
|
transcode_dialog_->show();
|
||||||
|
transcode_dialog_->raise();
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -2617,8 +2723,11 @@ void MainWindow::PlaylistCurrentChanged(const QModelIndex &proxy_current) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void MainWindow::Raise() {
|
void MainWindow::Raise() {
|
||||||
|
|
||||||
show();
|
show();
|
||||||
activateWindow();
|
activateWindow();
|
||||||
|
hidden_ = false;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
|
#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
|
||||||
@@ -2679,6 +2788,7 @@ void MainWindow::AutoCompleteTags() {
|
|||||||
track_selection_dialog_->Init(songs);
|
track_selection_dialog_->Init(songs);
|
||||||
tag_fetcher_->StartFetch(songs);
|
tag_fetcher_->StartFetch(songs);
|
||||||
track_selection_dialog_->show();
|
track_selection_dialog_->show();
|
||||||
|
track_selection_dialog_->raise();
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
@@ -2720,26 +2830,28 @@ void MainWindow::HandleNotificationPreview(OSDBase::Behaviour type, QString line
|
|||||||
}
|
}
|
||||||
|
|
||||||
void MainWindow::ShowConsole() {
|
void MainWindow::ShowConsole() {
|
||||||
Console *console = new Console(app_, this);
|
|
||||||
console->show();
|
console_->show();
|
||||||
|
console_->raise();
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void MainWindow::keyPressEvent(QKeyEvent *event) {
|
void MainWindow::keyPressEvent(QKeyEvent *e) {
|
||||||
|
|
||||||
if (event->key() == Qt::Key_Space) {
|
if (e->key() == Qt::Key_Space) {
|
||||||
app_->player()->PlayPause();
|
app_->player()->PlayPause(Playlist::AutoScroll_Never);
|
||||||
event->accept();
|
e->accept();
|
||||||
}
|
}
|
||||||
else if (event->key() == Qt::Key_Left) {
|
else if (e->key() == Qt::Key_Left) {
|
||||||
ui_->track_slider->Seek(-1);
|
ui_->track_slider->Seek(-1);
|
||||||
event->accept();
|
e->accept();
|
||||||
}
|
}
|
||||||
else if (event->key() == Qt::Key_Right) {
|
else if (e->key() == Qt::Key_Right) {
|
||||||
ui_->track_slider->Seek(1);
|
ui_->track_slider->Seek(1);
|
||||||
event->accept();
|
e->accept();
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
QMainWindow::keyPressEvent(event);
|
QMainWindow::keyPressEvent(e);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -2850,3 +2962,39 @@ void MainWindow::Love() {
|
|||||||
if (tray_icon_) tray_icon_->LoveStateChanged(false);
|
if (tray_icon_) tray_icon_->LoveStateChanged(false);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void MainWindow::PlaylistDelete() {
|
||||||
|
|
||||||
|
if (!delete_files_) return;
|
||||||
|
|
||||||
|
SongList selected_songs;
|
||||||
|
QStringList files;
|
||||||
|
bool is_current_item = false;
|
||||||
|
for (const QModelIndex &proxy_idx : ui_->playlist->view()->selectionModel()->selectedRows()) {
|
||||||
|
QModelIndex source_idx = app_->playlist_manager()->current()->proxy()->mapToSource(proxy_idx);
|
||||||
|
PlaylistItemPtr item = app_->playlist_manager()->current()->item_at(source_idx.row());
|
||||||
|
if (!item || !item->Metadata().url().isLocalFile()) continue;
|
||||||
|
selected_songs << item->Metadata();
|
||||||
|
files << item->Metadata().url().toLocalFile();
|
||||||
|
if (item == app_->player()->GetCurrentItem()) is_current_item = true;
|
||||||
|
}
|
||||||
|
if (selected_songs.isEmpty()) return;
|
||||||
|
|
||||||
|
if (DeleteConfirmationDialog::warning(files) != QDialogButtonBox::Yes) return;
|
||||||
|
|
||||||
|
if (app_->player()->GetState() == Engine::Playing && app_->playlist_manager()->current()->rowCount() == selected_songs.count()) {
|
||||||
|
app_->player()->Stop();
|
||||||
|
}
|
||||||
|
|
||||||
|
ui_->playlist->view()->RemoveSelected();
|
||||||
|
|
||||||
|
if (app_->player()->GetState() == Engine::Playing && is_current_item) {
|
||||||
|
app_->player()->Next();
|
||||||
|
}
|
||||||
|
|
||||||
|
std::shared_ptr<MusicStorage> storage(new FilesystemMusicStorage("/"));
|
||||||
|
DeleteFiles *delete_files = new DeleteFiles(app_->task_manager(), storage, true);
|
||||||
|
connect(delete_files, SIGNAL(Finished(SongList)), SLOT(DeleteFinished(SongList)));
|
||||||
|
delete_files->Start(selected_songs);
|
||||||
|
|
||||||
|
}
|
||||||
|
|||||||
@@ -56,12 +56,14 @@
|
|||||||
#include "mac_startup.h"
|
#include "mac_startup.h"
|
||||||
#include "osd/osdbase.h"
|
#include "osd/osdbase.h"
|
||||||
#include "collection/collectionmodel.h"
|
#include "collection/collectionmodel.h"
|
||||||
|
#include "playlist/playlist.h"
|
||||||
#include "playlist/playlistitem.h"
|
#include "playlist/playlistitem.h"
|
||||||
#include "settings/settingsdialog.h"
|
#include "settings/settingsdialog.h"
|
||||||
#include "settings/behavioursettingspage.h"
|
#include "settings/behavioursettingspage.h"
|
||||||
#include "covermanager/albumcoverloaderresult.h"
|
#include "covermanager/albumcoverloaderresult.h"
|
||||||
|
|
||||||
class About;
|
class About;
|
||||||
|
class Console;
|
||||||
class AlbumCoverManager;
|
class AlbumCoverManager;
|
||||||
class Application;
|
class Application;
|
||||||
class ContextView;
|
class ContextView;
|
||||||
@@ -91,27 +93,30 @@ class TranscodeDialog;
|
|||||||
class Ui_MainWindow;
|
class Ui_MainWindow;
|
||||||
class InternetSongsView;
|
class InternetSongsView;
|
||||||
class InternetTabsView;
|
class InternetTabsView;
|
||||||
|
class SmartPlaylistsViewContainer;
|
||||||
#ifdef Q_OS_WIN
|
#ifdef Q_OS_WIN
|
||||||
class Windows7ThumbBar;
|
class Windows7ThumbBar;
|
||||||
#endif
|
#endif
|
||||||
class AddStreamDialog;
|
class AddStreamDialog;
|
||||||
|
class LastFMImportDialog;
|
||||||
|
|
||||||
class MainWindow : public QMainWindow, public PlatformInterface {
|
class MainWindow : public QMainWindow, public PlatformInterface {
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
|
|
||||||
public:
|
public:
|
||||||
explicit MainWindow(Application *app, SystemTrayIcon *tray_icon, OSDBase *osd, const CommandlineOptions& options, QWidget *parent = nullptr);
|
explicit MainWindow(Application *app, SystemTrayIcon *tray_icon, OSDBase *osd, const CommandlineOptions &options, QWidget *parent = nullptr);
|
||||||
~MainWindow() override;
|
~MainWindow() override;
|
||||||
|
|
||||||
static const char *kSettingsGroup;
|
static const char *kSettingsGroup;
|
||||||
static const char *kAllFilesFilterSpec;
|
static const char *kAllFilesFilterSpec;
|
||||||
|
|
||||||
void SetHiddenInTray(const bool hidden);
|
void SetHiddenInTray(const bool hidden);
|
||||||
void CommandlineOptionsReceived(const CommandlineOptions& options);
|
void CommandlineOptionsReceived(const CommandlineOptions &options);
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
void keyPressEvent(QKeyEvent *event) override;
|
void showEvent(QShowEvent *e) override;
|
||||||
void closeEvent(QCloseEvent *event) override;
|
void closeEvent(QCloseEvent *e) override;
|
||||||
|
void keyPressEvent(QKeyEvent *e) override;
|
||||||
#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
|
#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
|
||||||
bool nativeEvent(const QByteArray &eventType, void *message, qintptr *result) override;
|
bool nativeEvent(const QByteArray &eventType, void *message, qintptr *result) override;
|
||||||
#else
|
#else
|
||||||
@@ -120,7 +125,7 @@ class MainWindow : public QMainWindow, public PlatformInterface {
|
|||||||
|
|
||||||
// PlatformInterface
|
// PlatformInterface
|
||||||
void Activate() override;
|
void Activate() override;
|
||||||
bool LoadUrl(const QString& url) override;
|
bool LoadUrl(const QString &url) override;
|
||||||
|
|
||||||
signals:
|
signals:
|
||||||
void AlbumCoverReady(const Song &song, const QImage &image);
|
void AlbumCoverReady(const Song &song, const QImage &image);
|
||||||
@@ -128,23 +133,21 @@ class MainWindow : public QMainWindow, public PlatformInterface {
|
|||||||
// Signals that stop playing after track was toggled.
|
// Signals that stop playing after track was toggled.
|
||||||
void StopAfterToggled(bool stop);
|
void StopAfterToggled(bool stop);
|
||||||
|
|
||||||
void IntroPointReached();
|
|
||||||
|
|
||||||
void AuthorizationUrlReceived(const QUrl &url);
|
void AuthorizationUrlReceived(const QUrl &url);
|
||||||
|
|
||||||
private slots:
|
private slots:
|
||||||
void FilePathChanged(const QString& path);
|
void FilePathChanged(const QString &path);
|
||||||
|
|
||||||
void EngineChanged(Engine::EngineType enginetype);
|
void EngineChanged(Engine::EngineType enginetype);
|
||||||
void MediaStopped();
|
void MediaStopped();
|
||||||
void MediaPaused();
|
void MediaPaused();
|
||||||
void MediaPlaying();
|
void MediaPlaying();
|
||||||
void TrackSkipped(PlaylistItemPtr item);
|
void TrackSkipped(PlaylistItemPtr item);
|
||||||
void ForceShowOSD(const Song& song, const bool toggle);
|
void ForceShowOSD(const Song &song, const bool toggle);
|
||||||
|
|
||||||
void PlaylistMenuHidden();
|
void PlaylistMenuHidden();
|
||||||
void PlaylistRightClick(const QPoint& global_pos, const QModelIndex& index);
|
void PlaylistRightClick(const QPoint &global_pos, const QModelIndex &index);
|
||||||
void PlaylistCurrentChanged(const QModelIndex& current);
|
void PlaylistCurrentChanged(const QModelIndex ¤t);
|
||||||
void PlaylistViewSelectionModelChanged();
|
void PlaylistViewSelectionModelChanged();
|
||||||
void PlaylistPlay();
|
void PlaylistPlay();
|
||||||
void PlaylistStopAfter();
|
void PlaylistStopAfter();
|
||||||
@@ -152,7 +155,7 @@ class MainWindow : public QMainWindow, public PlatformInterface {
|
|||||||
void PlaylistQueuePlayNext();
|
void PlaylistQueuePlayNext();
|
||||||
void PlaylistSkip();
|
void PlaylistSkip();
|
||||||
void PlaylistRemoveCurrent();
|
void PlaylistRemoveCurrent();
|
||||||
void PlaylistEditFinished(const QModelIndex& index);
|
void PlaylistEditFinished(const QModelIndex &idx);
|
||||||
void PlaylistClearCurrent();
|
void PlaylistClearCurrent();
|
||||||
void RescanSongs();
|
void RescanSongs();
|
||||||
void EditTracks();
|
void EditTracks();
|
||||||
@@ -175,17 +178,17 @@ class MainWindow : public QMainWindow, public PlatformInterface {
|
|||||||
|
|
||||||
void ChangeCollectionQueryMode(QAction *action);
|
void ChangeCollectionQueryMode(QAction *action);
|
||||||
|
|
||||||
void PlayIndex(const QModelIndex& index);
|
void PlayIndex(const QModelIndex &idx, Playlist::AutoScroll autoscroll);
|
||||||
void PlaylistDoubleClick(const QModelIndex& index);
|
void PlaylistDoubleClick(const QModelIndex &idx);
|
||||||
void StopAfterCurrent();
|
void StopAfterCurrent();
|
||||||
|
|
||||||
void SongChanged(const Song& song);
|
void SongChanged(const Song &song);
|
||||||
void VolumeChanged(const int volume);
|
void VolumeChanged(const int volume);
|
||||||
|
|
||||||
void CopyFilesToCollection(const QList<QUrl>& urls);
|
void CopyFilesToCollection(const QList<QUrl> &urls);
|
||||||
void MoveFilesToCollection(const QList<QUrl>& urls);
|
void MoveFilesToCollection(const QList<QUrl> &urls);
|
||||||
void CopyFilesToDevice(const QList<QUrl>& urls);
|
void CopyFilesToDevice(const QList<QUrl> &urls);
|
||||||
void EditFileTags(const QList<QUrl>& urls);
|
void EditFileTags(const QList<QUrl> &urls);
|
||||||
|
|
||||||
void AddToPlaylist(QMimeData *q_mimedata);
|
void AddToPlaylist(QMimeData *q_mimedata);
|
||||||
void AddToPlaylist(QAction *action);
|
void AddToPlaylist(QAction *action);
|
||||||
@@ -217,12 +220,13 @@ class MainWindow : public QMainWindow, public PlatformInterface {
|
|||||||
|
|
||||||
void PlayingWidgetPositionChanged(const bool above_status_bar);
|
void PlayingWidgetPositionChanged(const bool above_status_bar);
|
||||||
|
|
||||||
void SongSaveComplete(TagReaderReply *reply, const QPersistentModelIndex& index);
|
void SongSaveComplete(TagReaderReply *reply, const QPersistentModelIndex &idx);
|
||||||
|
|
||||||
void ShowCoverManager();
|
void ShowCoverManager();
|
||||||
|
void ShowEqualizer();
|
||||||
|
|
||||||
void ShowAboutDialog();
|
void ShowAboutDialog();
|
||||||
void ShowErrorDialog(const QString& message);
|
void ShowErrorDialog(const QString &message);
|
||||||
void ShowTranscodeDialog();
|
void ShowTranscodeDialog();
|
||||||
SettingsDialog *CreateSettingsDialog();
|
SettingsDialog *CreateSettingsDialog();
|
||||||
EditTagDialog *CreateEditTagDialog();
|
EditTagDialog *CreateEditTagDialog();
|
||||||
@@ -264,12 +268,14 @@ class MainWindow : public QMainWindow, public PlatformInterface {
|
|||||||
|
|
||||||
void ExitFinished();
|
void ExitFinished();
|
||||||
|
|
||||||
|
void PlaylistDelete();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
|
||||||
void SaveSettings();
|
void SaveSettings();
|
||||||
|
|
||||||
void ApplyAddBehaviour(BehaviourSettingsPage::AddBehaviour b, MimeData *mimedata) const;
|
void ApplyAddBehaviour(const BehaviourSettingsPage::AddBehaviour b, MimeData *mimedata) const;
|
||||||
void ApplyPlayBehaviour(BehaviourSettingsPage::PlayBehaviour b, MimeData *mimedata) const;
|
void ApplyPlayBehaviour(const BehaviourSettingsPage::PlayBehaviour b, MimeData *mimedata) const;
|
||||||
|
|
||||||
void CheckFullRescanRevisions();
|
void CheckFullRescanRevisions();
|
||||||
|
|
||||||
@@ -290,6 +296,7 @@ class MainWindow : public QMainWindow, public PlatformInterface {
|
|||||||
SystemTrayIcon *tray_icon_;
|
SystemTrayIcon *tray_icon_;
|
||||||
OSDBase *osd_;
|
OSDBase *osd_;
|
||||||
Lazy<About> about_dialog_;
|
Lazy<About> about_dialog_;
|
||||||
|
Lazy<Console> console_;
|
||||||
Lazy<EditTagDialog> edit_tag_dialog_;
|
Lazy<EditTagDialog> edit_tag_dialog_;
|
||||||
AlbumCoverChoiceController *album_cover_choice_controller_;
|
AlbumCoverChoiceController *album_cover_choice_controller_;
|
||||||
|
|
||||||
@@ -320,8 +327,13 @@ class MainWindow : public QMainWindow, public PlatformInterface {
|
|||||||
std::unique_ptr<TrackSelectionDialog> track_selection_dialog_;
|
std::unique_ptr<TrackSelectionDialog> track_selection_dialog_;
|
||||||
PlaylistItemList autocomplete_tag_items_;
|
PlaylistItemList autocomplete_tag_items_;
|
||||||
|
|
||||||
|
SmartPlaylistsViewContainer *smartplaylists_view_;
|
||||||
|
|
||||||
InternetSongsView *subsonic_view_;
|
InternetSongsView *subsonic_view_;
|
||||||
InternetTabsView *tidal_view_;
|
InternetTabsView *tidal_view_;
|
||||||
|
InternetTabsView *qobuz_view_;
|
||||||
|
|
||||||
|
LastFMImportDialog *lastfm_import_dialog_;
|
||||||
|
|
||||||
QAction *collection_show_all_;
|
QAction *collection_show_all_;
|
||||||
QAction *collection_show_duplicates_;
|
QAction *collection_show_duplicates_;
|
||||||
@@ -331,15 +343,16 @@ class MainWindow : public QMainWindow, public PlatformInterface {
|
|||||||
QAction *playlist_play_pause_;
|
QAction *playlist_play_pause_;
|
||||||
QAction *playlist_stop_after_;
|
QAction *playlist_stop_after_;
|
||||||
QAction *playlist_undoredo_;
|
QAction *playlist_undoredo_;
|
||||||
QAction *playlist_organize_;
|
QAction *playlist_copy_url_;
|
||||||
QAction *playlist_show_in_collection_;
|
QAction *playlist_show_in_collection_;
|
||||||
QAction *playlist_copy_to_collection_;
|
QAction *playlist_copy_to_collection_;
|
||||||
QAction *playlist_move_to_collection_;
|
QAction *playlist_move_to_collection_;
|
||||||
|
QAction *playlist_open_in_browser_;
|
||||||
|
QAction *playlist_organize_;
|
||||||
#ifndef Q_OS_WIN
|
#ifndef Q_OS_WIN
|
||||||
QAction *playlist_copy_to_device_;
|
QAction *playlist_copy_to_device_;
|
||||||
#endif
|
#endif
|
||||||
QAction *playlist_open_in_browser_;
|
QAction *playlist_delete_;
|
||||||
QAction *playlist_copy_url_;
|
|
||||||
QAction *playlist_queue_;
|
QAction *playlist_queue_;
|
||||||
QAction* playlist_queue_play_next_;
|
QAction* playlist_queue_play_next_;
|
||||||
QAction *playlist_skip_;
|
QAction *playlist_skip_;
|
||||||
@@ -356,19 +369,23 @@ class MainWindow : public QMainWindow, public PlatformInterface {
|
|||||||
QTimer *track_slider_timer_;
|
QTimer *track_slider_timer_;
|
||||||
QSettings settings_;
|
QSettings settings_;
|
||||||
|
|
||||||
bool initialized_;
|
bool keep_running_;
|
||||||
bool was_maximized_;
|
|
||||||
bool was_minimized_;
|
|
||||||
bool playing_widget_;
|
bool playing_widget_;
|
||||||
BehaviourSettingsPage::AddBehaviour doubleclick_addmode_;
|
BehaviourSettingsPage::AddBehaviour doubleclick_addmode_;
|
||||||
BehaviourSettingsPage::PlayBehaviour doubleclick_playmode_;
|
BehaviourSettingsPage::PlayBehaviour doubleclick_playmode_;
|
||||||
BehaviourSettingsPage::PlaylistAddBehaviour doubleclick_playlist_addmode_;
|
BehaviourSettingsPage::PlaylistAddBehaviour doubleclick_playlist_addmode_;
|
||||||
BehaviourSettingsPage::PlayBehaviour menu_playmode_;
|
BehaviourSettingsPage::PlayBehaviour menu_playmode_;
|
||||||
|
|
||||||
|
bool initialized_;
|
||||||
|
bool was_maximized_;
|
||||||
|
bool was_minimized_;
|
||||||
|
bool hidden_;
|
||||||
|
|
||||||
Song song_;
|
Song song_;
|
||||||
Song song_playing_;
|
Song song_playing_;
|
||||||
QImage image_original_;
|
QImage image_original_;
|
||||||
int exit_count_;
|
int exit_count_;
|
||||||
|
bool delete_files_;
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -511,6 +511,7 @@
|
|||||||
<addaction name="action_abort_collection_scan"/>
|
<addaction name="action_abort_collection_scan"/>
|
||||||
<addaction name="separator"/>
|
<addaction name="separator"/>
|
||||||
<addaction name="action_settings"/>
|
<addaction name="action_settings"/>
|
||||||
|
<addaction name="action_import_data_from_last_fm"/>
|
||||||
<addaction name="action_console"/>
|
<addaction name="action_console"/>
|
||||||
<addaction name="separator"/>
|
<addaction name="separator"/>
|
||||||
<addaction name="action_toggle_show_sidebar"/>
|
<addaction name="action_toggle_show_sidebar"/>
|
||||||
@@ -844,6 +845,11 @@
|
|||||||
<string>Show sidebar</string>
|
<string>Show sidebar</string>
|
||||||
</property>
|
</property>
|
||||||
</action>
|
</action>
|
||||||
|
<action name="action_import_data_from_last_fm">
|
||||||
|
<property name="text">
|
||||||
|
<string>Import data from last.fm...</string>
|
||||||
|
</property>
|
||||||
|
</action>
|
||||||
</widget>
|
</widget>
|
||||||
<layoutdefault spacing="6" margin="11"/>
|
<layoutdefault spacing="6" margin="11"/>
|
||||||
<customwidgets>
|
<customwidgets>
|
||||||
|
|||||||
@@ -42,6 +42,7 @@
|
|||||||
#include <QItemSelection>
|
#include <QItemSelection>
|
||||||
#ifdef HAVE_DBUS
|
#ifdef HAVE_DBUS
|
||||||
# include <QDBusMetaType>
|
# include <QDBusMetaType>
|
||||||
|
# include <QDBusArgument>
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#include "song.h"
|
#include "song.h"
|
||||||
@@ -62,11 +63,14 @@
|
|||||||
|
|
||||||
#ifdef HAVE_DBUS
|
#ifdef HAVE_DBUS
|
||||||
# include "mpris2.h"
|
# include "mpris2.h"
|
||||||
|
# include "osd/osddbus.h"
|
||||||
# include "dbus/metatypes.h"
|
# include "dbus/metatypes.h"
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#include "internet/internetsearchview.h"
|
#include "internet/internetsearchview.h"
|
||||||
|
|
||||||
|
#include "smartplaylists/playlistgenerator_fwd.h"
|
||||||
|
|
||||||
void RegisterMetaTypes() {
|
void RegisterMetaTypes() {
|
||||||
|
|
||||||
qRegisterMetaType<const char*>("const char*");
|
qRegisterMetaType<const char*>("const char*");
|
||||||
@@ -81,8 +85,12 @@ void RegisterMetaTypes() {
|
|||||||
qRegisterMetaType<QNetworkReply*>("QNetworkReply*");
|
qRegisterMetaType<QNetworkReply*>("QNetworkReply*");
|
||||||
qRegisterMetaType<QNetworkReply**>("QNetworkReply**");
|
qRegisterMetaType<QNetworkReply**>("QNetworkReply**");
|
||||||
qRegisterMetaType<QItemSelection>("QItemSelection");
|
qRegisterMetaType<QItemSelection>("QItemSelection");
|
||||||
|
qRegisterMetaType<QMap<int, Qt::Alignment>>("ColumnAlignmentMap");
|
||||||
|
qRegisterMetaType<QMap<int, int>>("ColumnAlignmentIntMap");
|
||||||
|
#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
|
||||||
qRegisterMetaTypeStreamOperators<QMap<int, Qt::Alignment>>("ColumnAlignmentMap");
|
qRegisterMetaTypeStreamOperators<QMap<int, Qt::Alignment>>("ColumnAlignmentMap");
|
||||||
qRegisterMetaTypeStreamOperators<QMap<int, int>>("ColumnAlignmentIntMap");
|
qRegisterMetaTypeStreamOperators<QMap<int, int>>("ColumnAlignmentIntMap");
|
||||||
|
#endif
|
||||||
qRegisterMetaType<Directory>("Directory");
|
qRegisterMetaType<Directory>("Directory");
|
||||||
qRegisterMetaType<DirectoryList>("DirectoryList");
|
qRegisterMetaType<DirectoryList>("DirectoryList");
|
||||||
qRegisterMetaType<Subdirectory>("Subdirectory");
|
qRegisterMetaType<Subdirectory>("Subdirectory");
|
||||||
@@ -112,9 +120,12 @@ void RegisterMetaTypes() {
|
|||||||
qRegisterMetaType<QList<CoverSearchResult> >("QList<CoverSearchResult>");
|
qRegisterMetaType<QList<CoverSearchResult> >("QList<CoverSearchResult>");
|
||||||
qRegisterMetaType<CoverSearchResults>("CoverSearchResults");
|
qRegisterMetaType<CoverSearchResults>("CoverSearchResults");
|
||||||
qRegisterMetaType<Equalizer::Params>("Equalizer::Params");
|
qRegisterMetaType<Equalizer::Params>("Equalizer::Params");
|
||||||
|
#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
|
||||||
qRegisterMetaTypeStreamOperators<Equalizer::Params>("Equalizer::Params");
|
qRegisterMetaTypeStreamOperators<Equalizer::Params>("Equalizer::Params");
|
||||||
|
#endif
|
||||||
#ifdef HAVE_DBUS
|
#ifdef HAVE_DBUS
|
||||||
qDBusRegisterMetaType<QList<QByteArray>>();
|
qDBusRegisterMetaType<QList<QByteArray>>();
|
||||||
|
qDBusRegisterMetaType<QImage>();
|
||||||
qDBusRegisterMetaType<TrackMetadata>();
|
qDBusRegisterMetaType<TrackMetadata>();
|
||||||
qDBusRegisterMetaType<Track_Ids>();
|
qDBusRegisterMetaType<Track_Ids>();
|
||||||
qDBusRegisterMetaType<MprisPlaylist>();
|
qDBusRegisterMetaType<MprisPlaylist>();
|
||||||
@@ -122,12 +133,11 @@ void RegisterMetaTypes() {
|
|||||||
qDBusRegisterMetaType<MaybePlaylist>();
|
qDBusRegisterMetaType<MaybePlaylist>();
|
||||||
qDBusRegisterMetaType<InterfacesAndProperties>();
|
qDBusRegisterMetaType<InterfacesAndProperties>();
|
||||||
qDBusRegisterMetaType<ManagedObjectList>();
|
qDBusRegisterMetaType<ManagedObjectList>();
|
||||||
#ifdef HAVE_X11
|
|
||||||
qDBusRegisterMetaType<QImage>();
|
|
||||||
#endif
|
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
qRegisterMetaType<InternetSearchView::ResultList>("InternetSearchView::ResultList");
|
qRegisterMetaType<InternetSearchView::ResultList>("InternetSearchView::ResultList");
|
||||||
qRegisterMetaType<InternetSearchView::Result>("InternetSearchView::Result");
|
qRegisterMetaType<InternetSearchView::Result>("InternetSearchView::Result");
|
||||||
|
|
||||||
|
qRegisterMetaType<PlaylistGeneratorPtr>("PlaylistGeneratorPtr");
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -36,7 +36,6 @@
|
|||||||
#include <QString>
|
#include <QString>
|
||||||
#include <QStringList>
|
#include <QStringList>
|
||||||
#include <QUrl>
|
#include <QUrl>
|
||||||
#include <QImage>
|
|
||||||
#include <QDBusConnection>
|
#include <QDBusConnection>
|
||||||
#include <QDBusMessage>
|
#include <QDBusMessage>
|
||||||
#include <QDBusArgument>
|
#include <QDBusArgument>
|
||||||
@@ -72,7 +71,7 @@ QDBusArgument &operator<<(QDBusArgument &arg, const MprisPlaylist &playlist) {
|
|||||||
return arg;
|
return arg;
|
||||||
}
|
}
|
||||||
|
|
||||||
const QDBusArgument &operator>> (const QDBusArgument &arg, MprisPlaylist &playlist) {
|
const QDBusArgument &operator>>(const QDBusArgument &arg, MprisPlaylist &playlist) {
|
||||||
arg.beginStructure();
|
arg.beginStructure();
|
||||||
arg >> playlist.id >> playlist.name >> playlist.icon;
|
arg >> playlist.id >> playlist.name >> playlist.icon;
|
||||||
arg.endStructure();
|
arg.endStructure();
|
||||||
@@ -87,7 +86,7 @@ QDBusArgument &operator<<(QDBusArgument &arg, const MaybePlaylist &playlist) {
|
|||||||
return arg;
|
return arg;
|
||||||
}
|
}
|
||||||
|
|
||||||
const QDBusArgument &operator>> (const QDBusArgument &arg, MaybePlaylist &playlist) {
|
const QDBusArgument &operator>>(const QDBusArgument &arg, MaybePlaylist &playlist) {
|
||||||
arg.beginStructure();
|
arg.beginStructure();
|
||||||
arg >> playlist.valid >> playlist.playlist;
|
arg >> playlist.valid >> playlist.playlist;
|
||||||
arg.endStructure();
|
arg.endStructure();
|
||||||
|
|||||||
@@ -32,7 +32,6 @@
|
|||||||
#include <QVariant>
|
#include <QVariant>
|
||||||
#include <QString>
|
#include <QString>
|
||||||
#include <QStringList>
|
#include <QStringList>
|
||||||
#include <QImage>
|
|
||||||
#include <QtDBus>
|
#include <QtDBus>
|
||||||
#include <QDBusArgument>
|
#include <QDBusArgument>
|
||||||
#include <qdbusextratypes.h>
|
#include <qdbusextratypes.h>
|
||||||
@@ -70,9 +69,6 @@ const QDBusArgument &operator>> (const QDBusArgument &arg, MprisPlaylist &playli
|
|||||||
QDBusArgument &operator<<(QDBusArgument &arg, const MaybePlaylist &playlist);
|
QDBusArgument &operator<<(QDBusArgument &arg, const MaybePlaylist &playlist);
|
||||||
const QDBusArgument &operator>> (const QDBusArgument &arg, MaybePlaylist &playlist);
|
const QDBusArgument &operator>> (const QDBusArgument &arg, MaybePlaylist &playlist);
|
||||||
|
|
||||||
QDBusArgument &operator<< (QDBusArgument &arg, const QImage &image);
|
|
||||||
const QDBusArgument &operator>> (const QDBusArgument &arg, QImage &image);
|
|
||||||
|
|
||||||
namespace mpris {
|
namespace mpris {
|
||||||
|
|
||||||
class Mpris2 : public QObject {
|
class Mpris2 : public QObject {
|
||||||
|
|||||||
@@ -56,6 +56,7 @@ class MusicStorage {
|
|||||||
typedef std::function<void(float progress)> ProgressFunction;
|
typedef std::function<void(float progress)> ProgressFunction;
|
||||||
|
|
||||||
struct CopyJob {
|
struct CopyJob {
|
||||||
|
CopyJob() : overwrite_(false), mark_as_listened_(false), remove_original_(false), albumcover_(false) {}
|
||||||
QString source_;
|
QString source_;
|
||||||
QString destination_;
|
QString destination_;
|
||||||
Song metadata_;
|
Song metadata_;
|
||||||
@@ -70,7 +71,9 @@ class MusicStorage {
|
|||||||
};
|
};
|
||||||
|
|
||||||
struct DeleteJob {
|
struct DeleteJob {
|
||||||
|
DeleteJob() : use_trash_(false) {}
|
||||||
Song metadata_;
|
Song metadata_;
|
||||||
|
bool use_trash_;
|
||||||
};
|
};
|
||||||
|
|
||||||
virtual QString LocalPath() const { return QString(); }
|
virtual QString LocalPath() const { return QString(); }
|
||||||
|
|||||||
@@ -81,6 +81,7 @@ Player::Player(Application *app, QObject *parent)
|
|||||||
analyzer_(nullptr),
|
analyzer_(nullptr),
|
||||||
equalizer_(nullptr),
|
equalizer_(nullptr),
|
||||||
stream_change_type_(Engine::First),
|
stream_change_type_(Engine::First),
|
||||||
|
autoscroll_(Playlist::AutoScroll_Maybe),
|
||||||
last_state_(Engine::Empty),
|
last_state_(Engine::Empty),
|
||||||
nb_errors_received_(0),
|
nb_errors_received_(0),
|
||||||
volume_before_mute_(100),
|
volume_before_mute_(100),
|
||||||
@@ -236,10 +237,11 @@ void Player::HandleLoadResult(const UrlHandler::LoadResult &result) {
|
|||||||
if (!item) {
|
if (!item) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const bool has_next_row = app_->playlist_manager()->active()->next_row() != -1;
|
int next_row = app_->playlist_manager()->active()->next_row();
|
||||||
|
const bool has_next_row = next_row != -1;
|
||||||
PlaylistItemPtr next_item;
|
PlaylistItemPtr next_item;
|
||||||
if (has_next_row) {
|
if (has_next_row) {
|
||||||
next_item = app_->playlist_manager()->active()->item_at(app_->playlist_manager()->active()->next_row());
|
next_item = app_->playlist_manager()->active()->item_at(next_row);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool is_current(false);
|
bool is_current(false);
|
||||||
@@ -265,7 +267,7 @@ void Player::HandleLoadResult(const UrlHandler::LoadResult &result) {
|
|||||||
|
|
||||||
case UrlHandler::LoadResult::NoMoreTracks:
|
case UrlHandler::LoadResult::NoMoreTracks:
|
||||||
qLog(Debug) << "URL handler for" << result.original_url_ << "said no more tracks" << is_current;
|
qLog(Debug) << "URL handler for" << result.original_url_ << "said no more tracks" << is_current;
|
||||||
if (is_current) NextItem(stream_change_type_);
|
if (is_current) NextItem(stream_change_type_, autoscroll_);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case UrlHandler::LoadResult::TrackAvailable: {
|
case UrlHandler::LoadResult::TrackAvailable: {
|
||||||
@@ -321,11 +323,12 @@ void Player::HandleLoadResult(const UrlHandler::LoadResult &result) {
|
|||||||
if (update) {
|
if (update) {
|
||||||
if (is_current) {
|
if (is_current) {
|
||||||
item->SetTemporaryMetadata(song);
|
item->SetTemporaryMetadata(song);
|
||||||
app_->playlist_manager()->active()->InformOfCurrentSongChange();
|
app_->playlist_manager()->active()->InformOfCurrentSongChange(autoscroll_, true);
|
||||||
|
app_->playlist_manager()->active()->UpdateScrobblePoint();
|
||||||
}
|
}
|
||||||
else if (is_next) {
|
else if (is_next) {
|
||||||
next_item->SetTemporaryMetadata(song);
|
next_item->SetTemporaryMetadata(song);
|
||||||
app_->playlist_manager()->active()->ItemChanged(next_item);
|
app_->playlist_manager()->active()->ItemChanged(next_row);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -352,17 +355,17 @@ void Player::HandleLoadResult(const UrlHandler::LoadResult &result) {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void Player::Next() { NextInternal(Engine::Manual); }
|
void Player::Next() { NextInternal(Engine::Manual, Playlist::AutoScroll_Always); }
|
||||||
|
|
||||||
void Player::NextInternal(Engine::TrackChangeFlags change) {
|
void Player::NextInternal(const Engine::TrackChangeFlags change, const Playlist::AutoScroll autoscroll) {
|
||||||
|
|
||||||
if (HandleStopAfter()) return;
|
if (HandleStopAfter(autoscroll)) return;
|
||||||
|
|
||||||
NextItem(change);
|
NextItem(change, autoscroll);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void Player::NextItem(Engine::TrackChangeFlags change) {
|
void Player::NextItem(const Engine::TrackChangeFlags change, const Playlist::AutoScroll autoscroll) {
|
||||||
|
|
||||||
Playlist *active_playlist = app_->playlist_manager()->active();
|
Playlist *active_playlist = app_->playlist_manager()->active();
|
||||||
|
|
||||||
@@ -391,17 +394,17 @@ void Player::NextItem(Engine::TrackChangeFlags change) {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
PlayAt(i, change, false);
|
PlayAt(i, change, autoscroll, false, true);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bool Player::HandleStopAfter() {
|
bool Player::HandleStopAfter(const Playlist::AutoScroll autoscroll) {
|
||||||
|
|
||||||
if (app_->playlist_manager()->active()->stop_after_current()) {
|
if (app_->playlist_manager()->active()->stop_after_current()) {
|
||||||
// Find what the next track would've been, and mark that one as current so it plays next time the user presses Play.
|
// Find what the next track would've been, and mark that one as current so it plays next time the user presses Play.
|
||||||
const int next_row = app_->playlist_manager()->active()->next_row();
|
const int next_row = app_->playlist_manager()->active()->next_row();
|
||||||
if (next_row != -1) {
|
if (next_row != -1) {
|
||||||
app_->playlist_manager()->active()->set_current_row(next_row, true);
|
app_->playlist_manager()->active()->set_current_row(next_row, autoscroll, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
app_->playlist_manager()->active()->StopAfter(-1);
|
app_->playlist_manager()->active()->StopAfter(-1);
|
||||||
@@ -419,13 +422,13 @@ void Player::TrackEnded() {
|
|||||||
app_->playlist_manager()->collection_backend()->IncrementPlayCountAsync(current_item_->Metadata().id());
|
app_->playlist_manager()->collection_backend()->IncrementPlayCountAsync(current_item_->Metadata().id());
|
||||||
}
|
}
|
||||||
|
|
||||||
if (HandleStopAfter()) return;
|
if (HandleStopAfter(Playlist::AutoScroll_Maybe)) return;
|
||||||
|
|
||||||
NextInternal(Engine::Auto);
|
NextInternal(Engine::Auto, Playlist::AutoScroll_Maybe);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void Player::PlayPause() {
|
void Player::PlayPause(Playlist::AutoScroll autoscroll) {
|
||||||
|
|
||||||
switch (engine_->state()) {
|
switch (engine_->state()) {
|
||||||
case Engine::Paused:
|
case Engine::Paused:
|
||||||
@@ -451,7 +454,7 @@ void Player::PlayPause() {
|
|||||||
int i = app_->playlist_manager()->active()->current_row();
|
int i = app_->playlist_manager()->active()->current_row();
|
||||||
if (i == -1) i = app_->playlist_manager()->active()->last_played_row();
|
if (i == -1) i = app_->playlist_manager()->active()->last_played_row();
|
||||||
if (i == -1) i = 0;
|
if (i == -1) i = 0;
|
||||||
PlayAt(i, Engine::First, true);
|
PlayAt(i, Engine::First, autoscroll, true);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -475,7 +478,6 @@ void Player::Stop(bool stop_after) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void Player::StopAfterCurrent() {
|
void Player::StopAfterCurrent() {
|
||||||
|
|
||||||
app_->playlist_manager()->active()->StopAfter(app_->playlist_manager()->active()->current_row());
|
app_->playlist_manager()->active()->StopAfter(app_->playlist_manager()->active()->current_row());
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -487,7 +489,7 @@ bool Player::PreviousWouldRestartTrack() const {
|
|||||||
|
|
||||||
void Player::Previous() { PreviousItem(Engine::Manual); }
|
void Player::Previous() { PreviousItem(Engine::Manual); }
|
||||||
|
|
||||||
void Player::PreviousItem(Engine::TrackChangeFlags change) {
|
void Player::PreviousItem(const Engine::TrackChangeFlags change) {
|
||||||
|
|
||||||
const bool ignore_repeat_track = change & Engine::Manual;
|
const bool ignore_repeat_track = change & Engine::Manual;
|
||||||
|
|
||||||
@@ -496,25 +498,25 @@ void Player::PreviousItem(Engine::TrackChangeFlags change) {
|
|||||||
QDateTime now = QDateTime::currentDateTime();
|
QDateTime now = QDateTime::currentDateTime();
|
||||||
if (last_pressed_previous_.isValid() && last_pressed_previous_.secsTo(now) >= 2) {
|
if (last_pressed_previous_.isValid() && last_pressed_previous_.secsTo(now) >= 2) {
|
||||||
last_pressed_previous_ = now;
|
last_pressed_previous_ = now;
|
||||||
PlayAt(app_->playlist_manager()->active()->current_row(), change, false);
|
PlayAt(app_->playlist_manager()->active()->current_row(), change, Playlist::AutoScroll_Always, false, true);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
last_pressed_previous_ = now;
|
last_pressed_previous_ = now;
|
||||||
}
|
}
|
||||||
|
|
||||||
int i = app_->playlist_manager()->active()->previous_row(ignore_repeat_track);
|
int i = app_->playlist_manager()->active()->previous_row(ignore_repeat_track);
|
||||||
app_->playlist_manager()->active()->set_current_row(i);
|
app_->playlist_manager()->active()->set_current_row(i, Playlist::AutoScroll_Always, false);
|
||||||
if (i == -1) {
|
if (i == -1) {
|
||||||
Stop();
|
Stop();
|
||||||
PlayAt(i, change, true);
|
PlayAt(i, change, Playlist::AutoScroll_Always, true);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
PlayAt(i, change, false);
|
PlayAt(i, change, Playlist::AutoScroll_Always, false);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void Player::EngineStateChanged(Engine::State state) {
|
void Player::EngineStateChanged(const Engine::State state) {
|
||||||
|
|
||||||
if (Engine::Error == state) {
|
if (Engine::Error == state) {
|
||||||
nb_errors_received_++;
|
nb_errors_received_++;
|
||||||
@@ -542,7 +544,7 @@ void Player::EngineStateChanged(Engine::State state) {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void Player::SetVolume(int value) {
|
void Player::SetVolume(const int value) {
|
||||||
|
|
||||||
int old_volume = engine_->volume();
|
int old_volume = engine_->volume();
|
||||||
|
|
||||||
@@ -558,7 +560,7 @@ void Player::SetVolume(int value) {
|
|||||||
|
|
||||||
int Player::GetVolume() const { return engine_->volume(); }
|
int Player::GetVolume() const { return engine_->volume(); }
|
||||||
|
|
||||||
void Player::PlayAt(int index, Engine::TrackChangeFlags change, bool reshuffle) {
|
void Player::PlayAt(const int index, Engine::TrackChangeFlags change, const Playlist::AutoScroll autoscroll, const bool reshuffle, const bool force_inform) {
|
||||||
|
|
||||||
if (current_item_ && change == Engine::Manual && engine_->position_nanosec() != engine_->length_nanosec()) {
|
if (current_item_ && change == Engine::Manual && engine_->position_nanosec() != engine_->length_nanosec()) {
|
||||||
emit TrackSkipped(current_item_);
|
emit TrackSkipped(current_item_);
|
||||||
@@ -569,7 +571,8 @@ void Player::PlayAt(int index, Engine::TrackChangeFlags change, bool reshuffle)
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (reshuffle) app_->playlist_manager()->active()->ReshuffleIndices();
|
if (reshuffle) app_->playlist_manager()->active()->ReshuffleIndices();
|
||||||
app_->playlist_manager()->active()->set_current_row(index);
|
|
||||||
|
app_->playlist_manager()->active()->set_current_row(index, autoscroll, false, force_inform);
|
||||||
if (app_->playlist_manager()->active()->current_row() == -1) {
|
if (app_->playlist_manager()->active()->current_row() == -1) {
|
||||||
// Maybe index didn't exist in the playlist.
|
// Maybe index didn't exist in the playlist.
|
||||||
return;
|
return;
|
||||||
@@ -583,13 +586,11 @@ void Player::PlayAt(int index, Engine::TrackChangeFlags change, bool reshuffle)
|
|||||||
if (loading_async_.contains(url)) return;
|
if (loading_async_.contains(url)) return;
|
||||||
|
|
||||||
stream_change_type_ = change;
|
stream_change_type_ = change;
|
||||||
|
autoscroll_ = autoscroll;
|
||||||
HandleLoadResult(url_handlers_[url.scheme()]->StartLoading(url));
|
HandleLoadResult(url_handlers_[url.scheme()]->StartLoading(url));
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
qLog(Debug) << "Playing song" << current_item_->Metadata().title() << url;
|
qLog(Debug) << "Playing song" << current_item_->Metadata().title() << url;
|
||||||
if (current_item_->HasTemporaryMetadata()) {
|
|
||||||
app_->playlist_manager()->active()->InformOfCurrentSongChange();
|
|
||||||
}
|
|
||||||
engine_->Play(url, current_item_->Url(), change, current_item_->Metadata().has_cue(), current_item_->effective_beginning_nanosec(), current_item_->effective_end_nanosec());
|
engine_->Play(url, current_item_->Url(), change, current_item_->Metadata().has_cue(), current_item_->effective_beginning_nanosec(), current_item_->effective_end_nanosec());
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -600,18 +601,9 @@ void Player::CurrentMetadataChanged(const Song &metadata) {
|
|||||||
// Those things might have changed (especially when a previously invalid song was reloaded) so we push the latest version into Engine
|
// Those things might have changed (especially when a previously invalid song was reloaded) so we push the latest version into Engine
|
||||||
engine_->RefreshMarkers(metadata.beginning_nanosec(), metadata.end_nanosec());
|
engine_->RefreshMarkers(metadata.beginning_nanosec(), metadata.end_nanosec());
|
||||||
|
|
||||||
// Send now playing to scrobble services
|
|
||||||
if (app_->scrobbler()->IsEnabled() && engine_->state() == Engine::Playing) {
|
|
||||||
Playlist *playlist = app_->playlist_manager()->active();
|
|
||||||
current_item_ = playlist->current_item();
|
|
||||||
if (playlist && current_item_ && !playlist->nowplaying() && current_item_->Metadata() == metadata && current_item_->Metadata().is_metadata_good()) {
|
|
||||||
emit SendNowPlaying();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void Player::SeekTo(int seconds) {
|
void Player::SeekTo(const int seconds) {
|
||||||
|
|
||||||
const qint64 length_nanosec = engine_->length_nanosec();
|
const qint64 length_nanosec = engine_->length_nanosec();
|
||||||
|
|
||||||
@@ -628,6 +620,10 @@ void Player::SeekTo(int seconds) {
|
|||||||
|
|
||||||
emit Seeked(nanosec / 1000);
|
emit Seeked(nanosec / 1000);
|
||||||
|
|
||||||
|
if (seconds == 0) {
|
||||||
|
app_->playlist_manager()->active()->InformOfCurrentSongChange(Playlist::AutoScroll_Maybe, false);
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void Player::SeekForward() {
|
void Player::SeekForward() {
|
||||||
@@ -650,19 +646,20 @@ void Player::EngineMetadataReceived(const Engine::SimpleMetaBundle &bundle) {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (app_->playlist_manager()->active()->next_row() != -1) {
|
int next_row = app_->playlist_manager()->active()->next_row();
|
||||||
PlaylistItemPtr next_item = app_->playlist_manager()->active()->item_at(app_->playlist_manager()->active()->next_row());
|
if (next_row != -1) {
|
||||||
|
PlaylistItemPtr next_item = app_->playlist_manager()->active()->item_at(next_row);
|
||||||
if (bundle.url == next_item->Url()) {
|
if (bundle.url == next_item->Url()) {
|
||||||
Song song = next_item->Metadata();
|
Song song = next_item->Metadata();
|
||||||
song.MergeFromSimpleMetaBundle(bundle);
|
song.MergeFromSimpleMetaBundle(bundle);
|
||||||
next_item->SetTemporaryMetadata(song);
|
next_item->SetTemporaryMetadata(song);
|
||||||
app_->playlist_manager()->active()->ItemChanged(next_item);
|
app_->playlist_manager()->active()->ItemChanged(next_row);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
PlaylistItemPtr Player::GetItemAt(int pos) const {
|
PlaylistItemPtr Player::GetItemAt(const int pos) const {
|
||||||
|
|
||||||
if (pos < 0 || pos >= app_->playlist_manager()->active()->rowCount())
|
if (pos < 0 || pos >= app_->playlist_manager()->active()->rowCount())
|
||||||
return PlaylistItemPtr();
|
return PlaylistItemPtr();
|
||||||
@@ -743,6 +740,7 @@ void Player::TrackAboutToEnd() {
|
|||||||
// Get the actual track URL rather than the stream URL.
|
// Get the actual track URL rather than the stream URL.
|
||||||
if (url_handlers_.contains(url.scheme())) {
|
if (url_handlers_.contains(url.scheme())) {
|
||||||
if (loading_async_.contains(url)) return;
|
if (loading_async_.contains(url)) return;
|
||||||
|
autoscroll_ = Playlist::AutoScroll_Maybe;
|
||||||
UrlHandler::LoadResult result = url_handlers_[url.scheme()]->StartLoading(url);
|
UrlHandler::LoadResult result = url_handlers_[url.scheme()]->StartLoading(url);
|
||||||
switch (result.type_) {
|
switch (result.type_) {
|
||||||
case UrlHandler::LoadResult::Error:
|
case UrlHandler::LoadResult::Error:
|
||||||
@@ -754,7 +752,11 @@ void Player::TrackAboutToEnd() {
|
|||||||
loading_async_ << url;
|
loading_async_ << url;
|
||||||
return;
|
return;
|
||||||
case UrlHandler::LoadResult::TrackAvailable:
|
case UrlHandler::LoadResult::TrackAvailable:
|
||||||
|
qLog(Debug) << "URL handler for" << result.original_url_ << "returned" << result.stream_url_;
|
||||||
url = result.stream_url_;
|
url = result.stream_url_;
|
||||||
|
Song song = next_item->Metadata();
|
||||||
|
song.set_stream_url(url);
|
||||||
|
next_item->SetTemporaryMetadata(song);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -763,8 +765,6 @@ void Player::TrackAboutToEnd() {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void Player::IntroPointReached() { NextInternal(Engine::Intro); }
|
|
||||||
|
|
||||||
void Player::FatalError() {
|
void Player::FatalError() {
|
||||||
nb_errors_received_ = 0;
|
nb_errors_received_ = 0;
|
||||||
Stop();
|
Stop();
|
||||||
@@ -783,7 +783,7 @@ void Player::InvalidSongRequested(const QUrl &url) {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
NextItem(Engine::Auto);
|
NextItem(Engine::Auto, Playlist::AutoScroll_Maybe);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -37,6 +37,7 @@
|
|||||||
#include "urlhandler.h"
|
#include "urlhandler.h"
|
||||||
#include "engine/engine_fwd.h"
|
#include "engine/engine_fwd.h"
|
||||||
#include "engine/enginetype.h"
|
#include "engine/enginetype.h"
|
||||||
|
#include "playlist/playlist.h"
|
||||||
#include "playlist/playlistitem.h"
|
#include "playlist/playlistitem.h"
|
||||||
#include "settings/behavioursettingspage.h"
|
#include "settings/behavioursettingspage.h"
|
||||||
|
|
||||||
@@ -72,20 +73,20 @@ class PlayerInterface : public QObject {
|
|||||||
virtual void ReloadSettings() = 0;
|
virtual void ReloadSettings() = 0;
|
||||||
|
|
||||||
// Manual track change to the specified track
|
// Manual track change to the specified track
|
||||||
virtual void PlayAt(int i, Engine::TrackChangeFlags change, bool reshuffle) = 0;
|
virtual void PlayAt(const int index, Engine::TrackChangeFlags change, const Playlist::AutoScroll autoscroll, const bool reshuffle, const bool force_inform = false) = 0;
|
||||||
|
|
||||||
// If there's currently a song playing, pause it, otherwise play the track that was playing last, or the first one on the playlist
|
// If there's currently a song playing, pause it, otherwise play the track that was playing last, or the first one on the playlist
|
||||||
virtual void PlayPause() = 0;
|
virtual void PlayPause(Playlist::AutoScroll autoscroll = Playlist::AutoScroll_Always) = 0;
|
||||||
virtual void RestartOrPrevious() = 0;
|
virtual void RestartOrPrevious() = 0;
|
||||||
|
|
||||||
// Skips this track. Might load more of the current radio station.
|
// Skips this track. Might load more of the current radio station.
|
||||||
virtual void Next() = 0;
|
virtual void Next() = 0;
|
||||||
|
|
||||||
virtual void Previous() = 0;
|
virtual void Previous() = 0;
|
||||||
virtual void SetVolume(int value) = 0;
|
virtual void SetVolume(const int value) = 0;
|
||||||
virtual void VolumeUp() = 0;
|
virtual void VolumeUp() = 0;
|
||||||
virtual void VolumeDown() = 0;
|
virtual void VolumeDown() = 0;
|
||||||
virtual void SeekTo(int seconds) = 0;
|
virtual void SeekTo(const int seconds) = 0;
|
||||||
// Moves the position of the currently playing song five seconds forward.
|
// Moves the position of the currently playing song five seconds forward.
|
||||||
virtual void SeekForward() = 0;
|
virtual void SeekForward() = 0;
|
||||||
// Moves the position of the currently playing song five seconds backwards.
|
// Moves the position of the currently playing song five seconds backwards.
|
||||||
@@ -109,19 +110,18 @@ class PlayerInterface : public QObject {
|
|||||||
void PlaylistFinished();
|
void PlaylistFinished();
|
||||||
void VolumeEnabled(bool);
|
void VolumeEnabled(bool);
|
||||||
void VolumeChanged(int volume);
|
void VolumeChanged(int volume);
|
||||||
void Error(const QString &message);
|
void Error(QString message);
|
||||||
void TrackSkipped(PlaylistItemPtr old_track);
|
void TrackSkipped(PlaylistItemPtr old_track);
|
||||||
// Emitted when there's a manual change to the current's track position.
|
// Emitted when there's a manual change to the current's track position.
|
||||||
void Seeked(qlonglong microseconds);
|
void Seeked(qlonglong microseconds);
|
||||||
|
|
||||||
// Emitted when Player has processed a request to play another song.
|
// Emitted when Player has processed a request to play another song.
|
||||||
// This contains the URL of the song and a flag saying whether it was able to play the song.
|
// This contains the URL of the song and a flag saying whether it was able to play the song.
|
||||||
void SongChangeRequestProcessed(const QUrl &url, bool valid);
|
void SongChangeRequestProcessed(QUrl url, bool valid);
|
||||||
|
|
||||||
// The toggle parameter is true when user requests to toggle visibility for Pretty OSD
|
// The toggle parameter is true when user requests to toggle visibility for Pretty OSD
|
||||||
void ForceShowOSD(Song, bool toggle);
|
void ForceShowOSD(Song, bool toggle);
|
||||||
|
|
||||||
void SendNowPlaying();
|
|
||||||
void Authenticated();
|
void Authenticated();
|
||||||
|
|
||||||
};
|
};
|
||||||
@@ -158,15 +158,15 @@ class Player : public PlayerInterface {
|
|||||||
public slots:
|
public slots:
|
||||||
void ReloadSettings() override;
|
void ReloadSettings() override;
|
||||||
|
|
||||||
void PlayAt(int i, Engine::TrackChangeFlags change, bool reshuffle) override;
|
void PlayAt(const int index, Engine::TrackChangeFlags change, const Playlist::AutoScroll autoscroll, const bool reshuffle, const bool force_inform = false) override;
|
||||||
void PlayPause() override;
|
void PlayPause(Playlist::AutoScroll autoscroll = Playlist::AutoScroll_Always) override;
|
||||||
void RestartOrPrevious() override;
|
void RestartOrPrevious() override;
|
||||||
void Next() override;
|
void Next() override;
|
||||||
void Previous() override;
|
void Previous() override;
|
||||||
void SetVolume(int value) override;
|
void SetVolume(const int value) override;
|
||||||
void VolumeUp() override { SetVolume(GetVolume() + 5); }
|
void VolumeUp() override { SetVolume(GetVolume() + 5); }
|
||||||
void VolumeDown() override { SetVolume(GetVolume() - 5); }
|
void VolumeDown() override { SetVolume(GetVolume() - 5); }
|
||||||
void SeekTo(int seconds) override;
|
void SeekTo(const int seconds) override;
|
||||||
void SeekForward() override;
|
void SeekForward() override;
|
||||||
void SeekBackward() override;
|
void SeekBackward() override;
|
||||||
|
|
||||||
@@ -176,7 +176,6 @@ class Player : public PlayerInterface {
|
|||||||
void Pause() override;
|
void Pause() override;
|
||||||
void Stop(bool stop_after = false) override;
|
void Stop(bool stop_after = false) override;
|
||||||
void StopAfterCurrent();
|
void StopAfterCurrent();
|
||||||
void IntroPointReached();
|
|
||||||
void Play() override;
|
void Play() override;
|
||||||
void ShowOSD() override;
|
void ShowOSD() override;
|
||||||
void TogglePrettyOSD();
|
void TogglePrettyOSD();
|
||||||
@@ -187,15 +186,15 @@ class Player : public PlayerInterface {
|
|||||||
void EngineChanged(Engine::EngineType enginetype);
|
void EngineChanged(Engine::EngineType enginetype);
|
||||||
|
|
||||||
private slots:
|
private slots:
|
||||||
void EngineStateChanged(Engine::State);
|
void EngineStateChanged(const Engine::State);
|
||||||
void EngineMetadataReceived(const Engine::SimpleMetaBundle &bundle);
|
void EngineMetadataReceived(const Engine::SimpleMetaBundle &bundle);
|
||||||
void TrackAboutToEnd();
|
void TrackAboutToEnd();
|
||||||
void TrackEnded();
|
void TrackEnded();
|
||||||
// Play the next item on the playlist - disregarding radio stations like last.fm that might have more tracks.
|
// Play the next item on the playlist - disregarding radio stations like last.fm that might have more tracks.
|
||||||
void NextItem(Engine::TrackChangeFlags change);
|
void NextItem(const Engine::TrackChangeFlags change, const Playlist::AutoScroll autoscroll);
|
||||||
void PreviousItem(Engine::TrackChangeFlags change);
|
void PreviousItem(const Engine::TrackChangeFlags change);
|
||||||
|
|
||||||
void NextInternal(Engine::TrackChangeFlags);
|
void NextInternal(const Engine::TrackChangeFlags, const Playlist::AutoScroll autoscroll);
|
||||||
|
|
||||||
void FatalError();
|
void FatalError();
|
||||||
void ValidSongRequested(const QUrl&);
|
void ValidSongRequested(const QUrl&);
|
||||||
@@ -206,7 +205,7 @@ class Player : public PlayerInterface {
|
|||||||
|
|
||||||
private:
|
private:
|
||||||
// Returns true if we were supposed to stop after this track.
|
// Returns true if we were supposed to stop after this track.
|
||||||
bool HandleStopAfter();
|
bool HandleStopAfter(const Playlist::AutoScroll autoscroll);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
Application *app_;
|
Application *app_;
|
||||||
@@ -222,6 +221,7 @@ class Player : public PlayerInterface {
|
|||||||
PlaylistItemPtr current_item_;
|
PlaylistItemPtr current_item_;
|
||||||
|
|
||||||
Engine::TrackChangeFlags stream_change_type_;
|
Engine::TrackChangeFlags stream_change_type_;
|
||||||
|
Playlist::AutoScroll autoscroll_;
|
||||||
Engine::State last_state_;
|
Engine::State last_state_;
|
||||||
int nb_errors_received_;
|
int nb_errors_received_;
|
||||||
|
|
||||||
|
|||||||
@@ -24,8 +24,6 @@
|
|||||||
#include <QCoreApplication>
|
#include <QCoreApplication>
|
||||||
#include <QSystemTrayIcon>
|
#include <QSystemTrayIcon>
|
||||||
#include <QAction>
|
#include <QAction>
|
||||||
#include <QIODevice>
|
|
||||||
#include <QFile>
|
|
||||||
#include <QMenu>
|
#include <QMenu>
|
||||||
#include <QIcon>
|
#include <QIcon>
|
||||||
#include <QString>
|
#include <QString>
|
||||||
@@ -61,16 +59,6 @@ QtSystemTrayIcon::QtSystemTrayIcon(QObject *parent)
|
|||||||
tray_->installEventFilter(this);
|
tray_->installEventFilter(this);
|
||||||
ClearNowPlaying();
|
ClearNowPlaying();
|
||||||
|
|
||||||
#ifndef Q_OS_WIN
|
|
||||||
de_ = Utilities::DesktopEnvironment().toLower();
|
|
||||||
QFile pattern_file(":/html/playing-tooltip.html");
|
|
||||||
if (pattern_file.open(QIODevice::ReadOnly)) {
|
|
||||||
pattern_ = QString::fromLatin1(pattern_file.readAll());
|
|
||||||
pattern_file.close();
|
|
||||||
}
|
|
||||||
|
|
||||||
#endif
|
|
||||||
|
|
||||||
connect(tray_, SIGNAL(activated(QSystemTrayIcon::ActivationReason)), SLOT(Clicked(QSystemTrayIcon::ActivationReason)));
|
connect(tray_, SIGNAL(activated(QSystemTrayIcon::ActivationReason)), SLOT(Clicked(QSystemTrayIcon::ActivationReason)));
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -231,50 +219,9 @@ void QtSystemTrayIcon::SetVisible(bool visible) {
|
|||||||
tray_->setVisible(visible);
|
tray_->setVisible(visible);
|
||||||
}
|
}
|
||||||
|
|
||||||
void QtSystemTrayIcon::SetNowPlaying(const Song &song, const QUrl &cover_url) {
|
void QtSystemTrayIcon::SetNowPlaying(const Song &song, const QUrl&) {
|
||||||
|
|
||||||
#ifdef Q_OS_WIN
|
|
||||||
Q_UNUSED(song);
|
|
||||||
Q_UNUSED(cover_url);
|
|
||||||
// Windows doesn't support HTML in tooltips, so just show something basic
|
|
||||||
tray_->setToolTip(song.PrettyTitleWithArtist());
|
tray_->setToolTip(song.PrettyTitleWithArtist());
|
||||||
#else
|
|
||||||
|
|
||||||
if (de_ == "kde") {
|
|
||||||
tray_->setToolTip(song.PrettyTitleWithArtist());
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
int columns = cover_url.isEmpty() ? 1 : 2;
|
|
||||||
|
|
||||||
QString tooltip(pattern_);
|
|
||||||
|
|
||||||
tooltip.replace("%columns" , QString::number(columns));
|
|
||||||
tooltip.replace("%appName" , app_name_);
|
|
||||||
|
|
||||||
tooltip.replace("%titleKey" , tr("Title") % ":");
|
|
||||||
tooltip.replace("%titleValue" , song.PrettyTitle().toHtmlEscaped());
|
|
||||||
tooltip.replace("%artistKey" , tr("Artist") % ":");
|
|
||||||
tooltip.replace("%artistValue", song.artist().toHtmlEscaped());
|
|
||||||
tooltip.replace("%albumKey" , tr("Album") % ":");
|
|
||||||
tooltip.replace("%albumValue" , song.album().toHtmlEscaped());
|
|
||||||
|
|
||||||
tooltip.replace("%lengthKey" , tr("Length") % ":");
|
|
||||||
tooltip.replace("%lengthValue", song.PrettyLength().toHtmlEscaped());
|
|
||||||
|
|
||||||
if (columns == 2) {
|
|
||||||
QString final_path = cover_url.isLocalFile() ? cover_url.path() : cover_url.toString();
|
|
||||||
tooltip.replace("%image", " <td> <img src=\"" % final_path % "\" /> </td>");
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
tooltip.replace("<td>%image</td>", "");
|
|
||||||
tooltip.replace("%image", "");
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: we should also repaint this
|
|
||||||
tray_->setToolTip(tooltip);
|
|
||||||
|
|
||||||
#endif
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -52,7 +52,7 @@ class QtSystemTrayIcon : public SystemTrayIcon {
|
|||||||
|
|
||||||
void ShowPopup(const QString &summary, const QString &message, int timeout) override;
|
void ShowPopup(const QString &summary, const QString &message, int timeout) override;
|
||||||
|
|
||||||
void SetNowPlaying(const Song &song, const QUrl &cover_url) override;
|
void SetNowPlaying(const Song &song, const QUrl&) override;
|
||||||
void ClearNowPlaying() override;
|
void ClearNowPlaying() override;
|
||||||
|
|
||||||
bool MuteEnabled() const override { return action_mute_->isVisible(); }
|
bool MuteEnabled() const override { return action_mute_->isVisible(); }
|
||||||
@@ -88,11 +88,6 @@ class QtSystemTrayIcon : public SystemTrayIcon {
|
|||||||
QAction *action_mute_;
|
QAction *action_mute_;
|
||||||
QAction *action_love_;
|
QAction *action_love_;
|
||||||
|
|
||||||
#ifndef Q_OS_WIN
|
|
||||||
QString de_;
|
|
||||||
QString pattern_;
|
|
||||||
#endif
|
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif // QTSYSTEMTRAYICON_H
|
#endif // QTSYSTEMTRAYICON_H
|
||||||
|
|||||||
@@ -1,61 +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 <QtGlobal>
|
|
||||||
|
|
||||||
#ifdef HAVE_DBUS
|
|
||||||
# include <QDBusConnection>
|
|
||||||
# include <QDBusConnectionInterface>
|
|
||||||
# include <QDBusReply>
|
|
||||||
# include "dbusscreensaver.h"
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#include "screensaver.h"
|
|
||||||
|
|
||||||
#ifdef Q_OS_MACOS
|
|
||||||
#include "macscreensaver.h"
|
|
||||||
#endif
|
|
||||||
|
|
||||||
const char *Screensaver::kGnomeService = "org.gnome.ScreenSaver";
|
|
||||||
const char *Screensaver::kGnomePath = "/";
|
|
||||||
const char *Screensaver::kGnomeInterface = "org.gnome.ScreenSaver";
|
|
||||||
const char *Screensaver::kKdeService = "org.kde.ScreenSaver";
|
|
||||||
const char *Screensaver::kKdePath = "/ScreenSaver/";
|
|
||||||
const char *Screensaver::kKdeInterface = "org.freedesktop.ScreenSaver";
|
|
||||||
|
|
||||||
Screensaver *Screensaver::screensaver_ = nullptr;
|
|
||||||
|
|
||||||
Screensaver *Screensaver::GetScreensaver() {
|
|
||||||
if (!screensaver_) {
|
|
||||||
#if defined(HAVE_DBUS)
|
|
||||||
if (QDBusConnection::sessionBus().interface()->isServiceRegistered(kGnomeService)) {
|
|
||||||
screensaver_ = new DBusScreensaver(kGnomeService, kGnomePath, kGnomeInterface);
|
|
||||||
}
|
|
||||||
else if (QDBusConnection::sessionBus().interface()->isServiceRegistered(kKdeService)) {
|
|
||||||
screensaver_ = new DBusScreensaver(kKdeService, kKdePath, kKdeInterface);
|
|
||||||
}
|
|
||||||
#elif defined(Q_OS_MACOS)
|
|
||||||
screensaver_ = new MacScreensaver();
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
return screensaver_;
|
|
||||||
}
|
|
||||||
@@ -32,21 +32,21 @@
|
|||||||
template <typename T>
|
template <typename T>
|
||||||
class SimpleTreeItem {
|
class SimpleTreeItem {
|
||||||
public:
|
public:
|
||||||
explicit SimpleTreeItem(int _type, SimpleTreeModel<T>* _model); // For the root item
|
explicit SimpleTreeItem(int _type, SimpleTreeModel<T> *_model); // For the root item
|
||||||
explicit SimpleTreeItem(int _type, const QString& _key, T* _parent = nullptr);
|
explicit SimpleTreeItem(int _type, const QString &_key, T *_parent = nullptr);
|
||||||
explicit SimpleTreeItem(int _type, T* _parent = nullptr);
|
explicit SimpleTreeItem(int _type, T *_parent = nullptr);
|
||||||
virtual ~SimpleTreeItem();
|
virtual ~SimpleTreeItem();
|
||||||
|
|
||||||
void InsertNotify(T* _parent);
|
void InsertNotify(T *_parent);
|
||||||
void DeleteNotify(int child_row);
|
void DeleteNotify(int child_row);
|
||||||
void ClearNotify();
|
void ClearNotify();
|
||||||
void ChangedNotify();
|
void ChangedNotify();
|
||||||
|
|
||||||
void Delete(int child_row);
|
void Delete(int child_row);
|
||||||
T* ChildByKey(const QString& key) const;
|
T* ChildByKey(const QString &key) const;
|
||||||
|
|
||||||
QString DisplayText() const { return display_text.isNull() ? key : display_text; }
|
QString DisplayText() const { return display_text; }
|
||||||
QString SortText() const { return sort_text.isNull() ? key : sort_text; }
|
QString SortText() const { return sort_text; }
|
||||||
|
|
||||||
int type;
|
int type;
|
||||||
QString key;
|
QString key;
|
||||||
@@ -64,7 +64,7 @@ class SimpleTreeItem {
|
|||||||
};
|
};
|
||||||
|
|
||||||
template <typename T>
|
template <typename T>
|
||||||
SimpleTreeItem<T>::SimpleTreeItem(int _type, SimpleTreeModel<T>* _model)
|
SimpleTreeItem<T>::SimpleTreeItem(int _type, SimpleTreeModel<T> *_model)
|
||||||
: type(_type),
|
: type(_type),
|
||||||
row(0),
|
row(0),
|
||||||
lazy_loaded(true),
|
lazy_loaded(true),
|
||||||
@@ -73,7 +73,7 @@ SimpleTreeItem<T>::SimpleTreeItem(int _type, SimpleTreeModel<T>* _model)
|
|||||||
model(_model) {}
|
model(_model) {}
|
||||||
|
|
||||||
template <typename T>
|
template <typename T>
|
||||||
SimpleTreeItem<T>::SimpleTreeItem(int _type, const QString& _key, T* _parent)
|
SimpleTreeItem<T>::SimpleTreeItem(int _type, const QString &_key, T *_parent)
|
||||||
: type(_type),
|
: type(_type),
|
||||||
key(_key),
|
key(_key),
|
||||||
lazy_loaded(false),
|
lazy_loaded(false),
|
||||||
@@ -87,7 +87,7 @@ SimpleTreeItem<T>::SimpleTreeItem(int _type, const QString& _key, T* _parent)
|
|||||||
}
|
}
|
||||||
|
|
||||||
template <typename T>
|
template <typename T>
|
||||||
SimpleTreeItem<T>::SimpleTreeItem(int _type, T* _parent)
|
SimpleTreeItem<T>::SimpleTreeItem(int _type, T *_parent)
|
||||||
: type(_type),
|
: type(_type),
|
||||||
lazy_loaded(false),
|
lazy_loaded(false),
|
||||||
parent(_parent),
|
parent(_parent),
|
||||||
@@ -100,7 +100,7 @@ SimpleTreeItem<T>::SimpleTreeItem(int _type, T* _parent)
|
|||||||
}
|
}
|
||||||
|
|
||||||
template <typename T>
|
template <typename T>
|
||||||
void SimpleTreeItem<T>::InsertNotify(T* _parent) {
|
void SimpleTreeItem<T>::InsertNotify(T *_parent) {
|
||||||
parent = _parent;
|
parent = _parent;
|
||||||
model = parent->model;
|
model = parent->model;
|
||||||
row = parent->children.count();
|
row = parent->children.count();
|
||||||
@@ -151,8 +151,8 @@ void SimpleTreeItem<T>::Delete(int child_row) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
template <typename T>
|
template <typename T>
|
||||||
T* SimpleTreeItem<T>::ChildByKey(const QString &_key) const {
|
T *SimpleTreeItem<T>::ChildByKey(const QString &_key) const {
|
||||||
for (T* child : children) {
|
for (T *child : children) {
|
||||||
if (child->key == _key) return child;
|
if (child->key == _key) return child;
|
||||||
}
|
}
|
||||||
return nullptr;
|
return nullptr;
|
||||||
|
|||||||