Compare commits
355 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8ee6de4162 | ||
|
|
2b9e7db924 | ||
|
|
49384ce294 | ||
|
|
ac59fff346 | ||
|
|
a3e9f152d8 | ||
|
|
ffe6a81b9a | ||
|
|
9bb051b4eb | ||
|
|
c1465a890f | ||
|
|
c3ee3318d7 | ||
|
|
272976f0b7 | ||
|
|
4724b170b1 | ||
|
|
337bd4bcef | ||
|
|
0604c78453 | ||
|
|
c8169adf7c | ||
|
|
7b610d131c | ||
|
|
18da55453f | ||
|
|
2aeab8b672 | ||
|
|
9c21707a55 | ||
|
|
b02ac833ad | ||
|
|
4de912cf41 | ||
|
|
ed260c6e20 | ||
|
|
fab38f693d | ||
|
|
aedbd52e9d | ||
|
|
4cbcb9d99c | ||
|
|
e967d15b4e | ||
|
|
bb43cc63ec | ||
|
|
ca176c319d | ||
|
|
17c960ecd4 | ||
|
|
b766ae3498 | ||
|
|
faf4c817cd | ||
|
|
b4e1f283c9 | ||
|
|
52c83d592c | ||
|
|
c8caea0d30 | ||
|
|
7a6c54d8e7 | ||
|
|
7082e52a4f | ||
|
|
6cdca617e0 | ||
|
|
a1adc1a75a | ||
|
|
b16bec704a | ||
|
|
664a8c79a1 | ||
|
|
bbf3d8e1a4 | ||
|
|
a35146440c | ||
|
|
8e79fafca9 | ||
|
|
6f8780d3cc | ||
|
|
264065a355 | ||
|
|
b606e4cd1a | ||
|
|
01d0eeaed0 | ||
|
|
8c4e24e65d | ||
|
|
19c9f9698d | ||
|
|
ae87c1b578 | ||
|
|
c42b1f5548 | ||
|
|
87ffbb0a85 | ||
|
|
702851a958 | ||
|
|
650f200a0b | ||
|
|
14d215bdf3 | ||
|
|
12aebc2fe9 | ||
|
|
f41b051ec7 | ||
|
|
25144eee89 | ||
|
|
0c62147536 | ||
|
|
7b282e21de | ||
|
|
c1fbe6d84c | ||
|
|
e20cbe4170 | ||
|
|
394955a03f | ||
|
|
16b4f5d065 | ||
|
|
c95295d8b4 | ||
|
|
658dce2607 | ||
|
|
01f8d0a27e | ||
|
|
39e1bfc84f | ||
|
|
e394416fa7 | ||
|
|
f1108bc0e2 | ||
|
|
ff31815d49 | ||
|
|
604aa63b47 | ||
|
|
ec4d036f50 | ||
|
|
1bf1c4ac63 | ||
|
|
981d46fbd4 | ||
|
|
eb6a353c31 | ||
|
|
ff673b1941 | ||
|
|
8b55cf8a3a | ||
|
|
d8682b4403 | ||
|
|
40a4bf195a | ||
|
|
312c5cbc3f | ||
|
|
f314c56ef0 | ||
|
|
ea8e5180ff | ||
|
|
7f76c3f2ce | ||
|
|
e4c5e99d0f | ||
|
|
80cfca5de2 | ||
|
|
9556a14de9 | ||
|
|
2025fc8325 | ||
|
|
2dd0f6a9ba | ||
|
|
a42039d6e5 | ||
|
|
7fafa8adfb | ||
|
|
f789657552 | ||
|
|
3e183bc10e | ||
|
|
16e5d93be3 | ||
|
|
6515e06a13 | ||
|
|
9ae0b32318 | ||
|
|
4cb3bc4185 | ||
|
|
f9ca24598e | ||
|
|
3ccc892d6a | ||
|
|
d7cacea843 | ||
|
|
e30233ac74 | ||
|
|
7c57631fcf | ||
|
|
0adc084dad | ||
|
|
e22199817c | ||
|
|
78f691d006 | ||
|
|
749bae1f16 | ||
|
|
1043e24322 | ||
|
|
a6d10b1fa7 | ||
|
|
a3159423f8 | ||
|
|
ecb5ca321b | ||
|
|
fd827fdfd8 | ||
|
|
92d77b14d5 | ||
|
|
be67d89d8b | ||
|
|
ddd1ce732a | ||
|
|
012d82183c | ||
|
|
f9c98ebcb3 | ||
|
|
10fe861dde | ||
|
|
16c027ecab | ||
|
|
cc578e7cc5 | ||
|
|
6972d3c4f9 | ||
|
|
46b164f2fb | ||
|
|
5431307527 | ||
|
|
79bf194ed6 | ||
|
|
506e670aa7 | ||
|
|
fdfe164dd1 | ||
|
|
af37056179 | ||
|
|
b0fc7187cf | ||
|
|
33ad1a7a86 | ||
|
|
dd72fb4ca5 | ||
|
|
e6c5f76872 | ||
|
|
14aa22d590 | ||
|
|
5ed4293641 | ||
|
|
f9fefcda57 | ||
|
|
99b40293db | ||
|
|
9b06e85f94 | ||
|
|
93d1d40ea5 | ||
|
|
98597c047a | ||
|
|
a5c1f4b0ee | ||
|
|
3d4c98d981 | ||
|
|
384e7dedb5 | ||
|
|
7df4453560 | ||
|
|
d406a1c341 | ||
|
|
6671d97b4a | ||
|
|
d02de72830 | ||
|
|
08f5172028 | ||
|
|
04f062547d | ||
|
|
4717d783dc | ||
|
|
93af064b36 | ||
|
|
c78d73d727 | ||
|
|
b69b3228be | ||
|
|
377f54700d | ||
|
|
d276339c80 | ||
|
|
b982a6a762 | ||
|
|
536fe637aa | ||
|
|
69f36eaa25 | ||
|
|
d6927a70bb | ||
|
|
1b1892a187 | ||
|
|
5bea71cd5c | ||
|
|
bb8d4e70ae | ||
|
|
8d8c7e8b7b | ||
|
|
9ff1f4d7b4 | ||
|
|
3a60dfe025 | ||
|
|
d358854e16 | ||
|
|
129587e94a | ||
|
|
0f575f4639 | ||
|
|
7aac741571 | ||
|
|
b8a9da8a4e | ||
|
|
be6b974334 | ||
|
|
194e81205b | ||
|
|
d711dcc99d | ||
|
|
3b72a12540 | ||
|
|
38a1b7765a | ||
|
|
02f2b8b6f0 | ||
|
|
7bfa75102c | ||
|
|
b0f3e7351c | ||
|
|
b5fa401db9 | ||
|
|
41f2710dea | ||
|
|
f19dda57f0 | ||
|
|
cc4a99ad80 | ||
|
|
1c1a3fc417 | ||
|
|
e97be5850b | ||
|
|
2c302654b7 | ||
|
|
3ee4dc77b2 | ||
|
|
db55f314c9 | ||
|
|
0b536b287f | ||
|
|
6f298a9917 | ||
|
|
70f829a2e5 | ||
|
|
1dfe07003f | ||
|
|
286b908062 | ||
|
|
4ec028e736 | ||
|
|
35a6d1437a | ||
|
|
7e3cb3de89 | ||
|
|
bd945039f1 | ||
|
|
46743c8f15 | ||
|
|
9b75284447 | ||
|
|
dca3b0052a | ||
|
|
6d05bb2de5 | ||
|
|
8b2e8d3804 | ||
|
|
1a2ab19ab4 | ||
|
|
04f010aa7b | ||
|
|
25323b4a3a | ||
|
|
f353c022f6 | ||
|
|
9d8b3d3428 | ||
|
|
e4b06772c0 | ||
|
|
a2b5c3ea08 | ||
|
|
f632e95600 | ||
|
|
4b5da8c6d4 | ||
|
|
77d7ffd10a | ||
|
|
d2f720011a | ||
|
|
6329a7711d | ||
|
|
fd14d044e3 | ||
|
|
6368d507e1 | ||
|
|
205b7f2401 | ||
|
|
f7d10f9530 | ||
|
|
66154bb51e | ||
|
|
fac4ad5313 | ||
|
|
fee63891ac | ||
|
|
efa979ee03 | ||
|
|
c756346357 | ||
|
|
cc95db25cc | ||
|
|
44970c3321 | ||
|
|
43c7934af7 | ||
|
|
3e7b51163c | ||
|
|
4d5f8a53f0 | ||
|
|
0b73ca8318 | ||
|
|
754cfc3bfd | ||
|
|
2ed1a5c06a | ||
|
|
b2073df3c3 | ||
|
|
6267edaa81 | ||
|
|
292f2de3e6 | ||
|
|
7803dc8be3 | ||
|
|
2a1260b79e | ||
|
|
f9ec438b7f | ||
|
|
de544c4856 | ||
|
|
55e04dd520 | ||
|
|
b2d06f900b | ||
|
|
b92ec71810 | ||
|
|
3a4199240e | ||
|
|
157de12bdf | ||
|
|
7c0c9fccdb | ||
|
|
2cb29d06b3 | ||
|
|
1fdeb50d93 | ||
|
|
f59632ae59 | ||
|
|
67b503da44 | ||
|
|
a351de6904 | ||
|
|
f94742abd5 | ||
|
|
891182637e | ||
|
|
086681c915 | ||
|
|
3970d2d02b | ||
|
|
2b3fddd015 | ||
|
|
30fa0480a3 | ||
|
|
320a81ce69 | ||
|
|
950411ef56 | ||
|
|
cb9cf084c0 | ||
|
|
bb18af09ae | ||
|
|
049bf0c7f9 | ||
|
|
ebc73ef775 | ||
|
|
1bd6f59355 | ||
|
|
c16396a690 | ||
|
|
32a9fe3e9c | ||
|
|
f48d133bc6 | ||
|
|
0678526d7d | ||
|
|
26d3e8371f | ||
|
|
6768f614c7 | ||
|
|
171dc84df1 | ||
|
|
5ca6513c04 | ||
|
|
41aeb0ac80 | ||
|
|
f871c4adef | ||
|
|
ad4d0e89b1 | ||
|
|
0d0c49caa1 | ||
|
|
7cbc7bdcfb | ||
|
|
6b46c8ed4a | ||
|
|
e33ffd1c8a | ||
|
|
8dce9cc938 | ||
|
|
f06218f8e3 | ||
|
|
5711eef2be | ||
|
|
db6aac603c | ||
|
|
f10e928106 | ||
|
|
99d963b99c | ||
|
|
5d35c22238 | ||
|
|
54fc11a235 | ||
|
|
ec99df3144 | ||
|
|
d722035883 | ||
|
|
52139fbaa0 | ||
|
|
88854eb558 | ||
|
|
c82bba01ee | ||
|
|
eaa33a03d7 | ||
|
|
89e8518f31 | ||
|
|
c7bf2e1da8 | ||
|
|
bf904a6afa | ||
|
|
43c14ae71b | ||
|
|
4abb8ef3c9 | ||
|
|
e46b92dd7d | ||
|
|
1a25faa5b9 | ||
|
|
3f4bf5f512 | ||
|
|
ae7dbf15ed | ||
|
|
b22320c48f | ||
|
|
61204e8d35 | ||
|
|
b0704c654c | ||
|
|
f40f8a8873 | ||
|
|
560a7db506 | ||
|
|
15baa8f70e | ||
|
|
97a7637294 | ||
|
|
3454656207 | ||
|
|
7dff6f26bc | ||
|
|
9e835a23fd | ||
|
|
c0259fb6ce | ||
|
|
acf106220b | ||
|
|
5d4dc9c907 | ||
|
|
2b9a56af7f | ||
|
|
f1e3ac65ac | ||
|
|
143f72cf6b | ||
|
|
5c8e49296c | ||
|
|
b2558b703c | ||
|
|
bf9d87106e | ||
|
|
5b7f507d9b | ||
|
|
59a7835ace | ||
|
|
2495838fe3 | ||
|
|
bab687bedf | ||
|
|
6467c3c8ee | ||
|
|
66c2b7aaa6 | ||
|
|
ab72c52661 | ||
|
|
f0bf1b8a54 | ||
|
|
119251719f | ||
|
|
0348400132 | ||
|
|
813473805b | ||
|
|
fe6368561b | ||
|
|
7ff06f424d | ||
|
|
31958592c7 | ||
|
|
adc21f4f75 | ||
|
|
9cecd89d6f | ||
|
|
d866b9b4d4 | ||
|
|
93f12baf51 | ||
|
|
78d6fd634b | ||
|
|
b5fc19f08a | ||
|
|
2dae6a6546 | ||
|
|
b6bba46391 | ||
|
|
b791d97116 | ||
|
|
6aa8255f34 | ||
|
|
98e2140761 | ||
|
|
a59e064778 | ||
|
|
655c4c66a7 | ||
|
|
407c128f65 | ||
|
|
59a261a5be | ||
|
|
ba4e1afefe | ||
|
|
98b53b81d8 | ||
|
|
8270cc0aa2 | ||
|
|
74207b1a87 | ||
|
|
77983445ce | ||
|
|
162190bcb8 | ||
|
|
86b92b20b7 | ||
|
|
2f5b60d548 | ||
|
|
c19661c977 | ||
|
|
82d101ca27 | ||
|
|
32f9c4e670 | ||
|
|
34c7225ab7 |
213
.clang-format
213
.clang-format
@@ -1,105 +1,216 @@
|
|||||||
BasedOnStyle: Chromium
|
---
|
||||||
Language: Cpp
|
Language: Cpp
|
||||||
Standard: Cpp11
|
# BasedOnStyle: LLVM
|
||||||
AccessModifierOffset: -1
|
AccessModifierOffset: -1
|
||||||
AlignAfterOpenBracket: false
|
AlignAfterOpenBracket: Align
|
||||||
AlignConsecutiveAssignments: false
|
AlignArrayOfStructures: None
|
||||||
AlignConsecutiveDeclarations: false
|
AlignConsecutiveAssignments:
|
||||||
AlignConsecutiveMacros: true
|
Enabled: false
|
||||||
AlignEscapedNewlines: true
|
AcrossEmptyLines: false
|
||||||
AlignOperands: false
|
AcrossComments: false
|
||||||
|
AlignCompound: false
|
||||||
|
PadOperators: true
|
||||||
|
AlignConsecutiveBitFields:
|
||||||
|
Enabled: false
|
||||||
|
AcrossEmptyLines: false
|
||||||
|
AcrossComments: false
|
||||||
|
AlignCompound: false
|
||||||
|
PadOperators: false
|
||||||
|
AlignConsecutiveDeclarations:
|
||||||
|
Enabled: false
|
||||||
|
AcrossEmptyLines: false
|
||||||
|
AcrossComments: false
|
||||||
|
AlignCompound: false
|
||||||
|
PadOperators: false
|
||||||
|
AlignConsecutiveMacros:
|
||||||
|
Enabled: true
|
||||||
|
AcrossEmptyLines: false
|
||||||
|
AcrossComments: false
|
||||||
|
AlignCompound: false
|
||||||
|
PadOperators: false
|
||||||
|
AlignEscapedNewlines: Left
|
||||||
|
AlignOperands: DontAlign
|
||||||
AlignTrailingComments: true
|
AlignTrailingComments: true
|
||||||
AllowAllArgumentsOnNextLine: false
|
AllowAllArgumentsOnNextLine: false
|
||||||
AllowAllConstructorInitializersOnNextLine: true
|
|
||||||
AllowAllParametersOfDeclarationOnNextLine: false
|
AllowAllParametersOfDeclarationOnNextLine: false
|
||||||
AllowShortBlocksOnASingleLine: false
|
AllowShortEnumsOnASingleLine: true
|
||||||
|
AllowShortBlocksOnASingleLine: Never
|
||||||
AllowShortCaseLabelsOnASingleLine: true
|
AllowShortCaseLabelsOnASingleLine: true
|
||||||
AllowShortFunctionsOnASingleLine: true
|
AllowShortFunctionsOnASingleLine: All
|
||||||
AllowShortIfStatementsOnASingleLine: true
|
AllowShortLambdasOnASingleLine: All
|
||||||
AllowShortLambdasOnASingleLine: true
|
AllowShortIfStatementsOnASingleLine: AllIfsAndElse
|
||||||
AllowShortLoopsOnASingleLine: true
|
AllowShortLoopsOnASingleLine: true
|
||||||
|
AlwaysBreakAfterDefinitionReturnType: None
|
||||||
AlwaysBreakAfterReturnType: None
|
AlwaysBreakAfterReturnType: None
|
||||||
AlwaysBreakBeforeMultilineStrings: false
|
AlwaysBreakBeforeMultilineStrings: false
|
||||||
AlwaysBreakTemplateDeclarations: No
|
AlwaysBreakTemplateDeclarations: No
|
||||||
|
AttributeMacros:
|
||||||
|
- __capability
|
||||||
BinPackArguments: true
|
BinPackArguments: true
|
||||||
BinPackParameters: true
|
BinPackParameters: true
|
||||||
BreakBeforeBraces: false
|
BraceWrapping:
|
||||||
BreakBeforeBinaryOperators: false
|
AfterCaseLabel: false
|
||||||
BreakBeforeBraces: Stroustrup
|
AfterClass: false
|
||||||
BreakBeforeTernaryOperators: false
|
AfterControlStatement: Never
|
||||||
BreakConstructorInitializers: BeforeColon
|
AfterEnum: false
|
||||||
|
AfterFunction: false
|
||||||
|
AfterNamespace: false
|
||||||
|
AfterObjCDeclaration: false
|
||||||
|
AfterStruct: false
|
||||||
|
AfterUnion: false
|
||||||
|
AfterExternBlock: false
|
||||||
|
BeforeCatch: false
|
||||||
|
BeforeElse: true
|
||||||
|
BeforeLambdaBody: false
|
||||||
|
BeforeWhile: false
|
||||||
|
IndentBraces: false
|
||||||
|
SplitEmptyFunction: false
|
||||||
|
SplitEmptyRecord: false
|
||||||
|
SplitEmptyNamespace: false
|
||||||
|
BreakBeforeBinaryOperators: None
|
||||||
|
BreakBeforeConceptDeclarations: Always
|
||||||
|
BreakBeforeBraces: Custom
|
||||||
|
BreakBeforeInheritanceComma: false
|
||||||
BreakInheritanceList: BeforeColon
|
BreakInheritanceList: BeforeColon
|
||||||
|
BreakBeforeTernaryOperators: false
|
||||||
|
BreakConstructorInitializersBeforeComma: false
|
||||||
|
BreakConstructorInitializers: BeforeColon
|
||||||
|
BreakAfterJavaFieldAnnotations: false
|
||||||
BreakStringLiterals: false
|
BreakStringLiterals: false
|
||||||
ColumnLimit: 0
|
ColumnLimit: 0
|
||||||
|
CommentPragmas: '^ IWYU pragma:'
|
||||||
|
QualifierAlignment: Leave
|
||||||
CompactNamespaces: false
|
CompactNamespaces: false
|
||||||
ConstructorInitializerAllOnOneLineOrOnePerLine: false
|
|
||||||
ConstructorInitializerIndentWidth: 4
|
ConstructorInitializerIndentWidth: 4
|
||||||
ContinuationIndentWidth: 2
|
ContinuationIndentWidth: 2
|
||||||
Cpp11BracedListStyle: false
|
Cpp11BracedListStyle: false
|
||||||
DeriveLineEnding: true
|
DeriveLineEnding: true
|
||||||
DerivePointerAlignment: true
|
DerivePointerAlignment: false
|
||||||
DisableFormat: false
|
DisableFormat: false
|
||||||
ExperimentalAutoDetectBinPacking: true
|
EmptyLineAfterAccessModifier: Never
|
||||||
|
EmptyLineBeforeAccessModifier: LogicalBlock
|
||||||
|
ExperimentalAutoDetectBinPacking: false
|
||||||
|
PackConstructorInitializers: BinPack
|
||||||
|
BasedOnStyle: ''
|
||||||
|
ConstructorInitializerAllOnOneLineOrOnePerLine: false
|
||||||
|
AllowAllConstructorInitializersOnNextLine: true
|
||||||
FixNamespaceComments: true
|
FixNamespaceComments: true
|
||||||
IncludeBlocks: Preserve
|
ForEachMacros:
|
||||||
|
- foreach
|
||||||
|
- Q_FOREACH
|
||||||
|
- BOOST_FOREACH
|
||||||
|
IfMacros:
|
||||||
|
- KJ_IF_MAYBE
|
||||||
|
IncludeBlocks: Preserve
|
||||||
|
IncludeCategories:
|
||||||
|
- Regex: '^"(llvm|llvm-c|clang|clang-c)/'
|
||||||
|
Priority: 2
|
||||||
|
SortPriority: 0
|
||||||
|
CaseSensitive: false
|
||||||
|
- Regex: '^(<|"(gtest|gmock|isl|json)/)'
|
||||||
|
Priority: 3
|
||||||
|
SortPriority: 0
|
||||||
|
CaseSensitive: false
|
||||||
|
- Regex: '.*'
|
||||||
|
Priority: 1
|
||||||
|
SortPriority: 0
|
||||||
|
CaseSensitive: false
|
||||||
|
IncludeIsMainRegex: '(Test)?$'
|
||||||
|
IncludeIsMainSourceRegex: ''
|
||||||
|
IndentAccessModifiers: false
|
||||||
IndentCaseLabels: true
|
IndentCaseLabels: true
|
||||||
|
IndentCaseBlocks: false
|
||||||
IndentGotoLabels: true
|
IndentGotoLabels: true
|
||||||
IndentPPDirectives: AfterHash
|
IndentPPDirectives: AfterHash
|
||||||
IndentWidth: 2
|
IndentExternBlock: AfterExternBlock
|
||||||
|
IndentRequiresClause: true
|
||||||
|
IndentWidth: 2
|
||||||
IndentWrappedFunctionNames: false
|
IndentWrappedFunctionNames: false
|
||||||
|
InsertBraces: false
|
||||||
|
InsertTrailingCommas: None
|
||||||
|
JavaScriptQuotes: Leave
|
||||||
|
JavaScriptWrapImports: true
|
||||||
KeepEmptyLinesAtTheStartOfBlocks: true
|
KeepEmptyLinesAtTheStartOfBlocks: true
|
||||||
|
LambdaBodyIndentation: Signature
|
||||||
|
MacroBlockBegin: ''
|
||||||
|
MacroBlockEnd: ''
|
||||||
MaxEmptyLinesToKeep: 100
|
MaxEmptyLinesToKeep: 100
|
||||||
NamespaceIndentation: None
|
NamespaceIndentation: None
|
||||||
|
ObjCBinPackProtocolList: Auto
|
||||||
ObjCBlockIndentWidth: 2
|
ObjCBlockIndentWidth: 2
|
||||||
|
ObjCBreakBeforeNestedBlockParam: true
|
||||||
ObjCSpaceAfterProperty: false
|
ObjCSpaceAfterProperty: false
|
||||||
ObjCSpaceBeforeProtocolList: false
|
ObjCSpaceBeforeProtocolList: true
|
||||||
PenaltyBreakAssignment: 0
|
PenaltyBreakAssignment: 0
|
||||||
PenaltyBreakBeforeFirstCallParameter: 0
|
PenaltyBreakBeforeFirstCallParameter: 0
|
||||||
PenaltyBreakComment: 0
|
PenaltyBreakComment: 0
|
||||||
PenaltyBreakFirstLessLess: 0
|
PenaltyBreakFirstLessLess: 0
|
||||||
|
PenaltyBreakOpenParenthesis: 0
|
||||||
PenaltyBreakString: 0
|
PenaltyBreakString: 0
|
||||||
PenaltyBreakTemplateDeclaration: 0
|
PenaltyBreakTemplateDeclaration: 0
|
||||||
PenaltyExcessCharacter: 0
|
PenaltyExcessCharacter: 0
|
||||||
PenaltyReturnTypeOnItsOwnLine: 0
|
PenaltyReturnTypeOnItsOwnLine: 0
|
||||||
|
PenaltyIndentedWhitespace: 0
|
||||||
PointerAlignment: Right
|
PointerAlignment: Right
|
||||||
ReflowComments: false
|
PPIndentWidth: -1
|
||||||
SortIncludes: false
|
ReferenceAlignment: Pointer
|
||||||
|
ReflowComments: false
|
||||||
|
RemoveBracesLLVM: false
|
||||||
|
RequiresClausePosition: OwnLine
|
||||||
|
SeparateDefinitionBlocks: Leave
|
||||||
|
ShortNamespaceLines: 1
|
||||||
|
SortIncludes: false
|
||||||
|
SortJavaStaticImport: Before
|
||||||
SortUsingDeclarations: false
|
SortUsingDeclarations: false
|
||||||
SpaceAfterCStyleCast: false
|
SpaceAfterCStyleCast: false
|
||||||
SpaceAfterLogicalNot: false
|
SpaceAfterLogicalNot: false
|
||||||
SpaceAfterTemplateKeyword: false
|
SpaceAfterTemplateKeyword: false
|
||||||
SpaceBeforeAssignmentOperators: true
|
SpaceBeforeAssignmentOperators: true
|
||||||
|
SpaceBeforeCaseColon: false
|
||||||
SpaceBeforeCpp11BracedList: true
|
SpaceBeforeCpp11BracedList: true
|
||||||
SpaceBeforeCtorInitializerColon: true
|
SpaceBeforeCtorInitializerColon: true
|
||||||
SpaceBeforeInheritanceColon: true
|
SpaceBeforeInheritanceColon: true
|
||||||
SpaceBeforeParens: ControlStatements
|
SpaceBeforeParens: ControlStatements
|
||||||
|
SpaceBeforeParensOptions:
|
||||||
|
AfterControlStatements: true
|
||||||
|
AfterForeachMacros: true
|
||||||
|
AfterFunctionDefinitionName: false
|
||||||
|
AfterFunctionDeclarationName: false
|
||||||
|
AfterIfMacros: true
|
||||||
|
AfterOverloadedOperator: false
|
||||||
|
AfterRequiresInClause: false
|
||||||
|
AfterRequiresInExpression: false
|
||||||
|
BeforeNonEmptyParentheses: false
|
||||||
|
SpaceAroundPointerQualifiers: Default
|
||||||
SpaceBeforeRangeBasedForLoopColon: true
|
SpaceBeforeRangeBasedForLoopColon: true
|
||||||
SpaceBeforeSquareBrackets: false
|
|
||||||
SpaceInEmptyBlock: false
|
SpaceInEmptyBlock: false
|
||||||
SpaceInEmptyParentheses: false
|
SpaceInEmptyParentheses: false
|
||||||
SpacesBeforeTrailingComments: 2
|
SpacesBeforeTrailingComments: 2
|
||||||
SpacesInAngles: false
|
SpacesInAngles: Never
|
||||||
SpacesInCStyleCastParentheses: false
|
|
||||||
SpacesInConditionalStatement: false
|
SpacesInConditionalStatement: false
|
||||||
SpacesInContainerLiterals: true
|
SpacesInContainerLiterals: true
|
||||||
|
SpacesInCStyleCastParentheses: false
|
||||||
|
SpacesInLineCommentPrefix:
|
||||||
|
Minimum: 1
|
||||||
|
Maximum: -1
|
||||||
SpacesInParentheses: false
|
SpacesInParentheses: false
|
||||||
SpacesInSquareBrackets: false
|
SpacesInSquareBrackets: false
|
||||||
UseCRLF: false
|
SpaceBeforeSquareBrackets: false
|
||||||
UseTab: Never
|
BitFieldColonSpacing: Both
|
||||||
BreakBeforeBraces: Custom
|
Standard: Latest
|
||||||
BraceWrapping:
|
StatementAttributeLikeMacros:
|
||||||
AfterFunction: false
|
- Q_EMIT
|
||||||
AfterCaseLabel: false
|
StatementMacros:
|
||||||
AfterStruct: false
|
- Q_UNUSED
|
||||||
AfterClass: false
|
- QT_REQUIRE_VERSION
|
||||||
AfterEnum: false
|
TabWidth: 8
|
||||||
AfterUnion: false
|
UseCRLF: false
|
||||||
AfterControlStatement: Never
|
UseTab: Never
|
||||||
AfterNamespace: false
|
WhitespaceSensitiveMacros:
|
||||||
AfterObjCDeclaration: false
|
- STRINGIZE
|
||||||
AfterExternBlock: false
|
- PP_STRINGIZE
|
||||||
BeforeCatch: false
|
- BOOST_PP_STRINGIZE
|
||||||
BeforeElse: true
|
- NS_SWIFT_NAME
|
||||||
SplitEmptyFunction: false
|
- CF_SWIFT_NAME
|
||||||
SplitEmptyRecord: false
|
...
|
||||||
SplitEmptyNamespace: false
|
|
||||||
|
|||||||
8
.github/ISSUE_TEMPLATE.md
vendored
8
.github/ISSUE_TEMPLATE.md
vendored
@@ -7,11 +7,9 @@ assignees: ''
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
See the FAQ before opening an issue: https://wiki.strawberrymusicplayer.org/wiki/FAQ
|
- [ ] I have checked the [FAQ](https://wiki.strawberrymusicplayer.org/wiki/FAQ) for answers.
|
||||||
Check the Changelog to see if the issue is already fixed: https://github.com/strawberrymusicplayer/strawberry/blob/master/Changelog
|
- [ ] I have checked the [Changelog](https://github.com/strawberrymusicplayer/strawberry/blob/master/Changelog) that the issue is not already fixed.
|
||||||
If it's already fixed, try the latest development build from: https://builds.strawberrymusicplayer.org/
|
- [ ] I believe this issue is a bug, and not a general technical issue, question or feature requests that can be discussed on the [forum](https://forum.strawberrymusicplayer.org/).
|
||||||
For technical issues, questions and discussion please use the forum on https://forum.strawberrymusicplayer.org/
|
|
||||||
Any issues related to feature requests will be closed. See the README for more details.
|
|
||||||
|
|
||||||
**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.
|
||||||
|
|||||||
6
.github/dependabot.yml
vendored
Normal file
6
.github/dependabot.yml
vendored
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
version: 2
|
||||||
|
updates:
|
||||||
|
- package-ecosystem: "github-actions"
|
||||||
|
directory: "/"
|
||||||
|
schedule:
|
||||||
|
interval: daily
|
||||||
1063
.github/workflows/build.yml
vendored
Normal file
1063
.github/workflows/build.yml
vendored
Normal file
File diff suppressed because it is too large
Load Diff
2914
.github/workflows/ccpp.yml
vendored
2914
.github/workflows/ccpp.yml
vendored
File diff suppressed because it is too large
Load Diff
88
.github/workflows/codeql.yml
vendored
Normal file
88
.github/workflows/codeql.yml
vendored
Normal file
@@ -0,0 +1,88 @@
|
|||||||
|
name: CodeQL
|
||||||
|
on: [push, pull_request]
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
codeql:
|
||||||
|
name: CodeQL Analyze
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
|
container:
|
||||||
|
image: opensuse/tumbleweed
|
||||||
|
|
||||||
|
steps:
|
||||||
|
|
||||||
|
- name: Refresh repositories
|
||||||
|
run: zypper -n --gpg-auto-import-keys ref
|
||||||
|
|
||||||
|
- name: Upgrade packages
|
||||||
|
run: zypper -n --gpg-auto-import-keys dup
|
||||||
|
|
||||||
|
- name: Install packages
|
||||||
|
run: >
|
||||||
|
zypper -n --gpg-auto-import-keys in
|
||||||
|
lsb-release
|
||||||
|
rpm-build
|
||||||
|
git
|
||||||
|
tar
|
||||||
|
gcc
|
||||||
|
gcc-c++
|
||||||
|
make
|
||||||
|
cmake
|
||||||
|
gettext-tools
|
||||||
|
glibc-devel
|
||||||
|
libboost_headers-devel
|
||||||
|
boost-devel
|
||||||
|
glib2-devel
|
||||||
|
glib2-tools
|
||||||
|
dbus-1-devel
|
||||||
|
alsa-devel
|
||||||
|
libnotify-devel
|
||||||
|
libgnutls-devel
|
||||||
|
protobuf-devel
|
||||||
|
sqlite3-devel
|
||||||
|
libpulse-devel
|
||||||
|
gstreamer-devel
|
||||||
|
gstreamer-plugins-base-devel
|
||||||
|
vlc-devel
|
||||||
|
taglib-devel
|
||||||
|
libicu-devel
|
||||||
|
libcdio-devel
|
||||||
|
libgpod-devel
|
||||||
|
libmtp-devel
|
||||||
|
libchromaprint-devel
|
||||||
|
qt6-core-devel
|
||||||
|
qt6-gui-devel
|
||||||
|
qt6-widgets-devel
|
||||||
|
qt6-concurrent-devel
|
||||||
|
qt6-network-devel
|
||||||
|
qt6-sql-devel
|
||||||
|
qt6-dbus-devel
|
||||||
|
qt6-test-devel
|
||||||
|
qt6-base-common-devel
|
||||||
|
qt6-sql-sqlite
|
||||||
|
qt6-linguist-devel
|
||||||
|
desktop-file-utils
|
||||||
|
update-desktop-files
|
||||||
|
appstream-glib
|
||||||
|
hicolor-icon-theme
|
||||||
|
|
||||||
|
- name: Checkout repository
|
||||||
|
uses: actions/checkout@v3
|
||||||
|
with:
|
||||||
|
fetch-depth: 0
|
||||||
|
|
||||||
|
- name: Add safe git directory
|
||||||
|
run: git config --global --add safe.directory ${GITHUB_WORKSPACE}
|
||||||
|
|
||||||
|
- name: Initialize CodeQL
|
||||||
|
uses: github/codeql-action/init@v2
|
||||||
|
with:
|
||||||
|
languages: cpp
|
||||||
|
|
||||||
|
- name: Autobuild
|
||||||
|
uses: github/codeql-action/autobuild@v2
|
||||||
|
|
||||||
|
- name: Perform CodeQL Analysis
|
||||||
|
uses: github/codeql-action/analyze@v2
|
||||||
|
with:
|
||||||
|
category: "/language:cpp"
|
||||||
34
3rdparty/macdeployqt/shared.cpp
vendored
34
3rdparty/macdeployqt/shared.cpp
vendored
@@ -40,6 +40,8 @@
|
|||||||
#include <QDir>
|
#include <QDir>
|
||||||
#include <QSet>
|
#include <QSet>
|
||||||
#include <QList>
|
#include <QList>
|
||||||
|
#include <QVariant>
|
||||||
|
#include <QVariantMap>
|
||||||
#include <QStack>
|
#include <QStack>
|
||||||
#include <QDirIterator>
|
#include <QDirIterator>
|
||||||
#include <QLibraryInfo>
|
#include <QLibraryInfo>
|
||||||
@@ -187,9 +189,7 @@ OtoolInfo findDependencyInfo(const QString &binaryPath)
|
|||||||
return info;
|
return info;
|
||||||
}
|
}
|
||||||
|
|
||||||
static const QRegularExpression regexp(QStringLiteral(
|
static const QRegularExpression regexp(QStringLiteral("^\\t(.+) \\(compatibility version (\\d+\\.\\d+\\.\\d+), current version (\\d+\\.\\d+\\.\\d+)(, weak|, reexport)?\\)$"));
|
||||||
"^\\t(.+) \\(compatibility version (\\d+\\.\\d+\\.\\d+), "
|
|
||||||
"current version (\\d+\\.\\d+\\.\\d+)(, weak)?\\)$"));
|
|
||||||
|
|
||||||
QString output = otool.readAllStandardOutput();
|
QString output = otool.readAllStandardOutput();
|
||||||
QStringList outputLines = output.split("\n", Qt::SkipEmptyParts);
|
QStringList outputLines = output.split("\n", Qt::SkipEmptyParts);
|
||||||
@@ -220,6 +220,8 @@ OtoolInfo findDependencyInfo(const QString &binaryPath)
|
|||||||
for (const QString &outputLine : outputLines) {
|
for (const QString &outputLine : outputLines) {
|
||||||
const auto match = regexp.match(outputLine);
|
const auto match = regexp.match(outputLine);
|
||||||
if (match.hasMatch()) {
|
if (match.hasMatch()) {
|
||||||
|
if (match.captured(1) == info.installName)
|
||||||
|
continue; // Another arch reference to the same binary
|
||||||
DylibInfo dylib;
|
DylibInfo dylib;
|
||||||
dylib.binaryPath = match.captured(1);
|
dylib.binaryPath = match.captured(1);
|
||||||
dylib.compatibilityVersion = QVersionNumber::fromString(match.captured(2));
|
dylib.compatibilityVersion = QVersionNumber::fromString(match.captured(2));
|
||||||
@@ -300,11 +302,11 @@ FrameworkInfo parseOtoolLibraryLine(const QString &line, const QString &appBundl
|
|||||||
if (state == QtPath) {
|
if (state == QtPath) {
|
||||||
// Check for library name part
|
// Check for library name part
|
||||||
if (part < parts.count() && parts.at(part).contains(".dylib")) {
|
if (part < parts.count() && parts.at(part).contains(".dylib")) {
|
||||||
info.frameworkDirectory += "/" + (qtPath + currentPart + "/").simplified();
|
info.frameworkDirectory += "/" + QString(qtPath + currentPart + "/").simplified();
|
||||||
state = DylibName;
|
state = DylibName;
|
||||||
continue;
|
continue;
|
||||||
} else if (part < parts.count() && parts.at(part).endsWith(".framework")) {
|
} else if (part < parts.count() && parts.at(part).endsWith(".framework")) {
|
||||||
info.frameworkDirectory += "/" + (qtPath + "lib/").simplified();
|
info.frameworkDirectory += "/" + QString(qtPath + "lib/").simplified();
|
||||||
state = FrameworkName;
|
state = FrameworkName;
|
||||||
continue;
|
continue;
|
||||||
} else if (trimmed.startsWith("/") == false) { // If the line does not contain a full path, the app is using a binary Qt package.
|
} else if (trimmed.startsWith("/") == false) { // If the line does not contain a full path, the app is using a binary Qt package.
|
||||||
@@ -865,6 +867,18 @@ void changeInstallName(const QString &bundlePath, const FrameworkInfo &framework
|
|||||||
if (!framework.installName.isEmpty() && framework.installName != framework.sourceFilePath) {
|
if (!framework.installName.isEmpty() && framework.installName != framework.sourceFilePath) {
|
||||||
changeInstallName(framework.installName, deployedInstallName, binary);
|
changeInstallName(framework.installName, deployedInstallName, binary);
|
||||||
}
|
}
|
||||||
|
// Workaround for the case when the library ID name is a symlink, while the dependencies
|
||||||
|
// specified using the canonical path to the library (QTBUG-56814)
|
||||||
|
QFileInfo fileInfo= QFileInfo(framework.installName);
|
||||||
|
QString canonicalInstallName = fileInfo.canonicalFilePath();
|
||||||
|
if (!canonicalInstallName.isEmpty() && canonicalInstallName != framework.installName) {
|
||||||
|
changeInstallName(canonicalInstallName, deployedInstallName, binary);
|
||||||
|
// some libraries' inner dependencies (such as ffmpeg, nettle) use symbol link (QTBUG-100093)
|
||||||
|
QString innerDependency = fileInfo.canonicalPath() + "/" + fileInfo.fileName();
|
||||||
|
if (innerDependency != canonicalInstallName && innerDependency != framework.installName) {
|
||||||
|
changeInstallName(innerDependency, deployedInstallName, binary);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1078,7 +1092,7 @@ QString getLibInfix(const QStringList &deployedFrameworks)
|
|||||||
{
|
{
|
||||||
QString libInfix;
|
QString libInfix;
|
||||||
for (const QString &framework : deployedFrameworks) {
|
for (const QString &framework : deployedFrameworks) {
|
||||||
if (framework.startsWith(QStringLiteral("QtCore")) && framework.endsWith(QStringLiteral(".framework"))) {
|
if (framework.startsWith(QStringLiteral("QtCore")) && framework.endsWith(QStringLiteral(".framework")) && !framework.contains(QStringLiteral("5Compat"))) {
|
||||||
Q_ASSERT(framework.length() >= 16);
|
Q_ASSERT(framework.length() >= 16);
|
||||||
// 16 == "QtCore" + ".framework"
|
// 16 == "QtCore" + ".framework"
|
||||||
const int lengthOfLibInfix = framework.length() - 16;
|
const int lengthOfLibInfix = framework.length() - 16;
|
||||||
@@ -1301,7 +1315,7 @@ void deployPlugins(const ApplicationBundleInfo &appBundleInfo, const QString &pl
|
|||||||
<< "libgstfdkaac.dylib"
|
<< "libgstfdkaac.dylib"
|
||||||
<< "libgstflac.dylib"
|
<< "libgstflac.dylib"
|
||||||
<< "libgstgio.dylib"
|
<< "libgstgio.dylib"
|
||||||
<< "libgstgme.dylib"
|
//<< "libgstgme.dylib"
|
||||||
<< "libgsthls.dylib"
|
<< "libgsthls.dylib"
|
||||||
<< "libgsticydemux.dylib"
|
<< "libgsticydemux.dylib"
|
||||||
<< "libgstid3demux.dylib"
|
<< "libgstid3demux.dylib"
|
||||||
@@ -1474,9 +1488,9 @@ bool deployQmlImports(const QString &appBundlePath, DeploymentInfo deploymentInf
|
|||||||
for (const QString &importPath : qmlImportPaths)
|
for (const QString &importPath : qmlImportPaths)
|
||||||
argumentList << "-importPath" << importPath;
|
argumentList << "-importPath" << importPath;
|
||||||
#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
|
#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
|
||||||
QString qmlImportsPath = QLibraryInfo::path(QLibraryInfo::Qml2ImportsPath);
|
QString qmlImportsPath = QLibraryInfo::path(QLibraryInfo::QmlImportsPath);
|
||||||
#else
|
#else
|
||||||
QString qmlImportsPath = QLibraryInfo::location(QLibraryInfo::Qml2ImportsPath);
|
QString qmlImportsPath = QLibraryInfo::location(QLibraryInfo::QmlImportsPath);
|
||||||
#endif
|
#endif
|
||||||
argumentList.append( "-importPath");
|
argumentList.append( "-importPath");
|
||||||
argumentList.append(qmlImportsPath);
|
argumentList.append(qmlImportsPath);
|
||||||
@@ -1488,7 +1502,7 @@ bool deployQmlImports(const QString &appBundlePath, DeploymentInfo deploymentInf
|
|||||||
LogError() << "Could not start qmlimpoortscanner. Process error is" << qmlImportScanner.errorString();
|
LogError() << "Could not start qmlimpoortscanner. Process error is" << qmlImportScanner.errorString();
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
qmlImportScanner.waitForFinished();
|
qmlImportScanner.waitForFinished(-1);
|
||||||
|
|
||||||
// log qmlimportscanner errors
|
// log qmlimportscanner errors
|
||||||
qmlImportScanner.setReadChannel(QProcess::StandardError);
|
qmlImportScanner.setReadChannel(QProcess::StandardError);
|
||||||
|
|||||||
30
3rdparty/singleapplication/CMakeLists.txt
vendored
30
3rdparty/singleapplication/CMakeLists.txt
vendored
@@ -6,31 +6,7 @@ include(CheckFunctionExists)
|
|||||||
check_function_exists(geteuid HAVE_GETEUID)
|
check_function_exists(geteuid HAVE_GETEUID)
|
||||||
check_function_exists(getpwuid HAVE_GETPWUID)
|
check_function_exists(getpwuid HAVE_GETPWUID)
|
||||||
|
|
||||||
set(SINGLEAPP-SOURCES singleapplication.cpp singleapplication_p.cpp)
|
|
||||||
set(SINGLEAPP-MOC-HEADERS singleapplication.h singleapplication_p.h)
|
|
||||||
qt_wrap_cpp(SINGLEAPP-SOURCES-MOC ${SINGLEAPP-MOC-HEADERS})
|
|
||||||
add_library(singleapplication STATIC ${SINGLEAPP-SOURCES} ${SINGLEAPP-SOURCES-MOC})
|
|
||||||
target_include_directories(singleapplication PRIVATE
|
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}
|
|
||||||
${CMAKE_CURRENT_BINARY_DIR}
|
|
||||||
)
|
|
||||||
target_link_libraries(singleapplication PRIVATE
|
|
||||||
${QtCore_LIBRARIES}
|
|
||||||
${QtWidgets_LIBRARIES}
|
|
||||||
${QtNetwork_LIBRARIES}
|
|
||||||
)
|
|
||||||
|
|
||||||
set(SINGLECOREAPP-SOURCES singlecoreapplication.cpp singlecoreapplication_p.cpp)
|
|
||||||
set(SINGLECOREAPP-MOC-HEADERS singlecoreapplication.h singlecoreapplication_p.h)
|
|
||||||
qt_wrap_cpp(SINGLECOREAPP-SOURCES-MOC ${SINGLECOREAPP-MOC-HEADERS})
|
|
||||||
add_library(singlecoreapplication STATIC ${SINGLECOREAPP-SOURCES} ${SINGLECOREAPP-SOURCES-MOC})
|
|
||||||
target_include_directories(singlecoreapplication PRIVATE
|
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}
|
|
||||||
${CMAKE_CURRENT_BINARY_DIR}
|
|
||||||
)
|
|
||||||
target_link_libraries(singlecoreapplication PRIVATE
|
|
||||||
${QtCore_LIBRARIES}
|
|
||||||
${QtNetwork_LIBRARIES}
|
|
||||||
)
|
|
||||||
|
|
||||||
configure_file(config.h.in "${CMAKE_CURRENT_BINARY_DIR}/config.h")
|
configure_file(config.h.in "${CMAKE_CURRENT_BINARY_DIR}/config.h")
|
||||||
|
|
||||||
|
add_subdirectory(singleapplication)
|
||||||
|
add_subdirectory(singlecoreapplication)
|
||||||
|
|||||||
266
3rdparty/singleapplication/singleapplication.cpp
vendored
266
3rdparty/singleapplication/singleapplication.cpp
vendored
@@ -1,266 +0,0 @@
|
|||||||
// The MIT License (MIT)
|
|
||||||
//
|
|
||||||
// Copyright (c) Itay Grudev 2015 - 2020
|
|
||||||
//
|
|
||||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
||||||
// of this software and associated documentation files (the "Software"), to deal
|
|
||||||
// in the Software without restriction, including without limitation the rights
|
|
||||||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
||||||
// copies of the Software, and to permit persons to whom the Software is
|
|
||||||
// furnished to do so, subject to the following conditions:
|
|
||||||
//
|
|
||||||
// The above copyright notice and this permission notice shall be included in
|
|
||||||
// all copies or substantial portions of the Software.
|
|
||||||
//
|
|
||||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
||||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
||||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
||||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
||||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
||||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
|
||||||
// THE SOFTWARE.
|
|
||||||
|
|
||||||
//
|
|
||||||
// W A R N I N G !!!
|
|
||||||
// -----------------
|
|
||||||
//
|
|
||||||
// This is a modified version of SingleApplication,
|
|
||||||
// The original version is at:
|
|
||||||
//
|
|
||||||
// https://github.com/itay-grudev/SingleApplication
|
|
||||||
//
|
|
||||||
//
|
|
||||||
|
|
||||||
#include <cstdlib>
|
|
||||||
#include <limits>
|
|
||||||
|
|
||||||
#include <QtGlobal>
|
|
||||||
#include <QApplication>
|
|
||||||
#include <QThread>
|
|
||||||
#include <QSharedMemory>
|
|
||||||
#include <QLocalSocket>
|
|
||||||
#include <QByteArray>
|
|
||||||
#include <QElapsedTimer>
|
|
||||||
#include <QtDebug>
|
|
||||||
|
|
||||||
#include "singleapplication.h"
|
|
||||||
#include "singleapplication_p.h"
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Constructor. Checks and fires up LocalServer or closes the program if another instance already exists
|
|
||||||
* @param argc
|
|
||||||
* @param argv
|
|
||||||
* @param allowSecondary Whether to enable secondary instance support
|
|
||||||
* @param options Optional flags to toggle specific behaviour
|
|
||||||
* @param timeout Maximum time blocking functions are allowed during app load
|
|
||||||
*/
|
|
||||||
SingleApplication::SingleApplication(int &argc, char *argv[], const bool allowSecondary, const Options options, const int timeout)
|
|
||||||
: app_t(argc, argv),
|
|
||||||
d_ptr(new SingleApplicationPrivate(this)) {
|
|
||||||
|
|
||||||
Q_D(SingleApplication);
|
|
||||||
|
|
||||||
// Store the current mode of the program
|
|
||||||
d->options_ = options;
|
|
||||||
|
|
||||||
// Generating an application ID used for identifying the shared memory block and QLocalServer
|
|
||||||
d->genBlockServerName();
|
|
||||||
|
|
||||||
// To mitigate QSharedMemory issues with large amount of processes attempting to attach at the same time
|
|
||||||
SingleApplicationPrivate::randomSleep();
|
|
||||||
|
|
||||||
#ifdef Q_OS_UNIX
|
|
||||||
// By explicitly attaching it and then deleting it we make sure that the memory is deleted even after the process has crashed on Unix.
|
|
||||||
d->memory_ = new QSharedMemory(d->blockServerName_);
|
|
||||||
d->memory_->attach();
|
|
||||||
delete d->memory_;
|
|
||||||
#endif
|
|
||||||
|
|
||||||
// Guarantee thread safe behaviour with a shared memory block.
|
|
||||||
d->memory_ = new QSharedMemory(d->blockServerName_);
|
|
||||||
|
|
||||||
// Create a shared memory block
|
|
||||||
if (d->memory_->create(sizeof(InstancesInfo))) {
|
|
||||||
// Initialize the shared memory block
|
|
||||||
if (!d->memory_->lock()) {
|
|
||||||
qCritical() << "SingleApplication: Unable to lock memory block after create.";
|
|
||||||
abortSafely();
|
|
||||||
}
|
|
||||||
d->initializeMemoryBlock();
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
if (d->memory_->error() == QSharedMemory::AlreadyExists) {
|
|
||||||
// Attempt to attach to the memory segment
|
|
||||||
if (!d->memory_->attach()) {
|
|
||||||
qCritical() << "SingleApplication: Unable to attach to shared memory block.";
|
|
||||||
abortSafely();
|
|
||||||
}
|
|
||||||
if (!d->memory_->lock()) {
|
|
||||||
qCritical() << "SingleApplication: Unable to lock memory block after attach.";
|
|
||||||
abortSafely();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
qCritical() << "SingleApplication: Unable to create block.";
|
|
||||||
abortSafely();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
InstancesInfo *instance = static_cast<InstancesInfo*>(d->memory_->data());
|
|
||||||
QElapsedTimer time;
|
|
||||||
time.start();
|
|
||||||
|
|
||||||
// Make sure the shared memory block is initialised and in consistent state
|
|
||||||
forever {
|
|
||||||
// If the shared memory block's checksum is valid continue
|
|
||||||
if (d->blockChecksum() == instance->checksum) break;
|
|
||||||
|
|
||||||
// If more than 5s have elapsed, assume the primary instance crashed and assume it's position
|
|
||||||
if (time.elapsed() > 5000) {
|
|
||||||
qWarning() << "SingleApplication: Shared memory block has been in an inconsistent state from more than 5s. Assuming primary instance failure.";
|
|
||||||
d->initializeMemoryBlock();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Otherwise wait for a random period and try again.
|
|
||||||
// The random sleep here limits the probability of a collision between two racing apps and allows the app to initialise faster
|
|
||||||
if (!d->memory_->unlock()) {
|
|
||||||
qDebug() << "SingleApplication: Unable to unlock memory for random wait.";
|
|
||||||
qDebug() << d->memory_->errorString();
|
|
||||||
}
|
|
||||||
SingleApplicationPrivate::randomSleep();
|
|
||||||
if (!d->memory_->lock()) {
|
|
||||||
qCritical() << "SingleApplication: Unable to lock memory after random wait.";
|
|
||||||
abortSafely();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!instance->primary) {
|
|
||||||
d->startPrimary();
|
|
||||||
if (!d->memory_->unlock()) {
|
|
||||||
qDebug() << "SingleApplication: Unable to unlock memory after primary start.";
|
|
||||||
qDebug() << d->memory_->errorString();
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check if another instance can be started
|
|
||||||
if (allowSecondary) {
|
|
||||||
d->startSecondary();
|
|
||||||
if (d->options_ & Mode::SecondaryNotification) {
|
|
||||||
d->connectToPrimary(timeout, SingleApplicationPrivate::SecondaryInstance);
|
|
||||||
}
|
|
||||||
if (!d->memory_->unlock()) {
|
|
||||||
qDebug() << "SingleApplication: Unable to unlock memory after secondary start.";
|
|
||||||
qDebug() << d->memory_->errorString();
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!d->memory_->unlock()) {
|
|
||||||
qDebug() << "SingleApplication: Unable to unlock memory at end of execution.";
|
|
||||||
qDebug() << d->memory_->errorString();
|
|
||||||
}
|
|
||||||
|
|
||||||
d->connectToPrimary(timeout, SingleApplicationPrivate::NewInstance);
|
|
||||||
|
|
||||||
delete d;
|
|
||||||
|
|
||||||
::exit(EXIT_SUCCESS);
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
SingleApplication::~SingleApplication() {
|
|
||||||
Q_D(SingleApplication);
|
|
||||||
delete d;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Checks if the current application instance is primary.
|
|
||||||
* @return Returns true if the instance is primary, false otherwise.
|
|
||||||
*/
|
|
||||||
bool SingleApplication::isPrimary() const {
|
|
||||||
Q_D(const SingleApplication);
|
|
||||||
return d->server_ != nullptr;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Checks if the current application instance is secondary.
|
|
||||||
* @return Returns true if the instance is secondary, false otherwise.
|
|
||||||
*/
|
|
||||||
bool SingleApplication::isSecondary() const {
|
|
||||||
Q_D(const SingleApplication);
|
|
||||||
return d->server_ == nullptr;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Allows you to identify an instance by returning unique consecutive instance ids.
|
|
||||||
* It is reset when the first (primary) instance of your app starts and only incremented afterwards.
|
|
||||||
* @return Returns a unique instance id.
|
|
||||||
*/
|
|
||||||
quint32 SingleApplication::instanceId() const {
|
|
||||||
Q_D(const SingleApplication);
|
|
||||||
return d->instanceNumber_;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the OS PID (Process Identifier) of the process running the primary instance.
|
|
||||||
* Especially useful when SingleApplication is coupled with OS. specific APIs.
|
|
||||||
* @return Returns the primary instance PID.
|
|
||||||
*/
|
|
||||||
qint64 SingleApplication::primaryPid() const {
|
|
||||||
Q_D(const SingleApplication);
|
|
||||||
return d->primaryPid();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the username the primary instance is running as.
|
|
||||||
* @return Returns the username the primary instance is running as.
|
|
||||||
*/
|
|
||||||
QString SingleApplication::primaryUser() const {
|
|
||||||
Q_D(const 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() const {
|
|
||||||
return SingleApplicationPrivate::getUsername();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Sends message to the Primary Instance.
|
|
||||||
* @param message The message to send.
|
|
||||||
* @param timeout the maximum timeout in milliseconds for blocking functions.
|
|
||||||
* @return true if the message was sent successfully, false otherwise.
|
|
||||||
*/
|
|
||||||
bool SingleApplication::sendMessage(const QByteArray &message, const int timeout) {
|
|
||||||
|
|
||||||
Q_D(SingleApplication);
|
|
||||||
|
|
||||||
// Nobody to connect to
|
|
||||||
if (isPrimary()) return false;
|
|
||||||
|
|
||||||
// Make sure the socket is connected
|
|
||||||
if (!d->connectToPrimary(timeout, SingleApplicationPrivate::Reconnect)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return d->writeConfirmedMessage(timeout, message);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Cleans up the shared memory block and exits with a failure.
|
|
||||||
* This function halts program execution.
|
|
||||||
*/
|
|
||||||
void SingleApplication::abortSafely() {
|
|
||||||
|
|
||||||
Q_D(SingleApplication);
|
|
||||||
|
|
||||||
qCritical() << "SingleApplication: " << d->memory_->error() << d->memory_->errorString();
|
|
||||||
delete d;
|
|
||||||
|
|
||||||
::exit(EXIT_FAILURE);
|
|
||||||
|
|
||||||
}
|
|
||||||
18
3rdparty/singleapplication/singleapplication/CMakeLists.txt
vendored
Normal file
18
3rdparty/singleapplication/singleapplication/CMakeLists.txt
vendored
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
cmake_minimum_required(VERSION 3.7)
|
||||||
|
|
||||||
|
add_definitions(-DSINGLEAPPLICATION)
|
||||||
|
|
||||||
|
set(SOURCES ../singleapplication_t.cpp ../singleapplication_p.cpp)
|
||||||
|
set(HEADERS ../singleapplication_t.h ../singleapplication_p.h)
|
||||||
|
qt_wrap_cpp(MOC ${HEADERS})
|
||||||
|
add_library(singleapplication STATIC ${SOURCES} ${MOC})
|
||||||
|
target_include_directories(singleapplication PUBLIC
|
||||||
|
${CMAKE_CURRENT_SOURCE_DIR}/..
|
||||||
|
${CMAKE_CURRENT_BINARY_DIR}/..
|
||||||
|
${Boost_INCLUDE_DIRS}
|
||||||
|
)
|
||||||
|
target_link_libraries(singleapplication PUBLIC
|
||||||
|
${QtCore_LIBRARIES}
|
||||||
|
${QtWidgets_LIBRARIES}
|
||||||
|
${QtNetwork_LIBRARIES}
|
||||||
|
)
|
||||||
13
3rdparty/singleapplication/singleapplication/singleapplication.h
vendored
Normal file
13
3rdparty/singleapplication/singleapplication/singleapplication.h
vendored
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
#ifndef SINGLEAPPLICATION_H
|
||||||
|
#define SINGLEAPPLICATION_H
|
||||||
|
|
||||||
|
#ifdef SINGLEAPPLICATION
|
||||||
|
# error "SINGLEAPPLICATION already defined."
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#define SINGLEAPPLICATION
|
||||||
|
#include "../singleapplication_t.h"
|
||||||
|
#undef SINGLEAPPLICATION_T_H
|
||||||
|
#undef SINGLEAPPLICATION
|
||||||
|
|
||||||
|
#endif // SINGLEAPPLICATION_H
|
||||||
114
3rdparty/singleapplication/singleapplication_p.cpp
vendored
114
3rdparty/singleapplication/singleapplication_p.cpp
vendored
@@ -68,44 +68,41 @@
|
|||||||
# include <QDateTime>
|
# include <QDateTime>
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#include "singleapplication.h"
|
#include "singleapplication_t.h"
|
||||||
#include "singleapplication_p.h"
|
#include "singleapplication_p.h"
|
||||||
|
|
||||||
SingleApplicationPrivate::SingleApplicationPrivate(SingleApplication *ptr)
|
SingleApplicationPrivateClass::SingleApplicationPrivateClass(SingleApplicationClass *ptr)
|
||||||
: q_ptr(ptr),
|
: q_ptr(ptr),
|
||||||
memory_(nullptr),
|
memory_(nullptr),
|
||||||
socket_(nullptr),
|
socket_(nullptr),
|
||||||
server_(nullptr),
|
server_(nullptr),
|
||||||
instanceNumber_(-1) {}
|
instanceNumber_(-1) {}
|
||||||
|
|
||||||
SingleApplicationPrivate::~SingleApplicationPrivate() {
|
SingleApplicationPrivateClass::~SingleApplicationPrivateClass() {
|
||||||
|
|
||||||
if (socket_ != nullptr) {
|
if (socket_ != nullptr && socket_->isOpen()) {
|
||||||
socket_->close();
|
socket_->close();
|
||||||
delete socket_;
|
|
||||||
socket_ = nullptr;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (memory_ != nullptr) {
|
if (memory_ != nullptr) {
|
||||||
memory_->lock();
|
memory_->lock();
|
||||||
InstancesInfo *instance = static_cast<InstancesInfo*>(memory_->data());
|
|
||||||
if (server_ != nullptr) {
|
if (server_ != nullptr) {
|
||||||
server_->close();
|
server_->close();
|
||||||
delete server_;
|
InstancesInfo *instance = static_cast<InstancesInfo*>(memory_->data());
|
||||||
instance->primary = false;
|
instance->primary = false;
|
||||||
instance->primaryPid = -1;
|
instance->primaryPid = -1;
|
||||||
instance->primaryUser[0] = '\0';
|
instance->primaryUser[0] = '\0';
|
||||||
instance->checksum = blockChecksum();
|
instance->checksum = blockChecksum();
|
||||||
}
|
}
|
||||||
memory_->unlock();
|
memory_->unlock();
|
||||||
|
if (memory_->isAttached()) {
|
||||||
delete memory_;
|
memory_->detach();
|
||||||
memory_ = nullptr;
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
QString SingleApplicationPrivate::getUsername() {
|
QString SingleApplicationPrivateClass::getUsername() {
|
||||||
|
|
||||||
#ifdef Q_OS_UNIX
|
#ifdef Q_OS_UNIX
|
||||||
QString username;
|
QString username;
|
||||||
@@ -141,36 +138,36 @@ QString SingleApplicationPrivate::getUsername() {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void SingleApplicationPrivate::genBlockServerName() {
|
void SingleApplicationPrivateClass::genBlockServerName() {
|
||||||
|
|
||||||
QCryptographicHash appData(QCryptographicHash::Sha256);
|
QCryptographicHash appData(QCryptographicHash::Sha256);
|
||||||
appData.addData("SingleApplication");
|
appData.addData("SingleApplication");
|
||||||
appData.addData(SingleApplication::app_t::applicationName().toUtf8());
|
appData.addData(SingleApplicationClass::applicationName().toUtf8());
|
||||||
appData.addData(SingleApplication::app_t::organizationName().toUtf8());
|
appData.addData(SingleApplicationClass::organizationName().toUtf8());
|
||||||
appData.addData(SingleApplication::app_t::organizationDomain().toUtf8());
|
appData.addData(SingleApplicationClass::organizationDomain().toUtf8());
|
||||||
|
|
||||||
if (!(options_ & SingleApplication::Mode::ExcludeAppVersion)) {
|
if (!(options_ & SingleApplicationClass::Mode::ExcludeAppVersion)) {
|
||||||
appData.addData(SingleApplication::app_t::applicationVersion().toUtf8());
|
appData.addData(SingleApplicationClass::applicationVersion().toUtf8());
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!(options_ & SingleApplication::Mode::ExcludeAppPath)) {
|
if (!(options_ & SingleApplicationClass::Mode::ExcludeAppPath)) {
|
||||||
#if defined(Q_OS_UNIX)
|
#if defined(Q_OS_UNIX)
|
||||||
const QByteArray appImagePath = qgetenv("APPIMAGE");
|
const QByteArray appImagePath = qgetenv("APPIMAGE");
|
||||||
if (appImagePath.isEmpty()) {
|
if (appImagePath.isEmpty()) {
|
||||||
appData.addData(SingleApplication::app_t::applicationFilePath().toUtf8());
|
appData.addData(SingleApplicationClass::applicationFilePath().toUtf8());
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
appData.addData(appImagePath);
|
appData.addData(appImagePath);
|
||||||
};
|
}
|
||||||
#elif defined(Q_OS_WIN)
|
#elif defined(Q_OS_WIN)
|
||||||
appData.addData(SingleApplication::app_t::applicationFilePath().toLower().toUtf8());
|
appData.addData(SingleApplicationClass::applicationFilePath().toLower().toUtf8());
|
||||||
#else
|
#else
|
||||||
appData.addData(SingleApplication::app_t::applicationFilePath().toUtf8());
|
appData.addData(SingleApplicationClass::applicationFilePath().toUtf8());
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
// 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_ & SingleApplicationClass::Mode::User) {
|
||||||
appData.addData(getUsername().toUtf8());
|
appData.addData(getUsername().toUtf8());
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -179,7 +176,7 @@ void SingleApplicationPrivate::genBlockServerName() {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void SingleApplicationPrivate::initializeMemoryBlock() const {
|
void SingleApplicationPrivateClass::initializeMemoryBlock() const {
|
||||||
|
|
||||||
InstancesInfo *instance = static_cast<InstancesInfo*>(memory_->data());
|
InstancesInfo *instance = static_cast<InstancesInfo*>(memory_->data());
|
||||||
instance->primary = false;
|
instance->primary = false;
|
||||||
@@ -190,7 +187,7 @@ void SingleApplicationPrivate::initializeMemoryBlock() const {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void SingleApplicationPrivate::startPrimary() {
|
void SingleApplicationPrivateClass::startPrimary() {
|
||||||
|
|
||||||
// Reset the number of connections
|
// Reset the number of connections
|
||||||
InstancesInfo *instance = static_cast<InstancesInfo*>(memory_->data());
|
InstancesInfo *instance = static_cast<InstancesInfo*>(memory_->data());
|
||||||
@@ -203,10 +200,10 @@ void SingleApplicationPrivate::startPrimary() {
|
|||||||
// Successful creation means that no main process exists
|
// Successful creation means that no main process exists
|
||||||
// So we start a QLocalServer to listen for connections
|
// So we start a QLocalServer to listen for connections
|
||||||
QLocalServer::removeServer(blockServerName_);
|
QLocalServer::removeServer(blockServerName_);
|
||||||
server_ = new QLocalServer();
|
server_ = new QLocalServer(this);
|
||||||
|
|
||||||
// Restrict access to the socket according to the SingleApplication::Mode::User flag on User level or no restrictions
|
// Restrict access to the socket according to the SingleApplication::Mode::User flag on User level or no restrictions
|
||||||
if (options_ & SingleApplication::Mode::User) {
|
if (options_ & SingleApplicationClass::Mode::User) {
|
||||||
server_->setSocketOptions(QLocalServer::UserAccessOption);
|
server_->setSocketOptions(QLocalServer::UserAccessOption);
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
@@ -214,11 +211,11 @@ void SingleApplicationPrivate::startPrimary() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
server_->listen(blockServerName_);
|
server_->listen(blockServerName_);
|
||||||
QObject::connect(server_, &QLocalServer::newConnection, this, &SingleApplicationPrivate::slotConnectionEstablished);
|
QObject::connect(server_, &QLocalServer::newConnection, this, &SingleApplicationPrivateClass::slotConnectionEstablished);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void SingleApplicationPrivate::startSecondary() {
|
void SingleApplicationPrivateClass::startSecondary() {
|
||||||
|
|
||||||
InstancesInfo *instance = static_cast<InstancesInfo*>(memory_->data());
|
InstancesInfo *instance = static_cast<InstancesInfo*>(memory_->data());
|
||||||
|
|
||||||
@@ -228,18 +225,18 @@ void SingleApplicationPrivate::startSecondary() {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bool SingleApplicationPrivate::connectToPrimary(const int timeout, const ConnectionType connectionType) {
|
bool SingleApplicationPrivateClass::connectToPrimary(const int timeout, const 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(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (socket_->state() == QLocalSocket::ConnectedState) return true;
|
if (socket_->state() == QLocalSocket::ConnectedState) return true;
|
||||||
|
|
||||||
|
QElapsedTimer time;
|
||||||
|
time.start();
|
||||||
|
|
||||||
if (socket_->state() != QLocalSocket::ConnectedState) {
|
if (socket_->state() != QLocalSocket::ConnectedState) {
|
||||||
|
|
||||||
forever {
|
forever {
|
||||||
@@ -261,7 +258,7 @@ bool SingleApplicationPrivate::connectToPrimary(const int timeout, const Connect
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Initialisation message according to the SingleApplication protocol
|
// Initialization message according to the SingleApplication protocol
|
||||||
QByteArray initMsg;
|
QByteArray initMsg;
|
||||||
QDataStream writeStream(&initMsg, QIODevice::WriteOnly);
|
QDataStream writeStream(&initMsg, QIODevice::WriteOnly);
|
||||||
writeStream.setVersion(QDataStream::Qt_5_8);
|
writeStream.setVersion(QDataStream::Qt_5_8);
|
||||||
@@ -282,11 +279,11 @@ bool SingleApplicationPrivate::connectToPrimary(const int timeout, const Connect
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void SingleApplicationPrivate::writeAck(QLocalSocket *sock) {
|
void SingleApplicationPrivateClass::writeAck(QLocalSocket *sock) {
|
||||||
sock->putChar('\n');
|
sock->putChar('\n');
|
||||||
}
|
}
|
||||||
|
|
||||||
bool SingleApplicationPrivate::writeConfirmedMessage(const int timeout, const QByteArray &msg) const {
|
bool SingleApplicationPrivateClass::writeConfirmedMessage(const int timeout, const QByteArray &msg) const {
|
||||||
|
|
||||||
QElapsedTimer time;
|
QElapsedTimer time;
|
||||||
time.start();
|
time.start();
|
||||||
@@ -306,7 +303,7 @@ bool SingleApplicationPrivate::writeConfirmedMessage(const int timeout, const QB
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bool SingleApplicationPrivate::writeConfirmedFrame(const int timeout, const QByteArray &msg) const {
|
bool SingleApplicationPrivateClass::writeConfirmedFrame(const int timeout, const QByteArray &msg) const {
|
||||||
|
|
||||||
socket_->write(msg);
|
socket_->write(msg);
|
||||||
socket_->flush();
|
socket_->flush();
|
||||||
@@ -321,7 +318,7 @@ bool SingleApplicationPrivate::writeConfirmedFrame(const int timeout, const QByt
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
quint16 SingleApplicationPrivate::blockChecksum() const {
|
quint16 SingleApplicationPrivateClass::blockChecksum() const {
|
||||||
|
|
||||||
#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
|
#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
|
||||||
quint16 checksum = qChecksum(QByteArray(static_cast<const char*>(memory_->constData()), offsetof(InstancesInfo, checksum)));
|
quint16 checksum = qChecksum(QByteArray(static_cast<const char*>(memory_->constData()), offsetof(InstancesInfo, checksum)));
|
||||||
@@ -333,7 +330,7 @@ quint16 SingleApplicationPrivate::blockChecksum() const {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
qint64 SingleApplicationPrivate::primaryPid() const {
|
qint64 SingleApplicationPrivateClass::primaryPid() const {
|
||||||
|
|
||||||
memory_->lock();
|
memory_->lock();
|
||||||
InstancesInfo *instance = static_cast<InstancesInfo*>(memory_->data());
|
InstancesInfo *instance = static_cast<InstancesInfo*>(memory_->data());
|
||||||
@@ -344,7 +341,7 @@ qint64 SingleApplicationPrivate::primaryPid() const {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
QString SingleApplicationPrivate::primaryUser() const {
|
QString SingleApplicationPrivateClass::primaryUser() const {
|
||||||
|
|
||||||
memory_->lock();
|
memory_->lock();
|
||||||
InstancesInfo *instance = static_cast<InstancesInfo*>(memory_->data());
|
InstancesInfo *instance = static_cast<InstancesInfo*>(memory_->data());
|
||||||
@@ -358,23 +355,23 @@ QString SingleApplicationPrivate::primaryUser() const {
|
|||||||
/**
|
/**
|
||||||
* @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 SingleApplicationPrivateClass::slotConnectionEstablished() {
|
||||||
|
|
||||||
QLocalSocket *nextConnSocket = server_->nextPendingConnection();
|
QLocalSocket *nextConnSocket = server_->nextPendingConnection();
|
||||||
connectionMap_.insert(nextConnSocket, ConnectionInfo());
|
connectionMap_.insert(nextConnSocket, ConnectionInfo());
|
||||||
|
|
||||||
QObject::connect(nextConnSocket, &QLocalSocket::aboutToClose, this, [nextConnSocket, this]() {
|
QObject::connect(nextConnSocket, &QLocalSocket::aboutToClose, this, [this, nextConnSocket]() {
|
||||||
const ConnectionInfo &info = connectionMap_[nextConnSocket];
|
const ConnectionInfo &info = connectionMap_[nextConnSocket];
|
||||||
slotClientConnectionClosed(nextConnSocket, info.instanceId);
|
slotClientConnectionClosed(nextConnSocket, info.instanceId);
|
||||||
});
|
});
|
||||||
|
|
||||||
QObject::connect(nextConnSocket, &QLocalSocket::disconnected, nextConnSocket, &QLocalSocket::deleteLater);
|
QObject::connect(nextConnSocket, &QLocalSocket::disconnected, nextConnSocket, &QLocalSocket::deleteLater);
|
||||||
|
|
||||||
QObject::connect(nextConnSocket, &QLocalSocket::destroyed, this, [nextConnSocket, this]() {
|
QObject::connect(nextConnSocket, &QLocalSocket::destroyed, this, [this, nextConnSocket]() {
|
||||||
connectionMap_.remove(nextConnSocket);
|
connectionMap_.remove(nextConnSocket);
|
||||||
});
|
});
|
||||||
|
|
||||||
QObject::connect(nextConnSocket, &QLocalSocket::readyRead, this, [nextConnSocket, this]() {
|
QObject::connect(nextConnSocket, &QLocalSocket::readyRead, this, [this, nextConnSocket]() {
|
||||||
const ConnectionInfo &info = connectionMap_[nextConnSocket];
|
const ConnectionInfo &info = connectionMap_[nextConnSocket];
|
||||||
switch (info.stage) {
|
switch (info.stage) {
|
||||||
case StageInitHeader:
|
case StageInitHeader:
|
||||||
@@ -387,16 +384,16 @@ void SingleApplicationPrivate::slotConnectionEstablished() {
|
|||||||
readMessageHeader(nextConnSocket, StageConnectedBody);
|
readMessageHeader(nextConnSocket, StageConnectedBody);
|
||||||
break;
|
break;
|
||||||
case StageConnectedBody:
|
case StageConnectedBody:
|
||||||
this->slotDataAvailable(nextConnSocket, info.instanceId);
|
slotDataAvailable(nextConnSocket, info.instanceId);
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
break;
|
break;
|
||||||
};
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void SingleApplicationPrivate::readMessageHeader(QLocalSocket *sock, const SingleApplicationPrivate::ConnectionStage nextStage) {
|
void SingleApplicationPrivateClass::readMessageHeader(QLocalSocket *sock, const SingleApplicationPrivateClass::ConnectionStage nextStage) {
|
||||||
|
|
||||||
if (!connectionMap_.contains(sock)) {
|
if (!connectionMap_.contains(sock)) {
|
||||||
return;
|
return;
|
||||||
@@ -420,7 +417,7 @@ void SingleApplicationPrivate::readMessageHeader(QLocalSocket *sock, const Singl
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bool SingleApplicationPrivate::isFrameComplete(QLocalSocket *sock) {
|
bool SingleApplicationPrivateClass::isFrameComplete(QLocalSocket *sock) {
|
||||||
|
|
||||||
if (!connectionMap_.contains(sock)) {
|
if (!connectionMap_.contains(sock)) {
|
||||||
return false;
|
return false;
|
||||||
@@ -431,9 +428,9 @@ bool SingleApplicationPrivate::isFrameComplete(QLocalSocket *sock) {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void SingleApplicationPrivate::readInitMessageBody(QLocalSocket *sock) {
|
void SingleApplicationPrivateClass::readInitMessageBody(QLocalSocket *sock) {
|
||||||
|
|
||||||
Q_Q(SingleApplication);
|
Q_Q(SingleApplicationClass);
|
||||||
|
|
||||||
if (!isFrameComplete(sock)) {
|
if (!isFrameComplete(sock)) {
|
||||||
return;
|
return;
|
||||||
@@ -449,10 +446,9 @@ void SingleApplicationPrivate::readInitMessageBody(QLocalSocket *sock) {
|
|||||||
readStream >> latin1Name;
|
readStream >> latin1Name;
|
||||||
|
|
||||||
// connection type
|
// connection type
|
||||||
ConnectionType connectionType = InvalidConnection;
|
|
||||||
quint8 connTypeVal = InvalidConnection;
|
quint8 connTypeVal = InvalidConnection;
|
||||||
readStream >> connTypeVal;
|
readStream >> connTypeVal;
|
||||||
connectionType = static_cast<ConnectionType>(connTypeVal);
|
const ConnectionType connectionType = static_cast<ConnectionType>(connTypeVal);
|
||||||
|
|
||||||
// instance id
|
// instance id
|
||||||
quint32 instanceId = 0;
|
quint32 instanceId = 0;
|
||||||
@@ -479,7 +475,7 @@ void SingleApplicationPrivate::readInitMessageBody(QLocalSocket *sock) {
|
|||||||
info.instanceId = instanceId;
|
info.instanceId = instanceId;
|
||||||
info.stage = StageConnectedHeader;
|
info.stage = StageConnectedHeader;
|
||||||
|
|
||||||
if (connectionType == NewInstance || (connectionType == SecondaryInstance && options_ & SingleApplication::Mode::SecondaryNotification)) {
|
if (connectionType == NewInstance || (connectionType == SecondaryInstance && options_ & SingleApplicationClass::Mode::SecondaryNotification)) {
|
||||||
emit q->instanceStarted();
|
emit q->instanceStarted();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -487,9 +483,9 @@ void SingleApplicationPrivate::readInitMessageBody(QLocalSocket *sock) {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void SingleApplicationPrivate::slotDataAvailable(QLocalSocket *dataSocket, const quint32 instanceId) {
|
void SingleApplicationPrivateClass::slotDataAvailable(QLocalSocket *dataSocket, const quint32 instanceId) {
|
||||||
|
|
||||||
Q_Q(SingleApplication);
|
Q_Q(SingleApplicationClass);
|
||||||
|
|
||||||
if (!isFrameComplete(dataSocket)) {
|
if (!isFrameComplete(dataSocket)) {
|
||||||
return;
|
return;
|
||||||
@@ -506,7 +502,7 @@ void SingleApplicationPrivate::slotDataAvailable(QLocalSocket *dataSocket, const
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void SingleApplicationPrivate::slotClientConnectionClosed(QLocalSocket *closedSocket, const quint32 instanceId) {
|
void SingleApplicationPrivateClass::slotClientConnectionClosed(QLocalSocket *closedSocket, const quint32 instanceId) {
|
||||||
|
|
||||||
if (closedSocket->bytesAvailable() > 0) {
|
if (closedSocket->bytesAvailable() > 0) {
|
||||||
slotDataAvailable(closedSocket, instanceId);
|
slotDataAvailable(closedSocket, instanceId);
|
||||||
@@ -514,7 +510,7 @@ void SingleApplicationPrivate::slotClientConnectionClosed(QLocalSocket *closedSo
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void SingleApplicationPrivate::randomSleep() {
|
void SingleApplicationPrivateClass::randomSleep() {
|
||||||
|
|
||||||
#if QT_VERSION >= QT_VERSION_CHECK(5, 10, 0)
|
#if QT_VERSION >= QT_VERSION_CHECK(5, 10, 0)
|
||||||
QThread::msleep(QRandomGenerator::global()->bounded(8U, 18U));
|
QThread::msleep(QRandomGenerator::global()->bounded(8U, 18U));
|
||||||
|
|||||||
47
3rdparty/singleapplication/singleapplication_p.h
vendored
47
3rdparty/singleapplication/singleapplication_p.h
vendored
@@ -39,31 +39,19 @@
|
|||||||
#include <QString>
|
#include <QString>
|
||||||
#include <QHash>
|
#include <QHash>
|
||||||
|
|
||||||
#include "singleapplication.h"
|
#include "singleapplication_t.h"
|
||||||
|
|
||||||
class QLocalServer;
|
class QLocalServer;
|
||||||
class QLocalSocket;
|
class QLocalSocket;
|
||||||
class QSharedMemory;
|
class QSharedMemory;
|
||||||
|
|
||||||
struct InstancesInfo {
|
class SingleApplicationPrivateClass : public QObject {
|
||||||
bool primary;
|
|
||||||
quint32 secondary;
|
|
||||||
qint64 primaryPid;
|
|
||||||
char primaryUser[128];
|
|
||||||
quint16 checksum;
|
|
||||||
};
|
|
||||||
|
|
||||||
struct ConnectionInfo {
|
|
||||||
explicit ConnectionInfo() : msgLen(0), instanceId(0), stage(0) {}
|
|
||||||
quint64 msgLen;
|
|
||||||
quint32 instanceId;
|
|
||||||
quint8 stage;
|
|
||||||
};
|
|
||||||
|
|
||||||
class SingleApplicationPrivate : public QObject {
|
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
|
|
||||||
public:
|
public:
|
||||||
|
explicit SingleApplicationPrivateClass(SingleApplicationClass *ptr);
|
||||||
|
~SingleApplicationPrivateClass() override;
|
||||||
|
|
||||||
enum ConnectionType : quint8 {
|
enum ConnectionType : quint8 {
|
||||||
InvalidConnection = 0,
|
InvalidConnection = 0,
|
||||||
NewInstance = 1,
|
NewInstance = 1,
|
||||||
@@ -74,12 +62,25 @@ class SingleApplicationPrivate : public QObject {
|
|||||||
StageInitHeader = 0,
|
StageInitHeader = 0,
|
||||||
StageInitBody = 1,
|
StageInitBody = 1,
|
||||||
StageConnectedHeader = 2,
|
StageConnectedHeader = 2,
|
||||||
StageConnectedBody = 3,
|
StageConnectedBody = 3
|
||||||
};
|
};
|
||||||
Q_DECLARE_PUBLIC(SingleApplication)
|
Q_DECLARE_PUBLIC(SingleApplicationClass)
|
||||||
|
|
||||||
explicit SingleApplicationPrivate(SingleApplication *ptr);
|
struct InstancesInfo {
|
||||||
~SingleApplicationPrivate() override;
|
explicit InstancesInfo() : primary(false), secondary(0), primaryPid(0), checksum(0) {}
|
||||||
|
bool primary;
|
||||||
|
quint32 secondary;
|
||||||
|
qint64 primaryPid;
|
||||||
|
char primaryUser[128];
|
||||||
|
quint16 checksum;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct ConnectionInfo {
|
||||||
|
explicit ConnectionInfo() : msgLen(0), instanceId(0), stage(0) {}
|
||||||
|
quint64 msgLen;
|
||||||
|
quint32 instanceId;
|
||||||
|
quint8 stage;
|
||||||
|
};
|
||||||
|
|
||||||
static QString getUsername();
|
static QString getUsername();
|
||||||
void genBlockServerName();
|
void genBlockServerName();
|
||||||
@@ -98,13 +99,13 @@ class SingleApplicationPrivate : public QObject {
|
|||||||
bool writeConfirmedMessage(const int timeout, const QByteArray &msg) const;
|
bool writeConfirmedMessage(const int timeout, const QByteArray &msg) const;
|
||||||
static void randomSleep();
|
static void randomSleep();
|
||||||
|
|
||||||
SingleApplication *q_ptr;
|
SingleApplicationClass *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_;
|
SingleApplicationClass::Options options_;
|
||||||
QHash<QLocalSocket*, ConnectionInfo> connectionMap_;
|
QHash<QLocalSocket*, ConnectionInfo> connectionMap_;
|
||||||
|
|
||||||
public slots:
|
public slots:
|
||||||
|
|||||||
330
3rdparty/singleapplication/singleapplication_t.cpp
vendored
Normal file
330
3rdparty/singleapplication/singleapplication_t.cpp
vendored
Normal file
@@ -0,0 +1,330 @@
|
|||||||
|
// The MIT License (MIT)
|
||||||
|
//
|
||||||
|
// Copyright (c) Itay Grudev 2015 - 2020
|
||||||
|
//
|
||||||
|
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
// of this software and associated documentation files (the "Software"), to deal
|
||||||
|
// in the Software without restriction, including without limitation the rights
|
||||||
|
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
// copies of the Software, and to permit persons to whom the Software is
|
||||||
|
// furnished to do so, subject to the following conditions:
|
||||||
|
//
|
||||||
|
// The above copyright notice and this permission notice shall be included in
|
||||||
|
// all copies or substantial portions of the Software.
|
||||||
|
//
|
||||||
|
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
|
// THE SOFTWARE.
|
||||||
|
|
||||||
|
//
|
||||||
|
// W A R N I N G !!!
|
||||||
|
// -----------------
|
||||||
|
//
|
||||||
|
// This is a modified version of SingleApplication,
|
||||||
|
// The original version is at:
|
||||||
|
//
|
||||||
|
// https://github.com/itay-grudev/SingleApplication
|
||||||
|
//
|
||||||
|
//
|
||||||
|
|
||||||
|
#include <cstdlib>
|
||||||
|
#include <limits>
|
||||||
|
#include <memory>
|
||||||
|
|
||||||
|
#include <boost/scope_exit.hpp>
|
||||||
|
|
||||||
|
#include <QtGlobal>
|
||||||
|
#include <QThread>
|
||||||
|
#include <QSharedMemory>
|
||||||
|
#include <QLocalSocket>
|
||||||
|
#include <QByteArray>
|
||||||
|
#include <QElapsedTimer>
|
||||||
|
#include <QtDebug>
|
||||||
|
#if QT_VERSION >= QT_VERSION_CHECK(6, 6, 0)
|
||||||
|
# include <QNativeIpcKey>
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#include "singleapplication_t.h"
|
||||||
|
#include "singleapplication_p.h"
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Constructor. Checks and fires up LocalServer or closes the program if another instance already exists
|
||||||
|
* @param argc
|
||||||
|
* @param argv
|
||||||
|
* @param allowSecondary Whether to enable secondary instance support
|
||||||
|
* @param options Optional flags to toggle specific behaviour
|
||||||
|
* @param timeout Maximum time blocking functions are allowed during app load
|
||||||
|
*/
|
||||||
|
SingleApplicationClass::SingleApplicationClass(int &argc, char *argv[], const bool allowSecondary, const Options options, const int timeout)
|
||||||
|
: ApplicationClass(argc, argv),
|
||||||
|
d_ptr(new SingleApplicationPrivateClass(this)) {
|
||||||
|
|
||||||
|
#if defined(SINGLEAPPLICATION)
|
||||||
|
Q_D(SingleApplication);
|
||||||
|
#elif defined(SINGLECOREAPPLICATION)
|
||||||
|
Q_D(SingleCoreApplication);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// Store the current mode of the program
|
||||||
|
d->options_ = options;
|
||||||
|
|
||||||
|
// Generating an application ID used for identifying the shared memory block and QLocalServer
|
||||||
|
d->genBlockServerName();
|
||||||
|
|
||||||
|
// To mitigate QSharedMemory issues with large amount of processes attempting to attach at the same time
|
||||||
|
SingleApplicationPrivateClass::randomSleep();
|
||||||
|
|
||||||
|
#ifdef Q_OS_UNIX
|
||||||
|
// By explicitly attaching it and then deleting it we make sure that the memory is deleted even after the process has crashed on Unix.
|
||||||
|
{
|
||||||
|
# if QT_VERSION >= QT_VERSION_CHECK(6, 6, 0)
|
||||||
|
std::unique_ptr<QSharedMemory> memory = std::make_unique<QSharedMemory>(QNativeIpcKey(d->blockServerName_));
|
||||||
|
# else
|
||||||
|
std::unique_ptr<QSharedMemory> memory = std::make_unique<QSharedMemory>(d->blockServerName_);
|
||||||
|
# endif
|
||||||
|
if (memory->attach()) {
|
||||||
|
memory->detach();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// Guarantee thread safe behaviour with a shared memory block.
|
||||||
|
#if QT_VERSION >= QT_VERSION_CHECK(6, 6, 0)
|
||||||
|
QSharedMemory *memory = new QSharedMemory(QNativeIpcKey(d->blockServerName_), this);
|
||||||
|
#else
|
||||||
|
QSharedMemory *memory = new QSharedMemory(d->blockServerName_, this);
|
||||||
|
#endif
|
||||||
|
d->memory_ = memory;
|
||||||
|
|
||||||
|
bool primary = false;
|
||||||
|
|
||||||
|
// Create a shared memory block
|
||||||
|
if (d->memory_->create(sizeof(SingleApplicationPrivateClass::InstancesInfo))) {
|
||||||
|
primary = true;
|
||||||
|
}
|
||||||
|
else if (d->memory_->error() == QSharedMemory::AlreadyExists) {
|
||||||
|
if (!d->memory_->attach()) {
|
||||||
|
qCritical() << "SingleApplication: Unable to attach to shared memory block:" << d->memory_->error() << d->memory_->errorString();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
qCritical() << "SingleApplication: Unable to create shared memory block:" << d->memory_->error() << d->memory_->errorString();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool locked = false;
|
||||||
|
|
||||||
|
BOOST_SCOPE_EXIT((memory)(&locked)) {
|
||||||
|
if (locked && !memory->unlock()) {
|
||||||
|
qWarning() << "SingleApplication: Unable to unlock shared memory block:" << memory->error() << memory->errorString();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}BOOST_SCOPE_EXIT_END
|
||||||
|
|
||||||
|
if (!d->memory_->lock()) {
|
||||||
|
qCritical() << "SingleApplication: Unable to lock shared memory block:" << d->memory_->error() << d->memory_->errorString();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
locked = true;
|
||||||
|
|
||||||
|
if (primary) {
|
||||||
|
// Initialize the shared memory block
|
||||||
|
d->initializeMemoryBlock();
|
||||||
|
}
|
||||||
|
|
||||||
|
SingleApplicationPrivateClass::InstancesInfo *instance = static_cast<SingleApplicationPrivateClass::InstancesInfo*>(d->memory_->data());
|
||||||
|
QElapsedTimer time;
|
||||||
|
time.start();
|
||||||
|
|
||||||
|
// Make sure the shared memory block is initialized and in a consistent state
|
||||||
|
while (d->blockChecksum() != instance->checksum) {
|
||||||
|
|
||||||
|
// If more than 5 seconds have elapsed, assume the primary instance crashed and assume its position
|
||||||
|
if (time.elapsed() > 5000) {
|
||||||
|
qWarning() << "SingleApplication: Shared memory block has been in an inconsistent state from more than 5 seconds. Assuming primary instance failure.";
|
||||||
|
d->initializeMemoryBlock();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Otherwise wait for a random period and try again.
|
||||||
|
// The random sleep here limits the probability of a collision between two racing apps and allows the app to initialize faster
|
||||||
|
if (locked) {
|
||||||
|
if (d->memory_->unlock()) {
|
||||||
|
locked = false;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
qCritical() << "SingleApplication: Unable to unlock shared memory block for random wait:" << memory->error() << memory->errorString();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
SingleApplicationPrivateClass::randomSleep();
|
||||||
|
|
||||||
|
if (!d->memory_->lock()) {
|
||||||
|
qCritical() << "SingleApplication: Unable to lock shared memory block after random wait:" << memory->error() << memory->errorString();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
locked = true;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
if (instance->primary) {
|
||||||
|
// Check if another instance can be started
|
||||||
|
if (allowSecondary) {
|
||||||
|
d->startSecondary();
|
||||||
|
if (d->options_ & Mode::SecondaryNotification) {
|
||||||
|
d->connectToPrimary(timeout, SingleApplicationPrivateClass::SecondaryInstance);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
d->startPrimary();
|
||||||
|
primary = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (locked) {
|
||||||
|
if (d->memory_->unlock()) {
|
||||||
|
locked = false;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
qWarning() << "SingleApplication: Unable to unlock shared memory block:" << memory->error() << memory->errorString();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!primary && !allowSecondary) {
|
||||||
|
d->connectToPrimary(timeout, SingleApplicationPrivateClass::NewInstance);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
SingleApplicationClass::~SingleApplicationClass() {
|
||||||
|
|
||||||
|
#if defined(SINGLEAPPLICATION)
|
||||||
|
Q_D(SingleApplication);
|
||||||
|
#elif defined(SINGLECOREAPPLICATION)
|
||||||
|
Q_D(SingleCoreApplication);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
delete d;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks if the current application instance is primary.
|
||||||
|
* @return Returns true if the instance is primary, false otherwise.
|
||||||
|
*/
|
||||||
|
bool SingleApplicationClass::isPrimary() const {
|
||||||
|
|
||||||
|
#if defined(SINGLEAPPLICATION)
|
||||||
|
Q_D(const SingleApplication);
|
||||||
|
#elif defined(SINGLECOREAPPLICATION)
|
||||||
|
Q_D(const SingleCoreApplication);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
return d->server_ != nullptr;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks if the current application instance is secondary.
|
||||||
|
* @return Returns true if the instance is secondary, false otherwise.
|
||||||
|
*/
|
||||||
|
bool SingleApplicationClass::isSecondary() const {
|
||||||
|
|
||||||
|
#if defined(SINGLEAPPLICATION)
|
||||||
|
Q_D(const SingleApplication);
|
||||||
|
#elif defined(SINGLECOREAPPLICATION)
|
||||||
|
Q_D(const SingleCoreApplication);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
return d->server_ == nullptr;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Allows you to identify an instance by returning unique consecutive instance ids.
|
||||||
|
* It is reset when the first (primary) instance of your app starts and only incremented afterwards.
|
||||||
|
* @return Returns a unique instance id.
|
||||||
|
*/
|
||||||
|
quint32 SingleApplicationClass::instanceId() const {
|
||||||
|
|
||||||
|
#if defined(SINGLEAPPLICATION)
|
||||||
|
Q_D(const SingleApplication);
|
||||||
|
#elif defined(SINGLECOREAPPLICATION)
|
||||||
|
Q_D(const SingleCoreApplication);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
return d->instanceNumber_;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the OS PID (Process Identifier) of the process running the primary instance.
|
||||||
|
* Especially useful when SingleApplication is coupled with OS. specific APIs.
|
||||||
|
* @return Returns the primary instance PID.
|
||||||
|
*/
|
||||||
|
qint64 SingleApplicationClass::primaryPid() const {
|
||||||
|
|
||||||
|
#if defined(SINGLEAPPLICATION)
|
||||||
|
Q_D(const SingleApplication);
|
||||||
|
#elif defined(SINGLECOREAPPLICATION)
|
||||||
|
Q_D(const SingleCoreApplication);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
return d->primaryPid();
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the username the primary instance is running as.
|
||||||
|
* @return Returns the username the primary instance is running as.
|
||||||
|
*/
|
||||||
|
QString SingleApplicationClass::primaryUser() const {
|
||||||
|
|
||||||
|
#if defined(SINGLEAPPLICATION)
|
||||||
|
Q_D(const SingleApplication);
|
||||||
|
#elif defined(SINGLECOREAPPLICATION)
|
||||||
|
Q_D(const SingleCoreApplication);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
return d->primaryUser();
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the username the current instance is running as.
|
||||||
|
* @return Returns the username the current instance is running as.
|
||||||
|
*/
|
||||||
|
QString SingleApplicationClass::currentUser() const {
|
||||||
|
return SingleApplicationPrivateClass::getUsername();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sends message to the Primary Instance.
|
||||||
|
* @param message The message to send.
|
||||||
|
* @param timeout the maximum timeout in milliseconds for blocking functions.
|
||||||
|
* @return true if the message was sent successfully, false otherwise.
|
||||||
|
*/
|
||||||
|
bool SingleApplicationClass::sendMessage(const QByteArray &message, const int timeout) {
|
||||||
|
|
||||||
|
#if defined(SINGLEAPPLICATION)
|
||||||
|
Q_D(SingleApplication);
|
||||||
|
#elif defined(SINGLECOREAPPLICATION)
|
||||||
|
Q_D(SingleCoreApplication);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// Nobody to connect to
|
||||||
|
if (isPrimary()) return false;
|
||||||
|
|
||||||
|
// Make sure the socket is connected
|
||||||
|
if (!d->connectToPrimary(timeout, SingleApplicationPrivateClass::Reconnect)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return d->writeConfirmedMessage(timeout, message);
|
||||||
|
|
||||||
|
}
|
||||||
@@ -31,25 +31,41 @@
|
|||||||
//
|
//
|
||||||
//
|
//
|
||||||
|
|
||||||
#ifndef SINGLEAPPLICATION_H
|
#ifndef SINGLEAPPLICATION_T_H
|
||||||
#define SINGLEAPPLICATION_H
|
#define SINGLEAPPLICATION_T_H
|
||||||
|
|
||||||
#include <QtGlobal>
|
#include <QtGlobal>
|
||||||
#include <QApplication>
|
|
||||||
|
#undef ApplicationClass
|
||||||
|
#undef SingleApplicationClass
|
||||||
|
#undef SingleApplicationPrivateClass
|
||||||
|
|
||||||
|
#if defined(SINGLEAPPLICATION)
|
||||||
|
# include <QApplication>
|
||||||
|
# define ApplicationClass QApplication
|
||||||
|
# define SingleApplicationClass SingleApplication
|
||||||
|
# define SingleApplicationPrivateClass SingleApplicationPrivate
|
||||||
|
#elif defined(SINGLECOREAPPLICATION)
|
||||||
|
# include <QCoreApplication>
|
||||||
|
# define ApplicationClass QCoreApplication
|
||||||
|
# define SingleApplicationClass SingleCoreApplication
|
||||||
|
# define SingleApplicationPrivateClass SingleCoreApplicationPrivate
|
||||||
|
#else
|
||||||
|
# error "Define SINGLEAPPLICATION or SINGLECOREAPPLICATION."
|
||||||
|
#endif
|
||||||
|
|
||||||
#include <QFlags>
|
#include <QFlags>
|
||||||
#include <QByteArray>
|
#include <QByteArray>
|
||||||
|
|
||||||
class SingleApplicationPrivate;
|
class SingleApplicationPrivateClass;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief The SingleApplication class handles multiple instances of the same Application
|
* @brief The SingleApplication class handles multiple instances of the same Application
|
||||||
* @see QApplication
|
* @see QApplication
|
||||||
*/
|
*/
|
||||||
class SingleApplication : public QApplication { // clazy:exclude=ctor-missing-parent-argument
|
class SingleApplicationClass : public ApplicationClass { // clazy:exclude=ctor-missing-parent-argument
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
|
|
||||||
using app_t = QApplication;
|
|
||||||
|
|
||||||
public:
|
public:
|
||||||
/**
|
/**
|
||||||
* @brief Mode of operation of SingleApplication.
|
* @brief Mode of operation of SingleApplication.
|
||||||
@@ -61,7 +77,7 @@ class SingleApplication : public QApplication { // clazy:exclude=ctor-missing-p
|
|||||||
* block will be user wide.
|
* block will be user wide.
|
||||||
* @enum
|
* @enum
|
||||||
*/
|
*/
|
||||||
enum Mode {
|
enum class Mode {
|
||||||
User = 1 << 0,
|
User = 1 << 0,
|
||||||
System = 1 << 1,
|
System = 1 << 1,
|
||||||
SecondaryNotification = 1 << 2,
|
SecondaryNotification = 1 << 2,
|
||||||
@@ -86,11 +102,11 @@ class SingleApplication : public QApplication { // clazy:exclude=ctor-missing-p
|
|||||||
* instance and the secondary instance.
|
* instance and the secondary instance.
|
||||||
* @note The timeout is just a hint for the maximum time of blocking
|
* @note The timeout is just a hint for the maximum time of blocking
|
||||||
* operations. It does not guarantee that the SingleApplication
|
* operations. It does not guarantee that the SingleApplication
|
||||||
* initialisation will be completed in given time, though is a good hint.
|
* initialization 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.
|
||||||
*/
|
*/
|
||||||
explicit SingleApplication(int &argc, char *argv[], const bool allowSecondary = false, const Options options = Mode::User, const int timeout = 1000);
|
explicit SingleApplicationClass(int &argc, char *argv[], const bool allowSecondary = false, const Options options = Mode::User, const int timeout = 1000);
|
||||||
~SingleApplication() override;
|
~SingleApplicationClass() override;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Returns if the instance is the primary instance
|
* @brief Returns if the instance is the primary instance
|
||||||
@@ -142,11 +158,15 @@ class SingleApplication : public QApplication { // clazy:exclude=ctor-missing-p
|
|||||||
void receivedMessage(quint32 instanceId, QByteArray message);
|
void receivedMessage(quint32 instanceId, QByteArray message);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
SingleApplicationPrivate *d_ptr;
|
SingleApplicationPrivateClass *d_ptr;
|
||||||
|
#if defined(SINGLEAPPLICATION)
|
||||||
Q_DECLARE_PRIVATE(SingleApplication)
|
Q_DECLARE_PRIVATE(SingleApplication)
|
||||||
|
#elif defined(SINGLECOREAPPLICATION)
|
||||||
|
Q_DECLARE_PRIVATE(SingleCoreApplication)
|
||||||
|
#endif
|
||||||
void abortSafely();
|
void abortSafely();
|
||||||
};
|
};
|
||||||
|
|
||||||
Q_DECLARE_OPERATORS_FOR_FLAGS(SingleApplication::Options)
|
Q_DECLARE_OPERATORS_FOR_FLAGS(SingleApplicationClass::Options)
|
||||||
|
|
||||||
#endif // SINGLEAPPLICATION_H
|
#endif // SINGLEAPPLICATION_T_H
|
||||||
266
3rdparty/singleapplication/singlecoreapplication.cpp
vendored
266
3rdparty/singleapplication/singlecoreapplication.cpp
vendored
@@ -1,266 +0,0 @@
|
|||||||
// The MIT License (MIT)
|
|
||||||
//
|
|
||||||
// Copyright (c) Itay Grudev 2015 - 2020
|
|
||||||
//
|
|
||||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
||||||
// of this software and associated documentation files (the "Software"), to deal
|
|
||||||
// in the Software without restriction, including without limitation the rights
|
|
||||||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
||||||
// copies of the Software, and to permit persons to whom the Software is
|
|
||||||
// furnished to do so, subject to the following conditions:
|
|
||||||
//
|
|
||||||
// The above copyright notice and this permission notice shall be included in
|
|
||||||
// all copies or substantial portions of the Software.
|
|
||||||
//
|
|
||||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
||||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
||||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
||||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
||||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
||||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
|
||||||
// THE SOFTWARE.
|
|
||||||
|
|
||||||
//
|
|
||||||
// W A R N I N G !!!
|
|
||||||
// -----------------
|
|
||||||
//
|
|
||||||
// This is a modified version of SingleApplication,
|
|
||||||
// The original version is at:
|
|
||||||
//
|
|
||||||
// https://github.com/itay-grudev/SingleApplication
|
|
||||||
//
|
|
||||||
//
|
|
||||||
|
|
||||||
#include <cstdlib>
|
|
||||||
#include <limits>
|
|
||||||
|
|
||||||
#include <QtGlobal>
|
|
||||||
#include <QCoreApplication>
|
|
||||||
#include <QThread>
|
|
||||||
#include <QSharedMemory>
|
|
||||||
#include <QLocalSocket>
|
|
||||||
#include <QByteArray>
|
|
||||||
#include <QElapsedTimer>
|
|
||||||
#include <QtDebug>
|
|
||||||
|
|
||||||
#include "singlecoreapplication.h"
|
|
||||||
#include "singlecoreapplication_p.h"
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Constructor. Checks and fires up LocalServer or closes the program if another instance already exists
|
|
||||||
* @param argc
|
|
||||||
* @param argv
|
|
||||||
* @param allowSecondary Whether to enable secondary instance support
|
|
||||||
* @param options Optional flags to toggle specific behaviour
|
|
||||||
* @param timeout Maximum time blocking functions are allowed during app load
|
|
||||||
*/
|
|
||||||
SingleCoreApplication::SingleCoreApplication(int &argc, char *argv[], const bool allowSecondary, const Options options, const int timeout)
|
|
||||||
: app_t(argc, argv),
|
|
||||||
d_ptr(new SingleCoreApplicationPrivate(this)) {
|
|
||||||
|
|
||||||
Q_D(SingleCoreApplication);
|
|
||||||
|
|
||||||
// Store the current mode of the program
|
|
||||||
d->options_ = options;
|
|
||||||
|
|
||||||
// Generating an application ID used for identifying the shared memory block and QLocalServer
|
|
||||||
d->genBlockServerName();
|
|
||||||
|
|
||||||
// To mitigate QSharedMemory issues with large amount of processes attempting to attach at the same time
|
|
||||||
SingleCoreApplicationPrivate::randomSleep();
|
|
||||||
|
|
||||||
#ifdef Q_OS_UNIX
|
|
||||||
// By explicitly attaching it and then deleting it we make sure that the memory is deleted even after the process has crashed on Unix.
|
|
||||||
d->memory_ = new QSharedMemory(d->blockServerName_);
|
|
||||||
d->memory_->attach();
|
|
||||||
delete d->memory_;
|
|
||||||
#endif
|
|
||||||
|
|
||||||
// Guarantee thread safe behaviour with a shared memory block.
|
|
||||||
d->memory_ = new QSharedMemory(d->blockServerName_);
|
|
||||||
|
|
||||||
// Create a shared memory block
|
|
||||||
if (d->memory_->create(sizeof(InstancesInfo))) {
|
|
||||||
// Initialize the shared memory block
|
|
||||||
if (!d->memory_->lock()) {
|
|
||||||
qCritical() << "SingleCoreApplication: Unable to lock memory block after create.";
|
|
||||||
abortSafely();
|
|
||||||
}
|
|
||||||
d->initializeMemoryBlock();
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
if (d->memory_->error() == QSharedMemory::AlreadyExists) {
|
|
||||||
// Attempt to attach to the memory segment
|
|
||||||
if (!d->memory_->attach()) {
|
|
||||||
qCritical() << "SingleCoreApplication: Unable to attach to shared memory block.";
|
|
||||||
abortSafely();
|
|
||||||
}
|
|
||||||
if (!d->memory_->lock()) {
|
|
||||||
qCritical() << "SingleCoreApplication: Unable to lock memory block after attach.";
|
|
||||||
abortSafely();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
qCritical() << "SingleCoreApplication: Unable to create block.";
|
|
||||||
abortSafely();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
InstancesInfo *instance = static_cast<InstancesInfo*>(d->memory_->data());
|
|
||||||
QElapsedTimer time;
|
|
||||||
time.start();
|
|
||||||
|
|
||||||
// Make sure the shared memory block is initialised and in consistent state
|
|
||||||
forever {
|
|
||||||
// If the shared memory block's checksum is valid continue
|
|
||||||
if (d->blockChecksum() == instance->checksum) break;
|
|
||||||
|
|
||||||
// If more than 5s have elapsed, assume the primary instance crashed and assume it's position
|
|
||||||
if (time.elapsed() > 5000) {
|
|
||||||
qWarning() << "SingleCoreApplication: Shared memory block has been in an inconsistent state from more than 5s. Assuming primary instance failure.";
|
|
||||||
d->initializeMemoryBlock();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Otherwise wait for a random period and try again.
|
|
||||||
// The random sleep here limits the probability of a collision between two racing apps and allows the app to initialise faster
|
|
||||||
if (!d->memory_->unlock()) {
|
|
||||||
qDebug() << "SingleCoreApplication: Unable to unlock memory for random wait.";
|
|
||||||
qDebug() << d->memory_->errorString();
|
|
||||||
}
|
|
||||||
SingleCoreApplicationPrivate::randomSleep();
|
|
||||||
if (!d->memory_->lock()) {
|
|
||||||
qCritical() << "SingleCoreApplication: Unable to lock memory after random wait.";
|
|
||||||
abortSafely();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!instance->primary) {
|
|
||||||
d->startPrimary();
|
|
||||||
if (!d->memory_->unlock()) {
|
|
||||||
qDebug() << "SingleCoreApplication: Unable to unlock memory after primary start.";
|
|
||||||
qDebug() << d->memory_->errorString();
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check if another instance can be started
|
|
||||||
if (allowSecondary) {
|
|
||||||
d->startSecondary();
|
|
||||||
if (d->options_ & Mode::SecondaryNotification) {
|
|
||||||
d->connectToPrimary(timeout, SingleCoreApplicationPrivate::SecondaryInstance);
|
|
||||||
}
|
|
||||||
if (!d->memory_->unlock()) {
|
|
||||||
qDebug() << "SingleCoreApplication: Unable to unlock memory after secondary start.";
|
|
||||||
qDebug() << d->memory_->errorString();
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!d->memory_->unlock()) {
|
|
||||||
qDebug() << "SingleCoreApplication: Unable to unlock memory at end of execution.";
|
|
||||||
qDebug() << d->memory_->errorString();
|
|
||||||
}
|
|
||||||
|
|
||||||
d->connectToPrimary(timeout, SingleCoreApplicationPrivate::NewInstance);
|
|
||||||
|
|
||||||
delete d;
|
|
||||||
|
|
||||||
::exit(EXIT_SUCCESS);
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
SingleCoreApplication::~SingleCoreApplication() {
|
|
||||||
Q_D(SingleCoreApplication);
|
|
||||||
delete d;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Checks if the current application instance is primary.
|
|
||||||
* @return Returns true if the instance is primary, false otherwise.
|
|
||||||
*/
|
|
||||||
bool SingleCoreApplication::isPrimary() const {
|
|
||||||
Q_D(const SingleCoreApplication);
|
|
||||||
return d->server_ != nullptr;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Checks if the current application instance is secondary.
|
|
||||||
* @return Returns true if the instance is secondary, false otherwise.
|
|
||||||
*/
|
|
||||||
bool SingleCoreApplication::isSecondary() const {
|
|
||||||
Q_D(const SingleCoreApplication);
|
|
||||||
return d->server_ == nullptr;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Allows you to identify an instance by returning unique consecutive instance ids.
|
|
||||||
* It is reset when the first (primary) instance of your app starts and only incremented afterwards.
|
|
||||||
* @return Returns a unique instance id.
|
|
||||||
*/
|
|
||||||
quint32 SingleCoreApplication::instanceId() const {
|
|
||||||
Q_D(const SingleCoreApplication);
|
|
||||||
return d->instanceNumber_;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the OS PID (Process Identifier) of the process running the primary instance.
|
|
||||||
* Especially useful when SingleCoreApplication is coupled with OS. specific APIs.
|
|
||||||
* @return Returns the primary instance PID.
|
|
||||||
*/
|
|
||||||
qint64 SingleCoreApplication::primaryPid() const {
|
|
||||||
Q_D(const SingleCoreApplication);
|
|
||||||
return d->primaryPid();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the username the primary instance is running as.
|
|
||||||
* @return Returns the username the primary instance is running as.
|
|
||||||
*/
|
|
||||||
QString SingleCoreApplication::primaryUser() const {
|
|
||||||
Q_D(const 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() const {
|
|
||||||
return SingleCoreApplicationPrivate::getUsername();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Sends message to the Primary Instance.
|
|
||||||
* @param message The message to send.
|
|
||||||
* @param timeout the maximum timeout in milliseconds for blocking functions.
|
|
||||||
* @return true if the message was sent successfully, false otherwise.
|
|
||||||
*/
|
|
||||||
bool SingleCoreApplication::sendMessage(const QByteArray &message, const int timeout) {
|
|
||||||
|
|
||||||
Q_D(SingleCoreApplication);
|
|
||||||
|
|
||||||
// Nobody to connect to
|
|
||||||
if (isPrimary()) return false;
|
|
||||||
|
|
||||||
// Make sure the socket is connected
|
|
||||||
if (!d->connectToPrimary(timeout, SingleCoreApplicationPrivate::Reconnect)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return d->writeConfirmedMessage(timeout, message);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Cleans up the shared memory block and exits with a failure.
|
|
||||||
* This function halts program execution.
|
|
||||||
*/
|
|
||||||
void SingleCoreApplication::abortSafely() {
|
|
||||||
|
|
||||||
Q_D(SingleCoreApplication);
|
|
||||||
|
|
||||||
qCritical() << "SingleCoreApplication: " << d->memory_->error() << d->memory_->errorString();
|
|
||||||
delete d;
|
|
||||||
|
|
||||||
::exit(EXIT_FAILURE);
|
|
||||||
|
|
||||||
}
|
|
||||||
152
3rdparty/singleapplication/singlecoreapplication.h
vendored
152
3rdparty/singleapplication/singlecoreapplication.h
vendored
@@ -1,152 +0,0 @@
|
|||||||
// The MIT License (MIT)
|
|
||||||
//
|
|
||||||
// Copyright (c) Itay Grudev 2015 - 2020
|
|
||||||
//
|
|
||||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
||||||
// of this software and associated documentation files (the "Software"), to deal
|
|
||||||
// in the Software without restriction, including without limitation the rights
|
|
||||||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
||||||
// copies of the Software, and to permit persons to whom the Software is
|
|
||||||
// furnished to do so, subject to the following conditions:
|
|
||||||
//
|
|
||||||
// The above copyright notice and this permission notice shall be included in
|
|
||||||
// all copies or substantial portions of the Software.
|
|
||||||
//
|
|
||||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
||||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
||||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
||||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
||||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
||||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
|
||||||
// THE SOFTWARE.
|
|
||||||
|
|
||||||
//
|
|
||||||
// W A R N I N G !!!
|
|
||||||
// -----------------
|
|
||||||
//
|
|
||||||
// This is a modified version of SingleApplication,
|
|
||||||
// The original version is at:
|
|
||||||
//
|
|
||||||
// https://github.com/itay-grudev/SingleApplication
|
|
||||||
//
|
|
||||||
//
|
|
||||||
|
|
||||||
#ifndef SINGLECOREAPPLICATION_H
|
|
||||||
#define SINGLECOREAPPLICATION_H
|
|
||||||
|
|
||||||
#include <QtGlobal>
|
|
||||||
#include <QCoreApplication>
|
|
||||||
#include <QFlags>
|
|
||||||
#include <QByteArray>
|
|
||||||
|
|
||||||
class SingleCoreApplicationPrivate;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief The SingleCoreApplication class handles multiple instances of the same Application
|
|
||||||
* @see QCoreApplication
|
|
||||||
*/
|
|
||||||
class SingleCoreApplication : public QCoreApplication { // clazy:exclude=ctor-missing-parent-argument
|
|
||||||
Q_OBJECT
|
|
||||||
|
|
||||||
using app_t = QCoreApplication;
|
|
||||||
|
|
||||||
public:
|
|
||||||
/**
|
|
||||||
* @brief Mode of operation of SingleCoreApplication.
|
|
||||||
* Whether the block should be user-wide or system-wide and whether the
|
|
||||||
* primary instance should be notified when a secondary instance had been
|
|
||||||
* started.
|
|
||||||
* @note Operating system can restrict the shared memory blocks to the same
|
|
||||||
* user, in which case the User/System modes will have no effect and the
|
|
||||||
* block will be user wide.
|
|
||||||
* @enum
|
|
||||||
*/
|
|
||||||
enum Mode {
|
|
||||||
User = 1 << 0,
|
|
||||||
System = 1 << 1,
|
|
||||||
SecondaryNotification = 1 << 2,
|
|
||||||
ExcludeAppVersion = 1 << 3,
|
|
||||||
ExcludeAppPath = 1 << 4
|
|
||||||
};
|
|
||||||
Q_DECLARE_FLAGS(Options, Mode)
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Intitializes a SingleCoreApplication instance with argc command line
|
|
||||||
* arguments in argv
|
|
||||||
* @arg {int &} argc - Number of arguments in argv
|
|
||||||
* @arg {const char *[]} argv - Supplied command line arguments
|
|
||||||
* @arg {bool} allowSecondary - Whether to start the instance as secondary
|
|
||||||
* if there is already a primary instance.
|
|
||||||
* @arg {Mode} mode - Whether for the SingleCoreApplication block to be applied
|
|
||||||
* User wide or System wide.
|
|
||||||
* @arg {int} timeout - Timeout to wait in milliseconds.
|
|
||||||
* @note argc and argv may be changed as Qt removes arguments that it
|
|
||||||
* recognizes
|
|
||||||
* @note Mode::SecondaryNotification only works if set on both the primary
|
|
||||||
* instance and the secondary instance.
|
|
||||||
* @note The timeout is just a hint for the maximum time of blocking
|
|
||||||
* operations. It does not guarantee that the SingleCoreApplication
|
|
||||||
* initialisation will be completed in given time, though is a good hint.
|
|
||||||
* Usually 4*timeout would be the worst case (fail) scenario.
|
|
||||||
*/
|
|
||||||
explicit SingleCoreApplication(int &argc, char *argv[], const bool allowSecondary = false, const Options options = Mode::User, const int timeout = 1000);
|
|
||||||
~SingleCoreApplication() override;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Returns if the instance is the primary instance
|
|
||||||
* @returns {bool}
|
|
||||||
*/
|
|
||||||
bool isPrimary() const;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Returns if the instance is a secondary instance
|
|
||||||
* @returns {bool}
|
|
||||||
*/
|
|
||||||
bool isSecondary() const;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Returns a unique identifier for the current instance
|
|
||||||
* @returns {qint32}
|
|
||||||
*/
|
|
||||||
quint32 instanceId() const;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Returns the process ID (PID) of the primary instance
|
|
||||||
* @returns {qint64}
|
|
||||||
*/
|
|
||||||
qint64 primaryPid() const;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Returns the username of the user running the primary instance
|
|
||||||
* @returns {QString}
|
|
||||||
*/
|
|
||||||
QString primaryUser() const;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Returns the username of the current user
|
|
||||||
* @returns {QString}
|
|
||||||
*/
|
|
||||||
QString currentUser() const;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Sends a message to the primary instance. Returns true on success.
|
|
||||||
* @param {int} timeout - Timeout for connecting
|
|
||||||
* @returns {bool}
|
|
||||||
* @note sendMessage() will return false if invoked from the primary
|
|
||||||
* instance.
|
|
||||||
*/
|
|
||||||
bool sendMessage(const QByteArray &message, const int timeout = 1000);
|
|
||||||
|
|
||||||
signals:
|
|
||||||
void instanceStarted();
|
|
||||||
void receivedMessage(quint32 instanceId, QByteArray message);
|
|
||||||
|
|
||||||
private:
|
|
||||||
SingleCoreApplicationPrivate *d_ptr;
|
|
||||||
Q_DECLARE_PRIVATE(SingleCoreApplication)
|
|
||||||
void abortSafely();
|
|
||||||
};
|
|
||||||
|
|
||||||
Q_DECLARE_OPERATORS_FOR_FLAGS(SingleCoreApplication::Options)
|
|
||||||
|
|
||||||
#endif // SINGLECOREAPPLICATION_H
|
|
||||||
17
3rdparty/singleapplication/singlecoreapplication/CMakeLists.txt
vendored
Normal file
17
3rdparty/singleapplication/singlecoreapplication/CMakeLists.txt
vendored
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
cmake_minimum_required(VERSION 3.7)
|
||||||
|
|
||||||
|
add_definitions(-DSINGLECOREAPPLICATION)
|
||||||
|
|
||||||
|
set(SOURCES ../singleapplication_t.cpp ../singleapplication_p.cpp)
|
||||||
|
set(HEADERS ../singleapplication_t.h ../singleapplication_p.h)
|
||||||
|
qt_wrap_cpp(MOC ${HEADERS})
|
||||||
|
add_library(singlecoreapplication STATIC ${SOURCES} ${MOC})
|
||||||
|
target_include_directories(singlecoreapplication PUBLIC
|
||||||
|
${CMAKE_CURRENT_SOURCE_DIR}/..
|
||||||
|
${CMAKE_CURRENT_BINARY_DIR}/..
|
||||||
|
${Boost_INCLUDE_DIRS}
|
||||||
|
)
|
||||||
|
target_link_libraries(singlecoreapplication PUBLIC
|
||||||
|
${QtCore_LIBRARIES}
|
||||||
|
${QtNetwork_LIBRARIES}
|
||||||
|
)
|
||||||
13
3rdparty/singleapplication/singlecoreapplication/singlecoreapplication.h
vendored
Normal file
13
3rdparty/singleapplication/singlecoreapplication/singlecoreapplication.h
vendored
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
#ifndef SINGLECOREAPPLICATION_H
|
||||||
|
#define SINGLECOREAPPLICATION_H
|
||||||
|
|
||||||
|
#ifdef SINGLECOREAPPLICATION
|
||||||
|
# error "SINGLECOREAPPLICATION already defined."
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#define SINGLECOREAPPLICATION
|
||||||
|
#include "../singleapplication_t.h"
|
||||||
|
#undef SINGLEAPPLICATION_T_H
|
||||||
|
#undef SINGLECOREAPPLICATION
|
||||||
|
|
||||||
|
#endif // SINGLECOREAPPLICATION_H
|
||||||
@@ -1,526 +0,0 @@
|
|||||||
// The MIT License (MIT)
|
|
||||||
//
|
|
||||||
// Copyright (c) Itay Grudev 2015 - 2020
|
|
||||||
//
|
|
||||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
||||||
// of this software and associated documentation files (the "Software"), to deal
|
|
||||||
// in the Software without restriction, including without limitation the rights
|
|
||||||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
||||||
// copies of the Software, and to permit persons to whom the Software is
|
|
||||||
// furnished to do so, subject to the following conditions:
|
|
||||||
//
|
|
||||||
// The above copyright notice and this permission notice shall be included in
|
|
||||||
// all copies or substantial portions of the Software.
|
|
||||||
//
|
|
||||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
||||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
||||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
||||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
||||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
||||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
|
||||||
// THE SOFTWARE.
|
|
||||||
|
|
||||||
//
|
|
||||||
// W A R N I N G !!!
|
|
||||||
// -----------------
|
|
||||||
//
|
|
||||||
// This is a modified version of SingleApplication,
|
|
||||||
// The original version is at:
|
|
||||||
//
|
|
||||||
// https://github.com/itay-grudev/SingleApplication
|
|
||||||
//
|
|
||||||
//
|
|
||||||
|
|
||||||
#include "config.h"
|
|
||||||
|
|
||||||
#include <QtGlobal>
|
|
||||||
|
|
||||||
#include <cstdlib>
|
|
||||||
#include <cstddef>
|
|
||||||
|
|
||||||
#ifdef Q_OS_UNIX
|
|
||||||
# include <unistd.h>
|
|
||||||
# include <sys/types.h>
|
|
||||||
# include <pwd.h>
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#ifdef Q_OS_WIN
|
|
||||||
# ifndef NOMINMAX
|
|
||||||
# define NOMINMAX 1
|
|
||||||
# endif
|
|
||||||
# include <windows.h>
|
|
||||||
# include <lmcons.h>
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#include <QObject>
|
|
||||||
#include <QThread>
|
|
||||||
#include <QIODevice>
|
|
||||||
#include <QSharedMemory>
|
|
||||||
#include <QByteArray>
|
|
||||||
#include <QDataStream>
|
|
||||||
#include <QCryptographicHash>
|
|
||||||
#include <QLocalServer>
|
|
||||||
#include <QLocalSocket>
|
|
||||||
#include <QElapsedTimer>
|
|
||||||
#if QT_VERSION >= QT_VERSION_CHECK(5, 10, 0)
|
|
||||||
# include <QRandomGenerator>
|
|
||||||
#else
|
|
||||||
# include <QDateTime>
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#include "singlecoreapplication.h"
|
|
||||||
#include "singlecoreapplication_p.h"
|
|
||||||
|
|
||||||
SingleCoreApplicationPrivate::SingleCoreApplicationPrivate(SingleCoreApplication *ptr)
|
|
||||||
: q_ptr(ptr),
|
|
||||||
memory_(nullptr),
|
|
||||||
socket_(nullptr),
|
|
||||||
server_(nullptr),
|
|
||||||
instanceNumber_(-1) {}
|
|
||||||
|
|
||||||
SingleCoreApplicationPrivate::~SingleCoreApplicationPrivate() {
|
|
||||||
|
|
||||||
if (socket_ != nullptr) {
|
|
||||||
socket_->close();
|
|
||||||
delete socket_;
|
|
||||||
socket_ = nullptr;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (memory_ != nullptr) {
|
|
||||||
memory_->lock();
|
|
||||||
InstancesInfo *instance = static_cast<InstancesInfo*>(memory_->data());
|
|
||||||
if (server_ != nullptr) {
|
|
||||||
server_->close();
|
|
||||||
delete server_;
|
|
||||||
instance->primary = false;
|
|
||||||
instance->primaryPid = -1;
|
|
||||||
instance->primaryUser[0] = '\0';
|
|
||||||
instance->checksum = blockChecksum();
|
|
||||||
}
|
|
||||||
memory_->unlock();
|
|
||||||
|
|
||||||
delete memory_;
|
|
||||||
memory_ = nullptr;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
QString SingleCoreApplicationPrivate::getUsername() {
|
|
||||||
|
|
||||||
#ifdef Q_OS_UNIX
|
|
||||||
QString username;
|
|
||||||
#if defined(HAVE_GETEUID) && defined(HAVE_GETPWUID)
|
|
||||||
struct passwd *pw = getpwuid(geteuid());
|
|
||||||
if (pw) {
|
|
||||||
username = QString::fromLocal8Bit(pw->pw_name);
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
if (username.isEmpty()) {
|
|
||||||
#if QT_VERSION >= QT_VERSION_CHECK(5, 10, 0)
|
|
||||||
username = qEnvironmentVariable("USER");
|
|
||||||
#else
|
|
||||||
username = QString::fromLocal8Bit(qgetenv("USER"));
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
return username;
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#ifdef Q_OS_WIN
|
|
||||||
wchar_t username[UNLEN + 1];
|
|
||||||
// Specifies size of the buffer on input
|
|
||||||
DWORD usernameLength = UNLEN + 1;
|
|
||||||
if (GetUserNameW(username, &usernameLength)) {
|
|
||||||
return QString::fromWCharArray(username);
|
|
||||||
}
|
|
||||||
#if QT_VERSION >= QT_VERSION_CHECK(5, 10, 0)
|
|
||||||
return qEnvironmentVariable("USERNAME");
|
|
||||||
#else
|
|
||||||
return QString::fromLocal8Bit(qgetenv("USERNAME"));
|
|
||||||
#endif
|
|
||||||
#endif
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
void SingleCoreApplicationPrivate::genBlockServerName() {
|
|
||||||
|
|
||||||
QCryptographicHash appData(QCryptographicHash::Sha256);
|
|
||||||
appData.addData("SingleApplication");
|
|
||||||
appData.addData(SingleCoreApplication::app_t::applicationName().toUtf8());
|
|
||||||
appData.addData(SingleCoreApplication::app_t::organizationName().toUtf8());
|
|
||||||
appData.addData(SingleCoreApplication::app_t::organizationDomain().toUtf8());
|
|
||||||
|
|
||||||
if (!(options_ & SingleCoreApplication::Mode::ExcludeAppVersion)) {
|
|
||||||
appData.addData(SingleCoreApplication::app_t::applicationVersion().toUtf8());
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!(options_ & SingleCoreApplication::Mode::ExcludeAppPath)) {
|
|
||||||
#if defined(Q_OS_UNIX)
|
|
||||||
const QByteArray appImagePath = qgetenv("APPIMAGE");
|
|
||||||
if (appImagePath.isEmpty()) {
|
|
||||||
appData.addData(SingleCoreApplication::app_t::applicationFilePath().toUtf8());
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
appData.addData(appImagePath);
|
|
||||||
};
|
|
||||||
#elif defined(Q_OS_WIN)
|
|
||||||
appData.addData(SingleCoreApplication::app_t::applicationFilePath().toLower().toUtf8());
|
|
||||||
#else
|
|
||||||
appData.addData(SingleCoreApplication::app_t::applicationFilePath().toUtf8());
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
|
|
||||||
// User level block requires a user specific data in the hash
|
|
||||||
if (options_ & SingleCoreApplication::Mode::User) {
|
|
||||||
appData.addData(getUsername().toUtf8());
|
|
||||||
}
|
|
||||||
|
|
||||||
// Replace the backslash in RFC 2045 Base64 [a-zA-Z0-9+/=] to comply with server naming requirements.
|
|
||||||
blockServerName_ = appData.result().toBase64().replace("/", "_");
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
void SingleCoreApplicationPrivate::initializeMemoryBlock() const {
|
|
||||||
|
|
||||||
InstancesInfo *instance = static_cast<InstancesInfo*>(memory_->data());
|
|
||||||
instance->primary = false;
|
|
||||||
instance->secondary = 0;
|
|
||||||
instance->primaryPid = -1;
|
|
||||||
instance->primaryUser[0] = '\0';
|
|
||||||
instance->checksum = blockChecksum();
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
void SingleCoreApplicationPrivate::startPrimary() {
|
|
||||||
|
|
||||||
// Reset the number of connections
|
|
||||||
InstancesInfo *instance = static_cast<InstancesInfo*>(memory_->data());
|
|
||||||
|
|
||||||
instance->primary = true;
|
|
||||||
instance->primaryPid = QCoreApplication::applicationPid();
|
|
||||||
qstrncpy(instance->primaryUser, getUsername().toUtf8().data(), sizeof(instance->primaryUser));
|
|
||||||
instance->checksum = blockChecksum();
|
|
||||||
instanceNumber_ = 0;
|
|
||||||
// Successful creation means that no main process exists
|
|
||||||
// So we start a QLocalServer to listen for connections
|
|
||||||
QLocalServer::removeServer(blockServerName_);
|
|
||||||
server_ = new QLocalServer();
|
|
||||||
|
|
||||||
// Restrict access to the socket according to the SingleCoreApplication::Mode::User flag on User level or no restrictions
|
|
||||||
if (options_ & SingleCoreApplication::Mode::User) {
|
|
||||||
server_->setSocketOptions(QLocalServer::UserAccessOption);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
server_->setSocketOptions(QLocalServer::WorldAccessOption);
|
|
||||||
}
|
|
||||||
|
|
||||||
server_->listen(blockServerName_);
|
|
||||||
QObject::connect(server_, &QLocalServer::newConnection, this, &SingleCoreApplicationPrivate::slotConnectionEstablished);
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
void SingleCoreApplicationPrivate::startSecondary() {
|
|
||||||
|
|
||||||
InstancesInfo *instance = static_cast<InstancesInfo*>(memory_->data());
|
|
||||||
|
|
||||||
instance->secondary += 1;
|
|
||||||
instance->checksum = blockChecksum();
|
|
||||||
instanceNumber_ = instance->secondary;
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
bool SingleCoreApplicationPrivate::connectToPrimary(const int timeout, const ConnectionType connectionType) {
|
|
||||||
|
|
||||||
QElapsedTimer time;
|
|
||||||
time.start();
|
|
||||||
|
|
||||||
// Connect to the Local Server of the Primary Instance if not already connected.
|
|
||||||
if (socket_ == nullptr) {
|
|
||||||
socket_ = new QLocalSocket();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (socket_->state() == QLocalSocket::ConnectedState) return true;
|
|
||||||
|
|
||||||
if (socket_->state() != QLocalSocket::ConnectedState) {
|
|
||||||
|
|
||||||
forever {
|
|
||||||
randomSleep();
|
|
||||||
|
|
||||||
if (socket_->state() != QLocalSocket::ConnectingState) {
|
|
||||||
socket_->connectToServer(blockServerName_);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (socket_->state() == QLocalSocket::ConnectingState) {
|
|
||||||
socket_->waitForConnected(static_cast<int>(timeout - time.elapsed()));
|
|
||||||
}
|
|
||||||
|
|
||||||
// If connected break out of the loop
|
|
||||||
if (socket_->state() == QLocalSocket::ConnectedState) break;
|
|
||||||
|
|
||||||
// If elapsed time since start is longer than the method timeout return
|
|
||||||
if (time.elapsed() >= timeout) return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Initialisation message according to the SingleCoreApplication protocol
|
|
||||||
QByteArray initMsg;
|
|
||||||
QDataStream writeStream(&initMsg, QIODevice::WriteOnly);
|
|
||||||
writeStream.setVersion(QDataStream::Qt_5_8);
|
|
||||||
|
|
||||||
writeStream << blockServerName_.toLatin1();
|
|
||||||
writeStream << static_cast<quint8>(connectionType);
|
|
||||||
writeStream << instanceNumber_;
|
|
||||||
|
|
||||||
#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
|
|
||||||
quint16 checksum = qChecksum(QByteArray(initMsg, static_cast<quint32>(initMsg.length())));
|
|
||||||
#else
|
|
||||||
quint16 checksum = qChecksum(initMsg.constData(), static_cast<quint32>(initMsg.length()));
|
|
||||||
#endif
|
|
||||||
|
|
||||||
writeStream << checksum;
|
|
||||||
|
|
||||||
return writeConfirmedMessage(static_cast<int>(timeout - time.elapsed()), initMsg);
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
void SingleCoreApplicationPrivate::writeAck(QLocalSocket *sock) {
|
|
||||||
sock->putChar('\n');
|
|
||||||
}
|
|
||||||
|
|
||||||
bool SingleCoreApplicationPrivate::writeConfirmedMessage(const int timeout, const QByteArray &msg) const {
|
|
||||||
|
|
||||||
QElapsedTimer time;
|
|
||||||
time.start();
|
|
||||||
|
|
||||||
// Frame 1: The header indicates the message length that follows
|
|
||||||
QByteArray header;
|
|
||||||
QDataStream headerStream(&header, QIODevice::WriteOnly);
|
|
||||||
headerStream.setVersion(QDataStream::Qt_5_8);
|
|
||||||
headerStream << static_cast<quint64>(msg.length());
|
|
||||||
|
|
||||||
if (!writeConfirmedFrame(static_cast<int>(timeout - time.elapsed()), header)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Frame 2: The message
|
|
||||||
return writeConfirmedFrame(static_cast<int>(timeout - time.elapsed()), msg);
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
bool SingleCoreApplicationPrivate::writeConfirmedFrame(const int timeout, const QByteArray &msg) const {
|
|
||||||
|
|
||||||
socket_->write(msg);
|
|
||||||
socket_->flush();
|
|
||||||
|
|
||||||
bool result = socket_->waitForReadyRead(timeout);
|
|
||||||
if (result) {
|
|
||||||
socket_->read(1);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
quint16 SingleCoreApplicationPrivate::blockChecksum() const {
|
|
||||||
|
|
||||||
#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
|
|
||||||
quint16 checksum = qChecksum(QByteArray(static_cast<const char*>(memory_->constData()), offsetof(InstancesInfo, checksum)));
|
|
||||||
#else
|
|
||||||
quint16 checksum = qChecksum(static_cast<const char*>(memory_->constData()), offsetof(InstancesInfo, checksum));
|
|
||||||
#endif
|
|
||||||
|
|
||||||
return checksum;
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
qint64 SingleCoreApplicationPrivate::primaryPid() const {
|
|
||||||
|
|
||||||
memory_->lock();
|
|
||||||
InstancesInfo *instance = static_cast<InstancesInfo*>(memory_->data());
|
|
||||||
qint64 pid = instance->primaryPid;
|
|
||||||
memory_->unlock();
|
|
||||||
|
|
||||||
return pid;
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
QString SingleCoreApplicationPrivate::primaryUser() const {
|
|
||||||
|
|
||||||
memory_->lock();
|
|
||||||
InstancesInfo *instance = static_cast<InstancesInfo*>(memory_->data());
|
|
||||||
QByteArray username = instance->primaryUser;
|
|
||||||
memory_->unlock();
|
|
||||||
|
|
||||||
return QString::fromUtf8(username);
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Executed when a connection has been made to the LocalServer
|
|
||||||
*/
|
|
||||||
void SingleCoreApplicationPrivate::slotConnectionEstablished() {
|
|
||||||
|
|
||||||
QLocalSocket *nextConnSocket = server_->nextPendingConnection();
|
|
||||||
connectionMap_.insert(nextConnSocket, ConnectionInfo());
|
|
||||||
|
|
||||||
QObject::connect(nextConnSocket, &QLocalSocket::aboutToClose, this, [nextConnSocket, this]() {
|
|
||||||
const ConnectionInfo &info = connectionMap_[nextConnSocket];
|
|
||||||
slotClientConnectionClosed(nextConnSocket, info.instanceId);
|
|
||||||
});
|
|
||||||
|
|
||||||
QObject::connect(nextConnSocket, &QLocalSocket::disconnected, nextConnSocket, &QLocalSocket::deleteLater);
|
|
||||||
|
|
||||||
QObject::connect(nextConnSocket, &QLocalSocket::destroyed, this, [nextConnSocket, this]() {
|
|
||||||
connectionMap_.remove(nextConnSocket);
|
|
||||||
});
|
|
||||||
|
|
||||||
QObject::connect(nextConnSocket, &QLocalSocket::readyRead, this, [nextConnSocket, this]() {
|
|
||||||
const ConnectionInfo &info = connectionMap_[nextConnSocket];
|
|
||||||
switch (info.stage) {
|
|
||||||
case StageInitHeader:
|
|
||||||
readMessageHeader(nextConnSocket, StageInitBody);
|
|
||||||
break;
|
|
||||||
case StageInitBody:
|
|
||||||
readInitMessageBody(nextConnSocket);
|
|
||||||
break;
|
|
||||||
case StageConnectedHeader:
|
|
||||||
readMessageHeader(nextConnSocket, StageConnectedBody);
|
|
||||||
break;
|
|
||||||
case StageConnectedBody:
|
|
||||||
this->slotDataAvailable(nextConnSocket, info.instanceId);
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
break;
|
|
||||||
};
|
|
||||||
});
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
void SingleCoreApplicationPrivate::readMessageHeader(QLocalSocket *sock, SingleCoreApplicationPrivate::ConnectionStage nextStage) {
|
|
||||||
|
|
||||||
if (!connectionMap_.contains(sock)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (sock->bytesAvailable() < static_cast<qint64>(sizeof(quint64))) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
QDataStream headerStream(sock);
|
|
||||||
headerStream.setVersion(QDataStream::Qt_5_8);
|
|
||||||
|
|
||||||
// Read the header to know the message length
|
|
||||||
quint64 msgLen = 0;
|
|
||||||
headerStream >> msgLen;
|
|
||||||
ConnectionInfo &info = connectionMap_[sock];
|
|
||||||
info.stage = nextStage;
|
|
||||||
info.msgLen = msgLen;
|
|
||||||
|
|
||||||
writeAck(sock);
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
bool SingleCoreApplicationPrivate::isFrameComplete(QLocalSocket *sock) {
|
|
||||||
|
|
||||||
if (!connectionMap_.contains(sock)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
ConnectionInfo &info = connectionMap_[sock];
|
|
||||||
return (sock->bytesAvailable() >= static_cast<qint64>(info.msgLen));
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
void SingleCoreApplicationPrivate::readInitMessageBody(QLocalSocket *sock) {
|
|
||||||
|
|
||||||
Q_Q(SingleCoreApplication);
|
|
||||||
|
|
||||||
if (!isFrameComplete(sock)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Read the message body
|
|
||||||
QByteArray msgBytes = sock->readAll();
|
|
||||||
QDataStream readStream(msgBytes);
|
|
||||||
readStream.setVersion(QDataStream::Qt_5_8);
|
|
||||||
|
|
||||||
// server name
|
|
||||||
QByteArray latin1Name;
|
|
||||||
readStream >> latin1Name;
|
|
||||||
|
|
||||||
// connection type
|
|
||||||
ConnectionType connectionType = InvalidConnection;
|
|
||||||
quint8 connTypeVal = InvalidConnection;
|
|
||||||
readStream >> connTypeVal;
|
|
||||||
connectionType = static_cast<ConnectionType>(connTypeVal);
|
|
||||||
|
|
||||||
// instance id
|
|
||||||
quint32 instanceId = 0;
|
|
||||||
readStream >> instanceId;
|
|
||||||
|
|
||||||
// checksum
|
|
||||||
quint16 msgChecksum = 0;
|
|
||||||
readStream >> msgChecksum;
|
|
||||||
|
|
||||||
#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
|
|
||||||
const quint16 actualChecksum = qChecksum(QByteArray(msgBytes, static_cast<quint32>(msgBytes.length() - sizeof(quint16))));
|
|
||||||
#else
|
|
||||||
const quint16 actualChecksum = qChecksum(msgBytes.constData(), static_cast<quint32>(msgBytes.length() - sizeof(quint16)));
|
|
||||||
#endif
|
|
||||||
|
|
||||||
bool isValid = readStream.status() == QDataStream::Ok && QLatin1String(latin1Name) == blockServerName_ && msgChecksum == actualChecksum;
|
|
||||||
|
|
||||||
if (!isValid) {
|
|
||||||
sock->close();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
ConnectionInfo &info = connectionMap_[sock];
|
|
||||||
info.instanceId = instanceId;
|
|
||||||
info.stage = StageConnectedHeader;
|
|
||||||
|
|
||||||
if (connectionType == NewInstance || (connectionType == SecondaryInstance && options_ & SingleCoreApplication::Mode::SecondaryNotification)) {
|
|
||||||
emit q->instanceStarted();
|
|
||||||
}
|
|
||||||
|
|
||||||
writeAck(sock);
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
void SingleCoreApplicationPrivate::slotDataAvailable(QLocalSocket *dataSocket, const quint32 instanceId) {
|
|
||||||
|
|
||||||
Q_Q(SingleCoreApplication);
|
|
||||||
|
|
||||||
if (!isFrameComplete(dataSocket)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const QByteArray message = dataSocket->readAll();
|
|
||||||
|
|
||||||
writeAck(dataSocket);
|
|
||||||
|
|
||||||
ConnectionInfo &info = connectionMap_[dataSocket];
|
|
||||||
info.stage = StageConnectedHeader;
|
|
||||||
|
|
||||||
emit q->receivedMessage(instanceId, message);
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
void SingleCoreApplicationPrivate::slotClientConnectionClosed(QLocalSocket *closedSocket, const quint32 instanceId) {
|
|
||||||
|
|
||||||
if (closedSocket->bytesAvailable() > 0) {
|
|
||||||
slotDataAvailable(closedSocket, instanceId);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
void SingleCoreApplicationPrivate::randomSleep() {
|
|
||||||
|
|
||||||
#if QT_VERSION >= QT_VERSION_CHECK(5, 10, 0)
|
|
||||||
QThread::msleep(QRandomGenerator::global()->bounded(8U, 18U));
|
|
||||||
#else
|
|
||||||
qsrand(QDateTime::currentMSecsSinceEpoch() % std::numeric_limits<uint>::max());
|
|
||||||
QThread::msleep(qrand() % 11 + 8);
|
|
||||||
#endif
|
|
||||||
|
|
||||||
}
|
|
||||||
116
3rdparty/singleapplication/singlecoreapplication_p.h
vendored
116
3rdparty/singleapplication/singlecoreapplication_p.h
vendored
@@ -1,116 +0,0 @@
|
|||||||
// The MIT License (MIT)
|
|
||||||
//
|
|
||||||
// Copyright (c) Itay Grudev 2015 - 2020
|
|
||||||
//
|
|
||||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
||||||
// of this software and associated documentation files (the "Software"), to deal
|
|
||||||
// in the Software without restriction, including without limitation the rights
|
|
||||||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
||||||
// copies of the Software, and to permit persons to whom the Software is
|
|
||||||
// furnished to do so, subject to the following conditions:
|
|
||||||
//
|
|
||||||
// The above copyright notice and this permission notice shall be included in
|
|
||||||
// all copies or substantial portions of the Software.
|
|
||||||
//
|
|
||||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
||||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
||||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
||||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
||||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
||||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
|
||||||
// THE SOFTWARE.
|
|
||||||
|
|
||||||
//
|
|
||||||
// W A R N I N G !!!
|
|
||||||
// -----------------
|
|
||||||
//
|
|
||||||
// This is a modified version of SingleApplication,
|
|
||||||
// The original version is at:
|
|
||||||
//
|
|
||||||
// https://github.com/itay-grudev/SingleApplication
|
|
||||||
//
|
|
||||||
//
|
|
||||||
|
|
||||||
#ifndef SINGLECOREAPPLICATION_P_H
|
|
||||||
#define SINGLECOREAPPLICATION_P_H
|
|
||||||
|
|
||||||
#include <QtGlobal>
|
|
||||||
#include <QObject>
|
|
||||||
#include <QString>
|
|
||||||
#include <QHash>
|
|
||||||
|
|
||||||
#include "singlecoreapplication.h"
|
|
||||||
|
|
||||||
class QLocalServer;
|
|
||||||
class QLocalSocket;
|
|
||||||
class QSharedMemory;
|
|
||||||
|
|
||||||
struct InstancesInfo {
|
|
||||||
bool primary;
|
|
||||||
quint32 secondary;
|
|
||||||
qint64 primaryPid;
|
|
||||||
char primaryUser[128];
|
|
||||||
quint16 checksum;
|
|
||||||
};
|
|
||||||
|
|
||||||
struct ConnectionInfo {
|
|
||||||
explicit ConnectionInfo() : msgLen(0), instanceId(0), stage(0) {}
|
|
||||||
quint64 msgLen;
|
|
||||||
quint32 instanceId;
|
|
||||||
quint8 stage;
|
|
||||||
};
|
|
||||||
|
|
||||||
class SingleCoreApplicationPrivate : public QObject {
|
|
||||||
Q_OBJECT
|
|
||||||
|
|
||||||
public:
|
|
||||||
enum ConnectionType : quint8 {
|
|
||||||
InvalidConnection = 0,
|
|
||||||
NewInstance = 1,
|
|
||||||
SecondaryInstance = 2,
|
|
||||||
Reconnect = 3
|
|
||||||
};
|
|
||||||
enum ConnectionStage : quint8 {
|
|
||||||
StageInitHeader = 0,
|
|
||||||
StageInitBody = 1,
|
|
||||||
StageConnectedHeader = 2,
|
|
||||||
StageConnectedBody = 3,
|
|
||||||
};
|
|
||||||
Q_DECLARE_PUBLIC(SingleCoreApplication)
|
|
||||||
|
|
||||||
explicit SingleCoreApplicationPrivate(SingleCoreApplication *ptr);
|
|
||||||
~SingleCoreApplicationPrivate() override;
|
|
||||||
|
|
||||||
static QString getUsername();
|
|
||||||
void genBlockServerName();
|
|
||||||
void initializeMemoryBlock() const;
|
|
||||||
void startPrimary();
|
|
||||||
void startSecondary();
|
|
||||||
bool connectToPrimary(const int timeout, const ConnectionType connectionType);
|
|
||||||
quint16 blockChecksum() const;
|
|
||||||
qint64 primaryPid() const;
|
|
||||||
QString primaryUser() const;
|
|
||||||
bool isFrameComplete(QLocalSocket *sock);
|
|
||||||
void readMessageHeader(QLocalSocket *socket, const ConnectionStage nextStage);
|
|
||||||
void readInitMessageBody(QLocalSocket *socket);
|
|
||||||
void writeAck(QLocalSocket *sock);
|
|
||||||
bool writeConfirmedFrame(const int timeout, const QByteArray &msg) const;
|
|
||||||
bool writeConfirmedMessage(const int timeout, const QByteArray &msg) const;
|
|
||||||
static void randomSleep();
|
|
||||||
|
|
||||||
SingleCoreApplication *q_ptr;
|
|
||||||
QSharedMemory *memory_;
|
|
||||||
QLocalSocket *socket_;
|
|
||||||
QLocalServer *server_;
|
|
||||||
quint32 instanceNumber_;
|
|
||||||
QString blockServerName_;
|
|
||||||
SingleCoreApplication::Options options_;
|
|
||||||
QHash<QLocalSocket*, ConnectionInfo> connectionMap_;
|
|
||||||
|
|
||||||
public slots:
|
|
||||||
void slotConnectionEstablished();
|
|
||||||
void slotDataAvailable(QLocalSocket*, const quint32);
|
|
||||||
void slotClientConnectionClosed(QLocalSocket*, const quint32);
|
|
||||||
};
|
|
||||||
|
|
||||||
#endif // SINGLECOREAPPLICATION_P_H
|
|
||||||
@@ -1,8 +1,11 @@
|
|||||||
|
cmake_minimum_required(VERSION 3.7)
|
||||||
|
|
||||||
project(strawberry)
|
project(strawberry)
|
||||||
|
|
||||||
cmake_minimum_required(VERSION 3.7)
|
if(POLICY CMP0054)
|
||||||
cmake_policy(SET CMP0054 NEW)
|
cmake_policy(SET CMP0054 NEW)
|
||||||
if (CMAKE_VERSION VERSION_GREATER_EQUAL 3.12)
|
endif()
|
||||||
|
if(POLICY CMP0074)
|
||||||
cmake_policy(SET CMP0074 NEW)
|
cmake_policy(SET CMP0074 NEW)
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
@@ -15,13 +18,13 @@ include(cmake/Summary.cmake)
|
|||||||
include(cmake/OptionalSource.cmake)
|
include(cmake/OptionalSource.cmake)
|
||||||
include(cmake/ParseArguments.cmake)
|
include(cmake/ParseArguments.cmake)
|
||||||
|
|
||||||
if(${CMAKE_SYSTEM_NAME} STREQUAL "Linux")
|
if(CMAKE_SYSTEM_NAME STREQUAL "Linux")
|
||||||
set(LINUX ON)
|
set(LINUX ON)
|
||||||
endif()
|
endif()
|
||||||
if(${CMAKE_SYSTEM_NAME} STREQUAL "FreeBSD")
|
if(CMAKE_SYSTEM_NAME STREQUAL "FreeBSD")
|
||||||
set(FREEBSD ON)
|
set(FREEBSD ON)
|
||||||
endif()
|
endif()
|
||||||
if(${CMAKE_SYSTEM_NAME} STREQUAL "OpenBSD")
|
if(CMAKE_SYSTEM_NAME STREQUAL "OpenBSD")
|
||||||
set(OPENBSD ON)
|
set(OPENBSD ON)
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
@@ -71,23 +74,22 @@ else()
|
|||||||
$<$<COMPILE_LANGUAGE:CXX>:-Woverloaded-virtual>
|
$<$<COMPILE_LANGUAGE:CXX>:-Woverloaded-virtual>
|
||||||
$<$<COMPILE_LANGUAGE:CXX>:-Wold-style-cast>
|
$<$<COMPILE_LANGUAGE:CXX>:-Wold-style-cast>
|
||||||
)
|
)
|
||||||
endif()
|
option(BUILD_WERROR "Build with -Werror" OFF)
|
||||||
|
if(BUILD_WERROR)
|
||||||
option(BUILD_WERROR "Build with -Werror" OFF)
|
list(APPEND COMPILE_OPTIONS -Werror)
|
||||||
if(BUILD_WERROR)
|
endif()
|
||||||
list(APPEND COMPILE_OPTIONS -Werror)
|
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
add_compile_options(${COMPILE_OPTIONS})
|
add_compile_options(${COMPILE_OPTIONS})
|
||||||
|
|
||||||
if(${CMAKE_BUILD_TYPE} MATCHES "Release")
|
if(CMAKE_BUILD_TYPE MATCHES "Release")
|
||||||
add_definitions(-DNDEBUG)
|
add_definitions(-DNDEBUG)
|
||||||
add_definitions(-DQT_NO_DEBUG_OUTPUT)
|
add_definitions(-DQT_NO_DEBUG_OUTPUT)
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
if(APPLE)
|
option(USE_RPATH "Use RPATH" APPLE)
|
||||||
set(CMAKE_BUILD_WITH_INSTALL_RPATH TRUE)
|
if(USE_RPATH)
|
||||||
set(CMAKE_INSTALL_RPATH "@loader_path/../Frameworks")
|
set(CMAKE_INSTALL_RPATH_USE_LINK_PATH TRUE)
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
find_program(CCACHE_EXECUTABLE NAMES ccache)
|
find_program(CCACHE_EXECUTABLE NAMES ccache)
|
||||||
@@ -309,7 +311,10 @@ endif()
|
|||||||
|
|
||||||
# SingleApplication
|
# SingleApplication
|
||||||
add_subdirectory(3rdparty/singleapplication)
|
add_subdirectory(3rdparty/singleapplication)
|
||||||
set(SINGLEAPPLICATION_INCLUDE_DIRS ${CMAKE_CURRENT_SOURCE_DIR}/3rdparty/singleapplication)
|
set(SINGLEAPPLICATION_INCLUDE_DIRS
|
||||||
|
${CMAKE_CURRENT_SOURCE_DIR}/3rdparty/singleapplication/singleapplication
|
||||||
|
${CMAKE_CURRENT_SOURCE_DIR}/3rdparty/singleapplication/singlecoreapplication
|
||||||
|
)
|
||||||
set(SINGLEAPPLICATION_LIBRARIES singleapplication)
|
set(SINGLEAPPLICATION_LIBRARIES singleapplication)
|
||||||
set(SINGLECOREAPPLICATION_LIBRARIES singlecoreapplication)
|
set(SINGLECOREAPPLICATION_LIBRARIES singlecoreapplication)
|
||||||
|
|
||||||
@@ -376,12 +381,12 @@ optional_component(VLC ON "Engine: VLC backend"
|
|||||||
|
|
||||||
optional_component(SONGFINGERPRINTING ON "Song fingerprinting and tracking"
|
optional_component(SONGFINGERPRINTING ON "Song fingerprinting and tracking"
|
||||||
DEPENDS "chromaprint" CHROMAPRINT_FOUND
|
DEPENDS "chromaprint" CHROMAPRINT_FOUND
|
||||||
DEPENDS "gstreamer" GSTREAMER_FOUND
|
DEPENDS "gstreamer" HAVE_GSTREAMER
|
||||||
)
|
)
|
||||||
|
|
||||||
optional_component(MUSICBRAINZ ON "MusicBrainz integration"
|
optional_component(MUSICBRAINZ ON "MusicBrainz integration"
|
||||||
DEPENDS "chromaprint" CHROMAPRINT_FOUND
|
DEPENDS "chromaprint" CHROMAPRINT_FOUND
|
||||||
DEPENDS "gstreamer" GSTREAMER_FOUND
|
DEPENDS "gstreamer" HAVE_GSTREAMER
|
||||||
)
|
)
|
||||||
|
|
||||||
if(X11_FOUND OR HAVE_DBUS OR APPLE OR WIN32)
|
if(X11_FOUND OR HAVE_DBUS OR APPLE OR WIN32)
|
||||||
@@ -406,7 +411,7 @@ endif()
|
|||||||
|
|
||||||
optional_component(AUDIOCD ON "Devices: Audio CD support"
|
optional_component(AUDIOCD ON "Devices: Audio CD support"
|
||||||
DEPENDS "libcdio" LIBCDIO_FOUND
|
DEPENDS "libcdio" LIBCDIO_FOUND
|
||||||
DEPENDS "gstreamer" GSTREAMER_FOUND
|
DEPENDS "gstreamer" HAVE_GSTREAMER
|
||||||
)
|
)
|
||||||
|
|
||||||
optional_component(UDISKS2 ON "Devices: UDisks2 backend"
|
optional_component(UDISKS2 ON "Devices: UDisks2 backend"
|
||||||
@@ -512,9 +517,12 @@ endif()
|
|||||||
add_definitions(
|
add_definitions(
|
||||||
-DBOOST_BIND_NO_PLACEHOLDERS
|
-DBOOST_BIND_NO_PLACEHOLDERS
|
||||||
-DQT_STRICT_ITERATORS
|
-DQT_STRICT_ITERATORS
|
||||||
|
-DQT_NO_CAST_FROM_BYTEARRAY
|
||||||
-DQT_USE_QSTRINGBUILDER
|
-DQT_USE_QSTRINGBUILDER
|
||||||
-DQT_NO_URL_CAST_FROM_STRING
|
-DQT_NO_URL_CAST_FROM_STRING
|
||||||
-DQT_NO_CAST_TO_ASCII
|
-DQT_NO_CAST_TO_ASCII
|
||||||
|
-DQT_NO_NARROWING_CONVERSIONS_IN_CONNECT
|
||||||
|
-DQT_NO_FOREACH
|
||||||
)
|
)
|
||||||
|
|
||||||
if(WIN32)
|
if(WIN32)
|
||||||
|
|||||||
112
CONTRIBUTING.md
Normal file
112
CONTRIBUTING.md
Normal file
@@ -0,0 +1,112 @@
|
|||||||
|
# Contribution guidelines
|
||||||
|
|
||||||
|
Strawberry is an free and open-source project, it is possible and encouraged to participate in the development.
|
||||||
|
You can also participate by answering questions, reporting bugs or helping with documentation.
|
||||||
|
|
||||||
|
|
||||||
|
## Submitting a pull request
|
||||||
|
|
||||||
|
You should start by creating a fork of the Strawberry repository using the GitHub
|
||||||
|
fork button, after that you can clone the repository from your fork.
|
||||||
|
Replace "username" with your own.
|
||||||
|
|
||||||
|
|
||||||
|
### Clone the repository
|
||||||
|
|
||||||
|
git clone git@github.com:username/strawberry.git
|
||||||
|
cd strawberry
|
||||||
|
|
||||||
|
|
||||||
|
### Setup the remote
|
||||||
|
|
||||||
|
git remote add upstream git@github.com:strawberrymusicplayer/strawberry.git
|
||||||
|
|
||||||
|
|
||||||
|
### Create a new branch
|
||||||
|
|
||||||
|
This creates a new branch from the master branch that you use for specific
|
||||||
|
changes.
|
||||||
|
|
||||||
|
git checkout -b your-branch
|
||||||
|
|
||||||
|
|
||||||
|
### Stage changes
|
||||||
|
|
||||||
|
Once you've finished working on a specific change, stage the changes for
|
||||||
|
a specific commit.
|
||||||
|
|
||||||
|
Always keep your commits relevant to the pull request, and each commit as
|
||||||
|
small as possible.
|
||||||
|
|
||||||
|
git add -p
|
||||||
|
|
||||||
|
|
||||||
|
### Commit changes
|
||||||
|
|
||||||
|
git commit
|
||||||
|
|
||||||
|
|
||||||
|
### Commit messages
|
||||||
|
|
||||||
|
The first line should start with "Class:", which referer to the class
|
||||||
|
that is changed. Don't use a trailing period after the first line.
|
||||||
|
If this change affects more than one class, omit the class and write a
|
||||||
|
more general message.
|
||||||
|
You only need to include a main description (body) for larger changes
|
||||||
|
where the one line is not enough to describe everything.
|
||||||
|
The main description starts after two newlines, it is normal prose and
|
||||||
|
should use normal punctuation and capital letters where appropriate.
|
||||||
|
|
||||||
|
An example of the expected format for git commit messages is as follows:
|
||||||
|
|
||||||
|
```
|
||||||
|
class: Short explanation of the commit
|
||||||
|
|
||||||
|
Longer explanation explaining exactly what's changed, why it's changed,
|
||||||
|
and what bugs were fixed.
|
||||||
|
|
||||||
|
Fixes #1234
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
### Push the changes to GitHub
|
||||||
|
|
||||||
|
Once you've finished working on the changes, push the branch
|
||||||
|
to the Git repository and open a new pull request.
|
||||||
|
|
||||||
|
|
||||||
|
git push origin your-branch
|
||||||
|
|
||||||
|
|
||||||
|
### Update your fork's master branch
|
||||||
|
|
||||||
|
git checkout master
|
||||||
|
git pull --rebase origin master
|
||||||
|
git fetch upstream
|
||||||
|
git merge upstream/master
|
||||||
|
git push origin master
|
||||||
|
|
||||||
|
|
||||||
|
### Update your branch
|
||||||
|
|
||||||
|
git checkout your-branch
|
||||||
|
git fetch upstream
|
||||||
|
git rebase upstream/master
|
||||||
|
git push origin your-branch --force-with-lease
|
||||||
|
|
||||||
|
|
||||||
|
### Rebase your branch
|
||||||
|
|
||||||
|
If you need fix any issues with your commits, you need to rebase your
|
||||||
|
branch to squash any commits, or to change the commit message.
|
||||||
|
|
||||||
|
git checkout your-branch
|
||||||
|
git log
|
||||||
|
git rebase -i commit_sha~
|
||||||
|
git push origin your-branch --force-with-lease
|
||||||
|
|
||||||
|
|
||||||
|
### Delete your fork
|
||||||
|
|
||||||
|
If you do not plan to work more on Strawberry, please delete your fork from GitHub
|
||||||
|
once the pull requests are merged.
|
||||||
123
Changelog
123
Changelog
@@ -2,6 +2,121 @@ Strawberry Music Player
|
|||||||
=======================
|
=======================
|
||||||
ChangeLog
|
ChangeLog
|
||||||
|
|
||||||
|
Version 1.0.16 (2023.03.27):
|
||||||
|
|
||||||
|
Bugfixes:
|
||||||
|
* Fixed lyrics from Musixmatch.
|
||||||
|
* Fixed possible file corruption when saving both tags and embedded cover using the tag editor (#1158).
|
||||||
|
* Fixed compile without GStreamer.
|
||||||
|
* Fixed context and playing now album art rendering on High DPI displays (#1161).
|
||||||
|
* Fixed setting source properties (device, user-agent, ssl-strict) with GStreamer 1.22 (playbin3) and higher (#1148).
|
||||||
|
* Fixed rescan songs feature not ignoring mtime.
|
||||||
|
* Search lyrics by artist instead of album artist by default.
|
||||||
|
|
||||||
|
Code improvements:
|
||||||
|
* Replace use of deprecated QSqlDatabase::exec().
|
||||||
|
|
||||||
|
Added features:
|
||||||
|
* Added backend setting for strict SSL mode.
|
||||||
|
* Read AcoustID and MusicBrainz tags.
|
||||||
|
* Submit MusicBrainz tags with ListenBrainz.
|
||||||
|
|
||||||
|
Version 1.0.15 (2023.03.04):
|
||||||
|
|
||||||
|
Bugfixes:
|
||||||
|
* Fixed playlist column showing invalid last played date for streams.
|
||||||
|
* Fixed crash when the audio bin failed to initialize (#1123, #1133).
|
||||||
|
* Fixed duplicated filename when organizing files using dot in the filename (#1136).
|
||||||
|
* Fixed tag inline editing for streams (#1130).
|
||||||
|
* Fixed resetting play statistics using tag edit dialog (#1124).
|
||||||
|
* Fixed compilation songs not showing if group by was set to other than (Album) Artist / Album (#1140).
|
||||||
|
|
||||||
|
Enhancements:
|
||||||
|
* Added lyrics from stands4 (lyrics.com).
|
||||||
|
* Added Sonogram analyzer.
|
||||||
|
* Use GStreamer playbin3 with GStreamer 1.22.0 and higher.
|
||||||
|
|
||||||
|
Code improvements:
|
||||||
|
* Made use of C++11 enum class where possible.
|
||||||
|
* Use new QNativeIpcKey based QSharedMemory constructor with Qt 6.6 and higher.
|
||||||
|
|
||||||
|
Version 1.0.14 (2023.01.13):
|
||||||
|
|
||||||
|
Bugfixes:
|
||||||
|
* Fix initial volume not set when using Auto as output (#1104).
|
||||||
|
* Fix saving moodbar if the URL contains host, ie.: UNC paths for SMB (#1101).
|
||||||
|
* Fix CollectionBackendTest compile error (#1100).
|
||||||
|
* Remove explicitly enabling debug messages (#1106).
|
||||||
|
|
||||||
|
Version 1.0.13 (2023.01.09):
|
||||||
|
|
||||||
|
Bugfixes:
|
||||||
|
* Fixed volume synchronization leading to infinite loop resulting in crash when adjusting volume while playing (#1089).
|
||||||
|
* Fixed incorrect volume.
|
||||||
|
* Fixed collection organizing incorrectly handling slashes inside {} brackets for variables (#1091).
|
||||||
|
* Fixed saving relative playlists to non-existing playlist files (#1092).
|
||||||
|
* Fixed intermittent crash on collection model query (#1095).
|
||||||
|
* Require system icons for fancy tabbar and settings sidebar to be larger than 22x22 (#1084).
|
||||||
|
|
||||||
|
Version 1.0.12 (2023.01.02):
|
||||||
|
|
||||||
|
Bugfixes:
|
||||||
|
* Fixed crash when adjusting volume with mouse wheel (#1089).
|
||||||
|
* Fixed playback stopping in certain cases where the next track was unavailable (#958).
|
||||||
|
* (Windows) Apply patch for fonts too large on High DPI screen (QTBUG-108593).
|
||||||
|
|
||||||
|
Removed features:
|
||||||
|
* Removed appearance settings for changing palette colors, it was never properly implemented.
|
||||||
|
|
||||||
|
Version 1.0.11 (2022.12.30):
|
||||||
|
|
||||||
|
Bugfixes:
|
||||||
|
* Capitalize GLib application name so it appears nicely in GNOME and PulseAudio Volume Control (#1066).
|
||||||
|
* Fixed missing application icon for PulseAudio Volume Control (#1066).
|
||||||
|
* Ignore errors for missing albums when updating Tidal collection if there are results (#1061).
|
||||||
|
* Only run periodic collection scan when moitoring collection setting is on.
|
||||||
|
* Fixed an edge case where the context headline text was being cut short (#1067).
|
||||||
|
* Made "Show in file browser" support SpaceFM filemanager (#1073).
|
||||||
|
* Fixed incorrect tab order in edit tag dialog (#1075).
|
||||||
|
* Changed "FMPS_PlayCount" to "FMPS_Playcount" when saving tag (#1074).
|
||||||
|
* Fixed compilation tag read and write for MP4 (#1076).
|
||||||
|
* Removed incorrect use of "TPE1" for performer when reading ID3 tags (#1076).
|
||||||
|
* Disable tag fields for unsupported tags in tag editor.
|
||||||
|
* Don't allow organizing files without unique tags (track or title) for filename (#1077).
|
||||||
|
* Don't remove disc from album title when creating cover hash to allow different covers for each disc on an album (#1069).
|
||||||
|
* Fixed incorrect relative paths for song filenames when saving playlists if the saved playlist location is a symablic link to the song filename (#1071).
|
||||||
|
* Scrobble "Various Artists" as album artist (#1082).
|
||||||
|
|
||||||
|
Enhancements:
|
||||||
|
* Use system volume instead of own software volume when available (#1037).
|
||||||
|
* Improved Tidal and Qobuz support with timed requests.
|
||||||
|
* Support MPRIS2 xesam:userRating.
|
||||||
|
|
||||||
|
Version 1.0.10 (2022.10.21):
|
||||||
|
|
||||||
|
Bugfixes:
|
||||||
|
* Fixed "Could not open settings file for writing: No such file or directory" error before settings file is created.
|
||||||
|
* Fixed visual glitch on currently playing track (#1051).
|
||||||
|
* Fixed "Unknown error" on Tidal search (#1047).
|
||||||
|
* Fixed incomplete lyrics from Genius.
|
||||||
|
* Fixed icons not showing in the file view on some systems (#1024).
|
||||||
|
* Fixed issues with context and playing widget stopping when using VLC (#1054).
|
||||||
|
* (macOS) Fixed search field related crash when playlist toolbar is turned off.
|
||||||
|
|
||||||
|
Enhancements:
|
||||||
|
* Fixed narrowing conversions in connects.
|
||||||
|
* Fixed casts from QByteArray.
|
||||||
|
* Removed subdir for generated dbus files
|
||||||
|
* Removed use of fixed font in context (#1040).
|
||||||
|
* Improve Musixmatch lyrics search.
|
||||||
|
|
||||||
|
Version 1.0.9 (2022.09.03):
|
||||||
|
|
||||||
|
Bugfixes:
|
||||||
|
* Fixed parsing album title from radio stream metadata (#1023).
|
||||||
|
* (macOS) Fixed Strawberry not starting, incorrect rpath for libgcc_s.1.1.dylib (#1025).
|
||||||
|
* (macOS) Fixed HTTP streaming.
|
||||||
|
|
||||||
Version 1.0.8 (2022.08.29):
|
Version 1.0.8 (2022.08.29):
|
||||||
|
|
||||||
Bugfixes:
|
Bugfixes:
|
||||||
@@ -225,7 +340,7 @@ Version 0.9.3 (2021.04.18)
|
|||||||
Bugfixes:
|
Bugfixes:
|
||||||
* Fix "Show in file browser" to work with thunar.
|
* Fix "Show in file browser" to work with thunar.
|
||||||
* Check that the clicked rating position is to the right or left of the rectangle.
|
* Check that the clicked rating position is to the right or left of the rectangle.
|
||||||
* Fix rescan when collection directory is removed and readded.
|
* Fix rescan when collection directory is removed and re-added.
|
||||||
* Create GLib main event loop on non-glib systems to fix stream discoverer.
|
* Create GLib main event loop on non-glib systems to fix stream discoverer.
|
||||||
* (macOS) Fix intermittent abort on startup.
|
* (macOS) Fix intermittent abort on startup.
|
||||||
* (macOS) Fix Tidal and Qobuz search field not showing.
|
* (macOS) Fix Tidal and Qobuz search field not showing.
|
||||||
@@ -562,7 +677,7 @@ Version 0.6.10 (2020.05.01)
|
|||||||
* Made font and font sizes in context configurable.
|
* Made font and font sizes in context configurable.
|
||||||
* Splitting artist and song title to the relevant metadata when artist and song title is sent as title separated by a dash in streams.
|
* Splitting artist and song title to the relevant metadata when artist and song title is sent as title separated by a dash in streams.
|
||||||
* Added label to show collection pixmap disk cache used in settings.
|
* Added label to show collection pixmap disk cache used in settings.
|
||||||
* Icreased default collection pixmap disk cache to 360.
|
* Increased default collection pixmap disk cache to 360.
|
||||||
|
|
||||||
New features:
|
New features:
|
||||||
* Added back Tidal streaming support.
|
* Added back Tidal streaming support.
|
||||||
@@ -630,7 +745,7 @@ Version 0.6.7 (2019.11.27)
|
|||||||
* Fixed "Pressing Previous in player" behaviour setting
|
* Fixed "Pressing Previous in player" behaviour setting
|
||||||
* Fixed updating compilations where there are spaces or special characters in filenames
|
* Fixed updating compilations where there are spaces or special characters in filenames
|
||||||
* Fixed cases where songs were stuck in "Various Artists" because not all songs in
|
* Fixed cases where songs were stuck in "Various Artists" because not all songs in
|
||||||
the same compilation was removed from the model before readded with actual artist.
|
the same compilation was removed from the model before re-added with actual artist.
|
||||||
* Fixed a bug when importing playlists where metadata was reset
|
* Fixed a bug when importing playlists where metadata was reset
|
||||||
* Fixed scrobbler to also scrobble songs without album title
|
* Fixed scrobbler to also scrobble songs without album title
|
||||||
* Fixed text for replay gain setting not loading in backend setting
|
* Fixed text for replay gain setting not loading in backend setting
|
||||||
@@ -983,7 +1098,7 @@ Version 0.1.5 (2018.05.16)
|
|||||||
|
|
||||||
|
|
||||||
Version 0.1.4 (2018.05.14)
|
Version 0.1.4 (2018.05.14)
|
||||||
* Fixed compliation with clang compiler
|
* Fixed compilation with clang compiler
|
||||||
* This release is mainly to get it working on openbsd and freebsd.
|
* This release is mainly to get it working on openbsd and freebsd.
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
16
README.md
16
README.md
@@ -1,4 +1,4 @@
|
|||||||
:strawberry: Strawberry Music Player [](https://github.com/strawberrymusicplayer/strawberry/actions)
|
:strawberry: Strawberry Music Player [](https://github.com/strawberrymusicplayer/strawberry/actions)
|
||||||
=======================
|
=======================
|
||||||
[](https://github.com/sponsors/jonaski)
|
[](https://github.com/sponsors/jonaski)
|
||||||
[](https://patreon.com/jonaskvinge)
|
[](https://patreon.com/jonaskvinge)
|
||||||
@@ -51,7 +51,7 @@ Funding developers is a way to contribute to open source projects you appreciate
|
|||||||
* Edit tags on audio files
|
* Edit tags on audio files
|
||||||
* Fetch tags from MusicBrainz
|
* Fetch tags from MusicBrainz
|
||||||
* Album cover art from [Last.fm](https://www.last.fm/), [Musicbrainz](https://musicbrainz.org/), [Discogs](https://www.discogs.com/), [Musixmatch](https://www.musixmatch.com/), [Deezer](https://www.deezer.com/), [Tidal](https://www.tidal.com/), [Qobuz](https://www.qobuz.com/) and [Spotify](https://www.spotify.com/)
|
* Album cover art from [Last.fm](https://www.last.fm/), [Musicbrainz](https://musicbrainz.org/), [Discogs](https://www.discogs.com/), [Musixmatch](https://www.musixmatch.com/), [Deezer](https://www.deezer.com/), [Tidal](https://www.tidal.com/), [Qobuz](https://www.qobuz.com/) and [Spotify](https://www.spotify.com/)
|
||||||
* Song lyrics from [AudD](https://audd.io/), [Genius](https://genius.com/), [Musixmatch](https://www.musixmatch.com/), [ChartLyrics](http://www.chartlyrics.com/), [lyrics.ovh](https://lyrics.ovh/) and [lololyrics.com](https://www.lololyrics.com/)
|
* Song lyrics from [Lyrics.com](https://www.lyrics.com/), [AudD](https://audd.io/), [Genius](https://genius.com/), [Musixmatch](https://www.musixmatch.com/), [ChartLyrics](http://www.chartlyrics.com/), [lyrics.ovh](https://lyrics.ovh/) and [lololyrics.com](https://www.lololyrics.com/)
|
||||||
* Support for multiple backends
|
* Support for multiple backends
|
||||||
* Audio analyzer
|
* Audio analyzer
|
||||||
* Audio equalizer
|
* Audio equalizer
|
||||||
@@ -69,10 +69,11 @@ It has so far been tested to work on Linux, OpenBSD, FreeBSD, macOS and Windows.
|
|||||||
To build Strawberry from source you need the following installed on your system with the additional development packages/headers:
|
To build Strawberry from source you need the following installed on your system with the additional development packages/headers:
|
||||||
|
|
||||||
* [CMake](https://cmake.org/)
|
* [CMake](https://cmake.org/)
|
||||||
* [GCC](https://gcc.gnu.org/), [Clang](https://clang.llvm.org/) or [MSVC](https://visualstudio.microsoft.com/vs/features/cplusplus/) compiler
|
* C/C++ compiler ([GCC](https://gcc.gnu.org/), [Clang](https://clang.llvm.org/) or [MSVC](https://visualstudio.microsoft.com/vs/features/cplusplus/))
|
||||||
|
* [pkg-config](https://www.freedesktop.org/wiki/Software/pkg-config/) or [pkgconf](https://github.com/pkgconf/pkgconf)
|
||||||
* [Boost](https://www.boost.org/)
|
* [Boost](https://www.boost.org/)
|
||||||
* [GLib](https://developer.gnome.org/glib/)
|
* [GLib](https://developer.gnome.org/glib/)
|
||||||
* [Qt 5.9 or higher (or Qt 6) with components Core, Gui, Widgets, Concurrent, Network and Sql](https://www.qt.io/)
|
* [Qt 6 or Qt 5.9 or higher with components Core, Gui, Widgets, Concurrent, Network and Sql](https://www.qt.io/)
|
||||||
* [SQLite 3.9 or newer with FTS5](https://www.sqlite.org)
|
* [SQLite 3.9 or newer with FTS5](https://www.sqlite.org)
|
||||||
* [Protobuf](https://developers.google.com/protocol-buffers/)
|
* [Protobuf](https://developers.google.com/protocol-buffers/)
|
||||||
* [ALSA (Required on Linux)](https://www.alsa-project.org/)
|
* [ALSA (Required on Linux)](https://www.alsa-project.org/)
|
||||||
@@ -102,15 +103,18 @@ You should also install the gstreamer plugins base and good, and optionally bad,
|
|||||||
### Compile and install:
|
### Compile and install:
|
||||||
|
|
||||||
cd strawberry
|
cd strawberry
|
||||||
mkdir build && cd build
|
mkdir build
|
||||||
|
cd build
|
||||||
cmake .. -DBUILD_WITH_QT6=ON
|
cmake .. -DBUILD_WITH_QT6=ON
|
||||||
make -j$(nproc)
|
make -j $(nproc)
|
||||||
sudo make install
|
sudo make install
|
||||||
|
|
||||||
Strawberry is backwards compatible with Qt 5, to compile with Qt 5 use:
|
Strawberry is backwards compatible with Qt 5, to compile with Qt 5 use:
|
||||||
|
|
||||||
cmake .. -DBUILD_WITH_QT5=ON
|
cmake .. -DBUILD_WITH_QT5=ON
|
||||||
|
|
||||||
|
To compile on Windows with Visual Studio 2019 or 2022, see https://github.com/strawberrymusicplayer/strawberry-msvc
|
||||||
|
|
||||||
### :penguin: Packaging status
|
### :penguin: Packaging status
|
||||||
|
|
||||||
[](https://repology.org/metapackage/strawberry/versions)
|
[](https://repology.org/metapackage/strawberry/versions)
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
set(STRAWBERRY_VERSION_MAJOR 1)
|
set(STRAWBERRY_VERSION_MAJOR 1)
|
||||||
set(STRAWBERRY_VERSION_MINOR 0)
|
set(STRAWBERRY_VERSION_MINOR 0)
|
||||||
set(STRAWBERRY_VERSION_PATCH 8)
|
set(STRAWBERRY_VERSION_PATCH 16)
|
||||||
#set(STRAWBERRY_VERSION_PRERELEASE rc1)
|
#set(STRAWBERRY_VERSION_PRERELEASE rc1)
|
||||||
|
|
||||||
set(INCLUDE_GIT_REVISION OFF)
|
set(INCLUDE_GIT_REVISION OFF)
|
||||||
@@ -14,10 +14,6 @@ set(STRAWBERRY_VERSION_RPM_R "1")
|
|||||||
set(STRAWBERRY_VERSION_PAC_V "${majorminorpatch}")
|
set(STRAWBERRY_VERSION_PAC_V "${majorminorpatch}")
|
||||||
set(STRAWBERRY_VERSION_PAC_R "1")
|
set(STRAWBERRY_VERSION_PAC_R "1")
|
||||||
|
|
||||||
if(${STRAWBERRY_VERSION_PATCH} EQUAL "0")
|
|
||||||
set(STRAWBERRY_VERSION_DISPLAY "${STRAWBERRY_VERSION_MAJOR}.${STRAWBERRY_VERSION_MINOR}")
|
|
||||||
endif(${STRAWBERRY_VERSION_PATCH} EQUAL "0")
|
|
||||||
|
|
||||||
if(STRAWBERRY_VERSION_PRERELEASE)
|
if(STRAWBERRY_VERSION_PRERELEASE)
|
||||||
set(STRAWBERRY_VERSION_DISPLAY "${STRAWBERRY_VERSION_DISPLAY} ${STRAWBERRY_VERSION_PRERELEASE}")
|
set(STRAWBERRY_VERSION_DISPLAY "${STRAWBERRY_VERSION_DISPLAY} ${STRAWBERRY_VERSION_PRERELEASE}")
|
||||||
set(STRAWBERRY_VERSION_RPM_R "0.${STRAWBERRY_VERSION_PRERELEASE}")
|
set(STRAWBERRY_VERSION_RPM_R "0.${STRAWBERRY_VERSION_PRERELEASE}")
|
||||||
|
|||||||
@@ -7,6 +7,7 @@
|
|||||||
<file>schema/schema-13.sql</file>
|
<file>schema/schema-13.sql</file>
|
||||||
<file>schema/schema-14.sql</file>
|
<file>schema/schema-14.sql</file>
|
||||||
<file>schema/schema-15.sql</file>
|
<file>schema/schema-15.sql</file>
|
||||||
|
<file>schema/schema-16.sql</file>
|
||||||
<file>schema/device-schema.sql</file>
|
<file>schema/device-schema.sql</file>
|
||||||
<file>style/strawberry.css</file>
|
<file>style/strawberry.css</file>
|
||||||
<file>style/smartplaylistsearchterm.css</file>
|
<file>style/smartplaylistsearchterm.css</file>
|
||||||
|
|||||||
@@ -67,7 +67,21 @@ CREATE TABLE device_%deviceid_songs (
|
|||||||
|
|
||||||
cue_path TEXT,
|
cue_path TEXT,
|
||||||
|
|
||||||
rating INTEGER DEFAULT -1
|
rating INTEGER DEFAULT -1,
|
||||||
|
|
||||||
|
acoustid_id TEXT,
|
||||||
|
acoustid_fingerprint TEXT,
|
||||||
|
|
||||||
|
musicbrainz_album_artist_id TEXT,
|
||||||
|
musicbrainz_artist_id TEXT,
|
||||||
|
musicbrainz_original_artist_id TEXT,
|
||||||
|
musicbrainz_album_id TEXT,
|
||||||
|
musicbrainz_original_album_id TEXT,
|
||||||
|
musicbrainz_recording_id TEXT,
|
||||||
|
musicbrainz_track_id TEXT,
|
||||||
|
musicbrainz_disc_id TEXT,
|
||||||
|
musicbrainz_release_group_id TEXT,
|
||||||
|
musicbrainz_work_id TEXT
|
||||||
|
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -80,4 +94,4 @@ CREATE VIRTUAL TABLE device_%deviceid_fts USING fts5(
|
|||||||
tokenize = "unicode61 remove_diacritics 1"
|
tokenize = "unicode61 remove_diacritics 1"
|
||||||
);
|
);
|
||||||
|
|
||||||
UPDATE devices SET schema_version=3 WHERE ROWID=%deviceid;
|
UPDATE devices SET schema_version=4 WHERE ROWID=%deviceid;
|
||||||
|
|||||||
217
data/schema/schema-16.sql
Normal file
217
data/schema/schema-16.sql
Normal file
@@ -0,0 +1,217 @@
|
|||||||
|
ALTER TABLE songs ADD COLUMN acoustid_id TEXT;
|
||||||
|
|
||||||
|
ALTER TABLE songs ADD COLUMN acoustid_fingerprint TEXT;
|
||||||
|
|
||||||
|
ALTER TABLE songs ADD COLUMN musicbrainz_album_artist_id TEXT;
|
||||||
|
|
||||||
|
ALTER TABLE songs ADD COLUMN musicbrainz_artist_id TEXT;
|
||||||
|
|
||||||
|
ALTER TABLE songs ADD COLUMN musicbrainz_original_artist_id TEXT;
|
||||||
|
|
||||||
|
ALTER TABLE songs ADD COLUMN musicbrainz_album_id TEXT;
|
||||||
|
|
||||||
|
ALTER TABLE songs ADD COLUMN musicbrainz_original_album_id TEXT;
|
||||||
|
|
||||||
|
ALTER TABLE songs ADD COLUMN musicbrainz_recording_id TEXT;
|
||||||
|
|
||||||
|
ALTER TABLE songs ADD COLUMN musicbrainz_track_id TEXT;
|
||||||
|
|
||||||
|
ALTER TABLE songs ADD COLUMN musicbrainz_disc_id TEXT;
|
||||||
|
|
||||||
|
ALTER TABLE songs ADD COLUMN musicbrainz_release_group_id TEXT;
|
||||||
|
|
||||||
|
ALTER TABLE songs ADD COLUMN musicbrainz_work_id TEXT;
|
||||||
|
|
||||||
|
ALTER TABLE subsonic_songs ADD COLUMN acoustid_id TEXT;
|
||||||
|
|
||||||
|
ALTER TABLE subsonic_songs ADD COLUMN acoustid_fingerprint TEXT;
|
||||||
|
|
||||||
|
ALTER TABLE subsonic_songs ADD COLUMN musicbrainz_album_artist_id TEXT;
|
||||||
|
|
||||||
|
ALTER TABLE subsonic_songs ADD COLUMN musicbrainz_artist_id TEXT;
|
||||||
|
|
||||||
|
ALTER TABLE subsonic_songs ADD COLUMN musicbrainz_original_artist_id TEXT;
|
||||||
|
|
||||||
|
ALTER TABLE subsonic_songs ADD COLUMN musicbrainz_album_id TEXT;
|
||||||
|
|
||||||
|
ALTER TABLE subsonic_songs ADD COLUMN musicbrainz_original_album_id TEXT;
|
||||||
|
|
||||||
|
ALTER TABLE subsonic_songs ADD COLUMN musicbrainz_recording_id TEXT;
|
||||||
|
|
||||||
|
ALTER TABLE subsonic_songs ADD COLUMN musicbrainz_track_id TEXT;
|
||||||
|
|
||||||
|
ALTER TABLE subsonic_songs ADD COLUMN musicbrainz_disc_id TEXT;
|
||||||
|
|
||||||
|
ALTER TABLE subsonic_songs ADD COLUMN musicbrainz_release_group_id TEXT;
|
||||||
|
|
||||||
|
ALTER TABLE subsonic_songs ADD COLUMN musicbrainz_work_id TEXT;
|
||||||
|
|
||||||
|
ALTER TABLE tidal_artists_songs ADD COLUMN acoustid_id TEXT;
|
||||||
|
|
||||||
|
ALTER TABLE tidal_artists_songs ADD COLUMN acoustid_fingerprint TEXT;
|
||||||
|
|
||||||
|
ALTER TABLE tidal_artists_songs ADD COLUMN musicbrainz_album_artist_id TEXT;
|
||||||
|
|
||||||
|
ALTER TABLE tidal_artists_songs ADD COLUMN musicbrainz_artist_id TEXT;
|
||||||
|
|
||||||
|
ALTER TABLE tidal_artists_songs ADD COLUMN musicbrainz_original_artist_id TEXT;
|
||||||
|
|
||||||
|
ALTER TABLE tidal_artists_songs ADD COLUMN musicbrainz_album_id TEXT;
|
||||||
|
|
||||||
|
ALTER TABLE tidal_artists_songs ADD COLUMN musicbrainz_original_album_id TEXT;
|
||||||
|
|
||||||
|
ALTER TABLE tidal_artists_songs ADD COLUMN musicbrainz_recording_id TEXT;
|
||||||
|
|
||||||
|
ALTER TABLE tidal_artists_songs ADD COLUMN musicbrainz_track_id TEXT;
|
||||||
|
|
||||||
|
ALTER TABLE tidal_artists_songs ADD COLUMN musicbrainz_disc_id TEXT;
|
||||||
|
|
||||||
|
ALTER TABLE tidal_artists_songs ADD COLUMN musicbrainz_release_group_id TEXT;
|
||||||
|
|
||||||
|
ALTER TABLE tidal_artists_songs ADD COLUMN musicbrainz_work_id TEXT;
|
||||||
|
|
||||||
|
ALTER TABLE tidal_albums_songs ADD COLUMN acoustid_id TEXT;
|
||||||
|
|
||||||
|
ALTER TABLE tidal_albums_songs ADD COLUMN acoustid_fingerprint TEXT;
|
||||||
|
|
||||||
|
ALTER TABLE tidal_albums_songs ADD COLUMN musicbrainz_album_artist_id TEXT;
|
||||||
|
|
||||||
|
ALTER TABLE tidal_albums_songs ADD COLUMN musicbrainz_artist_id TEXT;
|
||||||
|
|
||||||
|
ALTER TABLE tidal_albums_songs ADD COLUMN musicbrainz_original_artist_id TEXT;
|
||||||
|
|
||||||
|
ALTER TABLE tidal_albums_songs ADD COLUMN musicbrainz_album_id TEXT;
|
||||||
|
|
||||||
|
ALTER TABLE tidal_albums_songs ADD COLUMN musicbrainz_original_album_id TEXT;
|
||||||
|
|
||||||
|
ALTER TABLE tidal_albums_songs ADD COLUMN musicbrainz_recording_id TEXT;
|
||||||
|
|
||||||
|
ALTER TABLE tidal_albums_songs ADD COLUMN musicbrainz_track_id TEXT;
|
||||||
|
|
||||||
|
ALTER TABLE tidal_albums_songs ADD COLUMN musicbrainz_disc_id TEXT;
|
||||||
|
|
||||||
|
ALTER TABLE tidal_albums_songs ADD COLUMN musicbrainz_release_group_id TEXT;
|
||||||
|
|
||||||
|
ALTER TABLE tidal_albums_songs ADD COLUMN musicbrainz_work_id TEXT;
|
||||||
|
|
||||||
|
ALTER TABLE tidal_songs ADD COLUMN acoustid_id TEXT;
|
||||||
|
|
||||||
|
ALTER TABLE tidal_songs ADD COLUMN acoustid_fingerprint TEXT;
|
||||||
|
|
||||||
|
ALTER TABLE tidal_songs ADD COLUMN musicbrainz_album_artist_id TEXT;
|
||||||
|
|
||||||
|
ALTER TABLE tidal_songs ADD COLUMN musicbrainz_artist_id TEXT;
|
||||||
|
|
||||||
|
ALTER TABLE tidal_songs ADD COLUMN musicbrainz_original_artist_id TEXT;
|
||||||
|
|
||||||
|
ALTER TABLE tidal_songs ADD COLUMN musicbrainz_album_id TEXT;
|
||||||
|
|
||||||
|
ALTER TABLE tidal_songs ADD COLUMN musicbrainz_original_album_id TEXT;
|
||||||
|
|
||||||
|
ALTER TABLE tidal_songs ADD COLUMN musicbrainz_recording_id TEXT;
|
||||||
|
|
||||||
|
ALTER TABLE tidal_songs ADD COLUMN musicbrainz_track_id TEXT;
|
||||||
|
|
||||||
|
ALTER TABLE tidal_songs ADD COLUMN musicbrainz_disc_id TEXT;
|
||||||
|
|
||||||
|
ALTER TABLE tidal_songs ADD COLUMN musicbrainz_release_group_id TEXT;
|
||||||
|
|
||||||
|
ALTER TABLE tidal_songs ADD COLUMN musicbrainz_work_id TEXT;
|
||||||
|
|
||||||
|
ALTER TABLE qobuz_artists_songs ADD COLUMN acoustid_id TEXT;
|
||||||
|
|
||||||
|
ALTER TABLE qobuz_artists_songs ADD COLUMN acoustid_fingerprint TEXT;
|
||||||
|
|
||||||
|
ALTER TABLE qobuz_artists_songs ADD COLUMN musicbrainz_album_artist_id TEXT;
|
||||||
|
|
||||||
|
ALTER TABLE qobuz_artists_songs ADD COLUMN musicbrainz_artist_id TEXT;
|
||||||
|
|
||||||
|
ALTER TABLE qobuz_artists_songs ADD COLUMN musicbrainz_original_artist_id TEXT;
|
||||||
|
|
||||||
|
ALTER TABLE qobuz_artists_songs ADD COLUMN musicbrainz_album_id TEXT;
|
||||||
|
|
||||||
|
ALTER TABLE qobuz_artists_songs ADD COLUMN musicbrainz_original_album_id TEXT;
|
||||||
|
|
||||||
|
ALTER TABLE qobuz_artists_songs ADD COLUMN musicbrainz_recording_id TEXT;
|
||||||
|
|
||||||
|
ALTER TABLE qobuz_artists_songs ADD COLUMN musicbrainz_track_id TEXT;
|
||||||
|
|
||||||
|
ALTER TABLE qobuz_artists_songs ADD COLUMN musicbrainz_disc_id TEXT;
|
||||||
|
|
||||||
|
ALTER TABLE qobuz_artists_songs ADD COLUMN musicbrainz_release_group_id TEXT;
|
||||||
|
|
||||||
|
ALTER TABLE qobuz_artists_songs ADD COLUMN musicbrainz_work_id TEXT;
|
||||||
|
|
||||||
|
ALTER TABLE qobuz_albums_songs ADD COLUMN acoustid_id TEXT;
|
||||||
|
|
||||||
|
ALTER TABLE qobuz_albums_songs ADD COLUMN acoustid_fingerprint TEXT;
|
||||||
|
|
||||||
|
ALTER TABLE qobuz_albums_songs ADD COLUMN musicbrainz_album_artist_id TEXT;
|
||||||
|
|
||||||
|
ALTER TABLE qobuz_albums_songs ADD COLUMN musicbrainz_artist_id TEXT;
|
||||||
|
|
||||||
|
ALTER TABLE qobuz_albums_songs ADD COLUMN musicbrainz_original_artist_id TEXT;
|
||||||
|
|
||||||
|
ALTER TABLE qobuz_albums_songs ADD COLUMN musicbrainz_album_id TEXT;
|
||||||
|
|
||||||
|
ALTER TABLE qobuz_albums_songs ADD COLUMN musicbrainz_original_album_id TEXT;
|
||||||
|
|
||||||
|
ALTER TABLE qobuz_albums_songs ADD COLUMN musicbrainz_recording_id TEXT;
|
||||||
|
|
||||||
|
ALTER TABLE qobuz_albums_songs ADD COLUMN musicbrainz_track_id TEXT;
|
||||||
|
|
||||||
|
ALTER TABLE qobuz_albums_songs ADD COLUMN musicbrainz_disc_id TEXT;
|
||||||
|
|
||||||
|
ALTER TABLE qobuz_albums_songs ADD COLUMN musicbrainz_release_group_id TEXT;
|
||||||
|
|
||||||
|
ALTER TABLE qobuz_albums_songs ADD COLUMN musicbrainz_work_id TEXT;
|
||||||
|
|
||||||
|
ALTER TABLE qobuz_songs ADD COLUMN acoustid_id TEXT;
|
||||||
|
|
||||||
|
ALTER TABLE qobuz_songs ADD COLUMN acoustid_fingerprint TEXT;
|
||||||
|
|
||||||
|
ALTER TABLE qobuz_songs ADD COLUMN musicbrainz_album_artist_id TEXT;
|
||||||
|
|
||||||
|
ALTER TABLE qobuz_songs ADD COLUMN musicbrainz_artist_id TEXT;
|
||||||
|
|
||||||
|
ALTER TABLE qobuz_songs ADD COLUMN musicbrainz_original_artist_id TEXT;
|
||||||
|
|
||||||
|
ALTER TABLE qobuz_songs ADD COLUMN musicbrainz_album_id TEXT;
|
||||||
|
|
||||||
|
ALTER TABLE qobuz_songs ADD COLUMN musicbrainz_original_album_id TEXT;
|
||||||
|
|
||||||
|
ALTER TABLE qobuz_songs ADD COLUMN musicbrainz_recording_id TEXT;
|
||||||
|
|
||||||
|
ALTER TABLE qobuz_songs ADD COLUMN musicbrainz_track_id TEXT;
|
||||||
|
|
||||||
|
ALTER TABLE qobuz_songs ADD COLUMN musicbrainz_disc_id TEXT;
|
||||||
|
|
||||||
|
ALTER TABLE qobuz_songs ADD COLUMN musicbrainz_release_group_id TEXT;
|
||||||
|
|
||||||
|
ALTER TABLE qobuz_songs ADD COLUMN musicbrainz_work_id TEXT;
|
||||||
|
|
||||||
|
ALTER TABLE playlist_items ADD COLUMN acoustid_id TEXT;
|
||||||
|
|
||||||
|
ALTER TABLE playlist_items ADD COLUMN acoustid_fingerprint TEXT;
|
||||||
|
|
||||||
|
ALTER TABLE playlist_items ADD COLUMN musicbrainz_album_artist_id TEXT;
|
||||||
|
|
||||||
|
ALTER TABLE playlist_items ADD COLUMN musicbrainz_artist_id TEXT;
|
||||||
|
|
||||||
|
ALTER TABLE playlist_items ADD COLUMN musicbrainz_original_artist_id TEXT;
|
||||||
|
|
||||||
|
ALTER TABLE playlist_items ADD COLUMN musicbrainz_album_id TEXT;
|
||||||
|
|
||||||
|
ALTER TABLE playlist_items ADD COLUMN musicbrainz_original_album_id TEXT;
|
||||||
|
|
||||||
|
ALTER TABLE playlist_items ADD COLUMN musicbrainz_recording_id TEXT;
|
||||||
|
|
||||||
|
ALTER TABLE playlist_items ADD COLUMN musicbrainz_track_id TEXT;
|
||||||
|
|
||||||
|
ALTER TABLE playlist_items ADD COLUMN musicbrainz_disc_id TEXT;
|
||||||
|
|
||||||
|
ALTER TABLE playlist_items ADD COLUMN musicbrainz_release_group_id TEXT;
|
||||||
|
|
||||||
|
ALTER TABLE playlist_items ADD COLUMN musicbrainz_work_id TEXT;
|
||||||
|
|
||||||
|
UPDATE schema_version SET version=16;
|
||||||
@@ -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 (15);
|
INSERT INTO schema_version (version) VALUES (16);
|
||||||
|
|
||||||
CREATE TABLE IF NOT EXISTS directories (
|
CREATE TABLE IF NOT EXISTS directories (
|
||||||
path TEXT NOT NULL,
|
path TEXT NOT NULL,
|
||||||
@@ -75,7 +75,21 @@ CREATE TABLE IF NOT EXISTS songs (
|
|||||||
|
|
||||||
cue_path TEXT,
|
cue_path TEXT,
|
||||||
|
|
||||||
rating INTEGER DEFAULT -1
|
rating INTEGER DEFAULT -1,
|
||||||
|
|
||||||
|
acoustid_id TEXT,
|
||||||
|
acoustid_fingerprint TEXT,
|
||||||
|
|
||||||
|
musicbrainz_album_artist_id TEXT,
|
||||||
|
musicbrainz_artist_id TEXT,
|
||||||
|
musicbrainz_original_artist_id TEXT,
|
||||||
|
musicbrainz_album_id TEXT,
|
||||||
|
musicbrainz_original_album_id TEXT,
|
||||||
|
musicbrainz_recording_id TEXT,
|
||||||
|
musicbrainz_track_id TEXT,
|
||||||
|
musicbrainz_disc_id TEXT,
|
||||||
|
musicbrainz_release_group_id TEXT,
|
||||||
|
musicbrainz_work_id TEXT
|
||||||
|
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -137,7 +151,21 @@ CREATE TABLE IF NOT EXISTS subsonic_songs (
|
|||||||
|
|
||||||
cue_path TEXT,
|
cue_path TEXT,
|
||||||
|
|
||||||
rating INTEGER DEFAULT -1
|
rating INTEGER DEFAULT -1,
|
||||||
|
|
||||||
|
acoustid_id TEXT,
|
||||||
|
acoustid_fingerprint TEXT,
|
||||||
|
|
||||||
|
musicbrainz_album_artist_id TEXT,
|
||||||
|
musicbrainz_artist_id TEXT,
|
||||||
|
musicbrainz_original_artist_id TEXT,
|
||||||
|
musicbrainz_album_id TEXT,
|
||||||
|
musicbrainz_original_album_id TEXT,
|
||||||
|
musicbrainz_recording_id TEXT,
|
||||||
|
musicbrainz_track_id TEXT,
|
||||||
|
musicbrainz_disc_id TEXT,
|
||||||
|
musicbrainz_release_group_id TEXT,
|
||||||
|
musicbrainz_work_id TEXT
|
||||||
|
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -199,7 +227,21 @@ CREATE TABLE IF NOT EXISTS tidal_artists_songs (
|
|||||||
|
|
||||||
cue_path TEXT,
|
cue_path TEXT,
|
||||||
|
|
||||||
rating INTEGER DEFAULT -1
|
rating INTEGER DEFAULT -1,
|
||||||
|
|
||||||
|
acoustid_id TEXT,
|
||||||
|
acoustid_fingerprint TEXT,
|
||||||
|
|
||||||
|
musicbrainz_album_artist_id TEXT,
|
||||||
|
musicbrainz_artist_id TEXT,
|
||||||
|
musicbrainz_original_artist_id TEXT,
|
||||||
|
musicbrainz_album_id TEXT,
|
||||||
|
musicbrainz_original_album_id TEXT,
|
||||||
|
musicbrainz_recording_id TEXT,
|
||||||
|
musicbrainz_track_id TEXT,
|
||||||
|
musicbrainz_disc_id TEXT,
|
||||||
|
musicbrainz_release_group_id TEXT,
|
||||||
|
musicbrainz_work_id TEXT
|
||||||
|
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -261,7 +303,21 @@ CREATE TABLE IF NOT EXISTS tidal_albums_songs (
|
|||||||
|
|
||||||
cue_path TEXT,
|
cue_path TEXT,
|
||||||
|
|
||||||
rating INTEGER DEFAULT -1
|
rating INTEGER DEFAULT -1,
|
||||||
|
|
||||||
|
acoustid_id TEXT,
|
||||||
|
acoustid_fingerprint TEXT,
|
||||||
|
|
||||||
|
musicbrainz_album_artist_id TEXT,
|
||||||
|
musicbrainz_artist_id TEXT,
|
||||||
|
musicbrainz_original_artist_id TEXT,
|
||||||
|
musicbrainz_album_id TEXT,
|
||||||
|
musicbrainz_original_album_id TEXT,
|
||||||
|
musicbrainz_recording_id TEXT,
|
||||||
|
musicbrainz_track_id TEXT,
|
||||||
|
musicbrainz_disc_id TEXT,
|
||||||
|
musicbrainz_release_group_id TEXT,
|
||||||
|
musicbrainz_work_id TEXT
|
||||||
|
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -323,7 +379,21 @@ CREATE TABLE IF NOT EXISTS tidal_songs (
|
|||||||
|
|
||||||
cue_path TEXT,
|
cue_path TEXT,
|
||||||
|
|
||||||
rating INTEGER DEFAULT -1
|
rating INTEGER DEFAULT -1,
|
||||||
|
|
||||||
|
acoustid_id TEXT,
|
||||||
|
acoustid_fingerprint TEXT,
|
||||||
|
|
||||||
|
musicbrainz_album_artist_id TEXT,
|
||||||
|
musicbrainz_artist_id TEXT,
|
||||||
|
musicbrainz_original_artist_id TEXT,
|
||||||
|
musicbrainz_album_id TEXT,
|
||||||
|
musicbrainz_original_album_id TEXT,
|
||||||
|
musicbrainz_recording_id TEXT,
|
||||||
|
musicbrainz_track_id TEXT,
|
||||||
|
musicbrainz_disc_id TEXT,
|
||||||
|
musicbrainz_release_group_id TEXT,
|
||||||
|
musicbrainz_work_id TEXT
|
||||||
|
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -385,7 +455,21 @@ CREATE TABLE IF NOT EXISTS qobuz_artists_songs (
|
|||||||
|
|
||||||
cue_path TEXT,
|
cue_path TEXT,
|
||||||
|
|
||||||
rating INTEGER DEFAULT -1
|
rating INTEGER DEFAULT -1,
|
||||||
|
|
||||||
|
acoustid_id TEXT,
|
||||||
|
acoustid_fingerprint TEXT,
|
||||||
|
|
||||||
|
musicbrainz_album_artist_id TEXT,
|
||||||
|
musicbrainz_artist_id TEXT,
|
||||||
|
musicbrainz_original_artist_id TEXT,
|
||||||
|
musicbrainz_album_id TEXT,
|
||||||
|
musicbrainz_original_album_id TEXT,
|
||||||
|
musicbrainz_recording_id TEXT,
|
||||||
|
musicbrainz_track_id TEXT,
|
||||||
|
musicbrainz_disc_id TEXT,
|
||||||
|
musicbrainz_release_group_id TEXT,
|
||||||
|
musicbrainz_work_id TEXT
|
||||||
|
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -447,7 +531,21 @@ CREATE TABLE IF NOT EXISTS qobuz_albums_songs (
|
|||||||
|
|
||||||
cue_path TEXT,
|
cue_path TEXT,
|
||||||
|
|
||||||
rating INTEGER DEFAULT -1
|
rating INTEGER DEFAULT -1,
|
||||||
|
|
||||||
|
acoustid_id TEXT,
|
||||||
|
acoustid_fingerprint TEXT,
|
||||||
|
|
||||||
|
musicbrainz_album_artist_id TEXT,
|
||||||
|
musicbrainz_artist_id TEXT,
|
||||||
|
musicbrainz_original_artist_id TEXT,
|
||||||
|
musicbrainz_album_id TEXT,
|
||||||
|
musicbrainz_original_album_id TEXT,
|
||||||
|
musicbrainz_recording_id TEXT,
|
||||||
|
musicbrainz_track_id TEXT,
|
||||||
|
musicbrainz_disc_id TEXT,
|
||||||
|
musicbrainz_release_group_id TEXT,
|
||||||
|
musicbrainz_work_id TEXT
|
||||||
|
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -509,7 +607,21 @@ CREATE TABLE IF NOT EXISTS qobuz_songs (
|
|||||||
|
|
||||||
cue_path TEXT,
|
cue_path TEXT,
|
||||||
|
|
||||||
rating INTEGER DEFAULT -1
|
rating INTEGER DEFAULT -1,
|
||||||
|
|
||||||
|
acoustid_id TEXT,
|
||||||
|
acoustid_fingerprint TEXT,
|
||||||
|
|
||||||
|
musicbrainz_album_artist_id TEXT,
|
||||||
|
musicbrainz_artist_id TEXT,
|
||||||
|
musicbrainz_original_artist_id TEXT,
|
||||||
|
musicbrainz_album_id TEXT,
|
||||||
|
musicbrainz_original_album_id TEXT,
|
||||||
|
musicbrainz_recording_id TEXT,
|
||||||
|
musicbrainz_track_id TEXT,
|
||||||
|
musicbrainz_disc_id TEXT,
|
||||||
|
musicbrainz_release_group_id TEXT,
|
||||||
|
musicbrainz_work_id TEXT
|
||||||
|
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -591,7 +703,21 @@ CREATE TABLE IF NOT EXISTS playlist_items (
|
|||||||
|
|
||||||
cue_path TEXT,
|
cue_path TEXT,
|
||||||
|
|
||||||
rating INTEGER DEFAULT -1
|
rating INTEGER DEFAULT -1,
|
||||||
|
|
||||||
|
acoustid_id TEXT,
|
||||||
|
acoustid_fingerprint TEXT,
|
||||||
|
|
||||||
|
musicbrainz_album_artist_id TEXT,
|
||||||
|
musicbrainz_artist_id TEXT,
|
||||||
|
musicbrainz_original_artist_id TEXT,
|
||||||
|
musicbrainz_album_id TEXT,
|
||||||
|
musicbrainz_original_album_id TEXT,
|
||||||
|
musicbrainz_recording_id TEXT,
|
||||||
|
musicbrainz_track_id TEXT,
|
||||||
|
musicbrainz_disc_id TEXT,
|
||||||
|
musicbrainz_release_group_id TEXT,
|
||||||
|
musicbrainz_work_id TEXT
|
||||||
|
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|||||||
4
debian/control.in
vendored
4
debian/control.in
vendored
@@ -17,7 +17,7 @@ Build-Depends: debhelper (>= 11),
|
|||||||
libasound2-dev,
|
libasound2-dev,
|
||||||
libpulse-dev,
|
libpulse-dev,
|
||||||
libtag1-dev,
|
libtag1-dev,
|
||||||
libicu-devel,
|
libicu-dev,
|
||||||
@DEBIAN_BUILD_DEPENDS_QT_PACKAGES@,
|
@DEBIAN_BUILD_DEPENDS_QT_PACKAGES@,
|
||||||
libgstreamer1.0-dev,
|
libgstreamer1.0-dev,
|
||||||
libgstreamer-plugins-base1.0-dev,
|
libgstreamer-plugins-base1.0-dev,
|
||||||
@@ -52,7 +52,7 @@ Description: music player and music collection organizer
|
|||||||
- Edit tags on audio files
|
- Edit tags on audio files
|
||||||
- Automatically retrieve tags from MusicBrainz
|
- Automatically retrieve tags from MusicBrainz
|
||||||
- Album cover art from Last.fm, Musicbrainz, Discogs, Musixmatch, Deezer, Tidal, Qobuz and Spotify
|
- Album cover art from Last.fm, Musicbrainz, Discogs, Musixmatch, Deezer, Tidal, Qobuz and Spotify
|
||||||
- Song lyrics from AudD, Genius, Musixmatch, ChartLyrics, lyrics.ovh and lololyrics.com
|
- Song lyrics from Lyrics.com, AudD, Genius, Musixmatch, ChartLyrics, lyrics.ovh and lololyrics.com
|
||||||
- Audio analyzer
|
- Audio analyzer
|
||||||
- Audio equalizer
|
- Audio equalizer
|
||||||
- Transfer music to mass-storage USB players, MTP compatible devices and iPod Nano/Classic
|
- Transfer music to mass-storage USB players, MTP compatible devices and iPod Nano/Classic
|
||||||
|
|||||||
20
debian/copyright
vendored
20
debian/copyright
vendored
@@ -5,10 +5,10 @@ Source: https://github.com/strawberrymusicplayer/strawberry
|
|||||||
|
|
||||||
Files: *
|
Files: *
|
||||||
Copyright: 2010-2015, David Sansome <me@davidsansome.com>
|
Copyright: 2010-2015, David Sansome <me@davidsansome.com>
|
||||||
2012-2014, 2017-2022 Jonas Kvinge <jonas@jkvinge.net>
|
2012-2014, 2017-2023 Jonas Kvinge <jonas@jkvinge.net>
|
||||||
License: GPL-3+
|
License: GPL-3+
|
||||||
|
|
||||||
Files: src/core/timeconstants.h
|
Files: src/utilities/timeconstants.h
|
||||||
ext/libstrawberry-common/core/logging.cpp
|
ext/libstrawberry-common/core/logging.cpp
|
||||||
ext/libstrawberry-common/core/logging.h
|
ext/libstrawberry-common/core/logging.h
|
||||||
ext/libstrawberry-common/core/messagehandler.cpp
|
ext/libstrawberry-common/core/messagehandler.cpp
|
||||||
@@ -98,7 +98,7 @@ Files: src/core/main.h
|
|||||||
ext/macdeploycheck/*
|
ext/macdeploycheck/*
|
||||||
src/widgets/resizabletextedit.cpp
|
src/widgets/resizabletextedit.cpp
|
||||||
src/widgets/resizabletextedit.h
|
src/widgets/resizabletextedit.h
|
||||||
Copyright: 2012-2014, 2017-2022, Jonas Kvinge <jonas@jkvinge.net>
|
Copyright: 2012-2014, 2017-2023, Jonas Kvinge <jonas@jkvinge.net>
|
||||||
License: GPL-3+
|
License: GPL-3+
|
||||||
|
|
||||||
Files: src/engine/enginebase.cpp
|
Files: src/engine/enginebase.cpp
|
||||||
@@ -130,11 +130,6 @@ Copyright: 2012, David Sansome <me@davidsansome.com>
|
|||||||
2018-2021, Jonas Kvinge <jonas@jkvinge.net>
|
2018-2021, Jonas Kvinge <jonas@jkvinge.net>
|
||||||
License: GPL-3+
|
License: GPL-3+
|
||||||
|
|
||||||
Files: src/core/appearance.cpp
|
|
||||||
src/core/appearance.h
|
|
||||||
Copyright: 2012, Arnaud Bienner <arnaud.bienner@gmail.com>
|
|
||||||
License: GPL-3+
|
|
||||||
|
|
||||||
Files: src/covermanager/discogscoverprovider.cpp
|
Files: src/covermanager/discogscoverprovider.cpp
|
||||||
src/covermanager/discogscoverprovider.h
|
src/covermanager/discogscoverprovider.h
|
||||||
Copyright: 2012, Martin Björklund <mbj4668@gmail.com>
|
Copyright: 2012, Martin Björklund <mbj4668@gmail.com>
|
||||||
@@ -232,9 +227,14 @@ Files: src/widgets/clickablelabel.cpp
|
|||||||
Copyright: 2010, 2011, Andrea Decorte <adecorte@gmail.com>
|
Copyright: 2010, 2011, Andrea Decorte <adecorte@gmail.com>
|
||||||
License: GPL-3+
|
License: GPL-3+
|
||||||
|
|
||||||
Files: src/widgets/volumeslider.cpp
|
Files: src/widgets/sliderslider.cpp
|
||||||
|
src/widgets/sliderslider.h
|
||||||
|
src/widgets/prettyslider.cpp
|
||||||
|
src/widgets/prettyslider.h
|
||||||
|
src/widgets/volumeslider.cpp
|
||||||
src/widgets/volumeslider.h
|
src/widgets/volumeslider.h
|
||||||
Copyright: 2005, Gábor Lehel
|
Copyright: 2018-2023, Jonas Kvinge <jonas@jkvinge.net>
|
||||||
|
2005, Gábor Lehel
|
||||||
2003, Mark Kretschmann <markey@web.de>
|
2003, Mark Kretschmann <markey@web.de>
|
||||||
License: GPL-2+
|
License: GPL-2+
|
||||||
|
|
||||||
|
|||||||
@@ -29,7 +29,7 @@
|
|||||||
<li>Edit tags on audio files</li>
|
<li>Edit tags on audio files</li>
|
||||||
<li>Automatically retrieve tags from MusicBrainz</li>
|
<li>Automatically retrieve tags from MusicBrainz</li>
|
||||||
<li>Album cover art from Last.fm, Musicbrainz, Discogs, Musixmatch, Deezer, Tidal, Qobuz and Spotify</li>
|
<li>Album cover art from Last.fm, Musicbrainz, Discogs, Musixmatch, Deezer, Tidal, Qobuz and Spotify</li>
|
||||||
<li>Song lyrics from AudD, Genius, Musixmatch, ChartLyrics, lyrics.ovh and lololyrics.com</li>
|
<li>Song lyrics from Lyrics.com, AudD, Genius, Musixmatch, ChartLyrics, lyrics.ovh and lololyrics.com</li>
|
||||||
<li>Support for multiple backends</li>
|
<li>Support for multiple backends</li>
|
||||||
<li>Audio analyzer and equalizer</li>
|
<li>Audio analyzer and equalizer</li>
|
||||||
<li>Transfer music to mass-storage USB players, MTP compatible devices and iPod Nano/Classic</li>
|
<li>Transfer music to mass-storage USB players, MTP compatible devices and iPod Nano/Classic</li>
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ TryExec=strawberry
|
|||||||
Icon=strawberry
|
Icon=strawberry
|
||||||
Terminal=false
|
Terminal=false
|
||||||
Categories=AudioVideo;Player;Qt;Audio;
|
Categories=AudioVideo;Player;Qt;Audio;
|
||||||
|
Keywords=Audio;Player;
|
||||||
StartupNotify=false
|
StartupNotify=false
|
||||||
MimeType=x-content/audio-player;application/ogg;application/x-ogg;application/x-ogm-audio;audio/flac;audio/ogg;audio/vorbis;audio/aac;audio/mp4;audio/mpeg;audio/mpegurl;audio/vnd.rn-realaudio;audio/x-flac;audio/x-oggflac;audio/x-vorbis;audio/x-vorbis+ogg;audio/x-speex;audio/x-wav;audio/x-wavpack;audio/x-ape;audio/x-mp3;audio/x-mpeg;audio/x-mpegurl;audio/x-ms-wma;audio/x-musepack;audio/x-pn-realaudio;audio/x-scpls;video/x-ms-asf;x-scheme-handler/tidal;
|
MimeType=x-content/audio-player;application/ogg;application/x-ogg;application/x-ogm-audio;audio/flac;audio/ogg;audio/vorbis;audio/aac;audio/mp4;audio/mpeg;audio/mpegurl;audio/vnd.rn-realaudio;audio/x-flac;audio/x-oggflac;audio/x-vorbis;audio/x-vorbis+ogg;audio/x-speex;audio/x-wav;audio/x-wavpack;audio/x-ape;audio/x-mp3;audio/x-mpeg;audio/x-mpegurl;audio/x-ms-wma;audio/x-musepack;audio/x-pn-realaudio;audio/x-scpls;video/x-ms-asf;x-scheme-handler/tidal;
|
||||||
StartupWMClass=strawberry
|
StartupWMClass=strawberry
|
||||||
|
|||||||
2
dist/unix/strawberry.1
vendored
2
dist/unix/strawberry.1
vendored
@@ -29,7 +29,7 @@ Features:
|
|||||||
.br
|
.br
|
||||||
- Album cover art from Last.fm, Musicbrainz, Discogs, Musixmatch, Deezer, Tidal, Qobuz and Spotify
|
- Album cover art from Last.fm, Musicbrainz, Discogs, Musixmatch, Deezer, Tidal, Qobuz and Spotify
|
||||||
.br
|
.br
|
||||||
- Song lyrics from AudD, Genius, Musixmatch, ChartLyrics, lyrics.ovh and lololyrics.com
|
- Song lyrics from Lyrics.com, AudD, Genius, Musixmatch, ChartLyrics, lyrics.ovh and lololyrics.com
|
||||||
.br
|
.br
|
||||||
- Support for multiple backends
|
- Support for multiple backends
|
||||||
.br
|
.br
|
||||||
|
|||||||
2
dist/unix/strawberry.spec.in
vendored
2
dist/unix/strawberry.spec.in
vendored
@@ -119,7 +119,7 @@ Features:
|
|||||||
- Edit tags on audio files
|
- Edit tags on audio files
|
||||||
- Automatically retrieve tags from MusicBrainz
|
- Automatically retrieve tags from MusicBrainz
|
||||||
- Album cover art from Last.fm, Musicbrainz, Discogs, Musixmatch, Deezer, Tidal, Qobuz and Spotify
|
- Album cover art from Last.fm, Musicbrainz, Discogs, Musixmatch, Deezer, Tidal, Qobuz and Spotify
|
||||||
- Song lyrics from AudD, Genius, Musixmatch, ChartLyrics, lyrics.ovh and lololyrics.com
|
- Song lyrics from Lyrics.com, AudD, Genius, Musixmatch, ChartLyrics, lyrics.ovh and lololyrics.com
|
||||||
- Support for multiple backends
|
- Support for multiple backends
|
||||||
- Audio analyzer
|
- Audio analyzer
|
||||||
- Audio equalizer
|
- Audio equalizer
|
||||||
|
|||||||
245
dist/windows/strawberry.nsi.in
vendored
245
dist/windows/strawberry.nsi.in
vendored
@@ -1,57 +1,68 @@
|
|||||||
!define build_type ""
|
|
||||||
!define compiler "unknown"
|
|
||||||
!define arch "unknown"
|
|
||||||
|
|
||||||
!if "@ARCH@" == "x86"
|
|
||||||
!define arch_x86
|
|
||||||
!undef arch
|
|
||||||
!define arch "x86"
|
|
||||||
!endif
|
|
||||||
|
|
||||||
!if "@ARCH@" == "i686-w64-mingw32.shared"
|
|
||||||
!define arch_x86
|
|
||||||
!undef arch
|
|
||||||
!define arch "x86"
|
|
||||||
!endif
|
|
||||||
|
|
||||||
!if "@ARCH@" == "x86_64"
|
|
||||||
!define arch_x64
|
|
||||||
!undef arch
|
|
||||||
!define arch "x64"
|
|
||||||
!endif
|
|
||||||
|
|
||||||
!if "@ARCH@" == "x86_64-w64-mingw32.shared"
|
|
||||||
!define arch_x64
|
|
||||||
!undef arch
|
|
||||||
!define arch "x64"
|
|
||||||
!endif
|
|
||||||
|
|
||||||
!if "@MINGW@" == "1"
|
!if "@MINGW@" == "1"
|
||||||
!define mingw
|
!define mingw
|
||||||
!undef compiler
|
|
||||||
!define compiler "mingw"
|
!define compiler "mingw"
|
||||||
!endif
|
!endif
|
||||||
|
|
||||||
!if "@MSVC@" == "1"
|
!if "@MSVC@" == "1"
|
||||||
!define msvc
|
!define msvc
|
||||||
!undef compiler
|
|
||||||
!define compiler "msvc"
|
!define compiler "msvc"
|
||||||
!endif
|
!endif
|
||||||
|
|
||||||
|
|
||||||
|
!if "@ARCH@" == "x86"
|
||||||
|
!define arch_x86
|
||||||
|
!else if "@ARCH@" == "i686"
|
||||||
|
!define arch_x86
|
||||||
|
!else if "@ARCH@" == "i686-w64-mingw32.shared"
|
||||||
|
!define arch_x86
|
||||||
|
!else if "@ARCH@" == "x64"
|
||||||
|
!define arch_x64
|
||||||
|
!else if "@ARCH@" == "x86_64"
|
||||||
|
!define arch_x64
|
||||||
|
!else if "@ARCH@" == "x86_64-w64-mingw32.shared"
|
||||||
|
!define arch_x64
|
||||||
|
!endif
|
||||||
|
|
||||||
|
!ifdef arch_x86
|
||||||
|
!define arch "x86"
|
||||||
|
!endif
|
||||||
|
|
||||||
|
!ifdef arch_x64
|
||||||
|
!define arch "x64"
|
||||||
|
!endif
|
||||||
|
|
||||||
|
|
||||||
!if "@CMAKE_BUILD_TYPE@" == "Release"
|
!if "@CMAKE_BUILD_TYPE@" == "Release"
|
||||||
!define release
|
!define release
|
||||||
!endif
|
!else if "@CMAKE_BUILD_TYPE@" == "RelWithDebInfo"
|
||||||
|
|
||||||
!if "@CMAKE_BUILD_TYPE@" == "RelWithDebInfo"
|
|
||||||
!define release
|
!define release
|
||||||
|
!else if "@CMAKE_BUILD_TYPE@" == "Debug"
|
||||||
|
!define debug
|
||||||
!endif
|
!endif
|
||||||
|
|
||||||
!if "@CMAKE_BUILD_TYPE@" == "Debug"
|
!ifdef release
|
||||||
!define debug
|
!define build_type ""
|
||||||
!undef build_type
|
!endif
|
||||||
|
|
||||||
|
!ifdef debug
|
||||||
!define build_type "-Debug"
|
!define build_type "-Debug"
|
||||||
!endif
|
!endif
|
||||||
|
|
||||||
|
|
||||||
|
!ifndef compiler
|
||||||
|
!error "Missing compiler."
|
||||||
|
!endif
|
||||||
|
|
||||||
|
!ifndef build_type
|
||||||
|
!error "Missing build type."
|
||||||
|
!endif
|
||||||
|
|
||||||
|
!ifndef arch
|
||||||
|
!error "Missing arch."
|
||||||
|
!endif
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
!ifdef debug
|
!ifdef debug
|
||||||
!define PRODUCT_NAME "Strawberry Music Player Debug"
|
!define PRODUCT_NAME "Strawberry Music Player Debug"
|
||||||
!define PRODUCT_NAME_SHORT "Strawberry"
|
!define PRODUCT_NAME_SHORT "Strawberry"
|
||||||
@@ -241,11 +252,11 @@ Section "Strawberry" Strawberry
|
|||||||
File "libssl-3-x64.dll"
|
File "libssl-3-x64.dll"
|
||||||
!endif
|
!endif
|
||||||
|
|
||||||
File "avcodec-59.dll"
|
File "avcodec-60.dll"
|
||||||
File "avfilter-8.dll"
|
File "avfilter-9.dll"
|
||||||
File "avformat-59.dll"
|
File "avformat-60.dll"
|
||||||
File "avutil-57.dll"
|
File "avutil-58.dll"
|
||||||
File "libFLAC-8.dll"
|
File "libFLAC-12.dll"
|
||||||
File "libbrotlicommon.dll"
|
File "libbrotlicommon.dll"
|
||||||
File "libbrotlidec.dll"
|
File "libbrotlidec.dll"
|
||||||
File "libbrotlienc.dll"
|
File "libbrotlienc.dll"
|
||||||
@@ -301,7 +312,6 @@ Section "Strawberry" Strawberry
|
|||||||
File "libopus-0.dll"
|
File "libopus-0.dll"
|
||||||
File "liborc-0.4-0.dll"
|
File "liborc-0.4-0.dll"
|
||||||
File "libpng16-16.dll"
|
File "libpng16-16.dll"
|
||||||
File "libprotobuf-32.dll"
|
|
||||||
File "libpsl-5.dll"
|
File "libpsl-5.dll"
|
||||||
File "libqtsparkle-qt6.dll"
|
File "libqtsparkle-qt6.dll"
|
||||||
File "libsoup-3.0-0.dll"
|
File "libsoup-3.0-0.dll"
|
||||||
@@ -312,7 +322,7 @@ Section "Strawberry" Strawberry
|
|||||||
File "libtag.dll"
|
File "libtag.dll"
|
||||||
File "libtasn1-6.dll"
|
File "libtasn1-6.dll"
|
||||||
File "libtwolame-0.dll"
|
File "libtwolame-0.dll"
|
||||||
File "libunistring-2.dll"
|
File "libunistring-5.dll"
|
||||||
File "libvorbis-0.dll"
|
File "libvorbis-0.dll"
|
||||||
File "libvorbisenc-2.dll"
|
File "libvorbisenc-2.dll"
|
||||||
File "libvorbisfile-3.dll"
|
File "libvorbisfile-3.dll"
|
||||||
@@ -320,11 +330,51 @@ Section "Strawberry" Strawberry
|
|||||||
File "libwinpthread-1.dll"
|
File "libwinpthread-1.dll"
|
||||||
File "libxml2-2.dll"
|
File "libxml2-2.dll"
|
||||||
File "libzstd.dll"
|
File "libzstd.dll"
|
||||||
File "postproc-56.dll"
|
File "postproc-57.dll"
|
||||||
File "swresample-4.dll"
|
File "swresample-4.dll"
|
||||||
File "swscale-6.dll"
|
File "swscale-7.dll"
|
||||||
File "zlib1.dll"
|
File "zlib1.dll"
|
||||||
|
|
||||||
|
File "libabsl_base.dll"
|
||||||
|
File "libabsl_city.dll"
|
||||||
|
File "libabsl_cord.dll"
|
||||||
|
File "libabsl_cord_internal.dll"
|
||||||
|
File "libabsl_cordz_handle.dll"
|
||||||
|
File "libabsl_cordz_info.dll"
|
||||||
|
File "libabsl_crc32c.dll"
|
||||||
|
File "libabsl_crc_cord_state.dll"
|
||||||
|
File "libabsl_crc_internal.dll"
|
||||||
|
File "libabsl_die_if_null.dll"
|
||||||
|
File "libabsl_examine_stack.dll"
|
||||||
|
File "libabsl_hash.dll"
|
||||||
|
File "libabsl_int128.dll"
|
||||||
|
File "libabsl_log_globals.dll"
|
||||||
|
File "libabsl_log_internal_check_op.dll"
|
||||||
|
File "libabsl_log_internal_format.dll"
|
||||||
|
File "libabsl_log_internal_globals.dll"
|
||||||
|
File "libabsl_log_internal_log_sink_set.dll"
|
||||||
|
File "libabsl_log_internal_message.dll"
|
||||||
|
File "libabsl_log_internal_nullguard.dll"
|
||||||
|
File "libabsl_log_internal_proto.dll"
|
||||||
|
File "libabsl_log_sink.dll"
|
||||||
|
File "libabsl_low_level_hash.dll"
|
||||||
|
File "libabsl_malloc_internal.dll"
|
||||||
|
File "libabsl_raw_hash_set.dll"
|
||||||
|
File "libabsl_raw_logging_internal.dll"
|
||||||
|
File "libabsl_spinlock_wait.dll"
|
||||||
|
File "libabsl_stacktrace.dll"
|
||||||
|
File "libabsl_status.dll"
|
||||||
|
File "libabsl_statusor.dll"
|
||||||
|
File "libabsl_strerror.dll"
|
||||||
|
File "libabsl_str_format_internal.dll"
|
||||||
|
File "libabsl_strings.dll"
|
||||||
|
File "libabsl_strings_internal.dll"
|
||||||
|
File "libabsl_symbolize.dll"
|
||||||
|
File "libabsl_synchronization.dll"
|
||||||
|
File "libabsl_throw_delegate.dll"
|
||||||
|
File "libabsl_time.dll"
|
||||||
|
File "libabsl_time_zone.dll"
|
||||||
|
|
||||||
!ifdef debug
|
!ifdef debug
|
||||||
File "gdb.exe"
|
File "gdb.exe"
|
||||||
File "libexpat-1.dll"
|
File "libexpat-1.dll"
|
||||||
@@ -334,6 +384,7 @@ Section "Strawberry" Strawberry
|
|||||||
File "libpcre2-16d.dll"
|
File "libpcre2-16d.dll"
|
||||||
File "libreadline8.dll"
|
File "libreadline8.dll"
|
||||||
File "libtermcap.dll"
|
File "libtermcap.dll"
|
||||||
|
File "libabsl_graphcycles_internal.dll"
|
||||||
!else
|
!else
|
||||||
File "libpcre2-8.dll"
|
File "libpcre2-8.dll"
|
||||||
File "libpcre2-16.dll"
|
File "libpcre2-16.dll"
|
||||||
@@ -358,7 +409,6 @@ Section "Strawberry" Strawberry
|
|||||||
File "avcodec-58.dll"
|
File "avcodec-58.dll"
|
||||||
File "avfilter-7.dll"
|
File "avfilter-7.dll"
|
||||||
File "avformat-58.dll"
|
File "avformat-58.dll"
|
||||||
File "avresample-4.dll"
|
|
||||||
File "avutil-56.dll"
|
File "avutil-56.dll"
|
||||||
File "brotlicommon.dll"
|
File "brotlicommon.dll"
|
||||||
File "brotlidec.dll"
|
File "brotlidec.dll"
|
||||||
@@ -391,9 +441,9 @@ Section "Strawberry" Strawberry
|
|||||||
File "gstvideo-1.0-0.dll"
|
File "gstvideo-1.0-0.dll"
|
||||||
File "harfbuzz.dll"
|
File "harfbuzz.dll"
|
||||||
File "intl-8.dll"
|
File "intl-8.dll"
|
||||||
|
File "jpeg62.dll"
|
||||||
File "libbs2b.dll"
|
File "libbs2b.dll"
|
||||||
File "libfaac_dll.dll"
|
File "libfaac_dll.dll"
|
||||||
File "libiconv.dll"
|
|
||||||
File "liblzma.dll"
|
File "liblzma.dll"
|
||||||
File "libmp3lame.dll"
|
File "libmp3lame.dll"
|
||||||
File "libopenmpt.dll"
|
File "libopenmpt.dll"
|
||||||
@@ -415,11 +465,12 @@ Section "Strawberry" Strawberry
|
|||||||
File "vorbis.dll"
|
File "vorbis.dll"
|
||||||
File "vorbisfile.dll"
|
File "vorbisfile.dll"
|
||||||
File "wavpackdll.dll"
|
File "wavpackdll.dll"
|
||||||
|
File "abseil_dll.dll"
|
||||||
|
|
||||||
!ifdef release
|
!ifdef release
|
||||||
File "freetype.dll"
|
File "freetype.dll"
|
||||||
|
File "libiconv.dll"
|
||||||
File "libpng16.dll"
|
File "libpng16.dll"
|
||||||
File "libprotobuf.dll"
|
|
||||||
File "libxml2.dll"
|
File "libxml2.dll"
|
||||||
File "pcre2-8.dll"
|
File "pcre2-8.dll"
|
||||||
File "pcre2-16.dll"
|
File "pcre2-16.dll"
|
||||||
@@ -428,8 +479,8 @@ Section "Strawberry" Strawberry
|
|||||||
!endif
|
!endif
|
||||||
!ifdef debug
|
!ifdef debug
|
||||||
File "freetyped.dll"
|
File "freetyped.dll"
|
||||||
|
File "libiconvd.dll"
|
||||||
File "libpng16d.dll"
|
File "libpng16d.dll"
|
||||||
File "libprotobufd.dll"
|
|
||||||
File "libxml2d.dll"
|
File "libxml2d.dll"
|
||||||
File "pcre2-8d.dll"
|
File "pcre2-8d.dll"
|
||||||
File "pcre2-16d.dll"
|
File "pcre2-16d.dll"
|
||||||
@@ -441,11 +492,16 @@ Section "Strawberry" Strawberry
|
|||||||
|
|
||||||
; Common files
|
; Common files
|
||||||
|
|
||||||
File "icudt71.dll"
|
File "icudt72.dll"
|
||||||
File "libfftw3-3.dll"
|
File "libfftw3-3.dll"
|
||||||
|
!ifdef debug
|
||||||
|
File "libprotobufd.dll"
|
||||||
|
!else
|
||||||
|
File "libprotobuf.dll"
|
||||||
|
!endif
|
||||||
!ifdef msvc && debug
|
!ifdef msvc && debug
|
||||||
File "icuin71d.dll"
|
File "icuin72d.dll"
|
||||||
File "icuuc71d.dll"
|
File "icuuc72d.dll"
|
||||||
File "Qt6Concurrentd.dll"
|
File "Qt6Concurrentd.dll"
|
||||||
File "Qt6Cored.dll"
|
File "Qt6Cored.dll"
|
||||||
File "Qt6Guid.dll"
|
File "Qt6Guid.dll"
|
||||||
@@ -453,8 +509,8 @@ Section "Strawberry" Strawberry
|
|||||||
File "Qt6Sqld.dll"
|
File "Qt6Sqld.dll"
|
||||||
File "Qt6Widgetsd.dll"
|
File "Qt6Widgetsd.dll"
|
||||||
!else
|
!else
|
||||||
File "icuin71.dll"
|
File "icuin72.dll"
|
||||||
File "icuuc71.dll"
|
File "icuuc72.dll"
|
||||||
File "Qt6Concurrent.dll"
|
File "Qt6Concurrent.dll"
|
||||||
File "Qt6Core.dll"
|
File "Qt6Core.dll"
|
||||||
File "Qt6Gui.dll"
|
File "Qt6Gui.dll"
|
||||||
@@ -743,11 +799,11 @@ Section "Uninstall"
|
|||||||
Delete "$INSTDIR\libssl-3-x64.dll"
|
Delete "$INSTDIR\libssl-3-x64.dll"
|
||||||
!endif
|
!endif
|
||||||
|
|
||||||
Delete "$INSTDIR\avcodec-59.dll"
|
Delete "$INSTDIR\avcodec-60.dll"
|
||||||
Delete "$INSTDIR\avfilter-8.dll"
|
Delete "$INSTDIR\avfilter-9.dll"
|
||||||
Delete "$INSTDIR\avformat-59.dll"
|
Delete "$INSTDIR\avformat-60.dll"
|
||||||
Delete "$INSTDIR\avutil-57.dll"
|
Delete "$INSTDIR\avutil-58.dll"
|
||||||
Delete "$INSTDIR\libFLAC-8.dll"
|
Delete "$INSTDIR\libFLAC-12.dll"
|
||||||
Delete "$INSTDIR\libbrotlicommon.dll"
|
Delete "$INSTDIR\libbrotlicommon.dll"
|
||||||
Delete "$INSTDIR\libbrotlidec.dll"
|
Delete "$INSTDIR\libbrotlidec.dll"
|
||||||
Delete "$INSTDIR\libbrotlienc.dll"
|
Delete "$INSTDIR\libbrotlienc.dll"
|
||||||
@@ -803,7 +859,6 @@ Section "Uninstall"
|
|||||||
Delete "$INSTDIR\libopus-0.dll"
|
Delete "$INSTDIR\libopus-0.dll"
|
||||||
Delete "$INSTDIR\liborc-0.4-0.dll"
|
Delete "$INSTDIR\liborc-0.4-0.dll"
|
||||||
Delete "$INSTDIR\libpng16-16.dll"
|
Delete "$INSTDIR\libpng16-16.dll"
|
||||||
Delete "$INSTDIR\libprotobuf-32.dll"
|
|
||||||
Delete "$INSTDIR\libpsl-5.dll"
|
Delete "$INSTDIR\libpsl-5.dll"
|
||||||
Delete "$INSTDIR\libqtsparkle-qt6.dll"
|
Delete "$INSTDIR\libqtsparkle-qt6.dll"
|
||||||
Delete "$INSTDIR\libsoup-3.0-0.dll"
|
Delete "$INSTDIR\libsoup-3.0-0.dll"
|
||||||
@@ -814,7 +869,7 @@ Section "Uninstall"
|
|||||||
Delete "$INSTDIR\libtag.dll"
|
Delete "$INSTDIR\libtag.dll"
|
||||||
Delete "$INSTDIR\libtasn1-6.dll"
|
Delete "$INSTDIR\libtasn1-6.dll"
|
||||||
Delete "$INSTDIR\libtwolame-0.dll"
|
Delete "$INSTDIR\libtwolame-0.dll"
|
||||||
Delete "$INSTDIR\libunistring-2.dll"
|
Delete "$INSTDIR\libunistring-5.dll"
|
||||||
Delete "$INSTDIR\libvorbis-0.dll"
|
Delete "$INSTDIR\libvorbis-0.dll"
|
||||||
Delete "$INSTDIR\libvorbisenc-2.dll"
|
Delete "$INSTDIR\libvorbisenc-2.dll"
|
||||||
Delete "$INSTDIR\libvorbisfile-3.dll"
|
Delete "$INSTDIR\libvorbisfile-3.dll"
|
||||||
@@ -822,11 +877,51 @@ Section "Uninstall"
|
|||||||
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\libzstd.dll"
|
||||||
Delete "$INSTDIR\postproc-56.dll"
|
Delete "$INSTDIR\postproc-57.dll"
|
||||||
Delete "$INSTDIR\swresample-4.dll"
|
Delete "$INSTDIR\swresample-4.dll"
|
||||||
Delete "$INSTDIR\swscale-6.dll"
|
Delete "$INSTDIR\swscale-7.dll"
|
||||||
Delete "$INSTDIR\zlib1.dll"
|
Delete "$INSTDIR\zlib1.dll"
|
||||||
|
|
||||||
|
Delete "$INSTDIR\libabsl_base.dll"
|
||||||
|
Delete "$INSTDIR\libabsl_city.dll"
|
||||||
|
Delete "$INSTDIR\libabsl_cord.dll"
|
||||||
|
Delete "$INSTDIR\libabsl_cord_internal.dll"
|
||||||
|
Delete "$INSTDIR\libabsl_cordz_handle.dll"
|
||||||
|
Delete "$INSTDIR\libabsl_cordz_info.dll"
|
||||||
|
Delete "$INSTDIR\libabsl_crc32c.dll"
|
||||||
|
Delete "$INSTDIR\libabsl_crc_cord_state.dll"
|
||||||
|
Delete "$INSTDIR\libabsl_crc_internal.dll"
|
||||||
|
Delete "$INSTDIR\libabsl_die_if_null.dll"
|
||||||
|
Delete "$INSTDIR\libabsl_examine_stack.dll"
|
||||||
|
Delete "$INSTDIR\libabsl_hash.dll"
|
||||||
|
Delete "$INSTDIR\libabsl_int128.dll"
|
||||||
|
Delete "$INSTDIR\libabsl_log_globals.dll"
|
||||||
|
Delete "$INSTDIR\libabsl_log_internal_check_op.dll"
|
||||||
|
Delete "$INSTDIR\libabsl_log_internal_format.dll"
|
||||||
|
Delete "$INSTDIR\libabsl_log_internal_globals.dll"
|
||||||
|
Delete "$INSTDIR\libabsl_log_internal_log_sink_set.dll"
|
||||||
|
Delete "$INSTDIR\libabsl_log_internal_message.dll"
|
||||||
|
Delete "$INSTDIR\libabsl_log_internal_nullguard.dll"
|
||||||
|
Delete "$INSTDIR\libabsl_log_internal_proto.dll"
|
||||||
|
Delete "$INSTDIR\libabsl_log_sink.dll"
|
||||||
|
Delete "$INSTDIR\libabsl_low_level_hash.dll"
|
||||||
|
Delete "$INSTDIR\libabsl_malloc_internal.dll"
|
||||||
|
Delete "$INSTDIR\libabsl_raw_hash_set.dll"
|
||||||
|
Delete "$INSTDIR\libabsl_raw_logging_internal.dll"
|
||||||
|
Delete "$INSTDIR\libabsl_spinlock_wait.dll"
|
||||||
|
Delete "$INSTDIR\libabsl_stacktrace.dll"
|
||||||
|
Delete "$INSTDIR\libabsl_status.dll"
|
||||||
|
Delete "$INSTDIR\libabsl_statusor.dll"
|
||||||
|
Delete "$INSTDIR\libabsl_strerror.dll"
|
||||||
|
Delete "$INSTDIR\libabsl_str_format_internal.dll"
|
||||||
|
Delete "$INSTDIR\libabsl_strings.dll"
|
||||||
|
Delete "$INSTDIR\libabsl_strings_internal.dll"
|
||||||
|
Delete "$INSTDIR\libabsl_symbolize.dll"
|
||||||
|
Delete "$INSTDIR\libabsl_synchronization.dll"
|
||||||
|
Delete "$INSTDIR\libabsl_throw_delegate.dll"
|
||||||
|
Delete "$INSTDIR\libabsl_time.dll"
|
||||||
|
Delete "$INSTDIR\libabsl_time_zone.dll"
|
||||||
|
|
||||||
!ifdef debug
|
!ifdef debug
|
||||||
Delete "$INSTDIR\gdb.exe"
|
Delete "$INSTDIR\gdb.exe"
|
||||||
Delete "$INSTDIR\libexpat-1.dll"
|
Delete "$INSTDIR\libexpat-1.dll"
|
||||||
@@ -836,6 +931,7 @@ Section "Uninstall"
|
|||||||
Delete "$INSTDIR\libpcre2-16d.dll"
|
Delete "$INSTDIR\libpcre2-16d.dll"
|
||||||
Delete "$INSTDIR\libreadline8.dll"
|
Delete "$INSTDIR\libreadline8.dll"
|
||||||
Delete "$INSTDIR\libtermcap.dll"
|
Delete "$INSTDIR\libtermcap.dll"
|
||||||
|
Delete "$INSTDIR\libabsl_graphcycles_internal.dll"
|
||||||
!else
|
!else
|
||||||
Delete "$INSTDIR\libpcre2-8.dll"
|
Delete "$INSTDIR\libpcre2-8.dll"
|
||||||
Delete "$INSTDIR\libpcre2-16.dll"
|
Delete "$INSTDIR\libpcre2-16.dll"
|
||||||
@@ -860,7 +956,6 @@ Section "Uninstall"
|
|||||||
Delete "$INSTDIR\avcodec-58.dll"
|
Delete "$INSTDIR\avcodec-58.dll"
|
||||||
Delete "$INSTDIR\avfilter-7.dll"
|
Delete "$INSTDIR\avfilter-7.dll"
|
||||||
Delete "$INSTDIR\avformat-58.dll"
|
Delete "$INSTDIR\avformat-58.dll"
|
||||||
Delete "$INSTDIR\avresample-4.dll"
|
|
||||||
Delete "$INSTDIR\avutil-56.dll"
|
Delete "$INSTDIR\avutil-56.dll"
|
||||||
Delete "$INSTDIR\brotlicommon.dll"
|
Delete "$INSTDIR\brotlicommon.dll"
|
||||||
Delete "$INSTDIR\brotlidec.dll"
|
Delete "$INSTDIR\brotlidec.dll"
|
||||||
@@ -893,9 +988,9 @@ Section "Uninstall"
|
|||||||
Delete "$INSTDIR\gstvideo-1.0-0.dll"
|
Delete "$INSTDIR\gstvideo-1.0-0.dll"
|
||||||
Delete "$INSTDIR\harfbuzz.dll"
|
Delete "$INSTDIR\harfbuzz.dll"
|
||||||
Delete "$INSTDIR\intl-8.dll"
|
Delete "$INSTDIR\intl-8.dll"
|
||||||
|
Delete "$INSTDIR\jpeg62.dll"
|
||||||
Delete "$INSTDIR\libbs2b.dll"
|
Delete "$INSTDIR\libbs2b.dll"
|
||||||
Delete "$INSTDIR\libfaac_dll.dll"
|
Delete "$INSTDIR\libfaac_dll.dll"
|
||||||
Delete "$INSTDIR\libiconv.dll"
|
|
||||||
Delete "$INSTDIR\liblzma.dll"
|
Delete "$INSTDIR\liblzma.dll"
|
||||||
Delete "$INSTDIR\libmp3lame.dll"
|
Delete "$INSTDIR\libmp3lame.dll"
|
||||||
Delete "$INSTDIR\libopenmpt.dll"
|
Delete "$INSTDIR\libopenmpt.dll"
|
||||||
@@ -917,11 +1012,12 @@ Section "Uninstall"
|
|||||||
Delete "$INSTDIR\vorbis.dll"
|
Delete "$INSTDIR\vorbis.dll"
|
||||||
Delete "$INSTDIR\vorbisfile.dll"
|
Delete "$INSTDIR\vorbisfile.dll"
|
||||||
Delete "$INSTDIR\wavpackdll.dll"
|
Delete "$INSTDIR\wavpackdll.dll"
|
||||||
|
Delete "$INSTDIR\abseil_dll.dll"
|
||||||
|
|
||||||
!ifdef release
|
!ifdef release
|
||||||
Delete "$INSTDIR\freetype.dll"
|
Delete "$INSTDIR\freetype.dll"
|
||||||
|
Delete "$INSTDIR\libiconv.dll"
|
||||||
Delete "$INSTDIR\libpng16.dll"
|
Delete "$INSTDIR\libpng16.dll"
|
||||||
Delete "$INSTDIR\libprotobuf.dll"
|
|
||||||
Delete "$INSTDIR\libxml2.dll"
|
Delete "$INSTDIR\libxml2.dll"
|
||||||
Delete "$INSTDIR\pcre2-8.dll"
|
Delete "$INSTDIR\pcre2-8.dll"
|
||||||
Delete "$INSTDIR\pcre2-16.dll"
|
Delete "$INSTDIR\pcre2-16.dll"
|
||||||
@@ -930,8 +1026,8 @@ Section "Uninstall"
|
|||||||
!endif
|
!endif
|
||||||
!ifdef debug
|
!ifdef debug
|
||||||
Delete "$INSTDIR\freetyped.dll"
|
Delete "$INSTDIR\freetyped.dll"
|
||||||
|
Delete "$INSTDIR\libiconvd.dll"
|
||||||
Delete "$INSTDIR\libpng16d.dll"
|
Delete "$INSTDIR\libpng16d.dll"
|
||||||
Delete "$INSTDIR\libprotobufd.dll"
|
|
||||||
Delete "$INSTDIR\libxml2d.dll"
|
Delete "$INSTDIR\libxml2d.dll"
|
||||||
Delete "$INSTDIR\pcre2-8d.dll"
|
Delete "$INSTDIR\pcre2-8d.dll"
|
||||||
Delete "$INSTDIR\pcre2-16d.dll"
|
Delete "$INSTDIR\pcre2-16d.dll"
|
||||||
@@ -943,11 +1039,16 @@ Section "Uninstall"
|
|||||||
|
|
||||||
; Common files
|
; Common files
|
||||||
|
|
||||||
Delete "$INSTDIR\icudt71.dll"
|
Delete "$INSTDIR\icudt72.dll"
|
||||||
Delete "$INSTDIR\libfftw3-3.dll"
|
Delete "$INSTDIR\libfftw3-3.dll"
|
||||||
|
!ifdef debug
|
||||||
|
Delete "$INSTDIR\libprotobufd.dll"
|
||||||
|
!else
|
||||||
|
Delete "$INSTDIR\libprotobuf.dll"
|
||||||
|
!endif
|
||||||
!ifdef msvc && debug
|
!ifdef msvc && debug
|
||||||
Delete "$INSTDIR\icuin71d.dll"
|
Delete "$INSTDIR\icuin72d.dll"
|
||||||
Delete "$INSTDIR\icuuc71d.dll"
|
Delete "$INSTDIR\icuuc72d.dll"
|
||||||
Delete "$INSTDIR\Qt6Concurrentd.dll"
|
Delete "$INSTDIR\Qt6Concurrentd.dll"
|
||||||
Delete "$INSTDIR\Qt6Cored.dll"
|
Delete "$INSTDIR\Qt6Cored.dll"
|
||||||
Delete "$INSTDIR\Qt6Guid.dll"
|
Delete "$INSTDIR\Qt6Guid.dll"
|
||||||
@@ -955,8 +1056,8 @@ Section "Uninstall"
|
|||||||
Delete "$INSTDIR\Qt6Sqld.dll"
|
Delete "$INSTDIR\Qt6Sqld.dll"
|
||||||
Delete "$INSTDIR\Qt6Widgetsd.dll"
|
Delete "$INSTDIR\Qt6Widgetsd.dll"
|
||||||
!else
|
!else
|
||||||
Delete "$INSTDIR\icuin71.dll"
|
Delete "$INSTDIR\icuin72.dll"
|
||||||
Delete "$INSTDIR\icuuc71.dll"
|
Delete "$INSTDIR\icuuc72.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"
|
||||||
|
|||||||
@@ -82,9 +82,9 @@ static void gst_fastspectrum_class_init(GstFastSpectrumClass *klass) {
|
|||||||
|
|
||||||
filter_class->setup = GST_DEBUG_FUNCPTR(gst_fastspectrum_setup);
|
filter_class->setup = GST_DEBUG_FUNCPTR(gst_fastspectrum_setup);
|
||||||
|
|
||||||
g_object_class_install_property(gobject_class, PROP_INTERVAL, g_param_spec_uint64("interval", "Interval", "Interval of time between message posts (in nanoseconds)", 1, G_MAXUINT64, DEFAULT_INTERVAL, GParamFlags(G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)));
|
g_object_class_install_property(gobject_class, PROP_INTERVAL, g_param_spec_uint64("interval", "Interval", "Interval of time between message posts (in nanoseconds)", 1, G_MAXUINT64, DEFAULT_INTERVAL, static_cast<GParamFlags>(G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)));
|
||||||
|
|
||||||
g_object_class_install_property(gobject_class, PROP_BANDS, g_param_spec_uint("bands", "Bands", "Number of frequency bands", 0, G_MAXUINT, DEFAULT_BANDS, GParamFlags(G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)));
|
g_object_class_install_property(gobject_class, PROP_BANDS, g_param_spec_uint("bands", "Bands", "Number of frequency bands", 0, G_MAXUINT, DEFAULT_BANDS, static_cast<GParamFlags>(G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)));
|
||||||
|
|
||||||
GST_DEBUG_CATEGORY_INIT(gst_fastspectrum_debug, "spectrum", 0, "audio spectrum analyser element");
|
GST_DEBUG_CATEGORY_INIT(gst_fastspectrum_debug, "spectrum", 0, "audio spectrum analyser element");
|
||||||
|
|
||||||
|
|||||||
@@ -47,7 +47,7 @@ class QMutex;
|
|||||||
|
|
||||||
typedef void (*GstFastSpectrumInputData)(const guint8 *in, double *out, guint len, double max_value, guint op, guint nfft);
|
typedef void (*GstFastSpectrumInputData)(const guint8 *in, double *out, guint len, double max_value, guint op, guint nfft);
|
||||||
|
|
||||||
typedef std::function<void(double *magnitudes, int size)> OutputCallback;
|
using OutputCallback = std::function<void(double *magnitudes, int size)>;
|
||||||
|
|
||||||
struct GstFastSpectrum {
|
struct GstFastSpectrum {
|
||||||
GstAudioFilter parent;
|
GstAudioFilter parent;
|
||||||
|
|||||||
@@ -126,7 +126,8 @@ class LoggedDebug : public DebugBase<LoggedDebug> {
|
|||||||
static void MessageHandler(QtMsgType type, const QMessageLogContext&, const QString &message) {
|
static void MessageHandler(QtMsgType type, const QMessageLogContext&, const QString &message) {
|
||||||
|
|
||||||
if (message.startsWith(kMessageHandlerMagic)) {
|
if (message.startsWith(kMessageHandlerMagic)) {
|
||||||
fprintf(type == QtCriticalMsg || type == QtFatalMsg ? stderr : stdout, "%s\n", message.toUtf8().data() + kMessageHandlerMagicLength);
|
QByteArray message_data = message.toUtf8();
|
||||||
|
fprintf(type == QtCriticalMsg || type == QtFatalMsg ? stderr : stdout, "%s\n", message_data.constData() + kMessageHandlerMagicLength);
|
||||||
fflush(type == QtCriticalMsg || type == QtFatalMsg ? stderr : stdout);
|
fflush(type == QtCriticalMsg || type == QtFatalMsg ? stderr : stdout);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -82,8 +82,8 @@ class AbstractMessageHandler : public _MessageHandlerBase {
|
|||||||
AbstractMessageHandler(QIODevice *device, QObject *parent);
|
AbstractMessageHandler(QIODevice *device, QObject *parent);
|
||||||
~AbstractMessageHandler() override { AbstractMessageHandler::AbortAll(); }
|
~AbstractMessageHandler() override { AbstractMessageHandler::AbortAll(); }
|
||||||
|
|
||||||
typedef MT MessageType;
|
using MessageType = MT;
|
||||||
typedef MessageReply<MT> ReplyType;
|
using ReplyType = MessageReply<MT>;
|
||||||
|
|
||||||
// Serialises the message and writes it to the socket.
|
// Serialises the message and writes it to the socket.
|
||||||
// This version MUST be called from the thread in which the AbstractMessageHandler was created.
|
// This version MUST be called from the thread in which the AbstractMessageHandler was created.
|
||||||
|
|||||||
@@ -76,8 +76,8 @@ class WorkerPool : public _WorkerPoolBase {
|
|||||||
explicit WorkerPool(QObject *parent = nullptr);
|
explicit WorkerPool(QObject *parent = nullptr);
|
||||||
~WorkerPool() override;
|
~WorkerPool() override;
|
||||||
|
|
||||||
typedef typename HandlerType::MessageType MessageType;
|
using MessageType = typename HandlerType::MessageType;
|
||||||
typedef typename HandlerType::ReplyType ReplyType;
|
using ReplyType = typename HandlerType::ReplyType;
|
||||||
|
|
||||||
// Sets the name of the worker executable. This is looked for first in the current directory, and then in $PATH.
|
// Sets the name of the worker executable. This is looked for first in the current directory, and then in $PATH.
|
||||||
// You must call this before calling Start().
|
// You must call this before calling Start().
|
||||||
@@ -165,10 +165,10 @@ class WorkerPool : public _WorkerPoolBase {
|
|||||||
template<typename HandlerType>
|
template<typename HandlerType>
|
||||||
WorkerPool<HandlerType>::WorkerPool(QObject *parent)
|
WorkerPool<HandlerType>::WorkerPool(QObject *parent)
|
||||||
: _WorkerPoolBase(parent),
|
: _WorkerPoolBase(parent),
|
||||||
|
worker_count_(1),
|
||||||
next_worker_(0),
|
next_worker_(0),
|
||||||
next_id_(0) {
|
next_id_(0) {
|
||||||
|
|
||||||
worker_count_ = qBound(1, QThread::idealThreadCount() / 2, 4);
|
|
||||||
local_server_name_ = qApp->applicationName().toLower();
|
local_server_name_ = qApp->applicationName().toLower();
|
||||||
|
|
||||||
if (local_server_name_.isEmpty()) {
|
if (local_server_name_.isEmpty()) {
|
||||||
|
|||||||
@@ -43,12 +43,21 @@ target_include_directories(libstrawberry-tagreader PRIVATE
|
|||||||
|
|
||||||
target_link_libraries(libstrawberry-tagreader PRIVATE
|
target_link_libraries(libstrawberry-tagreader PRIVATE
|
||||||
${GLIB_LIBRARIES}
|
${GLIB_LIBRARIES}
|
||||||
${PROTOBUF_LIBRARY}
|
${Protobuf_LIBRARIES}
|
||||||
${QtCore_LIBRARIES}
|
${QtCore_LIBRARIES}
|
||||||
${QtNetwork_LIBRARIES}
|
${QtNetwork_LIBRARIES}
|
||||||
|
${QtGui_LIBRARIES}
|
||||||
libstrawberry-common
|
libstrawberry-common
|
||||||
)
|
)
|
||||||
|
|
||||||
|
if(WIN32 AND Protobuf_VERSION VERSION_GREATER_EQUAL 4.22.0)
|
||||||
|
if (MSVC)
|
||||||
|
target_link_libraries(libstrawberry-tagreader PRIVATE abseil_dll)
|
||||||
|
else()
|
||||||
|
target_link_libraries(libstrawberry-tagreader PRIVATE absl_log_internal_message absl_log_internal_check_op)
|
||||||
|
endif()
|
||||||
|
endif()
|
||||||
|
|
||||||
if(USE_TAGLIB AND TAGLIB_FOUND)
|
if(USE_TAGLIB AND TAGLIB_FOUND)
|
||||||
target_include_directories(libstrawberry-tagreader SYSTEM PRIVATE ${TAGLIB_INCLUDE_DIRS})
|
target_include_directories(libstrawberry-tagreader SYSTEM PRIVATE ${TAGLIB_INCLUDE_DIRS})
|
||||||
target_link_libraries(libstrawberry-tagreader PRIVATE ${TAGLIB_LIBRARIES})
|
target_link_libraries(libstrawberry-tagreader PRIVATE ${TAGLIB_LIBRARIES})
|
||||||
|
|||||||
@@ -19,6 +19,15 @@
|
|||||||
|
|
||||||
#include <string>
|
#include <string>
|
||||||
|
|
||||||
|
#include <QByteArray>
|
||||||
|
#include <QString>
|
||||||
|
#include <QIODevice>
|
||||||
|
#include <QFile>
|
||||||
|
#include <QBuffer>
|
||||||
|
#include <QImage>
|
||||||
|
#include <QMimeDatabase>
|
||||||
|
|
||||||
|
#include "core/logging.h"
|
||||||
#include "tagreaderbase.h"
|
#include "tagreaderbase.h"
|
||||||
|
|
||||||
const std::string TagReaderBase::kEmbeddedCover = "(embedded)";
|
const std::string TagReaderBase::kEmbeddedCover = "(embedded)";
|
||||||
@@ -26,12 +35,6 @@ const std::string TagReaderBase::kEmbeddedCover = "(embedded)";
|
|||||||
TagReaderBase::TagReaderBase() = default;
|
TagReaderBase::TagReaderBase() = default;
|
||||||
TagReaderBase::~TagReaderBase() = default;
|
TagReaderBase::~TagReaderBase() = default;
|
||||||
|
|
||||||
void TagReaderBase::Decode(const QString &tag, std::string *output) {
|
|
||||||
|
|
||||||
output->assign(DataCommaSizeFromQString(tag));
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
float TagReaderBase::ConvertPOPMRating(const int POPM_rating) {
|
float TagReaderBase::ConvertPOPMRating(const int POPM_rating) {
|
||||||
|
|
||||||
if (POPM_rating < 0x01) return 0.0F;
|
if (POPM_rating < 0x01) return 0.0F;
|
||||||
@@ -55,3 +58,86 @@ int TagReaderBase::ConvertToPOPMRating(const float rating) {
|
|||||||
return 0xFF;
|
return 0xFF;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
QByteArray TagReaderBase::LoadCoverDataFromRequest(const spb::tagreader::SaveFileRequest &request) {
|
||||||
|
|
||||||
|
if (!request.has_save_cover() || !request.save_cover()) {
|
||||||
|
return QByteArray();
|
||||||
|
}
|
||||||
|
|
||||||
|
const QString song_filename = QString::fromUtf8(request.filename().data(), request.filename().size());
|
||||||
|
QString cover_filename;
|
||||||
|
if (request.has_cover_filename()) {
|
||||||
|
cover_filename = QString::fromUtf8(request.cover_filename().data(), request.cover_filename().size());
|
||||||
|
}
|
||||||
|
QByteArray cover_data;
|
||||||
|
if (request.has_cover_data()) {
|
||||||
|
cover_data = QByteArray(request.cover_data().data(), request.cover_data().size());
|
||||||
|
}
|
||||||
|
bool cover_is_jpeg = false;
|
||||||
|
if (request.has_cover_is_jpeg()) {
|
||||||
|
cover_is_jpeg = request.cover_is_jpeg();
|
||||||
|
}
|
||||||
|
|
||||||
|
return LoadCoverDataFromRequest(song_filename, cover_filename, cover_data, cover_is_jpeg);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
QByteArray TagReaderBase::LoadCoverDataFromRequest(const spb::tagreader::SaveEmbeddedArtRequest &request) {
|
||||||
|
|
||||||
|
const QString song_filename = QString::fromUtf8(request.filename().data(), request.filename().size());
|
||||||
|
QString cover_filename;
|
||||||
|
if (request.has_cover_filename()) {
|
||||||
|
cover_filename = QString::fromUtf8(request.cover_filename().data(), request.cover_filename().size());
|
||||||
|
}
|
||||||
|
QByteArray cover_data;
|
||||||
|
if (request.has_cover_data()) {
|
||||||
|
cover_data = QByteArray(request.cover_data().data(), request.cover_data().size());
|
||||||
|
}
|
||||||
|
bool cover_is_jpeg = false;
|
||||||
|
if (request.has_cover_is_jpeg()) {
|
||||||
|
cover_is_jpeg = request.cover_is_jpeg();
|
||||||
|
}
|
||||||
|
|
||||||
|
return LoadCoverDataFromRequest(song_filename, cover_filename, cover_data, cover_is_jpeg);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
QByteArray TagReaderBase::LoadCoverDataFromRequest(const QString &song_filename, const QString &cover_filename, QByteArray cover_data, const bool cover_is_jpeg) {
|
||||||
|
|
||||||
|
if (!cover_data.isEmpty() && cover_is_jpeg) {
|
||||||
|
qLog(Debug) << "Using cover from JPEG data for" << song_filename;
|
||||||
|
return cover_data;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (cover_data.isEmpty() && !cover_filename.isEmpty()) {
|
||||||
|
qLog(Debug) << "Loading cover from" << cover_filename << "for" << song_filename;
|
||||||
|
QFile file(cover_filename);
|
||||||
|
if (!file.open(QIODevice::ReadOnly)) {
|
||||||
|
qLog(Error) << "Failed to open file" << cover_filename << "for reading:" << file.errorString();
|
||||||
|
return QByteArray();
|
||||||
|
}
|
||||||
|
cover_data = file.readAll();
|
||||||
|
file.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!cover_data.isEmpty()) {
|
||||||
|
if (QMimeDatabase().mimeTypeForData(cover_data).name() == "image/jpeg") {
|
||||||
|
qLog(Debug) << "Using cover from JPEG data for" << song_filename;
|
||||||
|
return cover_data;
|
||||||
|
}
|
||||||
|
// Convert image to JPEG.
|
||||||
|
qLog(Debug) << "Converting cover to JPEG data for" << song_filename;
|
||||||
|
QImage cover_image(cover_data);
|
||||||
|
cover_data.clear();
|
||||||
|
QBuffer buffer(&cover_data);
|
||||||
|
if (buffer.open(QIODevice::WriteOnly)) {
|
||||||
|
cover_image.save(&buffer, "JPEG");
|
||||||
|
buffer.close();
|
||||||
|
}
|
||||||
|
return cover_data;
|
||||||
|
}
|
||||||
|
|
||||||
|
return QByteArray();
|
||||||
|
|
||||||
|
}
|
||||||
|
|||||||
@@ -27,9 +27,6 @@
|
|||||||
|
|
||||||
#include "tagreadermessages.pb.h"
|
#include "tagreadermessages.pb.h"
|
||||||
|
|
||||||
#define QStringFromStdString(x) QString::fromUtf8((x).data(), (x).size())
|
|
||||||
#define DataCommaSizeFromQString(x) (x).toUtf8().constData(), (x).toUtf8().length()
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* This class holds all useful methods to read and write tags from/to files.
|
* This class holds all useful methods to read and write tags from/to files.
|
||||||
* You should not use it directly in the main process but rather use a TagReaderWorker process (using TagReaderClient)
|
* You should not use it directly in the main process but rather use a TagReaderWorker process (using TagReaderClient)
|
||||||
@@ -42,19 +39,23 @@ class TagReaderBase {
|
|||||||
virtual bool IsMediaFile(const QString &filename) const = 0;
|
virtual bool IsMediaFile(const QString &filename) const = 0;
|
||||||
|
|
||||||
virtual bool ReadFile(const QString &filename, spb::tagreader::SongMetadata *song) const = 0;
|
virtual bool ReadFile(const QString &filename, spb::tagreader::SongMetadata *song) const = 0;
|
||||||
virtual bool SaveFile(const QString &filename, const spb::tagreader::SongMetadata &song) const = 0;
|
virtual bool SaveFile(const spb::tagreader::SaveFileRequest &request) const = 0;
|
||||||
|
|
||||||
virtual QByteArray LoadEmbeddedArt(const QString &filename) const = 0;
|
virtual QByteArray LoadEmbeddedArt(const QString &filename) const = 0;
|
||||||
virtual bool SaveEmbeddedArt(const QString &filename, const QByteArray &data) = 0;
|
virtual bool SaveEmbeddedArt(const spb::tagreader::SaveEmbeddedArtRequest &request) const = 0;
|
||||||
|
|
||||||
virtual bool SaveSongPlaycountToFile(const QString &filename, const spb::tagreader::SongMetadata &song) const = 0;
|
virtual bool SaveSongPlaycountToFile(const QString &filename, const spb::tagreader::SongMetadata &song) const = 0;
|
||||||
virtual bool SaveSongRatingToFile(const QString &filename, const spb::tagreader::SongMetadata &song) const = 0;
|
virtual bool SaveSongRatingToFile(const QString &filename, const spb::tagreader::SongMetadata &song) const = 0;
|
||||||
|
|
||||||
static void Decode(const QString &tag, std::string *output);
|
|
||||||
|
|
||||||
static float ConvertPOPMRating(const int POPM_rating);
|
static float ConvertPOPMRating(const int POPM_rating);
|
||||||
static int ConvertToPOPMRating(const float rating);
|
static int ConvertToPOPMRating(const float rating);
|
||||||
|
|
||||||
|
static QByteArray LoadCoverDataFromRequest(const spb::tagreader::SaveFileRequest &request);
|
||||||
|
static QByteArray LoadCoverDataFromRequest(const spb::tagreader::SaveEmbeddedArtRequest &request);
|
||||||
|
|
||||||
|
private:
|
||||||
|
static QByteArray LoadCoverDataFromRequest(const QString &song_filename, const QString &cover_filename, QByteArray cover_data, const bool cover_is_jpeg);
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
static const std::string kEmbeddedCover;
|
static const std::string kEmbeddedCover;
|
||||||
|
|
||||||
|
|||||||
@@ -29,8 +29,8 @@
|
|||||||
#include <QFile>
|
#include <QFile>
|
||||||
#include <QTextStream>
|
#include <QTextStream>
|
||||||
|
|
||||||
|
#include "utilities/timeconstants.h"
|
||||||
#include "core/logging.h"
|
#include "core/logging.h"
|
||||||
#include "core/timeconstants.h"
|
|
||||||
#include "core/messagehandler.h"
|
#include "core/messagehandler.h"
|
||||||
#include "tagreaderbase.h"
|
#include "tagreaderbase.h"
|
||||||
#include "tagreadertaglib.h"
|
#include "tagreadertaglib.h"
|
||||||
@@ -84,7 +84,8 @@ void GME::SPC::Read(const QFileInfo &file_info, spb::tagreader::SongMetadata *so
|
|||||||
// Make sure to check id6 documentation before changing the read values!
|
// Make sure to check id6 documentation before changing the read values!
|
||||||
|
|
||||||
file.seek(HAS_ID6_OFFSET);
|
file.seek(HAS_ID6_OFFSET);
|
||||||
bool has_id6 = (file.read(1)[0] == static_cast<char>(xID6_STATUS::ON));
|
const QByteArray id6_status = file.read(1);
|
||||||
|
const bool has_id6 = id6_status.length() >= 1 && id6_status[0] == static_cast<char>(xID6_STATUS::ON);
|
||||||
|
|
||||||
file.seek(SONG_TITLE_OFFSET);
|
file.seek(SONG_TITLE_OFFSET);
|
||||||
song_info->set_title(QString::fromLatin1(file.read(32)).toStdString());
|
song_info->set_title(QString::fromLatin1(file.read(32)).toStdString());
|
||||||
@@ -156,10 +157,10 @@ void GME::SPC::Read(const QFileInfo &file_info, spb::tagreader::SongMetadata *so
|
|||||||
TagLib::Tag *tag = ape.tag();
|
TagLib::Tag *tag = ape.tag();
|
||||||
if (!tag) return;
|
if (!tag) return;
|
||||||
|
|
||||||
TagReaderTagLib::Decode(tag->artist(), song_info->mutable_artist());
|
TagReaderTagLib::TStringToStdString(tag->artist(), song_info->mutable_artist());
|
||||||
TagReaderTagLib::Decode(tag->album(), song_info->mutable_album());
|
TagReaderTagLib::TStringToStdString(tag->album(), song_info->mutable_album());
|
||||||
TagReaderTagLib::Decode(tag->title(), song_info->mutable_title());
|
TagReaderTagLib::TStringToStdString(tag->title(), song_info->mutable_title());
|
||||||
TagReaderTagLib::Decode(tag->genre(), song_info->mutable_genre());
|
TagReaderTagLib::TStringToStdString(tag->genre(), song_info->mutable_genre());
|
||||||
song_info->set_track(tag->track());
|
song_info->set_track(tag->track());
|
||||||
song_info->set_year(tag->year());
|
song_info->set_year(tag->year());
|
||||||
}
|
}
|
||||||
@@ -200,7 +201,7 @@ void GME::VGM::Read(const QFileInfo &file_info, spb::tagreader::SongMetadata *so
|
|||||||
QByteArray gd3_head = file.read(4);
|
QByteArray gd3_head = file.read(4);
|
||||||
if (gd3_head.size() < 4) return;
|
if (gd3_head.size() < 4) return;
|
||||||
|
|
||||||
quint64 pt = GME::UnpackBytes32(gd3_head, gd3_head.size());
|
quint64 pt = GME::UnpackBytes32(gd3_head.constData(), gd3_head.size());
|
||||||
|
|
||||||
file.seek(SAMPLE_COUNT);
|
file.seek(SAMPLE_COUNT);
|
||||||
QByteArray sample_count_bytes = file.read(4);
|
QByteArray sample_count_bytes = file.read(4);
|
||||||
@@ -215,7 +216,7 @@ void GME::VGM::Read(const QFileInfo &file_info, spb::tagreader::SongMetadata *so
|
|||||||
|
|
||||||
file.seek(file.pos() + 4);
|
file.seek(file.pos() + 4);
|
||||||
QByteArray gd3_length_bytes = file.read(4);
|
QByteArray gd3_length_bytes = file.read(4);
|
||||||
quint32 gd3_length = GME::UnpackBytes32(gd3_length_bytes, gd3_length_bytes.size());
|
quint32 gd3_length = GME::UnpackBytes32(gd3_length_bytes.constData(), gd3_length_bytes.size());
|
||||||
|
|
||||||
QByteArray gd3Data = file.read(gd3_length);
|
QByteArray gd3Data = file.read(gd3_length);
|
||||||
QTextStream fileTagStream(gd3Data, QIODevice::ReadOnly);
|
QTextStream fileTagStream(gd3Data, QIODevice::ReadOnly);
|
||||||
@@ -246,11 +247,11 @@ bool GME::VGM::GetPlaybackLength(const QByteArray &sample_count_bytes, const QBy
|
|||||||
if (sample_count_bytes.size() != 4) return false;
|
if (sample_count_bytes.size() != 4) return false;
|
||||||
if (loop_count_bytes.size() != 4) return false;
|
if (loop_count_bytes.size() != 4) return false;
|
||||||
|
|
||||||
quint64 sample_count = GME::UnpackBytes32(sample_count_bytes, sample_count_bytes.size());
|
quint64 sample_count = GME::UnpackBytes32(sample_count_bytes.constData(), sample_count_bytes.size());
|
||||||
|
|
||||||
if (sample_count <= 0) return false;
|
if (sample_count <= 0) return false;
|
||||||
|
|
||||||
quint64 loop_sample_count = GME::UnpackBytes32(loop_count_bytes, loop_count_bytes.size());
|
quint64 loop_sample_count = GME::UnpackBytes32(loop_count_bytes.constData(), loop_count_bytes.size());
|
||||||
|
|
||||||
if (loop_sample_count <= 0) {
|
if (loop_sample_count <= 0) {
|
||||||
out_length = sample_count * 1000 / SAMPLE_TIMEBASE;
|
out_length = sample_count * 1000 / SAMPLE_TIMEBASE;
|
||||||
@@ -278,7 +279,7 @@ bool TagReaderGME::ReadFile(const QString &filename, spb::tagreader::SongMetadat
|
|||||||
return GME::ReadFile(fileinfo, song);
|
return GME::ReadFile(fileinfo, song);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool TagReaderGME::SaveFile(const QString&, const spb::tagreader::SongMetadata&) const {
|
bool TagReaderGME::SaveFile(const spb::tagreader::SaveFileRequest&) const {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -286,7 +287,7 @@ QByteArray TagReaderGME::LoadEmbeddedArt(const QString&) const {
|
|||||||
return QByteArray();
|
return QByteArray();
|
||||||
}
|
}
|
||||||
|
|
||||||
bool TagReaderGME::SaveEmbeddedArt(const QString&, const QByteArray&) {
|
bool TagReaderGME::SaveEmbeddedArt(const spb::tagreader::SaveEmbeddedArtRequest&) const {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -55,14 +55,22 @@ constexpr int XID6_OFFSET = (0x101C0 + 64);
|
|||||||
|
|
||||||
constexpr int NANO_PER_MS = 1000000;
|
constexpr int NANO_PER_MS = 1000000;
|
||||||
|
|
||||||
enum xID6_STATUS {
|
enum class xID6_STATUS {
|
||||||
ON = 0x26,
|
ON = 0x26,
|
||||||
OFF = 0x27,
|
OFF = 0x27
|
||||||
};
|
};
|
||||||
|
|
||||||
enum xID6_ID { SongName = 0x01, GameName = 0x02, ArtistName = 0x03 };
|
enum class xID6_ID {
|
||||||
|
SongName = 0x01,
|
||||||
|
GameName = 0x02,
|
||||||
|
ArtistName = 0x03
|
||||||
|
};
|
||||||
|
|
||||||
enum xID6_TYPE { Length = 0x0, String = 0x1, Integer = 0x4 };
|
enum class xID6_TYPE {
|
||||||
|
Length = 0x0,
|
||||||
|
String = 0x1,
|
||||||
|
Integer = 0x4
|
||||||
|
};
|
||||||
|
|
||||||
void Read(const QFileInfo &file_info, spb::tagreader::SongMetadata *song_info);
|
void Read(const QFileInfo &file_info, spb::tagreader::SongMetadata *song_info);
|
||||||
qint16 GetNextMemAddressAlign32bit(qint16 input);
|
qint16 GetNextMemAddressAlign32bit(qint16 input);
|
||||||
@@ -99,13 +107,13 @@ class TagReaderGME : public TagReaderBase {
|
|||||||
bool IsMediaFile(const QString &filename) const override;
|
bool IsMediaFile(const QString &filename) const override;
|
||||||
|
|
||||||
bool ReadFile(const QString &filename, spb::tagreader::SongMetadata *song) const override;
|
bool ReadFile(const QString &filename, spb::tagreader::SongMetadata *song) const override;
|
||||||
bool SaveFile(const QString &filename, const spb::tagreader::SongMetadata &song) const override;
|
bool SaveFile(const spb::tagreader::SaveFileRequest &request) const override;
|
||||||
|
|
||||||
QByteArray LoadEmbeddedArt(const QString &filename) const override;
|
QByteArray LoadEmbeddedArt(const QString &filename) const override;
|
||||||
bool SaveEmbeddedArt(const QString &filename, const QByteArray &data) override;
|
bool SaveEmbeddedArt(const spb::tagreader::SaveEmbeddedArtRequest &request) const override;
|
||||||
|
|
||||||
bool SaveSongPlaycountToFile(const QString &filename, const spb::tagreader::SongMetadata &song) const override;
|
bool SaveSongPlaycountToFile(const QString &filename, const spb::tagreader::SongMetadata &song) const override;
|
||||||
bool SaveSongRatingToFile(const QString &filename, const spb::tagreader::SongMetadata &song) const override;
|
bool SaveSongRatingToFile(const QString &filename, const spb::tagreader::SongMetadata &song) const override;
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif
|
#endif // TAGREADERGME_H
|
||||||
|
|||||||
@@ -73,8 +73,30 @@ message SongMetadata {
|
|||||||
|
|
||||||
optional float rating = 32;
|
optional float rating = 32;
|
||||||
|
|
||||||
optional bool suspicious_tags = 40;
|
optional string acoustid_id = 33;
|
||||||
|
optional string acoustid_fingerprint = 34;
|
||||||
|
|
||||||
|
optional string musicbrainz_album_artist_id = 35; // MusicBrainz Release Artist ID (MUSICBRAINZ_ALBUMARTISTID)
|
||||||
|
optional string musicbrainz_artist_id = 36; // MusicBrainz Artist ID (MUSICBRAINZ_ARTISTID)
|
||||||
|
optional string musicbrainz_original_artist_id = 37; // MusicBrainz Original Artist ID (MUSICBRAINZ_ORIGINALARTISTID)
|
||||||
|
optional string musicbrainz_album_id = 38; // MusicBrainz Release ID (MUSICBRAINZ_ALBUMID)
|
||||||
|
optional string musicbrainz_original_album_id = 39; // MusicBrainz Original Release ID (MUSICBRAINZ_ORIGINALALBUMID)
|
||||||
|
optional string musicbrainz_recording_id = 40; // MusicBrainz Recording ID (MUSICBRAINZ_TRACKID)
|
||||||
|
optional string musicbrainz_track_id = 41; // MusicBrainz Track ID (MUSICBRAINZ_RELEASETRACKID)
|
||||||
|
optional string musicbrainz_disc_id = 42; // MusicBrainz Disc ID (MUSICBRAINZ_DISCID)
|
||||||
|
optional string musicbrainz_release_group_id = 43; // MusicBrainz Release Group ID (MUSICBRAINZ_RELEASEGROUPID)
|
||||||
|
optional string musicbrainz_work_id = 44; // MusicBrainz Work ID (MUSICBRAINZ_WORKID)
|
||||||
|
|
||||||
|
optional bool suspicious_tags = 50;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
message IsMediaFileRequest {
|
||||||
|
optional string filename = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
message IsMediaFileResponse {
|
||||||
|
optional bool success = 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
message ReadFileRequest {
|
message ReadFileRequest {
|
||||||
@@ -87,21 +109,20 @@ message ReadFileResponse {
|
|||||||
|
|
||||||
message SaveFileRequest {
|
message SaveFileRequest {
|
||||||
optional string filename = 1;
|
optional string filename = 1;
|
||||||
optional SongMetadata metadata = 2;
|
optional bool save_tags = 2;
|
||||||
|
optional bool save_playcount = 3;
|
||||||
|
optional bool save_rating = 4;
|
||||||
|
optional bool save_cover = 5;
|
||||||
|
optional SongMetadata metadata = 6;
|
||||||
|
optional string cover_filename = 7;
|
||||||
|
optional bytes cover_data = 8;
|
||||||
|
optional bool cover_is_jpeg = 9;
|
||||||
}
|
}
|
||||||
|
|
||||||
message SaveFileResponse {
|
message SaveFileResponse {
|
||||||
optional bool success = 1;
|
optional bool success = 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
message IsMediaFileRequest {
|
|
||||||
optional string filename = 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
message IsMediaFileResponse {
|
|
||||||
optional bool success = 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
message LoadEmbeddedArtRequest {
|
message LoadEmbeddedArtRequest {
|
||||||
optional string filename = 1;
|
optional string filename = 1;
|
||||||
}
|
}
|
||||||
@@ -112,7 +133,9 @@ message LoadEmbeddedArtResponse {
|
|||||||
|
|
||||||
message SaveEmbeddedArtRequest {
|
message SaveEmbeddedArtRequest {
|
||||||
optional string filename = 1;
|
optional string filename = 1;
|
||||||
optional bytes data = 2;
|
optional string cover_filename = 2;
|
||||||
|
optional bytes cover_data = 3;
|
||||||
|
optional bool cover_is_jpeg = 4;
|
||||||
}
|
}
|
||||||
|
|
||||||
message SaveEmbeddedArtResponse {
|
message SaveEmbeddedArtResponse {
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -29,8 +29,12 @@
|
|||||||
#include <taglib/tstring.h>
|
#include <taglib/tstring.h>
|
||||||
#include <taglib/fileref.h>
|
#include <taglib/fileref.h>
|
||||||
#include <taglib/xiphcomment.h>
|
#include <taglib/xiphcomment.h>
|
||||||
|
#include <taglib/flacfile.h>
|
||||||
|
#include <taglib/mpegfile.h>
|
||||||
|
#include <taglib/mp4file.h>
|
||||||
#include <taglib/apetag.h>
|
#include <taglib/apetag.h>
|
||||||
#include <taglib/apefile.h>
|
#include <taglib/apefile.h>
|
||||||
|
#include <taglib/asffile.h>
|
||||||
#include <taglib/id3v2tag.h>
|
#include <taglib/id3v2tag.h>
|
||||||
#include <taglib/popularimeterframe.h>
|
#include <taglib/popularimeterframe.h>
|
||||||
|
|
||||||
@@ -51,15 +55,15 @@ class TagReaderTagLib : public TagReaderBase {
|
|||||||
bool IsMediaFile(const QString &filename) const override;
|
bool IsMediaFile(const QString &filename) const override;
|
||||||
|
|
||||||
bool ReadFile(const QString &filename, spb::tagreader::SongMetadata *song) const override;
|
bool ReadFile(const QString &filename, spb::tagreader::SongMetadata *song) const override;
|
||||||
bool SaveFile(const QString &filename, const spb::tagreader::SongMetadata &song) const override;
|
bool SaveFile(const spb::tagreader::SaveFileRequest &request) const override;
|
||||||
|
|
||||||
QByteArray LoadEmbeddedArt(const QString &filename) const override;
|
QByteArray LoadEmbeddedArt(const QString &filename) const override;
|
||||||
bool SaveEmbeddedArt(const QString &filename, const QByteArray &data) override;
|
bool SaveEmbeddedArt(const spb::tagreader::SaveEmbeddedArtRequest &request) const override;
|
||||||
|
|
||||||
bool SaveSongPlaycountToFile(const QString &filename, const spb::tagreader::SongMetadata &song) const override;
|
bool SaveSongPlaycountToFile(const QString &filename, const spb::tagreader::SongMetadata &song) const override;
|
||||||
bool SaveSongRatingToFile(const QString &filename, const spb::tagreader::SongMetadata &song) const override;
|
bool SaveSongRatingToFile(const QString &filename, const spb::tagreader::SongMetadata &song) const override;
|
||||||
|
|
||||||
static void Decode(const TagLib::String &tag, std::string *output);
|
static void TStringToStdString(const TagLib::String &tag, std::string *output);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
spb::tagreader::SongMetadata_FileType GuessFileType(TagLib::FileRef *fileref) const;
|
spb::tagreader::SongMetadata_FileType GuessFileType(TagLib::FileRef *fileref) const;
|
||||||
@@ -67,7 +71,7 @@ class TagReaderTagLib : public TagReaderBase {
|
|||||||
void ParseOggTag(const TagLib::Ogg::FieldListMap &map, QString *disc, QString *compilation, spb::tagreader::SongMetadata *song) const;
|
void ParseOggTag(const TagLib::Ogg::FieldListMap &map, QString *disc, QString *compilation, spb::tagreader::SongMetadata *song) const;
|
||||||
void ParseAPETag(const TagLib::APE::ItemListMap &map, QString *disc, QString *compilation, spb::tagreader::SongMetadata *song) const;
|
void ParseAPETag(const TagLib::APE::ItemListMap &map, QString *disc, QString *compilation, spb::tagreader::SongMetadata *song) const;
|
||||||
|
|
||||||
void SetVorbisComments(TagLib::Ogg::XiphComment *vorbis_comments, const spb::tagreader::SongMetadata &song) const;
|
void SetVorbisComments(TagLib::Ogg::XiphComment *vorbis_comment, const spb::tagreader::SongMetadata &song) const;
|
||||||
void SaveAPETag(TagLib::APE::Tag *tag, const spb::tagreader::SongMetadata &song) const;
|
void SaveAPETag(TagLib::APE::Tag *tag, const spb::tagreader::SongMetadata &song) const;
|
||||||
|
|
||||||
void SetTextFrame(const char *id, const QString &value, TagLib::ID3v2::Tag *tag) const;
|
void SetTextFrame(const char *id, const QString &value, TagLib::ID3v2::Tag *tag) const;
|
||||||
@@ -80,6 +84,23 @@ class TagReaderTagLib : public TagReaderBase {
|
|||||||
|
|
||||||
static TagLib::ID3v2::PopularimeterFrame *GetPOPMFrameFromTag(TagLib::ID3v2::Tag *tag);
|
static TagLib::ID3v2::PopularimeterFrame *GetPOPMFrameFromTag(TagLib::ID3v2::Tag *tag);
|
||||||
|
|
||||||
|
void SetPlaycount(TagLib::Ogg::XiphComment *xiph_comment, const spb::tagreader::SongMetadata &song) const;
|
||||||
|
void SetPlaycount(TagLib::APE::Tag *tag, const spb::tagreader::SongMetadata &song) const;
|
||||||
|
void SetPlaycount(TagLib::ID3v2::Tag *tag, const spb::tagreader::SongMetadata &song) const;
|
||||||
|
void SetPlaycount(TagLib::MP4::Tag *tag, const spb::tagreader::SongMetadata &song) const;
|
||||||
|
void SetPlaycount(TagLib::ASF::Tag *tag, const spb::tagreader::SongMetadata &song) const;
|
||||||
|
|
||||||
|
void SetRating(TagLib::Ogg::XiphComment *xiph_comment, const spb::tagreader::SongMetadata &song) const;
|
||||||
|
void SetRating(TagLib::APE::Tag *tag, const spb::tagreader::SongMetadata &song) const;
|
||||||
|
void SetRating(TagLib::ID3v2::Tag *tag, const spb::tagreader::SongMetadata &song) const;
|
||||||
|
void SetRating(TagLib::MP4::Tag *tag, const spb::tagreader::SongMetadata &song) const;
|
||||||
|
void SetRating(TagLib::ASF::Tag *tag, const spb::tagreader::SongMetadata &song) const;
|
||||||
|
|
||||||
|
void SetEmbeddedArt(TagLib::FLAC::File *flac_file, TagLib::Ogg::XiphComment *xiph_comment, const QByteArray &data) const;
|
||||||
|
void SetEmbeddedArt(TagLib::Ogg::XiphComment *xiph_comment, const QByteArray &data) const;
|
||||||
|
void SetEmbeddedArt(TagLib::MPEG::File *file_mp3, TagLib::ID3v2::Tag *tag, const QByteArray &data) const;
|
||||||
|
void SetEmbeddedArt(TagLib::MP4::File *aac_file, TagLib::MP4::Tag *tag, const QByteArray &data) const;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
FileRefFactory *factory_;
|
FileRefFactory *factory_;
|
||||||
|
|
||||||
|
|||||||
@@ -22,6 +22,7 @@
|
|||||||
#include <string>
|
#include <string>
|
||||||
#include <memory>
|
#include <memory>
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
|
#include <vector>
|
||||||
#include <sys/stat.h>
|
#include <sys/stat.h>
|
||||||
|
|
||||||
#include <tagparser/mediafileinfo.h>
|
#include <tagparser/mediafileinfo.h>
|
||||||
@@ -41,7 +42,7 @@
|
|||||||
|
|
||||||
#include "core/logging.h"
|
#include "core/logging.h"
|
||||||
#include "core/messagehandler.h"
|
#include "core/messagehandler.h"
|
||||||
#include "core/timeconstants.h"
|
#include "utilities/timeconstants.h"
|
||||||
|
|
||||||
TagReaderTagParser::TagReaderTagParser() = default;
|
TagReaderTagParser::TagReaderTagParser() = default;
|
||||||
|
|
||||||
@@ -79,7 +80,7 @@ bool TagReaderTagParser::IsMediaFile(const QString &filename) const {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const auto tracks = taginfo.tracks();
|
const auto tracks = taginfo.tracks();
|
||||||
for (const auto track : tracks) {
|
for (TagParser::AbstractTrack *track : tracks) {
|
||||||
if (track->mediaType() == TagParser::MediaType::Audio) {
|
if (track->mediaType() == TagParser::MediaType::Audio) {
|
||||||
taginfo.close();
|
taginfo.close();
|
||||||
return true;
|
return true;
|
||||||
@@ -102,8 +103,9 @@ bool TagReaderTagParser::ReadFile(const QString &filename, spb::tagreader::SongM
|
|||||||
if (!fileinfo.exists() || fileinfo.suffix().compare("bak", Qt::CaseInsensitive) == 0) return false;
|
if (!fileinfo.exists() || fileinfo.suffix().compare("bak", Qt::CaseInsensitive) == 0) return false;
|
||||||
|
|
||||||
const QByteArray url(QUrl::fromLocalFile(filename).toEncoded());
|
const QByteArray url(QUrl::fromLocalFile(filename).toEncoded());
|
||||||
|
const QByteArray basefilename = fileinfo.fileName().toUtf8();
|
||||||
|
|
||||||
song->set_basefilename(DataCommaSizeFromQString(fileinfo.fileName()));
|
song->set_basefilename(basefilename.constData(), basefilename.size());
|
||||||
song->set_url(url.constData(), url.size());
|
song->set_url(url.constData(), url.size());
|
||||||
song->set_filesize(fileinfo.size());
|
song->set_filesize(fileinfo.size());
|
||||||
song->set_mtime(fileinfo.lastModified().isValid() ? std::max(fileinfo.lastModified().toSecsSinceEpoch(), 0LL) : 0LL);
|
song->set_mtime(fileinfo.lastModified().isValid() ? std::max(fileinfo.lastModified().toSecsSinceEpoch(), 0LL) : 0LL);
|
||||||
@@ -154,8 +156,8 @@ bool TagReaderTagParser::ReadFile(const QString &filename, spb::tagreader::SongM
|
|||||||
qLog(Debug) << QString::fromStdString(msg.message());
|
qLog(Debug) << QString::fromStdString(msg.message());
|
||||||
}
|
}
|
||||||
|
|
||||||
const auto tracks = taginfo.tracks();
|
std::vector<TagParser::AbstractTrack*> tracks = taginfo.tracks();
|
||||||
for (const auto track : tracks) {
|
for (TagParser::AbstractTrack *track : tracks) {
|
||||||
switch (track->format().general) {
|
switch (track->format().general) {
|
||||||
case TagParser::GeneralMediaFormat::Flac:
|
case TagParser::GeneralMediaFormat::Flac:
|
||||||
song->set_filetype(spb::tagreader::SongMetadata_FileType::SongMetadata_FileType_FLAC);
|
song->set_filetype(spb::tagreader::SongMetadata_FileType::SongMetadata_FileType_FLAC);
|
||||||
@@ -209,7 +211,7 @@ bool TagReaderTagParser::ReadFile(const QString &filename, spb::tagreader::SongM
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
for (const auto tag : taginfo.tags()) {
|
for (TagParser::Tag *tag : taginfo.tags()) {
|
||||||
song->set_albumartist(tag->value(TagParser::KnownField::AlbumArtist).toString(TagParser::TagTextEncoding::Utf8));
|
song->set_albumartist(tag->value(TagParser::KnownField::AlbumArtist).toString(TagParser::TagTextEncoding::Utf8));
|
||||||
song->set_artist(tag->value(TagParser::KnownField::Artist).toString(TagParser::TagTextEncoding::Utf8));
|
song->set_artist(tag->value(TagParser::KnownField::Artist).toString(TagParser::TagTextEncoding::Utf8));
|
||||||
song->set_album(tag->value(TagParser::KnownField::Album).toString(TagParser::TagTextEncoding::Utf8));
|
song->set_album(tag->value(TagParser::KnownField::Album).toString(TagParser::TagTextEncoding::Utf8));
|
||||||
@@ -256,11 +258,34 @@ bool TagReaderTagParser::ReadFile(const QString &filename, spb::tagreader::SongM
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bool TagReaderTagParser::SaveFile(const QString &filename, const spb::tagreader::SongMetadata &song) const {
|
bool TagReaderTagParser::SaveFile(const spb::tagreader::SaveFileRequest &request) const {
|
||||||
|
|
||||||
if (filename.isEmpty()) return false;
|
if (request.filename().empty()) return false;
|
||||||
|
|
||||||
qLog(Debug) << "Saving tags to" << filename;
|
const QString filename = QString::fromUtf8(request.filename().data(), request.filename().size());
|
||||||
|
const spb::tagreader::SongMetadata song = request.metadata();
|
||||||
|
const bool save_tags = request.has_save_tags() && request.save_tags();
|
||||||
|
const bool save_playcount = request.has_save_playcount() && request.save_playcount();
|
||||||
|
const bool save_rating = request.has_save_rating() && request.save_rating();
|
||||||
|
const bool save_cover = request.has_save_cover() && request.save_cover();
|
||||||
|
|
||||||
|
QStringList save_tags_options;
|
||||||
|
if (save_tags) {
|
||||||
|
save_tags_options << "tags";
|
||||||
|
}
|
||||||
|
if (save_playcount) {
|
||||||
|
save_tags_options << "playcount";
|
||||||
|
}
|
||||||
|
if (save_rating) {
|
||||||
|
save_tags_options << "rating";
|
||||||
|
}
|
||||||
|
if (save_cover) {
|
||||||
|
save_tags_options << "embedded cover";
|
||||||
|
}
|
||||||
|
|
||||||
|
qLog(Debug) << "Saving" << save_tags_options.join(", ") << "to" << filename;
|
||||||
|
|
||||||
|
const QByteArray cover_data = LoadCoverDataFromRequest(request);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
TagParser::MediaFileInfo taginfo;
|
TagParser::MediaFileInfo taginfo;
|
||||||
@@ -295,22 +320,34 @@ bool TagReaderTagParser::SaveFile(const QString &filename, const spb::tagreader:
|
|||||||
taginfo.createAppropriateTags();
|
taginfo.createAppropriateTags();
|
||||||
}
|
}
|
||||||
|
|
||||||
for (const auto tag : taginfo.tags()) {
|
for (TagParser::Tag *tag : taginfo.tags()) {
|
||||||
tag->setValue(TagParser::KnownField::AlbumArtist, TagParser::TagValue(song.albumartist(), TagParser::TagTextEncoding::Utf8, tag->proposedTextEncoding()));
|
if (save_tags) {
|
||||||
tag->setValue(TagParser::KnownField::Artist, TagParser::TagValue(song.artist(), TagParser::TagTextEncoding::Utf8, tag->proposedTextEncoding()));
|
tag->setValue(TagParser::KnownField::AlbumArtist, TagParser::TagValue(song.albumartist(), TagParser::TagTextEncoding::Utf8, tag->proposedTextEncoding()));
|
||||||
tag->setValue(TagParser::KnownField::Album, TagParser::TagValue(song.album(), TagParser::TagTextEncoding::Utf8, tag->proposedTextEncoding()));
|
tag->setValue(TagParser::KnownField::Artist, TagParser::TagValue(song.artist(), TagParser::TagTextEncoding::Utf8, tag->proposedTextEncoding()));
|
||||||
tag->setValue(TagParser::KnownField::Title, TagParser::TagValue(song.title(), TagParser::TagTextEncoding::Utf8, tag->proposedTextEncoding()));
|
tag->setValue(TagParser::KnownField::Album, TagParser::TagValue(song.album(), TagParser::TagTextEncoding::Utf8, tag->proposedTextEncoding()));
|
||||||
tag->setValue(TagParser::KnownField::Genre, TagParser::TagValue(song.genre(), TagParser::TagTextEncoding::Utf8, tag->proposedTextEncoding()));
|
tag->setValue(TagParser::KnownField::Title, TagParser::TagValue(song.title(), TagParser::TagTextEncoding::Utf8, tag->proposedTextEncoding()));
|
||||||
tag->setValue(TagParser::KnownField::Composer, TagParser::TagValue(song.composer(), TagParser::TagTextEncoding::Utf8, tag->proposedTextEncoding()));
|
tag->setValue(TagParser::KnownField::Genre, TagParser::TagValue(song.genre(), TagParser::TagTextEncoding::Utf8, tag->proposedTextEncoding()));
|
||||||
tag->setValue(TagParser::KnownField::Performers, TagParser::TagValue(song.performer(), TagParser::TagTextEncoding::Utf8, tag->proposedTextEncoding()));
|
tag->setValue(TagParser::KnownField::Composer, TagParser::TagValue(song.composer(), TagParser::TagTextEncoding::Utf8, tag->proposedTextEncoding()));
|
||||||
tag->setValue(TagParser::KnownField::Grouping, TagParser::TagValue(song.grouping(), TagParser::TagTextEncoding::Utf8, tag->proposedTextEncoding()));
|
tag->setValue(TagParser::KnownField::Performers, TagParser::TagValue(song.performer(), TagParser::TagTextEncoding::Utf8, tag->proposedTextEncoding()));
|
||||||
tag->setValue(TagParser::KnownField::Comment, TagParser::TagValue(song.comment(), TagParser::TagTextEncoding::Utf8, tag->proposedTextEncoding()));
|
tag->setValue(TagParser::KnownField::Grouping, TagParser::TagValue(song.grouping(), TagParser::TagTextEncoding::Utf8, tag->proposedTextEncoding()));
|
||||||
tag->setValue(TagParser::KnownField::Lyrics, TagParser::TagValue(song.lyrics(), TagParser::TagTextEncoding::Utf8, tag->proposedTextEncoding()));
|
tag->setValue(TagParser::KnownField::Comment, TagParser::TagValue(song.comment(), TagParser::TagTextEncoding::Utf8, tag->proposedTextEncoding()));
|
||||||
tag->setValue(TagParser::KnownField::TrackPosition, TagParser::TagValue(song.track()));
|
tag->setValue(TagParser::KnownField::Lyrics, TagParser::TagValue(song.lyrics(), TagParser::TagTextEncoding::Utf8, tag->proposedTextEncoding()));
|
||||||
tag->setValue(TagParser::KnownField::DiskPosition, TagParser::TagValue(song.disc()));
|
tag->setValue(TagParser::KnownField::TrackPosition, TagParser::TagValue(song.track()));
|
||||||
tag->setValue(TagParser::KnownField::RecordDate, TagParser::TagValue(song.year()));
|
tag->setValue(TagParser::KnownField::DiskPosition, TagParser::TagValue(song.disc()));
|
||||||
tag->setValue(TagParser::KnownField::ReleaseDate, TagParser::TagValue(song.originalyear()));
|
tag->setValue(TagParser::KnownField::RecordDate, TagParser::TagValue(song.year()));
|
||||||
|
tag->setValue(TagParser::KnownField::ReleaseDate, TagParser::TagValue(song.originalyear()));
|
||||||
|
}
|
||||||
|
if (save_playcount) {
|
||||||
|
SaveSongPlaycountToFile(tag, song);
|
||||||
|
}
|
||||||
|
if (save_rating) {
|
||||||
|
SaveSongRatingToFile(tag, song);
|
||||||
|
}
|
||||||
|
if (save_cover) {
|
||||||
|
SaveEmbeddedArt(tag, cover_data);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
taginfo.applyChanges(diag, progress);
|
taginfo.applyChanges(diag, progress);
|
||||||
taginfo.close();
|
taginfo.close();
|
||||||
|
|
||||||
@@ -358,7 +395,7 @@ QByteArray TagReaderTagParser::LoadEmbeddedArt(const QString &filename) const {
|
|||||||
return QByteArray();
|
return QByteArray();
|
||||||
}
|
}
|
||||||
|
|
||||||
for (const auto tag : taginfo.tags()) {
|
for (TagParser::Tag *tag : taginfo.tags()) {
|
||||||
if (!tag->value(TagParser::KnownField::Cover).empty() && tag->value(TagParser::KnownField::Cover).dataSize() > 0) {
|
if (!tag->value(TagParser::KnownField::Cover).empty() && tag->value(TagParser::KnownField::Cover).dataSize() > 0) {
|
||||||
QByteArray data(tag->value(TagParser::KnownField::Cover).dataPointer(), tag->value(TagParser::KnownField::Cover).dataSize());
|
QByteArray data(tag->value(TagParser::KnownField::Cover).dataPointer(), tag->value(TagParser::KnownField::Cover).dataSize());
|
||||||
taginfo.close();
|
taginfo.close();
|
||||||
@@ -379,12 +416,22 @@ QByteArray TagReaderTagParser::LoadEmbeddedArt(const QString &filename) const {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bool TagReaderTagParser::SaveEmbeddedArt(const QString &filename, const QByteArray &data) {
|
void TagReaderTagParser::SaveEmbeddedArt(TagParser::Tag *tag, const QByteArray &data) const {
|
||||||
|
|
||||||
if (filename.isEmpty()) return false;
|
tag->setValue(TagParser::KnownField::Cover, TagParser::TagValue(data.toStdString()));
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
bool TagReaderTagParser::SaveEmbeddedArt(const spb::tagreader::SaveEmbeddedArtRequest &request) const {
|
||||||
|
|
||||||
|
if (request.filename().empty()) return false;
|
||||||
|
|
||||||
|
const QString filename = QString::fromUtf8(request.filename().data(), request.filename().size());
|
||||||
|
|
||||||
qLog(Debug) << "Saving art to" << filename;
|
qLog(Debug) << "Saving art to" << filename;
|
||||||
|
|
||||||
|
const QByteArray cover_data = LoadCoverDataFromRequest(request);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
|
||||||
TagParser::MediaFileInfo taginfo;
|
TagParser::MediaFileInfo taginfo;
|
||||||
@@ -415,8 +462,8 @@ bool TagReaderTagParser::SaveEmbeddedArt(const QString &filename, const QByteArr
|
|||||||
taginfo.createAppropriateTags();
|
taginfo.createAppropriateTags();
|
||||||
}
|
}
|
||||||
|
|
||||||
for (const auto tag : taginfo.tags()) {
|
for (TagParser::Tag *tag : taginfo.tags()) {
|
||||||
tag->setValue(TagParser::KnownField::Cover, TagParser::TagValue(data.toStdString()));
|
SaveEmbeddedArt(tag, cover_data);
|
||||||
}
|
}
|
||||||
|
|
||||||
taginfo.applyChanges(diag, progress);
|
taginfo.applyChanges(diag, progress);
|
||||||
@@ -435,8 +482,16 @@ bool TagReaderTagParser::SaveEmbeddedArt(const QString &filename, const QByteArr
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void TagReaderTagParser::SaveSongPlaycountToFile(TagParser::Tag*, const spb::tagreader::SongMetadata&) const {}
|
||||||
|
|
||||||
bool TagReaderTagParser::SaveSongPlaycountToFile(const QString&, const spb::tagreader::SongMetadata&) const { return false; }
|
bool TagReaderTagParser::SaveSongPlaycountToFile(const QString&, const spb::tagreader::SongMetadata&) const { return false; }
|
||||||
|
|
||||||
|
void TagReaderTagParser::SaveSongRatingToFile(TagParser::Tag *tag, const spb::tagreader::SongMetadata &song) const {
|
||||||
|
|
||||||
|
tag->setValue(TagParser::KnownField::Rating, TagParser::TagValue(ConvertToPOPMRating(song.rating())));
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
bool TagReaderTagParser::SaveSongRatingToFile(const QString &filename, const spb::tagreader::SongMetadata &song) const {
|
bool TagReaderTagParser::SaveSongRatingToFile(const QString &filename, const spb::tagreader::SongMetadata &song) const {
|
||||||
|
|
||||||
if (filename.isEmpty()) return false;
|
if (filename.isEmpty()) return false;
|
||||||
@@ -476,9 +531,10 @@ bool TagReaderTagParser::SaveSongRatingToFile(const QString &filename, const spb
|
|||||||
taginfo.createAppropriateTags();
|
taginfo.createAppropriateTags();
|
||||||
}
|
}
|
||||||
|
|
||||||
for (const auto tag : taginfo.tags()) {
|
for (TagParser::Tag *tag : taginfo.tags()) {
|
||||||
tag->setValue(TagParser::KnownField::Rating, TagParser::TagValue(ConvertToPOPMRating(song.rating())));
|
SaveSongRatingToFile(tag, song);
|
||||||
}
|
}
|
||||||
|
|
||||||
taginfo.applyChanges(diag, progress);
|
taginfo.applyChanges(diag, progress);
|
||||||
taginfo.close();
|
taginfo.close();
|
||||||
|
|
||||||
|
|||||||
@@ -22,6 +22,8 @@
|
|||||||
|
|
||||||
#include <string>
|
#include <string>
|
||||||
|
|
||||||
|
#include <tagparser/tag.h>
|
||||||
|
|
||||||
#include <QByteArray>
|
#include <QByteArray>
|
||||||
#include <QString>
|
#include <QString>
|
||||||
|
|
||||||
@@ -40,14 +42,20 @@ class TagReaderTagParser : public TagReaderBase {
|
|||||||
bool IsMediaFile(const QString &filename) const override;
|
bool IsMediaFile(const QString &filename) const override;
|
||||||
|
|
||||||
bool ReadFile(const QString &filename, spb::tagreader::SongMetadata *song) const override;
|
bool ReadFile(const QString &filename, spb::tagreader::SongMetadata *song) const override;
|
||||||
bool SaveFile(const QString &filename, const spb::tagreader::SongMetadata &song) const override;
|
bool SaveFile(const spb::tagreader::SaveFileRequest &request) const override;
|
||||||
|
|
||||||
QByteArray LoadEmbeddedArt(const QString &filename) const override;
|
QByteArray LoadEmbeddedArt(const QString &filename) const override;
|
||||||
bool SaveEmbeddedArt(const QString &filename, const QByteArray &data) override;
|
bool SaveEmbeddedArt(const spb::tagreader::SaveEmbeddedArtRequest &request) const override;
|
||||||
|
|
||||||
bool SaveSongPlaycountToFile(const QString &filename, const spb::tagreader::SongMetadata &song) const override;
|
bool SaveSongPlaycountToFile(const QString &filename, const spb::tagreader::SongMetadata &song) const override;
|
||||||
bool SaveSongRatingToFile(const QString &filename, const spb::tagreader::SongMetadata &song) const override;
|
bool SaveSongRatingToFile(const QString &filename, const spb::tagreader::SongMetadata &song) const override;
|
||||||
|
|
||||||
|
private:
|
||||||
|
void SaveSongPlaycountToFile(TagParser::Tag *tag, const spb::tagreader::SongMetadata &song) const;
|
||||||
|
void SaveSongRatingToFile(TagParser::Tag *tag, const spb::tagreader::SongMetadata &song) const;
|
||||||
|
void SaveEmbeddedArt(TagParser::Tag *tag, const QByteArray &data) const;
|
||||||
|
|
||||||
|
public:
|
||||||
Q_DISABLE_COPY(TagReaderTagParser)
|
Q_DISABLE_COPY(TagReaderTagParser)
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -91,7 +91,7 @@ int main(int argc, char **argv) {
|
|||||||
qLog(Error) << "First line" << first_line << "does not match" << filepath;
|
qLog(Error) << "First line" << first_line << "does not match" << filepath;
|
||||||
success = false;
|
success = false;
|
||||||
}
|
}
|
||||||
QRegularExpression regexp(QStringLiteral("^\\t(.+) \\(compatibility version (\\d+\\.\\d+\\.\\d+), current version (\\d+\\.\\d+\\.\\d+)(, weak)?\\)$"));
|
QRegularExpression regexp(QStringLiteral("^\\t(.+) \\(compatibility version (\\d+\\.\\d+\\.\\d+), current version (\\d+\\.\\d+\\.\\d+)(, weak|, reexport)?\\)$"));
|
||||||
for (const QString &output_line : output_lines) {
|
for (const QString &output_line : output_lines) {
|
||||||
|
|
||||||
//qDebug() << "Final check on" << filepath << output_line;
|
//qDebug() << "Final check on" << filepath << output_line;
|
||||||
@@ -130,9 +130,6 @@ int main(int argc, char **argv) {
|
|||||||
else if (library.startsWith("/System/Library/") || library.startsWith("/usr/lib/")) { // System library
|
else if (library.startsWith("/System/Library/") || library.startsWith("/usr/lib/")) { // System library
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
else if (library.endsWith("libgcc_s.1.dylib")) { // fftw points to it for some reason.
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
else {
|
else {
|
||||||
qLog(Error) << "File" << filepath << "points to" << library;
|
qLog(Error) << "File" << filepath << "points to" << library;
|
||||||
success = false;
|
success = false;
|
||||||
@@ -140,7 +137,7 @@ int main(int argc, char **argv) {
|
|||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
qLog(Error) << "Could not parse otool output line:" << output_line;
|
qLog(Error) << "Could not parse otool output line:" << output_line;
|
||||||
continue;
|
success = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -56,36 +56,41 @@ void TagReaderWorker::DeviceClosed() {
|
|||||||
bool TagReaderWorker::HandleMessage(const spb::tagreader::Message &message, spb::tagreader::Message &reply, TagReaderBase *reader) {
|
bool TagReaderWorker::HandleMessage(const spb::tagreader::Message &message, spb::tagreader::Message &reply, TagReaderBase *reader) {
|
||||||
|
|
||||||
if (message.has_is_media_file_request()) {
|
if (message.has_is_media_file_request()) {
|
||||||
bool success = reader->IsMediaFile(QStringFromStdString(message.is_media_file_request().filename()));
|
const QString filename = QString::fromUtf8(message.is_media_file_request().filename().data(), message.is_media_file_request().filename().size());
|
||||||
|
bool success = reader->IsMediaFile(filename);
|
||||||
reply.mutable_is_media_file_response()->set_success(success);
|
reply.mutable_is_media_file_response()->set_success(success);
|
||||||
return success;
|
return success;
|
||||||
}
|
}
|
||||||
else if (message.has_read_file_request()) {
|
else if (message.has_read_file_request()) {
|
||||||
bool success = reader->ReadFile(QStringFromStdString(message.read_file_request().filename()), reply.mutable_read_file_response()->mutable_metadata());
|
const QString filename = QString::fromUtf8(message.read_file_request().filename().data(), message.read_file_request().filename().size());
|
||||||
|
bool success = reader->ReadFile(filename, reply.mutable_read_file_response()->mutable_metadata());
|
||||||
return success;
|
return success;
|
||||||
}
|
}
|
||||||
else if (message.has_save_file_request()) {
|
else if (message.has_save_file_request()) {
|
||||||
bool success = reader->SaveFile(QStringFromStdString(message.save_file_request().filename()), message.save_file_request().metadata());
|
bool success = reader->SaveFile(message.save_file_request());
|
||||||
reply.mutable_save_file_response()->set_success(success);
|
reply.mutable_save_file_response()->set_success(success);
|
||||||
return success;
|
return success;
|
||||||
}
|
}
|
||||||
else if (message.has_load_embedded_art_request()) {
|
else if (message.has_load_embedded_art_request()) {
|
||||||
QByteArray data = reader->LoadEmbeddedArt(QStringFromStdString(message.load_embedded_art_request().filename()));
|
const QString filename = QString::fromUtf8(message.load_embedded_art_request().filename().data(), message.load_embedded_art_request().filename().size());
|
||||||
|
QByteArray data = reader->LoadEmbeddedArt(filename);
|
||||||
reply.mutable_load_embedded_art_response()->set_data(data.constData(), data.size());
|
reply.mutable_load_embedded_art_response()->set_data(data.constData(), data.size());
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
else if (message.has_save_embedded_art_request()) {
|
else if (message.has_save_embedded_art_request()) {
|
||||||
bool success = reader->SaveEmbeddedArt(QStringFromStdString(message.save_embedded_art_request().filename()), QByteArray(message.save_embedded_art_request().data().data(), static_cast<qint64>(message.save_embedded_art_request().data().size())));
|
bool success = reader->SaveEmbeddedArt(message.save_embedded_art_request());
|
||||||
reply.mutable_save_embedded_art_response()->set_success(success);
|
reply.mutable_save_embedded_art_response()->set_success(success);
|
||||||
return success;
|
return success;
|
||||||
}
|
}
|
||||||
else if (message.has_save_song_playcount_to_file_request()) {
|
else if (message.has_save_song_playcount_to_file_request()) {
|
||||||
bool success = reader->SaveSongPlaycountToFile(QStringFromStdString(message.save_song_playcount_to_file_request().filename()), message.save_song_playcount_to_file_request().metadata());
|
const QString filename = QString::fromUtf8(message.save_song_playcount_to_file_request().filename().data(), message.save_song_playcount_to_file_request().filename().size());
|
||||||
|
bool success = reader->SaveSongPlaycountToFile(filename, message.save_song_playcount_to_file_request().metadata());
|
||||||
reply.mutable_save_song_playcount_to_file_response()->set_success(success);
|
reply.mutable_save_song_playcount_to_file_response()->set_success(success);
|
||||||
return success;
|
return success;
|
||||||
}
|
}
|
||||||
else if (message.has_save_song_rating_to_file_request()) {
|
else if (message.has_save_song_rating_to_file_request()) {
|
||||||
bool success = reader->SaveSongRatingToFile(QStringFromStdString(message.save_song_rating_to_file_request().filename()), message.save_song_rating_to_file_request().metadata());
|
const QString filename = QString::fromUtf8(message.save_song_rating_to_file_request().filename().data(), message.save_song_rating_to_file_request().filename().size());
|
||||||
|
bool success = reader->SaveSongRatingToFile(filename, message.save_song_rating_to_file_request().metadata());
|
||||||
reply.mutable_save_song_rating_to_file_response()->set_success(success);
|
reply.mutable_save_song_rating_to_file_response()->set_success(success);
|
||||||
return success;
|
return success;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,7 +7,6 @@ endif()
|
|||||||
set(SOURCES
|
set(SOURCES
|
||||||
core/mainwindow.cpp
|
core/mainwindow.cpp
|
||||||
core/application.cpp
|
core/application.cpp
|
||||||
core/appearance.cpp
|
|
||||||
core/player.cpp
|
core/player.cpp
|
||||||
core/commandlineoptions.cpp
|
core/commandlineoptions.cpp
|
||||||
core/database.cpp
|
core/database.cpp
|
||||||
@@ -35,14 +34,29 @@ set(SOURCES
|
|||||||
core/taskmanager.cpp
|
core/taskmanager.cpp
|
||||||
core/thread.cpp
|
core/thread.cpp
|
||||||
core/urlhandler.cpp
|
core/urlhandler.cpp
|
||||||
core/utilities.cpp
|
|
||||||
core/imageutils.cpp
|
|
||||||
core/iconloader.cpp
|
core/iconloader.cpp
|
||||||
core/standarditemiconloader.cpp
|
core/standarditemiconloader.cpp
|
||||||
core/scopedtransaction.cpp
|
core/scopedtransaction.cpp
|
||||||
core/translations.cpp
|
core/translations.cpp
|
||||||
core/systemtrayicon.cpp
|
core/systemtrayicon.cpp
|
||||||
|
|
||||||
|
utilities/strutils.cpp
|
||||||
|
utilities/envutils.cpp
|
||||||
|
utilities/colorutils.cpp
|
||||||
|
utilities/cryptutils.cpp
|
||||||
|
utilities/fileutils.cpp
|
||||||
|
utilities/diskutils.cpp
|
||||||
|
utilities/imageutils.cpp
|
||||||
|
utilities/macaddrutils.cpp
|
||||||
|
utilities/mimeutils.cpp
|
||||||
|
utilities/randutils.cpp
|
||||||
|
utilities/threadutils.cpp
|
||||||
|
utilities/timeutils.cpp
|
||||||
|
utilities/transliterate.cpp
|
||||||
|
utilities/xmlutils.cpp
|
||||||
|
utilities/filemanagerutils.cpp
|
||||||
|
utilities/coverutils.cpp
|
||||||
|
|
||||||
engine/enginetype.cpp
|
engine/enginetype.cpp
|
||||||
engine/enginebase.cpp
|
engine/enginebase.cpp
|
||||||
engine/devicefinders.cpp
|
engine/devicefinders.cpp
|
||||||
@@ -54,6 +68,7 @@ set(SOURCES
|
|||||||
analyzer/blockanalyzer.cpp
|
analyzer/blockanalyzer.cpp
|
||||||
analyzer/boomanalyzer.cpp
|
analyzer/boomanalyzer.cpp
|
||||||
analyzer/rainbowanalyzer.cpp
|
analyzer/rainbowanalyzer.cpp
|
||||||
|
analyzer/sonogram.cpp
|
||||||
|
|
||||||
equalizer/equalizer.cpp
|
equalizer/equalizer.cpp
|
||||||
equalizer/equalizerslider.cpp
|
equalizer/equalizerslider.cpp
|
||||||
@@ -69,9 +84,11 @@ set(SOURCES
|
|||||||
collection/collectionitemdelegate.cpp
|
collection/collectionitemdelegate.cpp
|
||||||
collection/collectionviewcontainer.cpp
|
collection/collectionviewcontainer.cpp
|
||||||
collection/collectiondirectorymodel.cpp
|
collection/collectiondirectorymodel.cpp
|
||||||
|
collection/collectionfilteroptions.cpp
|
||||||
collection/collectionfilterwidget.cpp
|
collection/collectionfilterwidget.cpp
|
||||||
collection/collectionplaylistitem.cpp
|
collection/collectionplaylistitem.cpp
|
||||||
collection/collectionquery.cpp
|
collection/collectionquery.cpp
|
||||||
|
collection/collectionqueryoptions.cpp
|
||||||
collection/savedgroupingmanager.cpp
|
collection/savedgroupingmanager.cpp
|
||||||
collection/groupbydialog.cpp
|
collection/groupbydialog.cpp
|
||||||
collection/collectiontask.cpp
|
collection/collectiontask.cpp
|
||||||
@@ -152,6 +169,8 @@ set(SOURCES
|
|||||||
|
|
||||||
lyrics/lyricsproviders.cpp
|
lyrics/lyricsproviders.cpp
|
||||||
lyrics/lyricsprovider.cpp
|
lyrics/lyricsprovider.cpp
|
||||||
|
lyrics/lyricssearchrequest.h
|
||||||
|
lyrics/lyricssearchresult.h
|
||||||
lyrics/lyricsfetcher.cpp
|
lyrics/lyricsfetcher.cpp
|
||||||
lyrics/lyricsfetchersearch.cpp
|
lyrics/lyricsfetchersearch.cpp
|
||||||
lyrics/jsonlyricsprovider.cpp
|
lyrics/jsonlyricsprovider.cpp
|
||||||
@@ -161,6 +180,9 @@ set(SOURCES
|
|||||||
lyrics/geniuslyricsprovider.cpp
|
lyrics/geniuslyricsprovider.cpp
|
||||||
lyrics/musixmatchlyricsprovider.cpp
|
lyrics/musixmatchlyricsprovider.cpp
|
||||||
lyrics/chartlyricsprovider.cpp
|
lyrics/chartlyricsprovider.cpp
|
||||||
|
lyrics/lyricscomlyricsprovider.cpp
|
||||||
|
|
||||||
|
providers/musixmatchprovider.cpp
|
||||||
|
|
||||||
settings/settingsdialog.cpp
|
settings/settingsdialog.cpp
|
||||||
settings/settingspage.cpp
|
settings/settingspage.cpp
|
||||||
@@ -203,6 +225,8 @@ set(SOURCES
|
|||||||
widgets/multiloadingindicator.cpp
|
widgets/multiloadingindicator.cpp
|
||||||
widgets/playingwidget.cpp
|
widgets/playingwidget.cpp
|
||||||
widgets/renametablineedit.cpp
|
widgets/renametablineedit.cpp
|
||||||
|
widgets/sliderslider.cpp
|
||||||
|
widgets/prettyslider.cpp
|
||||||
widgets/volumeslider.cpp
|
widgets/volumeslider.cpp
|
||||||
widgets/stickyslider.cpp
|
widgets/stickyslider.cpp
|
||||||
widgets/stretchheaderview.cpp
|
widgets/stretchheaderview.cpp
|
||||||
@@ -246,6 +270,7 @@ set(SOURCES
|
|||||||
scrobbler/scrobblerservice.cpp
|
scrobbler/scrobblerservice.cpp
|
||||||
scrobbler/scrobblercache.cpp
|
scrobbler/scrobblercache.cpp
|
||||||
scrobbler/scrobblercacheitem.cpp
|
scrobbler/scrobblercacheitem.cpp
|
||||||
|
scrobbler/scrobblemetadata.cpp
|
||||||
scrobbler/scrobblingapi20.cpp
|
scrobbler/scrobblingapi20.cpp
|
||||||
scrobbler/lastfmscrobbler.cpp
|
scrobbler/lastfmscrobbler.cpp
|
||||||
scrobbler/librefmscrobbler.cpp
|
scrobbler/librefmscrobbler.cpp
|
||||||
@@ -262,7 +287,6 @@ set(SOURCES
|
|||||||
set(HEADERS
|
set(HEADERS
|
||||||
core/mainwindow.h
|
core/mainwindow.h
|
||||||
core/application.h
|
core/application.h
|
||||||
core/appearance.h
|
|
||||||
core/player.h
|
core/player.h
|
||||||
core/database.h
|
core/database.h
|
||||||
core/deletefiles.h
|
core/deletefiles.h
|
||||||
@@ -292,6 +316,7 @@ set(HEADERS
|
|||||||
analyzer/blockanalyzer.h
|
analyzer/blockanalyzer.h
|
||||||
analyzer/boomanalyzer.h
|
analyzer/boomanalyzer.h
|
||||||
analyzer/rainbowanalyzer.h
|
analyzer/rainbowanalyzer.h
|
||||||
|
analyzer/sonogram.h
|
||||||
|
|
||||||
equalizer/equalizer.h
|
equalizer/equalizer.h
|
||||||
equalizer/equalizerslider.h
|
equalizer/equalizerslider.h
|
||||||
@@ -393,6 +418,7 @@ set(HEADERS
|
|||||||
lyrics/geniuslyricsprovider.h
|
lyrics/geniuslyricsprovider.h
|
||||||
lyrics/musixmatchlyricsprovider.h
|
lyrics/musixmatchlyricsprovider.h
|
||||||
lyrics/chartlyricsprovider.h
|
lyrics/chartlyricsprovider.h
|
||||||
|
lyrics/lyricscomlyricsprovider.h
|
||||||
|
|
||||||
settings/settingsdialog.h
|
settings/settingsdialog.h
|
||||||
settings/settingspage.h
|
settings/settingspage.h
|
||||||
@@ -434,6 +460,8 @@ set(HEADERS
|
|||||||
widgets/multiloadingindicator.h
|
widgets/multiloadingindicator.h
|
||||||
widgets/playingwidget.h
|
widgets/playingwidget.h
|
||||||
widgets/renametablineedit.h
|
widgets/renametablineedit.h
|
||||||
|
widgets/sliderslider.h
|
||||||
|
widgets/prettyslider.h
|
||||||
widgets/volumeslider.h
|
widgets/volumeslider.h
|
||||||
widgets/stickyslider.h
|
widgets/stickyslider.h
|
||||||
widgets/stretchheaderview.h
|
widgets/stretchheaderview.h
|
||||||
@@ -444,7 +472,6 @@ set(HEADERS
|
|||||||
widgets/qsearchfield.h
|
widgets/qsearchfield.h
|
||||||
widgets/ratingwidget.h
|
widgets/ratingwidget.h
|
||||||
widgets/forcescrollperpixel.h
|
widgets/forcescrollperpixel.h
|
||||||
widgets/resizabletextedit.h
|
|
||||||
|
|
||||||
osd/osdbase.h
|
osd/osdbase.h
|
||||||
osd/osdpretty.h
|
osd/osdpretty.h
|
||||||
@@ -476,7 +503,6 @@ set(HEADERS
|
|||||||
scrobbler/scrobblerservices.h
|
scrobbler/scrobblerservices.h
|
||||||
scrobbler/scrobblerservice.h
|
scrobbler/scrobblerservice.h
|
||||||
scrobbler/scrobblercache.h
|
scrobbler/scrobblercache.h
|
||||||
scrobbler/scrobblercacheitem.h
|
|
||||||
scrobbler/scrobblingapi20.h
|
scrobbler/scrobblingapi20.h
|
||||||
scrobbler/lastfmscrobbler.h
|
scrobbler/lastfmscrobbler.h
|
||||||
scrobbler/librefmscrobbler.h
|
scrobbler/librefmscrobbler.h
|
||||||
@@ -570,7 +596,7 @@ option(USE_INSTALL_PREFIX "Look for data in CMAKE_INSTALL_PREFIX" ON)
|
|||||||
|
|
||||||
if(NOT APPLE)
|
if(NOT APPLE)
|
||||||
set(NOT_APPLE ON)
|
set(NOT_APPLE ON)
|
||||||
optional_source(NOT_APPLE SOURCES widgets/qsearchfield_nonmac.cpp core/qtsystemtrayicon.cpp HEADERS core/qtsystemtrayicon.h)
|
optional_source(NOT_APPLE SOURCES widgets/qsearchfield_qt.cpp core/qtsystemtrayicon.cpp HEADERS core/qtsystemtrayicon.h)
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
if(HAVE_GLOBALSHORTCUTS)
|
if(HAVE_GLOBALSHORTCUTS)
|
||||||
@@ -610,43 +636,42 @@ optional_source(HAVE_VLC SOURCES engine/vlcengine.cpp HEADERS engine/vlcengine.h
|
|||||||
|
|
||||||
# DBUS and MPRIS - Unix specific
|
# DBUS and MPRIS - Unix specific
|
||||||
if(UNIX AND HAVE_DBUS)
|
if(UNIX AND HAVE_DBUS)
|
||||||
file(MAKE_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/dbus)
|
|
||||||
|
|
||||||
optional_source(HAVE_DBUS SOURCES core/mpris2.cpp HEADERS core/mpris2.h)
|
optional_source(HAVE_DBUS SOURCES core/mpris2.cpp HEADERS core/mpris2.h)
|
||||||
optional_source(HAVE_UDISKS2 SOURCES device/udisks2lister.cpp HEADERS device/udisks2lister.h)
|
optional_source(HAVE_UDISKS2 SOURCES device/udisks2lister.cpp HEADERS device/udisks2lister.h)
|
||||||
|
|
||||||
# MPRIS 2.0 DBUS interfaces
|
# MPRIS 2.0 DBUS interfaces
|
||||||
qt_add_dbus_adaptor(SOURCES dbus/org.mpris.MediaPlayer2.Player.xml core/mpris2.h mpris::Mpris2 core/mpris2_player Mpris2Player)
|
qt_add_dbus_adaptor(SOURCES dbus/org.mpris.MediaPlayer2.Player.xml core/mpris2.h mpris::Mpris2 mpris2_player Mpris2Player)
|
||||||
qt_add_dbus_adaptor(SOURCES dbus/org.mpris.MediaPlayer2.xml core/mpris2.h mpris::Mpris2 core/mpris2_root Mpris2Root)
|
qt_add_dbus_adaptor(SOURCES dbus/org.mpris.MediaPlayer2.xml core/mpris2.h mpris::Mpris2 mpris2_root Mpris2Root)
|
||||||
qt_add_dbus_adaptor(SOURCES dbus/org.mpris.MediaPlayer2.TrackList.xml core/mpris2.h mpris::Mpris2 core/mpris2_tracklist Mpris2TrackList)
|
qt_add_dbus_adaptor(SOURCES dbus/org.mpris.MediaPlayer2.TrackList.xml core/mpris2.h mpris::Mpris2 mpris2_tracklist Mpris2TrackList)
|
||||||
|
|
||||||
# MPRIS 2.1 DBUS interfaces
|
# MPRIS 2.1 DBUS interfaces
|
||||||
qt_add_dbus_adaptor(SOURCES dbus/org.mpris.MediaPlayer2.Playlists.xml core/mpris2.h mpris::Mpris2 core/mpris2_playlists Mpris2Playlists)
|
qt_add_dbus_adaptor(SOURCES dbus/org.mpris.MediaPlayer2.Playlists.xml core/mpris2.h mpris::Mpris2 mpris2_playlists Mpris2Playlists)
|
||||||
|
|
||||||
# org.freedesktop.Notifications DBUS interface
|
# org.freedesktop.Notifications DBUS interface
|
||||||
qt_add_dbus_interface(SOURCES dbus/org.freedesktop.Notifications.xml dbus/notification)
|
qt_add_dbus_interface(SOURCES dbus/org.freedesktop.Notifications.xml notification)
|
||||||
|
|
||||||
# org.gnome.SettingsDaemon interface
|
# org.gnome.SettingsDaemon interface
|
||||||
qt_add_dbus_interface(SOURCES dbus/org.gnome.SettingsDaemon.MediaKeys.xml dbus/gnomesettingsdaemon)
|
qt_add_dbus_interface(SOURCES dbus/org.gnome.SettingsDaemon.MediaKeys.xml gnomesettingsdaemon)
|
||||||
|
|
||||||
# org.mate.SettingsDaemon interface
|
# org.mate.SettingsDaemon interface
|
||||||
qt_add_dbus_interface(SOURCES dbus/org.mate.SettingsDaemon.MediaKeys.xml dbus/matesettingsdaemon)
|
qt_add_dbus_interface(SOURCES dbus/org.mate.SettingsDaemon.MediaKeys.xml matesettingsdaemon)
|
||||||
|
|
||||||
# org.kde.KGlobalAccel interface
|
# org.kde.KGlobalAccel interface
|
||||||
qt_add_dbus_interface(SOURCES dbus/org.kde.KGlobalAccel.xml dbus/kglobalaccel)
|
qt_add_dbus_interface(SOURCES dbus/org.kde.KGlobalAccel.xml kglobalaccel)
|
||||||
qt_add_dbus_interface(SOURCES dbus/org.kde.KGlobalAccel.Component.xml dbus/kglobalaccelcomponent)
|
qt_add_dbus_interface(SOURCES dbus/org.kde.KGlobalAccel.Component.xml kglobalaccelcomponent)
|
||||||
|
|
||||||
if(HAVE_UDISKS2)
|
if(HAVE_UDISKS2)
|
||||||
set_source_files_properties(dbus/org.freedesktop.DBus.ObjectManager.xml PROPERTIES NO_NAMESPACE dbus/objectmanager INCLUDE dbus/metatypes.h)
|
set_source_files_properties(dbus/org.freedesktop.DBus.ObjectManager.xml PROPERTIES NO_NAMESPACE objectmanager INCLUDE dbus/metatypes.h)
|
||||||
set_source_files_properties(dbus/org.freedesktop.UDisks2.Filesystem.xml PROPERTIES NO_NAMESPACE dbus/udisks2filesystem INCLUDE dbus/metatypes.h)
|
set_source_files_properties(dbus/org.freedesktop.UDisks2.Filesystem.xml PROPERTIES NO_NAMESPACE udisks2filesystem INCLUDE dbus/metatypes.h)
|
||||||
set_source_files_properties(dbus/org.freedesktop.UDisks2.Block.xml PROPERTIES NO_NAMESPACE dbus/udisks2block INCLUDE dbus/metatypes.h)
|
set_source_files_properties(dbus/org.freedesktop.UDisks2.Block.xml PROPERTIES NO_NAMESPACE udisks2block INCLUDE dbus/metatypes.h)
|
||||||
set_source_files_properties(dbus/org.freedesktop.UDisks2.Drive.xml PROPERTIES NO_NAMESPACE dbus/udisks2drive INCLUDE dbus/metatypes.h)
|
set_source_files_properties(dbus/org.freedesktop.UDisks2.Drive.xml PROPERTIES NO_NAMESPACE udisks2drive INCLUDE dbus/metatypes.h)
|
||||||
set_source_files_properties(dbus/org.freedesktop.UDisks2.Job.xml PROPERTIES NO_NAMESPACE dbus/udisks2job INCLUDE dbus/metatypes.h)
|
set_source_files_properties(dbus/org.freedesktop.UDisks2.Job.xml PROPERTIES NO_NAMESPACE udisks2job INCLUDE dbus/metatypes.h)
|
||||||
qt_add_dbus_interface(SOURCES dbus/org.freedesktop.DBus.ObjectManager.xml dbus/objectmanager)
|
qt_add_dbus_interface(SOURCES dbus/org.freedesktop.DBus.ObjectManager.xml objectmanager)
|
||||||
qt_add_dbus_interface(SOURCES dbus/org.freedesktop.UDisks2.Filesystem.xml dbus/udisks2filesystem)
|
qt_add_dbus_interface(SOURCES dbus/org.freedesktop.UDisks2.Filesystem.xml udisks2filesystem)
|
||||||
qt_add_dbus_interface(SOURCES dbus/org.freedesktop.UDisks2.Block.xml dbus/udisks2block)
|
qt_add_dbus_interface(SOURCES dbus/org.freedesktop.UDisks2.Block.xml udisks2block)
|
||||||
qt_add_dbus_interface(SOURCES dbus/org.freedesktop.UDisks2.Drive.xml dbus/udisks2drive)
|
qt_add_dbus_interface(SOURCES dbus/org.freedesktop.UDisks2.Drive.xml udisks2drive)
|
||||||
qt_add_dbus_interface(SOURCES dbus/org.freedesktop.UDisks2.Job.xml dbus/udisks2job)
|
qt_add_dbus_interface(SOURCES dbus/org.freedesktop.UDisks2.Job.xml udisks2job)
|
||||||
endif(HAVE_UDISKS2)
|
endif(HAVE_UDISKS2)
|
||||||
|
|
||||||
endif(UNIX AND HAVE_DBUS)
|
endif(UNIX AND HAVE_DBUS)
|
||||||
@@ -782,8 +807,8 @@ optional_source(HAVE_AUDIOCD
|
|||||||
# Platform specific - macOS
|
# Platform specific - macOS
|
||||||
optional_source(APPLE
|
optional_source(APPLE
|
||||||
SOURCES
|
SOURCES
|
||||||
|
utilities/macosutils.mm
|
||||||
core/scoped_nsautorelease_pool.mm
|
core/scoped_nsautorelease_pool.mm
|
||||||
core/mac_utilities.mm
|
|
||||||
core/mac_startup.mm
|
core/mac_startup.mm
|
||||||
core/macsystemtrayicon.mm
|
core/macsystemtrayicon.mm
|
||||||
core/macfslistener.mm
|
core/macfslistener.mm
|
||||||
@@ -804,8 +829,10 @@ optional_source(APPLE
|
|||||||
# Platform specific - Windows
|
# Platform specific - Windows
|
||||||
optional_source(WIN32
|
optional_source(WIN32
|
||||||
SOURCES
|
SOURCES
|
||||||
|
utilities/winutils.cpp
|
||||||
engine/directsounddevicefinder.cpp
|
engine/directsounddevicefinder.cpp
|
||||||
engine/mmdevicefinder.cpp
|
engine/mmdevicefinder.cpp
|
||||||
|
core/scopedwchararray.cpp
|
||||||
core/windows7thumbbar.cpp
|
core/windows7thumbbar.cpp
|
||||||
HEADERS
|
HEADERS
|
||||||
core/windows7thumbbar.h
|
core/windows7thumbbar.h
|
||||||
@@ -942,6 +969,7 @@ link_directories(
|
|||||||
${GOBJECT_LIBRARY_DIRS}
|
${GOBJECT_LIBRARY_DIRS}
|
||||||
${GNUTLS_LIBRARY_DIRS}
|
${GNUTLS_LIBRARY_DIRS}
|
||||||
${SQLITE_LIBRARY_DIRS}
|
${SQLITE_LIBRARY_DIRS}
|
||||||
|
${PROTOBUF_LIBRARY_DIRS}
|
||||||
${SINGLEAPPLICATION_LIBRARY_DIRS}
|
${SINGLEAPPLICATION_LIBRARY_DIRS}
|
||||||
${SINGLECOREAPPLICATION_LIBRARY_DIRS}
|
${SINGLECOREAPPLICATION_LIBRARY_DIRS}
|
||||||
)
|
)
|
||||||
@@ -1061,6 +1089,7 @@ target_link_libraries(strawberry_lib PUBLIC
|
|||||||
${GNUTLS_LIBRARIES}
|
${GNUTLS_LIBRARIES}
|
||||||
${SQLITE_LIBRARIES}
|
${SQLITE_LIBRARIES}
|
||||||
${QT_LIBRARIES}
|
${QT_LIBRARIES}
|
||||||
|
${Protobuf_LIBRARIES}
|
||||||
${SINGLEAPPLICATION_LIBRARIES}
|
${SINGLEAPPLICATION_LIBRARIES}
|
||||||
${SINGLECOREAPPLICATION_LIBRARIES}
|
${SINGLECOREAPPLICATION_LIBRARIES}
|
||||||
libstrawberry-common
|
libstrawberry-common
|
||||||
|
|||||||
@@ -108,7 +108,7 @@ void Analyzer::Base::paintEvent(QPaintEvent *e) {
|
|||||||
p.fillRect(e->rect(), palette().color(QPalette::Window));
|
p.fillRect(e->rect(), palette().color(QPalette::Window));
|
||||||
|
|
||||||
switch (engine_->state()) {
|
switch (engine_->state()) {
|
||||||
case Engine::Playing: {
|
case Engine::State::Playing: {
|
||||||
const Engine::Scope &thescope = engine_->scope(timeout_);
|
const Engine::Scope &thescope = engine_->scope(timeout_);
|
||||||
int i = 0;
|
int i = 0;
|
||||||
|
|
||||||
@@ -126,7 +126,7 @@ void Analyzer::Base::paintEvent(QPaintEvent *e) {
|
|||||||
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case Engine::Paused:
|
case Engine::State::Paused:
|
||||||
is_playing_ = false;
|
is_playing_ = false;
|
||||||
analyze(p, lastscope_, new_frame_);
|
analyze(p, lastscope_, new_frame_);
|
||||||
break;
|
break;
|
||||||
|
|||||||
@@ -49,7 +49,7 @@ class QTimerEvent;
|
|||||||
|
|
||||||
namespace Analyzer {
|
namespace Analyzer {
|
||||||
|
|
||||||
typedef std::vector<float> Scope;
|
using Scope = std::vector<float>;
|
||||||
|
|
||||||
class Base : public QWidget {
|
class Base : public QWidget {
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
|
|||||||
@@ -40,6 +40,7 @@
|
|||||||
#include "blockanalyzer.h"
|
#include "blockanalyzer.h"
|
||||||
#include "boomanalyzer.h"
|
#include "boomanalyzer.h"
|
||||||
#include "rainbowanalyzer.h"
|
#include "rainbowanalyzer.h"
|
||||||
|
#include "sonogram.h"
|
||||||
|
|
||||||
#include "core/logging.h"
|
#include "core/logging.h"
|
||||||
#include "engine/enginebase.h"
|
#include "engine/enginebase.h"
|
||||||
@@ -85,6 +86,7 @@ AnalyzerContainer::AnalyzerContainer(QWidget *parent)
|
|||||||
AddAnalyzerType<BoomAnalyzer>();
|
AddAnalyzerType<BoomAnalyzer>();
|
||||||
AddAnalyzerType<Rainbow::NyanCatAnalyzer>();
|
AddAnalyzerType<Rainbow::NyanCatAnalyzer>();
|
||||||
AddAnalyzerType<Rainbow::RainbowDashAnalyzer>();
|
AddAnalyzerType<Rainbow::RainbowDashAnalyzer>();
|
||||||
|
AddAnalyzerType<Sonogram>();
|
||||||
|
|
||||||
disable_action_ = context_menu_->addAction(tr("No analyzer"), this, &AnalyzerContainer::DisableAnalyzer);
|
disable_action_ = context_menu_->addAction(tr("No analyzer"), this, &AnalyzerContainer::DisableAnalyzer);
|
||||||
disable_action_->setCheckable(true);
|
disable_action_->setCheckable(true);
|
||||||
|
|||||||
@@ -110,7 +110,7 @@ void BoomAnalyzer::transform(Scope &s) {
|
|||||||
|
|
||||||
void BoomAnalyzer::analyze(QPainter &p, const Scope &scope, const bool new_frame) {
|
void BoomAnalyzer::analyze(QPainter &p, const Scope &scope, const bool new_frame) {
|
||||||
|
|
||||||
if (!new_frame || engine_->state() == Engine::Paused) {
|
if (!new_frame || engine_->state() == Engine::State::Paused) {
|
||||||
p.drawPixmap(0, 0, canvas_);
|
p.drawPixmap(0, 0, canvas_);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|||||||
92
src/analyzer/sonogram.cpp
Normal file
92
src/analyzer/sonogram.cpp
Normal file
@@ -0,0 +1,92 @@
|
|||||||
|
/*
|
||||||
|
Strawberry Music Player
|
||||||
|
This file was part of Clementine.
|
||||||
|
Copyright 2004, Melchior FRANZ <mfranz@kde.org>
|
||||||
|
Copyright 2009-2010, David Sansome <davidsansome@gmail.com>
|
||||||
|
Copyright 2010, 2014, John Maguire <john.maguire@gmail.com>
|
||||||
|
Copyright 2014-2015, Mark Furneaux <mark@furneaux.ca>
|
||||||
|
Copyright 2014, Krzysztof Sobiecki <sobkas@gmail.com>
|
||||||
|
|
||||||
|
Strawberry is free software: you can redistribute it and/or modify
|
||||||
|
it under the terms of the GNU General Public License as published by
|
||||||
|
the Free Software Foundation, either version 3 of the License, or
|
||||||
|
(at your option) any later version.
|
||||||
|
|
||||||
|
Strawberry is distributed in the hope that it will be useful,
|
||||||
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
GNU General Public License for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU General Public License
|
||||||
|
along with Strawberry. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <QPainter>
|
||||||
|
#include <QResizeEvent>
|
||||||
|
|
||||||
|
#include "sonogram.h"
|
||||||
|
|
||||||
|
const char *Sonogram::kName = QT_TRANSLATE_NOOP("AnalyzerContainer", "Sonogram");
|
||||||
|
|
||||||
|
Sonogram::Sonogram(QWidget *parent)
|
||||||
|
: Analyzer::Base(parent, 9) {}
|
||||||
|
|
||||||
|
void Sonogram::resizeEvent(QResizeEvent *e) {
|
||||||
|
|
||||||
|
Q_UNUSED(e)
|
||||||
|
|
||||||
|
canvas_ = QPixmap(size());
|
||||||
|
canvas_.fill(palette().color(QPalette::Window));
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
void Sonogram::analyze(QPainter &p, const Analyzer::Scope &s, bool new_frame) {
|
||||||
|
|
||||||
|
if (!new_frame || engine_->state() == Engine::State::Paused) {
|
||||||
|
p.drawPixmap(0, 0, canvas_);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
QPainter canvas_painter(&canvas_);
|
||||||
|
canvas_painter.drawPixmap(0, 0, canvas_, 1, 0, width() - 1, -1);
|
||||||
|
|
||||||
|
Analyzer::Scope::const_iterator it = s.begin(), end = s.end();
|
||||||
|
|
||||||
|
for (int y = height() - 1; y;) {
|
||||||
|
QColor c;
|
||||||
|
if (it >= end || *it < .005) {
|
||||||
|
c = palette().color(QPalette::Window);
|
||||||
|
}
|
||||||
|
else if (*it < .05) {
|
||||||
|
c.setHsv(95, 255, 255 - static_cast<int>(*it * 4000.0));
|
||||||
|
}
|
||||||
|
else if (*it < 1.0) {
|
||||||
|
c.setHsv(95 - static_cast<int>(*it * 90.0), 255, 255);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
c = Qt::red;
|
||||||
|
}
|
||||||
|
|
||||||
|
canvas_painter.setPen(c);
|
||||||
|
canvas_painter.drawPoint(width() - 1, y--);
|
||||||
|
|
||||||
|
if (it < end) ++it;
|
||||||
|
}
|
||||||
|
|
||||||
|
canvas_painter.end();
|
||||||
|
|
||||||
|
p.drawPixmap(0, 0, canvas_);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
void Sonogram::transform(Analyzer::Scope &scope) {
|
||||||
|
|
||||||
|
fht_->power2(scope.data());
|
||||||
|
fht_->scale(scope.data(), 1.0 / 256);
|
||||||
|
scope.resize(fht_->size() / 2);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
void Sonogram::demo(QPainter &p) {
|
||||||
|
analyze(p, Analyzer::Scope(fht_->size(), 0), new_frame_);
|
||||||
|
}
|
||||||
49
src/analyzer/sonogram.h
Normal file
49
src/analyzer/sonogram.h
Normal file
@@ -0,0 +1,49 @@
|
|||||||
|
/*
|
||||||
|
Strawberry Music Player
|
||||||
|
This file was part of Clementine.
|
||||||
|
Copyright 2004, Melchior FRANZ <mfranz@kde.org>
|
||||||
|
Copyright 2009-2010, David Sansome <davidsansome@gmail.com>
|
||||||
|
Copyright 2014, Krzysztof Sobiecki <sobkas@gmail.com>
|
||||||
|
Copyright 2014, John Maguire <john.maguire@gmail.com>
|
||||||
|
Copyright 2015, Mark Furneaux <mark@furneaux.ca>
|
||||||
|
|
||||||
|
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 SONOGRAM_H
|
||||||
|
#define SONOGRAM_H
|
||||||
|
|
||||||
|
#include <QPixmap>
|
||||||
|
#include <QPainter>
|
||||||
|
|
||||||
|
#include "analyzerbase.h"
|
||||||
|
|
||||||
|
class Sonogram : public Analyzer::Base {
|
||||||
|
Q_OBJECT
|
||||||
|
public:
|
||||||
|
Q_INVOKABLE explicit Sonogram(QWidget *parent);
|
||||||
|
|
||||||
|
static const char *kName;
|
||||||
|
|
||||||
|
protected:
|
||||||
|
void resizeEvent(QResizeEvent *e) override;
|
||||||
|
void analyze(QPainter &p, const Analyzer::Scope &s, bool new_frame) override;
|
||||||
|
void transform(Analyzer::Scope &scope) override;
|
||||||
|
void demo(QPainter &p) override;
|
||||||
|
|
||||||
|
private:
|
||||||
|
QPixmap canvas_;
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif // SONOGRAM_H
|
||||||
@@ -34,9 +34,9 @@
|
|||||||
#include "core/player.h"
|
#include "core/player.h"
|
||||||
#include "core/tagreaderclient.h"
|
#include "core/tagreaderclient.h"
|
||||||
#include "core/thread.h"
|
#include "core/thread.h"
|
||||||
#include "core/utilities.h"
|
|
||||||
#include "core/song.h"
|
#include "core/song.h"
|
||||||
#include "core/logging.h"
|
#include "core/logging.h"
|
||||||
|
#include "utilities/threadutils.h"
|
||||||
#include "collection.h"
|
#include "collection.h"
|
||||||
#include "collectionwatcher.h"
|
#include "collectionwatcher.h"
|
||||||
#include "collectionbackend.h"
|
#include "collectionbackend.h"
|
||||||
@@ -58,8 +58,6 @@ SCollection::SCollection(Application *app, QObject *parent)
|
|||||||
watcher_(nullptr),
|
watcher_(nullptr),
|
||||||
watcher_thread_(nullptr),
|
watcher_thread_(nullptr),
|
||||||
original_thread_(nullptr),
|
original_thread_(nullptr),
|
||||||
io_priority_(Utilities::IoPriority::IOPRIO_CLASS_IDLE),
|
|
||||||
thread_priority_(QThread::Priority::IdlePriority),
|
|
||||||
save_playcounts_to_files_(false),
|
save_playcounts_to_files_(false),
|
||||||
save_ratings_to_files_(false) {
|
save_ratings_to_files_(false) {
|
||||||
|
|
||||||
@@ -69,7 +67,7 @@ SCollection::SCollection(Application *app, QObject *parent)
|
|||||||
backend()->moveToThread(app->database()->thread());
|
backend()->moveToThread(app->database()->thread());
|
||||||
qLog(Debug) << backend_ << "moved to thread" << app->database()->thread();
|
qLog(Debug) << backend_ << "moved to thread" << app->database()->thread();
|
||||||
|
|
||||||
backend_->Init(app->database(), app->task_manager(), Song::Source_Collection, kSongsTable, kFtsTable, kDirsTable, kSubdirsTable);
|
backend_->Init(app->database(), app->task_manager(), Song::Source::Collection, kSongsTable, kFtsTable, kDirsTable, kSubdirsTable);
|
||||||
|
|
||||||
model_ = new CollectionModel(backend_, app_, this);
|
model_ = new CollectionModel(backend_, app_, this);
|
||||||
|
|
||||||
@@ -93,20 +91,16 @@ SCollection::~SCollection() {
|
|||||||
|
|
||||||
void SCollection::Init() {
|
void SCollection::Init() {
|
||||||
|
|
||||||
watcher_ = new CollectionWatcher(Song::Source_Collection);
|
watcher_ = new CollectionWatcher(Song::Source::Collection);
|
||||||
watcher_thread_ = new Thread(this);
|
watcher_thread_ = new Thread(this);
|
||||||
|
|
||||||
#ifndef Q_OS_WIN32
|
watcher_thread_->SetIoPriority(Utilities::IoPriority::IOPRIO_CLASS_IDLE);
|
||||||
if (io_priority_ != Utilities::IoPriority::IOPRIO_CLASS_NONE) {
|
|
||||||
watcher_thread_->SetIoPriority(io_priority_);
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
|
|
||||||
watcher_->moveToThread(watcher_thread_);
|
watcher_->moveToThread(watcher_thread_);
|
||||||
|
|
||||||
qLog(Debug) << watcher_ << "moved to thread" << watcher_thread_ << "with I/O priority" << io_priority_ << "and thread priority" << thread_priority_;
|
qLog(Debug) << watcher_ << "moved to thread" << watcher_thread_;
|
||||||
|
|
||||||
watcher_thread_->start(thread_priority_);
|
watcher_thread_->start(QThread::IdlePriority);
|
||||||
|
|
||||||
watcher_->set_backend(backend_);
|
watcher_->set_backend(backend_);
|
||||||
watcher_->set_task_manager(app_->task_manager());
|
watcher_->set_task_manager(app_->task_manager());
|
||||||
@@ -169,7 +163,9 @@ void SCollection::AbortScan() { watcher_->Stop(); }
|
|||||||
void SCollection::Rescan(const SongList &songs) {
|
void SCollection::Rescan(const SongList &songs) {
|
||||||
|
|
||||||
qLog(Debug) << "Rescan" << songs.size() << "songs";
|
qLog(Debug) << "Rescan" << songs.size() << "songs";
|
||||||
if (!songs.isEmpty()) watcher_->RescanTracksAsync(songs);
|
if (!songs.isEmpty()) {
|
||||||
|
watcher_->RescanSongsAsync(songs);
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -184,8 +180,6 @@ void SCollection::ReloadSettings() {
|
|||||||
|
|
||||||
QSettings s;
|
QSettings s;
|
||||||
s.beginGroup(CollectionSettingsPage::kSettingsGroup);
|
s.beginGroup(CollectionSettingsPage::kSettingsGroup);
|
||||||
io_priority_ = static_cast<Utilities::IoPriority>(s.value("io_priority", Utilities::IOPRIO_CLASS_IDLE).toInt());
|
|
||||||
thread_priority_ = static_cast<QThread::Priority>(s.value("thread_priority", QThread::Priority::IdlePriority).toInt());
|
|
||||||
save_playcounts_to_files_ = s.value("save_playcounts", false).toBool();
|
save_playcounts_to_files_ = s.value("save_playcounts", false).toBool();
|
||||||
save_ratings_to_files_ = s.value("save_ratings", false).toBool();
|
save_ratings_to_files_ = s.value("save_ratings", false).toBool();
|
||||||
s.endGroup();
|
s.endGroup();
|
||||||
@@ -219,9 +213,9 @@ void SCollection::SyncPlaycountAndRatingToFiles() {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void SCollection::SongsPlaycountChanged(const SongList &songs) {
|
void SCollection::SongsPlaycountChanged(const SongList &songs, const bool save_tags) {
|
||||||
|
|
||||||
if (save_playcounts_to_files_) {
|
if (save_tags || save_playcounts_to_files_) {
|
||||||
app_->tag_reader_client()->UpdateSongsPlaycount(songs);
|
app_->tag_reader_client()->UpdateSongsPlaycount(songs);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -28,11 +28,10 @@
|
|||||||
#include <QList>
|
#include <QList>
|
||||||
#include <QHash>
|
#include <QHash>
|
||||||
#include <QString>
|
#include <QString>
|
||||||
#include <QThread>
|
|
||||||
|
|
||||||
#include "core/song.h"
|
#include "core/song.h"
|
||||||
#include "core/utilities.h"
|
|
||||||
|
|
||||||
|
class QThread;
|
||||||
class Application;
|
class Application;
|
||||||
class Thread;
|
class Thread;
|
||||||
class CollectionBackend;
|
class CollectionBackend;
|
||||||
@@ -78,7 +77,7 @@ class SCollection : public QObject {
|
|||||||
|
|
||||||
private slots:
|
private slots:
|
||||||
void ExitReceived();
|
void ExitReceived();
|
||||||
void SongsPlaycountChanged(const SongList &songs);
|
void SongsPlaycountChanged(const SongList &songs, const bool save_tags = false);
|
||||||
void SongsRatingChanged(const SongList &songs, const bool save_tags = false);
|
void SongsRatingChanged(const SongList &songs, const bool save_tags = false);
|
||||||
|
|
||||||
signals:
|
signals:
|
||||||
@@ -99,8 +98,6 @@ class SCollection : public QObject {
|
|||||||
|
|
||||||
QList<QObject*> wait_for_exit_;
|
QList<QObject*> wait_for_exit_;
|
||||||
|
|
||||||
Utilities::IoPriority io_priority_;
|
|
||||||
QThread::Priority thread_priority_;
|
|
||||||
bool save_playcounts_to_files_;
|
bool save_playcounts_to_files_;
|
||||||
bool save_ratings_to_files_;
|
bool save_ratings_to_files_;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -50,8 +50,9 @@
|
|||||||
#include "core/sqlrow.h"
|
#include "core/sqlrow.h"
|
||||||
#include "smartplaylists/smartplaylistsearch.h"
|
#include "smartplaylists/smartplaylistsearch.h"
|
||||||
|
|
||||||
#include "directory.h"
|
#include "collectiondirectory.h"
|
||||||
#include "collectionbackend.h"
|
#include "collectionbackend.h"
|
||||||
|
#include "collectionfilteroptions.h"
|
||||||
#include "collectionquery.h"
|
#include "collectionquery.h"
|
||||||
#include "collectiontask.h"
|
#include "collectiontask.h"
|
||||||
|
|
||||||
@@ -59,7 +60,7 @@ CollectionBackend::CollectionBackend(QObject *parent)
|
|||||||
: CollectionBackendInterface(parent),
|
: CollectionBackendInterface(parent),
|
||||||
db_(nullptr),
|
db_(nullptr),
|
||||||
task_manager_(nullptr),
|
task_manager_(nullptr),
|
||||||
source_(Song::Source_Unknown),
|
source_(Song::Source::Unknown),
|
||||||
original_thread_(nullptr) {
|
original_thread_(nullptr) {
|
||||||
|
|
||||||
original_thread_ = thread();
|
original_thread_ = thread();
|
||||||
@@ -139,18 +140,22 @@ void CollectionBackend::IncrementSkipCountAsync(const int id, const float progre
|
|||||||
QMetaObject::invokeMethod(this, "IncrementSkipCount", Qt::QueuedConnection, Q_ARG(int, id), Q_ARG(float, progress));
|
QMetaObject::invokeMethod(this, "IncrementSkipCount", Qt::QueuedConnection, Q_ARG(int, id), Q_ARG(float, progress));
|
||||||
}
|
}
|
||||||
|
|
||||||
void CollectionBackend::ResetStatisticsAsync(const int id) {
|
void CollectionBackend::ResetPlayStatisticsAsync(const int id, const bool save_tags) {
|
||||||
QMetaObject::invokeMethod(this, "ResetStatistics", Qt::QueuedConnection, Q_ARG(int, id));
|
QMetaObject::invokeMethod(this, "ResetPlayStatistics", Qt::QueuedConnection, Q_ARG(int, id), Q_ARG(bool, save_tags));
|
||||||
|
}
|
||||||
|
|
||||||
|
void CollectionBackend::ResetPlayStatisticsAsync(const QList<int> &id_list, const bool save_tags) {
|
||||||
|
QMetaObject::invokeMethod(this, "ResetPlayStatistics", Qt::QueuedConnection, Q_ARG(QList<int>, id_list), Q_ARG(bool, save_tags));
|
||||||
}
|
}
|
||||||
|
|
||||||
void CollectionBackend::LoadDirectories() {
|
void CollectionBackend::LoadDirectories() {
|
||||||
|
|
||||||
DirectoryList dirs = GetAllDirectories();
|
CollectionDirectoryList dirs = GetAllDirectories();
|
||||||
|
|
||||||
QMutexLocker l(db_->Mutex());
|
QMutexLocker l(db_->Mutex());
|
||||||
QSqlDatabase db(db_->Connect());
|
QSqlDatabase db(db_->Connect());
|
||||||
|
|
||||||
for (const Directory &dir : dirs) {
|
for (const CollectionDirectory &dir : dirs) {
|
||||||
emit DirectoryDiscovered(dir, SubdirsInDirectory(dir.id, db));
|
emit DirectoryDiscovered(dir, SubdirsInDirectory(dir.id, db));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -207,12 +212,12 @@ void CollectionBackend::ChangeDirPath(const int id, const QString &old_path, con
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
DirectoryList CollectionBackend::GetAllDirectories() {
|
CollectionDirectoryList CollectionBackend::GetAllDirectories() {
|
||||||
|
|
||||||
QMutexLocker l(db_->Mutex());
|
QMutexLocker l(db_->Mutex());
|
||||||
QSqlDatabase db(db_->Connect());
|
QSqlDatabase db(db_->Connect());
|
||||||
|
|
||||||
DirectoryList ret;
|
CollectionDirectoryList ret;
|
||||||
|
|
||||||
SqlQuery q(db);
|
SqlQuery q(db);
|
||||||
q.prepare(QString("SELECT ROWID, path FROM %1").arg(dirs_table_));
|
q.prepare(QString("SELECT ROWID, path FROM %1").arg(dirs_table_));
|
||||||
@@ -222,7 +227,7 @@ DirectoryList CollectionBackend::GetAllDirectories() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
while (q.next()) {
|
while (q.next()) {
|
||||||
Directory dir;
|
CollectionDirectory dir;
|
||||||
dir.id = q.value(0).toInt();
|
dir.id = q.value(0).toInt();
|
||||||
dir.path = q.value(1).toString();
|
dir.path = q.value(1).toString();
|
||||||
|
|
||||||
@@ -232,7 +237,7 @@ DirectoryList CollectionBackend::GetAllDirectories() {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
SubdirectoryList CollectionBackend::SubdirsInDirectory(const int id) {
|
CollectionSubdirectoryList CollectionBackend::SubdirsInDirectory(const int id) {
|
||||||
|
|
||||||
QMutexLocker l(db_->Mutex());
|
QMutexLocker l(db_->Mutex());
|
||||||
QSqlDatabase db = db_->Connect();
|
QSqlDatabase db = db_->Connect();
|
||||||
@@ -240,19 +245,19 @@ SubdirectoryList CollectionBackend::SubdirsInDirectory(const int id) {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
SubdirectoryList CollectionBackend::SubdirsInDirectory(const int id, QSqlDatabase &db) {
|
CollectionSubdirectoryList CollectionBackend::SubdirsInDirectory(const int id, QSqlDatabase &db) {
|
||||||
|
|
||||||
SqlQuery q(db);
|
SqlQuery 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_));
|
||||||
q.BindValue(":dir", id);
|
q.BindValue(":dir", id);
|
||||||
if (!q.Exec()) {
|
if (!q.Exec()) {
|
||||||
db_->ReportErrors(q);
|
db_->ReportErrors(q);
|
||||||
return SubdirectoryList();
|
return CollectionSubdirectoryList();
|
||||||
}
|
}
|
||||||
|
|
||||||
SubdirectoryList subdirs;
|
CollectionSubdirectoryList subdirs;
|
||||||
while (q.next()) {
|
while (q.next()) {
|
||||||
Subdirectory subdir;
|
CollectionSubdirectory subdir;
|
||||||
subdir.directory_id = id;
|
subdir.directory_id = id;
|
||||||
subdir.path = q.value(0).toString();
|
subdir.path = q.value(0).toString();
|
||||||
subdir.mtime = q.value(1).toLongLong();
|
subdir.mtime = q.value(1).toLongLong();
|
||||||
@@ -339,15 +344,15 @@ void CollectionBackend::AddDirectory(const QString &path) {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
Directory dir;
|
CollectionDirectory dir;
|
||||||
dir.path = canonical_path;
|
dir.path = canonical_path;
|
||||||
dir.id = q.lastInsertId().toInt();
|
dir.id = q.lastInsertId().toInt();
|
||||||
|
|
||||||
emit DirectoryDiscovered(dir, SubdirectoryList());
|
emit DirectoryDiscovered(dir, CollectionSubdirectoryList());
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void CollectionBackend::RemoveDirectory(const Directory &dir) {
|
void CollectionBackend::RemoveDirectory(const CollectionDirectory &dir) {
|
||||||
|
|
||||||
QMutexLocker l(db_->Mutex());
|
QMutexLocker l(db_->Mutex());
|
||||||
QSqlDatabase db(db_->Connect());
|
QSqlDatabase db(db_->Connect());
|
||||||
@@ -447,13 +452,13 @@ void CollectionBackend::SongPathChanged(const Song &song, const QFileInfo &new_f
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void CollectionBackend::AddOrUpdateSubdirs(const SubdirectoryList &subdirs) {
|
void CollectionBackend::AddOrUpdateSubdirs(const CollectionSubdirectoryList &subdirs) {
|
||||||
|
|
||||||
QMutexLocker l(db_->Mutex());
|
QMutexLocker l(db_->Mutex());
|
||||||
QSqlDatabase db(db_->Connect());
|
QSqlDatabase db(db_->Connect());
|
||||||
|
|
||||||
ScopedTransaction transaction(&db);
|
ScopedTransaction transaction(&db);
|
||||||
for (const Subdirectory &subdir : subdirs) {
|
for (const CollectionSubdirectory &subdir : subdirs) {
|
||||||
if (subdir.mtime == 0) {
|
if (subdir.mtime == 0) {
|
||||||
// Delete the subdirectory
|
// Delete the subdirectory
|
||||||
SqlQuery q(db);
|
SqlQuery q(db);
|
||||||
@@ -713,7 +718,7 @@ void CollectionBackend::UpdateSongsBySongID(const SongMap &new_songs) {
|
|||||||
|
|
||||||
Song old_song = old_songs[new_song.song_id()];
|
Song old_song = old_songs[new_song.song_id()];
|
||||||
|
|
||||||
if (!new_song.IsMetadataEqual(old_song)) { // Update existing song.
|
if (!new_song.IsAllMetadataEqual(old_song) || !new_song.IsFingerprintEqual(old_song)) { // Update existing song.
|
||||||
|
|
||||||
{
|
{
|
||||||
SqlQuery q(db);
|
SqlQuery q(db);
|
||||||
@@ -900,12 +905,12 @@ void CollectionBackend::MarkSongsUnavailable(const SongList &songs, const bool u
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
QStringList CollectionBackend::GetAll(const QString &column, const QueryOptions &opt) {
|
QStringList CollectionBackend::GetAll(const QString &column, const CollectionFilterOptions &filter_options) {
|
||||||
|
|
||||||
QMutexLocker l(db_->Mutex());
|
QMutexLocker l(db_->Mutex());
|
||||||
QSqlDatabase db(db_->Connect());
|
QSqlDatabase db(db_->Connect());
|
||||||
|
|
||||||
CollectionQuery query(db, songs_table_, fts_table_, opt);
|
CollectionQuery query(db, songs_table_, fts_table_, filter_options);
|
||||||
query.SetColumnSpec("DISTINCT " + column);
|
query.SetColumnSpec("DISTINCT " + column);
|
||||||
query.AddCompilationRequirement(false);
|
query.AddCompilationRequirement(false);
|
||||||
|
|
||||||
@@ -922,12 +927,12 @@ QStringList CollectionBackend::GetAll(const QString &column, const QueryOptions
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
QStringList CollectionBackend::GetAllArtists(const QueryOptions &opt) {
|
QStringList CollectionBackend::GetAllArtists(const CollectionFilterOptions &opt) {
|
||||||
|
|
||||||
return GetAll("artist", opt);
|
return GetAll("artist", opt);
|
||||||
}
|
}
|
||||||
|
|
||||||
QStringList CollectionBackend::GetAllArtistsWithAlbums(const QueryOptions &opt) {
|
QStringList CollectionBackend::GetAllArtistsWithAlbums(const CollectionFilterOptions &opt) {
|
||||||
|
|
||||||
QMutexLocker l(db_->Mutex());
|
QMutexLocker l(db_->Mutex());
|
||||||
QSqlDatabase db(db_->Connect());
|
QSqlDatabase db(db_->Connect());
|
||||||
@@ -967,15 +972,15 @@ QStringList CollectionBackend::GetAllArtistsWithAlbums(const QueryOptions &opt)
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
CollectionBackend::AlbumList CollectionBackend::GetAllAlbums(const QueryOptions &opt) {
|
CollectionBackend::AlbumList CollectionBackend::GetAllAlbums(const CollectionFilterOptions &opt) {
|
||||||
return GetAlbums(QString(), false, opt);
|
return GetAlbums(QString(), false, opt);
|
||||||
}
|
}
|
||||||
|
|
||||||
CollectionBackend::AlbumList CollectionBackend::GetAlbumsByArtist(const QString &artist, const QueryOptions &opt) {
|
CollectionBackend::AlbumList CollectionBackend::GetAlbumsByArtist(const QString &artist, const CollectionFilterOptions &opt) {
|
||||||
return GetAlbums(artist, false, opt);
|
return GetAlbums(artist, false, opt);
|
||||||
}
|
}
|
||||||
|
|
||||||
SongList CollectionBackend::GetArtistSongs(const QString &effective_albumartist, const QueryOptions &opt) {
|
SongList CollectionBackend::GetArtistSongs(const QString &effective_albumartist, const CollectionFilterOptions &opt) {
|
||||||
|
|
||||||
QSqlDatabase db(db_->Connect());
|
QSqlDatabase db(db_->Connect());
|
||||||
QMutexLocker l(db_->Mutex());
|
QMutexLocker l(db_->Mutex());
|
||||||
@@ -993,7 +998,7 @@ SongList CollectionBackend::GetArtistSongs(const QString &effective_albumartist,
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
SongList CollectionBackend::GetAlbumSongs(const QString &effective_albumartist, const QString &album, const QueryOptions &opt) {
|
SongList CollectionBackend::GetAlbumSongs(const QString &effective_albumartist, const QString &album, const CollectionFilterOptions &opt) {
|
||||||
|
|
||||||
QSqlDatabase db(db_->Connect());
|
QSqlDatabase db(db_->Connect());
|
||||||
QMutexLocker l(db_->Mutex());
|
QMutexLocker l(db_->Mutex());
|
||||||
@@ -1012,7 +1017,7 @@ SongList CollectionBackend::GetAlbumSongs(const QString &effective_albumartist,
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
SongList CollectionBackend::GetSongsByAlbum(const QString &album, const QueryOptions &opt) {
|
SongList CollectionBackend::GetSongsByAlbum(const QString &album, const CollectionFilterOptions &opt) {
|
||||||
|
|
||||||
QSqlDatabase db(db_->Connect());
|
QSqlDatabase db(db_->Connect());
|
||||||
QMutexLocker l(db_->Mutex());
|
QMutexLocker l(db_->Mutex());
|
||||||
@@ -1285,11 +1290,11 @@ SongList CollectionBackend::GetSongsByFingerprint(const QString &fingerprint) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
CollectionBackend::AlbumList CollectionBackend::GetCompilationAlbums(const QueryOptions &opt) {
|
CollectionBackend::AlbumList CollectionBackend::GetCompilationAlbums(const CollectionFilterOptions &opt) {
|
||||||
return GetAlbums(QString(), true, opt);
|
return GetAlbums(QString(), true, opt);
|
||||||
}
|
}
|
||||||
|
|
||||||
SongList CollectionBackend::GetCompilationSongs(const QString &album, const QueryOptions &opt) {
|
SongList CollectionBackend::GetCompilationSongs(const QString &album, const CollectionFilterOptions &opt) {
|
||||||
|
|
||||||
QMutexLocker l(db_->Mutex());
|
QMutexLocker l(db_->Mutex());
|
||||||
QSqlDatabase db(db_->Connect());
|
QSqlDatabase db(db_->Connect());
|
||||||
@@ -1314,10 +1319,6 @@ SongList CollectionBackend::GetCompilationSongs(const QString &album, const Quer
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Song::Source CollectionBackend::Source() const {
|
|
||||||
return source_;
|
|
||||||
}
|
|
||||||
|
|
||||||
void CollectionBackend::CompilationsNeedUpdating() {
|
void CollectionBackend::CompilationsNeedUpdating() {
|
||||||
|
|
||||||
QMutexLocker l(db_->Mutex());
|
QMutexLocker l(db_->Mutex());
|
||||||
@@ -1430,7 +1431,7 @@ bool CollectionBackend::UpdateCompilations(const QSqlDatabase &db, SongList &del
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
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 CollectionFilterOptions &opt) {
|
||||||
|
|
||||||
QMutexLocker l(db_->Mutex());
|
QMutexLocker l(db_->Mutex());
|
||||||
QSqlDatabase db(db_->Connect());
|
QSqlDatabase db(db_->Connect());
|
||||||
@@ -1520,7 +1521,7 @@ CollectionBackend::Album CollectionBackend::GetAlbumArt(const QString &effective
|
|||||||
ret.album = album;
|
ret.album = album;
|
||||||
ret.album_artist = effective_albumartist;
|
ret.album_artist = effective_albumartist;
|
||||||
|
|
||||||
CollectionQuery query(db, songs_table_, fts_table_, QueryOptions());
|
CollectionQuery query(db, songs_table_, fts_table_);
|
||||||
query.SetColumnSpec("art_automatic, art_manual, url");
|
query.SetColumnSpec("art_automatic, art_manual, url");
|
||||||
if (!effective_albumartist.isEmpty()) {
|
if (!effective_albumartist.isEmpty()) {
|
||||||
query.AddWhere("effective_albumartist", effective_albumartist);
|
query.AddWhere("effective_albumartist", effective_albumartist);
|
||||||
@@ -1779,23 +1780,48 @@ void CollectionBackend::IncrementSkipCount(const int id, const float progress) {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void CollectionBackend::ResetStatistics(const int id) {
|
void CollectionBackend::ResetPlayStatistics(const int id, const bool save_tags) {
|
||||||
|
|
||||||
if (id == -1) return;
|
if (id == -1) return;
|
||||||
|
|
||||||
|
ResetPlayStatistics(QList<int>() << id, save_tags);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
void CollectionBackend::ResetPlayStatistics(const QList<int> &id_list, const bool save_tags) {
|
||||||
|
|
||||||
|
if (id_list.isEmpty()) return;
|
||||||
|
|
||||||
|
QStringList id_str_list;
|
||||||
|
id_str_list.reserve(id_list.count());
|
||||||
|
for (const int id : id_list) {
|
||||||
|
id_str_list << QString::number(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
const bool success = ResetPlayStatistics(id_str_list);
|
||||||
|
if (success) {
|
||||||
|
const SongList songs = GetSongsById(id_list);
|
||||||
|
emit SongsStatisticsChanged(songs, save_tags);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
bool CollectionBackend::ResetPlayStatistics(const QStringList &id_str_list) {
|
||||||
|
|
||||||
|
if (id_str_list.isEmpty()) return false;
|
||||||
|
|
||||||
QMutexLocker l(db_->Mutex());
|
QMutexLocker l(db_->Mutex());
|
||||||
QSqlDatabase db(db_->Connect());
|
QSqlDatabase db(db_->Connect());
|
||||||
|
|
||||||
SqlQuery q(db);
|
SqlQuery q(db);
|
||||||
q.prepare(QString("UPDATE %1 SET playcount = 0, skipcount = 0, lastplayed = -1 WHERE ROWID = :id").arg(songs_table_));
|
q.prepare(QString("UPDATE %1 SET playcount = 0, skipcount = 0, lastplayed = -1 WHERE ROWID IN (:ids)").arg(songs_table_));
|
||||||
q.BindValue(":id", id);
|
q.BindValue(":ids", id_str_list.join(","));
|
||||||
if (!q.Exec()) {
|
if (!q.Exec()) {
|
||||||
db_->ReportErrors(q);
|
db_->ReportErrors(q);
|
||||||
return;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
Song new_song = GetSongById(id, db);
|
return true;
|
||||||
emit SongsStatisticsChanged(SongList() << new_song);
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1867,7 +1893,7 @@ SongList CollectionBackend::SmartPlaylistsFindSongs(const SmartPlaylistSearch &s
|
|||||||
SongList CollectionBackend::SmartPlaylistsGetAllSongs() {
|
SongList CollectionBackend::SmartPlaylistsGetAllSongs() {
|
||||||
|
|
||||||
// Get all the songs!
|
// Get all the songs!
|
||||||
return SmartPlaylistsFindSongs(SmartPlaylistSearch(SmartPlaylistSearch::Type_All, SmartPlaylistSearch::TermList(), SmartPlaylistSearch::Sort_FieldAsc, SmartPlaylistSearchTerm::Field_Artist, -1));
|
return SmartPlaylistsFindSongs(SmartPlaylistSearch(SmartPlaylistSearch::SearchType::All, SmartPlaylistSearch::TermList(), SmartPlaylistSearch::SortType::FieldAsc, SmartPlaylistSearchTerm::Field::Artist, -1));
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1930,7 +1956,7 @@ void CollectionBackend::UpdateLastPlayed(const QString &artist, const QString &a
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void CollectionBackend::UpdatePlayCount(const QString &artist, const QString &title, const int playcount) {
|
void CollectionBackend::UpdatePlayCount(const QString &artist, const QString &title, const int playcount, const bool save_tags) {
|
||||||
|
|
||||||
SongList songs = GetSongsBy(artist, QString(), title);
|
SongList songs = GetSongsBy(artist, QString(), title);
|
||||||
if (songs.isEmpty()) {
|
if (songs.isEmpty()) {
|
||||||
@@ -1952,7 +1978,7 @@ void CollectionBackend::UpdatePlayCount(const QString &artist, const QString &ti
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
emit SongsStatisticsChanged(SongList() << songs);
|
emit SongsStatisticsChanged(SongList() << songs, save_tags);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -38,8 +38,9 @@
|
|||||||
|
|
||||||
#include "core/song.h"
|
#include "core/song.h"
|
||||||
#include "core/sqlquery.h"
|
#include "core/sqlquery.h"
|
||||||
|
#include "collectionfilteroptions.h"
|
||||||
#include "collectionquery.h"
|
#include "collectionquery.h"
|
||||||
#include "directory.h"
|
#include "collectiondirectory.h"
|
||||||
|
|
||||||
class QThread;
|
class QThread;
|
||||||
class TaskManager;
|
class TaskManager;
|
||||||
@@ -53,7 +54,7 @@ class CollectionBackendInterface : public QObject {
|
|||||||
explicit CollectionBackendInterface(QObject *parent = nullptr) : QObject(parent) {}
|
explicit CollectionBackendInterface(QObject *parent = nullptr) : QObject(parent) {}
|
||||||
|
|
||||||
struct Album {
|
struct Album {
|
||||||
Album() {}
|
Album() : filetype(Song::FileType::Unknown) {}
|
||||||
Album(const QString &_album_artist, const QString &_album, const QUrl &_art_automatic, const QUrl &_art_manual, const QList<QUrl> &_urls, const Song::FileType _filetype, const QString &_cue_path)
|
Album(const QString &_album_artist, const QString &_album, const QUrl &_art_automatic, const QUrl &_art_manual, const QList<QUrl> &_urls, const Song::FileType _filetype, const QString &_cue_path)
|
||||||
: album_artist(_album_artist),
|
: album_artist(_album_artist),
|
||||||
album(_album),
|
album(_album),
|
||||||
@@ -72,11 +73,13 @@ class CollectionBackendInterface : public QObject {
|
|||||||
Song::FileType filetype;
|
Song::FileType filetype;
|
||||||
QString cue_path;
|
QString cue_path;
|
||||||
};
|
};
|
||||||
typedef QList<Album> AlbumList;
|
using AlbumList = QList<Album>;
|
||||||
|
|
||||||
virtual QString songs_table() const = 0;
|
virtual QString songs_table() const = 0;
|
||||||
virtual QString fts_table() const = 0;
|
virtual QString fts_table() const = 0;
|
||||||
|
|
||||||
|
virtual Song::Source source() const = 0;
|
||||||
|
|
||||||
virtual Database *db() const = 0;
|
virtual Database *db() const = 0;
|
||||||
|
|
||||||
// Get a list of directories in the collection. Emits DirectoriesDiscovered.
|
// Get a list of directories in the collection. Emits DirectoriesDiscovered.
|
||||||
@@ -88,23 +91,23 @@ class CollectionBackendInterface : public QObject {
|
|||||||
|
|
||||||
virtual SongList FindSongsInDirectory(const int id) = 0;
|
virtual SongList FindSongsInDirectory(const int id) = 0;
|
||||||
virtual SongList SongsWithMissingFingerprint(const int id) = 0;
|
virtual SongList SongsWithMissingFingerprint(const int id) = 0;
|
||||||
virtual SubdirectoryList SubdirsInDirectory(const int id) = 0;
|
virtual CollectionSubdirectoryList SubdirsInDirectory(const int id) = 0;
|
||||||
virtual DirectoryList GetAllDirectories() = 0;
|
virtual CollectionDirectoryList GetAllDirectories() = 0;
|
||||||
virtual void ChangeDirPath(const 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 SongList GetAllSongs() = 0;
|
virtual SongList GetAllSongs() = 0;
|
||||||
|
|
||||||
virtual QStringList GetAllArtists(const QueryOptions &opt = QueryOptions()) = 0;
|
virtual QStringList GetAllArtists(const CollectionFilterOptions &opt = CollectionFilterOptions()) = 0;
|
||||||
virtual QStringList GetAllArtistsWithAlbums(const QueryOptions &opt = QueryOptions()) = 0;
|
virtual QStringList GetAllArtistsWithAlbums(const CollectionFilterOptions &opt = CollectionFilterOptions()) = 0;
|
||||||
virtual SongList GetArtistSongs(const QString &effective_albumartist, const QueryOptions &opt = QueryOptions()) = 0;
|
virtual SongList GetArtistSongs(const QString &effective_albumartist, const CollectionFilterOptions &opt = CollectionFilterOptions()) = 0;
|
||||||
virtual SongList GetAlbumSongs(const QString &effective_albumartist, const QString &album, const QueryOptions &opt = QueryOptions()) = 0;
|
virtual SongList GetAlbumSongs(const QString &effective_albumartist, const QString &album, const CollectionFilterOptions &opt = CollectionFilterOptions()) = 0;
|
||||||
virtual SongList GetSongsByAlbum(const QString &album, const QueryOptions &opt = QueryOptions()) = 0;
|
virtual SongList GetSongsByAlbum(const QString &album, const CollectionFilterOptions &opt = CollectionFilterOptions()) = 0;
|
||||||
|
|
||||||
virtual SongList GetCompilationSongs(const QString &album, const QueryOptions &opt = QueryOptions()) = 0;
|
virtual SongList GetCompilationSongs(const QString &album, const CollectionFilterOptions &opt = CollectionFilterOptions()) = 0;
|
||||||
|
|
||||||
virtual AlbumList GetAllAlbums(const QueryOptions &opt = QueryOptions()) = 0;
|
virtual AlbumList GetAllAlbums(const CollectionFilterOptions &opt = CollectionFilterOptions()) = 0;
|
||||||
virtual AlbumList GetAlbumsByArtist(const QString &artist, const QueryOptions &opt = QueryOptions()) = 0;
|
virtual AlbumList GetAlbumsByArtist(const QString &artist, const CollectionFilterOptions &opt = CollectionFilterOptions()) = 0;
|
||||||
virtual AlbumList GetCompilationAlbums(const QueryOptions &opt = QueryOptions()) = 0;
|
virtual AlbumList GetCompilationAlbums(const CollectionFilterOptions &opt = CollectionFilterOptions()) = 0;
|
||||||
|
|
||||||
virtual void UpdateManualAlbumArtAsync(const QString &effective_albumartist, const QString &album, const QUrl &cover_url, const bool clear_art_automatic = false) = 0;
|
virtual void UpdateManualAlbumArtAsync(const QString &effective_albumartist, const QString &album, const QUrl &cover_url, const bool clear_art_automatic = false) = 0;
|
||||||
virtual void UpdateAutomaticAlbumArtAsync(const QString &effective_albumartist, const QString &album, const QUrl &cover_url, const bool clear_art_manual = false) = 0;
|
virtual void UpdateAutomaticAlbumArtAsync(const QString &effective_albumartist, const QString &album, const QUrl &cover_url, const bool clear_art_manual = false) = 0;
|
||||||
@@ -122,7 +125,7 @@ class CollectionBackendInterface : public QObject {
|
|||||||
virtual Song GetSongByUrl(const QUrl &url, const qint64 beginning = 0) = 0;
|
virtual Song GetSongByUrl(const QUrl &url, const qint64 beginning = 0) = 0;
|
||||||
|
|
||||||
virtual void AddDirectory(const QString &path) = 0;
|
virtual void AddDirectory(const QString &path) = 0;
|
||||||
virtual void RemoveDirectory(const Directory &dir) = 0;
|
virtual void RemoveDirectory(const CollectionDirectory &dir) = 0;
|
||||||
};
|
};
|
||||||
|
|
||||||
class CollectionBackend : public CollectionBackendInterface {
|
class CollectionBackend : public CollectionBackendInterface {
|
||||||
@@ -139,6 +142,8 @@ class CollectionBackend : public CollectionBackendInterface {
|
|||||||
|
|
||||||
void ReportErrors(const CollectionQuery &query);
|
void ReportErrors(const CollectionQuery &query);
|
||||||
|
|
||||||
|
Song::Source source() const override { return source_; }
|
||||||
|
|
||||||
Database *db() const override { return db_; }
|
Database *db() const override { return db_; }
|
||||||
|
|
||||||
QString songs_table() const override { return songs_table_; }
|
QString songs_table() const override { return songs_table_; }
|
||||||
@@ -155,24 +160,24 @@ class CollectionBackend : public CollectionBackendInterface {
|
|||||||
|
|
||||||
SongList FindSongsInDirectory(const int id) override;
|
SongList FindSongsInDirectory(const int id) override;
|
||||||
SongList SongsWithMissingFingerprint(const int id) override;
|
SongList SongsWithMissingFingerprint(const int id) override;
|
||||||
SubdirectoryList SubdirsInDirectory(const int id) override;
|
CollectionSubdirectoryList SubdirsInDirectory(const int id) override;
|
||||||
DirectoryList GetAllDirectories() override;
|
CollectionDirectoryList GetAllDirectories() override;
|
||||||
void ChangeDirPath(const int id, const QString &old_path, const QString &new_path) override;
|
void ChangeDirPath(const int id, const QString &old_path, const QString &new_path) override;
|
||||||
|
|
||||||
SongList GetAllSongs() override;
|
SongList GetAllSongs() override;
|
||||||
|
|
||||||
QStringList GetAll(const QString &column, const QueryOptions &opt = QueryOptions());
|
QStringList GetAll(const QString &column, const CollectionFilterOptions &filter_options = CollectionFilterOptions());
|
||||||
QStringList GetAllArtists(const QueryOptions &opt = QueryOptions()) override;
|
QStringList GetAllArtists(const CollectionFilterOptions &opt = CollectionFilterOptions()) override;
|
||||||
QStringList GetAllArtistsWithAlbums(const QueryOptions &opt = QueryOptions()) override;
|
QStringList GetAllArtistsWithAlbums(const CollectionFilterOptions &opt = CollectionFilterOptions()) override;
|
||||||
SongList GetArtistSongs(const QString &effective_albumartist, const QueryOptions &opt = QueryOptions()) override;
|
SongList GetArtistSongs(const QString &effective_albumartist, const CollectionFilterOptions &opt = CollectionFilterOptions()) override;
|
||||||
SongList GetAlbumSongs(const QString &effective_albumartist, const QString &album, const QueryOptions &opt = QueryOptions()) override;
|
SongList GetAlbumSongs(const QString &effective_albumartist, const QString &album, const CollectionFilterOptions &opt = CollectionFilterOptions()) override;
|
||||||
SongList GetSongsByAlbum(const QString &album, const QueryOptions &opt = QueryOptions()) override;
|
SongList GetSongsByAlbum(const QString &album, const CollectionFilterOptions &opt = CollectionFilterOptions()) override;
|
||||||
|
|
||||||
SongList GetCompilationSongs(const QString &album, const QueryOptions &opt = QueryOptions()) override;
|
SongList GetCompilationSongs(const QString &album, const CollectionFilterOptions &opt = CollectionFilterOptions()) override;
|
||||||
|
|
||||||
AlbumList GetAllAlbums(const QueryOptions &opt = QueryOptions()) override;
|
AlbumList GetAllAlbums(const CollectionFilterOptions &opt = CollectionFilterOptions()) override;
|
||||||
AlbumList GetCompilationAlbums(const QueryOptions &opt = QueryOptions()) override;
|
AlbumList GetCompilationAlbums(const CollectionFilterOptions &opt = CollectionFilterOptions()) override;
|
||||||
AlbumList GetAlbumsByArtist(const QString &artist, const QueryOptions &opt = QueryOptions()) override;
|
AlbumList GetAlbumsByArtist(const QString &artist, const CollectionFilterOptions &opt = CollectionFilterOptions()) override;
|
||||||
|
|
||||||
void UpdateManualAlbumArtAsync(const QString &effective_albumartist, const QString &album, const QUrl &cover_url, const bool clear_art_automatic = false) override;
|
void UpdateManualAlbumArtAsync(const QString &effective_albumartist, const QString &album, const QUrl &cover_url, const bool clear_art_automatic = false) override;
|
||||||
void UpdateAutomaticAlbumArtAsync(const QString &effective_albumartist, const QString &album, const QUrl &cover_url, const bool clear_art_manual = false) override;
|
void UpdateAutomaticAlbumArtAsync(const QString &effective_albumartist, const QString &album, const QUrl &cover_url, const bool clear_art_manual = false) override;
|
||||||
@@ -188,14 +193,15 @@ class CollectionBackend : public CollectionBackendInterface {
|
|||||||
Song GetSongByUrl(const QUrl &url, qint64 beginning = 0) override;
|
Song GetSongByUrl(const QUrl &url, qint64 beginning = 0) override;
|
||||||
|
|
||||||
void AddDirectory(const QString &path) override;
|
void AddDirectory(const QString &path) override;
|
||||||
void RemoveDirectory(const Directory &dir) override;
|
void RemoveDirectory(const CollectionDirectory &dir) override;
|
||||||
|
|
||||||
bool ExecCollectionQuery(CollectionQuery *query, SongList &songs);
|
bool ExecCollectionQuery(CollectionQuery *query, SongList &songs);
|
||||||
bool ExecCollectionQuery(CollectionQuery *query, SongMap &songs);
|
bool ExecCollectionQuery(CollectionQuery *query, SongMap &songs);
|
||||||
|
|
||||||
void IncrementPlayCountAsync(const int id);
|
void IncrementPlayCountAsync(const int id);
|
||||||
void IncrementSkipCountAsync(const int id, const float progress);
|
void IncrementSkipCountAsync(const int id, const float progress);
|
||||||
void ResetStatisticsAsync(const int id);
|
void ResetPlayStatisticsAsync(const int id, const bool save_tags = false);
|
||||||
|
void ResetPlayStatisticsAsync(const QList<int> &id_list, const bool save_tags = false);
|
||||||
|
|
||||||
void DeleteAllAsync();
|
void DeleteAllAsync();
|
||||||
|
|
||||||
@@ -207,8 +213,6 @@ class CollectionBackend : public CollectionBackendInterface {
|
|||||||
SongList SmartPlaylistsGetAllSongs();
|
SongList SmartPlaylistsGetAllSongs();
|
||||||
SongList SmartPlaylistsFindSongs(const SmartPlaylistSearch &search);
|
SongList SmartPlaylistsFindSongs(const SmartPlaylistSearch &search);
|
||||||
|
|
||||||
Song::Source Source() const;
|
|
||||||
|
|
||||||
void AddOrUpdateSongsAsync(const SongList &songs);
|
void AddOrUpdateSongsAsync(const SongList &songs);
|
||||||
void UpdateSongsBySongIDAsync(const SongMap &new_songs);
|
void UpdateSongsBySongIDAsync(const SongMap &new_songs);
|
||||||
|
|
||||||
@@ -226,20 +230,22 @@ class CollectionBackend : public CollectionBackendInterface {
|
|||||||
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, const bool unavailable = true);
|
void MarkSongsUnavailable(const SongList &songs, const bool unavailable = true);
|
||||||
void AddOrUpdateSubdirs(const SubdirectoryList &subdirs);
|
void AddOrUpdateSubdirs(const CollectionSubdirectoryList &subdirs);
|
||||||
void CompilationsNeedUpdating();
|
void CompilationsNeedUpdating();
|
||||||
void UpdateManualAlbumArt(const QString &effective_albumartist, const QString &album, const QUrl &cover_url, const bool clear_art_automatic = false);
|
void UpdateManualAlbumArt(const QString &effective_albumartist, const QString &album, const QUrl &cover_url, const bool clear_art_automatic = false);
|
||||||
void UpdateAutomaticAlbumArt(const QString &effective_albumartist, const QString &album, const QUrl &cover_url, const bool clear_art_manual = false);
|
void UpdateAutomaticAlbumArt(const QString &effective_albumartist, const QString &album, const QUrl &cover_url, const bool clear_art_manual = false);
|
||||||
void ForceCompilation(const QString &album, const QList<QString> &artists, const bool on);
|
void ForceCompilation(const QString &album, const QList<QString> &artists, const bool on);
|
||||||
void IncrementPlayCount(const int id);
|
void IncrementPlayCount(const int id);
|
||||||
void IncrementSkipCount(const int id, const float progress);
|
void IncrementSkipCount(const int id, const float progress);
|
||||||
void ResetStatistics(const int id);
|
void ResetPlayStatistics(const int id, const bool save_tags = false);
|
||||||
|
void ResetPlayStatistics(const QList<int> &id_list, const bool save_tags = false);
|
||||||
|
bool ResetPlayStatistics(const QStringList &id_str_list);
|
||||||
void DeleteAll();
|
void DeleteAll();
|
||||||
void SongPathChanged(const Song &song, const QFileInfo &new_file, const std::optional<int> new_collection_directory_id);
|
void SongPathChanged(const Song &song, const QFileInfo &new_file, const std::optional<int> new_collection_directory_id);
|
||||||
|
|
||||||
SongList GetSongsBy(const QString &artist, const QString &album, const QString &title);
|
SongList GetSongsBy(const QString &artist, const QString &album, const QString &title);
|
||||||
void UpdateLastPlayed(const QString &artist, const QString &album, const QString &title, const qint64 lastplayed);
|
void UpdateLastPlayed(const QString &artist, const QString &album, const QString &title, const qint64 lastplayed);
|
||||||
void UpdatePlayCount(const QString &artist, const QString &title, const int playcount);
|
void UpdatePlayCount(const QString &artist, const QString &title, const int playcount, const bool save_tags = false);
|
||||||
|
|
||||||
void UpdateSongRating(const int id, const float rating, const bool save_tags = false);
|
void UpdateSongRating(const int id, const float rating, const bool save_tags = false);
|
||||||
void UpdateSongsRating(const QList<int> &id_list, const float rating, const bool save_tags = false);
|
void UpdateSongsRating(const QList<int> &id_list, const float rating, const bool save_tags = false);
|
||||||
@@ -248,12 +254,12 @@ class CollectionBackend : public CollectionBackendInterface {
|
|||||||
void ExpireSongs(const int directory_id, const int expire_unavailable_songs_days);
|
void ExpireSongs(const int directory_id, const int expire_unavailable_songs_days);
|
||||||
|
|
||||||
signals:
|
signals:
|
||||||
void DirectoryDiscovered(Directory, SubdirectoryList);
|
void DirectoryDiscovered(CollectionDirectory, CollectionSubdirectoryList);
|
||||||
void DirectoryDeleted(Directory);
|
void DirectoryDeleted(CollectionDirectory);
|
||||||
|
|
||||||
void SongsDiscovered(SongList);
|
void SongsDiscovered(SongList);
|
||||||
void SongsDeleted(SongList);
|
void SongsDeleted(SongList);
|
||||||
void SongsStatisticsChanged(SongList);
|
void SongsStatisticsChanged(SongList, bool = false);
|
||||||
|
|
||||||
void DatabaseReset();
|
void DatabaseReset();
|
||||||
|
|
||||||
@@ -278,9 +284,9 @@ class CollectionBackend : public CollectionBackendInterface {
|
|||||||
};
|
};
|
||||||
|
|
||||||
bool UpdateCompilations(const QSqlDatabase &db, SongList &deleted_songs, SongList &added_songs, const QUrl &url, const bool compilation_detected);
|
bool UpdateCompilations(const QSqlDatabase &db, SongList &deleted_songs, SongList &added_songs, const QUrl &url, const bool compilation_detected);
|
||||||
AlbumList GetAlbums(const QString &artist, const QString &album_artist, const bool compilation_required = false, const QueryOptions &opt = QueryOptions());
|
AlbumList GetAlbums(const QString &artist, const QString &album_artist, const bool compilation_required = false, const CollectionFilterOptions &opt = CollectionFilterOptions());
|
||||||
AlbumList GetAlbums(const QString &artist, const bool compilation_required, const QueryOptions &opt = QueryOptions());
|
AlbumList GetAlbums(const QString &artist, const bool compilation_required, const CollectionFilterOptions &opt = CollectionFilterOptions());
|
||||||
SubdirectoryList SubdirsInDirectory(const int id, QSqlDatabase &db);
|
CollectionSubdirectoryList SubdirsInDirectory(const int id, QSqlDatabase &db);
|
||||||
|
|
||||||
Song GetSongById(const int id, QSqlDatabase &db);
|
Song GetSongById(const int id, QSqlDatabase &db);
|
||||||
SongList GetSongsById(const QStringList &ids, QSqlDatabase &db);
|
SongList GetSongsById(const QStringList &ids, QSqlDatabase &db);
|
||||||
|
|||||||
57
src/collection/collectiondirectory.h
Normal file
57
src/collection/collectiondirectory.h
Normal file
@@ -0,0 +1,57 @@
|
|||||||
|
/*
|
||||||
|
* 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/>.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef COLLECTIONDIRECTORY_H
|
||||||
|
#define COLLECTIONDIRECTORY_H
|
||||||
|
|
||||||
|
#include "config.h"
|
||||||
|
|
||||||
|
#include <QMetaType>
|
||||||
|
#include <QList>
|
||||||
|
#include <QString>
|
||||||
|
|
||||||
|
struct CollectionDirectory {
|
||||||
|
CollectionDirectory() : id(-1) {}
|
||||||
|
|
||||||
|
bool operator==(const CollectionDirectory &other) const {
|
||||||
|
return path == other.path && id == other.id;
|
||||||
|
}
|
||||||
|
|
||||||
|
QString path;
|
||||||
|
int id;
|
||||||
|
};
|
||||||
|
Q_DECLARE_METATYPE(CollectionDirectory)
|
||||||
|
|
||||||
|
using CollectionDirectoryList = QList<CollectionDirectory>;
|
||||||
|
Q_DECLARE_METATYPE(CollectionDirectoryList)
|
||||||
|
|
||||||
|
struct CollectionSubdirectory {
|
||||||
|
CollectionSubdirectory() : directory_id(-1), mtime(0) {}
|
||||||
|
|
||||||
|
int directory_id;
|
||||||
|
QString path;
|
||||||
|
qint64 mtime;
|
||||||
|
};
|
||||||
|
Q_DECLARE_METATYPE(CollectionSubdirectory)
|
||||||
|
|
||||||
|
using CollectionSubdirectoryList = QList<CollectionSubdirectory>;
|
||||||
|
Q_DECLARE_METATYPE(CollectionSubdirectoryList)
|
||||||
|
|
||||||
|
#endif // COLLECTIONDIRECTORY_H
|
||||||
@@ -29,8 +29,8 @@
|
|||||||
#include "core/filesystemmusicstorage.h"
|
#include "core/filesystemmusicstorage.h"
|
||||||
#include "core/iconloader.h"
|
#include "core/iconloader.h"
|
||||||
#include "core/musicstorage.h"
|
#include "core/musicstorage.h"
|
||||||
#include "core/utilities.h"
|
#include "utilities/diskutils.h"
|
||||||
#include "directory.h"
|
#include "collectiondirectory.h"
|
||||||
#include "collectionbackend.h"
|
#include "collectionbackend.h"
|
||||||
#include "collectiondirectorymodel.h"
|
#include "collectiondirectorymodel.h"
|
||||||
|
|
||||||
@@ -46,17 +46,17 @@ CollectionDirectoryModel::CollectionDirectoryModel(CollectionBackend *backend, Q
|
|||||||
|
|
||||||
CollectionDirectoryModel::~CollectionDirectoryModel() = default;
|
CollectionDirectoryModel::~CollectionDirectoryModel() = default;
|
||||||
|
|
||||||
void CollectionDirectoryModel::DirectoryDiscovered(const Directory &dir) {
|
void CollectionDirectoryModel::DirectoryDiscovered(const CollectionDirectory &dir) {
|
||||||
|
|
||||||
QStandardItem *item = new QStandardItem(dir.path);
|
QStandardItem *item = new QStandardItem(dir.path);
|
||||||
item->setData(dir.id, kIdRole);
|
item->setData(dir.id, kIdRole);
|
||||||
item->setIcon(dir_icon_);
|
item->setIcon(dir_icon_);
|
||||||
storage_ << std::make_shared<FilesystemMusicStorage>(dir.path, dir.id);
|
storage_ << std::make_shared<FilesystemMusicStorage>(backend_->source(), dir.path, dir.id);
|
||||||
appendRow(item);
|
appendRow(item);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void CollectionDirectoryModel::DirectoryDeleted(const Directory &dir) {
|
void CollectionDirectoryModel::DirectoryDeleted(const CollectionDirectory &dir) {
|
||||||
|
|
||||||
for (int i = 0; i < rowCount(); ++i) {
|
for (int i = 0; i < rowCount(); ++i) {
|
||||||
if (item(i, 0)->data(kIdRole).toInt() == dir.id) {
|
if (item(i, 0)->data(kIdRole).toInt() == dir.id) {
|
||||||
@@ -80,7 +80,7 @@ void CollectionDirectoryModel::RemoveDirectory(const QModelIndex &idx) {
|
|||||||
|
|
||||||
if (!backend_ || !idx.isValid()) return;
|
if (!backend_ || !idx.isValid()) return;
|
||||||
|
|
||||||
Directory dir;
|
CollectionDirectory dir;
|
||||||
dir.path = idx.data().toString();
|
dir.path = idx.data().toString();
|
||||||
dir.id = idx.data(kIdRole).toInt();
|
dir.id = idx.data(kIdRole).toInt();
|
||||||
|
|
||||||
|
|||||||
@@ -34,7 +34,7 @@
|
|||||||
|
|
||||||
class QModelIndex;
|
class QModelIndex;
|
||||||
|
|
||||||
struct Directory;
|
struct CollectionDirectory;
|
||||||
class CollectionBackend;
|
class CollectionBackend;
|
||||||
class MusicStorage;
|
class MusicStorage;
|
||||||
|
|
||||||
@@ -53,8 +53,8 @@ class CollectionDirectoryModel : public QStandardItemModel {
|
|||||||
|
|
||||||
private slots:
|
private slots:
|
||||||
// To be called by the backend
|
// To be called by the backend
|
||||||
void DirectoryDiscovered(const Directory &directories);
|
void DirectoryDiscovered(const CollectionDirectory &directories);
|
||||||
void DirectoryDeleted(const Directory &directories);
|
void DirectoryDeleted(const CollectionDirectory &directories);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
static const int kIdRole = Qt::UserRole + 1;
|
static const int kIdRole = Qt::UserRole + 1;
|
||||||
|
|||||||
42
src/collection/collectionfilteroptions.cpp
Normal file
42
src/collection/collectionfilteroptions.cpp
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
/*
|
||||||
|
* Strawberry Music Player
|
||||||
|
* Copyright 2023, Jonas Kvinge <jonas@jkvinge.net>
|
||||||
|
*
|
||||||
|
* Strawberry is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* Strawberry is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with Strawberry. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <QtGlobal>
|
||||||
|
#include <QDateTime>
|
||||||
|
|
||||||
|
#include "core/song.h"
|
||||||
|
|
||||||
|
#include "collectionfilteroptions.h"
|
||||||
|
|
||||||
|
CollectionFilterOptions::CollectionFilterOptions() : filter_mode_(FilterMode::All), max_age_(-1) {}
|
||||||
|
|
||||||
|
bool CollectionFilterOptions::Matches(const Song &song) const {
|
||||||
|
|
||||||
|
if (max_age_ != -1) {
|
||||||
|
const qint64 cutoff = QDateTime::currentDateTime().toSecsSinceEpoch() - max_age_;
|
||||||
|
if (song.ctime() <= cutoff) return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!filter_text_.isNull()) {
|
||||||
|
return song.albumartist().contains(filter_text_, Qt::CaseInsensitive) || song.artist().contains(filter_text_, Qt::CaseInsensitive) || song.album().contains(filter_text_, Qt::CaseInsensitive) || song.title().contains(filter_text_, Qt::CaseInsensitive);
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
|
||||||
|
}
|
||||||
65
src/collection/collectionfilteroptions.h
Normal file
65
src/collection/collectionfilteroptions.h
Normal file
@@ -0,0 +1,65 @@
|
|||||||
|
/*
|
||||||
|
* Strawberry Music Player
|
||||||
|
* Copyright 2023, Jonas Kvinge <jonas@jkvinge.net>
|
||||||
|
*
|
||||||
|
* Strawberry is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* Strawberry is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with Strawberry. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef COLLECTIONFILTEROPTIONS_H
|
||||||
|
#define COLLECTIONFILTEROPTIONS_H
|
||||||
|
|
||||||
|
#include <QString>
|
||||||
|
|
||||||
|
#include "core/song.h"
|
||||||
|
|
||||||
|
class CollectionFilterOptions {
|
||||||
|
public:
|
||||||
|
|
||||||
|
explicit CollectionFilterOptions();
|
||||||
|
|
||||||
|
// Filter mode:
|
||||||
|
// - use the all songs table
|
||||||
|
// - use the duplicated songs view; by duplicated we mean those songs for which the (artist, album, title) tuple is found more than once in the songs table
|
||||||
|
// - use the untagged songs view; by untagged we mean those for which at least one of the (artist, album, title) tags is empty
|
||||||
|
// Please note that additional filtering based on FTS table (the filter attribute) won't work in Duplicates and Untagged modes.
|
||||||
|
enum class FilterMode {
|
||||||
|
All,
|
||||||
|
Duplicates,
|
||||||
|
Untagged
|
||||||
|
};
|
||||||
|
|
||||||
|
FilterMode filter_mode() const { return filter_mode_; }
|
||||||
|
int max_age() const { return max_age_; }
|
||||||
|
QString filter_text() const { return filter_text_; }
|
||||||
|
|
||||||
|
void set_filter_mode(const FilterMode filter_mode) {
|
||||||
|
filter_mode_ = filter_mode;
|
||||||
|
filter_text_.clear();
|
||||||
|
}
|
||||||
|
void set_max_age(const int max_age) { max_age_ = max_age; }
|
||||||
|
void set_filter_text(const QString &filter_text) {
|
||||||
|
filter_mode_ = FilterMode::All;
|
||||||
|
filter_text_ = filter_text;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Matches(const Song &song) const;
|
||||||
|
|
||||||
|
private:
|
||||||
|
FilterMode filter_mode_;
|
||||||
|
int max_age_;
|
||||||
|
QString filter_text_;
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif // COLLECTIONFILTEROPTIONS_H
|
||||||
@@ -46,6 +46,7 @@
|
|||||||
#include "core/iconloader.h"
|
#include "core/iconloader.h"
|
||||||
#include "core/song.h"
|
#include "core/song.h"
|
||||||
#include "core/logging.h"
|
#include "core/logging.h"
|
||||||
|
#include "collectionfilteroptions.h"
|
||||||
#include "collectionmodel.h"
|
#include "collectionmodel.h"
|
||||||
#include "collectionquery.h"
|
#include "collectionquery.h"
|
||||||
#include "savedgroupingmanager.h"
|
#include "savedgroupingmanager.h"
|
||||||
@@ -53,6 +54,7 @@
|
|||||||
#include "groupbydialog.h"
|
#include "groupbydialog.h"
|
||||||
#include "ui_collectionfilterwidget.h"
|
#include "ui_collectionfilterwidget.h"
|
||||||
#include "widgets/qsearchfield.h"
|
#include "widgets/qsearchfield.h"
|
||||||
|
#include "settings/collectionsettingspage.h"
|
||||||
#include "settings/appearancesettingspage.h"
|
#include "settings/appearancesettingspage.h"
|
||||||
|
|
||||||
CollectionFilterWidget::CollectionFilterWidget(QWidget *parent)
|
CollectionFilterWidget::CollectionFilterWidget(QWidget *parent)
|
||||||
@@ -67,7 +69,7 @@ CollectionFilterWidget::CollectionFilterWidget(QWidget *parent)
|
|||||||
group_by_group_(nullptr),
|
group_by_group_(nullptr),
|
||||||
filter_delay_(new QTimer(this)),
|
filter_delay_(new QTimer(this)),
|
||||||
filter_applies_to_model_(true),
|
filter_applies_to_model_(true),
|
||||||
delay_behaviour_(DelayedOnLargeLibraries) {
|
delay_behaviour_(DelayBehaviour::DelayedOnLargeLibraries) {
|
||||||
|
|
||||||
ui_->setupUi(this);
|
ui_->setupUi(this);
|
||||||
|
|
||||||
@@ -177,12 +179,13 @@ void CollectionFilterWidget::Init(CollectionModel *model) {
|
|||||||
if (s.contains(group_by_version())) version = s.value(group_by_version(), 0).toInt();
|
if (s.contains(group_by_version())) version = s.value(group_by_version(), 0).toInt();
|
||||||
if (version == 1) {
|
if (version == 1) {
|
||||||
model_->SetGroupBy(CollectionModel::Grouping(
|
model_->SetGroupBy(CollectionModel::Grouping(
|
||||||
CollectionModel::GroupBy(s.value(group_by_key(1), static_cast<int>(CollectionModel::GroupBy_AlbumArtist)).toInt()),
|
static_cast<CollectionModel::GroupBy>(s.value(group_by_key(1), static_cast<int>(CollectionModel::GroupBy::AlbumArtist)).toInt()),
|
||||||
CollectionModel::GroupBy(s.value(group_by_key(2), static_cast<int>(CollectionModel::GroupBy_AlbumDisc)).toInt()),
|
static_cast<CollectionModel::GroupBy>(s.value(group_by_key(2), static_cast<int>(CollectionModel::GroupBy::AlbumDisc)).toInt()),
|
||||||
CollectionModel::GroupBy(s.value(group_by_key(3), static_cast<int>(CollectionModel::GroupBy_None)).toInt())), s.value(separate_albums_by_grouping_key(), false).toBool());
|
static_cast<CollectionModel::GroupBy>(s.value(group_by_key(3), static_cast<int>(CollectionModel::GroupBy::None)).toInt())),
|
||||||
|
s.value(separate_albums_by_grouping_key(), false).toBool());
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
model_->SetGroupBy(CollectionModel::Grouping(CollectionModel::GroupBy_AlbumArtist, CollectionModel::GroupBy_AlbumDisc, CollectionModel::GroupBy_None), false);
|
model_->SetGroupBy(CollectionModel::Grouping(CollectionModel::GroupBy::AlbumArtist, CollectionModel::GroupBy::AlbumDisc, CollectionModel::GroupBy::None), false);
|
||||||
}
|
}
|
||||||
s.endGroup();
|
s.endGroup();
|
||||||
}
|
}
|
||||||
@@ -271,24 +274,24 @@ QActionGroup *CollectionFilterWidget::CreateGroupByActions(const QString &saved_
|
|||||||
|
|
||||||
QActionGroup *ret = new QActionGroup(parent);
|
QActionGroup *ret = new QActionGroup(parent);
|
||||||
|
|
||||||
ret->addAction(CreateGroupByAction(tr("Group by Album artist/Album"), parent, CollectionModel::Grouping(CollectionModel::GroupBy_AlbumArtist, CollectionModel::GroupBy_Album)));
|
ret->addAction(CreateGroupByAction(tr("Group by Album artist/Album"), parent, CollectionModel::Grouping(CollectionModel::GroupBy::AlbumArtist, CollectionModel::GroupBy::Album)));
|
||||||
ret->addAction(CreateGroupByAction(tr("Group by Album artist/Album - Disc"), parent, CollectionModel::Grouping(CollectionModel::GroupBy_AlbumArtist, CollectionModel::GroupBy_AlbumDisc)));
|
ret->addAction(CreateGroupByAction(tr("Group by Album artist/Album - Disc"), parent, CollectionModel::Grouping(CollectionModel::GroupBy::AlbumArtist, CollectionModel::GroupBy::AlbumDisc)));
|
||||||
ret->addAction(CreateGroupByAction(tr("Group by Album artist/Year - Album"), parent, CollectionModel::Grouping(CollectionModel::GroupBy_AlbumArtist, CollectionModel::GroupBy_YearAlbum)));
|
ret->addAction(CreateGroupByAction(tr("Group by Album artist/Year - Album"), parent, CollectionModel::Grouping(CollectionModel::GroupBy::AlbumArtist, CollectionModel::GroupBy::YearAlbum)));
|
||||||
ret->addAction(CreateGroupByAction(tr("Group by Album artist/Year - Album - Disc"), parent, CollectionModel::Grouping(CollectionModel::GroupBy_AlbumArtist, CollectionModel::GroupBy_YearAlbumDisc)));
|
ret->addAction(CreateGroupByAction(tr("Group by Album artist/Year - Album - Disc"), parent, CollectionModel::Grouping(CollectionModel::GroupBy::AlbumArtist, CollectionModel::GroupBy::YearAlbumDisc)));
|
||||||
|
|
||||||
ret->addAction(CreateGroupByAction(tr("Group by Artist/Album"), parent, CollectionModel::Grouping(CollectionModel::GroupBy_Artist, CollectionModel::GroupBy_Album)));
|
ret->addAction(CreateGroupByAction(tr("Group by Artist/Album"), parent, CollectionModel::Grouping(CollectionModel::GroupBy::Artist, CollectionModel::GroupBy::Album)));
|
||||||
ret->addAction(CreateGroupByAction(tr("Group by Artist/Album - Disc"), parent, CollectionModel::Grouping(CollectionModel::GroupBy_Artist, CollectionModel::GroupBy_AlbumDisc)));
|
ret->addAction(CreateGroupByAction(tr("Group by Artist/Album - Disc"), parent, CollectionModel::Grouping(CollectionModel::GroupBy::Artist, CollectionModel::GroupBy::AlbumDisc)));
|
||||||
ret->addAction(CreateGroupByAction(tr("Group by Artist/Year - Album"), parent, CollectionModel::Grouping(CollectionModel::GroupBy_Artist, CollectionModel::GroupBy_YearAlbum)));
|
ret->addAction(CreateGroupByAction(tr("Group by Artist/Year - Album"), parent, CollectionModel::Grouping(CollectionModel::GroupBy::Artist, CollectionModel::GroupBy::YearAlbum)));
|
||||||
ret->addAction(CreateGroupByAction(tr("Group by Artist/Year - Album - Disc"), parent, CollectionModel::Grouping(CollectionModel::GroupBy_Artist, CollectionModel::GroupBy_YearAlbumDisc)));
|
ret->addAction(CreateGroupByAction(tr("Group by Artist/Year - Album - Disc"), parent, CollectionModel::Grouping(CollectionModel::GroupBy::Artist, CollectionModel::GroupBy::YearAlbumDisc)));
|
||||||
|
|
||||||
ret->addAction(CreateGroupByAction(tr("Group by Genre/Album artist/Album"), parent, CollectionModel::Grouping(CollectionModel::GroupBy_Genre, CollectionModel::GroupBy_AlbumArtist, CollectionModel::GroupBy_Album)));
|
ret->addAction(CreateGroupByAction(tr("Group by Genre/Album artist/Album"), parent, CollectionModel::Grouping(CollectionModel::GroupBy::Genre, CollectionModel::GroupBy::AlbumArtist, CollectionModel::GroupBy::Album)));
|
||||||
ret->addAction(CreateGroupByAction(tr("Group by Genre/Artist/Album"), parent, CollectionModel::Grouping(CollectionModel::GroupBy_Genre, CollectionModel::GroupBy_Artist, CollectionModel::GroupBy_Album)));
|
ret->addAction(CreateGroupByAction(tr("Group by Genre/Artist/Album"), parent, CollectionModel::Grouping(CollectionModel::GroupBy::Genre, CollectionModel::GroupBy::Artist, CollectionModel::GroupBy::Album)));
|
||||||
|
|
||||||
ret->addAction(CreateGroupByAction(tr("Group by Album Artist"), parent, CollectionModel::Grouping(CollectionModel::GroupBy_AlbumArtist)));
|
ret->addAction(CreateGroupByAction(tr("Group by Album Artist"), parent, CollectionModel::Grouping(CollectionModel::GroupBy::AlbumArtist)));
|
||||||
ret->addAction(CreateGroupByAction(tr("Group by Artist"), parent, CollectionModel::Grouping(CollectionModel::GroupBy_Artist)));
|
ret->addAction(CreateGroupByAction(tr("Group by Artist"), parent, CollectionModel::Grouping(CollectionModel::GroupBy::Artist)));
|
||||||
|
|
||||||
ret->addAction(CreateGroupByAction(tr("Group by Album"), parent, CollectionModel::Grouping(CollectionModel::GroupBy_Album)));
|
ret->addAction(CreateGroupByAction(tr("Group by Album"), parent, CollectionModel::Grouping(CollectionModel::GroupBy::Album)));
|
||||||
ret->addAction(CreateGroupByAction(tr("Group by Genre/Album"), parent, CollectionModel::Grouping(CollectionModel::GroupBy_Genre, CollectionModel::GroupBy_Album)));
|
ret->addAction(CreateGroupByAction(tr("Group by Genre/Album"), parent, CollectionModel::Grouping(CollectionModel::GroupBy::Genre, CollectionModel::GroupBy::Album)));
|
||||||
|
|
||||||
QAction *sep1 = new QAction(parent);
|
QAction *sep1 = new QAction(parent);
|
||||||
sep1->setSeparator(true);
|
sep1->setSeparator(true);
|
||||||
@@ -333,7 +336,7 @@ QAction *CollectionFilterWidget::CreateGroupByAction(const QString &text, QObjec
|
|||||||
QAction *ret = new QAction(text, parent);
|
QAction *ret = new QAction(text, parent);
|
||||||
ret->setCheckable(true);
|
ret->setCheckable(true);
|
||||||
|
|
||||||
if (grouping.first != CollectionModel::GroupBy_None) {
|
if (grouping.first != CollectionModel::GroupBy::None) {
|
||||||
ret->setProperty("group_by", QVariant::fromValue(grouping));
|
ret->setProperty("group_by", QVariant::fromValue(grouping));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -455,12 +458,12 @@ void CollectionFilterWidget::SetFilterHint(const QString &hint) {
|
|||||||
ui_->search_field->setPlaceholderText(hint);
|
ui_->search_field->setPlaceholderText(hint);
|
||||||
}
|
}
|
||||||
|
|
||||||
void CollectionFilterWidget::SetQueryMode(QueryOptions::QueryMode query_mode) {
|
void CollectionFilterWidget::SetFilterMode(CollectionFilterOptions::FilterMode filter_mode) {
|
||||||
|
|
||||||
ui_->search_field->clear();
|
ui_->search_field->clear();
|
||||||
ui_->search_field->setEnabled(query_mode == QueryOptions::QueryMode_All);
|
ui_->search_field->setEnabled(filter_mode == CollectionFilterOptions::FilterMode::All);
|
||||||
|
|
||||||
model_->SetFilterQueryMode(query_mode);
|
model_->SetFilterMode(filter_mode);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -508,7 +511,7 @@ void CollectionFilterWidget::FilterTextChanged(const QString &text) {
|
|||||||
// Searching with one or two characters can be very expensive on the database even with FTS,
|
// Searching with one or two characters can be very expensive on the database even with FTS,
|
||||||
// so if there are a large number of songs in the database introduce a small delay before actually filtering the model,
|
// so if there are a large number of songs in the database introduce a small delay before actually filtering the model,
|
||||||
// so if the user is typing the first few characters of something it will be quicker.
|
// so if the user is typing the first few characters of something it will be quicker.
|
||||||
const bool delay = (delay_behaviour_ == AlwaysDelayed) || (delay_behaviour_ == DelayedOnLargeLibraries && !text.isEmpty() && text.length() < 3 && model_->total_song_count() >= 100000);
|
const bool delay = (delay_behaviour_ == DelayBehaviour::AlwaysDelayed) || (delay_behaviour_ == DelayBehaviour::DelayedOnLargeLibraries && !text.isEmpty() && text.length() < 3 && model_->total_song_count() >= 100000);
|
||||||
|
|
||||||
if (delay) {
|
if (delay) {
|
||||||
filter_delay_->start();
|
filter_delay_->start();
|
||||||
|
|||||||
@@ -32,6 +32,7 @@
|
|||||||
#include <QString>
|
#include <QString>
|
||||||
|
|
||||||
#include "collectionquery.h"
|
#include "collectionquery.h"
|
||||||
|
#include "collectionqueryoptions.h"
|
||||||
#include "collectionmodel.h"
|
#include "collectionmodel.h"
|
||||||
|
|
||||||
class QTimer;
|
class QTimer;
|
||||||
@@ -53,7 +54,7 @@ class CollectionFilterWidget : public QWidget {
|
|||||||
|
|
||||||
static const int kFilterDelay = 500; // msec
|
static const int kFilterDelay = 500; // msec
|
||||||
|
|
||||||
enum DelayBehaviour {
|
enum class DelayBehaviour {
|
||||||
AlwaysInstant,
|
AlwaysInstant,
|
||||||
DelayedOnLargeLibraries,
|
DelayedOnLargeLibraries,
|
||||||
AlwaysDelayed,
|
AlwaysDelayed,
|
||||||
@@ -88,7 +89,7 @@ class CollectionFilterWidget : public QWidget {
|
|||||||
|
|
||||||
public slots:
|
public slots:
|
||||||
void UpdateGroupByActions();
|
void UpdateGroupByActions();
|
||||||
void SetQueryMode(QueryOptions::QueryMode query_mode);
|
void SetFilterMode(CollectionFilterOptions::FilterMode filter_mode);
|
||||||
void FocusOnFilter(QKeyEvent *e);
|
void FocusOnFilter(QKeyEvent *e);
|
||||||
|
|
||||||
signals:
|
signals:
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -49,7 +49,9 @@
|
|||||||
#include "core/song.h"
|
#include "core/song.h"
|
||||||
#include "core/sqlrow.h"
|
#include "core/sqlrow.h"
|
||||||
#include "covermanager/albumcoverloader.h"
|
#include "covermanager/albumcoverloader.h"
|
||||||
|
#include "collectionfilteroptions.h"
|
||||||
#include "collectionquery.h"
|
#include "collectionquery.h"
|
||||||
|
#include "collectionqueryoptions.h"
|
||||||
#include "collectionitem.h"
|
#include "collectionitem.h"
|
||||||
#include "covermanager/albumcoverloaderoptions.h"
|
#include "covermanager/albumcoverloaderoptions.h"
|
||||||
|
|
||||||
@@ -81,34 +83,34 @@ 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 class GroupBy {
|
||||||
GroupBy_None = 0,
|
None = 0,
|
||||||
GroupBy_AlbumArtist = 1,
|
AlbumArtist = 1,
|
||||||
GroupBy_Artist = 2,
|
Artist = 2,
|
||||||
GroupBy_Album = 3,
|
Album = 3,
|
||||||
GroupBy_AlbumDisc = 4,
|
AlbumDisc = 4,
|
||||||
GroupBy_YearAlbum = 5,
|
YearAlbum = 5,
|
||||||
GroupBy_YearAlbumDisc = 6,
|
YearAlbumDisc = 6,
|
||||||
GroupBy_OriginalYearAlbum = 7,
|
OriginalYearAlbum = 7,
|
||||||
GroupBy_OriginalYearAlbumDisc = 8,
|
OriginalYearAlbumDisc = 8,
|
||||||
GroupBy_Disc = 9,
|
Disc = 9,
|
||||||
GroupBy_Year = 10,
|
Year = 10,
|
||||||
GroupBy_OriginalYear = 11,
|
OriginalYear = 11,
|
||||||
GroupBy_Genre = 12,
|
Genre = 12,
|
||||||
GroupBy_Composer = 13,
|
Composer = 13,
|
||||||
GroupBy_Performer = 14,
|
Performer = 14,
|
||||||
GroupBy_Grouping = 15,
|
Grouping = 15,
|
||||||
GroupBy_FileType = 16,
|
FileType = 16,
|
||||||
GroupBy_Format = 17,
|
Format = 17,
|
||||||
GroupBy_Samplerate = 18,
|
Samplerate = 18,
|
||||||
GroupBy_Bitdepth = 19,
|
Bitdepth = 19,
|
||||||
GroupBy_Bitrate = 20,
|
Bitrate = 20,
|
||||||
GroupByCount = 21,
|
GroupByCount = 21,
|
||||||
};
|
};
|
||||||
Q_ENUM(GroupBy)
|
Q_ENUM(GroupBy)
|
||||||
|
|
||||||
struct Grouping {
|
struct Grouping {
|
||||||
explicit Grouping(GroupBy f = GroupBy_None, GroupBy s = GroupBy_None, GroupBy t = GroupBy_None)
|
explicit Grouping(GroupBy f = GroupBy::None, GroupBy s = GroupBy::None, GroupBy t = GroupBy::None)
|
||||||
: first(f), second(s), third(t) {}
|
: first(f), second(s), third(t) {}
|
||||||
|
|
||||||
GroupBy first;
|
GroupBy first;
|
||||||
@@ -179,9 +181,9 @@ class CollectionModel : public SimpleTreeModel<CollectionItem> {
|
|||||||
quint64 icon_cache_disk_size() { return sIconCache->cacheSize(); }
|
quint64 icon_cache_disk_size() { return sIconCache->cacheSize(); }
|
||||||
|
|
||||||
static bool IsArtistGroupBy(const GroupBy group_by) {
|
static bool IsArtistGroupBy(const GroupBy group_by) {
|
||||||
return group_by == CollectionModel::GroupBy_Artist || group_by == CollectionModel::GroupBy_AlbumArtist;
|
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; }
|
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; }
|
void set_use_lazy_loading(const bool value) { use_lazy_loading_ = value; }
|
||||||
|
|
||||||
@@ -203,9 +205,9 @@ class CollectionModel : public SimpleTreeModel<CollectionItem> {
|
|||||||
void GroupingChanged(CollectionModel::Grouping g, bool separate_albums_by_grouping);
|
void GroupingChanged(CollectionModel::Grouping g, bool separate_albums_by_grouping);
|
||||||
|
|
||||||
public slots:
|
public slots:
|
||||||
void SetFilterAge(const int age);
|
void SetFilterMode(CollectionFilterOptions::FilterMode filter_mode);
|
||||||
void SetFilterText(const QString &text);
|
void SetFilterAge(const int filter_age);
|
||||||
void SetFilterQueryMode(QueryOptions::QueryMode query_mode);
|
void SetFilterText(const QString &filter_text);
|
||||||
|
|
||||||
void Init(const bool async = true);
|
void Init(const bool async = true);
|
||||||
void Reset();
|
void Reset();
|
||||||
@@ -232,20 +234,21 @@ class CollectionModel : public SimpleTreeModel<CollectionItem> {
|
|||||||
void AlbumCoverLoaded(const quint64 id, const AlbumCoverLoaderResult &result);
|
void AlbumCoverLoaded(const quint64 id, const AlbumCoverLoaderResult &result);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
// Provides some optimisations for loading the list of items in the root.
|
// Provides some optimizations for loading the list of items in the root.
|
||||||
// This gets called a lot when filtering the playlist, so it's nice to be able to do it in a background thread.
|
// This gets called a lot when filtering the playlist, so it's nice to be able to do it in a background thread.
|
||||||
QueryResult RunQuery(CollectionItem *parent);
|
CollectionQueryOptions PrepareQuery(CollectionItem *parent);
|
||||||
|
QueryResult RunQuery(const CollectionFilterOptions &filter_options = CollectionFilterOptions(), const CollectionQueryOptions &query_options = CollectionQueryOptions());
|
||||||
void PostQuery(CollectionItem *parent, const QueryResult &result, const bool signal);
|
void PostQuery(CollectionItem *parent, const QueryResult &result, const bool signal);
|
||||||
|
|
||||||
bool HasCompilations(const QSqlDatabase &db, const CollectionQuery &query);
|
bool HasCompilations(const QSqlDatabase &db, const CollectionFilterOptions &filter_options, const CollectionQueryOptions &query_options);
|
||||||
|
|
||||||
void BeginReset();
|
void BeginReset();
|
||||||
|
|
||||||
// Functions for working with queries and creating items.
|
// Functions for working with queries and creating items.
|
||||||
// When the model is reset or when a node is lazy-loaded the Collection constructs a database query to populate the items.
|
// When the model is reset or when a node is lazy-loaded the Collection constructs a database query to populate the items.
|
||||||
// Filters are added for each parent item, restricting the songs returned to a particular album or artist for example.
|
// Filters are added for each parent item, restricting the songs returned to a particular album or artist for example.
|
||||||
static void InitQuery(const GroupBy group_by, const bool separate_albums_by_grouping, CollectionQuery *q);
|
static void SetQueryColumnSpec(const GroupBy group_by, const bool separate_albums_by_grouping, CollectionQueryOptions *query_options);
|
||||||
static void FilterQuery(const GroupBy group_by, const bool separate_albums_by_grouping, CollectionItem *item, CollectionQuery *q);
|
static void AddQueryWhere(const GroupBy group_by, const bool separate_albums_by_grouping, CollectionItem *item, CollectionQueryOptions *query_options);
|
||||||
|
|
||||||
// Items can be created either from a query that's been run to populate a node, or by a spontaneous SongsDiscovered emission from the backend.
|
// Items can be created either from a query that's been run to populate a node, or by a spontaneous SongsDiscovered emission from the backend.
|
||||||
CollectionItem *ItemFromQuery(const GroupBy group_by, const bool separate_albums_by_grouping, const bool signal, const bool create_divider, CollectionItem *parent, const SqlRow &row, const int container_level);
|
CollectionItem *ItemFromQuery(const GroupBy group_by, const bool separate_albums_by_grouping, const bool signal, const bool create_divider, CollectionItem *parent, const SqlRow &row, const int container_level);
|
||||||
@@ -279,7 +282,7 @@ class CollectionModel : public SimpleTreeModel<CollectionItem> {
|
|||||||
int total_artist_count_;
|
int total_artist_count_;
|
||||||
int total_album_count_;
|
int total_album_count_;
|
||||||
|
|
||||||
QueryOptions query_options_;
|
CollectionFilterOptions filter_options_;
|
||||||
Grouping group_by_;
|
Grouping group_by_;
|
||||||
bool separate_albums_by_grouping_;
|
bool separate_albums_by_grouping_;
|
||||||
|
|
||||||
@@ -308,7 +311,7 @@ class CollectionModel : public SimpleTreeModel<CollectionItem> {
|
|||||||
|
|
||||||
AlbumCoverLoaderOptions cover_loader_options_;
|
AlbumCoverLoaderOptions cover_loader_options_;
|
||||||
|
|
||||||
typedef QPair<CollectionItem*, QString> ItemAndCacheKey;
|
using ItemAndCacheKey = QPair<CollectionItem*, QString>;
|
||||||
QMap<quint64, ItemAndCacheKey> pending_art_;
|
QMap<quint64, ItemAndCacheKey> pending_art_;
|
||||||
QSet<QString> pending_cache_keys_;
|
QSet<QString> pending_cache_keys_;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -29,12 +29,12 @@
|
|||||||
|
|
||||||
class SqlRow;
|
class SqlRow;
|
||||||
|
|
||||||
CollectionPlaylistItem::CollectionPlaylistItem() : PlaylistItem(Song::Source_Collection) {
|
CollectionPlaylistItem::CollectionPlaylistItem() : PlaylistItem(Song::Source::Collection) {
|
||||||
song_.set_source(Song::Source_Collection);
|
song_.set_source(Song::Source::Collection);
|
||||||
}
|
}
|
||||||
|
|
||||||
CollectionPlaylistItem::CollectionPlaylistItem(const Song &song) : PlaylistItem(Song::Source_Collection), song_(song) {
|
CollectionPlaylistItem::CollectionPlaylistItem(const Song &song) : PlaylistItem(Song::Source::Collection), song_(song) {
|
||||||
song_.set_source(Song::Source_Collection);
|
song_.set_source(Song::Source::Collection);
|
||||||
}
|
}
|
||||||
|
|
||||||
QUrl CollectionPlaylistItem::Url() const { return song_.url(); }
|
QUrl CollectionPlaylistItem::Url() const { return song_.url(); }
|
||||||
@@ -50,7 +50,7 @@ bool CollectionPlaylistItem::InitFromQuery(const SqlRow &query) {
|
|||||||
|
|
||||||
// Rows from the songs tables come first
|
// Rows from the songs tables come first
|
||||||
song_.InitFromQuery(query, true);
|
song_.InitFromQuery(query, true);
|
||||||
song_.set_source(Song::Source_Collection);
|
song_.set_source(Song::Source::Collection);
|
||||||
return song_.is_valid();
|
return song_.is_valid();
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -52,7 +52,7 @@ class CollectionPlaylistItem : public PlaylistItem {
|
|||||||
|
|
||||||
protected:
|
protected:
|
||||||
QVariant DatabaseValue(DatabaseColumn column) const override;
|
QVariant DatabaseValue(DatabaseColumn column) const override;
|
||||||
Song DatabaseSongMetadata() const override { return Song(Song::Source_Collection); }
|
Song DatabaseSongMetadata() const override { return Song(Song::Source::Collection); }
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
Song song_;
|
Song song_;
|
||||||
|
|||||||
@@ -31,16 +31,16 @@
|
|||||||
#include <QRegularExpression>
|
#include <QRegularExpression>
|
||||||
#include <QSqlDatabase>
|
#include <QSqlDatabase>
|
||||||
#include <QSqlQuery>
|
#include <QSqlQuery>
|
||||||
#include <QSqlError>
|
|
||||||
|
|
||||||
#include "core/logging.h"
|
#include "core/logging.h"
|
||||||
|
#include "core/sqlquery.h"
|
||||||
#include "core/song.h"
|
#include "core/song.h"
|
||||||
|
|
||||||
#include "collectionquery.h"
|
#include "collectionquery.h"
|
||||||
|
#include "collectionfilteroptions.h"
|
||||||
|
#include "collectionqueryoptions.h"
|
||||||
|
|
||||||
QueryOptions::QueryOptions() : max_age_(-1), query_mode_(QueryMode_All) {}
|
CollectionQuery::CollectionQuery(const QSqlDatabase &db, const QString &songs_table, const QString &fts_table, const CollectionFilterOptions &filter_options)
|
||||||
|
|
||||||
CollectionQuery::CollectionQuery(const QSqlDatabase &db, const QString &songs_table, const QString &fts_table, const QueryOptions &options)
|
|
||||||
: QSqlQuery(db),
|
: QSqlQuery(db),
|
||||||
songs_table_(songs_table),
|
songs_table_(songs_table),
|
||||||
fts_table_(fts_table),
|
fts_table_(fts_table),
|
||||||
@@ -49,7 +49,7 @@ CollectionQuery::CollectionQuery(const QSqlDatabase &db, const QString &songs_ta
|
|||||||
duplicates_only_(false),
|
duplicates_only_(false),
|
||||||
limit_(-1) {
|
limit_(-1) {
|
||||||
|
|
||||||
if (!options.filter().isEmpty()) {
|
if (!filter_options.filter_text().isEmpty()) {
|
||||||
// We need to munge the filter text a little bit to get it to work as expected with sqlite's FTS5:
|
// We need to munge the filter text a little bit to get it to work as expected with sqlite's FTS5:
|
||||||
// 1) Append * to all tokens.
|
// 1) Append * to all tokens.
|
||||||
// 2) Prefix "fts" to column names.
|
// 2) Prefix "fts" to column names.
|
||||||
@@ -57,9 +57,9 @@ CollectionQuery::CollectionQuery(const QSqlDatabase &db, const QString &songs_ta
|
|||||||
|
|
||||||
// Split on whitespace
|
// Split on whitespace
|
||||||
#if QT_VERSION >= QT_VERSION_CHECK(5, 14, 0)
|
#if QT_VERSION >= QT_VERSION_CHECK(5, 14, 0)
|
||||||
QStringList tokens(options.filter().split(QRegularExpression("\\s+"), Qt::SkipEmptyParts));
|
QStringList tokens(filter_options.filter_text().split(QRegularExpression("\\s+"), Qt::SkipEmptyParts));
|
||||||
#else
|
#else
|
||||||
QStringList tokens(options.filter().split(QRegularExpression("\\s+"), QString::SkipEmptyParts));
|
QStringList tokens(filter_options.filter_text().split(QRegularExpression("\\s+"), QString::SkipEmptyParts));
|
||||||
#endif
|
#endif
|
||||||
QString query;
|
QString query;
|
||||||
for (QString token : tokens) {
|
for (QString token : tokens) {
|
||||||
@@ -100,49 +100,40 @@ CollectionQuery::CollectionQuery(const QSqlDatabase &db, const QString &songs_ta
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (options.max_age() != -1) {
|
if (filter_options.max_age() != -1) {
|
||||||
qint64 cutoff = QDateTime::currentDateTime().toSecsSinceEpoch() - options.max_age();
|
qint64 cutoff = QDateTime::currentDateTime().toSecsSinceEpoch() - filter_options.max_age();
|
||||||
|
|
||||||
where_clauses_ << "ctime > ?";
|
where_clauses_ << "ctime > ?";
|
||||||
bound_values_ << cutoff;
|
bound_values_ << cutoff;
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: Currently you cannot use any QueryMode other than All and FTS at the same time.
|
// TODO: Currently you cannot use any FilterMode other than All and FTS at the same time.
|
||||||
// Joining songs, duplicated_songs and songs_fts all together takes a huge amount of time.
|
// Joining songs, duplicated_songs and songs_fts all together takes a huge amount of time.
|
||||||
// The query takes about 20 seconds on my machine then. Why?
|
// The query takes about 20 seconds on my machine then. Why?
|
||||||
// Untagged mode could work with additional filtering but I'm disabling it just to be consistent
|
// Untagged mode could work with additional filtering but I'm disabling it just to be consistent
|
||||||
// this way filtering is available only in the All mode.
|
// this way filtering is available only in the All mode.
|
||||||
// Remember though that when you fix the Duplicates + FTS cooperation, enable the filtering in both Duplicates and Untagged modes.
|
// Remember though that when you fix the Duplicates + FTS cooperation, enable the filtering in both Duplicates and Untagged modes.
|
||||||
duplicates_only_ = options.query_mode() == QueryOptions::QueryMode_Duplicates;
|
duplicates_only_ = filter_options.filter_mode() == CollectionFilterOptions::FilterMode::Duplicates;
|
||||||
|
|
||||||
if (options.query_mode() == QueryOptions::QueryMode_Untagged) {
|
if (filter_options.filter_mode() == CollectionFilterOptions::FilterMode::Untagged) {
|
||||||
where_clauses_ << "(artist = '' OR album = '' OR title ='')";
|
where_clauses_ << "(artist = '' OR album = '' OR title ='')";
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
QString CollectionQuery::GetInnerQuery() const {
|
|
||||||
return duplicates_only_
|
|
||||||
? QString(" INNER JOIN (select * from duplicated_songs) dsongs "
|
|
||||||
"ON (%songs_table.artist = dsongs.dup_artist "
|
|
||||||
"AND %songs_table.album = dsongs.dup_album "
|
|
||||||
"AND %songs_table.title = dsongs.dup_title) ")
|
|
||||||
: QString();
|
|
||||||
}
|
|
||||||
|
|
||||||
void CollectionQuery::AddWhere(const QString &column, const QVariant &value, const QString &op) {
|
void CollectionQuery::AddWhere(const QString &column, const QVariant &value, const QString &op) {
|
||||||
|
|
||||||
// Ignore 'literal' for IN
|
// Ignore 'literal' for IN
|
||||||
if (op.compare("IN", Qt::CaseInsensitive) == 0) {
|
if (op.compare("IN", Qt::CaseInsensitive) == 0) {
|
||||||
QStringList values = value.toStringList();
|
QStringList values = value.toStringList();
|
||||||
QStringList final;
|
QStringList final_values;
|
||||||
final.reserve(values.count());
|
final_values.reserve(values.count());
|
||||||
for (const QString &single_value : values) {
|
for (const QString &single_value : values) {
|
||||||
final.append("?");
|
final_values.append("?");
|
||||||
bound_values_ << single_value;
|
bound_values_ << single_value;
|
||||||
}
|
}
|
||||||
|
|
||||||
where_clauses_ << QString("%1 IN (" + final.join(",") + ")").arg(column);
|
where_clauses_ << QString("%1 IN (" + final_values.join(",") + ")").arg(column);
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
// Do integers inline - sqlite seems to get confused when you pass integers to bound parameters
|
// Do integers inline - sqlite seems to get confused when you pass integers to bound parameters
|
||||||
@@ -187,6 +178,15 @@ void CollectionQuery::AddCompilationRequirement(const bool compilation) {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
QString CollectionQuery::GetInnerQuery() const {
|
||||||
|
return duplicates_only_
|
||||||
|
? QString(" INNER JOIN (select * from duplicated_songs) dsongs "
|
||||||
|
"ON (%songs_table.artist = dsongs.dup_artist "
|
||||||
|
"AND %songs_table.album = dsongs.dup_album "
|
||||||
|
"AND %songs_table.title = dsongs.dup_title) ")
|
||||||
|
: QString();
|
||||||
|
}
|
||||||
|
|
||||||
bool CollectionQuery::Exec() {
|
bool CollectionQuery::Exec() {
|
||||||
|
|
||||||
QString sql;
|
QString sql;
|
||||||
@@ -213,32 +213,17 @@ bool CollectionQuery::Exec() {
|
|||||||
sql.replace("%fts_table_noprefix", fts_table_.section('.', -1, -1));
|
sql.replace("%fts_table_noprefix", fts_table_.section('.', -1, -1));
|
||||||
sql.replace("%fts_table", fts_table_);
|
sql.replace("%fts_table", fts_table_);
|
||||||
|
|
||||||
prepare(sql);
|
QSqlQuery::prepare(sql);
|
||||||
|
|
||||||
// Bind values
|
// Bind values
|
||||||
for (const QVariant &value : bound_values_) {
|
for (const QVariant &value : bound_values_) {
|
||||||
addBindValue(value);
|
QSqlQuery::addBindValue(value);
|
||||||
}
|
}
|
||||||
|
|
||||||
return exec();
|
return QSqlQuery::exec();
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bool CollectionQuery::Next() { return next(); }
|
bool CollectionQuery::Next() { return QSqlQuery::next(); }
|
||||||
|
|
||||||
QVariant CollectionQuery::Value(const int column) const { return value(column); }
|
QVariant CollectionQuery::Value(const int column) const { return QSqlQuery::value(column); }
|
||||||
|
|
||||||
bool QueryOptions::Matches(const Song &song) const {
|
|
||||||
|
|
||||||
if (max_age_ != -1) {
|
|
||||||
const qint64 cutoff = QDateTime::currentDateTime().toSecsSinceEpoch() - max_age_;
|
|
||||||
if (song.ctime() <= cutoff) return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!filter_.isNull()) {
|
|
||||||
return song.artist().contains(filter_, Qt::CaseInsensitive) || song.album().contains(filter_, Qt::CaseInsensitive) || song.title().contains(filter_, Qt::CaseInsensitive);
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -28,75 +28,23 @@
|
|||||||
#include <QVariant>
|
#include <QVariant>
|
||||||
#include <QString>
|
#include <QString>
|
||||||
#include <QStringList>
|
#include <QStringList>
|
||||||
#include <QMap>
|
|
||||||
#include <QSqlDatabase>
|
#include <QSqlDatabase>
|
||||||
#include <QSqlQuery>
|
#include <QSqlQuery>
|
||||||
|
|
||||||
class Song;
|
#include "collectionfilteroptions.h"
|
||||||
|
#include "collectionqueryoptions.h"
|
||||||
// This structure let's you customize behaviour of any CollectionQuery.
|
|
||||||
struct QueryOptions {
|
|
||||||
// Modes of CollectionQuery:
|
|
||||||
// - use the all songs table
|
|
||||||
// - use the duplicated songs view; by duplicated we mean those songs for which the (artist, album, title) tuple is found more than once in the songs table
|
|
||||||
// - use the untagged songs view; by untagged we mean those for which at least one of the (artist, album, title) tags is empty
|
|
||||||
// Please note that additional filtering based on FTS table (the filter attribute) won't work in Duplicates and Untagged modes.
|
|
||||||
enum QueryMode {
|
|
||||||
QueryMode_All,
|
|
||||||
QueryMode_Duplicates,
|
|
||||||
QueryMode_Untagged
|
|
||||||
};
|
|
||||||
|
|
||||||
QueryOptions();
|
|
||||||
|
|
||||||
bool Matches(const Song &song) const;
|
|
||||||
|
|
||||||
QString filter() const { return filter_; }
|
|
||||||
void set_filter(const QString &filter) {
|
|
||||||
filter_ = filter;
|
|
||||||
query_mode_ = QueryMode_All;
|
|
||||||
}
|
|
||||||
|
|
||||||
int max_age() const { return max_age_; }
|
|
||||||
void set_max_age(int max_age) { max_age_ = max_age; }
|
|
||||||
|
|
||||||
QueryMode query_mode() const { return query_mode_; }
|
|
||||||
void set_query_mode(QueryMode query_mode) {
|
|
||||||
query_mode_ = query_mode;
|
|
||||||
filter_ = QString();
|
|
||||||
}
|
|
||||||
|
|
||||||
private:
|
|
||||||
QString filter_;
|
|
||||||
int max_age_;
|
|
||||||
QueryMode query_mode_;
|
|
||||||
};
|
|
||||||
|
|
||||||
class CollectionQuery : public QSqlQuery {
|
class CollectionQuery : public QSqlQuery {
|
||||||
public:
|
public:
|
||||||
explicit CollectionQuery(const QSqlDatabase &db, const QString &songs_table, const QString &fts_table, const QueryOptions &options = QueryOptions());
|
explicit CollectionQuery(const QSqlDatabase &db, const QString &songs_table, const QString &fts_table, const CollectionFilterOptions &filter_options = CollectionFilterOptions());
|
||||||
|
|
||||||
// Sets contents of SELECT clause on the query (list of columns to get).
|
QVariant Value(const int column) const;
|
||||||
void SetColumnSpec(const QString &spec) { column_spec_ = spec; }
|
QVariant value(const int column) const { return Value(column); }
|
||||||
|
|
||||||
// Sets an ORDER BY clause on the query.
|
|
||||||
void SetOrderBy(const QString &order_by) { order_by_ = order_by; }
|
|
||||||
|
|
||||||
// Adds a fragment of WHERE clause. When executed, this Query will connect all the fragments with AND operator.
|
|
||||||
// Please note that IN operator expects a QStringList as value.
|
|
||||||
void AddWhere(const QString &column, const QVariant &value, const QString &op = "=");
|
|
||||||
void AddWhereArtist(const QVariant &value);
|
|
||||||
|
|
||||||
void SetWhereClauses(const QStringList &where_clauses) { where_clauses_ = where_clauses; }
|
|
||||||
void SetBoundValues(const QVariantList &bound_values) { bound_values_ = bound_values; }
|
|
||||||
void SetDuplicatesOnly(const bool duplicates_only) { duplicates_only_ = duplicates_only; }
|
|
||||||
void SetIncludeUnavailable(const bool include_unavailable) { include_unavailable_ = include_unavailable; }
|
|
||||||
void SetLimit(const int limit) { limit_ = limit; }
|
|
||||||
void AddCompilationRequirement(const bool compilation);
|
|
||||||
|
|
||||||
bool Exec();
|
bool Exec();
|
||||||
|
bool exec() { return QSqlQuery::exec(); }
|
||||||
|
|
||||||
bool Next();
|
bool Next();
|
||||||
QVariant Value(const int column) const;
|
|
||||||
|
|
||||||
QString column_spec() const { return column_spec_; }
|
QString column_spec() const { return column_spec_; }
|
||||||
QString order_by() const { return order_by_; }
|
QString order_by() const { return order_by_; }
|
||||||
@@ -107,6 +55,24 @@ class CollectionQuery : public QSqlQuery {
|
|||||||
bool duplicates_only() const { return duplicates_only_; }
|
bool duplicates_only() const { return duplicates_only_; }
|
||||||
int limit() const { return limit_; }
|
int limit() const { return limit_; }
|
||||||
|
|
||||||
|
// Sets contents of SELECT clause on the query (list of columns to get).
|
||||||
|
void SetColumnSpec(const QString &column_spec) { column_spec_ = column_spec; }
|
||||||
|
|
||||||
|
// Sets an ORDER BY clause on the query.
|
||||||
|
void SetOrderBy(const QString &order_by) { order_by_ = order_by; }
|
||||||
|
|
||||||
|
void SetWhereClauses(const QStringList &where_clauses) { where_clauses_ = where_clauses; }
|
||||||
|
// Adds a fragment of WHERE clause. When executed, this Query will connect all the fragments with AND operator.
|
||||||
|
// Please note that IN operator expects a QStringList as value.
|
||||||
|
void AddWhere(const QString &column, const QVariant &value, const QString &op = "=");
|
||||||
|
void AddWhereArtist(const QVariant &value);
|
||||||
|
|
||||||
|
void SetBoundValues(const QVariantList &bound_values) { bound_values_ = bound_values; }
|
||||||
|
void SetDuplicatesOnly(const bool duplicates_only) { duplicates_only_ = duplicates_only; }
|
||||||
|
void SetIncludeUnavailable(const bool include_unavailable) { include_unavailable_ = include_unavailable; }
|
||||||
|
void SetLimit(const int limit) { limit_ = limit; }
|
||||||
|
void AddCompilationRequirement(const bool compilation);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
QString GetInnerQuery() const;
|
QString GetInnerQuery() const;
|
||||||
|
|
||||||
|
|||||||
34
src/collection/collectionqueryoptions.cpp
Normal file
34
src/collection/collectionqueryoptions.cpp
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
/*
|
||||||
|
* Strawberry Music Player
|
||||||
|
* Copyright 2023, Jonas Kvinge <jonas@jkvinge.net>
|
||||||
|
*
|
||||||
|
* Strawberry is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* Strawberry is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with Strawberry. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <QVariant>
|
||||||
|
#include <QString>
|
||||||
|
|
||||||
|
#include "collectionqueryoptions.h"
|
||||||
|
#include "collectionfilteroptions.h"
|
||||||
|
|
||||||
|
CollectionQueryOptions::CollectionQueryOptions()
|
||||||
|
: compilation_requirement_(CollectionQueryOptions::CompilationRequirement::None),
|
||||||
|
query_have_compilations_(false) {}
|
||||||
|
|
||||||
|
void CollectionQueryOptions::AddWhere(const QString &column, const QVariant &value, const QString &op) {
|
||||||
|
|
||||||
|
where_clauses_ << Where(column, value, op);
|
||||||
|
|
||||||
|
}
|
||||||
63
src/collection/collectionqueryoptions.h
Normal file
63
src/collection/collectionqueryoptions.h
Normal file
@@ -0,0 +1,63 @@
|
|||||||
|
/*
|
||||||
|
* Strawberry Music Player
|
||||||
|
* Copyright 2023, Jonas Kvinge <jonas@jkvinge.net>
|
||||||
|
*
|
||||||
|
* Strawberry is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* Strawberry is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with Strawberry. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef COLLECTIONQUERYOPTIONS_H
|
||||||
|
#define COLLECTIONQUERYOPTIONS_H
|
||||||
|
|
||||||
|
#include <QList>
|
||||||
|
#include <QVariant>
|
||||||
|
#include <QString>
|
||||||
|
|
||||||
|
class CollectionQueryOptions {
|
||||||
|
public:
|
||||||
|
|
||||||
|
explicit CollectionQueryOptions();
|
||||||
|
|
||||||
|
struct Where {
|
||||||
|
explicit Where(const QString _column = QString(), const QVariant _value = QString(), const QString _op = QString()) : column(_column), value(_value), op(_op) {}
|
||||||
|
QString column;
|
||||||
|
QVariant value;
|
||||||
|
QString op;
|
||||||
|
};
|
||||||
|
|
||||||
|
enum class CompilationRequirement {
|
||||||
|
None,
|
||||||
|
On,
|
||||||
|
Off
|
||||||
|
};
|
||||||
|
|
||||||
|
QString column_spec() const { return column_spec_; }
|
||||||
|
CompilationRequirement compilation_requirement() const { return compilation_requirement_; }
|
||||||
|
bool query_have_compilations() const { return query_have_compilations_; }
|
||||||
|
|
||||||
|
void set_column_spec(const QString &column_spec) { column_spec_ = column_spec; }
|
||||||
|
void set_compilation_requirement(const CompilationRequirement compilation_requirement) { compilation_requirement_ = compilation_requirement; }
|
||||||
|
void set_query_have_compilations(const bool query_have_compilations) { query_have_compilations_ = query_have_compilations; }
|
||||||
|
|
||||||
|
QList<Where> where_clauses() const { return where_clauses_; }
|
||||||
|
void AddWhere(const QString &column, const QVariant &value, const QString &op = "=");
|
||||||
|
|
||||||
|
private:
|
||||||
|
QString column_spec_;
|
||||||
|
CompilationRequirement compilation_requirement_;
|
||||||
|
bool query_have_compilations_;
|
||||||
|
QList<Where> where_clauses_;
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif // COLLECTIONQUERYOPTIONS_H
|
||||||
@@ -50,9 +50,9 @@
|
|||||||
#include "core/application.h"
|
#include "core/application.h"
|
||||||
#include "core/iconloader.h"
|
#include "core/iconloader.h"
|
||||||
#include "core/mimedata.h"
|
#include "core/mimedata.h"
|
||||||
#include "core/utilities.h"
|
|
||||||
#include "core/musicstorage.h"
|
#include "core/musicstorage.h"
|
||||||
#include "core/deletefiles.h"
|
#include "core/deletefiles.h"
|
||||||
|
#include "utilities/filemanagerutils.h"
|
||||||
#include "collection.h"
|
#include "collection.h"
|
||||||
#include "collectionbackend.h"
|
#include "collectionbackend.h"
|
||||||
#include "collectiondirectorymodel.h"
|
#include "collectiondirectorymodel.h"
|
||||||
@@ -699,7 +699,7 @@ void CollectionView::DeleteFilesFinished(const SongList &songs_with_errors) {
|
|||||||
if (songs_with_errors.isEmpty()) return;
|
if (songs_with_errors.isEmpty()) return;
|
||||||
|
|
||||||
OrganizeErrorDialog *dialog = new OrganizeErrorDialog(this);
|
OrganizeErrorDialog *dialog = new OrganizeErrorDialog(this);
|
||||||
dialog->Show(OrganizeErrorDialog::Type_Delete, songs_with_errors);
|
dialog->Show(OrganizeErrorDialog::OperationType::Delete, songs_with_errors);
|
||||||
// It deletes itself when the user closes it
|
// It deletes itself when the user closes it
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -46,11 +46,11 @@
|
|||||||
|
|
||||||
#include "core/filesystemwatcherinterface.h"
|
#include "core/filesystemwatcherinterface.h"
|
||||||
#include "core/logging.h"
|
#include "core/logging.h"
|
||||||
#include "core/timeconstants.h"
|
|
||||||
#include "core/tagreaderclient.h"
|
#include "core/tagreaderclient.h"
|
||||||
#include "core/taskmanager.h"
|
#include "core/taskmanager.h"
|
||||||
#include "core/imageutils.h"
|
#include "utilities/imageutils.h"
|
||||||
#include "directory.h"
|
#include "utilities/timeconstants.h"
|
||||||
|
#include "collectiondirectory.h"
|
||||||
#include "collectionbackend.h"
|
#include "collectionbackend.h"
|
||||||
#include "collectionwatcher.h"
|
#include "collectionwatcher.h"
|
||||||
#include "playlistparsers/cueparser.h"
|
#include "playlistparsers/cueparser.h"
|
||||||
@@ -78,13 +78,12 @@ CollectionWatcher::CollectionWatcher(Song::Source source, QObject *parent)
|
|||||||
scan_on_startup_(true),
|
scan_on_startup_(true),
|
||||||
monitor_(true),
|
monitor_(true),
|
||||||
song_tracking_(false),
|
song_tracking_(false),
|
||||||
mark_songs_unavailable_(source_ == Song::Source_Collection),
|
mark_songs_unavailable_(source_ == Song::Source::Collection),
|
||||||
expire_unavailable_songs_days_(60),
|
expire_unavailable_songs_days_(60),
|
||||||
overwrite_playcount_(false),
|
overwrite_playcount_(false),
|
||||||
overwrite_rating_(false),
|
overwrite_rating_(false),
|
||||||
stop_requested_(false),
|
stop_requested_(false),
|
||||||
abort_requested_(false),
|
abort_requested_(false),
|
||||||
rescan_in_progress_(false),
|
|
||||||
rescan_timer_(new QTimer(this)),
|
rescan_timer_(new QTimer(this)),
|
||||||
periodic_scan_timer_(new QTimer(this)),
|
periodic_scan_timer_(new QTimer(this)),
|
||||||
rescan_paused_(false),
|
rescan_paused_(false),
|
||||||
@@ -143,7 +142,7 @@ 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();
|
||||||
QStringList filters = s.value("cover_art_patterns", QStringList() << "front" << "cover").toStringList();
|
QStringList filters = s.value("cover_art_patterns", QStringList() << "front" << "cover").toStringList();
|
||||||
if (source_ == Song::Source_Collection) {
|
if (source_ == Song::Source::Collection) {
|
||||||
song_tracking_ = s.value("song_tracking", false).toBool();
|
song_tracking_ = s.value("song_tracking", false).toBool();
|
||||||
mark_songs_unavailable_ = song_tracking_ ? true : s.value("mark_songs_unavailable", true).toBool();
|
mark_songs_unavailable_ = song_tracking_ ? true : s.value("mark_songs_unavailable", true).toBool();
|
||||||
}
|
}
|
||||||
@@ -167,15 +166,15 @@ void CollectionWatcher::ReloadSettings() {
|
|||||||
}
|
}
|
||||||
else if (monitor_ && !was_monitoring_before) {
|
else if (monitor_ && !was_monitoring_before) {
|
||||||
// Add all directories to all QFileSystemWatchers again
|
// Add all directories to all QFileSystemWatchers again
|
||||||
for (const Directory &dir : std::as_const(watched_dirs_)) {
|
for (const CollectionDirectory &dir : std::as_const(watched_dirs_)) {
|
||||||
SubdirectoryList subdirs = backend_->SubdirsInDirectory(dir.id);
|
CollectionSubdirectoryList subdirs = backend_->SubdirsInDirectory(dir.id);
|
||||||
for (const Subdirectory &subdir : subdirs) {
|
for (const CollectionSubdirectory &subdir : subdirs) {
|
||||||
AddWatch(dir, subdir.path);
|
AddWatch(dir, subdir.path);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (mark_songs_unavailable_ && !periodic_scan_timer_->isActive()) {
|
if (monitor_ && scan_on_startup_ && mark_songs_unavailable_ && !periodic_scan_timer_->isActive()) {
|
||||||
periodic_scan_timer_->start();
|
periodic_scan_timer_->start();
|
||||||
}
|
}
|
||||||
else if (!mark_songs_unavailable_ && periodic_scan_timer_->isActive()) {
|
else if (!mark_songs_unavailable_ && periodic_scan_timer_->isActive()) {
|
||||||
@@ -239,7 +238,7 @@ void CollectionWatcher::ScanTransaction::AddToProgressMax(const quint64 n) {
|
|||||||
void CollectionWatcher::ScanTransaction::CommitNewOrUpdatedSongs() {
|
void CollectionWatcher::ScanTransaction::CommitNewOrUpdatedSongs() {
|
||||||
|
|
||||||
if (!deleted_songs.isEmpty()) {
|
if (!deleted_songs.isEmpty()) {
|
||||||
if (mark_songs_unavailable_ && watcher_->source() == Song::Source_Collection) {
|
if (mark_songs_unavailable_ && watcher_->source() == Song::Source::Collection) {
|
||||||
emit watcher_->SongsUnavailable(deleted_songs);
|
emit watcher_->SongsUnavailable(deleted_songs);
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
@@ -272,7 +271,7 @@ void CollectionWatcher::ScanTransaction::CommitNewOrUpdatedSongs() {
|
|||||||
touched_subdirs.clear();
|
touched_subdirs.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
for (const Subdirectory &subdir : deleted_subdirs) {
|
for (const CollectionSubdirectory &subdir : deleted_subdirs) {
|
||||||
if (watcher_->watched_dirs_.contains(dir_)) {
|
if (watcher_->watched_dirs_.contains(dir_)) {
|
||||||
watcher_->RemoveWatch(watcher_->watched_dirs_[dir_], subdir);
|
watcher_->RemoveWatch(watcher_->watched_dirs_[dir_], subdir);
|
||||||
}
|
}
|
||||||
@@ -281,7 +280,7 @@ void CollectionWatcher::ScanTransaction::CommitNewOrUpdatedSongs() {
|
|||||||
|
|
||||||
if (watcher_->monitor_) {
|
if (watcher_->monitor_) {
|
||||||
// Watch the new subdirectories
|
// Watch the new subdirectories
|
||||||
for (const Subdirectory &subdir : new_subdirs) {
|
for (const CollectionSubdirectory &subdir : new_subdirs) {
|
||||||
if (watcher_->watched_dirs_.contains(dir_)) {
|
if (watcher_->watched_dirs_.contains(dir_)) {
|
||||||
watcher_->AddWatch(watcher_->watched_dirs_[dir_], subdir.path);
|
watcher_->AddWatch(watcher_->watched_dirs_[dir_], subdir.path);
|
||||||
}
|
}
|
||||||
@@ -329,7 +328,7 @@ bool CollectionWatcher::ScanTransaction::HasSongsWithMissingFingerprint(const QS
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void CollectionWatcher::ScanTransaction::SetKnownSubdirs(const SubdirectoryList &subdirs) {
|
void CollectionWatcher::ScanTransaction::SetKnownSubdirs(const CollectionSubdirectoryList &subdirs) {
|
||||||
|
|
||||||
known_subdirs_ = subdirs;
|
known_subdirs_ = subdirs;
|
||||||
known_subdirs_dirty_ = false;
|
known_subdirs_dirty_ = false;
|
||||||
@@ -342,18 +341,18 @@ bool CollectionWatcher::ScanTransaction::HasSeenSubdir(const QString &path) {
|
|||||||
SetKnownSubdirs(watcher_->backend_->SubdirsInDirectory(dir_));
|
SetKnownSubdirs(watcher_->backend_->SubdirsInDirectory(dir_));
|
||||||
}
|
}
|
||||||
|
|
||||||
return std::any_of(known_subdirs_.begin(), known_subdirs_.end(), [path](const Subdirectory &subdir) { return subdir.path == path && subdir.mtime != 0; });
|
return std::any_of(known_subdirs_.begin(), known_subdirs_.end(), [path](const CollectionSubdirectory &subdir) { return subdir.path == path && subdir.mtime != 0; });
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
SubdirectoryList CollectionWatcher::ScanTransaction::GetImmediateSubdirs(const QString &path) {
|
CollectionSubdirectoryList CollectionWatcher::ScanTransaction::GetImmediateSubdirs(const QString &path) {
|
||||||
|
|
||||||
if (known_subdirs_dirty_) {
|
if (known_subdirs_dirty_) {
|
||||||
SetKnownSubdirs(watcher_->backend_->SubdirsInDirectory(dir_));
|
SetKnownSubdirs(watcher_->backend_->SubdirsInDirectory(dir_));
|
||||||
}
|
}
|
||||||
|
|
||||||
SubdirectoryList ret;
|
CollectionSubdirectoryList ret;
|
||||||
for (const Subdirectory &subdir : known_subdirs_) {
|
for (const CollectionSubdirectory &subdir : known_subdirs_) {
|
||||||
if (subdir.path.left(subdir.path.lastIndexOf(QDir::separator())) == path && subdir.mtime != 0) {
|
if (subdir.path.left(subdir.path.lastIndexOf(QDir::separator())) == path && subdir.mtime != 0) {
|
||||||
ret << subdir;
|
ret << subdir;
|
||||||
}
|
}
|
||||||
@@ -363,7 +362,7 @@ SubdirectoryList CollectionWatcher::ScanTransaction::GetImmediateSubdirs(const Q
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
SubdirectoryList CollectionWatcher::ScanTransaction::GetAllSubdirs() {
|
CollectionSubdirectoryList CollectionWatcher::ScanTransaction::GetAllSubdirs() {
|
||||||
|
|
||||||
if (known_subdirs_dirty_) {
|
if (known_subdirs_dirty_) {
|
||||||
SetKnownSubdirs(watcher_->backend_->SubdirsInDirectory(dir_));
|
SetKnownSubdirs(watcher_->backend_->SubdirsInDirectory(dir_));
|
||||||
@@ -373,7 +372,7 @@ SubdirectoryList CollectionWatcher::ScanTransaction::GetAllSubdirs() {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void CollectionWatcher::AddDirectory(const Directory &dir, const SubdirectoryList &subdirs) {
|
void CollectionWatcher::AddDirectory(const CollectionDirectory &dir, const CollectionSubdirectoryList &subdirs) {
|
||||||
|
|
||||||
stop_requested_ = false;
|
stop_requested_ = false;
|
||||||
|
|
||||||
@@ -385,7 +384,7 @@ void CollectionWatcher::AddDirectory(const Directory &dir, const SubdirectoryLis
|
|||||||
const quint64 files_count = FilesCountForPath(&transaction, dir.path);
|
const quint64 files_count = FilesCountForPath(&transaction, dir.path);
|
||||||
transaction.SetKnownSubdirs(subdirs);
|
transaction.SetKnownSubdirs(subdirs);
|
||||||
transaction.AddToProgressMax(files_count);
|
transaction.AddToProgressMax(files_count);
|
||||||
ScanSubdirectory(dir.path, Subdirectory(), files_count, &transaction);
|
ScanSubdirectory(dir.path, CollectionSubdirectory(), files_count, &transaction);
|
||||||
last_scan_time_ = QDateTime::currentDateTime().toSecsSinceEpoch();
|
last_scan_time_ = QDateTime::currentDateTime().toSecsSinceEpoch();
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
@@ -395,7 +394,7 @@ void CollectionWatcher::AddDirectory(const Directory &dir, const SubdirectoryLis
|
|||||||
const quint64 files_count = FilesCountForSubdirs(&transaction, subdirs, subdir_files_count);
|
const quint64 files_count = FilesCountForSubdirs(&transaction, subdirs, subdir_files_count);
|
||||||
transaction.SetKnownSubdirs(subdirs);
|
transaction.SetKnownSubdirs(subdirs);
|
||||||
transaction.AddToProgressMax(files_count);
|
transaction.AddToProgressMax(files_count);
|
||||||
for (const Subdirectory &subdir : subdirs) {
|
for (const CollectionSubdirectory &subdir : subdirs) {
|
||||||
if (stop_requested_ || abort_requested_) break;
|
if (stop_requested_ || abort_requested_) break;
|
||||||
|
|
||||||
if (scan_on_startup_) ScanSubdirectory(subdir.path, subdir, subdir_files_count[subdir.path], &transaction);
|
if (scan_on_startup_) ScanSubdirectory(subdir.path, subdir, subdir_files_count[subdir.path], &transaction);
|
||||||
@@ -411,14 +410,14 @@ void CollectionWatcher::AddDirectory(const Directory &dir, const SubdirectoryLis
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void CollectionWatcher::ScanSubdirectory(const QString &path, const Subdirectory &subdir, const quint64 files_count, ScanTransaction *t, const bool force_noincremental) {
|
void CollectionWatcher::ScanSubdirectory(const QString &path, const CollectionSubdirectory &subdir, const quint64 files_count, ScanTransaction *t, const bool force_noincremental) {
|
||||||
|
|
||||||
QFileInfo path_info(path);
|
QFileInfo path_info(path);
|
||||||
|
|
||||||
// Do not scan symlinked dirs that are already in collection
|
// Do not scan symlinked dirs that are already in collection
|
||||||
if (path_info.isSymLink()) {
|
if (path_info.isSymLink()) {
|
||||||
QString real_path = path_info.symLinkTarget();
|
QString real_path = path_info.symLinkTarget();
|
||||||
for (const Directory &dir : std::as_const(watched_dirs_)) {
|
for (const CollectionDirectory &dir : std::as_const(watched_dirs_)) {
|
||||||
if (real_path.startsWith(dir.path)) {
|
if (real_path.startsWith(dir.path)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -440,12 +439,12 @@ void CollectionWatcher::ScanSubdirectory(const QString &path, const Subdirectory
|
|||||||
|
|
||||||
QMap<QString, QStringList> album_art;
|
QMap<QString, QStringList> album_art;
|
||||||
QStringList files_on_disk;
|
QStringList files_on_disk;
|
||||||
SubdirectoryList my_new_subdirs;
|
CollectionSubdirectoryList my_new_subdirs;
|
||||||
|
|
||||||
// If a directory is moved then only its parent gets a changed notification, so we need to look and see if any of our children don't exist anymore.
|
// If a directory is moved then only its parent gets a changed notification, so we need to look and see if any of our children don't exist anymore.
|
||||||
// If one has been removed, "rescan" it to get the deleted songs
|
// If one has been removed, "rescan" it to get the deleted songs
|
||||||
SubdirectoryList previous_subdirs = t->GetImmediateSubdirs(path);
|
CollectionSubdirectoryList previous_subdirs = t->GetImmediateSubdirs(path);
|
||||||
for (const Subdirectory &prev_subdir : previous_subdirs) {
|
for (const CollectionSubdirectory &prev_subdir : previous_subdirs) {
|
||||||
if (!QFile::exists(prev_subdir.path) && prev_subdir.path != path) {
|
if (!QFile::exists(prev_subdir.path) && prev_subdir.path != path) {
|
||||||
ScanSubdirectory(prev_subdir.path, prev_subdir, 0, t, true);
|
ScanSubdirectory(prev_subdir.path, prev_subdir, 0, t, true);
|
||||||
}
|
}
|
||||||
@@ -463,7 +462,7 @@ void CollectionWatcher::ScanSubdirectory(const QString &path, const Subdirectory
|
|||||||
if (child_info.isDir()) {
|
if (child_info.isDir()) {
|
||||||
if (!t->HasSeenSubdir(child)) {
|
if (!t->HasSeenSubdir(child)) {
|
||||||
// We haven't seen this subdirectory before - add it to a list, and later we'll tell the backend about it and scan it.
|
// We haven't seen this subdirectory before - add it to a list, and later we'll tell the backend about it and scan it.
|
||||||
Subdirectory new_subdir;
|
CollectionSubdirectory new_subdir;
|
||||||
new_subdir.directory_id = -1;
|
new_subdir.directory_id = -1;
|
||||||
new_subdir.path = child;
|
new_subdir.path = child;
|
||||||
new_subdir.mtime = child_info.lastModified().toSecsSinceEpoch();
|
new_subdir.mtime = child_info.lastModified().toSecsSinceEpoch();
|
||||||
@@ -676,7 +675,7 @@ void CollectionWatcher::ScanSubdirectory(const QString &path, const Subdirectory
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Add this subdir to the new or touched list
|
// Add this subdir to the new or touched list
|
||||||
Subdirectory updated_subdir;
|
CollectionSubdirectory updated_subdir;
|
||||||
updated_subdir.directory_id = t->dir();
|
updated_subdir.directory_id = t->dir();
|
||||||
updated_subdir.mtime = path_info.exists() ? path_info.lastModified().toSecsSinceEpoch() : 0;
|
updated_subdir.mtime = path_info.exists() ? path_info.lastModified().toSecsSinceEpoch() : 0;
|
||||||
updated_subdir.path = path;
|
updated_subdir.path = path;
|
||||||
@@ -688,12 +687,12 @@ void CollectionWatcher::ScanSubdirectory(const QString &path, const Subdirectory
|
|||||||
t->touched_subdirs << updated_subdir;
|
t->touched_subdirs << updated_subdir;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (updated_subdir.mtime == 0) { // Subdirectory deleted, mark it for removal from the watcher.
|
if (updated_subdir.mtime == 0) { // CollectionSubdirectory deleted, mark it for removal from the watcher.
|
||||||
t->deleted_subdirs << updated_subdir;
|
t->deleted_subdirs << updated_subdir;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Recurse into the new subdirs that we found
|
// Recurse into the new subdirs that we found
|
||||||
for (const Subdirectory &my_new_subdir : my_new_subdirs) {
|
for (const CollectionSubdirectory &my_new_subdir : my_new_subdirs) {
|
||||||
if (stop_requested_ || abort_requested_) return;
|
if (stop_requested_ || abort_requested_) return;
|
||||||
ScanSubdirectory(my_new_subdir.path, my_new_subdir, 0, t, true);
|
ScanSubdirectory(my_new_subdir.path, my_new_subdir, 0, t, true);
|
||||||
}
|
}
|
||||||
@@ -763,7 +762,7 @@ void CollectionWatcher::UpdateNonCueAssociatedSong(const QString &file,
|
|||||||
const Song &matching_song = matching_songs.first();
|
const Song &matching_song = matching_songs.first();
|
||||||
if (cue_deleted) {
|
if (cue_deleted) {
|
||||||
for (const Song &song : matching_songs) {
|
for (const Song &song : matching_songs) {
|
||||||
if (!song.IsMetadataAndMoreEqual(matching_song)) {
|
if (!song.IsAllMetadataEqual(matching_song)) {
|
||||||
t->deleted_songs << song;
|
t->deleted_songs << song;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -855,10 +854,26 @@ void CollectionWatcher::AddChangedSong(const QString &file, const Song &matching
|
|||||||
changes << "metadata";
|
changes << "metadata";
|
||||||
notify_new = true;
|
notify_new = true;
|
||||||
}
|
}
|
||||||
if (matching_song.art_automatic() != new_song.art_automatic() || matching_song.art_manual() != new_song.art_manual()) {
|
if (!matching_song.IsPlayStatisticsEqual(new_song)) {
|
||||||
|
changes << "play statistics";
|
||||||
|
notify_new = true;
|
||||||
|
}
|
||||||
|
if (!matching_song.IsRatingEqual(new_song)) {
|
||||||
|
changes << "rating";
|
||||||
|
notify_new = true;
|
||||||
|
}
|
||||||
|
if (!matching_song.IsArtEqual(new_song)) {
|
||||||
changes << "album art";
|
changes << "album art";
|
||||||
notify_new = true;
|
notify_new = true;
|
||||||
}
|
}
|
||||||
|
if (!matching_song.IsAcoustIdEqual(new_song)) {
|
||||||
|
changes << "acoustid";
|
||||||
|
notify_new = true;
|
||||||
|
}
|
||||||
|
if (!matching_song.IsMusicBrainzEqual(new_song)) {
|
||||||
|
changes << "musicbrainz";
|
||||||
|
notify_new = true;
|
||||||
|
}
|
||||||
if (matching_song.mtime() != new_song.mtime()) {
|
if (matching_song.mtime() != new_song.mtime()) {
|
||||||
changes << "mtime";
|
changes << "mtime";
|
||||||
}
|
}
|
||||||
@@ -897,7 +912,7 @@ quint64 CollectionWatcher::GetMtimeForCue(const QString &cue_path) {
|
|||||||
return cue_last_modified.isValid() ? cue_last_modified.toSecsSinceEpoch() : 0;
|
return cue_last_modified.isValid() ? cue_last_modified.toSecsSinceEpoch() : 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
void CollectionWatcher::AddWatch(const Directory &dir, const QString &path) {
|
void CollectionWatcher::AddWatch(const CollectionDirectory &dir, const QString &path) {
|
||||||
|
|
||||||
if (!QFile::exists(path)) return;
|
if (!QFile::exists(path)) return;
|
||||||
|
|
||||||
@@ -907,7 +922,7 @@ void CollectionWatcher::AddWatch(const Directory &dir, const QString &path) {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void CollectionWatcher::RemoveWatch(const Directory &dir, const Subdirectory &subdir) {
|
void CollectionWatcher::RemoveWatch(const CollectionDirectory &dir, const CollectionSubdirectory &subdir) {
|
||||||
|
|
||||||
QStringList subdir_paths = subdir_mapping_.keys(dir);
|
QStringList subdir_paths = subdir_mapping_.keys(dir);
|
||||||
for (const QString &subdir_path : subdir_paths) {
|
for (const QString &subdir_path : subdir_paths) {
|
||||||
@@ -919,7 +934,7 @@ void CollectionWatcher::RemoveWatch(const Directory &dir, const Subdirectory &su
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void CollectionWatcher::RemoveDirectory(const Directory &dir) {
|
void CollectionWatcher::RemoveDirectory(const CollectionDirectory &dir) {
|
||||||
|
|
||||||
rescan_queue_.remove(dir.id);
|
rescan_queue_.remove(dir.id);
|
||||||
watched_dirs_.remove(dir.id);
|
watched_dirs_.remove(dir.id);
|
||||||
@@ -979,11 +994,11 @@ bool CollectionWatcher::FindSongsByFingerprint(const QString &file, const SongLi
|
|||||||
void CollectionWatcher::DirectoryChanged(const QString &subdir) {
|
void CollectionWatcher::DirectoryChanged(const QString &subdir) {
|
||||||
|
|
||||||
// Find what dir it was in
|
// Find what dir it was in
|
||||||
QHash<QString, Directory>::const_iterator it = subdir_mapping_.constFind(subdir);
|
QHash<QString, CollectionDirectory>::const_iterator it = subdir_mapping_.constFind(subdir);
|
||||||
if (it == subdir_mapping_.constEnd()) {
|
if (it == subdir_mapping_.constEnd()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
Directory dir = *it;
|
CollectionDirectory dir = *it;
|
||||||
|
|
||||||
qLog(Debug) << "Subdir" << subdir << "changed under directory" << dir.path << "id" << dir.id;
|
qLog(Debug) << "Subdir" << subdir << "changed under directory" << dir.path << "id" << dir.id;
|
||||||
|
|
||||||
@@ -1010,7 +1025,7 @@ void CollectionWatcher::RescanPathsNow() {
|
|||||||
|
|
||||||
for (const QString &path : rescan_queue_[dir]) {
|
for (const QString &path : rescan_queue_[dir]) {
|
||||||
if (stop_requested_ || abort_requested_) break;
|
if (stop_requested_ || abort_requested_) break;
|
||||||
Subdirectory subdir;
|
CollectionSubdirectory subdir;
|
||||||
subdir.directory_id = dir;
|
subdir.directory_id = dir;
|
||||||
subdir.mtime = 0;
|
subdir.mtime = 0;
|
||||||
subdir.path = path;
|
subdir.path = path;
|
||||||
@@ -1113,18 +1128,6 @@ void CollectionWatcher::FullScanAsync() {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void CollectionWatcher::RescanTracksAsync(const SongList &songs) {
|
|
||||||
|
|
||||||
// Is List thread safe? if not, this may crash.
|
|
||||||
song_rescan_queue_.append(songs);
|
|
||||||
|
|
||||||
// Call only if it's not already running
|
|
||||||
if (!rescan_in_progress_) {
|
|
||||||
QMetaObject::invokeMethod(this, "RescanTracksNow", Qt::QueuedConnection);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
void CollectionWatcher::IncrementalScanCheck() {
|
void CollectionWatcher::IncrementalScanCheck() {
|
||||||
|
|
||||||
qint64 duration = QDateTime::currentDateTime().toSecsSinceEpoch() - last_scan_time_;
|
qint64 duration = QDateTime::currentDateTime().toSecsSinceEpoch() - last_scan_time_;
|
||||||
@@ -1139,48 +1142,20 @@ void CollectionWatcher::IncrementalScanNow() { PerformScan(true, false); }
|
|||||||
|
|
||||||
void CollectionWatcher::FullScanNow() { PerformScan(false, true); }
|
void CollectionWatcher::FullScanNow() { PerformScan(false, true); }
|
||||||
|
|
||||||
void CollectionWatcher::RescanTracksNow() {
|
|
||||||
|
|
||||||
Q_ASSERT(!rescan_in_progress_);
|
|
||||||
stop_requested_ = false;
|
|
||||||
|
|
||||||
// Currently we are too stupid to rescan one file at a time, so we'll just scan the full directories
|
|
||||||
QStringList scanned_dirs; // To avoid double scans
|
|
||||||
while (!song_rescan_queue_.isEmpty()) {
|
|
||||||
if (stop_requested_ || abort_requested_) break;
|
|
||||||
Song song = song_rescan_queue_.takeFirst();
|
|
||||||
QString songdir = song.url().toLocalFile().section('/', 0, -2);
|
|
||||||
if (!scanned_dirs.contains(songdir)) {
|
|
||||||
qLog(Debug) << "Song" << song.title() << "dir id" << song.directory_id() << "dir" << songdir;
|
|
||||||
ScanTransaction transaction(this, song.directory_id(), false, false, mark_songs_unavailable_);
|
|
||||||
quint64 files_count = FilesCountForPath(&transaction, songdir);
|
|
||||||
ScanSubdirectory(songdir, Subdirectory(), files_count, &transaction);
|
|
||||||
scanned_dirs << songdir;
|
|
||||||
emit CompilationsNeedUpdating();
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
qLog(Debug) << "Directory" << songdir << "already scanned - skipping.";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Q_ASSERT(song_rescan_queue_.isEmpty());
|
|
||||||
rescan_in_progress_ = false;
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
void CollectionWatcher::PerformScan(const bool incremental, const bool ignore_mtimes) {
|
void CollectionWatcher::PerformScan(const bool incremental, const bool ignore_mtimes) {
|
||||||
|
|
||||||
stop_requested_ = false;
|
stop_requested_ = false;
|
||||||
|
|
||||||
for (const Directory &dir : std::as_const(watched_dirs_)) {
|
for (const CollectionDirectory &dir : std::as_const(watched_dirs_)) {
|
||||||
|
|
||||||
if (stop_requested_ || abort_requested_) break;
|
if (stop_requested_ || abort_requested_) break;
|
||||||
|
|
||||||
ScanTransaction transaction(this, dir.id, incremental, ignore_mtimes, mark_songs_unavailable_);
|
ScanTransaction transaction(this, dir.id, incremental, ignore_mtimes, mark_songs_unavailable_);
|
||||||
SubdirectoryList subdirs(transaction.GetAllSubdirs());
|
CollectionSubdirectoryList subdirs(transaction.GetAllSubdirs());
|
||||||
|
|
||||||
if (subdirs.isEmpty()) {
|
if (subdirs.isEmpty()) {
|
||||||
qLog(Debug) << "Collection directory wasn't in subdir list.";
|
qLog(Debug) << "Collection directory wasn't in subdir list.";
|
||||||
Subdirectory subdir;
|
CollectionSubdirectory subdir;
|
||||||
subdir.path = dir.path;
|
subdir.path = dir.path;
|
||||||
subdir.directory_id = dir.id;
|
subdir.directory_id = dir.id;
|
||||||
subdirs << subdir;
|
subdirs << subdir;
|
||||||
@@ -1190,7 +1165,7 @@ void CollectionWatcher::PerformScan(const bool incremental, const bool ignore_mt
|
|||||||
quint64 files_count = FilesCountForSubdirs(&transaction, subdirs, subdir_files_count);
|
quint64 files_count = FilesCountForSubdirs(&transaction, subdirs, subdir_files_count);
|
||||||
transaction.AddToProgressMax(files_count);
|
transaction.AddToProgressMax(files_count);
|
||||||
|
|
||||||
for (const Subdirectory &subdir : subdirs) {
|
for (const CollectionSubdirectory &subdir : subdirs) {
|
||||||
if (stop_requested_ || abort_requested_) break;
|
if (stop_requested_ || abort_requested_) break;
|
||||||
ScanSubdirectory(subdir.path, subdir, subdir_files_count[subdir.path], &transaction);
|
ScanSubdirectory(subdir.path, subdir, subdir_files_count[subdir.path], &transaction);
|
||||||
}
|
}
|
||||||
@@ -1217,7 +1192,7 @@ quint64 CollectionWatcher::FilesCountForPath(ScanTransaction *t, const QString &
|
|||||||
if (path_info.isDir()) {
|
if (path_info.isDir()) {
|
||||||
if (path_info.isSymLink()) {
|
if (path_info.isSymLink()) {
|
||||||
QString real_path = path_info.symLinkTarget();
|
QString real_path = path_info.symLinkTarget();
|
||||||
for (const Directory &dir : std::as_const(watched_dirs_)) {
|
for (const CollectionDirectory &dir : std::as_const(watched_dirs_)) {
|
||||||
if (real_path.startsWith(dir.path)) {
|
if (real_path.startsWith(dir.path)) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
@@ -1239,10 +1214,10 @@ quint64 CollectionWatcher::FilesCountForPath(ScanTransaction *t, const QString &
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
quint64 CollectionWatcher::FilesCountForSubdirs(ScanTransaction *t, const SubdirectoryList &subdirs, QMap<QString, quint64> &subdir_files_count) {
|
quint64 CollectionWatcher::FilesCountForSubdirs(ScanTransaction *t, const CollectionSubdirectoryList &subdirs, QMap<QString, quint64> &subdir_files_count) {
|
||||||
|
|
||||||
quint64 i = 0;
|
quint64 i = 0;
|
||||||
for (const Subdirectory &subdir : subdirs) {
|
for (const CollectionSubdirectory &subdir : subdirs) {
|
||||||
if (stop_requested_ || abort_requested_) break;
|
if (stop_requested_ || abort_requested_) break;
|
||||||
const quint64 files_count = FilesCountForPath(t, subdir.path);
|
const quint64 files_count = FilesCountForPath(t, subdir.path);
|
||||||
subdir_files_count[subdir.path] = files_count;
|
subdir_files_count[subdir.path] = files_count;
|
||||||
@@ -1252,3 +1227,34 @@ quint64 CollectionWatcher::FilesCountForSubdirs(ScanTransaction *t, const Subdir
|
|||||||
return i;
|
return i;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void CollectionWatcher::RescanSongsAsync(const SongList &songs) {
|
||||||
|
|
||||||
|
QMetaObject::invokeMethod(this, "RescanSongs", Qt::QueuedConnection, Q_ARG(SongList, songs));
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
void CollectionWatcher::RescanSongs(const SongList &songs) {
|
||||||
|
|
||||||
|
stop_requested_ = false;
|
||||||
|
|
||||||
|
QStringList scanned_paths;
|
||||||
|
for (const Song &song : songs) {
|
||||||
|
if (stop_requested_ || abort_requested_) break;
|
||||||
|
const QString song_path = song.url().toLocalFile().section('/', 0, -2);
|
||||||
|
if (scanned_paths.contains(song_path)) continue;
|
||||||
|
ScanTransaction transaction(this, song.directory_id(), false, true, mark_songs_unavailable_);
|
||||||
|
CollectionSubdirectoryList subdirs(transaction.GetAllSubdirs());
|
||||||
|
for (const CollectionSubdirectory &subdir : subdirs) {
|
||||||
|
if (stop_requested_ || abort_requested_) break;
|
||||||
|
if (subdir.path != song_path) continue;
|
||||||
|
qLog(Debug) << "Rescan for directory ID" << song.directory_id() << "directory" << subdir.path;
|
||||||
|
quint64 files_count = FilesCountForPath(&transaction, subdir.path);
|
||||||
|
ScanSubdirectory(song_path, subdir, files_count, &transaction);
|
||||||
|
scanned_paths << subdir.path;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
emit CompilationsNeedUpdating();
|
||||||
|
|
||||||
|
}
|
||||||
|
|||||||
@@ -34,7 +34,7 @@
|
|||||||
#include <QStringList>
|
#include <QStringList>
|
||||||
#include <QUrl>
|
#include <QUrl>
|
||||||
|
|
||||||
#include "directory.h"
|
#include "collectiondirectory.h"
|
||||||
#include "core/song.h"
|
#include "core/song.h"
|
||||||
|
|
||||||
class QThread;
|
class QThread;
|
||||||
@@ -59,7 +59,6 @@ class CollectionWatcher : public QObject {
|
|||||||
|
|
||||||
void IncrementalScanAsync();
|
void IncrementalScanAsync();
|
||||||
void FullScanAsync();
|
void FullScanAsync();
|
||||||
void RescanTracksAsync(const SongList &songs);
|
|
||||||
void SetRescanPausedAsync(const bool pause);
|
void SetRescanPausedAsync(const bool pause);
|
||||||
void ReloadSettingsAsync();
|
void ReloadSettingsAsync();
|
||||||
|
|
||||||
@@ -68,14 +67,16 @@ class CollectionWatcher : public QObject {
|
|||||||
|
|
||||||
void ExitAsync();
|
void ExitAsync();
|
||||||
|
|
||||||
|
void RescanSongsAsync(const SongList &songs);
|
||||||
|
|
||||||
signals:
|
signals:
|
||||||
void NewOrUpdatedSongs(SongList);
|
void NewOrUpdatedSongs(SongList);
|
||||||
void SongsMTimeUpdated(SongList);
|
void SongsMTimeUpdated(SongList);
|
||||||
void SongsDeleted(SongList);
|
void SongsDeleted(SongList);
|
||||||
void SongsUnavailable(SongList songs, bool unavailable = true);
|
void SongsUnavailable(SongList songs, bool unavailable = true);
|
||||||
void SongsReadded(SongList songs, bool unavailable = false);
|
void SongsReadded(SongList songs, bool unavailable = false);
|
||||||
void SubdirsDiscovered(SubdirectoryList subdirs);
|
void SubdirsDiscovered(CollectionSubdirectoryList subdirs);
|
||||||
void SubdirsMTimeUpdated(SubdirectoryList subdirs);
|
void SubdirsMTimeUpdated(CollectionSubdirectoryList subdirs);
|
||||||
void CompilationsNeedUpdating();
|
void CompilationsNeedUpdating();
|
||||||
void UpdateLastSeen(int directory_id, int expire_unavailable_songs_days);
|
void UpdateLastSeen(int directory_id, int expire_unavailable_songs_days);
|
||||||
void ExitFinished();
|
void ExitFinished();
|
||||||
@@ -83,8 +84,8 @@ class CollectionWatcher : public QObject {
|
|||||||
void ScanStarted(int task_id);
|
void ScanStarted(int task_id);
|
||||||
|
|
||||||
public slots:
|
public slots:
|
||||||
void AddDirectory(const Directory &dir, const SubdirectoryList &subdirs);
|
void AddDirectory(const CollectionDirectory &dir, const CollectionSubdirectoryList &subdirs);
|
||||||
void RemoveDirectory(const Directory &dir);
|
void RemoveDirectory(const CollectionDirectory &dir);
|
||||||
void SetRescanPaused(bool pause);
|
void SetRescanPaused(bool pause);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
@@ -102,9 +103,9 @@ class CollectionWatcher : public QObject {
|
|||||||
SongList FindSongsInSubdirectory(const QString &path);
|
SongList FindSongsInSubdirectory(const QString &path);
|
||||||
bool HasSongsWithMissingFingerprint(const QString &path);
|
bool HasSongsWithMissingFingerprint(const QString &path);
|
||||||
bool HasSeenSubdir(const QString &path);
|
bool HasSeenSubdir(const QString &path);
|
||||||
void SetKnownSubdirs(const SubdirectoryList &subdirs);
|
void SetKnownSubdirs(const CollectionSubdirectoryList &subdirs);
|
||||||
SubdirectoryList GetImmediateSubdirs(const QString &path);
|
CollectionSubdirectoryList GetImmediateSubdirs(const QString &path);
|
||||||
SubdirectoryList GetAllSubdirs();
|
CollectionSubdirectoryList GetAllSubdirs();
|
||||||
|
|
||||||
void AddToProgress(const quint64 n = 1);
|
void AddToProgress(const quint64 n = 1);
|
||||||
void AddToProgressMax(const quint64 n);
|
void AddToProgressMax(const quint64 n);
|
||||||
@@ -120,9 +121,9 @@ class CollectionWatcher : public QObject {
|
|||||||
SongList readded_songs;
|
SongList readded_songs;
|
||||||
SongList new_songs;
|
SongList new_songs;
|
||||||
SongList touched_songs;
|
SongList touched_songs;
|
||||||
SubdirectoryList new_subdirs;
|
CollectionSubdirectoryList new_subdirs;
|
||||||
SubdirectoryList touched_subdirs;
|
CollectionSubdirectoryList touched_subdirs;
|
||||||
SubdirectoryList deleted_subdirs;
|
CollectionSubdirectoryList deleted_subdirs;
|
||||||
|
|
||||||
QStringList files_changed_path_;
|
QStringList files_changed_path_;
|
||||||
|
|
||||||
@@ -155,7 +156,7 @@ class CollectionWatcher : public QObject {
|
|||||||
QMultiMap<QString, Song> cached_songs_missing_fingerprint_;
|
QMultiMap<QString, Song> cached_songs_missing_fingerprint_;
|
||||||
bool cached_songs_missing_fingerprint_dirty_;
|
bool cached_songs_missing_fingerprint_dirty_;
|
||||||
|
|
||||||
SubdirectoryList known_subdirs_;
|
CollectionSubdirectoryList known_subdirs_;
|
||||||
bool known_subdirs_dirty_;
|
bool known_subdirs_dirty_;
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -166,9 +167,9 @@ class CollectionWatcher : public QObject {
|
|||||||
void IncrementalScanCheck();
|
void IncrementalScanCheck();
|
||||||
void IncrementalScanNow();
|
void IncrementalScanNow();
|
||||||
void FullScanNow();
|
void FullScanNow();
|
||||||
void RescanTracksNow();
|
|
||||||
void RescanPathsNow();
|
void RescanPathsNow();
|
||||||
void ScanSubdirectory(const QString &path, const Subdirectory &subdir, const quint64 files_count, CollectionWatcher::ScanTransaction *t, const bool force_noincremental = false);
|
void ScanSubdirectory(const QString &path, const CollectionSubdirectory &subdir, const quint64 files_count, CollectionWatcher::ScanTransaction *t, const bool force_noincremental = false);
|
||||||
|
void RescanSongs(const SongList &songs);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
static bool FindSongsByPath(const SongList &songs, const QString &path, SongList *out);
|
static bool FindSongsByPath(const SongList &songs, const QString &path, SongList *out);
|
||||||
@@ -179,8 +180,8 @@ class CollectionWatcher : public QObject {
|
|||||||
inline static QString DirectoryPart(const QString &fileName);
|
inline static QString DirectoryPart(const QString &fileName);
|
||||||
QString PickBestImage(const QStringList &images);
|
QString PickBestImage(const QStringList &images);
|
||||||
QUrl ImageForSong(const QString &path, QMap<QString, QStringList> &album_art);
|
QUrl ImageForSong(const QString &path, QMap<QString, QStringList> &album_art);
|
||||||
void AddWatch(const Directory &dir, const QString &path);
|
void AddWatch(const CollectionDirectory &dir, const QString &path);
|
||||||
void RemoveWatch(const Directory &dir, const Subdirectory &subdir);
|
void RemoveWatch(const CollectionDirectory &dir, const CollectionSubdirectory &subdir);
|
||||||
static quint64 GetMtimeForCue(const QString &cue_path);
|
static quint64 GetMtimeForCue(const QString &cue_path);
|
||||||
void PerformScan(const bool incremental, const bool ignore_mtimes);
|
void PerformScan(const bool incremental, const bool ignore_mtimes);
|
||||||
|
|
||||||
@@ -195,7 +196,7 @@ class CollectionWatcher : public QObject {
|
|||||||
static void AddChangedSong(const QString &file, const Song &matching_song, const Song &new_song, ScanTransaction *t);
|
static void AddChangedSong(const QString &file, const Song &matching_song, const Song &new_song, ScanTransaction *t);
|
||||||
|
|
||||||
quint64 FilesCountForPath(ScanTransaction *t, const QString &path);
|
quint64 FilesCountForPath(ScanTransaction *t, const QString &path);
|
||||||
quint64 FilesCountForSubdirs(ScanTransaction *t, const SubdirectoryList &subdirs, QMap<QString, quint64> &subdir_files_count);
|
quint64 FilesCountForSubdirs(ScanTransaction *t, const CollectionSubdirectoryList &subdirs, QMap<QString, quint64> &subdir_files_count);
|
||||||
|
|
||||||
QString FindCueFilename(const QString &filename);
|
QString FindCueFilename(const QString &filename);
|
||||||
|
|
||||||
@@ -207,7 +208,7 @@ class CollectionWatcher : public QObject {
|
|||||||
|
|
||||||
FileSystemWatcherInterface *fs_watcher_;
|
FileSystemWatcherInterface *fs_watcher_;
|
||||||
QThread *original_thread_;
|
QThread *original_thread_;
|
||||||
QHash<QString, Directory> subdir_mapping_;
|
QHash<QString, CollectionDirectory> subdir_mapping_;
|
||||||
|
|
||||||
// A list of words use to try to identify the (likely) best image found in an directory to use as cover artwork.
|
// A list of words use to try to identify the (likely) best image found in an directory to use as cover artwork.
|
||||||
// e.g. using ["front", "cover"] would identify front.jpg and exclude back.jpg.
|
// e.g. using ["front", "cover"] would identify front.jpg and exclude back.jpg.
|
||||||
@@ -223,9 +224,8 @@ class CollectionWatcher : public QObject {
|
|||||||
|
|
||||||
bool stop_requested_;
|
bool stop_requested_;
|
||||||
bool abort_requested_;
|
bool abort_requested_;
|
||||||
bool rescan_in_progress_; // True if RescanTracksNow() has been called and is working.
|
|
||||||
|
|
||||||
QMap<int, Directory> watched_dirs_;
|
QMap<int, CollectionDirectory> watched_dirs_;
|
||||||
QTimer *rescan_timer_;
|
QTimer *rescan_timer_;
|
||||||
QTimer *periodic_scan_timer_;
|
QTimer *periodic_scan_timer_;
|
||||||
QMap<int, QStringList> rescan_queue_; // dir id -> list of subdirs to be scanned
|
QMap<int, QStringList> rescan_queue_; // dir id -> list of subdirs to be scanned
|
||||||
@@ -237,8 +237,6 @@ class CollectionWatcher : public QObject {
|
|||||||
|
|
||||||
static QStringList sValidImages;
|
static QStringList sValidImages;
|
||||||
|
|
||||||
SongList song_rescan_queue_; // Set by UI thread
|
|
||||||
|
|
||||||
qint64 last_scan_time_;
|
qint64 last_scan_time_;
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,60 +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/>.
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
|
|
||||||
#ifndef DIRECTORY_H
|
|
||||||
#define DIRECTORY_H
|
|
||||||
|
|
||||||
#include "config.h"
|
|
||||||
|
|
||||||
#include <QMetaType>
|
|
||||||
#include <QList>
|
|
||||||
#include <QString>
|
|
||||||
#include <QSqlQuery>
|
|
||||||
|
|
||||||
struct Directory {
|
|
||||||
Directory() : id(-1) {}
|
|
||||||
|
|
||||||
bool operator==(const Directory &other) const {
|
|
||||||
return path == other.path && id == other.id;
|
|
||||||
}
|
|
||||||
|
|
||||||
QString path;
|
|
||||||
int id;
|
|
||||||
};
|
|
||||||
Q_DECLARE_METATYPE(Directory)
|
|
||||||
|
|
||||||
typedef QList<Directory> DirectoryList;
|
|
||||||
Q_DECLARE_METATYPE(DirectoryList)
|
|
||||||
|
|
||||||
|
|
||||||
struct Subdirectory {
|
|
||||||
Subdirectory() : directory_id(-1), mtime(0) {}
|
|
||||||
|
|
||||||
int directory_id;
|
|
||||||
QString path;
|
|
||||||
qint64 mtime;
|
|
||||||
};
|
|
||||||
Q_DECLARE_METATYPE(Subdirectory)
|
|
||||||
|
|
||||||
typedef QList<Subdirectory> SubdirectoryList;
|
|
||||||
Q_DECLARE_METATYPE(SubdirectoryList)
|
|
||||||
|
|
||||||
#endif // DIRECTORY_H
|
|
||||||
|
|
||||||
|
|||||||
@@ -63,11 +63,7 @@ struct tag_group_by {};
|
|||||||
|
|
||||||
class GroupByDialogPrivate {
|
class GroupByDialogPrivate {
|
||||||
private:
|
private:
|
||||||
typedef multi_index_container<
|
using MappingContainer = multi_index_container<Mapping, indexed_by<ordered_unique<tag<tag_index>, member<Mapping, int, &Mapping::combo_box_index>>, ordered_unique<tag<tag_group_by>, member<Mapping, CollectionModel::GroupBy, &Mapping::group_by>>>>;
|
||||||
Mapping,
|
|
||||||
indexed_by<
|
|
||||||
ordered_unique<tag<tag_index>, member<Mapping, int, &Mapping::combo_box_index> >,
|
|
||||||
ordered_unique<tag<tag_group_by>, member<Mapping, CollectionModel::GroupBy, &Mapping::group_by> > > > MappingContainer;
|
|
||||||
|
|
||||||
public:
|
public:
|
||||||
MappingContainer mapping_;
|
MappingContainer mapping_;
|
||||||
@@ -78,26 +74,26 @@ GroupByDialog::GroupByDialog(QWidget *parent) : QDialog(parent), ui_(new Ui_Grou
|
|||||||
ui_->setupUi(this);
|
ui_->setupUi(this);
|
||||||
Reset();
|
Reset();
|
||||||
|
|
||||||
p_->mapping_.insert(Mapping(CollectionModel::GroupBy_None, 0));
|
p_->mapping_.insert(Mapping(CollectionModel::GroupBy::None, 0));
|
||||||
p_->mapping_.insert(Mapping(CollectionModel::GroupBy_Artist, 1));
|
p_->mapping_.insert(Mapping(CollectionModel::GroupBy::Artist, 1));
|
||||||
p_->mapping_.insert(Mapping(CollectionModel::GroupBy_AlbumArtist, 2));
|
p_->mapping_.insert(Mapping(CollectionModel::GroupBy::AlbumArtist, 2));
|
||||||
p_->mapping_.insert(Mapping(CollectionModel::GroupBy_Album, 3));
|
p_->mapping_.insert(Mapping(CollectionModel::GroupBy::Album, 3));
|
||||||
p_->mapping_.insert(Mapping(CollectionModel::GroupBy_AlbumDisc, 4));
|
p_->mapping_.insert(Mapping(CollectionModel::GroupBy::AlbumDisc, 4));
|
||||||
p_->mapping_.insert(Mapping(CollectionModel::GroupBy_Disc, 5));
|
p_->mapping_.insert(Mapping(CollectionModel::GroupBy::Disc, 5));
|
||||||
p_->mapping_.insert(Mapping(CollectionModel::GroupBy_Format, 6));
|
p_->mapping_.insert(Mapping(CollectionModel::GroupBy::Format, 6));
|
||||||
p_->mapping_.insert(Mapping(CollectionModel::GroupBy_Genre, 7));
|
p_->mapping_.insert(Mapping(CollectionModel::GroupBy::Genre, 7));
|
||||||
p_->mapping_.insert(Mapping(CollectionModel::GroupBy_Year, 8));
|
p_->mapping_.insert(Mapping(CollectionModel::GroupBy::Year, 8));
|
||||||
p_->mapping_.insert(Mapping(CollectionModel::GroupBy_YearAlbum, 9));
|
p_->mapping_.insert(Mapping(CollectionModel::GroupBy::YearAlbum, 9));
|
||||||
p_->mapping_.insert(Mapping(CollectionModel::GroupBy_YearAlbumDisc, 10));
|
p_->mapping_.insert(Mapping(CollectionModel::GroupBy::YearAlbumDisc, 10));
|
||||||
p_->mapping_.insert(Mapping(CollectionModel::GroupBy_OriginalYear, 11));
|
p_->mapping_.insert(Mapping(CollectionModel::GroupBy::OriginalYear, 11));
|
||||||
p_->mapping_.insert(Mapping(CollectionModel::GroupBy_OriginalYearAlbum, 12));
|
p_->mapping_.insert(Mapping(CollectionModel::GroupBy::OriginalYearAlbum, 12));
|
||||||
p_->mapping_.insert(Mapping(CollectionModel::GroupBy_Composer, 13));
|
p_->mapping_.insert(Mapping(CollectionModel::GroupBy::Composer, 13));
|
||||||
p_->mapping_.insert(Mapping(CollectionModel::GroupBy_Performer, 14));
|
p_->mapping_.insert(Mapping(CollectionModel::GroupBy::Performer, 14));
|
||||||
p_->mapping_.insert(Mapping(CollectionModel::GroupBy_Grouping, 15));
|
p_->mapping_.insert(Mapping(CollectionModel::GroupBy::Grouping, 15));
|
||||||
p_->mapping_.insert(Mapping(CollectionModel::GroupBy_FileType, 16));
|
p_->mapping_.insert(Mapping(CollectionModel::GroupBy::FileType, 16));
|
||||||
p_->mapping_.insert(Mapping(CollectionModel::GroupBy_Samplerate, 17));
|
p_->mapping_.insert(Mapping(CollectionModel::GroupBy::Samplerate, 17));
|
||||||
p_->mapping_.insert(Mapping(CollectionModel::GroupBy_Bitdepth, 18));
|
p_->mapping_.insert(Mapping(CollectionModel::GroupBy::Bitdepth, 18));
|
||||||
p_->mapping_.insert(Mapping(CollectionModel::GroupBy_Bitrate, 19));
|
p_->mapping_.insert(Mapping(CollectionModel::GroupBy::Bitrate, 19));
|
||||||
|
|
||||||
QObject::connect(ui_->buttonbox->button(QDialogButtonBox::Reset), &QPushButton::clicked, this, &GroupByDialog::Reset);
|
QObject::connect(ui_->buttonbox->button(QDialogButtonBox::Reset), &QPushButton::clicked, this, &GroupByDialog::Reset);
|
||||||
|
|
||||||
|
|||||||
@@ -36,6 +36,7 @@
|
|||||||
|
|
||||||
#include "core/logging.h"
|
#include "core/logging.h"
|
||||||
#include "core/iconloader.h"
|
#include "core/iconloader.h"
|
||||||
|
#include "settings/collectionsettingspage.h"
|
||||||
#include "collectionmodel.h"
|
#include "collectionmodel.h"
|
||||||
#include "savedgroupingmanager.h"
|
#include "savedgroupingmanager.h"
|
||||||
#include "ui_savedgroupingmanager.h"
|
#include "ui_savedgroupingmanager.h"
|
||||||
@@ -83,68 +84,68 @@ QString SavedGroupingManager::GetSavedGroupingsSettingsGroup(const QString &sett
|
|||||||
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: {
|
case CollectionModel::GroupBy::GroupByCount: {
|
||||||
return tr("None");
|
return tr("None");
|
||||||
}
|
}
|
||||||
case CollectionModel::GroupBy_AlbumArtist: {
|
case CollectionModel::GroupBy::AlbumArtist: {
|
||||||
return tr("Album artist");
|
return tr("Album artist");
|
||||||
}
|
}
|
||||||
case CollectionModel::GroupBy_Artist: {
|
case CollectionModel::GroupBy::Artist: {
|
||||||
return tr("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_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: {
|
case CollectionModel::GroupBy::OriginalYearAlbum: {
|
||||||
return tr("Original year - Album");
|
return tr("Original year - Album");
|
||||||
}
|
}
|
||||||
case CollectionModel::GroupBy_OriginalYearAlbumDisc: {
|
case CollectionModel::GroupBy::OriginalYearAlbumDisc: {
|
||||||
return tr("Original year - Album - Disc");
|
return tr("Original year - Album - Disc");
|
||||||
}
|
}
|
||||||
case CollectionModel::GroupBy_Disc: {
|
case CollectionModel::GroupBy::Disc: {
|
||||||
return tr("Disc");
|
return tr("Disc");
|
||||||
}
|
}
|
||||||
case CollectionModel::GroupBy_Year: {
|
case CollectionModel::GroupBy::Year: {
|
||||||
return tr("Year");
|
return tr("Year");
|
||||||
}
|
}
|
||||||
case CollectionModel::GroupBy_OriginalYear: {
|
case CollectionModel::GroupBy::OriginalYear: {
|
||||||
return tr("Original year");
|
return tr("Original year");
|
||||||
}
|
}
|
||||||
case CollectionModel::GroupBy_Genre: {
|
case CollectionModel::GroupBy::Genre: {
|
||||||
return tr("Genre");
|
return tr("Genre");
|
||||||
}
|
}
|
||||||
case CollectionModel::GroupBy_Composer: {
|
case CollectionModel::GroupBy::Composer: {
|
||||||
return tr("Composer");
|
return tr("Composer");
|
||||||
}
|
}
|
||||||
case CollectionModel::GroupBy_Performer: {
|
case CollectionModel::GroupBy::Performer: {
|
||||||
return tr("Performer");
|
return tr("Performer");
|
||||||
}
|
}
|
||||||
case CollectionModel::GroupBy_Grouping: {
|
case CollectionModel::GroupBy::Grouping: {
|
||||||
return tr("Grouping");
|
return tr("Grouping");
|
||||||
}
|
}
|
||||||
case CollectionModel::GroupBy_FileType: {
|
case CollectionModel::GroupBy::FileType: {
|
||||||
return tr("File type");
|
return tr("File type");
|
||||||
}
|
}
|
||||||
case CollectionModel::GroupBy_Format: {
|
case CollectionModel::GroupBy::Format: {
|
||||||
return tr("Format");
|
return tr("Format");
|
||||||
}
|
}
|
||||||
case CollectionModel::GroupBy_Samplerate: {
|
case CollectionModel::GroupBy::Samplerate: {
|
||||||
return tr("Sample rate");
|
return tr("Sample rate");
|
||||||
}
|
}
|
||||||
case CollectionModel::GroupBy_Bitdepth: {
|
case CollectionModel::GroupBy::Bitdepth: {
|
||||||
return tr("Bit depth");
|
return tr("Bit depth");
|
||||||
}
|
}
|
||||||
case CollectionModel::GroupBy_Bitrate: {
|
case CollectionModel::GroupBy::Bitrate: {
|
||||||
return tr("Bitrate");
|
return tr("Bitrate");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -37,7 +37,7 @@
|
|||||||
#include <QContextMenuEvent>
|
#include <QContextMenuEvent>
|
||||||
#include <QPaintEvent>
|
#include <QPaintEvent>
|
||||||
|
|
||||||
#include "core/imageutils.h"
|
#include "utilities/imageutils.h"
|
||||||
#include "covermanager/albumcoverchoicecontroller.h"
|
#include "covermanager/albumcoverchoicecontroller.h"
|
||||||
|
|
||||||
#include "contextview.h"
|
#include "contextview.h"
|
||||||
@@ -63,7 +63,7 @@ ContextAlbum::ContextAlbum(QWidget *parent)
|
|||||||
cover_loader_options_.desired_height_ = width();
|
cover_loader_options_.desired_height_ = width();
|
||||||
cover_loader_options_.pad_output_image_ = true;
|
cover_loader_options_.pad_output_image_ = true;
|
||||||
cover_loader_options_.scale_output_image_ = true;
|
cover_loader_options_.scale_output_image_ = true;
|
||||||
QImage image = ImageUtils::ScaleAndPad(image_strawberry_, cover_loader_options_.scale_output_image_, cover_loader_options_.pad_output_image_, cover_loader_options_.desired_height_);
|
QImage image = ImageUtils::ScaleAndPad(image_strawberry_, cover_loader_options_.scale_output_image_, cover_loader_options_.pad_output_image_, cover_loader_options_.desired_height_, devicePixelRatioF());
|
||||||
if (!image.isNull()) {
|
if (!image.isNull()) {
|
||||||
pixmap_current_ = QPixmap::fromImage(image);
|
pixmap_current_ = QPixmap::fromImage(image);
|
||||||
}
|
}
|
||||||
@@ -235,7 +235,7 @@ void ContextAlbum::FadePreviousCoverFinished(std::shared_ptr<PreviousCover> prev
|
|||||||
|
|
||||||
void ContextAlbum::ScaleCover() {
|
void ContextAlbum::ScaleCover() {
|
||||||
|
|
||||||
QImage image = ImageUtils::ScaleAndPad(image_original_, cover_loader_options_.scale_output_image_, cover_loader_options_.pad_output_image_, cover_loader_options_.desired_height_);
|
QImage image = ImageUtils::ScaleAndPad(image_original_, cover_loader_options_.scale_output_image_, cover_loader_options_.pad_output_image_, cover_loader_options_.desired_height_, devicePixelRatioF());
|
||||||
if (image.isNull()) {
|
if (image.isNull()) {
|
||||||
pixmap_current_ = QPixmap();
|
pixmap_current_ = QPixmap();
|
||||||
}
|
}
|
||||||
@@ -248,7 +248,7 @@ void ContextAlbum::ScaleCover() {
|
|||||||
void ContextAlbum::ScalePreviousCovers() {
|
void ContextAlbum::ScalePreviousCovers() {
|
||||||
|
|
||||||
for (std::shared_ptr<PreviousCover> previous_cover : previous_covers_) {
|
for (std::shared_ptr<PreviousCover> previous_cover : previous_covers_) {
|
||||||
QImage image = ImageUtils::ScaleAndPad(previous_cover->image, cover_loader_options_.scale_output_image_, cover_loader_options_.pad_output_image_, cover_loader_options_.desired_height_);
|
QImage image = ImageUtils::ScaleAndPad(previous_cover->image, cover_loader_options_.scale_output_image_, cover_loader_options_.pad_output_image_, cover_loader_options_.desired_height_, devicePixelRatioF());
|
||||||
if (image.isNull()) {
|
if (image.isNull()) {
|
||||||
previous_cover->pixmap = QPixmap();
|
previous_cover->pixmap = QPixmap();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -51,8 +51,9 @@
|
|||||||
#include "core/application.h"
|
#include "core/application.h"
|
||||||
#include "core/player.h"
|
#include "core/player.h"
|
||||||
#include "core/song.h"
|
#include "core/song.h"
|
||||||
#include "core/utilities.h"
|
|
||||||
#include "core/iconloader.h"
|
#include "core/iconloader.h"
|
||||||
|
#include "utilities/strutils.h"
|
||||||
|
#include "utilities/timeutils.h"
|
||||||
#include "widgets/resizabletextedit.h"
|
#include "widgets/resizabletextedit.h"
|
||||||
#include "engine/engine_fwd.h"
|
#include "engine/engine_fwd.h"
|
||||||
#include "engine/enginebase.h"
|
#include "engine/enginebase.h"
|
||||||
@@ -337,11 +338,11 @@ void ContextView::ReloadSettings() {
|
|||||||
s.beginGroup(ContextSettingsPage::kSettingsGroup);
|
s.beginGroup(ContextSettingsPage::kSettingsGroup);
|
||||||
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[static_cast<int>(ContextSettingsPage::ContextSettingsOrder::ALBUM)], true).toBool());
|
||||||
action_show_data_->setChecked(s.value(ContextSettingsPage::kSettingsGroupEnable[ContextSettingsPage::ContextSettingsOrder::TECHNICAL_DATA], false).toBool());
|
action_show_data_->setChecked(s.value(ContextSettingsPage::kSettingsGroupEnable[static_cast<int>(ContextSettingsPage::ContextSettingsOrder::TECHNICAL_DATA)], false).toBool());
|
||||||
action_show_output_->setChecked(s.value(ContextSettingsPage::kSettingsGroupEnable[ContextSettingsPage::ContextSettingsOrder::ENGINE_AND_DEVICE], false).toBool());
|
action_show_output_->setChecked(s.value(ContextSettingsPage::kSettingsGroupEnable[static_cast<int>(ContextSettingsPage::ContextSettingsOrder::ENGINE_AND_DEVICE)], false).toBool());
|
||||||
action_show_lyrics_->setChecked(s.value(ContextSettingsPage::kSettingsGroupEnable[ContextSettingsPage::ContextSettingsOrder::SONG_LYRICS], true).toBool());
|
action_show_lyrics_->setChecked(s.value(ContextSettingsPage::kSettingsGroupEnable[static_cast<int>(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[static_cast<int>(ContextSettingsPage::ContextSettingsOrder::SEARCH_LYRICS)], true).toBool());
|
||||||
font_headline_ = s.value("font_headline", font().family()).toString();
|
font_headline_ = s.value("font_headline", font().family()).toString();
|
||||||
font_normal_ = s.value("font_normal", font().family()).toString();
|
font_normal_ = s.value("font_normal", font().family()).toString();
|
||||||
font_size_headline_ = s.value("font_size_headline", ContextSettingsPage::kDefaultFontSizeHeadline).toReal();
|
font_size_headline_ = s.value("font_size_headline", ContextSettingsPage::kDefaultFontSizeHeadline).toReal();
|
||||||
@@ -406,7 +407,7 @@ void ContextView::SearchLyrics() {
|
|||||||
if (lyrics_.isEmpty() && action_show_lyrics_->isChecked() && action_search_lyrics_->isChecked() && !song_playing_.artist().isEmpty() && !song_playing_.title().isEmpty() && !lyrics_tried_ && lyrics_id_ == -1) {
|
if (lyrics_.isEmpty() && action_show_lyrics_->isChecked() && action_search_lyrics_->isChecked() && !song_playing_.artist().isEmpty() && !song_playing_.title().isEmpty() && !lyrics_tried_ && lyrics_id_ == -1) {
|
||||||
lyrics_fetcher_->Clear();
|
lyrics_fetcher_->Clear();
|
||||||
lyrics_tried_ = true;
|
lyrics_tried_ = true;
|
||||||
lyrics_id_ = static_cast<qint64>(lyrics_fetcher_->Search(song_playing_.effective_albumartist(), song_playing_.album(), song_playing_.title()));
|
lyrics_id_ = static_cast<qint64>(lyrics_fetcher_->Search(song_playing_.effective_albumartist(), song_playing_.artist(), song_playing_.album(), song_playing_.title()));
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -434,8 +435,8 @@ void ContextView::NoSong() {
|
|||||||
widget_album_->show();
|
widget_album_->show();
|
||||||
}
|
}
|
||||||
|
|
||||||
textedit_top_->setStyleSheet("font: 20pt 'Open Sans', 'FreeSans', 'FreeSerif', 'Liberation Serif'; font-weight: Regular;");
|
textedit_top_->setFont(QFont(font_headline_, static_cast<int>(font_size_headline_ * 1.6)));
|
||||||
textedit_top_->setText(tr("No song playing"));
|
textedit_top_->SetText(tr("No song playing"));
|
||||||
|
|
||||||
QString html;
|
QString html;
|
||||||
if (collectionview_->TotalSongs() == 1) html += tr("%1 song").arg(collectionview_->TotalSongs());
|
if (collectionview_->TotalSongs() == 1) html += tr("%1 song").arg(collectionview_->TotalSongs());
|
||||||
@@ -450,27 +451,28 @@ void ContextView::NoSong() {
|
|||||||
else html += tr("%1 albums").arg(collectionview_->TotalAlbums());
|
else html += tr("%1 albums").arg(collectionview_->TotalAlbums());
|
||||||
html += "<br />";
|
html += "<br />";
|
||||||
|
|
||||||
label_stop_summary_->setStyleSheet(QString("font: %1pt \"%2\"; font-weight: regular;").arg(font_size_normal_).arg(font_normal_));
|
label_stop_summary_->setFont(QFont(font_normal_, static_cast<int>(font_size_normal_)));
|
||||||
label_stop_summary_->setText(html);
|
label_stop_summary_->setText(html);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void ContextView::UpdateFonts() {
|
void ContextView::UpdateFonts() {
|
||||||
|
|
||||||
QString font_style = QString("font: %2pt \"%1\"; font-weight: regular;").arg(font_normal_).arg(font_size_normal_);
|
QFont font(font_normal_, static_cast<int>(font_size_normal_));
|
||||||
|
font.setBold(false);
|
||||||
for (QLabel *l : labels_play_all_) {
|
for (QLabel *l : labels_play_all_) {
|
||||||
l->setStyleSheet(font_style);
|
l->setFont(font);
|
||||||
}
|
}
|
||||||
for (QTextEdit *e : textedit_play_) {
|
for (QTextEdit *e : textedit_play_) {
|
||||||
e->setStyleSheet(font_style);
|
e->setFont(font);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void ContextView::SetSong() {
|
void ContextView::SetSong() {
|
||||||
|
|
||||||
textedit_top_->setStyleSheet(QString("font: %2pt \"%1\"; font-weight: regular;").arg(font_headline_).arg(font_size_headline_));
|
textedit_top_->setFont(QFont(font_headline_, static_cast<int>(font_size_headline_)));
|
||||||
textedit_top_->setText(QString("<b>%1</b><br />%2").arg(Utilities::ReplaceMessage(title_fmt_, song_playing_, "<br />", true), Utilities::ReplaceMessage(summary_fmt_, song_playing_, "<br />", true)));
|
textedit_top_->SetText(QString("<b>%1</b><br />%2").arg(Utilities::ReplaceMessage(title_fmt_, song_playing_, "<br />", true), Utilities::ReplaceMessage(summary_fmt_, song_playing_, "<br />", true)));
|
||||||
|
|
||||||
label_stop_summary_->clear();
|
label_stop_summary_->clear();
|
||||||
|
|
||||||
@@ -542,9 +544,9 @@ void ContextView::SetSong() {
|
|||||||
|
|
||||||
if (action_show_output_->isChecked()) {
|
if (action_show_output_->isChecked()) {
|
||||||
widget_play_output_->show();
|
widget_play_output_->show();
|
||||||
Engine::EngineType enginetype(Engine::None);
|
Engine::EngineType enginetype(Engine::EngineType::None);
|
||||||
if (app_->player()->engine()) enginetype = app_->player()->engine()->type();
|
if (app_->player()->engine()) enginetype = app_->player()->engine()->type();
|
||||||
QIcon icon_engine = IconLoader::Load(EngineName(enginetype), 32);
|
QIcon icon_engine = IconLoader::Load(EngineName(enginetype), true, 32);
|
||||||
|
|
||||||
label_engine_icon_->setPixmap(icon_engine.pixmap(QSize(32, 32)));
|
label_engine_icon_->setPixmap(icon_engine.pixmap(QSize(32, 32)));
|
||||||
label_engine_->setText(EngineDescription(enginetype));
|
label_engine_->setText(EngineDescription(enginetype));
|
||||||
@@ -562,7 +564,7 @@ void ContextView::SetSong() {
|
|||||||
label_device_title_->show();
|
label_device_title_->show();
|
||||||
label_device_icon_->show();
|
label_device_icon_->show();
|
||||||
label_device_->show();
|
label_device_->show();
|
||||||
QIcon icon_device = IconLoader::Load(device.iconname, 32);
|
QIcon icon_device = IconLoader::Load(device.iconname, true, 32);
|
||||||
label_device_icon_->setPixmap(icon_device.pixmap(QSize(32, 32)));
|
label_device_icon_->setPixmap(icon_device.pixmap(QSize(32, 32)));
|
||||||
label_device_->setText(device.description);
|
label_device_->setText(device.description);
|
||||||
}
|
}
|
||||||
@@ -584,7 +586,7 @@ void ContextView::SetSong() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (action_show_lyrics_->isChecked() && !lyrics_.isEmpty()) {
|
if (action_show_lyrics_->isChecked() && !lyrics_.isEmpty()) {
|
||||||
textedit_play_lyrics_->setText(lyrics_);
|
textedit_play_lyrics_->SetText(lyrics_);
|
||||||
textedit_play_lyrics_->show();
|
textedit_play_lyrics_->show();
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
@@ -599,7 +601,7 @@ void ContextView::SetSong() {
|
|||||||
|
|
||||||
void ContextView::UpdateSong(const Song &song) {
|
void ContextView::UpdateSong(const Song &song) {
|
||||||
|
|
||||||
textedit_top_->setText(QString("<b>%1</b><br />%2").arg(Utilities::ReplaceMessage(title_fmt_, song, "<br />", true), Utilities::ReplaceMessage(summary_fmt_, song, "<br />", true)));
|
textedit_top_->SetText(QString("<b>%1</b><br />%2").arg(Utilities::ReplaceMessage(title_fmt_, song, "<br />", true), Utilities::ReplaceMessage(summary_fmt_, song, "<br />", true)));
|
||||||
|
|
||||||
if (action_show_data_->isChecked()) {
|
if (action_show_data_->isChecked()) {
|
||||||
if (song.filetype() != song_playing_.filetype()) label_filetype_->setText(song.TextForFiletype());
|
if (song.filetype() != song_playing_.filetype()) label_filetype_->setText(song.TextForFiletype());
|
||||||
@@ -655,6 +657,8 @@ void ContextView::UpdateSong(const Song &song) {
|
|||||||
|
|
||||||
song_playing_ = song;
|
song_playing_ = song;
|
||||||
|
|
||||||
|
widget_stacked_->updateGeometry();
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void ContextView::ResetSong() {
|
void ContextView::ResetSong() {
|
||||||
@@ -681,7 +685,7 @@ void ContextView::UpdateLyrics(const quint64 id, const QString &provider, const
|
|||||||
lyrics_id_ = -1;
|
lyrics_id_ = -1;
|
||||||
|
|
||||||
if (action_show_lyrics_->isChecked() && !lyrics_.isEmpty()) {
|
if (action_show_lyrics_->isChecked() && !lyrics_.isEmpty()) {
|
||||||
textedit_play_lyrics_->setText(lyrics_);
|
textedit_play_lyrics_->SetText(lyrics_);
|
||||||
textedit_play_lyrics_->show();
|
textedit_play_lyrics_->show();
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
@@ -735,7 +739,7 @@ void ContextView::ActionShowAlbum() {
|
|||||||
|
|
||||||
QSettings s;
|
QSettings s;
|
||||||
s.beginGroup(ContextSettingsPage::kSettingsGroup);
|
s.beginGroup(ContextSettingsPage::kSettingsGroup);
|
||||||
s.setValue(ContextSettingsPage::kSettingsGroupEnable[ContextSettingsPage::ContextSettingsOrder::ALBUM], action_show_album_->isChecked());
|
s.setValue(ContextSettingsPage::kSettingsGroupEnable[static_cast<int>(ContextSettingsPage::ContextSettingsOrder::ALBUM)], action_show_album_->isChecked());
|
||||||
s.endGroup();
|
s.endGroup();
|
||||||
if (song_playing_.is_valid()) SetSong();
|
if (song_playing_.is_valid()) SetSong();
|
||||||
|
|
||||||
@@ -745,7 +749,7 @@ void ContextView::ActionShowData() {
|
|||||||
|
|
||||||
QSettings s;
|
QSettings s;
|
||||||
s.beginGroup(ContextSettingsPage::kSettingsGroup);
|
s.beginGroup(ContextSettingsPage::kSettingsGroup);
|
||||||
s.setValue(ContextSettingsPage::kSettingsGroupEnable[ContextSettingsPage::ContextSettingsOrder::TECHNICAL_DATA], action_show_data_->isChecked());
|
s.setValue(ContextSettingsPage::kSettingsGroupEnable[static_cast<int>(ContextSettingsPage::ContextSettingsOrder::TECHNICAL_DATA)], action_show_data_->isChecked());
|
||||||
s.endGroup();
|
s.endGroup();
|
||||||
if (song_playing_.is_valid()) SetSong();
|
if (song_playing_.is_valid()) SetSong();
|
||||||
|
|
||||||
@@ -755,7 +759,7 @@ void ContextView::ActionShowOutput() {
|
|||||||
|
|
||||||
QSettings s;
|
QSettings s;
|
||||||
s.beginGroup(ContextSettingsPage::kSettingsGroup);
|
s.beginGroup(ContextSettingsPage::kSettingsGroup);
|
||||||
s.setValue(ContextSettingsPage::kSettingsGroupEnable[ContextSettingsPage::ContextSettingsOrder::ENGINE_AND_DEVICE], action_show_output_->isChecked());
|
s.setValue(ContextSettingsPage::kSettingsGroupEnable[static_cast<int>(ContextSettingsPage::ContextSettingsOrder::ENGINE_AND_DEVICE)], action_show_output_->isChecked());
|
||||||
s.endGroup();
|
s.endGroup();
|
||||||
if (song_playing_.is_valid()) SetSong();
|
if (song_playing_.is_valid()) SetSong();
|
||||||
|
|
||||||
@@ -765,7 +769,7 @@ void ContextView::ActionShowLyrics() {
|
|||||||
|
|
||||||
QSettings s;
|
QSettings s;
|
||||||
s.beginGroup(ContextSettingsPage::kSettingsGroup);
|
s.beginGroup(ContextSettingsPage::kSettingsGroup);
|
||||||
s.setValue(ContextSettingsPage::kSettingsGroupEnable[ContextSettingsPage::ContextSettingsOrder::SONG_LYRICS], action_show_lyrics_->isChecked());
|
s.setValue(ContextSettingsPage::kSettingsGroupEnable[static_cast<int>(ContextSettingsPage::ContextSettingsOrder::SONG_LYRICS)], action_show_lyrics_->isChecked());
|
||||||
s.endGroup();
|
s.endGroup();
|
||||||
|
|
||||||
if (song_playing_.is_valid()) SetSong();
|
if (song_playing_.is_valid()) SetSong();
|
||||||
@@ -778,7 +782,7 @@ void ContextView::ActionSearchLyrics() {
|
|||||||
|
|
||||||
QSettings s;
|
QSettings s;
|
||||||
s.beginGroup(ContextSettingsPage::kSettingsGroup);
|
s.beginGroup(ContextSettingsPage::kSettingsGroup);
|
||||||
s.setValue(ContextSettingsPage::kSettingsGroupEnable[ContextSettingsPage::ContextSettingsOrder::SEARCH_LYRICS], action_search_lyrics_->isChecked());
|
s.setValue(ContextSettingsPage::kSettingsGroupEnable[static_cast<int>(ContextSettingsPage::ContextSettingsOrder::SEARCH_LYRICS)], action_search_lyrics_->isChecked());
|
||||||
s.endGroup();
|
s.endGroup();
|
||||||
|
|
||||||
if (song_playing_.is_valid()) SetSong();
|
if (song_playing_.is_valid()) SetSong();
|
||||||
|
|||||||
@@ -1,92 +0,0 @@
|
|||||||
/*
|
|
||||||
* Strawberry Music Player
|
|
||||||
* This file was part of Clementine.
|
|
||||||
* Copyright 2012, Arnaud Bienner <arnaud.bienner@gmail.com>
|
|
||||||
*
|
|
||||||
* Strawberry is free software: you can redistribute it and/or modify
|
|
||||||
* it under the terms of the GNU General Public License as published by
|
|
||||||
* the Free Software Foundation, either version 3 of the License, or
|
|
||||||
* (at your option) any later version.
|
|
||||||
*
|
|
||||||
* Strawberry is distributed in the hope that it will be useful,
|
|
||||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
* GNU General Public License for more details.
|
|
||||||
*
|
|
||||||
* You should have received a copy of the GNU General Public License
|
|
||||||
* along with Strawberry. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
|
|
||||||
#include "config.h"
|
|
||||||
|
|
||||||
#include <QApplication>
|
|
||||||
#include <QObject>
|
|
||||||
#include <QPalette>
|
|
||||||
#include <QColor>
|
|
||||||
#include <QSettings>
|
|
||||||
|
|
||||||
#include "appearance.h"
|
|
||||||
#include "settings/appearancesettingspage.h"
|
|
||||||
|
|
||||||
const QPalette Appearance::kDefaultPalette = QPalette();
|
|
||||||
|
|
||||||
Appearance::Appearance(QObject *parent) : QObject(parent) {
|
|
||||||
|
|
||||||
QPalette p = QApplication::palette();
|
|
||||||
|
|
||||||
QSettings s;
|
|
||||||
s.beginGroup(AppearanceSettingsPage::kSettingsGroup);
|
|
||||||
background_color_ = s.value(AppearanceSettingsPage::kBackgroundColor, p.color(QPalette::WindowText)).value<QColor>();
|
|
||||||
foreground_color_ = s.value(AppearanceSettingsPage::kForegroundColor, p.color(QPalette::Window)).value<QColor>();
|
|
||||||
s.endGroup();
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
void Appearance::LoadUserTheme() {
|
|
||||||
|
|
||||||
QSettings s;
|
|
||||||
s.beginGroup(AppearanceSettingsPage::kSettingsGroup);
|
|
||||||
bool use_a_custom_color_set = s.value(AppearanceSettingsPage::kUseCustomColorSet).toBool();
|
|
||||||
s.endGroup();
|
|
||||||
|
|
||||||
if (use_a_custom_color_set) {
|
|
||||||
ChangeForegroundColor(foreground_color_);
|
|
||||||
ChangeBackgroundColor(background_color_);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
void Appearance::ResetToSystemDefaultTheme() {
|
|
||||||
QApplication::setPalette(kDefaultPalette);
|
|
||||||
}
|
|
||||||
|
|
||||||
void Appearance::ChangeForegroundColor(const QColor &color) {
|
|
||||||
|
|
||||||
// Get the application palette
|
|
||||||
QPalette p = QApplication::palette();
|
|
||||||
|
|
||||||
// Modify the palette
|
|
||||||
p.setColor(QPalette::WindowText, color);
|
|
||||||
p.setColor(QPalette::Text, color);
|
|
||||||
|
|
||||||
// Make the modified palette the new application's palette
|
|
||||||
QApplication::setPalette(p);
|
|
||||||
foreground_color_ = color;
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
void Appearance::ChangeBackgroundColor(const QColor &color) {
|
|
||||||
|
|
||||||
// Get the application palette
|
|
||||||
QPalette p = QApplication::palette();
|
|
||||||
|
|
||||||
// Modify the palette
|
|
||||||
p.setColor(QPalette::Window, color);
|
|
||||||
p.setColor(QPalette::Base, color);
|
|
||||||
|
|
||||||
// Make the modified palette the new application's palette
|
|
||||||
QApplication::setPalette(p);
|
|
||||||
background_color_ = color;
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -38,7 +38,6 @@
|
|||||||
#include "database.h"
|
#include "database.h"
|
||||||
#include "taskmanager.h"
|
#include "taskmanager.h"
|
||||||
#include "player.h"
|
#include "player.h"
|
||||||
#include "appearance.h"
|
|
||||||
|
|
||||||
#include "engine/devicefinders.h"
|
#include "engine/devicefinders.h"
|
||||||
#ifndef Q_OS_WIN
|
#ifndef Q_OS_WIN
|
||||||
@@ -64,6 +63,7 @@
|
|||||||
#include "lyrics/lololyricsprovider.h"
|
#include "lyrics/lololyricsprovider.h"
|
||||||
#include "lyrics/musixmatchlyricsprovider.h"
|
#include "lyrics/musixmatchlyricsprovider.h"
|
||||||
#include "lyrics/chartlyricsprovider.h"
|
#include "lyrics/chartlyricsprovider.h"
|
||||||
|
#include "lyrics/lyricscomlyricsprovider.h"
|
||||||
|
|
||||||
#include "scrobbler/audioscrobbler.h"
|
#include "scrobbler/audioscrobbler.h"
|
||||||
#include "scrobbler/lastfmimport.h"
|
#include "scrobbler/lastfmimport.h"
|
||||||
@@ -109,7 +109,6 @@ class ApplicationImpl {
|
|||||||
QTimer::singleShot(30s, db, &Database::DoBackup);
|
QTimer::singleShot(30s, db, &Database::DoBackup);
|
||||||
return db;
|
return db;
|
||||||
}),
|
}),
|
||||||
appearance_([app]() { return new Appearance(app); }),
|
|
||||||
task_manager_([app]() { return new TaskManager(app); }),
|
task_manager_([app]() { return new TaskManager(app); }),
|
||||||
player_([app]() { return new Player(app, app); }),
|
player_([app]() { return new Player(app, app); }),
|
||||||
device_finders_([app]() { return new DeviceFinders(app); }),
|
device_finders_([app]() { return new DeviceFinders(app); }),
|
||||||
@@ -156,6 +155,7 @@ class ApplicationImpl {
|
|||||||
lyrics_providers->AddProvider(new LoloLyricsProvider(lyrics_providers->network(), app));
|
lyrics_providers->AddProvider(new LoloLyricsProvider(lyrics_providers->network(), app));
|
||||||
lyrics_providers->AddProvider(new MusixmatchLyricsProvider(lyrics_providers->network(), app));
|
lyrics_providers->AddProvider(new MusixmatchLyricsProvider(lyrics_providers->network(), app));
|
||||||
lyrics_providers->AddProvider(new ChartLyricsProvider(lyrics_providers->network(), app));
|
lyrics_providers->AddProvider(new ChartLyricsProvider(lyrics_providers->network(), app));
|
||||||
|
lyrics_providers->AddProvider(new LyricsComLyricsProvider(lyrics_providers->network(), app));
|
||||||
lyrics_providers->ReloadSettings();
|
lyrics_providers->ReloadSettings();
|
||||||
return lyrics_providers;
|
return lyrics_providers;
|
||||||
}),
|
}),
|
||||||
@@ -183,7 +183,6 @@ class ApplicationImpl {
|
|||||||
|
|
||||||
Lazy<TagReaderClient> tag_reader_client_;
|
Lazy<TagReaderClient> tag_reader_client_;
|
||||||
Lazy<Database> database_;
|
Lazy<Database> database_;
|
||||||
Lazy<Appearance> appearance_;
|
|
||||||
Lazy<TaskManager> task_manager_;
|
Lazy<TaskManager> task_manager_;
|
||||||
Lazy<Player> player_;
|
Lazy<Player> player_;
|
||||||
Lazy<DeviceFinders> device_finders_;
|
Lazy<DeviceFinders> device_finders_;
|
||||||
@@ -315,7 +314,6 @@ void Application::ReloadSettings() { emit SettingsChanged(); }
|
|||||||
void Application::OpenSettingsDialogAtPage(SettingsDialog::Page page) { emit SettingsDialogRequested(page); }
|
void Application::OpenSettingsDialogAtPage(SettingsDialog::Page page) { emit SettingsDialogRequested(page); }
|
||||||
|
|
||||||
TagReaderClient *Application::tag_reader_client() const { return p_->tag_reader_client_.get(); }
|
TagReaderClient *Application::tag_reader_client() const { return p_->tag_reader_client_.get(); }
|
||||||
Appearance *Application::appearance() const { return p_->appearance_.get(); }
|
|
||||||
Database *Application::database() const { return p_->database_.get(); }
|
Database *Application::database() const { return p_->database_.get(); }
|
||||||
TaskManager *Application::task_manager() const { return p_->task_manager_.get(); }
|
TaskManager *Application::task_manager() const { return p_->task_manager_.get(); }
|
||||||
Player *Application::player() const { return p_->player_.get(); }
|
Player *Application::player() const { return p_->player_.get(); }
|
||||||
|
|||||||
@@ -41,7 +41,6 @@ class TagReaderClient;
|
|||||||
class Database;
|
class Database;
|
||||||
class DeviceFinders;
|
class DeviceFinders;
|
||||||
class Player;
|
class Player;
|
||||||
class Appearance;
|
|
||||||
class SCollection;
|
class SCollection;
|
||||||
class CollectionBackend;
|
class CollectionBackend;
|
||||||
class CollectionModel;
|
class CollectionModel;
|
||||||
@@ -73,7 +72,6 @@ class Application : public QObject {
|
|||||||
|
|
||||||
TagReaderClient *tag_reader_client() const;
|
TagReaderClient *tag_reader_client() const;
|
||||||
Database *database() const;
|
Database *database() const;
|
||||||
Appearance *appearance() const;
|
|
||||||
TaskManager *task_manager() const;
|
TaskManager *task_manager() const;
|
||||||
Player *player() const;
|
Player *player() const;
|
||||||
DeviceFinders *device_finders() const;
|
DeviceFinders *device_finders() const;
|
||||||
|
|||||||
@@ -81,8 +81,8 @@ const char *CommandlineOptions::kVersionText = "Strawberry %1";
|
|||||||
CommandlineOptions::CommandlineOptions(int argc, char **argv)
|
CommandlineOptions::CommandlineOptions(int argc, char **argv)
|
||||||
: argc_(argc),
|
: argc_(argc),
|
||||||
argv_(argv),
|
argv_(argv),
|
||||||
url_list_action_(UrlList_None),
|
url_list_action_(UrlListAction::None),
|
||||||
player_action_(Player_None),
|
player_action_(PlayerAction::None),
|
||||||
set_volume_(-1),
|
set_volume_(-1),
|
||||||
volume_modifier_(0),
|
volume_modifier_(0),
|
||||||
seek_to_(-1),
|
seek_to_(-1),
|
||||||
@@ -128,13 +128,13 @@ bool CommandlineOptions::Parse() {
|
|||||||
{"previous", no_argument, nullptr, 'r'},
|
{"previous", no_argument, nullptr, 'r'},
|
||||||
{"next", no_argument, nullptr, 'f'},
|
{"next", no_argument, nullptr, 'f'},
|
||||||
{"volume", required_argument, nullptr, 'v'},
|
{"volume", required_argument, nullptr, 'v'},
|
||||||
{"volume-up", no_argument, nullptr, VolumeUp},
|
{"volume-up", no_argument, nullptr, LongOptions::VolumeUp},
|
||||||
{"volume-down", no_argument, nullptr, VolumeDown},
|
{"volume-down", no_argument, nullptr, LongOptions::VolumeDown},
|
||||||
{"volume-increase-by", required_argument, nullptr, VolumeIncreaseBy},
|
{"volume-increase-by", required_argument, nullptr, LongOptions::VolumeIncreaseBy},
|
||||||
{"volume-decrease-by", required_argument, nullptr, VolumeDecreaseBy},
|
{"volume-decrease-by", required_argument, nullptr, LongOptions::VolumeDecreaseBy},
|
||||||
{"seek-to", required_argument, nullptr, SeekTo},
|
{"seek-to", required_argument, nullptr, LongOptions::SeekTo},
|
||||||
{"seek-by", required_argument, nullptr, SeekBy},
|
{"seek-by", required_argument, nullptr, LongOptions::SeekBy},
|
||||||
{"restart-or-previous", no_argument, nullptr, RestartOrPrevious},
|
{"restart-or-previous", no_argument, nullptr, LongOptions::RestartOrPrevious},
|
||||||
{"create", required_argument, nullptr, 'c'},
|
{"create", required_argument, nullptr, 'c'},
|
||||||
{"append", no_argument, nullptr, 'a'},
|
{"append", no_argument, nullptr, 'a'},
|
||||||
{"load", no_argument, nullptr, 'l'},
|
{"load", no_argument, nullptr, 'l'},
|
||||||
@@ -144,10 +144,10 @@ bool CommandlineOptions::Parse() {
|
|||||||
{"toggle-pretty-osd", no_argument, nullptr, 'y'},
|
{"toggle-pretty-osd", no_argument, nullptr, 'y'},
|
||||||
{"language", required_argument, nullptr, 'g'},
|
{"language", required_argument, nullptr, 'g'},
|
||||||
{"resize-window", required_argument, nullptr, 'w'},
|
{"resize-window", required_argument, nullptr, 'w'},
|
||||||
{"quiet", no_argument, nullptr, Quiet},
|
{"quiet", no_argument, nullptr, LongOptions::Quiet},
|
||||||
{"verbose", no_argument, nullptr, Verbose},
|
{"verbose", no_argument, nullptr, LongOptions::Verbose},
|
||||||
{"log-levels", required_argument, nullptr, LogLevels},
|
{"log-levels", required_argument, nullptr, LongOptions::LogLevels},
|
||||||
{"version", no_argument, nullptr, Version},
|
{"version", no_argument, nullptr, LongOptions::Version},
|
||||||
{nullptr, 0, nullptr, 0}};
|
{nullptr, 0, nullptr, 0}};
|
||||||
|
|
||||||
// Parse the arguments
|
// Parse the arguments
|
||||||
@@ -198,39 +198,39 @@ bool CommandlineOptions::Parse() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
case 'p':
|
case 'p':
|
||||||
player_action_ = Player_Play;
|
player_action_ = PlayerAction::Play;
|
||||||
break;
|
break;
|
||||||
case 't':
|
case 't':
|
||||||
player_action_ = Player_PlayPause;
|
player_action_ = PlayerAction::PlayPause;
|
||||||
break;
|
break;
|
||||||
case 'u':
|
case 'u':
|
||||||
player_action_ = Player_Pause;
|
player_action_ = PlayerAction::Pause;
|
||||||
break;
|
break;
|
||||||
case 's':
|
case 's':
|
||||||
player_action_ = Player_Stop;
|
player_action_ = PlayerAction::Stop;
|
||||||
break;
|
break;
|
||||||
case 'q':
|
case 'q':
|
||||||
player_action_ = Player_StopAfterCurrent;
|
player_action_ = PlayerAction::StopAfterCurrent;
|
||||||
break;
|
break;
|
||||||
case 'r':
|
case 'r':
|
||||||
player_action_ = Player_Previous;
|
player_action_ = PlayerAction::Previous;
|
||||||
break;
|
break;
|
||||||
case 'f':
|
case 'f':
|
||||||
player_action_ = Player_Next;
|
player_action_ = PlayerAction::Next;
|
||||||
break;
|
break;
|
||||||
case 'i':
|
case 'i':
|
||||||
player_action_ = Player_PlayPlaylist;
|
player_action_ = PlayerAction::PlayPlaylist;
|
||||||
playlist_name_ = QString(optarg);
|
playlist_name_ = QString(optarg);
|
||||||
break;
|
break;
|
||||||
case 'c':
|
case 'c':
|
||||||
url_list_action_ = UrlList_CreateNew;
|
url_list_action_ = UrlListAction::CreateNew;
|
||||||
playlist_name_ = QString(optarg);
|
playlist_name_ = QString(optarg);
|
||||||
break;
|
break;
|
||||||
case 'a':
|
case 'a':
|
||||||
url_list_action_ = UrlList_Append;
|
url_list_action_ = UrlListAction::Append;
|
||||||
break;
|
break;
|
||||||
case 'l':
|
case 'l':
|
||||||
url_list_action_ = UrlList_Load;
|
url_list_action_ = UrlListAction::Load;
|
||||||
break;
|
break;
|
||||||
case 'o':
|
case 'o':
|
||||||
show_osd_ = true;
|
show_osd_ = true;
|
||||||
@@ -241,22 +241,22 @@ bool CommandlineOptions::Parse() {
|
|||||||
case 'g':
|
case 'g':
|
||||||
language_ = QString(optarg);
|
language_ = QString(optarg);
|
||||||
break;
|
break;
|
||||||
case VolumeUp:
|
case LongOptions::VolumeUp:
|
||||||
volume_modifier_ = +4;
|
volume_modifier_ = +4;
|
||||||
break;
|
break;
|
||||||
case VolumeDown:
|
case LongOptions::VolumeDown:
|
||||||
volume_modifier_ = -4;
|
volume_modifier_ = -4;
|
||||||
break;
|
break;
|
||||||
case Quiet:
|
case LongOptions::Quiet:
|
||||||
log_levels_ = "1";
|
log_levels_ = "1";
|
||||||
break;
|
break;
|
||||||
case Verbose:
|
case LongOptions::Verbose:
|
||||||
log_levels_ = "3";
|
log_levels_ = "3";
|
||||||
break;
|
break;
|
||||||
case LogLevels:
|
case LongOptions::LogLevels:
|
||||||
log_levels_ = QString(optarg);
|
log_levels_ = QString(optarg);
|
||||||
break;
|
break;
|
||||||
case Version: {
|
case LongOptions::Version: {
|
||||||
QString version_text = QString(kVersionText).arg(STRAWBERRY_VERSION_DISPLAY);
|
QString version_text = QString(kVersionText).arg(STRAWBERRY_VERSION_DISPLAY);
|
||||||
std::cout << version_text.toLocal8Bit().constData() << std::endl;
|
std::cout << version_text.toLocal8Bit().constData() << std::endl;
|
||||||
std::exit(0);
|
std::exit(0);
|
||||||
@@ -266,28 +266,28 @@ bool CommandlineOptions::Parse() {
|
|||||||
if (!ok) set_volume_ = -1;
|
if (!ok) set_volume_ = -1;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case VolumeIncreaseBy:
|
case LongOptions::VolumeIncreaseBy:
|
||||||
volume_modifier_ = QString(optarg).toInt(&ok);
|
volume_modifier_ = QString(optarg).toInt(&ok);
|
||||||
if (!ok) volume_modifier_ = 0;
|
if (!ok) volume_modifier_ = 0;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case VolumeDecreaseBy:
|
case LongOptions::VolumeDecreaseBy:
|
||||||
volume_modifier_ = -QString(optarg).toInt(&ok);
|
volume_modifier_ = -QString(optarg).toInt(&ok);
|
||||||
if (!ok) volume_modifier_ = 0;
|
if (!ok) volume_modifier_ = 0;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case SeekTo:
|
case LongOptions::SeekTo:
|
||||||
seek_to_ = QString(optarg).toInt(&ok);
|
seek_to_ = QString(optarg).toInt(&ok);
|
||||||
if (!ok) seek_to_ = -1;
|
if (!ok) seek_to_ = -1;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case SeekBy:
|
case LongOptions::SeekBy:
|
||||||
seek_by_ = QString(optarg).toInt(&ok);
|
seek_by_ = QString(optarg).toInt(&ok);
|
||||||
if (!ok) seek_by_ = 0;
|
if (!ok) seek_by_ = 0;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case RestartOrPrevious:
|
case LongOptions::RestartOrPrevious:
|
||||||
player_action_ = Player_RestartOrPrevious;
|
player_action_ = PlayerAction::RestartOrPrevious;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 'k':
|
case 'k':
|
||||||
@@ -297,7 +297,7 @@ bool CommandlineOptions::Parse() {
|
|||||||
|
|
||||||
case 'w':
|
case 'w':
|
||||||
window_size_ = QString(optarg);
|
window_size_ = QString(optarg);
|
||||||
player_action_ = Player_ResizeWindow;
|
player_action_ = PlayerAction::ResizeWindow;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case '?':
|
case '?':
|
||||||
@@ -323,7 +323,7 @@ bool CommandlineOptions::Parse() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
bool CommandlineOptions::is_empty() const {
|
bool CommandlineOptions::is_empty() const {
|
||||||
return player_action_ == Player_None &&
|
return player_action_ == PlayerAction::None &&
|
||||||
set_volume_ == -1 &&
|
set_volume_ == -1 &&
|
||||||
volume_modifier_ == 0 &&
|
volume_modifier_ == 0 &&
|
||||||
seek_to_ == -1 &&
|
seek_to_ == -1 &&
|
||||||
@@ -335,7 +335,7 @@ bool CommandlineOptions::is_empty() const {
|
|||||||
}
|
}
|
||||||
|
|
||||||
bool CommandlineOptions::contains_play_options() const {
|
bool CommandlineOptions::contains_play_options() const {
|
||||||
return player_action_ != Player_None || play_track_at_ != -1 || !urls_.isEmpty();
|
return player_action_ != PlayerAction::None || play_track_at_ != -1 || !urls_.isEmpty();
|
||||||
}
|
}
|
||||||
|
|
||||||
QByteArray CommandlineOptions::Serialize() const {
|
QByteArray CommandlineOptions::Serialize() const {
|
||||||
|
|||||||
@@ -41,24 +41,24 @@ class CommandlineOptions {
|
|||||||
|
|
||||||
// Don't change the values or order, these get serialised and sent to
|
// Don't change the values or order, these get serialised and sent to
|
||||||
// possibly a different version of Strawberry
|
// possibly a different version of Strawberry
|
||||||
enum UrlListAction {
|
enum class UrlListAction {
|
||||||
UrlList_Append = 0,
|
Append = 0,
|
||||||
UrlList_Load = 1,
|
Load = 1,
|
||||||
UrlList_None = 2,
|
None = 2,
|
||||||
UrlList_CreateNew = 3,
|
CreateNew = 3
|
||||||
};
|
};
|
||||||
enum PlayerAction {
|
enum class PlayerAction {
|
||||||
Player_None = 0,
|
None = 0,
|
||||||
Player_Play = 1,
|
Play = 1,
|
||||||
Player_PlayPause = 2,
|
PlayPause = 2,
|
||||||
Player_Pause = 3,
|
Pause = 3,
|
||||||
Player_Stop = 4,
|
Stop = 4,
|
||||||
Player_Previous = 5,
|
Previous = 5,
|
||||||
Player_Next = 6,
|
Next = 6,
|
||||||
Player_RestartOrPrevious = 7,
|
RestartOrPrevious = 7,
|
||||||
Player_StopAfterCurrent = 8,
|
StopAfterCurrent = 8,
|
||||||
Player_PlayPlaylist = 9,
|
PlayPlaylist = 9,
|
||||||
Player_ResizeWindow = 10
|
ResizeWindow = 10
|
||||||
};
|
};
|
||||||
|
|
||||||
bool Parse();
|
bool Parse();
|
||||||
|
|||||||
@@ -48,7 +48,7 @@
|
|||||||
#include "scopedtransaction.h"
|
#include "scopedtransaction.h"
|
||||||
|
|
||||||
const char *Database::kDatabaseFilename = "strawberry.db";
|
const char *Database::kDatabaseFilename = "strawberry.db";
|
||||||
const int Database::kSchemaVersion = 15;
|
const int Database::kSchemaVersion = 16;
|
||||||
const int Database::kMinSupportedSchemaVersion = 10;
|
const int Database::kMinSupportedSchemaVersion = 10;
|
||||||
const char *Database::kMagicAllSongsTables = "%allsongstables";
|
const char *Database::kMagicAllSongsTables = "%allsongstables";
|
||||||
|
|
||||||
@@ -536,7 +536,8 @@ void Database::DoBackup() {
|
|||||||
|
|
||||||
bool Database::OpenDatabase(const QString &filename, sqlite3 **connection) {
|
bool Database::OpenDatabase(const QString &filename, sqlite3 **connection) {
|
||||||
|
|
||||||
int ret = sqlite3_open(filename.toUtf8(), connection);
|
const QByteArray filename_data = filename.toUtf8();
|
||||||
|
int ret = sqlite3_open(filename_data.constData(), connection);
|
||||||
if (ret != 0) {
|
if (ret != 0) {
|
||||||
if (*connection) {
|
if (*connection) {
|
||||||
const char *error_message = sqlite3_errmsg(*connection);
|
const char *error_message = sqlite3_errmsg(*connection);
|
||||||
|
|||||||
@@ -29,12 +29,12 @@
|
|||||||
#include <QString>
|
#include <QString>
|
||||||
|
|
||||||
#include "core/logging.h"
|
#include "core/logging.h"
|
||||||
#include "utilities.h"
|
#include "utilities/fileutils.h"
|
||||||
#include "musicstorage.h"
|
#include "musicstorage.h"
|
||||||
|
|
||||||
#include "filesystemmusicstorage.h"
|
#include "filesystemmusicstorage.h"
|
||||||
|
|
||||||
FilesystemMusicStorage::FilesystemMusicStorage(const QString &root, const std::optional<int> collection_directory_id) : root_(root), collection_directory_id_(collection_directory_id) {}
|
FilesystemMusicStorage::FilesystemMusicStorage(const Song::Source source, const QString &root, const std::optional<int> collection_directory_id) : source_(source), root_(root), collection_directory_id_(collection_directory_id) {}
|
||||||
|
|
||||||
bool FilesystemMusicStorage::CopyToStorage(const CopyJob &job) {
|
bool FilesystemMusicStorage::CopyToStorage(const CopyJob &job) {
|
||||||
|
|
||||||
@@ -106,12 +106,7 @@ bool FilesystemMusicStorage::DeleteFromStorage(const DeleteJob &job) {
|
|||||||
|
|
||||||
if (job.use_trash_) {
|
if (job.use_trash_) {
|
||||||
#if QT_VERSION >= QT_VERSION_CHECK(5, 15, 0)
|
#if QT_VERSION >= QT_VERSION_CHECK(5, 15, 0)
|
||||||
if (fileInfo.isDir()) {
|
return QFile::moveToTrash(path);
|
||||||
return Utilities::MoveToTrashRecursive(path);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
return QFile::moveToTrash(path);
|
|
||||||
}
|
|
||||||
#else
|
#else
|
||||||
return false;
|
return false;
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
@@ -28,12 +28,14 @@
|
|||||||
|
|
||||||
#include <QString>
|
#include <QString>
|
||||||
|
|
||||||
|
#include "song.h"
|
||||||
#include "musicstorage.h"
|
#include "musicstorage.h"
|
||||||
|
|
||||||
class FilesystemMusicStorage : public virtual MusicStorage {
|
class FilesystemMusicStorage : public virtual MusicStorage {
|
||||||
public:
|
public:
|
||||||
explicit FilesystemMusicStorage(const QString &root, const std::optional<int> collection_directory_id = std::optional<int>());
|
explicit FilesystemMusicStorage(const Song::Source source, const QString &root, const std::optional<int> collection_directory_id = std::optional<int>());
|
||||||
|
|
||||||
|
Song::Source source() const override { return source_; }
|
||||||
QString LocalPath() const override { return root_; }
|
QString LocalPath() const override { return root_; }
|
||||||
std::optional<int> collection_directory_id() const override { return collection_directory_id_; }
|
std::optional<int> collection_directory_id() const override { return collection_directory_id_; }
|
||||||
|
|
||||||
@@ -41,6 +43,7 @@ class FilesystemMusicStorage : public virtual MusicStorage {
|
|||||||
bool DeleteFromStorage(const DeleteJob &job) override;
|
bool DeleteFromStorage(const DeleteJob &job) override;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
Song::Source source_;
|
||||||
QString root_;
|
QString root_;
|
||||||
std::optional<int> collection_directory_id_;
|
std::optional<int> collection_directory_id_;
|
||||||
|
|
||||||
|
|||||||
@@ -52,7 +52,7 @@ void IconLoader::Init() {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
QIcon IconLoader::Load(const QString &name, const int fixed_size, const int min_size, const int max_size) {
|
QIcon IconLoader::Load(const QString &name, const bool system_icon, const int fixed_size, const int min_size, const int max_size) {
|
||||||
|
|
||||||
QIcon ret;
|
QIcon ret;
|
||||||
|
|
||||||
@@ -69,7 +69,7 @@ QIcon IconLoader::Load(const QString &name, const int fixed_size, const int min_
|
|||||||
sizes << fixed_size;
|
sizes << fixed_size;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (system_icons_) {
|
if (system_icon && system_icons_) {
|
||||||
IconMapper::IconProperties icon_prop;
|
IconMapper::IconProperties icon_prop;
|
||||||
if (IconMapper::iconmapper_.contains(name)) {
|
if (IconMapper::iconmapper_.contains(name)) {
|
||||||
icon_prop = IconMapper::iconmapper_[name];
|
icon_prop = IconMapper::iconmapper_[name];
|
||||||
|
|||||||
@@ -26,7 +26,7 @@
|
|||||||
class IconLoader {
|
class IconLoader {
|
||||||
public:
|
public:
|
||||||
static void Init();
|
static void Init();
|
||||||
static QIcon Load(const QString &name, const int fixed_size = 0, const int min_size = 0, const int max_size = 0);
|
static QIcon Load(const QString &name, const bool system_icon = true, const int fixed_size = 0, const int min_size = 0, const int max_size = 0);
|
||||||
private:
|
private:
|
||||||
explicit IconLoader() {}
|
explicit IconLoader() {}
|
||||||
static bool system_icons_;
|
static bool system_icons_;
|
||||||
|
|||||||
@@ -17,7 +17,8 @@
|
|||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#pragma once
|
#ifndef ICONMAPPER_H
|
||||||
|
#define ICONMAPPER_H
|
||||||
|
|
||||||
#include "config.h"
|
#include "config.h"
|
||||||
|
|
||||||
@@ -135,3 +136,6 @@ static const QMap<QString, IconProperties> iconmapper_ = { // clazy:exclude=non
|
|||||||
};
|
};
|
||||||
|
|
||||||
} // namespace IconMapper
|
} // namespace IconMapper
|
||||||
|
|
||||||
|
#endif // ICONMAPPER_H
|
||||||
|
|
||||||
|
|||||||
@@ -1,29 +1,44 @@
|
|||||||
|
/*
|
||||||
|
* Strawberry Music Player
|
||||||
|
* This file was part of Clementine.
|
||||||
|
* Copyright 2010, David Sansome <me@davidsansome.com>
|
||||||
|
* Copyright 2018-2021, Jonas Kvinge <jonas@jkvinge.net>
|
||||||
|
*
|
||||||
|
* Strawberry is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* Strawberry is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with Strawberry. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
#ifndef MAC_STARTUP_H
|
#ifndef MAC_STARTUP_H
|
||||||
#define MAC_STARTUP_H
|
#define MAC_STARTUP_H
|
||||||
|
|
||||||
#include "config.h"
|
#include "config.h"
|
||||||
|
|
||||||
|
#include <CoreFoundation/CFDictionary.h>
|
||||||
|
|
||||||
#include <QString>
|
#include <QString>
|
||||||
|
#include <QWidget>
|
||||||
#include <QKeySequence>
|
#include <QKeySequence>
|
||||||
|
|
||||||
class QObject;
|
#ifdef __OBJC__
|
||||||
class QWidget;
|
@class NSEvent;
|
||||||
|
#else
|
||||||
|
class NSEvent;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
class PlatformInterface;
|
||||||
class GlobalShortcutsBackendMacOS;
|
class GlobalShortcutsBackendMacOS;
|
||||||
|
|
||||||
class PlatformInterface {
|
|
||||||
public:
|
|
||||||
PlatformInterface() = default;
|
|
||||||
virtual ~PlatformInterface() {}
|
|
||||||
|
|
||||||
// Called when the application should show itself.
|
|
||||||
virtual void Activate() = 0;
|
|
||||||
virtual bool LoadUrl(const QString &url) = 0;
|
|
||||||
|
|
||||||
private:
|
|
||||||
Q_DISABLE_COPY(PlatformInterface)
|
|
||||||
};
|
|
||||||
|
|
||||||
namespace mac {
|
namespace mac {
|
||||||
|
|
||||||
void MacMain();
|
void MacMain();
|
||||||
@@ -32,6 +47,9 @@ void SetApplicationHandler(PlatformInterface *handler);
|
|||||||
|
|
||||||
void EnableFullScreen(const QWidget &main_window);
|
void EnableFullScreen(const QWidget &main_window);
|
||||||
|
|
||||||
|
QKeySequence KeySequenceFromNSEvent(NSEvent *event);
|
||||||
|
void DumpDictionary(CFDictionaryRef dict);
|
||||||
|
|
||||||
} // namespace mac
|
} // namespace mac
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user