Compare commits
373 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c028770f8e | ||
|
|
8f1a99b37e | ||
|
|
e66651a4cb | ||
|
|
1d9a052870 | ||
|
|
d3ee749c14 | ||
|
|
33076aa33a | ||
|
|
2a2663eeb5 | ||
|
|
fa04eb67db | ||
|
|
f8ad8a7211 | ||
|
|
5f4d6dffef | ||
|
|
b9c7510946 | ||
|
|
c0e709a0e3 | ||
|
|
e690be1bdd | ||
|
|
6ed5190276 | ||
|
|
4c4a351fbd | ||
|
|
435ffc75b6 | ||
|
|
4dbc06bdd0 | ||
|
|
5a7cbb2f3d | ||
|
|
354b55cbbc | ||
|
|
b87a950357 | ||
|
|
950c236720 | ||
|
|
32982be4f2 | ||
|
|
f467331934 | ||
|
|
ec839e6aae | ||
|
|
45f1521da6 | ||
|
|
841a44a18e | ||
|
|
1deacaecf9 | ||
|
|
6af8e6c25b | ||
|
|
b0849d21f3 | ||
|
|
5b60ea8e77 | ||
|
|
ec3f95a260 | ||
|
|
0bfd4d2e04 | ||
|
|
a6ba0cfc97 | ||
|
|
6d55eb5974 | ||
|
|
972053c699 | ||
|
|
9db7896828 | ||
|
|
be6f93735d | ||
|
|
3bcea249ac | ||
|
|
8ee32dfa88 | ||
|
|
e2f9411b46 | ||
|
|
0bd68c3817 | ||
|
|
f03505ab67 | ||
|
|
f1ae795c0f | ||
|
|
50fcda2763 | ||
|
|
331aa382f9 | ||
|
|
3c160c2f13 | ||
|
|
4fd617a32f | ||
|
|
0d294eb218 | ||
|
|
a49e3b90b0 | ||
|
|
201392f22e | ||
|
|
3fa4ee772f | ||
|
|
529c83c572 | ||
|
|
6a9e56e9d9 | ||
|
|
716e80fb84 | ||
|
|
80067b806d | ||
|
|
315073f9a7 | ||
|
|
a1dbbba1a1 | ||
|
|
cb8a0b6853 | ||
|
|
a5a29f7ad3 | ||
|
|
8e14ef7c0c | ||
|
|
c270391772 | ||
|
|
e466cb6e30 | ||
|
|
f0df9dc0fb | ||
|
|
b31c08083a | ||
|
|
145a8d5b67 | ||
|
|
60d7a4e7ee | ||
|
|
b52ffd09b2 | ||
|
|
58278ab1e4 | ||
|
|
000cf5fd5a | ||
|
|
216b58e392 | ||
|
|
279cce49ba | ||
|
|
8184b77b13 | ||
|
|
b8b731ab04 | ||
|
|
1d4c39d13d | ||
|
|
ac26f5b2ef | ||
|
|
c07447d8f5 | ||
|
|
833ddf5f4c | ||
|
|
4dc4f468bb | ||
|
|
1aff69d3cf | ||
|
|
f2f63a703e | ||
|
|
97e6b17f96 | ||
|
|
b90d284b08 | ||
|
|
840a65c630 | ||
|
|
e5c89d4881 | ||
|
|
0fc6638294 | ||
|
|
7c7724e41c | ||
|
|
31319711dd | ||
|
|
8954729d55 | ||
|
|
ae4dc442b9 | ||
|
|
919ff414e6 | ||
|
|
b861703dad | ||
|
|
2f17647cd3 | ||
|
|
f8d2c7eba3 | ||
|
|
e511b2faf9 | ||
|
|
301e6b194a | ||
|
|
1208ca3ad4 | ||
|
|
84e7cd0df8 | ||
|
|
7e1077426e | ||
|
|
1e2c437a08 | ||
|
|
a7ce1d1225 | ||
|
|
1d3223e9c6 | ||
|
|
b01f3f4bb5 | ||
|
|
ee4cd11ae1 | ||
|
|
154f8b307e | ||
|
|
7aac7872e3 | ||
|
|
b2630032e7 | ||
|
|
80136b602b | ||
|
|
3df29ed2a9 | ||
|
|
69658f2022 | ||
|
|
5fd0a0831f | ||
|
|
43b299ebd1 | ||
|
|
9612304023 | ||
|
|
3154e59b36 | ||
|
|
2d5a496678 | ||
|
|
4c1c322b54 | ||
|
|
e9f3281694 | ||
|
|
c3534affdb | ||
|
|
c6e62b3263 | ||
|
|
d8ce177ce7 | ||
|
|
726bfbefb0 | ||
|
|
5c4a6487c0 | ||
|
|
3145433099 | ||
|
|
bdf6844b74 | ||
|
|
9ad430915c | ||
|
|
60bbd71a22 | ||
|
|
c96498758f | ||
|
|
f4600bd8eb | ||
|
|
7202a5734c | ||
|
|
8edb6eaa81 | ||
|
|
b8eecc05fd | ||
|
|
7fc5aef553 | ||
|
|
bee6b7f946 | ||
|
|
3bedfb6ac8 | ||
|
|
f49bf0192b | ||
|
|
f36ac5272b | ||
|
|
f0fe446f7f | ||
|
|
d9c4720a3e | ||
|
|
4de5d8f2a3 | ||
|
|
8651311016 | ||
|
|
1d27c5a14c | ||
|
|
b0b8ff2d49 | ||
|
|
cd03e1fc74 | ||
|
|
b273a449e3 | ||
|
|
b637867f9e | ||
|
|
41d5792b27 | ||
|
|
d22712a25b | ||
|
|
33968ee5da | ||
|
|
9baf1774e0 | ||
|
|
9ca66e4061 | ||
|
|
db2d34f840 | ||
|
|
195cc61df7 | ||
|
|
aaa530e72b | ||
|
|
fa856ee905 | ||
|
|
2bf7a5c4d3 | ||
|
|
780b982635 | ||
|
|
74bbc1f19f | ||
|
|
e314545f2e | ||
|
|
f1a3a12c1c | ||
|
|
ef080c3cb1 | ||
|
|
7313db5ac0 | ||
|
|
bf9aa524ed | ||
|
|
b59aa0827e | ||
|
|
2584f1293e | ||
|
|
de9d28adea | ||
|
|
b660287779 | ||
|
|
962536bc83 | ||
|
|
ee4fcf8100 | ||
|
|
9a4ed1a19d | ||
|
|
c1d0702b4d | ||
|
|
af1c46543b | ||
|
|
e2f5486987 | ||
|
|
b0d390aaf1 | ||
|
|
bae1b42394 | ||
|
|
a2533edd57 | ||
|
|
7b88c198fe | ||
|
|
c49cb0c119 | ||
|
|
03aabeb848 | ||
|
|
1d3a837f7a | ||
|
|
92adc18b8f | ||
|
|
e4697c8ff1 | ||
|
|
b741f1a580 | ||
|
|
8ee6de4162 | ||
|
|
2b9e7db924 | ||
|
|
49384ce294 | ||
|
|
ac59fff346 | ||
|
|
a3e9f152d8 | ||
|
|
ffe6a81b9a | ||
|
|
9bb051b4eb | ||
|
|
c1465a890f | ||
|
|
c3ee3318d7 | ||
|
|
272976f0b7 | ||
|
|
4724b170b1 | ||
|
|
337bd4bcef | ||
|
|
0604c78453 | ||
|
|
c8169adf7c | ||
|
|
7b610d131c | ||
|
|
18da55453f | ||
|
|
2aeab8b672 | ||
|
|
9c21707a55 | ||
|
|
b02ac833ad | ||
|
|
4de912cf41 | ||
|
|
ed260c6e20 | ||
|
|
fab38f693d | ||
|
|
aedbd52e9d | ||
|
|
4cbcb9d99c | ||
|
|
e967d15b4e | ||
|
|
bb43cc63ec | ||
|
|
ca176c319d | ||
|
|
17c960ecd4 | ||
|
|
b766ae3498 | ||
|
|
faf4c817cd | ||
|
|
b4e1f283c9 | ||
|
|
52c83d592c | ||
|
|
c8caea0d30 | ||
|
|
7a6c54d8e7 | ||
|
|
7082e52a4f | ||
|
|
6cdca617e0 | ||
|
|
a1adc1a75a | ||
|
|
b16bec704a | ||
|
|
664a8c79a1 | ||
|
|
bbf3d8e1a4 | ||
|
|
a35146440c | ||
|
|
8e79fafca9 | ||
|
|
6f8780d3cc | ||
|
|
264065a355 | ||
|
|
b606e4cd1a | ||
|
|
01d0eeaed0 | ||
|
|
8c4e24e65d | ||
|
|
19c9f9698d | ||
|
|
ae87c1b578 | ||
|
|
c42b1f5548 | ||
|
|
87ffbb0a85 | ||
|
|
702851a958 | ||
|
|
650f200a0b | ||
|
|
14d215bdf3 | ||
|
|
12aebc2fe9 | ||
|
|
f41b051ec7 | ||
|
|
25144eee89 | ||
|
|
0c62147536 | ||
|
|
7b282e21de | ||
|
|
c1fbe6d84c | ||
|
|
e20cbe4170 | ||
|
|
394955a03f | ||
|
|
16b4f5d065 | ||
|
|
c95295d8b4 | ||
|
|
658dce2607 | ||
|
|
01f8d0a27e | ||
|
|
39e1bfc84f | ||
|
|
e394416fa7 | ||
|
|
f1108bc0e2 | ||
|
|
ff31815d49 | ||
|
|
604aa63b47 | ||
|
|
ec4d036f50 | ||
|
|
1bf1c4ac63 | ||
|
|
981d46fbd4 | ||
|
|
eb6a353c31 | ||
|
|
ff673b1941 | ||
|
|
8b55cf8a3a | ||
|
|
d8682b4403 | ||
|
|
40a4bf195a | ||
|
|
312c5cbc3f | ||
|
|
f314c56ef0 | ||
|
|
ea8e5180ff | ||
|
|
7f76c3f2ce | ||
|
|
e4c5e99d0f | ||
|
|
80cfca5de2 | ||
|
|
9556a14de9 | ||
|
|
2025fc8325 | ||
|
|
2dd0f6a9ba | ||
|
|
a42039d6e5 | ||
|
|
7fafa8adfb | ||
|
|
f789657552 | ||
|
|
3e183bc10e | ||
|
|
16e5d93be3 | ||
|
|
6515e06a13 | ||
|
|
9ae0b32318 | ||
|
|
4cb3bc4185 | ||
|
|
f9ca24598e | ||
|
|
3ccc892d6a | ||
|
|
d7cacea843 | ||
|
|
e30233ac74 | ||
|
|
7c57631fcf | ||
|
|
0adc084dad | ||
|
|
e22199817c | ||
|
|
78f691d006 | ||
|
|
749bae1f16 | ||
|
|
1043e24322 | ||
|
|
a6d10b1fa7 | ||
|
|
a3159423f8 | ||
|
|
ecb5ca321b | ||
|
|
fd827fdfd8 | ||
|
|
92d77b14d5 | ||
|
|
be67d89d8b | ||
|
|
ddd1ce732a | ||
|
|
012d82183c | ||
|
|
f9c98ebcb3 | ||
|
|
10fe861dde | ||
|
|
16c027ecab | ||
|
|
cc578e7cc5 | ||
|
|
6972d3c4f9 | ||
|
|
46b164f2fb | ||
|
|
5431307527 | ||
|
|
79bf194ed6 | ||
|
|
506e670aa7 | ||
|
|
fdfe164dd1 | ||
|
|
af37056179 | ||
|
|
b0fc7187cf | ||
|
|
33ad1a7a86 | ||
|
|
dd72fb4ca5 | ||
|
|
e6c5f76872 | ||
|
|
14aa22d590 | ||
|
|
5ed4293641 | ||
|
|
f9fefcda57 | ||
|
|
99b40293db | ||
|
|
9b06e85f94 | ||
|
|
93d1d40ea5 | ||
|
|
98597c047a | ||
|
|
a5c1f4b0ee | ||
|
|
3d4c98d981 | ||
|
|
384e7dedb5 | ||
|
|
7df4453560 | ||
|
|
d406a1c341 | ||
|
|
6671d97b4a | ||
|
|
d02de72830 | ||
|
|
08f5172028 | ||
|
|
04f062547d | ||
|
|
4717d783dc | ||
|
|
93af064b36 | ||
|
|
c78d73d727 | ||
|
|
b69b3228be | ||
|
|
377f54700d | ||
|
|
d276339c80 | ||
|
|
b982a6a762 | ||
|
|
536fe637aa | ||
|
|
69f36eaa25 | ||
|
|
d6927a70bb | ||
|
|
1b1892a187 | ||
|
|
5bea71cd5c | ||
|
|
bb8d4e70ae | ||
|
|
8d8c7e8b7b | ||
|
|
9ff1f4d7b4 | ||
|
|
3a60dfe025 | ||
|
|
d358854e16 | ||
|
|
129587e94a | ||
|
|
0f575f4639 | ||
|
|
7aac741571 | ||
|
|
b8a9da8a4e | ||
|
|
be6b974334 | ||
|
|
194e81205b | ||
|
|
d711dcc99d | ||
|
|
3b72a12540 | ||
|
|
38a1b7765a | ||
|
|
02f2b8b6f0 | ||
|
|
7bfa75102c | ||
|
|
b0f3e7351c | ||
|
|
b5fa401db9 | ||
|
|
41f2710dea | ||
|
|
f19dda57f0 | ||
|
|
cc4a99ad80 | ||
|
|
1c1a3fc417 | ||
|
|
e97be5850b | ||
|
|
2c302654b7 | ||
|
|
3ee4dc77b2 | ||
|
|
db55f314c9 | ||
|
|
0b536b287f | ||
|
|
6f298a9917 | ||
|
|
70f829a2e5 | ||
|
|
1dfe07003f | ||
|
|
286b908062 | ||
|
|
4ec028e736 | ||
|
|
35a6d1437a | ||
|
|
7e3cb3de89 | ||
|
|
bd945039f1 |
@@ -86,7 +86,7 @@ ContinuationIndentWidth: 2
|
|||||||
Cpp11BracedListStyle: false
|
Cpp11BracedListStyle: false
|
||||||
DeriveLineEnding: true
|
DeriveLineEnding: true
|
||||||
DerivePointerAlignment: false
|
DerivePointerAlignment: false
|
||||||
DisableFormat: false
|
DisableFormat: true
|
||||||
EmptyLineAfterAccessModifier: Never
|
EmptyLineAfterAccessModifier: Never
|
||||||
EmptyLineBeforeAccessModifier: LogicalBlock
|
EmptyLineBeforeAccessModifier: LogicalBlock
|
||||||
ExperimentalAutoDetectBinPacking: false
|
ExperimentalAutoDetectBinPacking: false
|
||||||
|
|||||||
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
|
||||||
1020
.github/workflows/build.yml
vendored
1020
.github/workflows/build.yml
vendored
File diff suppressed because it is too large
Load Diff
10
3rdparty/README.md
vendored
10
3rdparty/README.md
vendored
@@ -1,15 +1,13 @@
|
|||||||
3rdparty libraries located in this directory
|
3rdparty libraries located in this directory
|
||||||
============================================
|
============================================
|
||||||
|
|
||||||
singleapplication
|
KDSingleApplication
|
||||||
-----------------
|
-----------------
|
||||||
This is a small static library used by Strawberry to prevent it from starting twice per user session.
|
This is a small static library used by Strawberry to prevent it from starting twice per user session.
|
||||||
If the user tries to start strawberry twice, the main window will maximize instead of starting another instance.
|
If the user tries to start strawberry twice, the main window will maximize instead of starting another instance.
|
||||||
If you dynamically link to your systems version, you'll need two versions, one defined as QApplication and
|
It is also used to pass command-line options through to the first instance.
|
||||||
one as a QCoreApplication.
|
|
||||||
It is included here because it is not packed by distros and is also used on macOS and Windows.
|
|
||||||
|
|
||||||
URL: https://github.com/itay-grudev/SingleApplication
|
URL: https://github.com/KDAB/KDSingleApplication/
|
||||||
|
|
||||||
|
|
||||||
SPMediaKeyTap
|
SPMediaKeyTap
|
||||||
@@ -27,4 +25,4 @@ Can safely be deleted on other platforms.
|
|||||||
|
|
||||||
getopt
|
getopt
|
||||||
------
|
------
|
||||||
getopt included only when compiling with MSVC on Windows.
|
getopt included only when compiling on Windows.
|
||||||
|
|||||||
3
3rdparty/getopt/CMakeLists.txt
vendored
3
3rdparty/getopt/CMakeLists.txt
vendored
@@ -1,2 +1,3 @@
|
|||||||
add_library(getopt STATIC getopt.c)
|
add_library(getopt STATIC getopt.cpp)
|
||||||
|
target_compile_definitions(getopt PRIVATE -DSTATIC_GETOPT -D_UNICODE)
|
||||||
target_include_directories(getopt PRIVATE ${CMAKE_CURRENT_SOURCE_DIR})
|
target_include_directories(getopt PRIVATE ${CMAKE_CURRENT_SOURCE_DIR})
|
||||||
|
|||||||
562
3rdparty/getopt/getopt.c
vendored
562
3rdparty/getopt/getopt.c
vendored
@@ -1,562 +0,0 @@
|
|||||||
/* $OpenBSD: getopt_long.c,v 1.23 2007/10/31 12:34:57 chl Exp $ */
|
|
||||||
/* $NetBSD: getopt_long.c,v 1.15 2002/01/31 22:43:40 tv Exp $ */
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Copyright (c) 2002 Todd C. Miller <Todd.Miller@courtesan.com>
|
|
||||||
*
|
|
||||||
* Permission to use, copy, modify, and distribute this software for any
|
|
||||||
* purpose with or without fee is hereby granted, provided that the above
|
|
||||||
* copyright notice and this permission notice appear in all copies.
|
|
||||||
*
|
|
||||||
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
|
||||||
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
|
||||||
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
|
||||||
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
|
||||||
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
|
||||||
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
|
||||||
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
|
||||||
*
|
|
||||||
* Sponsored in part by the Defense Advanced Research Projects
|
|
||||||
* Agency (DARPA) and Air Force Research Laboratory, Air Force
|
|
||||||
* Materiel Command, USAF, under agreement number F39502-99-1-0512.
|
|
||||||
*/
|
|
||||||
/*-
|
|
||||||
* Copyright (c) 2000 The NetBSD Foundation, Inc.
|
|
||||||
* All rights reserved.
|
|
||||||
*
|
|
||||||
* This code is derived from software contributed to The NetBSD Foundation
|
|
||||||
* by Dieter Baron and Thomas Klausner.
|
|
||||||
*
|
|
||||||
* Redistribution and use in source and binary forms, with or without
|
|
||||||
* modification, are permitted provided that the following conditions
|
|
||||||
* are met:
|
|
||||||
* 1. Redistributions of source code must retain the above copyright
|
|
||||||
* notice, this list of conditions and the following disclaimer.
|
|
||||||
* 2. Redistributions in binary form must reproduce the above copyright
|
|
||||||
* notice, this list of conditions and the following disclaimer in the
|
|
||||||
* documentation and/or other materials provided with the distribution.
|
|
||||||
*
|
|
||||||
* THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
|
|
||||||
* ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
|
|
||||||
* TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
|
|
||||||
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
|
|
||||||
* BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
|
||||||
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
|
||||||
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
|
||||||
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
|
||||||
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
|
||||||
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
|
||||||
* POSSIBILITY OF SUCH DAMAGE.
|
|
||||||
*/
|
|
||||||
|
|
||||||
#include <errno.h>
|
|
||||||
#include <stdlib.h>
|
|
||||||
#include <string.h>
|
|
||||||
#include <getopt.h>
|
|
||||||
#include <stdarg.h>
|
|
||||||
#include <stdio.h>
|
|
||||||
#include <windows.h>
|
|
||||||
|
|
||||||
#define REPLACE_GETOPT /* use this getopt as the system getopt(3) */
|
|
||||||
|
|
||||||
#ifdef REPLACE_GETOPT
|
|
||||||
int opterr = 1; /* if error message should be printed */
|
|
||||||
int optind = 1; /* index into parent argv vector */
|
|
||||||
int optopt = '?'; /* character checked for validity */
|
|
||||||
#undef optreset /* see getopt.h */
|
|
||||||
#define optreset __mingw_optreset
|
|
||||||
int optreset; /* reset getopt */
|
|
||||||
char *optarg; /* argument associated with option */
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#define PRINT_ERROR ((opterr) && (*options != ':'))
|
|
||||||
|
|
||||||
#define FLAG_PERMUTE 0x01 /* permute non-options to the end of argv */
|
|
||||||
#define FLAG_ALLARGS 0x02 /* treat non-options as args to option "-1" */
|
|
||||||
#define FLAG_LONGONLY 0x04 /* operate as getopt_long_only */
|
|
||||||
|
|
||||||
/* return values */
|
|
||||||
#define BADCH (int)'?'
|
|
||||||
#define BADARG ((*options == ':') ? (int)':' : (int)'?')
|
|
||||||
#define INORDER (int)1
|
|
||||||
|
|
||||||
#ifndef __CYGWIN__
|
|
||||||
#define __progname __argv[0]
|
|
||||||
#else
|
|
||||||
extern char __declspec(dllimport) *__progname;
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#ifdef __CYGWIN__
|
|
||||||
static char EMSG[] = "";
|
|
||||||
#else
|
|
||||||
#define EMSG ""
|
|
||||||
#endif
|
|
||||||
|
|
||||||
static int getopt_internal(int, char * const *, const char *,
|
|
||||||
const struct option *, int *, int);
|
|
||||||
static int parse_long_options(char * const *, const char *,
|
|
||||||
const struct option *, int *, int);
|
|
||||||
static int gcd(int, int);
|
|
||||||
static void permute_args(int, int, int, char * const *);
|
|
||||||
|
|
||||||
static char *place = EMSG; /* option letter processing */
|
|
||||||
|
|
||||||
/* XXX: set optreset to 1 rather than these two */
|
|
||||||
static int nonopt_start = -1; /* first non option argument (for permute) */
|
|
||||||
static int nonopt_end = -1; /* first option after non options (for permute) */
|
|
||||||
|
|
||||||
/* Error messages */
|
|
||||||
static const char recargchar[] = "option requires an argument -- %c";
|
|
||||||
static const char recargstring[] = "option requires an argument -- %s";
|
|
||||||
static const char ambig[] = "ambiguous option -- %.*s";
|
|
||||||
static const char noarg[] = "option doesn't take an argument -- %.*s";
|
|
||||||
static const char illoptchar[] = "unknown option -- %c";
|
|
||||||
static const char illoptstring[] = "unknown option -- %s";
|
|
||||||
|
|
||||||
static void
|
|
||||||
_vwarnx(const char *fmt,va_list ap)
|
|
||||||
{
|
|
||||||
(void)fprintf(stderr,"%s: ",__progname);
|
|
||||||
if (fmt != NULL)
|
|
||||||
(void)vfprintf(stderr,fmt,ap);
|
|
||||||
(void)fprintf(stderr,"\n");
|
|
||||||
}
|
|
||||||
|
|
||||||
static void
|
|
||||||
warnx(const char *fmt,...)
|
|
||||||
{
|
|
||||||
va_list ap;
|
|
||||||
va_start(ap,fmt);
|
|
||||||
_vwarnx(fmt,ap);
|
|
||||||
va_end(ap);
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Compute the greatest common divisor of a and b.
|
|
||||||
*/
|
|
||||||
static int
|
|
||||||
gcd(int a, int b)
|
|
||||||
{
|
|
||||||
int c;
|
|
||||||
|
|
||||||
c = a % b;
|
|
||||||
while (c != 0) {
|
|
||||||
a = b;
|
|
||||||
b = c;
|
|
||||||
c = a % b;
|
|
||||||
}
|
|
||||||
|
|
||||||
return (b);
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Exchange the block from nonopt_start to nonopt_end with the block
|
|
||||||
* from nonopt_end to opt_end (keeping the same order of arguments
|
|
||||||
* in each block).
|
|
||||||
*/
|
|
||||||
static void
|
|
||||||
permute_args(int panonopt_start, int panonopt_end, int opt_end,
|
|
||||||
char * const *nargv)
|
|
||||||
{
|
|
||||||
int cstart, cyclelen, i, j, ncycle, nnonopts, nopts, pos;
|
|
||||||
char *swap;
|
|
||||||
|
|
||||||
/*
|
|
||||||
* compute lengths of blocks and number and size of cycles
|
|
||||||
*/
|
|
||||||
nnonopts = panonopt_end - panonopt_start;
|
|
||||||
nopts = opt_end - panonopt_end;
|
|
||||||
ncycle = gcd(nnonopts, nopts);
|
|
||||||
cyclelen = (opt_end - panonopt_start) / ncycle;
|
|
||||||
|
|
||||||
for (i = 0; i < ncycle; i++) {
|
|
||||||
cstart = panonopt_end+i;
|
|
||||||
pos = cstart;
|
|
||||||
for (j = 0; j < cyclelen; j++) {
|
|
||||||
if (pos >= panonopt_end)
|
|
||||||
pos -= nnonopts;
|
|
||||||
else
|
|
||||||
pos += nopts;
|
|
||||||
swap = nargv[pos];
|
|
||||||
/* LINTED const cast */
|
|
||||||
((char **) nargv)[pos] = nargv[cstart];
|
|
||||||
/* LINTED const cast */
|
|
||||||
((char **)nargv)[cstart] = swap;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
* parse_long_options --
|
|
||||||
* Parse long options in argc/argv argument vector.
|
|
||||||
* Returns -1 if short_too is set and the option does not match long_options.
|
|
||||||
*/
|
|
||||||
static int
|
|
||||||
parse_long_options(char * const *nargv, const char *options,
|
|
||||||
const struct option *long_options, int *idx, int short_too)
|
|
||||||
{
|
|
||||||
char *current_argv, *has_equal;
|
|
||||||
size_t current_argv_len;
|
|
||||||
int i, ambiguous, match;
|
|
||||||
|
|
||||||
#define IDENTICAL_INTERPRETATION(_x, _y) \
|
|
||||||
(long_options[(_x)].has_arg == long_options[(_y)].has_arg && \
|
|
||||||
long_options[(_x)].flag == long_options[(_y)].flag && \
|
|
||||||
long_options[(_x)].val == long_options[(_y)].val)
|
|
||||||
|
|
||||||
current_argv = place;
|
|
||||||
match = -1;
|
|
||||||
ambiguous = 0;
|
|
||||||
|
|
||||||
optind++;
|
|
||||||
|
|
||||||
if ((has_equal = strchr(current_argv, '=')) != NULL) {
|
|
||||||
/* argument found (--option=arg) */
|
|
||||||
current_argv_len = has_equal - current_argv;
|
|
||||||
has_equal++;
|
|
||||||
} else
|
|
||||||
current_argv_len = strlen(current_argv);
|
|
||||||
|
|
||||||
for (i = 0; long_options[i].name; i++) {
|
|
||||||
/* find matching long option */
|
|
||||||
if (strncmp(current_argv, long_options[i].name,
|
|
||||||
current_argv_len))
|
|
||||||
continue;
|
|
||||||
|
|
||||||
if (strlen(long_options[i].name) == current_argv_len) {
|
|
||||||
/* exact match */
|
|
||||||
match = i;
|
|
||||||
ambiguous = 0;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
/*
|
|
||||||
* If this is a known short option, don't allow
|
|
||||||
* a partial match of a single character.
|
|
||||||
*/
|
|
||||||
if (short_too && current_argv_len == 1)
|
|
||||||
continue;
|
|
||||||
|
|
||||||
if (match == -1) /* partial match */
|
|
||||||
match = i;
|
|
||||||
else if (!IDENTICAL_INTERPRETATION(i, match))
|
|
||||||
ambiguous = 1;
|
|
||||||
}
|
|
||||||
if (ambiguous) {
|
|
||||||
/* ambiguous abbreviation */
|
|
||||||
if (PRINT_ERROR)
|
|
||||||
warnx(ambig, (int)current_argv_len,
|
|
||||||
current_argv);
|
|
||||||
optopt = 0;
|
|
||||||
return (BADCH);
|
|
||||||
}
|
|
||||||
if (match != -1) { /* option found */
|
|
||||||
if (long_options[match].has_arg == no_argument
|
|
||||||
&& has_equal) {
|
|
||||||
if (PRINT_ERROR)
|
|
||||||
warnx(noarg, (int)current_argv_len,
|
|
||||||
current_argv);
|
|
||||||
/*
|
|
||||||
* XXX: GNU sets optopt to val regardless of flag
|
|
||||||
*/
|
|
||||||
if (long_options[match].flag == NULL)
|
|
||||||
optopt = long_options[match].val;
|
|
||||||
else
|
|
||||||
optopt = 0;
|
|
||||||
return (BADARG);
|
|
||||||
}
|
|
||||||
if (long_options[match].has_arg == required_argument ||
|
|
||||||
long_options[match].has_arg == optional_argument) {
|
|
||||||
if (has_equal)
|
|
||||||
optarg = has_equal;
|
|
||||||
else if (long_options[match].has_arg ==
|
|
||||||
required_argument) {
|
|
||||||
/*
|
|
||||||
* optional argument doesn't use next nargv
|
|
||||||
*/
|
|
||||||
optarg = nargv[optind++];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if ((long_options[match].has_arg == required_argument)
|
|
||||||
&& (optarg == NULL)) {
|
|
||||||
/*
|
|
||||||
* Missing argument; leading ':' indicates no error
|
|
||||||
* should be generated.
|
|
||||||
*/
|
|
||||||
if (PRINT_ERROR)
|
|
||||||
warnx(recargstring,
|
|
||||||
current_argv);
|
|
||||||
/*
|
|
||||||
* XXX: GNU sets optopt to val regardless of flag
|
|
||||||
*/
|
|
||||||
if (long_options[match].flag == NULL)
|
|
||||||
optopt = long_options[match].val;
|
|
||||||
else
|
|
||||||
optopt = 0;
|
|
||||||
--optind;
|
|
||||||
return (BADARG);
|
|
||||||
}
|
|
||||||
} else { /* unknown option */
|
|
||||||
if (short_too) {
|
|
||||||
--optind;
|
|
||||||
return (-1);
|
|
||||||
}
|
|
||||||
if (PRINT_ERROR)
|
|
||||||
warnx(illoptstring, current_argv);
|
|
||||||
optopt = 0;
|
|
||||||
return (BADCH);
|
|
||||||
}
|
|
||||||
if (idx)
|
|
||||||
*idx = match;
|
|
||||||
if (long_options[match].flag) {
|
|
||||||
*long_options[match].flag = long_options[match].val;
|
|
||||||
return (0);
|
|
||||||
} else
|
|
||||||
return (long_options[match].val);
|
|
||||||
#undef IDENTICAL_INTERPRETATION
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
* getopt_internal --
|
|
||||||
* Parse argc/argv argument vector. Called by user level routines.
|
|
||||||
*/
|
|
||||||
static int
|
|
||||||
getopt_internal(int nargc, char * const *nargv, const char *options,
|
|
||||||
const struct option *long_options, int *idx, int flags)
|
|
||||||
{
|
|
||||||
char *oli; /* option letter list index */
|
|
||||||
int optchar, short_too;
|
|
||||||
static int posixly_correct = -1;
|
|
||||||
|
|
||||||
if (options == NULL)
|
|
||||||
return (-1);
|
|
||||||
|
|
||||||
/*
|
|
||||||
* XXX Some GNU programs (like cvs) set optind to 0 instead of
|
|
||||||
* XXX using optreset. Work around this braindamage.
|
|
||||||
*/
|
|
||||||
if (optind == 0)
|
|
||||||
optind = optreset = 1;
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Disable GNU extensions if POSIXLY_CORRECT is set or options
|
|
||||||
* string begins with a '+'.
|
|
||||||
*
|
|
||||||
* CV, 2009-12-14: Check POSIXLY_CORRECT anew if optind == 0 or
|
|
||||||
* optreset != 0 for GNU compatibility.
|
|
||||||
*/
|
|
||||||
if (posixly_correct == -1 || optreset != 0)
|
|
||||||
posixly_correct = (GetEnvironmentVariableW(L"POSIXLY_CORRECT", NULL, 0) != 0);
|
|
||||||
if (*options == '-')
|
|
||||||
flags |= FLAG_ALLARGS;
|
|
||||||
else if (posixly_correct || *options == '+')
|
|
||||||
flags &= ~FLAG_PERMUTE;
|
|
||||||
if (*options == '+' || *options == '-')
|
|
||||||
options++;
|
|
||||||
|
|
||||||
optarg = NULL;
|
|
||||||
if (optreset)
|
|
||||||
nonopt_start = nonopt_end = -1;
|
|
||||||
start:
|
|
||||||
if (optreset || !*place) { /* update scanning pointer */
|
|
||||||
optreset = 0;
|
|
||||||
if (optind >= nargc) { /* end of argument vector */
|
|
||||||
place = EMSG;
|
|
||||||
if (nonopt_end != -1) {
|
|
||||||
/* do permutation, if we have to */
|
|
||||||
permute_args(nonopt_start, nonopt_end,
|
|
||||||
optind, nargv);
|
|
||||||
optind -= nonopt_end - nonopt_start;
|
|
||||||
}
|
|
||||||
else if (nonopt_start != -1) {
|
|
||||||
/*
|
|
||||||
* If we skipped non-options, set optind
|
|
||||||
* to the first of them.
|
|
||||||
*/
|
|
||||||
optind = nonopt_start;
|
|
||||||
}
|
|
||||||
nonopt_start = nonopt_end = -1;
|
|
||||||
return (-1);
|
|
||||||
}
|
|
||||||
if (*(place = nargv[optind]) != '-' ||
|
|
||||||
(place[1] == '\0' && strchr(options, '-') == NULL)) {
|
|
||||||
place = EMSG; /* found non-option */
|
|
||||||
if (flags & FLAG_ALLARGS) {
|
|
||||||
/*
|
|
||||||
* GNU extension:
|
|
||||||
* return non-option as argument to option 1
|
|
||||||
*/
|
|
||||||
optarg = nargv[optind++];
|
|
||||||
return (INORDER);
|
|
||||||
}
|
|
||||||
if (!(flags & FLAG_PERMUTE)) {
|
|
||||||
/*
|
|
||||||
* If no permutation wanted, stop parsing
|
|
||||||
* at first non-option.
|
|
||||||
*/
|
|
||||||
return (-1);
|
|
||||||
}
|
|
||||||
/* do permutation */
|
|
||||||
if (nonopt_start == -1)
|
|
||||||
nonopt_start = optind;
|
|
||||||
else if (nonopt_end != -1) {
|
|
||||||
permute_args(nonopt_start, nonopt_end,
|
|
||||||
optind, nargv);
|
|
||||||
nonopt_start = optind -
|
|
||||||
(nonopt_end - nonopt_start);
|
|
||||||
nonopt_end = -1;
|
|
||||||
}
|
|
||||||
optind++;
|
|
||||||
/* process next argument */
|
|
||||||
goto start;
|
|
||||||
}
|
|
||||||
if (nonopt_start != -1 && nonopt_end == -1)
|
|
||||||
nonopt_end = optind;
|
|
||||||
|
|
||||||
/*
|
|
||||||
* If we have "-" do nothing, if "--" we are done.
|
|
||||||
*/
|
|
||||||
if (place[1] != '\0' && *++place == '-' && place[1] == '\0') {
|
|
||||||
optind++;
|
|
||||||
place = EMSG;
|
|
||||||
/*
|
|
||||||
* We found an option (--), so if we skipped
|
|
||||||
* non-options, we have to permute.
|
|
||||||
*/
|
|
||||||
if (nonopt_end != -1) {
|
|
||||||
permute_args(nonopt_start, nonopt_end,
|
|
||||||
optind, nargv);
|
|
||||||
optind -= nonopt_end - nonopt_start;
|
|
||||||
}
|
|
||||||
nonopt_start = nonopt_end = -1;
|
|
||||||
return (-1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Check long options if:
|
|
||||||
* 1) we were passed some
|
|
||||||
* 2) the arg is not just "-"
|
|
||||||
* 3) either the arg starts with -- we are getopt_long_only()
|
|
||||||
*/
|
|
||||||
if (long_options != NULL && place != nargv[optind] &&
|
|
||||||
(*place == '-' || (flags & FLAG_LONGONLY))) {
|
|
||||||
short_too = 0;
|
|
||||||
if (*place == '-')
|
|
||||||
place++; /* --foo long option */
|
|
||||||
else if (*place != ':' && strchr(options, *place) != NULL)
|
|
||||||
short_too = 1; /* could be short option too */
|
|
||||||
|
|
||||||
optchar = parse_long_options(nargv, options, long_options,
|
|
||||||
idx, short_too);
|
|
||||||
if (optchar != -1) {
|
|
||||||
place = EMSG;
|
|
||||||
return (optchar);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if ((optchar = (int)*place++) == (int)':' ||
|
|
||||||
(optchar == (int)'-' && *place != '\0') ||
|
|
||||||
(oli = strchr(options, optchar)) == NULL) {
|
|
||||||
/*
|
|
||||||
* If the user specified "-" and '-' isn't listed in
|
|
||||||
* options, return -1 (non-option) as per POSIX.
|
|
||||||
* Otherwise, it is an unknown option character (or ':').
|
|
||||||
*/
|
|
||||||
if (optchar == (int)'-' && *place == '\0')
|
|
||||||
return (-1);
|
|
||||||
if (!*place)
|
|
||||||
++optind;
|
|
||||||
if (PRINT_ERROR)
|
|
||||||
warnx(illoptchar, optchar);
|
|
||||||
optopt = optchar;
|
|
||||||
return (BADCH);
|
|
||||||
}
|
|
||||||
if (long_options != NULL && optchar == 'W' && oli[1] == ';') {
|
|
||||||
/* -W long-option */
|
|
||||||
if (*place) /* no space */
|
|
||||||
/* NOTHING */;
|
|
||||||
else if (++optind >= nargc) { /* no arg */
|
|
||||||
place = EMSG;
|
|
||||||
if (PRINT_ERROR)
|
|
||||||
warnx(recargchar, optchar);
|
|
||||||
optopt = optchar;
|
|
||||||
return (BADARG);
|
|
||||||
} else /* white space */
|
|
||||||
place = nargv[optind];
|
|
||||||
optchar = parse_long_options(nargv, options, long_options,
|
|
||||||
idx, 0);
|
|
||||||
place = EMSG;
|
|
||||||
return (optchar);
|
|
||||||
}
|
|
||||||
if (*++oli != ':') { /* doesn't take argument */
|
|
||||||
if (!*place)
|
|
||||||
++optind;
|
|
||||||
} else { /* takes (optional) argument */
|
|
||||||
optarg = NULL;
|
|
||||||
if (*place) /* no white space */
|
|
||||||
optarg = place;
|
|
||||||
else if (oli[1] != ':') { /* arg not optional */
|
|
||||||
if (++optind >= nargc) { /* no arg */
|
|
||||||
place = EMSG;
|
|
||||||
if (PRINT_ERROR)
|
|
||||||
warnx(recargchar, optchar);
|
|
||||||
optopt = optchar;
|
|
||||||
return (BADARG);
|
|
||||||
} else
|
|
||||||
optarg = nargv[optind];
|
|
||||||
}
|
|
||||||
place = EMSG;
|
|
||||||
++optind;
|
|
||||||
}
|
|
||||||
/* dump back option letter */
|
|
||||||
return (optchar);
|
|
||||||
}
|
|
||||||
|
|
||||||
#ifdef REPLACE_GETOPT
|
|
||||||
/*
|
|
||||||
* getopt --
|
|
||||||
* Parse argc/argv argument vector.
|
|
||||||
*
|
|
||||||
* [eventually this will replace the BSD getopt]
|
|
||||||
*/
|
|
||||||
int
|
|
||||||
getopt(int nargc, char * const *nargv, const char *options)
|
|
||||||
{
|
|
||||||
|
|
||||||
/*
|
|
||||||
* We don't pass FLAG_PERMUTE to getopt_internal() since
|
|
||||||
* the BSD getopt(3) (unlike GNU) has never done this.
|
|
||||||
*
|
|
||||||
* Furthermore, since many privileged programs call getopt()
|
|
||||||
* before dropping privileges it makes sense to keep things
|
|
||||||
* as simple (and bug-free) as possible.
|
|
||||||
*/
|
|
||||||
return (getopt_internal(nargc, nargv, options, NULL, NULL, 0));
|
|
||||||
}
|
|
||||||
#endif /* REPLACE_GETOPT */
|
|
||||||
|
|
||||||
/*
|
|
||||||
* getopt_long --
|
|
||||||
* Parse argc/argv argument vector.
|
|
||||||
*/
|
|
||||||
int
|
|
||||||
getopt_long(int nargc, char * const *nargv, const char *options,
|
|
||||||
const struct option *long_options, int *idx)
|
|
||||||
{
|
|
||||||
|
|
||||||
return (getopt_internal(nargc, nargv, options, long_options, idx,
|
|
||||||
FLAG_PERMUTE));
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
* getopt_long_only --
|
|
||||||
* Parse argc/argv argument vector.
|
|
||||||
*/
|
|
||||||
int
|
|
||||||
getopt_long_only(int nargc, char * const *nargv, const char *options,
|
|
||||||
const struct option *long_options, int *idx)
|
|
||||||
{
|
|
||||||
|
|
||||||
return (getopt_internal(nargc, nargv, options, long_options, idx,
|
|
||||||
FLAG_PERMUTE|FLAG_LONGONLY));
|
|
||||||
}
|
|
||||||
753
3rdparty/getopt/getopt.cpp
vendored
Normal file
753
3rdparty/getopt/getopt.cpp
vendored
Normal file
@@ -0,0 +1,753 @@
|
|||||||
|
/* Getopt for Microsoft C
|
||||||
|
This code is a modification of the Free Software Foundation, Inc.
|
||||||
|
Getopt library for parsing command line argument the purpose was
|
||||||
|
to provide a Microsoft Visual C friendly derivative. This code
|
||||||
|
provides functionality for both Unicode and Multibyte builds.
|
||||||
|
|
||||||
|
Date: 02/03/2011 - Ludvik Jerabek - Initial Release
|
||||||
|
Version: 1.1
|
||||||
|
Comment: Supports getopt, getopt_long, and getopt_long_only
|
||||||
|
and POSIXLY_CORRECT environment flag
|
||||||
|
License: LGPL
|
||||||
|
|
||||||
|
Revisions:
|
||||||
|
|
||||||
|
02/03/2011 - Ludvik Jerabek - Initial Release
|
||||||
|
02/20/2011 - Ludvik Jerabek - Fixed compiler warnings at Level 4
|
||||||
|
07/05/2011 - Ludvik Jerabek - Added no_argument, required_argument, optional_argument defs
|
||||||
|
08/03/2011 - Ludvik Jerabek - Fixed non-argument runtime bug which caused runtime exception
|
||||||
|
08/09/2011 - Ludvik Jerabek - Added code to export functions for DLL and LIB
|
||||||
|
02/15/2012 - Ludvik Jerabek - Fixed _GETOPT_THROW definition missing in implementation file
|
||||||
|
08/01/2012 - Ludvik Jerabek - Created separate functions for char and wchar_t characters so single dll can do both unicode and ansi
|
||||||
|
10/15/2012 - Ludvik Jerabek - Modified to match latest GNU features
|
||||||
|
06/19/2015 - Ludvik Jerabek - Fixed maximum option limitation caused by option_a (255) and option_w (65535) structure val variable
|
||||||
|
09/24/2022 - Ludvik Jerabek - Updated to match most recent getopt release
|
||||||
|
09/25/2022 - Ludvik Jerabek - Fixed memory allocation (malloc call) issue for wchar_t*
|
||||||
|
|
||||||
|
**DISCLAIMER**
|
||||||
|
THIS MATERIAL IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY KIND,
|
||||||
|
EITHER EXPRESS OR IMPLIED, INCLUDING, BUT Not LIMITED TO, THE
|
||||||
|
IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
|
||||||
|
PURPOSE, OR NON-INFRINGEMENT. SOME JURISDICTIONS DO NOT ALLOW THE
|
||||||
|
EXCLUSION OF IMPLIED WARRANTIES, SO THE ABOVE EXCLUSION MAY NOT
|
||||||
|
APPLY TO YOU. IN NO EVENT WILL I BE LIABLE TO ANY PARTY FOR ANY
|
||||||
|
DIRECT, INDIRECT, SPECIAL OR OTHER CONSEQUENTIAL DAMAGES FOR ANY
|
||||||
|
USE OF THIS MATERIAL INCLUDING, WITHOUT LIMITATION, ANY LOST
|
||||||
|
PROFITS, BUSINESS INTERRUPTION, LOSS OF PROGRAMS OR OTHER DATA ON
|
||||||
|
YOUR INFORMATION HANDLING SYSTEM OR OTHERWISE, EVEN If WE ARE
|
||||||
|
EXPRESSLY ADVISED OF THE POSSIBILITY OF SUCH DAMAGES.
|
||||||
|
*/
|
||||||
|
#define _CRT_SECURE_NO_WARNINGS
|
||||||
|
|
||||||
|
#include <assert.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <malloc.h>
|
||||||
|
#include "getopt.h"
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
# define _GETOPT_THROW throw()
|
||||||
|
#else
|
||||||
|
# define _GETOPT_THROW
|
||||||
|
#endif
|
||||||
|
|
||||||
|
int optind = 1;
|
||||||
|
int opterr = 1;
|
||||||
|
int optopt = '?';
|
||||||
|
enum ENUM_ORDERING {
|
||||||
|
REQUIRE_ORDER,
|
||||||
|
PERMUTE,
|
||||||
|
RETURN_IN_ORDER
|
||||||
|
};
|
||||||
|
|
||||||
|
//
|
||||||
|
//
|
||||||
|
// Ansi structures and functions follow
|
||||||
|
//
|
||||||
|
//
|
||||||
|
|
||||||
|
static struct _getopt_data_a {
|
||||||
|
int optind;
|
||||||
|
int opterr;
|
||||||
|
int optopt;
|
||||||
|
char *optarg;
|
||||||
|
int __initialized;
|
||||||
|
char *__nextchar;
|
||||||
|
enum ENUM_ORDERING __ordering;
|
||||||
|
int __first_nonopt;
|
||||||
|
int __last_nonopt;
|
||||||
|
} getopt_data_a;
|
||||||
|
char *optarg_a;
|
||||||
|
|
||||||
|
static void exchange_a(char **argv, struct _getopt_data_a *d) {
|
||||||
|
int bottom = d->__first_nonopt;
|
||||||
|
int middle = d->__last_nonopt;
|
||||||
|
int top = d->optind;
|
||||||
|
char *tem;
|
||||||
|
while (top > middle && middle > bottom) {
|
||||||
|
if (top - middle > middle - bottom) {
|
||||||
|
int len = middle - bottom;
|
||||||
|
int i;
|
||||||
|
for (i = 0; i < len; i++) {
|
||||||
|
tem = argv[bottom + i];
|
||||||
|
argv[bottom + i] = argv[top - (middle - bottom) + i];
|
||||||
|
argv[top - (middle - bottom) + i] = tem;
|
||||||
|
}
|
||||||
|
top -= len;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
int len = top - middle;
|
||||||
|
int i;
|
||||||
|
for (i = 0; i < len; i++) {
|
||||||
|
tem = argv[bottom + i];
|
||||||
|
argv[bottom + i] = argv[middle + i];
|
||||||
|
argv[middle + i] = tem;
|
||||||
|
}
|
||||||
|
bottom += len;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
d->__first_nonopt += (d->optind - d->__last_nonopt);
|
||||||
|
d->__last_nonopt = d->optind;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int process_long_option_a(int argc, char **argv, const char *optstring, const struct option_a *longopts, int *longind, int long_only, struct _getopt_data_a *d, int print_errors, const char *prefix);
|
||||||
|
static int process_long_option_a(int argc, char **argv, const char *optstring, const struct option_a *longopts, int *longind, int long_only, struct _getopt_data_a *d, int print_errors, const char *prefix) {
|
||||||
|
assert(longopts != NULL);
|
||||||
|
char *nameend;
|
||||||
|
size_t namelen;
|
||||||
|
const struct option_a *p;
|
||||||
|
const struct option_a *pfound = NULL;
|
||||||
|
int n_options;
|
||||||
|
int option_index = 0;
|
||||||
|
for (nameend = d->__nextchar; *nameend && *nameend != '='; nameend++)
|
||||||
|
;
|
||||||
|
namelen = nameend - d->__nextchar;
|
||||||
|
for (p = longopts, n_options = 0; p->name; p++, n_options++)
|
||||||
|
if (!strncmp(p->name, d->__nextchar, namelen) && namelen == strlen(p->name)) {
|
||||||
|
pfound = p;
|
||||||
|
option_index = n_options;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if (pfound == NULL) {
|
||||||
|
unsigned char *ambig_set = NULL;
|
||||||
|
int ambig_fallback = 0;
|
||||||
|
int indfound = -1;
|
||||||
|
for (p = longopts, option_index = 0; p->name; p++, option_index++)
|
||||||
|
if (!strncmp(p->name, d->__nextchar, namelen)) {
|
||||||
|
if (pfound == NULL) {
|
||||||
|
pfound = p;
|
||||||
|
indfound = option_index;
|
||||||
|
}
|
||||||
|
else if (long_only || pfound->has_arg != p->has_arg || pfound->flag != p->flag || pfound->val != p->val) {
|
||||||
|
if (!ambig_fallback) {
|
||||||
|
if (!print_errors)
|
||||||
|
ambig_fallback = 1;
|
||||||
|
|
||||||
|
else if (!ambig_set) {
|
||||||
|
if ((ambig_set = reinterpret_cast<unsigned char *>(malloc(n_options * sizeof(char)))) == NULL)
|
||||||
|
ambig_fallback = 1;
|
||||||
|
|
||||||
|
if (ambig_set) {
|
||||||
|
memset(ambig_set, 0, n_options * sizeof(char));
|
||||||
|
ambig_set[indfound] = 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (ambig_set)
|
||||||
|
ambig_set[option_index] = 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (ambig_set || ambig_fallback) {
|
||||||
|
if (print_errors) {
|
||||||
|
if (ambig_fallback)
|
||||||
|
fprintf(stderr, "%s: option '%s%s' is ambiguous\n", argv[0], prefix, d->__nextchar);
|
||||||
|
else {
|
||||||
|
_lock_file(stderr);
|
||||||
|
fprintf(stderr, "%s: option '%s%s' is ambiguous; possibilities:", argv[0], prefix, d->__nextchar);
|
||||||
|
for (option_index = 0; option_index < n_options; option_index++)
|
||||||
|
if (ambig_set[option_index])
|
||||||
|
fprintf(stderr, " '%s%s'", prefix, longopts[option_index].name);
|
||||||
|
fprintf(stderr, "\n");
|
||||||
|
_unlock_file(stderr);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
free(ambig_set);
|
||||||
|
d->__nextchar += strlen(d->__nextchar);
|
||||||
|
d->optind++;
|
||||||
|
d->optopt = 0;
|
||||||
|
return '?';
|
||||||
|
}
|
||||||
|
option_index = indfound;
|
||||||
|
}
|
||||||
|
if (pfound == NULL) {
|
||||||
|
if (!long_only || argv[d->optind][1] == '-' || strchr(optstring, *d->__nextchar) == NULL) {
|
||||||
|
if (print_errors)
|
||||||
|
fprintf(stderr, "%s: unrecognized option '%s%s'\n", argv[0], prefix, d->__nextchar);
|
||||||
|
d->__nextchar = NULL;
|
||||||
|
d->optind++;
|
||||||
|
d->optopt = 0;
|
||||||
|
return '?';
|
||||||
|
}
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
d->optind++;
|
||||||
|
d->__nextchar = NULL;
|
||||||
|
if (*nameend) {
|
||||||
|
if (pfound->has_arg)
|
||||||
|
d->optarg = nameend + 1;
|
||||||
|
else {
|
||||||
|
if (print_errors)
|
||||||
|
fprintf(stderr, "%s: option '%s%s' doesn't allow an argument\n", argv[0], prefix, pfound->name);
|
||||||
|
d->optopt = pfound->val;
|
||||||
|
return '?';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (pfound->has_arg == 1) {
|
||||||
|
if (d->optind < argc)
|
||||||
|
d->optarg = argv[d->optind++];
|
||||||
|
else {
|
||||||
|
if (print_errors)
|
||||||
|
fprintf(stderr, "%s: option '%s%s' requires an argument\n", argv[0], prefix, pfound->name);
|
||||||
|
d->optopt = pfound->val;
|
||||||
|
return optstring[0] == ':' ? ':' : '?';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (longind != NULL)
|
||||||
|
*longind = option_index;
|
||||||
|
|
||||||
|
if (pfound->flag) {
|
||||||
|
*(pfound->flag) = pfound->val;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
return pfound->val;
|
||||||
|
}
|
||||||
|
|
||||||
|
static const char *_getopt_initialize_a(const char *optstring, struct _getopt_data_a *d, int posixly_correct) {
|
||||||
|
if (d->optind == 0)
|
||||||
|
d->optind = 1;
|
||||||
|
|
||||||
|
d->__first_nonopt = d->__last_nonopt = d->optind;
|
||||||
|
d->__nextchar = NULL;
|
||||||
|
|
||||||
|
if (optstring[0] == '-') {
|
||||||
|
d->__ordering = RETURN_IN_ORDER;
|
||||||
|
++optstring;
|
||||||
|
}
|
||||||
|
else if (optstring[0] == '+') {
|
||||||
|
d->__ordering = REQUIRE_ORDER;
|
||||||
|
++optstring;
|
||||||
|
}
|
||||||
|
else if (posixly_correct | !!getenv("POSIXLY_CORRECT"))
|
||||||
|
d->__ordering = REQUIRE_ORDER;
|
||||||
|
else
|
||||||
|
d->__ordering = PERMUTE;
|
||||||
|
|
||||||
|
d->__initialized = 1;
|
||||||
|
return optstring;
|
||||||
|
}
|
||||||
|
|
||||||
|
int _getopt_internal_r_a(int argc, char *const *argv, const char *optstring, const struct option_a *longopts, int *longind, int long_only, struct _getopt_data_a *d, int posixly_correct);
|
||||||
|
int _getopt_internal_r_a(int argc, char *const *argv, const char *optstring, const struct option_a *longopts, int *longind, int long_only, struct _getopt_data_a *d, int posixly_correct) {
|
||||||
|
int print_errors = d->opterr;
|
||||||
|
if (argc < 1)
|
||||||
|
return -1;
|
||||||
|
d->optarg = NULL;
|
||||||
|
if (d->optind == 0 || !d->__initialized)
|
||||||
|
optstring = _getopt_initialize_a(optstring, d, posixly_correct);
|
||||||
|
else if (optstring[0] == '-' || optstring[0] == '+')
|
||||||
|
optstring++;
|
||||||
|
if (optstring[0] == ':')
|
||||||
|
print_errors = 0;
|
||||||
|
|
||||||
|
if (d->__nextchar == NULL || *d->__nextchar == '\0') {
|
||||||
|
if (d->__last_nonopt > d->optind)
|
||||||
|
d->__last_nonopt = d->optind;
|
||||||
|
if (d->__first_nonopt > d->optind)
|
||||||
|
d->__first_nonopt = d->optind;
|
||||||
|
if (d->__ordering == PERMUTE) {
|
||||||
|
if (d->__first_nonopt != d->__last_nonopt && d->__last_nonopt != d->optind)
|
||||||
|
exchange_a(const_cast<char **>(argv), d);
|
||||||
|
else if (d->__last_nonopt != d->optind)
|
||||||
|
d->__first_nonopt = d->optind;
|
||||||
|
while (d->optind < argc && (argv[d->optind][0] != '-' || argv[d->optind][1] == '\0'))
|
||||||
|
d->optind++;
|
||||||
|
d->__last_nonopt = d->optind;
|
||||||
|
}
|
||||||
|
if (d->optind != argc && !strcmp(argv[d->optind], "--")) {
|
||||||
|
d->optind++;
|
||||||
|
if (d->__first_nonopt != d->__last_nonopt && d->__last_nonopt != d->optind)
|
||||||
|
exchange_a(const_cast<char **>(argv), d);
|
||||||
|
else if (d->__first_nonopt == d->__last_nonopt)
|
||||||
|
d->__first_nonopt = d->optind;
|
||||||
|
d->__last_nonopt = argc;
|
||||||
|
d->optind = argc;
|
||||||
|
}
|
||||||
|
if (d->optind == argc) {
|
||||||
|
if (d->__first_nonopt != d->__last_nonopt)
|
||||||
|
d->optind = d->__first_nonopt;
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
if (argv[d->optind][0] != '-' || argv[d->optind][1] == '\0') {
|
||||||
|
if (d->__ordering == REQUIRE_ORDER)
|
||||||
|
return -1;
|
||||||
|
d->optarg = argv[d->optind++];
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
if (longopts) {
|
||||||
|
if (argv[d->optind][1] == '-') {
|
||||||
|
d->__nextchar = argv[d->optind] + 2;
|
||||||
|
return process_long_option_a(argc, const_cast<char **>(argv), optstring, longopts, longind, long_only, d, print_errors, "--");
|
||||||
|
}
|
||||||
|
if (long_only && (argv[d->optind][2] || !strchr(optstring, argv[d->optind][1]))) {
|
||||||
|
int code;
|
||||||
|
d->__nextchar = argv[d->optind] + 1;
|
||||||
|
code = process_long_option_a(argc, const_cast<char **>(argv), optstring, longopts,
|
||||||
|
longind, long_only, d,
|
||||||
|
print_errors, "-");
|
||||||
|
if (code != -1)
|
||||||
|
return code;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
d->__nextchar = argv[d->optind] + 1;
|
||||||
|
}
|
||||||
|
{
|
||||||
|
char c = *d->__nextchar++;
|
||||||
|
const char *temp = strchr(optstring, c);
|
||||||
|
if (*d->__nextchar == '\0')
|
||||||
|
++d->optind;
|
||||||
|
if (temp == NULL || c == ':' || c == ';') {
|
||||||
|
if (print_errors)
|
||||||
|
fprintf(stderr, "%s: invalid option -- '%c'\n", argv[0], c);
|
||||||
|
d->optopt = c;
|
||||||
|
return '?';
|
||||||
|
}
|
||||||
|
if (temp[0] == 'W' && temp[1] == ';' && longopts != NULL) {
|
||||||
|
if (*d->__nextchar != '\0')
|
||||||
|
d->optarg = d->__nextchar;
|
||||||
|
else if (d->optind == argc) {
|
||||||
|
if (print_errors)
|
||||||
|
fprintf(stderr, "%s: option requires an argument -- '%c'\n", argv[0], c);
|
||||||
|
d->optopt = c;
|
||||||
|
if (optstring[0] == ':')
|
||||||
|
c = ':';
|
||||||
|
else
|
||||||
|
c = '?';
|
||||||
|
return c;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
d->optarg = argv[d->optind];
|
||||||
|
d->__nextchar = d->optarg;
|
||||||
|
d->optarg = NULL;
|
||||||
|
return process_long_option_a(argc, const_cast<char **>(argv), optstring, longopts, longind, 0, d, print_errors, "-W ");
|
||||||
|
}
|
||||||
|
if (temp[1] == ':') {
|
||||||
|
if (temp[2] == ':') {
|
||||||
|
if (*d->__nextchar != '\0') {
|
||||||
|
d->optarg = d->__nextchar;
|
||||||
|
d->optind++;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
d->optarg = NULL;
|
||||||
|
d->__nextchar = NULL;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
if (*d->__nextchar != '\0') {
|
||||||
|
d->optarg = d->__nextchar;
|
||||||
|
d->optind++;
|
||||||
|
}
|
||||||
|
else if (d->optind == argc) {
|
||||||
|
if (print_errors)
|
||||||
|
fprintf(stderr, "%s: option requires an argument -- '%c'\n", argv[0], c);
|
||||||
|
d->optopt = c;
|
||||||
|
if (optstring[0] == ':')
|
||||||
|
c = ':';
|
||||||
|
else
|
||||||
|
c = '?';
|
||||||
|
}
|
||||||
|
else
|
||||||
|
d->optarg = argv[d->optind++];
|
||||||
|
d->__nextchar = NULL;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return c;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
int _getopt_internal_a(int argc, char *const *argv, const char *optstring, const struct option_a *longopts, int *longind, int long_only, int posixly_correct);
|
||||||
|
int _getopt_internal_a(int argc, char *const *argv, const char *optstring, const struct option_a *longopts, int *longind, int long_only, int posixly_correct) {
|
||||||
|
int result;
|
||||||
|
getopt_data_a.optind = optind;
|
||||||
|
getopt_data_a.opterr = opterr;
|
||||||
|
result = _getopt_internal_r_a(argc, argv, optstring, longopts, longind, long_only, &getopt_data_a, posixly_correct);
|
||||||
|
optind = getopt_data_a.optind;
|
||||||
|
optarg_a = getopt_data_a.optarg;
|
||||||
|
optopt = getopt_data_a.optopt;
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
int getopt_a(int argc, char *const *argv, const char *optstring) _GETOPT_THROW {
|
||||||
|
return _getopt_internal_a(argc, argv, optstring, static_cast<const struct option_a *>(0), static_cast<int *>(0), 0, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
int getopt_long_a(int argc, char *const *argv, const char *options, const struct option_a *long_options, int *opt_index) _GETOPT_THROW {
|
||||||
|
return _getopt_internal_a(argc, argv, options, long_options, opt_index, 0, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
int getopt_long_only_a(int argc, char *const *argv, const char *options, const struct option_a *long_options, int *opt_index) _GETOPT_THROW {
|
||||||
|
return _getopt_internal_a(argc, argv, options, long_options, opt_index, 1, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
int _getopt_long_r_a(int argc, char *const *argv, const char *options, const struct option_a *long_options, int *opt_index, struct _getopt_data_a *d);
|
||||||
|
int _getopt_long_r_a(int argc, char *const *argv, const char *options, const struct option_a *long_options, int *opt_index, struct _getopt_data_a *d) {
|
||||||
|
return _getopt_internal_r_a(argc, argv, options, long_options, opt_index, 0, d, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
int _getopt_long_only_r_a(int argc, char *const *argv, const char *options, const struct option_a *long_options, int *opt_index, struct _getopt_data_a *d);
|
||||||
|
int _getopt_long_only_r_a(int argc, char *const *argv, const char *options, const struct option_a *long_options, int *opt_index, struct _getopt_data_a *d) {
|
||||||
|
return _getopt_internal_r_a(argc, argv, options, long_options, opt_index, 1, d, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
//
|
||||||
|
//
|
||||||
|
// Unicode Structures and Functions
|
||||||
|
//
|
||||||
|
//
|
||||||
|
|
||||||
|
static struct _getopt_data_w {
|
||||||
|
int optind;
|
||||||
|
int opterr;
|
||||||
|
int optopt;
|
||||||
|
wchar_t *optarg;
|
||||||
|
int __initialized;
|
||||||
|
wchar_t *__nextchar;
|
||||||
|
enum ENUM_ORDERING __ordering;
|
||||||
|
int __first_nonopt;
|
||||||
|
int __last_nonopt;
|
||||||
|
} getopt_data_w;
|
||||||
|
wchar_t *optarg_w;
|
||||||
|
|
||||||
|
static void exchange_w(wchar_t **argv, struct _getopt_data_w *d) {
|
||||||
|
int bottom = d->__first_nonopt;
|
||||||
|
int middle = d->__last_nonopt;
|
||||||
|
int top = d->optind;
|
||||||
|
wchar_t *tem;
|
||||||
|
while (top > middle && middle > bottom) {
|
||||||
|
if (top - middle > middle - bottom) {
|
||||||
|
int len = middle - bottom;
|
||||||
|
int i;
|
||||||
|
for (i = 0; i < len; i++) {
|
||||||
|
tem = argv[bottom + i];
|
||||||
|
argv[bottom + i] = argv[top - (middle - bottom) + i];
|
||||||
|
argv[top - (middle - bottom) + i] = tem;
|
||||||
|
}
|
||||||
|
top -= len;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
int len = top - middle;
|
||||||
|
int i;
|
||||||
|
for (i = 0; i < len; i++) {
|
||||||
|
tem = argv[bottom + i];
|
||||||
|
argv[bottom + i] = argv[middle + i];
|
||||||
|
argv[middle + i] = tem;
|
||||||
|
}
|
||||||
|
bottom += len;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
d->__first_nonopt += (d->optind - d->__last_nonopt);
|
||||||
|
d->__last_nonopt = d->optind;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int process_long_option_w(int argc, wchar_t **argv, const wchar_t *optstring, const struct option_w *longopts, int *longind, int long_only, struct _getopt_data_w *d, int print_errors, const wchar_t *prefix) {
|
||||||
|
assert(longopts != NULL);
|
||||||
|
wchar_t *nameend;
|
||||||
|
size_t namelen;
|
||||||
|
const struct option_w *p;
|
||||||
|
const struct option_w *pfound = NULL;
|
||||||
|
int n_options;
|
||||||
|
int option_index = 0;
|
||||||
|
for (nameend = d->__nextchar; *nameend && *nameend != L'='; nameend++)
|
||||||
|
;
|
||||||
|
namelen = nameend - d->__nextchar;
|
||||||
|
for (p = longopts, n_options = 0; p->name; p++, n_options++)
|
||||||
|
if (!wcsncmp(p->name, d->__nextchar, namelen) && namelen == wcslen(p->name)) {
|
||||||
|
pfound = p;
|
||||||
|
option_index = n_options;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if (pfound == NULL) {
|
||||||
|
wchar_t *ambig_set = NULL;
|
||||||
|
int ambig_fallback = 0;
|
||||||
|
int indfound = -1;
|
||||||
|
for (p = longopts, option_index = 0; p->name; p++, option_index++)
|
||||||
|
if (!wcsncmp(p->name, d->__nextchar, namelen)) {
|
||||||
|
if (pfound == NULL) {
|
||||||
|
pfound = p;
|
||||||
|
indfound = option_index;
|
||||||
|
}
|
||||||
|
else if (long_only || pfound->has_arg != p->has_arg || pfound->flag != p->flag || pfound->val != p->val) {
|
||||||
|
if (!ambig_fallback) {
|
||||||
|
if (!print_errors)
|
||||||
|
ambig_fallback = 1;
|
||||||
|
|
||||||
|
else if (!ambig_set) {
|
||||||
|
if ((ambig_set = reinterpret_cast<wchar_t *>(malloc(n_options * sizeof(wchar_t)))) == NULL)
|
||||||
|
ambig_fallback = 1;
|
||||||
|
|
||||||
|
if (ambig_set) {
|
||||||
|
memset(ambig_set, 0, n_options * sizeof(wchar_t));
|
||||||
|
ambig_set[indfound] = 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (ambig_set)
|
||||||
|
ambig_set[option_index] = 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (ambig_set || ambig_fallback) {
|
||||||
|
if (print_errors) {
|
||||||
|
if (ambig_fallback)
|
||||||
|
fwprintf(stderr, L"%s: option '%s%s' is ambiguous\n", argv[0], prefix, d->__nextchar);
|
||||||
|
else {
|
||||||
|
_lock_file(stderr);
|
||||||
|
fwprintf(stderr, L"%s: option '%s%s' is ambiguous; possibilities:", argv[0], prefix, d->__nextchar);
|
||||||
|
for (option_index = 0; option_index < n_options; option_index++)
|
||||||
|
if (ambig_set[option_index])
|
||||||
|
fwprintf(stderr, L" '%s%s'", prefix, longopts[option_index].name);
|
||||||
|
fwprintf(stderr, L"\n");
|
||||||
|
_unlock_file(stderr);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
free(ambig_set);
|
||||||
|
d->__nextchar += wcslen(d->__nextchar);
|
||||||
|
d->optind++;
|
||||||
|
d->optopt = 0;
|
||||||
|
return L'?';
|
||||||
|
}
|
||||||
|
option_index = indfound;
|
||||||
|
}
|
||||||
|
if (pfound == NULL) {
|
||||||
|
if (!long_only || argv[d->optind][1] == L'-' || wcschr(optstring, *d->__nextchar) == NULL) {
|
||||||
|
if (print_errors)
|
||||||
|
fwprintf(stderr, L"%s: unrecognized option '%s%s'\n", argv[0], prefix, d->__nextchar);
|
||||||
|
d->__nextchar = NULL;
|
||||||
|
d->optind++;
|
||||||
|
d->optopt = 0;
|
||||||
|
return L'?';
|
||||||
|
}
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
d->optind++;
|
||||||
|
d->__nextchar = NULL;
|
||||||
|
if (*nameend) {
|
||||||
|
if (pfound->has_arg)
|
||||||
|
d->optarg = nameend + 1;
|
||||||
|
else {
|
||||||
|
if (print_errors)
|
||||||
|
fwprintf(stderr, L"%s: option '%s%s' doesn't allow an argument\n", argv[0], prefix, pfound->name);
|
||||||
|
d->optopt = pfound->val;
|
||||||
|
return L'?';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (pfound->has_arg == 1) {
|
||||||
|
if (d->optind < argc)
|
||||||
|
d->optarg = argv[d->optind++];
|
||||||
|
else {
|
||||||
|
if (print_errors)
|
||||||
|
fwprintf(stderr, L"%s: option '%s%s' requires an argument\n", argv[0], prefix, pfound->name);
|
||||||
|
d->optopt = pfound->val;
|
||||||
|
return optstring[0] == L':' ? L':' : L'?';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (longind != NULL)
|
||||||
|
*longind = option_index;
|
||||||
|
if (pfound->flag) {
|
||||||
|
*(pfound->flag) = pfound->val;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
return pfound->val;
|
||||||
|
}
|
||||||
|
|
||||||
|
static const wchar_t *_getopt_initialize_w(const wchar_t *optstring, struct _getopt_data_w *d, int posixly_correct) {
|
||||||
|
if (d->optind == 0)
|
||||||
|
d->optind = 1;
|
||||||
|
|
||||||
|
d->__first_nonopt = d->__last_nonopt = d->optind;
|
||||||
|
d->__nextchar = NULL;
|
||||||
|
|
||||||
|
if (optstring[0] == L'-') {
|
||||||
|
d->__ordering = RETURN_IN_ORDER;
|
||||||
|
++optstring;
|
||||||
|
}
|
||||||
|
else if (optstring[0] == L'+') {
|
||||||
|
d->__ordering = REQUIRE_ORDER;
|
||||||
|
++optstring;
|
||||||
|
}
|
||||||
|
else if (posixly_correct | !!_wgetenv(L"POSIXLY_CORRECT"))
|
||||||
|
d->__ordering = REQUIRE_ORDER;
|
||||||
|
else
|
||||||
|
d->__ordering = PERMUTE;
|
||||||
|
|
||||||
|
d->__initialized = 1;
|
||||||
|
return optstring;
|
||||||
|
}
|
||||||
|
|
||||||
|
int _getopt_internal_r_w(int argc, wchar_t *const *argv, const wchar_t *optstring, const struct option_w *longopts, int *longind, int long_only, struct _getopt_data_w *d, int posixly_correct);
|
||||||
|
int _getopt_internal_r_w(int argc, wchar_t *const *argv, const wchar_t *optstring, const struct option_w *longopts, int *longind, int long_only, struct _getopt_data_w *d, int posixly_correct) {
|
||||||
|
int print_errors = d->opterr;
|
||||||
|
if (argc < 1)
|
||||||
|
return -1;
|
||||||
|
d->optarg = NULL;
|
||||||
|
if (d->optind == 0 || !d->__initialized)
|
||||||
|
optstring = _getopt_initialize_w(optstring, d, posixly_correct);
|
||||||
|
else if (optstring[0] == L'-' || optstring[0] == L'+')
|
||||||
|
optstring++;
|
||||||
|
if (optstring[0] == L':')
|
||||||
|
print_errors = 0;
|
||||||
|
#define NONOPTION_P (argv[d->optind][0] != L'-' || argv[d->optind][1] == L'\0')
|
||||||
|
|
||||||
|
if (d->__nextchar == NULL || *d->__nextchar == L'\0') {
|
||||||
|
if (d->__last_nonopt > d->optind)
|
||||||
|
d->__last_nonopt = d->optind;
|
||||||
|
if (d->__first_nonopt > d->optind)
|
||||||
|
d->__first_nonopt = d->optind;
|
||||||
|
if (d->__ordering == PERMUTE) {
|
||||||
|
if (d->__first_nonopt != d->__last_nonopt && d->__last_nonopt != d->optind)
|
||||||
|
exchange_w(const_cast<wchar_t **>(argv), d);
|
||||||
|
else if (d->__last_nonopt != d->optind)
|
||||||
|
d->__first_nonopt = d->optind;
|
||||||
|
while (d->optind < argc && NONOPTION_P)
|
||||||
|
d->optind++;
|
||||||
|
d->__last_nonopt = d->optind;
|
||||||
|
}
|
||||||
|
if (d->optind != argc && !wcscmp(argv[d->optind], L"--")) {
|
||||||
|
d->optind++;
|
||||||
|
if (d->__first_nonopt != d->__last_nonopt && d->__last_nonopt != d->optind)
|
||||||
|
exchange_w(const_cast<wchar_t **>(argv), d);
|
||||||
|
else if (d->__first_nonopt == d->__last_nonopt)
|
||||||
|
d->__first_nonopt = d->optind;
|
||||||
|
d->__last_nonopt = argc;
|
||||||
|
d->optind = argc;
|
||||||
|
}
|
||||||
|
if (d->optind == argc) {
|
||||||
|
if (d->__first_nonopt != d->__last_nonopt)
|
||||||
|
d->optind = d->__first_nonopt;
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
if (NONOPTION_P) {
|
||||||
|
if (d->__ordering == REQUIRE_ORDER)
|
||||||
|
return -1;
|
||||||
|
d->optarg = argv[d->optind++];
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
if (longopts) {
|
||||||
|
if (argv[d->optind][1] == L'-') {
|
||||||
|
d->__nextchar = argv[d->optind] + 2;
|
||||||
|
return process_long_option_w(argc, const_cast<wchar_t **>(argv), optstring, longopts, longind, long_only, d, print_errors, L"--");
|
||||||
|
}
|
||||||
|
if (long_only && (argv[d->optind][2] || !wcschr(optstring, argv[d->optind][1]))) {
|
||||||
|
int code;
|
||||||
|
d->__nextchar = argv[d->optind] + 1;
|
||||||
|
code = process_long_option_w(argc, const_cast<wchar_t **>(argv), optstring, longopts, longind, long_only, d, print_errors, L"-");
|
||||||
|
if (code != -1)
|
||||||
|
return code;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
d->__nextchar = argv[d->optind] + 1;
|
||||||
|
}
|
||||||
|
{
|
||||||
|
wchar_t c = *d->__nextchar++;
|
||||||
|
const wchar_t *temp = wcschr(optstring, c);
|
||||||
|
if (*d->__nextchar == L'\0')
|
||||||
|
++d->optind;
|
||||||
|
if (temp == NULL || c == L':' || c == L';') {
|
||||||
|
if (print_errors)
|
||||||
|
fwprintf(stderr, L"%s: invalid option -- '%c'\n", argv[0], c);
|
||||||
|
d->optopt = c;
|
||||||
|
return L'?';
|
||||||
|
}
|
||||||
|
if (temp[0] == L'W' && temp[1] == L';' && longopts != NULL) {
|
||||||
|
if (*d->__nextchar != L'\0')
|
||||||
|
d->optarg = d->__nextchar;
|
||||||
|
else if (d->optind == argc) {
|
||||||
|
if (print_errors)
|
||||||
|
fwprintf(stderr, L"%s: option requires an argument -- '%c'\n", argv[0], c);
|
||||||
|
d->optopt = c;
|
||||||
|
if (optstring[0] == L':')
|
||||||
|
c = L':';
|
||||||
|
else
|
||||||
|
c = L'?';
|
||||||
|
return c;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
d->optarg = argv[d->optind];
|
||||||
|
d->__nextchar = d->optarg;
|
||||||
|
d->optarg = NULL;
|
||||||
|
return process_long_option_w(argc, const_cast<wchar_t **>(argv), optstring, longopts, longind,
|
||||||
|
0, d, print_errors, L"-W ");
|
||||||
|
}
|
||||||
|
if (temp[1] == L':') {
|
||||||
|
if (temp[2] == L':') {
|
||||||
|
if (*d->__nextchar != L'\0') {
|
||||||
|
d->optarg = d->__nextchar;
|
||||||
|
d->optind++;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
d->optarg = NULL;
|
||||||
|
d->__nextchar = NULL;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
if (*d->__nextchar != L'\0') {
|
||||||
|
d->optarg = d->__nextchar;
|
||||||
|
d->optind++;
|
||||||
|
}
|
||||||
|
else if (d->optind == argc) {
|
||||||
|
if (print_errors)
|
||||||
|
fwprintf(stderr, L"%s: option requires an argument -- '%c'\n", argv[0], c);
|
||||||
|
d->optopt = c;
|
||||||
|
if (optstring[0] == L':')
|
||||||
|
c = L':';
|
||||||
|
else
|
||||||
|
c = L'?';
|
||||||
|
}
|
||||||
|
else
|
||||||
|
d->optarg = argv[d->optind++];
|
||||||
|
d->__nextchar = NULL;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return c;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
int _getopt_internal_w(int argc, wchar_t *const *argv, const wchar_t *optstring, const struct option_w *longopts, int *longind, int long_only, int posixly_correct);
|
||||||
|
int _getopt_internal_w(int argc, wchar_t *const *argv, const wchar_t *optstring, const struct option_w *longopts, int *longind, int long_only, int posixly_correct) {
|
||||||
|
int result;
|
||||||
|
getopt_data_w.optind = optind;
|
||||||
|
getopt_data_w.opterr = opterr;
|
||||||
|
result = _getopt_internal_r_w(argc, argv, optstring, longopts, longind, long_only, &getopt_data_w, posixly_correct);
|
||||||
|
optind = getopt_data_w.optind;
|
||||||
|
optarg_w = getopt_data_w.optarg;
|
||||||
|
optopt = getopt_data_w.optopt;
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
int getopt_w(int argc, wchar_t *const *argv, const wchar_t *optstring) _GETOPT_THROW {
|
||||||
|
return _getopt_internal_w(argc, argv, optstring, static_cast<const struct option_w *>(0), static_cast<int *>(0), 0, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
int getopt_long_w(int argc, wchar_t *const *argv, const wchar_t *options, const struct option_w *long_options, int *opt_index) _GETOPT_THROW {
|
||||||
|
return _getopt_internal_w(argc, argv, options, long_options, opt_index, 0, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
int getopt_long_only_w(int argc, wchar_t *const *argv, const wchar_t *options, const struct option_w *long_options, int *opt_index) _GETOPT_THROW {
|
||||||
|
return _getopt_internal_w(argc, argv, options, long_options, opt_index, 1, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
int _getopt_long_r_w(int argc, wchar_t *const *argv, const wchar_t *options, const struct option_w *long_options, int *opt_index, struct _getopt_data_w *d);
|
||||||
|
int _getopt_long_r_w(int argc, wchar_t *const *argv, const wchar_t *options, const struct option_w *long_options, int *opt_index, struct _getopt_data_w *d) {
|
||||||
|
return _getopt_internal_r_w(argc, argv, options, long_options, opt_index, 0, d, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
int _getopt_long_only_r_w(int argc, wchar_t *const *argv, const wchar_t *options, const struct option_w *long_options, int *opt_index, struct _getopt_data_w *d);
|
||||||
|
int _getopt_long_only_r_w(int argc, wchar_t *const *argv, const wchar_t *options, const struct option_w *long_options, int *opt_index, struct _getopt_data_w *d) {
|
||||||
|
return _getopt_internal_r_w(argc, argv, options, long_options, opt_index, 1, d, 0);
|
||||||
|
}
|
||||||
230
3rdparty/getopt/getopt.h
vendored
230
3rdparty/getopt/getopt.h
vendored
@@ -1,95 +1,135 @@
|
|||||||
#ifndef __GETOPT_H__
|
/* Getopt for Microsoft C
|
||||||
/**
|
This code is a modification of the Free Software Foundation, Inc.
|
||||||
* DISCLAIMER
|
Getopt library for parsing command line argument the purpose was
|
||||||
* This file has no copyright assigned and is placed in the Public Domain.
|
to provide a Microsoft Visual C friendly derivative. This code
|
||||||
* This file is part of the mingw-w64 runtime package.
|
provides functionality for both Unicode and Multibyte builds.
|
||||||
*
|
|
||||||
* The mingw-w64 runtime package and its code is distributed in the hope that it
|
Date: 02/03/2011 - Ludvik Jerabek - Initial Release
|
||||||
* will be useful but WITHOUT ANY WARRANTY. ALL WARRANTIES, EXPRESSED OR
|
Version: 1.1
|
||||||
* IMPLIED ARE HEREBY DISCLAIMED. This includes but is not limited to
|
Comment: Supports getopt, getopt_long, and getopt_long_only
|
||||||
* warranties of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
and POSIXLY_CORRECT environment flag
|
||||||
*/
|
License: LGPL
|
||||||
|
|
||||||
#define __GETOPT_H__
|
Revisions:
|
||||||
|
|
||||||
/* All the headers include this file. */
|
02/03/2011 - Ludvik Jerabek - Initial Release
|
||||||
#include <crtdefs.h>
|
02/20/2011 - Ludvik Jerabek - Fixed compiler warnings at Level 4
|
||||||
|
07/05/2011 - Ludvik Jerabek - Added no_argument, required_argument, optional_argument defs
|
||||||
#ifdef __cplusplus
|
08/03/2011 - Ludvik Jerabek - Fixed non-argument runtime bug which caused runtime exception
|
||||||
extern "C" {
|
08/09/2011 - Ludvik Jerabek - Added code to export functions for DLL and LIB
|
||||||
#endif
|
02/15/2012 - Ludvik Jerabek - Fixed _GETOPT_THROW definition missing in implementation file
|
||||||
|
08/01/2012 - Ludvik Jerabek - Created separate functions for char and wchar_t characters so single dll can do both unicode and ansi
|
||||||
extern int optind; /* index of first non-option in argv */
|
10/15/2012 - Ludvik Jerabek - Modified to match latest GNU features
|
||||||
extern int optopt; /* single option character, as parsed */
|
06/19/2015 - Ludvik Jerabek - Fixed maximum option limitation caused by option_a (255) and option_w (65535) structure val variable
|
||||||
extern int opterr; /* flag to enable built-in diagnostics... */
|
09/24/2022 - Ludvik Jerabek - Updated to match most recent getopt release
|
||||||
/* (user may set to zero, to suppress) */
|
09/25/2022 - Ludvik Jerabek - Fixed memory allocation (malloc call) issue for wchar_t*
|
||||||
|
|
||||||
extern char *optarg; /* pointer to argument of current option */
|
**DISCLAIMER**
|
||||||
|
THIS MATERIAL IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY KIND,
|
||||||
extern int getopt(int nargc, char * const *nargv, const char *options);
|
EITHER EXPRESS OR IMPLIED, INCLUDING, BUT Not LIMITED TO, THE
|
||||||
|
IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
|
||||||
#ifdef _BSD_SOURCE
|
PURPOSE, OR NON-INFRINGEMENT. SOME JURISDICTIONS DO NOT ALLOW THE
|
||||||
/*
|
EXCLUSION OF IMPLIED WARRANTIES, SO THE ABOVE EXCLUSION MAY NOT
|
||||||
* BSD adds the non-standard `optreset' feature, for reinitialisation
|
APPLY TO YOU. IN NO EVENT WILL I BE LIABLE TO ANY PARTY FOR ANY
|
||||||
* of `getopt' parsing. We support this feature, for applications which
|
DIRECT, INDIRECT, SPECIAL OR OTHER CONSEQUENTIAL DAMAGES FOR ANY
|
||||||
* proclaim their BSD heritage, before including this header; however,
|
USE OF THIS MATERIAL INCLUDING, WITHOUT LIMITATION, ANY LOST
|
||||||
* to maintain portability, developers are advised to avoid it.
|
PROFITS, BUSINESS INTERRUPTION, LOSS OF PROGRAMS OR OTHER DATA ON
|
||||||
*/
|
YOUR INFORMATION HANDLING SYSTEM OR OTHERWISE, EVEN If WE ARE
|
||||||
# define optreset __mingw_optreset
|
EXPRESSLY ADVISED OF THE POSSIBILITY OF SUCH DAMAGES.
|
||||||
extern int optreset;
|
*/
|
||||||
#endif
|
#ifndef __GETOPT_H_
|
||||||
#ifdef __cplusplus
|
#define __GETOPT_H_
|
||||||
}
|
|
||||||
#endif
|
#ifdef _GETOPT_API
|
||||||
/*
|
# undef _GETOPT_API
|
||||||
* POSIX requires the `getopt' API to be specified in `unistd.h';
|
#endif
|
||||||
* thus, `unistd.h' includes this header. However, we do not want
|
|
||||||
* to expose the `getopt_long' or `getopt_long_only' APIs, when
|
#if defined(EXPORTS_GETOPT) && defined(STATIC_GETOPT)
|
||||||
* included in this manner. Thus, close the standard __GETOPT_H__
|
# error "The preprocessor definitions of EXPORTS_GETOPT and STATIC_GETOPT can only be used individually"
|
||||||
* declarations block, and open an additional __GETOPT_LONG_H__
|
#elif defined(STATIC_GETOPT)
|
||||||
* specific block, only when *not* __UNISTD_H_SOURCED__, in which
|
# define _GETOPT_API
|
||||||
* to declare the extended API.
|
#elif defined(EXPORTS_GETOPT)
|
||||||
*/
|
# pragma message("Exporting getopt library")
|
||||||
#endif /* !defined(__GETOPT_H__) */
|
# define _GETOPT_API __declspec(dllexport)
|
||||||
|
#else
|
||||||
#if !defined(__UNISTD_H_SOURCED__) && !defined(__GETOPT_LONG_H__)
|
# pragma message("Importing getopt library")
|
||||||
#define __GETOPT_LONG_H__
|
# define _GETOPT_API __declspec(dllimport)
|
||||||
|
#endif
|
||||||
#ifdef __cplusplus
|
|
||||||
extern "C" {
|
// Change behavior for C\C++
|
||||||
#endif
|
#ifdef __cplusplus
|
||||||
|
# define _BEGIN_EXTERN_C extern "C" {
|
||||||
struct option /* specification for a long form option... */
|
# define _END_EXTERN_C }
|
||||||
{
|
# define _GETOPT_THROW throw()
|
||||||
const char *name; /* option name, without leading hyphens */
|
#else
|
||||||
int has_arg; /* does it take an argument? */
|
# define _BEGIN_EXTERN_C
|
||||||
int *flag; /* where to save its status, or NULL */
|
# define _END_EXTERN_C
|
||||||
int val; /* its associated status value */
|
# define _GETOPT_THROW
|
||||||
};
|
#endif
|
||||||
|
|
||||||
enum /* permitted values for its `has_arg' field... */
|
// Standard GNU options
|
||||||
{
|
#define null_argument 0 /*Argument Null*/
|
||||||
no_argument = 0, /* option never takes an argument */
|
#define no_argument 0 /*Argument Switch Only*/
|
||||||
required_argument, /* option always requires an argument */
|
#define required_argument 1 /*Argument Required*/
|
||||||
optional_argument /* option may take an argument */
|
#define optional_argument 2 /*Argument Optional*/
|
||||||
};
|
|
||||||
|
// Shorter Options
|
||||||
extern int getopt_long(int nargc, char * const *nargv, const char *options,
|
#define ARG_NULL 0 /*Argument Null*/
|
||||||
const struct option *long_options, int *idx);
|
#define ARG_NONE 0 /*Argument Switch Only*/
|
||||||
extern int getopt_long_only(int nargc, char * const *nargv, const char *options,
|
#define ARG_REQ 1 /*Argument Required*/
|
||||||
const struct option *long_options, int *idx);
|
#define ARG_OPT 2 /*Argument Optional*/
|
||||||
/*
|
|
||||||
* Previous MinGW implementation had...
|
#include <string.h>
|
||||||
*/
|
#include <wchar.h>
|
||||||
#ifndef HAVE_DECL_GETOPT
|
|
||||||
/*
|
_BEGIN_EXTERN_C
|
||||||
* ...for the long form API only; keep this for compatibility.
|
|
||||||
*/
|
extern _GETOPT_API int optind;
|
||||||
# define HAVE_DECL_GETOPT 1
|
extern _GETOPT_API int opterr;
|
||||||
#endif
|
extern _GETOPT_API int optopt;
|
||||||
|
|
||||||
#ifdef __cplusplus
|
// Ansi
|
||||||
}
|
struct option_a {
|
||||||
#endif
|
const char *name;
|
||||||
|
int has_arg;
|
||||||
#endif /* !defined(__UNISTD_H_SOURCED__) && !defined(__GETOPT_LONG_H__) */
|
int *flag;
|
||||||
|
int val;
|
||||||
|
};
|
||||||
|
extern _GETOPT_API char *optarg_a;
|
||||||
|
extern _GETOPT_API int getopt_a(int argc, char *const *argv, const char *optstring) _GETOPT_THROW;
|
||||||
|
extern _GETOPT_API int getopt_long_a(int argc, char *const *argv, const char *options, const struct option_a *long_options, int *opt_index) _GETOPT_THROW;
|
||||||
|
extern _GETOPT_API int getopt_long_only_a(int argc, char *const *argv, const char *options, const struct option_a *long_options, int *opt_index) _GETOPT_THROW;
|
||||||
|
|
||||||
|
// Unicode
|
||||||
|
struct option_w {
|
||||||
|
const wchar_t *name;
|
||||||
|
int has_arg;
|
||||||
|
int *flag;
|
||||||
|
int val;
|
||||||
|
};
|
||||||
|
extern _GETOPT_API wchar_t *optarg_w;
|
||||||
|
extern _GETOPT_API int getopt_w(int argc, wchar_t *const *argv, const wchar_t *optstring) _GETOPT_THROW;
|
||||||
|
extern _GETOPT_API int getopt_long_w(int argc, wchar_t *const *argv, const wchar_t *options, const struct option_w *long_options, int *opt_index) _GETOPT_THROW;
|
||||||
|
extern _GETOPT_API int getopt_long_only_w(int argc, wchar_t *const *argv, const wchar_t *options, const struct option_w *long_options, int *opt_index) _GETOPT_THROW;
|
||||||
|
|
||||||
|
_END_EXTERN_C
|
||||||
|
|
||||||
|
#undef _BEGIN_EXTERN_C
|
||||||
|
#undef _END_EXTERN_C
|
||||||
|
#undef _GETOPT_THROW
|
||||||
|
#undef _GETOPT_API
|
||||||
|
|
||||||
|
#ifdef _UNICODE
|
||||||
|
# define getopt getopt_w
|
||||||
|
# define getopt_long getopt_long_w
|
||||||
|
# define getopt_long_only getopt_long_only_w
|
||||||
|
# define option option_w
|
||||||
|
# define optarg optarg_w
|
||||||
|
#else
|
||||||
|
# define getopt getopt_a
|
||||||
|
# define getopt_long getopt_long_a
|
||||||
|
# define getopt_long_only getopt_long_only_a
|
||||||
|
# define option option_a
|
||||||
|
# define optarg optarg_a
|
||||||
|
#endif
|
||||||
|
#endif // __GETOPT_H_
|
||||||
|
|||||||
8
3rdparty/kdsingleapplication/CMakeLists.txt
vendored
Normal file
8
3rdparty/kdsingleapplication/CMakeLists.txt
vendored
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
cmake_minimum_required(VERSION 3.7)
|
||||||
|
set(SOURCES kdsingleapplication.cpp kdsingleapplication_localsocket.cpp)
|
||||||
|
set(HEADERS kdsingleapplication.h kdsingleapplication_localsocket_p.h)
|
||||||
|
qt_wrap_cpp(MOC ${HEADERS})
|
||||||
|
add_library(kdsingleapplication STATIC ${SOURCES} ${MOC})
|
||||||
|
target_compile_definitions(kdsingleapplication PRIVATE -DKDSINGLEAPPLICATION_STATIC_BUILD)
|
||||||
|
target_include_directories(kdsingleapplication PUBLIC ${CMAKE_CURRENT_SOURCE_DIR})
|
||||||
|
target_link_libraries(kdsingleapplication PUBLIC ${QtCore_LIBRARIES} ${QtNetwork_LIBRARIES} )
|
||||||
6
3rdparty/kdsingleapplication/LICENSE
vendored
Normal file
6
3rdparty/kdsingleapplication/LICENSE
vendored
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
KDSingleApplication is (C) 2019-2023, Klarälvdalens Datakonsult AB,
|
||||||
|
and is available under the terms of the MIT license.
|
||||||
|
|
||||||
|
See the full license text in the LICENSES folder.
|
||||||
|
|
||||||
|
Contact KDAB at <info@kdab.com> to inquire about commercial licensing.
|
||||||
53
3rdparty/kdsingleapplication/README.md
vendored
Normal file
53
3rdparty/kdsingleapplication/README.md
vendored
Normal file
@@ -0,0 +1,53 @@
|
|||||||
|
# KDSingleApplication
|
||||||
|
|
||||||
|
`KDSingleApplication` is a helper class for single-instance policy applications
|
||||||
|
written by [KDAB](https://www.kdab.com).
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
Currently the documentation is woefully lacking, but see the examples or tests
|
||||||
|
for inspiration. Basically it involves:
|
||||||
|
|
||||||
|
1. Create a `Q(Core|Gui)Application` object.
|
||||||
|
2. Create a `KDSingleApplication` object.
|
||||||
|
3. Check if the current instance is *primary* (or "master") or
|
||||||
|
*secondary* (or "slave") by calling `isPrimaryInstance`:
|
||||||
|
* the *primary* instance needs to listen from messages coming from the
|
||||||
|
secondary instances, by connecting a slot to the `messageReceived` signal;
|
||||||
|
* the *secondary* instances can send messages to the primary instance
|
||||||
|
by calling `sendMessage`.
|
||||||
|
|
||||||
|
## Licensing
|
||||||
|
|
||||||
|
KDSingleApplication is (C) 2019-2023, Klarälvdalens Datakonsult AB, and is available
|
||||||
|
under the terms of the [MIT license](LICENSES/MIT.txt).
|
||||||
|
|
||||||
|
Contact KDAB at <info@kdab.com> if you need different licensing options.
|
||||||
|
|
||||||
|
## Get Involved
|
||||||
|
|
||||||
|
KDAB will happily accept external contributions.
|
||||||
|
|
||||||
|
Please submit your contributions or issue reports from our GitHub space at
|
||||||
|
<https://github.com/KDAB/KDSingleApplication>.
|
||||||
|
|
||||||
|
## About KDAB
|
||||||
|
|
||||||
|
KDSingleApplication is supported and maintained by Klarälvdalens Datakonsult AB (KDAB).
|
||||||
|
|
||||||
|
The KDAB Group is the global No.1 software consultancy for Qt, C++ and
|
||||||
|
OpenGL applications across desktop, embedded and mobile platforms.
|
||||||
|
|
||||||
|
The KDAB Group provides consulting and mentoring for developing Qt applications
|
||||||
|
from scratch and in porting from all popular and legacy frameworks to Qt.
|
||||||
|
We continue to help develop parts of Qt and are one of the major contributors
|
||||||
|
to the Qt Project. We can give advanced or standard trainings anywhere
|
||||||
|
around the globe on Qt as well as C++, OpenGL, 3D and more.
|
||||||
|
|
||||||
|
Please visit <https://www.kdab.com> to meet the people who write code like this.
|
||||||
|
|
||||||
|
Stay up-to-date with KDAB product announcements:
|
||||||
|
|
||||||
|
* [KDAB Newsletter](https://news.kdab.com)
|
||||||
|
* [KDAB Blogs](https://www.kdab.com/category/blogs)
|
||||||
|
* [KDAB on Twitter](https://twitter.com/KDABQt)
|
||||||
106
3rdparty/kdsingleapplication/kdsingleapplication.cpp
vendored
Normal file
106
3rdparty/kdsingleapplication/kdsingleapplication.cpp
vendored
Normal file
@@ -0,0 +1,106 @@
|
|||||||
|
/*
|
||||||
|
This file is part of KDSingleApplication.
|
||||||
|
|
||||||
|
SPDX-FileCopyrightText: 2019-2023 Klarälvdalens Datakonsult AB, a KDAB Group company <info@kdab.com>
|
||||||
|
|
||||||
|
SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
|
Contact KDAB at <info@kdab.com> for commercial licensing options.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "kdsingleapplication.h"
|
||||||
|
|
||||||
|
#include <QtCore/QCoreApplication>
|
||||||
|
#include <QtCore/QFileInfo>
|
||||||
|
|
||||||
|
// TODO: make this pluggable.
|
||||||
|
#include "kdsingleapplication_localsocket_p.h"
|
||||||
|
|
||||||
|
// Avoiding dragging in Qt private APIs for now, so this does not inherit
|
||||||
|
// from QObjectPrivate.
|
||||||
|
class KDSingleApplicationPrivate
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
explicit KDSingleApplicationPrivate(const QString &name, KDSingleApplication *q);
|
||||||
|
|
||||||
|
QString name() const
|
||||||
|
{
|
||||||
|
return m_name;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool isPrimaryInstance() const
|
||||||
|
{
|
||||||
|
return m_impl.isPrimaryInstance();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool sendMessage(const QByteArray &message, int timeout)
|
||||||
|
{
|
||||||
|
return m_impl.sendMessage(message, timeout);
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
Q_DECLARE_PUBLIC(KDSingleApplication)
|
||||||
|
|
||||||
|
KDSingleApplication *q_ptr;
|
||||||
|
QString m_name;
|
||||||
|
|
||||||
|
KDSingleApplicationLocalSocket m_impl;
|
||||||
|
};
|
||||||
|
|
||||||
|
KDSingleApplicationPrivate::KDSingleApplicationPrivate(const QString &name, KDSingleApplication *q)
|
||||||
|
: q_ptr(q)
|
||||||
|
, m_name(name)
|
||||||
|
, m_impl(name)
|
||||||
|
{
|
||||||
|
if (Q_UNLIKELY(name.isEmpty()))
|
||||||
|
qFatal("KDSingleApplication requires a non-empty application name");
|
||||||
|
|
||||||
|
if (isPrimaryInstance()) {
|
||||||
|
QObject::connect(&m_impl, &KDSingleApplicationLocalSocket::messageReceived,
|
||||||
|
q, &KDSingleApplication::messageReceived);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static QString extractExecutableName(const QString &applicationFilePath)
|
||||||
|
{
|
||||||
|
return QFileInfo(applicationFilePath).fileName();
|
||||||
|
}
|
||||||
|
|
||||||
|
KDSingleApplication::KDSingleApplication(QObject *parent)
|
||||||
|
: KDSingleApplication(extractExecutableName(QCoreApplication::applicationFilePath()), parent)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
KDSingleApplication::KDSingleApplication(const QString &name, QObject *parent)
|
||||||
|
: QObject(parent)
|
||||||
|
, d_ptr(new KDSingleApplicationPrivate(name, this))
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
QString KDSingleApplication::name() const
|
||||||
|
{
|
||||||
|
Q_D(const KDSingleApplication);
|
||||||
|
return d->name();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool KDSingleApplication::isPrimaryInstance() const
|
||||||
|
{
|
||||||
|
Q_D(const KDSingleApplication);
|
||||||
|
return d->isPrimaryInstance();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool KDSingleApplication::sendMessage(const QByteArray &message)
|
||||||
|
{
|
||||||
|
return sendMessageWithTimeout(message, 5000);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool KDSingleApplication::sendMessageWithTimeout(const QByteArray &message, int timeout)
|
||||||
|
{
|
||||||
|
Q_ASSERT(!isPrimaryInstance());
|
||||||
|
|
||||||
|
Q_D(KDSingleApplication);
|
||||||
|
return d->sendMessage(message, timeout);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
KDSingleApplication::~KDSingleApplication() = default;
|
||||||
48
3rdparty/kdsingleapplication/kdsingleapplication.h
vendored
Normal file
48
3rdparty/kdsingleapplication/kdsingleapplication.h
vendored
Normal file
@@ -0,0 +1,48 @@
|
|||||||
|
/*
|
||||||
|
This file is part of KDSingleApplication.
|
||||||
|
|
||||||
|
SPDX-FileCopyrightText: 2019-2023 Klarälvdalens Datakonsult AB, a KDAB Group company <info@kdab.com>
|
||||||
|
|
||||||
|
SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
|
Contact KDAB at <info@kdab.com> for commercial licensing options.
|
||||||
|
*/
|
||||||
|
#ifndef KDSINGLEAPPLICATION_H
|
||||||
|
#define KDSINGLEAPPLICATION_H
|
||||||
|
|
||||||
|
#include <QtCore/QObject>
|
||||||
|
|
||||||
|
#include <memory>
|
||||||
|
|
||||||
|
#include "kdsingleapplication_lib.h"
|
||||||
|
|
||||||
|
class KDSingleApplicationPrivate;
|
||||||
|
|
||||||
|
class KDSINGLEAPPLICATION_EXPORT KDSingleApplication : public QObject
|
||||||
|
{
|
||||||
|
Q_OBJECT
|
||||||
|
Q_PROPERTY(QString name READ name CONSTANT)
|
||||||
|
Q_PROPERTY(bool isPrimaryInstance READ isPrimaryInstance CONSTANT)
|
||||||
|
|
||||||
|
public:
|
||||||
|
explicit KDSingleApplication(QObject *parent = nullptr);
|
||||||
|
explicit KDSingleApplication(const QString &name, QObject *parent = nullptr);
|
||||||
|
~KDSingleApplication();
|
||||||
|
|
||||||
|
QString name() const;
|
||||||
|
bool isPrimaryInstance() const;
|
||||||
|
|
||||||
|
public Q_SLOTS:
|
||||||
|
// avoid default arguments and overloads, as they don't mix with connections
|
||||||
|
bool sendMessage(const QByteArray &message);
|
||||||
|
bool sendMessageWithTimeout(const QByteArray &message, int timeout);
|
||||||
|
|
||||||
|
Q_SIGNALS:
|
||||||
|
void messageReceived(const QByteArray &message);
|
||||||
|
|
||||||
|
private:
|
||||||
|
Q_DECLARE_PRIVATE(KDSingleApplication)
|
||||||
|
std::unique_ptr<KDSingleApplicationPrivate> d_ptr;
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif // KDSINGLEAPPLICATION_H
|
||||||
23
3rdparty/kdsingleapplication/kdsingleapplication_lib.h
vendored
Normal file
23
3rdparty/kdsingleapplication/kdsingleapplication_lib.h
vendored
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
/*
|
||||||
|
This file is part of KDSingleApplication.
|
||||||
|
|
||||||
|
SPDX-FileCopyrightText: 2019-2023 Klarälvdalens Datakonsult AB, a KDAB Group company <info@kdab.com>
|
||||||
|
|
||||||
|
SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
|
Contact KDAB at <info@kdab.com> for commercial licensing options.
|
||||||
|
*/
|
||||||
|
#ifndef KDSINGLEAPPLICATION_LIB_H
|
||||||
|
#define KDSINGLEAPPLICATION_LIB_H
|
||||||
|
|
||||||
|
#include <QtCore/QtGlobal>
|
||||||
|
|
||||||
|
#if defined(KDSINGLEAPPLICATION_STATIC_BUILD)
|
||||||
|
#define KDSINGLEAPPLICATION_EXPORT
|
||||||
|
#elif defined(KDSINGLEAPPLICATION_SHARED_BUILD)
|
||||||
|
#define KDSINGLEAPPLICATION_EXPORT Q_DECL_EXPORT
|
||||||
|
#else
|
||||||
|
#define KDSINGLEAPPLICATION_EXPORT Q_DECL_IMPORT
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#endif // KDSINGLEAPPLICATION_LIB_H
|
||||||
315
3rdparty/kdsingleapplication/kdsingleapplication_localsocket.cpp
vendored
Normal file
315
3rdparty/kdsingleapplication/kdsingleapplication_localsocket.cpp
vendored
Normal file
@@ -0,0 +1,315 @@
|
|||||||
|
/*
|
||||||
|
This file is part of KDSingleApplication.
|
||||||
|
|
||||||
|
SPDX-FileCopyrightText: 2019-2023 Klarälvdalens Datakonsult AB, a KDAB Group company <info@kdab.com>
|
||||||
|
|
||||||
|
SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
|
Contact KDAB at <info@kdab.com> for commercial licensing options.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "kdsingleapplication_localsocket_p.h"
|
||||||
|
|
||||||
|
#include <QtCore/QDir>
|
||||||
|
#include <QtCore/QDeadlineTimer>
|
||||||
|
#include <QtCore/QTimer>
|
||||||
|
#include <QtCore/QLockFile>
|
||||||
|
#include <QtCore/QDataStream>
|
||||||
|
|
||||||
|
#include <QtCore/QtDebug>
|
||||||
|
#include <QtCore/QLoggingCategory>
|
||||||
|
|
||||||
|
#include <QtNetwork/QLocalServer>
|
||||||
|
#include <QtNetwork/QLocalSocket>
|
||||||
|
|
||||||
|
#include <chrono>
|
||||||
|
#include <algorithm>
|
||||||
|
|
||||||
|
#ifdef Q_OS_UNIX
|
||||||
|
// for ::getuid()
|
||||||
|
# include <sys/types.h>
|
||||||
|
# include <unistd.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifdef Q_OS_WIN
|
||||||
|
# include <qt_windows.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
static constexpr auto LOCALSOCKET_CONNECTION_TIMEOUT = std::chrono::seconds(5);
|
||||||
|
static constexpr char LOCALSOCKET_PROTOCOL_VERSION = 2;
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
#ifdef __GNUC__
|
||||||
|
#pragma GCC diagnostic push
|
||||||
|
#pragma GCC diagnostic ignored "-Wmissing-declarations"
|
||||||
|
#endif
|
||||||
|
Q_LOGGING_CATEGORY(kdsaLocalSocket, "kdsingleapplication.localsocket", QtWarningMsg)
|
||||||
|
#ifdef __GNUC__
|
||||||
|
#pragma GCC diagnostic pop
|
||||||
|
#endif
|
||||||
|
|
||||||
|
KDSingleApplicationLocalSocket::KDSingleApplicationLocalSocket(const QString &name, QObject *parent)
|
||||||
|
: QObject(parent)
|
||||||
|
{
|
||||||
|
#if defined(Q_OS_UNIX)
|
||||||
|
/* cppcheck-suppress useInitializationList */
|
||||||
|
m_socketName = QStringLiteral("kdsingleapp-%1-%2")
|
||||||
|
.arg(::getuid())
|
||||||
|
.arg(name);
|
||||||
|
#elif defined(Q_OS_WIN)
|
||||||
|
// I'm not sure of a "global session identifier" on Windows; are
|
||||||
|
// multiple logins from the same user a possibility? For now, following this:
|
||||||
|
// https://docs.microsoft.com/en-us/windows/desktop/devnotes/getting-the-session-id-of-the-current-process
|
||||||
|
|
||||||
|
DWORD sessionId;
|
||||||
|
BOOL haveSessionId = ProcessIdToSessionId(GetCurrentProcessId(), &sessionId);
|
||||||
|
|
||||||
|
m_socketName = QString::fromUtf8("kdsingleapp-%1-%2")
|
||||||
|
.arg(haveSessionId ? sessionId : 0)
|
||||||
|
.arg(name);
|
||||||
|
#else
|
||||||
|
#error "KDSingleApplication has not been ported to this platform"
|
||||||
|
#endif
|
||||||
|
|
||||||
|
const QString lockFilePath =
|
||||||
|
QDir::tempPath() + QLatin1Char('/') + m_socketName + QLatin1String(".lock");
|
||||||
|
|
||||||
|
qCDebug(kdsaLocalSocket) << "Socket name is" << m_socketName;
|
||||||
|
qCDebug(kdsaLocalSocket) << "Lock file path is" << lockFilePath;
|
||||||
|
|
||||||
|
std::unique_ptr<QLockFile> lockFile(new QLockFile(lockFilePath));
|
||||||
|
lockFile->setStaleLockTime(0);
|
||||||
|
|
||||||
|
if (!lockFile->tryLock()) {
|
||||||
|
// someone else has the lock => we're secondary
|
||||||
|
qCDebug(kdsaLocalSocket) << "Secondary instance";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
qCDebug(kdsaLocalSocket) << "Primary instance";
|
||||||
|
|
||||||
|
std::unique_ptr<QLocalServer> server = std::make_unique<QLocalServer>();
|
||||||
|
if (!server->listen(m_socketName)) {
|
||||||
|
// maybe the primary crashed, leaving a stale socket; delete it and try again
|
||||||
|
QLocalServer::removeServer(m_socketName);
|
||||||
|
if (!server->listen(m_socketName)) {
|
||||||
|
// TODO: better error handling.
|
||||||
|
qWarning("KDSingleApplication: unable to make the primary instance listen on %ls: %ls",
|
||||||
|
qUtf16Printable(m_socketName),
|
||||||
|
qUtf16Printable(server->errorString()));
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
connect(server.get(), &QLocalServer::newConnection,
|
||||||
|
this, &KDSingleApplicationLocalSocket::handleNewConnection);
|
||||||
|
|
||||||
|
m_lockFile = std::move(lockFile);
|
||||||
|
m_localServer = std::move(server);
|
||||||
|
}
|
||||||
|
|
||||||
|
KDSingleApplicationLocalSocket::~KDSingleApplicationLocalSocket() = default;
|
||||||
|
|
||||||
|
bool KDSingleApplicationLocalSocket::isPrimaryInstance() const
|
||||||
|
{
|
||||||
|
return m_localServer != nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool KDSingleApplicationLocalSocket::sendMessage(const QByteArray &message, int timeout)
|
||||||
|
{
|
||||||
|
Q_ASSERT(!isPrimaryInstance());
|
||||||
|
QLocalSocket socket;
|
||||||
|
|
||||||
|
qCDebug(kdsaLocalSocket) << "Preparing to send message" << message << "with timeout" << timeout;
|
||||||
|
|
||||||
|
QDeadlineTimer deadline(timeout);
|
||||||
|
|
||||||
|
// There is an inherent race here with the setup of the server side.
|
||||||
|
// Even if the socket lock is held by the server, the server may not
|
||||||
|
// be listening yet. So this connection may fail; keep retrying
|
||||||
|
// until we hit the timeout.
|
||||||
|
do {
|
||||||
|
socket.connectToServer(m_socketName);
|
||||||
|
if (socket.waitForConnected(static_cast<int>(deadline.remainingTime())))
|
||||||
|
break;
|
||||||
|
} while (!deadline.hasExpired());
|
||||||
|
|
||||||
|
qCDebug(kdsaLocalSocket) << "Socket state:" << socket.state() << "Timer remaining" << deadline.remainingTime() << "Expired?" << deadline.hasExpired();
|
||||||
|
|
||||||
|
if (deadline.hasExpired()) {
|
||||||
|
qCWarning(kdsaLocalSocket) << "Connection timed out";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
socket.write(&LOCALSOCKET_PROTOCOL_VERSION, 1);
|
||||||
|
|
||||||
|
{
|
||||||
|
QByteArray encodedMessage;
|
||||||
|
QDataStream ds(&encodedMessage, QIODevice::WriteOnly);
|
||||||
|
ds << message;
|
||||||
|
socket.write(encodedMessage);
|
||||||
|
}
|
||||||
|
|
||||||
|
qCDebug(kdsaLocalSocket) << "Wrote message in the socket"
|
||||||
|
<< "Timer remaining" << deadline.remainingTime() << "Expired?" << deadline.hasExpired();
|
||||||
|
|
||||||
|
// There is no acknowledgement mechanism here.
|
||||||
|
// Should there be one?
|
||||||
|
|
||||||
|
while (socket.bytesToWrite() > 0) {
|
||||||
|
if (!socket.waitForBytesWritten(static_cast<int>(deadline.remainingTime()))) {
|
||||||
|
qCWarning(kdsaLocalSocket) << "Message to primary timed out";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
qCDebug(kdsaLocalSocket) << "Bytes written, now disconnecting"
|
||||||
|
<< "Timer remaining" << deadline.remainingTime() << "Expired?" << deadline.hasExpired();
|
||||||
|
|
||||||
|
socket.disconnectFromServer();
|
||||||
|
|
||||||
|
if (socket.state() == QLocalSocket::UnconnectedState) {
|
||||||
|
qCDebug(kdsaLocalSocket) << "Disconnected -- success!";
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!socket.waitForDisconnected(static_cast<int>(deadline.remainingTime()))) {
|
||||||
|
qCWarning(kdsaLocalSocket) << "Disconnection from primary timed out";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
qCDebug(kdsaLocalSocket) << "Disconnected -- success!";
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void KDSingleApplicationLocalSocket::handleNewConnection()
|
||||||
|
{
|
||||||
|
Q_ASSERT(m_localServer);
|
||||||
|
|
||||||
|
QLocalSocket *socket = nullptr;
|
||||||
|
while ((socket = m_localServer->nextPendingConnection())) {
|
||||||
|
qCDebug(kdsaLocalSocket) << "Got new connection on" << m_socketName << "state" << socket->state();
|
||||||
|
|
||||||
|
Connection c(socket);
|
||||||
|
socket = c.socket.get();
|
||||||
|
|
||||||
|
c.readDataConnection = QObjectConnectionHolder(
|
||||||
|
connect(socket, &QLocalSocket::readyRead,
|
||||||
|
this, &KDSingleApplicationLocalSocket::readDataFromSecondary));
|
||||||
|
|
||||||
|
c.secondaryDisconnectedConnection = QObjectConnectionHolder(
|
||||||
|
connect(socket, &QLocalSocket::disconnected,
|
||||||
|
this, &KDSingleApplicationLocalSocket::secondaryDisconnected));
|
||||||
|
|
||||||
|
c.abortConnection = QObjectConnectionHolder(
|
||||||
|
connect(c.timeoutTimer.get(), &QTimer::timeout,
|
||||||
|
this, &KDSingleApplicationLocalSocket::abortConnectionToSecondary));
|
||||||
|
|
||||||
|
m_clients.push_back(std::move(c));
|
||||||
|
|
||||||
|
// Note that by the time we get here, the socket could've already been closed,
|
||||||
|
// and no signals emitted (hello, Windows!). Read what's already in the socket.
|
||||||
|
if (readDataFromSecondarySocket(socket))
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (socket->state() == QLocalSocket::UnconnectedState)
|
||||||
|
secondarySocketDisconnected(socket);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
template<typename Container>
|
||||||
|
static auto findConnectionBySocket(Container &container, QLocalSocket *socket)
|
||||||
|
{
|
||||||
|
auto i = std::find_if(container.begin(),
|
||||||
|
container.end(),
|
||||||
|
[socket](const auto &c) { return c.socket.get() == socket; });
|
||||||
|
Q_ASSERT(i != container.end());
|
||||||
|
return i;
|
||||||
|
}
|
||||||
|
|
||||||
|
template<typename Container>
|
||||||
|
static auto findConnectionByTimer(Container &container, QTimer *timer)
|
||||||
|
{
|
||||||
|
auto i = std::find_if(container.begin(),
|
||||||
|
container.end(),
|
||||||
|
[timer](const auto &c) { return c.timeoutTimer.get() == timer; });
|
||||||
|
Q_ASSERT(i != container.end());
|
||||||
|
return i;
|
||||||
|
}
|
||||||
|
|
||||||
|
void KDSingleApplicationLocalSocket::readDataFromSecondary()
|
||||||
|
{
|
||||||
|
QLocalSocket *socket = static_cast<QLocalSocket *>(sender());
|
||||||
|
readDataFromSecondarySocket(socket);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool KDSingleApplicationLocalSocket::readDataFromSecondarySocket(QLocalSocket *socket)
|
||||||
|
{
|
||||||
|
auto i = findConnectionBySocket(m_clients, socket);
|
||||||
|
Connection &c = *i;
|
||||||
|
c.readData.append(socket->readAll());
|
||||||
|
|
||||||
|
qCDebug(kdsaLocalSocket) << "Got more data from a secondary. Data read so far:" << c.readData;
|
||||||
|
|
||||||
|
const QByteArray &data = c.readData;
|
||||||
|
|
||||||
|
if (data.size() >= 1) {
|
||||||
|
if (data[0] != LOCALSOCKET_PROTOCOL_VERSION) {
|
||||||
|
qCDebug(kdsaLocalSocket) << "Got an invalid protocol version";
|
||||||
|
m_clients.erase(i);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
QDataStream ds(data);
|
||||||
|
ds.skipRawData(1);
|
||||||
|
|
||||||
|
ds.startTransaction();
|
||||||
|
QByteArray message;
|
||||||
|
ds >> message;
|
||||||
|
|
||||||
|
if (ds.commitTransaction()) {
|
||||||
|
qCDebug(kdsaLocalSocket) << "Got a complete message:" << message;
|
||||||
|
Q_EMIT messageReceived(message);
|
||||||
|
m_clients.erase(i);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
void KDSingleApplicationLocalSocket::secondaryDisconnected()
|
||||||
|
{
|
||||||
|
QLocalSocket *socket = static_cast<QLocalSocket *>(sender());
|
||||||
|
secondarySocketDisconnected(socket);
|
||||||
|
}
|
||||||
|
|
||||||
|
void KDSingleApplicationLocalSocket::secondarySocketDisconnected(QLocalSocket *socket)
|
||||||
|
{
|
||||||
|
auto i = findConnectionBySocket(m_clients, socket);
|
||||||
|
Connection c = std::move(*i);
|
||||||
|
m_clients.erase(i);
|
||||||
|
|
||||||
|
qCDebug(kdsaLocalSocket) << "Secondary disconnected. Data read:" << c.readData;
|
||||||
|
}
|
||||||
|
|
||||||
|
void KDSingleApplicationLocalSocket::abortConnectionToSecondary()
|
||||||
|
{
|
||||||
|
QTimer *timer = static_cast<QTimer *>(sender());
|
||||||
|
|
||||||
|
auto i = findConnectionByTimer(m_clients, timer);
|
||||||
|
Connection c = std::move(*i);
|
||||||
|
m_clients.erase(i);
|
||||||
|
|
||||||
|
qCDebug(kdsaLocalSocket) << "Secondary timed out. Data read:" << c.readData;
|
||||||
|
}
|
||||||
|
|
||||||
|
KDSingleApplicationLocalSocket::Connection::Connection(QLocalSocket *_socket)
|
||||||
|
: socket(_socket)
|
||||||
|
, timeoutTimer(new QTimer)
|
||||||
|
{
|
||||||
|
timeoutTimer->start(LOCALSOCKET_CONNECTION_TIMEOUT);
|
||||||
|
}
|
||||||
126
3rdparty/kdsingleapplication/kdsingleapplication_localsocket_p.h
vendored
Normal file
126
3rdparty/kdsingleapplication/kdsingleapplication_localsocket_p.h
vendored
Normal file
@@ -0,0 +1,126 @@
|
|||||||
|
/*
|
||||||
|
This file is part of KDSingleApplication.
|
||||||
|
|
||||||
|
SPDX-FileCopyrightText: 2019-2023 Klarälvdalens Datakonsult AB, a KDAB Group company <info@kdab.com>
|
||||||
|
|
||||||
|
SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
|
Contact KDAB at <info@kdab.com> for commercial licensing options.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef KDSINGLEAPPLICATION_LOCALSOCKET_P_H
|
||||||
|
#define KDSINGLEAPPLICATION_LOCALSOCKET_P_H
|
||||||
|
|
||||||
|
#include <QtCore/QObject>
|
||||||
|
#include <QtCore/QByteArray>
|
||||||
|
#include <QtCore/QString>
|
||||||
|
|
||||||
|
QT_BEGIN_NAMESPACE
|
||||||
|
class QLockFile;
|
||||||
|
class QLocalServer;
|
||||||
|
class QLocalSocket;
|
||||||
|
class QTimer;
|
||||||
|
QT_END_NAMESPACE
|
||||||
|
|
||||||
|
#include <memory>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
struct QObjectDeleteLater
|
||||||
|
{
|
||||||
|
void operator()(QObject *o)
|
||||||
|
{
|
||||||
|
o->deleteLater();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
class QObjectConnectionHolder
|
||||||
|
{
|
||||||
|
Q_DISABLE_COPY(QObjectConnectionHolder)
|
||||||
|
QMetaObject::Connection c;
|
||||||
|
|
||||||
|
public:
|
||||||
|
QObjectConnectionHolder()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
explicit QObjectConnectionHolder(QMetaObject::Connection _c)
|
||||||
|
: c(std::move(_c))
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
~QObjectConnectionHolder()
|
||||||
|
{
|
||||||
|
QObject::disconnect(c);
|
||||||
|
}
|
||||||
|
|
||||||
|
QObjectConnectionHolder(QObjectConnectionHolder &&other) noexcept
|
||||||
|
: c(std::exchange(other.c, {}))
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
QObjectConnectionHolder &operator=(QObjectConnectionHolder &&other) noexcept
|
||||||
|
{
|
||||||
|
QObjectConnectionHolder moved(std::move(other));
|
||||||
|
swap(moved);
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
void swap(QObjectConnectionHolder &other) noexcept
|
||||||
|
{
|
||||||
|
using std::swap;
|
||||||
|
swap(c, other.c);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
class KDSingleApplicationLocalSocket : public QObject
|
||||||
|
{
|
||||||
|
Q_OBJECT
|
||||||
|
|
||||||
|
public:
|
||||||
|
explicit KDSingleApplicationLocalSocket(const QString &name,
|
||||||
|
QObject *parent = nullptr);
|
||||||
|
~KDSingleApplicationLocalSocket();
|
||||||
|
|
||||||
|
bool isPrimaryInstance() const;
|
||||||
|
|
||||||
|
public Q_SLOTS:
|
||||||
|
bool sendMessage(const QByteArray &message, int timeout);
|
||||||
|
|
||||||
|
Q_SIGNALS:
|
||||||
|
void messageReceived(const QByteArray &message);
|
||||||
|
|
||||||
|
private:
|
||||||
|
void handleNewConnection();
|
||||||
|
void readDataFromSecondary();
|
||||||
|
bool readDataFromSecondarySocket(QLocalSocket *socket);
|
||||||
|
void secondaryDisconnected();
|
||||||
|
void secondarySocketDisconnected(QLocalSocket *socket);
|
||||||
|
void abortConnectionToSecondary();
|
||||||
|
|
||||||
|
QString m_socketName;
|
||||||
|
|
||||||
|
std::unique_ptr<QLockFile> m_lockFile; // protects m_localServer
|
||||||
|
std::unique_ptr<QLocalServer> m_localServer;
|
||||||
|
|
||||||
|
struct Connection
|
||||||
|
{
|
||||||
|
explicit Connection(QLocalSocket *s);
|
||||||
|
|
||||||
|
std::unique_ptr<QLocalSocket, QObjectDeleteLater> socket;
|
||||||
|
std::unique_ptr<QTimer, QObjectDeleteLater> timeoutTimer;
|
||||||
|
QByteArray readData;
|
||||||
|
|
||||||
|
// socket/timeoutTimer are deleted via deleteLater (as we delete them
|
||||||
|
// in slots connected to their signals). Before the deleteLater is acted upon,
|
||||||
|
// they may emit further signals, triggering logic that it's not supposed
|
||||||
|
// to be triggered (as the Connection has already been destroyed).
|
||||||
|
// Use this Holder to break the connections.
|
||||||
|
QObjectConnectionHolder readDataConnection;
|
||||||
|
QObjectConnectionHolder secondaryDisconnectedConnection;
|
||||||
|
QObjectConnectionHolder abortConnection;
|
||||||
|
};
|
||||||
|
|
||||||
|
std::vector<Connection> m_clients;
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif // KDSINGLEAPPLICATION_LOCALSOCKET_P_H
|
||||||
36
3rdparty/singleapplication/CMakeLists.txt
vendored
36
3rdparty/singleapplication/CMakeLists.txt
vendored
@@ -1,36 +0,0 @@
|
|||||||
cmake_minimum_required(VERSION 3.7)
|
|
||||||
|
|
||||||
include(CheckIncludeFiles)
|
|
||||||
include(CheckFunctionExists)
|
|
||||||
|
|
||||||
check_function_exists(geteuid HAVE_GETEUID)
|
|
||||||
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")
|
|
||||||
24
3rdparty/singleapplication/LICENSE
vendored
24
3rdparty/singleapplication/LICENSE
vendored
@@ -1,24 +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.
|
|
||||||
|
|
||||||
Note: Some of the examples include code not distributed under the terms of the
|
|
||||||
MIT License.
|
|
||||||
305
3rdparty/singleapplication/README.md
vendored
305
3rdparty/singleapplication/README.md
vendored
@@ -1,305 +0,0 @@
|
|||||||
SingleApplication
|
|
||||||
=================
|
|
||||||
[](https://github.com/itay-grudev/SingleApplication/actions)
|
|
||||||
|
|
||||||
This is a replacement of the QtSingleApplication for `Qt5` and `Qt6`.
|
|
||||||
|
|
||||||
Keeps the Primary Instance of your Application and kills each subsequent
|
|
||||||
instances. It can (if enabled) spawn secondary (non-related to the primary)
|
|
||||||
instances and can send data to the primary instance from secondary instances.
|
|
||||||
|
|
||||||
Usage
|
|
||||||
-----
|
|
||||||
|
|
||||||
The `SingleApplication` class inherits from whatever `Q[Core|Gui]Application`
|
|
||||||
class you specify via the `QAPPLICATION_CLASS` macro (`QCoreApplication` is the
|
|
||||||
default). Further usage is similar to the use of the `Q[Core|Gui]Application`
|
|
||||||
classes.
|
|
||||||
|
|
||||||
You can use the library as if you use any other `QCoreApplication` derived
|
|
||||||
class:
|
|
||||||
|
|
||||||
```cpp
|
|
||||||
#include <QApplication>
|
|
||||||
#include <SingleApplication.h>
|
|
||||||
|
|
||||||
int main( int argc, char* argv[] )
|
|
||||||
{
|
|
||||||
SingleApplication app( argc, argv );
|
|
||||||
|
|
||||||
return app.exec();
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
To include the library files I would recommend that you add it as a git
|
|
||||||
submodule to your project. Here is how:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
git submodule add https://github.com/itay-grudev/SingleApplication.git singleapplication
|
|
||||||
```
|
|
||||||
|
|
||||||
**Qmake:**
|
|
||||||
|
|
||||||
Then include the `singleapplication.pri` file in your `.pro` project file.
|
|
||||||
|
|
||||||
```qmake
|
|
||||||
include(singleapplication/singleapplication.pri)
|
|
||||||
DEFINES += QAPPLICATION_CLASS=QApplication
|
|
||||||
```
|
|
||||||
|
|
||||||
**CMake:**
|
|
||||||
|
|
||||||
Then include the subdirectory in your `CMakeLists.txt` project file.
|
|
||||||
|
|
||||||
```cmake
|
|
||||||
set(QAPPLICATION_CLASS QApplication CACHE STRING "Inheritance class for SingleApplication")
|
|
||||||
add_subdirectory(src/third-party/singleapplication)
|
|
||||||
target_link_libraries(${PROJECT_NAME} SingleApplication::SingleApplication)
|
|
||||||
```
|
|
||||||
|
|
||||||
|
|
||||||
The library sets up a `QLocalServer` and a `QSharedMemory` block. The first
|
|
||||||
instance of your Application is your Primary Instance. It would check if the
|
|
||||||
shared memory block exists and if not it will start a `QLocalServer` and listen
|
|
||||||
for connections. Each subsequent instance of your application would check if the
|
|
||||||
shared memory block exists and if it does, it will connect to the QLocalServer
|
|
||||||
to notify the primary instance that a new instance had been started, after which
|
|
||||||
it would terminate with status code `0`. In the Primary Instance
|
|
||||||
`SingleApplication` would emit the `instanceStarted()` signal upon detecting
|
|
||||||
that a new instance had been started.
|
|
||||||
|
|
||||||
The library uses `stdlib` to terminate the program with the `exit()` function.
|
|
||||||
|
|
||||||
Also don't forget to specify which `QCoreApplication` class your app is using if it
|
|
||||||
is not `QCoreApplication` as in examples above.
|
|
||||||
|
|
||||||
The `Instance Started` signal
|
|
||||||
-----------------------------
|
|
||||||
|
|
||||||
The SingleApplication class implements a `instanceStarted()` signal. You can
|
|
||||||
bind to that signal to raise your application's window when a new instance had
|
|
||||||
been started, for example.
|
|
||||||
|
|
||||||
```cpp
|
|
||||||
// window is a QWindow instance
|
|
||||||
QObject::connect(
|
|
||||||
&app,
|
|
||||||
&SingleApplication::instanceStarted,
|
|
||||||
&window,
|
|
||||||
&QWindow::raise
|
|
||||||
);
|
|
||||||
```
|
|
||||||
|
|
||||||
Using `SingleApplication::instance()` is a neat way to get the
|
|
||||||
`SingleApplication` instance for binding to it's signals anywhere in your
|
|
||||||
program.
|
|
||||||
|
|
||||||
__Note:__ On Windows the ability to bring the application windows to the
|
|
||||||
foreground is restricted. See [Windows specific implementations](Windows.md)
|
|
||||||
for a workaround and an example implementation.
|
|
||||||
|
|
||||||
|
|
||||||
Secondary Instances
|
|
||||||
-------------------
|
|
||||||
|
|
||||||
If you want to be able to launch additional Secondary Instances (not related to
|
|
||||||
your Primary Instance) you have to enable that with the third parameter of the
|
|
||||||
`SingleApplication` constructor. The default is `false` meaning no Secondary
|
|
||||||
Instances. Here is an example of how you would start a Secondary Instance send
|
|
||||||
a message with the command line arguments to the primary instance and then shut
|
|
||||||
down.
|
|
||||||
|
|
||||||
```cpp
|
|
||||||
int main(int argc, char *argv[])
|
|
||||||
{
|
|
||||||
SingleApplication app( argc, argv, true );
|
|
||||||
|
|
||||||
if( app.isSecondary() ) {
|
|
||||||
app.sendMessage( app.arguments().join(' ')).toUtf8() );
|
|
||||||
app.exit( 0 );
|
|
||||||
}
|
|
||||||
|
|
||||||
return app.exec();
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
*__Note:__ A secondary instance won't cause the emission of the
|
|
||||||
`instanceStarted()` signal by default. See `SingleApplication::Mode` for more
|
|
||||||
details.*
|
|
||||||
|
|
||||||
You can check whether your instance is a primary or secondary with the following
|
|
||||||
methods:
|
|
||||||
|
|
||||||
```cpp
|
|
||||||
app.isPrimary();
|
|
||||||
// or
|
|
||||||
app.isSecondary();
|
|
||||||
```
|
|
||||||
|
|
||||||
*__Note:__ If your Primary Instance is terminated a newly launched instance
|
|
||||||
will replace the Primary one even if the Secondary flag has been set.*
|
|
||||||
|
|
||||||
Examples
|
|
||||||
--------
|
|
||||||
|
|
||||||
There are three examples provided in this repository:
|
|
||||||
|
|
||||||
* Basic example that prevents a secondary instance from starting [`examples/basic`](https://github.com/itay-grudev/SingleApplication/tree/master/examples/basic)
|
|
||||||
* An example of a graphical application raising it's parent window [`examples/calculator`](https://github.com/itay-grudev/SingleApplication/tree/master/examples/calculator)
|
|
||||||
* A console application sending the primary instance it's command line parameters [`examples/sending_arguments`](https://github.com/itay-grudev/SingleApplication/tree/master/examples/sending_arguments)
|
|
||||||
|
|
||||||
API
|
|
||||||
---
|
|
||||||
|
|
||||||
### Members
|
|
||||||
|
|
||||||
```cpp
|
|
||||||
SingleApplication::SingleApplication( int &argc, char *argv[], bool allowSecondary = false, Options options = Mode::User, int timeout = 100, QString userData = QString() )
|
|
||||||
```
|
|
||||||
|
|
||||||
Depending on whether `allowSecondary` is set, this constructor may terminate
|
|
||||||
your app if there is already a primary instance running. Additional `Options`
|
|
||||||
can be specified to set whether the SingleApplication block should work
|
|
||||||
user-wide or system-wide. Additionally the `Mode::SecondaryNotification` may be
|
|
||||||
used to notify the primary instance whenever a secondary instance had been
|
|
||||||
started (disabled by default). `timeout` specifies the maximum time in
|
|
||||||
milliseconds to wait for blocking operations. Setting `userData` provides additional data that will isolate this instance from other instances that do not have the same (or any) user data set.
|
|
||||||
|
|
||||||
*__Note:__ `argc` and `argv` may be changed as Qt removes arguments that it
|
|
||||||
recognizes.*
|
|
||||||
|
|
||||||
*__Note:__ `Mode::SecondaryNotification` only works if set on both the primary
|
|
||||||
and the secondary instance.*
|
|
||||||
|
|
||||||
*__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.*
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
```cpp
|
|
||||||
bool SingleApplication::sendMessage( QByteArray message, int timeout = 100 )
|
|
||||||
```
|
|
||||||
|
|
||||||
Sends `message` to the Primary Instance. Uses `timeout` as a the maximum timeout
|
|
||||||
in milliseconds for blocking functions. Returns `true` if the message has been sent
|
|
||||||
successfully. If the message can't be sent or the function timeouts - returns `false`.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
```cpp
|
|
||||||
bool SingleApplication::isPrimary()
|
|
||||||
```
|
|
||||||
|
|
||||||
Returns if the instance is the primary instance.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
```cpp
|
|
||||||
bool SingleApplication::isSecondary()
|
|
||||||
```
|
|
||||||
Returns if the instance is a secondary instance.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
```cpp
|
|
||||||
quint32 SingleApplication::instanceId()
|
|
||||||
```
|
|
||||||
|
|
||||||
Returns a unique identifier for the current instance.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
```cpp
|
|
||||||
qint64 SingleApplication::primaryPid()
|
|
||||||
```
|
|
||||||
|
|
||||||
Returns the process ID (PID) of the primary instance.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
```cpp
|
|
||||||
QString SingleApplication::primaryUser()
|
|
||||||
```
|
|
||||||
|
|
||||||
Returns the username the primary instance is running as.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
```cpp
|
|
||||||
QString SingleApplication::currentUser()
|
|
||||||
```
|
|
||||||
|
|
||||||
Returns the username the current instance is running as.
|
|
||||||
|
|
||||||
### Signals
|
|
||||||
|
|
||||||
```cpp
|
|
||||||
void SingleApplication::instanceStarted()
|
|
||||||
```
|
|
||||||
|
|
||||||
Triggered whenever a new instance had been started, except for secondary
|
|
||||||
instances if the `Mode::SecondaryNotification` flag is not specified.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
```cpp
|
|
||||||
void SingleApplication::receivedMessage( quint32 instanceId, QByteArray message )
|
|
||||||
```
|
|
||||||
|
|
||||||
Triggered whenever there is a message received from a secondary instance.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### Flags
|
|
||||||
|
|
||||||
```cpp
|
|
||||||
enum SingleApplication::Mode
|
|
||||||
```
|
|
||||||
|
|
||||||
* `Mode::User` - The SingleApplication block should apply user wide. This adds
|
|
||||||
user specific data to the key used for the shared memory and server name.
|
|
||||||
This is the default functionality.
|
|
||||||
* `Mode::System` – The SingleApplication block applies system-wide.
|
|
||||||
* `Mode::SecondaryNotification` – Whether to trigger `instanceStarted()` even
|
|
||||||
whenever secondary instances are started.
|
|
||||||
* `Mode::ExcludeAppPath` – Excludes the application path from the server name
|
|
||||||
(and memory block) hash.
|
|
||||||
* `Mode::ExcludeAppVersion` – Excludes the application version from the server
|
|
||||||
name (and memory block) hash.
|
|
||||||
|
|
||||||
*__Note:__ `Mode::SecondaryNotification` only works if set on both the primary
|
|
||||||
and the secondary instance.*
|
|
||||||
|
|
||||||
*__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.*
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
Versioning
|
|
||||||
----------
|
|
||||||
|
|
||||||
Each major version introduces either very significant changes or is not
|
|
||||||
backwards compatible with the previous version. Minor versions only add
|
|
||||||
additional features, bug fixes or performance improvements and are backwards
|
|
||||||
compatible with the previous release. See [`CHANGELOG.md`](CHANGELOG.md) for
|
|
||||||
more details.
|
|
||||||
|
|
||||||
Implementation
|
|
||||||
--------------
|
|
||||||
|
|
||||||
The library is implemented with a QSharedMemory block which is thread safe and
|
|
||||||
guarantees a race condition will not occur. It also uses a QLocalSocket to
|
|
||||||
notify the main process that a new instance had been spawned and thus invoke the
|
|
||||||
`instanceStarted()` signal and for messaging the primary instance.
|
|
||||||
|
|
||||||
Additionally the library can recover from being forcefully killed on *nix
|
|
||||||
systems and will reset the memory block given that there are no other
|
|
||||||
instances running.
|
|
||||||
|
|
||||||
License
|
|
||||||
-------
|
|
||||||
This library and it's supporting documentation are released under
|
|
||||||
`The MIT License (MIT)` with the exception of the Qt calculator examples which
|
|
||||||
is distributed under the BSD license.
|
|
||||||
2
3rdparty/singleapplication/config.h.in
vendored
2
3rdparty/singleapplication/config.h.in
vendored
@@ -1,2 +0,0 @@
|
|||||||
#cmakedefine HAVE_GETEUID
|
|
||||||
#cmakedefine HAVE_GETPWUID
|
|
||||||
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);
|
|
||||||
|
|
||||||
}
|
|
||||||
152
3rdparty/singleapplication/singleapplication.h
vendored
152
3rdparty/singleapplication/singleapplication.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 SINGLEAPPLICATION_H
|
|
||||||
#define SINGLEAPPLICATION_H
|
|
||||||
|
|
||||||
#include <QtGlobal>
|
|
||||||
#include <QApplication>
|
|
||||||
#include <QFlags>
|
|
||||||
#include <QByteArray>
|
|
||||||
|
|
||||||
class SingleApplicationPrivate;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief The SingleApplication class handles multiple instances of the same Application
|
|
||||||
* @see QApplication
|
|
||||||
*/
|
|
||||||
class SingleApplication : public QApplication { // clazy:exclude=ctor-missing-parent-argument
|
|
||||||
Q_OBJECT
|
|
||||||
|
|
||||||
using app_t = QApplication;
|
|
||||||
|
|
||||||
public:
|
|
||||||
/**
|
|
||||||
* @brief Mode of operation of SingleApplication.
|
|
||||||
* 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 SingleApplication 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 SingleApplication 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 SingleApplication
|
|
||||||
* initialisation will be completed in given time, though is a good hint.
|
|
||||||
* 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);
|
|
||||||
~SingleApplication() 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:
|
|
||||||
SingleApplicationPrivate *d_ptr;
|
|
||||||
Q_DECLARE_PRIVATE(SingleApplication)
|
|
||||||
void abortSafely();
|
|
||||||
};
|
|
||||||
|
|
||||||
Q_DECLARE_OPERATORS_FOR_FLAGS(SingleApplication::Options)
|
|
||||||
|
|
||||||
#endif // SINGLEAPPLICATION_H
|
|
||||||
526
3rdparty/singleapplication/singleapplication_p.cpp
vendored
526
3rdparty/singleapplication/singleapplication_p.cpp
vendored
@@ -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 "singleapplication.h"
|
|
||||||
#include "singleapplication_p.h"
|
|
||||||
|
|
||||||
SingleApplicationPrivate::SingleApplicationPrivate(SingleApplication *ptr)
|
|
||||||
: q_ptr(ptr),
|
|
||||||
memory_(nullptr),
|
|
||||||
socket_(nullptr),
|
|
||||||
server_(nullptr),
|
|
||||||
instanceNumber_(-1) {}
|
|
||||||
|
|
||||||
SingleApplicationPrivate::~SingleApplicationPrivate() {
|
|
||||||
|
|
||||||
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 SingleApplicationPrivate::getUsername() {
|
|
||||||
|
|
||||||
#ifdef Q_OS_UNIX
|
|
||||||
QString username;
|
|
||||||
#if defined(HAVE_GETEUID) && defined(HAVE_GETPWUID)
|
|
||||||
struct passwd *pw = getpwuid(geteuid());
|
|
||||||
if (pw) {
|
|
||||||
username = QString::fromLocal8Bit(pw->pw_name);
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
if (username.isEmpty()) {
|
|
||||||
#if QT_VERSION >= QT_VERSION_CHECK(5, 10, 0)
|
|
||||||
username = qEnvironmentVariable("USER");
|
|
||||||
#else
|
|
||||||
username = QString::fromLocal8Bit(qgetenv("USER"));
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
return username;
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#ifdef Q_OS_WIN
|
|
||||||
wchar_t username[UNLEN + 1];
|
|
||||||
// Specifies size of the buffer on input
|
|
||||||
DWORD usernameLength = UNLEN + 1;
|
|
||||||
if (GetUserNameW(username, &usernameLength)) {
|
|
||||||
return QString::fromWCharArray(username);
|
|
||||||
}
|
|
||||||
#if QT_VERSION >= QT_VERSION_CHECK(5, 10, 0)
|
|
||||||
return qEnvironmentVariable("USERNAME");
|
|
||||||
#else
|
|
||||||
return QString::fromLocal8Bit(qgetenv("USERNAME"));
|
|
||||||
#endif
|
|
||||||
#endif
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
void SingleApplicationPrivate::genBlockServerName() {
|
|
||||||
|
|
||||||
QCryptographicHash appData(QCryptographicHash::Sha256);
|
|
||||||
appData.addData("SingleApplication");
|
|
||||||
appData.addData(SingleApplication::app_t::applicationName().toUtf8());
|
|
||||||
appData.addData(SingleApplication::app_t::organizationName().toUtf8());
|
|
||||||
appData.addData(SingleApplication::app_t::organizationDomain().toUtf8());
|
|
||||||
|
|
||||||
if (!(options_ & SingleApplication::Mode::ExcludeAppVersion)) {
|
|
||||||
appData.addData(SingleApplication::app_t::applicationVersion().toUtf8());
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!(options_ & SingleApplication::Mode::ExcludeAppPath)) {
|
|
||||||
#if defined(Q_OS_UNIX)
|
|
||||||
const QByteArray appImagePath = qgetenv("APPIMAGE");
|
|
||||||
if (appImagePath.isEmpty()) {
|
|
||||||
appData.addData(SingleApplication::app_t::applicationFilePath().toUtf8());
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
appData.addData(appImagePath);
|
|
||||||
};
|
|
||||||
#elif defined(Q_OS_WIN)
|
|
||||||
appData.addData(SingleApplication::app_t::applicationFilePath().toLower().toUtf8());
|
|
||||||
#else
|
|
||||||
appData.addData(SingleApplication::app_t::applicationFilePath().toUtf8());
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
|
|
||||||
// User level block requires a user specific data in the hash
|
|
||||||
if (options_ & SingleApplication::Mode::User) {
|
|
||||||
appData.addData(getUsername().toUtf8());
|
|
||||||
}
|
|
||||||
|
|
||||||
// Replace the backslash in RFC 2045 Base64 [a-zA-Z0-9+/=] to comply with server naming requirements.
|
|
||||||
blockServerName_ = appData.result().toBase64().replace("/", "_");
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
void SingleApplicationPrivate::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 SingleApplicationPrivate::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 SingleApplication::Mode::User flag on User level or no restrictions
|
|
||||||
if (options_ & SingleApplication::Mode::User) {
|
|
||||||
server_->setSocketOptions(QLocalServer::UserAccessOption);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
server_->setSocketOptions(QLocalServer::WorldAccessOption);
|
|
||||||
}
|
|
||||||
|
|
||||||
server_->listen(blockServerName_);
|
|
||||||
QObject::connect(server_, &QLocalServer::newConnection, this, &SingleApplicationPrivate::slotConnectionEstablished);
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
void SingleApplicationPrivate::startSecondary() {
|
|
||||||
|
|
||||||
InstancesInfo *instance = static_cast<InstancesInfo*>(memory_->data());
|
|
||||||
|
|
||||||
instance->secondary += 1;
|
|
||||||
instance->checksum = blockChecksum();
|
|
||||||
instanceNumber_ = instance->secondary;
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
bool SingleApplicationPrivate::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 SingleApplication 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 SingleApplicationPrivate::writeAck(QLocalSocket *sock) {
|
|
||||||
sock->putChar('\n');
|
|
||||||
}
|
|
||||||
|
|
||||||
bool SingleApplicationPrivate::writeConfirmedMessage(const int timeout, const QByteArray &msg) const {
|
|
||||||
|
|
||||||
QElapsedTimer time;
|
|
||||||
time.start();
|
|
||||||
|
|
||||||
// Frame 1: The header indicates the message length that follows
|
|
||||||
QByteArray header;
|
|
||||||
QDataStream headerStream(&header, QIODevice::WriteOnly);
|
|
||||||
headerStream.setVersion(QDataStream::Qt_5_8);
|
|
||||||
headerStream << static_cast<quint64>(msg.length());
|
|
||||||
|
|
||||||
if (!writeConfirmedFrame(static_cast<int>(timeout - time.elapsed()), header)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Frame 2: The message
|
|
||||||
return writeConfirmedFrame(static_cast<int>(timeout - time.elapsed()), msg);
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
bool SingleApplicationPrivate::writeConfirmedFrame(const int timeout, const QByteArray &msg) const {
|
|
||||||
|
|
||||||
socket_->write(msg);
|
|
||||||
socket_->flush();
|
|
||||||
|
|
||||||
bool result = socket_->waitForReadyRead(timeout);
|
|
||||||
if (result) {
|
|
||||||
socket_->read(1);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
quint16 SingleApplicationPrivate::blockChecksum() const {
|
|
||||||
|
|
||||||
#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
|
|
||||||
quint16 checksum = qChecksum(QByteArray(static_cast<const char*>(memory_->constData()), offsetof(InstancesInfo, checksum)));
|
|
||||||
#else
|
|
||||||
quint16 checksum = qChecksum(static_cast<const char*>(memory_->constData()), offsetof(InstancesInfo, checksum));
|
|
||||||
#endif
|
|
||||||
|
|
||||||
return checksum;
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
qint64 SingleApplicationPrivate::primaryPid() const {
|
|
||||||
|
|
||||||
memory_->lock();
|
|
||||||
InstancesInfo *instance = static_cast<InstancesInfo*>(memory_->data());
|
|
||||||
qint64 pid = instance->primaryPid;
|
|
||||||
memory_->unlock();
|
|
||||||
|
|
||||||
return pid;
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
QString SingleApplicationPrivate::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 SingleApplicationPrivate::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 SingleApplicationPrivate::readMessageHeader(QLocalSocket *sock, const SingleApplicationPrivate::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 SingleApplicationPrivate::isFrameComplete(QLocalSocket *sock) {
|
|
||||||
|
|
||||||
if (!connectionMap_.contains(sock)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
const ConnectionInfo &info = connectionMap_[sock];
|
|
||||||
return (sock->bytesAvailable() >= static_cast<qint64>(info.msgLen));
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
void SingleApplicationPrivate::readInitMessageBody(QLocalSocket *sock) {
|
|
||||||
|
|
||||||
Q_Q(SingleApplication);
|
|
||||||
|
|
||||||
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_ & SingleApplication::Mode::SecondaryNotification)) {
|
|
||||||
emit q->instanceStarted();
|
|
||||||
}
|
|
||||||
|
|
||||||
writeAck(sock);
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
void SingleApplicationPrivate::slotDataAvailable(QLocalSocket *dataSocket, const quint32 instanceId) {
|
|
||||||
|
|
||||||
Q_Q(SingleApplication);
|
|
||||||
|
|
||||||
if (!isFrameComplete(dataSocket)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const QByteArray message = dataSocket->readAll();
|
|
||||||
|
|
||||||
writeAck(dataSocket);
|
|
||||||
|
|
||||||
ConnectionInfo &info = connectionMap_[dataSocket];
|
|
||||||
info.stage = StageConnectedHeader;
|
|
||||||
|
|
||||||
emit q->receivedMessage(instanceId, message);
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
void SingleApplicationPrivate::slotClientConnectionClosed(QLocalSocket *closedSocket, const quint32 instanceId) {
|
|
||||||
|
|
||||||
if (closedSocket->bytesAvailable() > 0) {
|
|
||||||
slotDataAvailable(closedSocket, instanceId);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
void SingleApplicationPrivate::randomSleep() {
|
|
||||||
|
|
||||||
#if QT_VERSION >= QT_VERSION_CHECK(5, 10, 0)
|
|
||||||
QThread::msleep(QRandomGenerator::global()->bounded(8U, 18U));
|
|
||||||
#else
|
|
||||||
qsrand(QDateTime::currentMSecsSinceEpoch() % std::numeric_limits<uint>::max());
|
|
||||||
QThread::msleep(qrand() % 11 + 8);
|
|
||||||
#endif
|
|
||||||
|
|
||||||
}
|
|
||||||
116
3rdparty/singleapplication/singleapplication_p.h
vendored
116
3rdparty/singleapplication/singleapplication_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 SINGLEAPPLICATION_P_H
|
|
||||||
#define SINGLEAPPLICATION_P_H
|
|
||||||
|
|
||||||
#include <QtGlobal>
|
|
||||||
#include <QObject>
|
|
||||||
#include <QString>
|
|
||||||
#include <QHash>
|
|
||||||
|
|
||||||
#include "singleapplication.h"
|
|
||||||
|
|
||||||
class QLocalServer;
|
|
||||||
class QLocalSocket;
|
|
||||||
class QSharedMemory;
|
|
||||||
|
|
||||||
struct InstancesInfo {
|
|
||||||
bool primary;
|
|
||||||
quint32 secondary;
|
|
||||||
qint64 primaryPid;
|
|
||||||
char primaryUser[128];
|
|
||||||
quint16 checksum;
|
|
||||||
};
|
|
||||||
|
|
||||||
struct ConnectionInfo {
|
|
||||||
explicit ConnectionInfo() : msgLen(0), instanceId(0), stage(0) {}
|
|
||||||
quint64 msgLen;
|
|
||||||
quint32 instanceId;
|
|
||||||
quint8 stage;
|
|
||||||
};
|
|
||||||
|
|
||||||
class SingleApplicationPrivate : public QObject {
|
|
||||||
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(SingleApplication)
|
|
||||||
|
|
||||||
explicit SingleApplicationPrivate(SingleApplication *ptr);
|
|
||||||
~SingleApplicationPrivate() 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();
|
|
||||||
|
|
||||||
SingleApplication *q_ptr;
|
|
||||||
QSharedMemory *memory_;
|
|
||||||
QLocalSocket *socket_;
|
|
||||||
QLocalServer *server_;
|
|
||||||
quint32 instanceNumber_;
|
|
||||||
QString blockServerName_;
|
|
||||||
SingleApplication::Options options_;
|
|
||||||
QHash<QLocalSocket*, ConnectionInfo> connectionMap_;
|
|
||||||
|
|
||||||
public slots:
|
|
||||||
void slotConnectionEstablished();
|
|
||||||
void slotDataAvailable(QLocalSocket*, const quint32);
|
|
||||||
void slotClientConnectionClosed(QLocalSocket*, const quint32);
|
|
||||||
};
|
|
||||||
|
|
||||||
#endif // SINGLEAPPLICATION_P_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
|
|
||||||
@@ -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
|
|
||||||
@@ -2,8 +2,10 @@ cmake_minimum_required(VERSION 3.7)
|
|||||||
|
|
||||||
project(strawberry)
|
project(strawberry)
|
||||||
|
|
||||||
cmake_policy(SET CMP0054 NEW)
|
if(POLICY CMP0054)
|
||||||
if (CMAKE_VERSION VERSION_GREATER_EQUAL 3.12)
|
cmake_policy(SET CMP0054 NEW)
|
||||||
|
endif()
|
||||||
|
if(POLICY CMP0074)
|
||||||
cmake_policy(SET CMP0074 NEW)
|
cmake_policy(SET CMP0074 NEW)
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
@@ -115,9 +117,14 @@ else()
|
|||||||
find_package(Iconv)
|
find_package(Iconv)
|
||||||
endif()
|
endif()
|
||||||
find_package(GnuTLS REQUIRED)
|
find_package(GnuTLS REQUIRED)
|
||||||
find_package(Protobuf REQUIRED)
|
if(NOT APPLE)
|
||||||
if(NOT Protobuf_PROTOC_EXECUTABLE)
|
find_package(Protobuf CONFIG)
|
||||||
message(FATAL_ERROR "Missing protobuf compiler.")
|
endif()
|
||||||
|
if(NOT Protobuf_FOUND)
|
||||||
|
find_package(Protobuf REQUIRED)
|
||||||
|
endif()
|
||||||
|
if(NOT TARGET protobuf::protoc)
|
||||||
|
message(FATAL_ERROR "Missing Protobuf compiler.")
|
||||||
endif()
|
endif()
|
||||||
if(LINUX)
|
if(LINUX)
|
||||||
find_package(ALSA REQUIRED)
|
find_package(ALSA REQUIRED)
|
||||||
@@ -177,7 +184,7 @@ if(DBUS_FOUND AND NOT WIN32)
|
|||||||
list(APPEND QT_COMPONENTS DBus)
|
list(APPEND QT_COMPONENTS DBus)
|
||||||
endif()
|
endif()
|
||||||
set(QT_OPTIONAL_COMPONENTS Test)
|
set(QT_OPTIONAL_COMPONENTS Test)
|
||||||
set(QT_MIN_VERSION 5.9)
|
set(QT_MIN_VERSION 5.12)
|
||||||
|
|
||||||
if(BUILD_WITH_QT6 OR QT_VERSION_MAJOR EQUAL 6)
|
if(BUILD_WITH_QT6 OR QT_VERSION_MAJOR EQUAL 6)
|
||||||
set(QT_VERSION_MAJOR 6 CACHE STRING "" FORCE)
|
set(QT_VERSION_MAJOR 6 CACHE STRING "" FORCE)
|
||||||
@@ -274,6 +281,24 @@ if(X11_FOUND)
|
|||||||
else()
|
else()
|
||||||
message(STATUS "Missing qpa/qplatformnativeinterface.h header.")
|
message(STATUS "Missing qpa/qplatformnativeinterface.h header.")
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
|
# Check for QX11Application (Qt 6 compiled with XCB).
|
||||||
|
if(BUILD_WITH_QT6 AND Qt6Gui_VERSION VERSION_GREATER_EQUAL 6.2.0)
|
||||||
|
set(CMAKE_REQUIRED_FLAGS "-std=c++17")
|
||||||
|
set(CMAKE_REQUIRED_LIBRARIES ${QtCore_LIBRARIES} ${QtGui_LIBRARIES})
|
||||||
|
check_cxx_source_compiles("
|
||||||
|
#include <QGuiApplication>
|
||||||
|
int main() {
|
||||||
|
(void)qApp->nativeInterface<QNativeInterface::QX11Application>();
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
"
|
||||||
|
HAVE_QX11APPLICATION
|
||||||
|
)
|
||||||
|
unset(CMAKE_REQUIRED_FLAGS)
|
||||||
|
unset(CMAKE_REQUIRED_LIBRARIES)
|
||||||
|
endif()
|
||||||
|
|
||||||
endif(X11_FOUND)
|
endif(X11_FOUND)
|
||||||
|
|
||||||
option(USE_TAGLIB "Build with TagLib" OFF)
|
option(USE_TAGLIB "Build with TagLib" OFF)
|
||||||
@@ -308,10 +333,10 @@ if(NOT TAGLIB_FOUND AND NOT TAGPARSER_FOUND)
|
|||||||
endif()
|
endif()
|
||||||
|
|
||||||
# SingleApplication
|
# SingleApplication
|
||||||
add_subdirectory(3rdparty/singleapplication)
|
add_subdirectory(3rdparty/kdsingleapplication)
|
||||||
set(SINGLEAPPLICATION_INCLUDE_DIRS ${CMAKE_CURRENT_SOURCE_DIR}/3rdparty/singleapplication)
|
set(SINGLEAPPLICATION_INCLUDE_DIRS ${CMAKE_CURRENT_SOURCE_DIR}/3rdparty/kdsingleapplication)
|
||||||
set(SINGLEAPPLICATION_LIBRARIES singleapplication)
|
set(SINGLEAPPLICATION_LIBRARIES kdsingleapplication)
|
||||||
set(SINGLECOREAPPLICATION_LIBRARIES singlecoreapplication)
|
add_definitions(-DKDSINGLEAPPLICATION_STATIC_BUILD)
|
||||||
|
|
||||||
if(APPLE)
|
if(APPLE)
|
||||||
add_subdirectory(3rdparty/SPMediaKeyTap)
|
add_subdirectory(3rdparty/SPMediaKeyTap)
|
||||||
@@ -332,10 +357,11 @@ if(WIN32)
|
|||||||
endif()
|
endif()
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
if(WIN32 AND MSVC)
|
if(WIN32)
|
||||||
add_subdirectory(3rdparty/getopt)
|
add_subdirectory(3rdparty/getopt)
|
||||||
set(GETOPT_INCLUDE_DIRS ${CMAKE_CURRENT_SOURCE_DIR}/3rdparty/getopt)
|
set(GETOPT_INCLUDE_DIRS ${CMAKE_CURRENT_SOURCE_DIR}/3rdparty/getopt)
|
||||||
set(GETOPT_LIBRARIES getopt)
|
set(GETOPT_LIBRARIES getopt)
|
||||||
|
add_definitions(-DSTATIC_GETOPT -D_UNICODE)
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
if(WIN32 AND NOT MSVC)
|
if(WIN32 AND NOT MSVC)
|
||||||
@@ -376,12 +402,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)
|
||||||
@@ -392,21 +418,17 @@ optional_component(GLOBALSHORTCUTS ON "Global shortcuts"
|
|||||||
DEPENDS "D-Bus, X11, Windows or macOS" HAVE_GLOBALSHORTCUTS_SUPPORT
|
DEPENDS "D-Bus, X11, Windows or macOS" HAVE_GLOBALSHORTCUTS_SUPPORT
|
||||||
)
|
)
|
||||||
|
|
||||||
if(BUILD_WITH_QT6 AND (Qt6Core_VERSION VERSION_EQUAL 6.2.0 OR Qt6Core_VERSION VERSION_GREATER 6.2.0))
|
if(HAVE_QX11APPLICATION OR HAVE_X11EXTRAS OR HAVE_QPA_QPLATFORMNATIVEINTERFACE_H)
|
||||||
optional_component(X11_GLOBALSHORTCUTS ON "X11 global shortcuts" DEPENDS "X11" X11_FOUND)
|
set(X11_GLOBALSHORTCUTS_REQUIREMENT_FOUND ON)
|
||||||
else()
|
|
||||||
if(HAVE_X11EXTRAS OR HAVE_QPA_QPLATFORMNATIVEINTERFACE_H)
|
|
||||||
set(HAVE_X11EXTRAS_OR_QPA_QPLATFORMNATIVEINTERFACE_H ON)
|
|
||||||
endif()
|
|
||||||
optional_component(X11_GLOBALSHORTCUTS ON "X11 global shortcuts"
|
|
||||||
DEPENDS "X11" X11_FOUND
|
|
||||||
DEPENDS "Qt >= 6.2, X11Extras or qpa/qplatformnativeinterface.h header" HAVE_X11EXTRAS_OR_QPA_QPLATFORMNATIVEINTERFACE_H
|
|
||||||
)
|
|
||||||
endif()
|
endif()
|
||||||
|
optional_component(X11_GLOBALSHORTCUTS ON "X11 global shortcuts"
|
||||||
|
DEPENDS "X11" X11_FOUND
|
||||||
|
DEPENDS "QX11Application, X11Extras or qpa/qplatformnativeinterface.h header" X11_GLOBALSHORTCUTS_REQUIREMENT_FOUND
|
||||||
|
)
|
||||||
|
|
||||||
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"
|
||||||
@@ -505,6 +527,8 @@ if(NOT CMAKE_CROSSCOMPILING)
|
|||||||
SQLITE_FTS5_TEST
|
SQLITE_FTS5_TEST
|
||||||
)
|
)
|
||||||
endif()
|
endif()
|
||||||
|
unset(CMAKE_REQUIRED_FLAGS)
|
||||||
|
unset(CMAKE_REQUIRED_LIBRARIES)
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
# Set up definitions
|
# Set up definitions
|
||||||
|
|||||||
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.
|
||||||
103
Changelog
103
Changelog
@@ -2,6 +2,109 @@ Strawberry Music Player
|
|||||||
=======================
|
=======================
|
||||||
ChangeLog
|
ChangeLog
|
||||||
|
|
||||||
|
Version 1.0.18 (2023.07.02):
|
||||||
|
|
||||||
|
Bugfixes:
|
||||||
|
* Fixed reading disc from QObuz songs (#1168).
|
||||||
|
* Fixed volume being reset on playback with PulseAudio (#1174).
|
||||||
|
* Fixed <br> tags in SQL query error message.
|
||||||
|
* Fixed compile with Qt 6 without XCB (QX11Application).
|
||||||
|
* Fixed smart playlist editor not properly loading search terms (#1172).
|
||||||
|
* Fixed use of fixed icon for playlist favorite star icon (#1178).
|
||||||
|
* Possible fix for collection thumbnails using disk cache having identical covers for albums with hashtag (#) in the album title (#1183).
|
||||||
|
* Fixed listenbrainz scrobbling for songs with multiple artist mbids.
|
||||||
|
* Fixed listenbrainz scrobbling for songs without duration.
|
||||||
|
* Fixed gapless playback sometimes not working.
|
||||||
|
* Fixed writing PNG images as embedded covers (#1209).
|
||||||
|
* Fixed greyscale album covers not working in OSD D-Bus (#1205).
|
||||||
|
* Fixed collection thumbnail disk cache with Qt 6.5.1 and newer.
|
||||||
|
* Fixed moodbar disk cache with Qt 6.5.1 and newer.
|
||||||
|
* Fixed playlist edit tag F2 shortcut only working for title tag (#1210).
|
||||||
|
* Append number to filename if the destination file already exist when transcoding audio (#1200).
|
||||||
|
* Fixed abseil linking issues with protobuf 1.22.0 and newer.
|
||||||
|
* (macOS) Fixed "Show this message" checkbox having no affect on Rosetta warning dialog (#1180).
|
||||||
|
* (macOS) Disable unused D-Bus.
|
||||||
|
* (Windows) Fixed command line options not working with diacritics (#1191).
|
||||||
|
* (Windows) Fixed issue with saving album covers in album directory being saved in temp directory instead.
|
||||||
|
* (Windows) Fixed crash when trying a play a song which doesn't exist, gstreamer issue #1683 (#1214).
|
||||||
|
|
||||||
|
Enhancements:
|
||||||
|
* Reduce memory overhead with album cover handling (#1046).
|
||||||
|
* Improved listenbrainz error handling.
|
||||||
|
* Show error dialog for listenbrainz errors similar to last.fm/libre.fm.
|
||||||
|
* Reduce NetworkAccessManager instances.
|
||||||
|
* Replace SingleApplication with KDSingleApplication.
|
||||||
|
* Require Qt 5.12 or higher.
|
||||||
|
* Add new database fields for art_embedded and art_unset.
|
||||||
|
* Rewrite album cover loader.
|
||||||
|
* Move cover filename settings from collection to covers settings.
|
||||||
|
* Add setting to set priorities for album cover types.
|
||||||
|
* Add rating filtering to playlist search (#1212).
|
||||||
|
* (Windows|MSVC) Add WSAPI2 plugin.
|
||||||
|
|
||||||
|
Version 1.0.17 (2023.03.29):
|
||||||
|
|
||||||
|
Bugfixes:
|
||||||
|
* Fixed over-sized context album cover with device pixel ratio higher than 1.0 (#1166).
|
||||||
|
* Fixed playing widget fading from a blurry previous cover with device pixel ratio higher than 1.0.
|
||||||
|
* Made playlist source icon, album cover manager and OSD pretty cover respect device pixel ratio.
|
||||||
|
|
||||||
|
Version 1.0.16 (2023.03.27):
|
||||||
|
|
||||||
|
Bugfixes:
|
||||||
|
* Fixed lyrics from Musixmatch.
|
||||||
|
* Fixed possible file corruption when saving both tags and embedded cover using the tag editor (#1158).
|
||||||
|
* Fixed compile without GStreamer.
|
||||||
|
* Fixed context and playing now album art rendering on High DPI displays (#1161).
|
||||||
|
* Fixed setting source properties (device, user-agent, ssl-strict) with GStreamer 1.22 (playbin3) and higher (#1148).
|
||||||
|
* Fixed rescan songs feature not ignoring mtime.
|
||||||
|
* Search lyrics by artist instead of album artist by default.
|
||||||
|
|
||||||
|
Code improvements:
|
||||||
|
* Replace use of deprecated QSqlDatabase::exec().
|
||||||
|
|
||||||
|
Added features:
|
||||||
|
* Added backend setting for strict SSL mode.
|
||||||
|
* Read AcoustID and MusicBrainz tags.
|
||||||
|
* Submit MusicBrainz tags with ListenBrainz.
|
||||||
|
|
||||||
|
Version 1.0.15 (2023.03.04):
|
||||||
|
|
||||||
|
Bugfixes:
|
||||||
|
* Fixed playlist column showing invalid last played date for streams.
|
||||||
|
* Fixed crash when the audio bin failed to initialize (#1123, #1133).
|
||||||
|
* Fixed duplicated filename when organizing files using dot in the filename (#1136).
|
||||||
|
* Fixed tag inline editing for streams (#1130).
|
||||||
|
* Fixed resetting play statistics using tag edit dialog (#1124).
|
||||||
|
* Fixed compilation songs not showing if group by was set to other than (Album) Artist / Album (#1140).
|
||||||
|
|
||||||
|
Enhancements:
|
||||||
|
* Added lyrics from stands4 (lyrics.com).
|
||||||
|
* Added Sonogram analyzer.
|
||||||
|
* Use GStreamer playbin3 with GStreamer 1.22.0 and higher.
|
||||||
|
|
||||||
|
Code improvements:
|
||||||
|
* Made use of C++11 enum class where possible.
|
||||||
|
* Use new QNativeIpcKey based QSharedMemory constructor with Qt 6.6 and higher.
|
||||||
|
|
||||||
|
Version 1.0.14 (2023.01.13):
|
||||||
|
|
||||||
|
Bugfixes:
|
||||||
|
* Fix initial volume not set when using Auto as output (#1104).
|
||||||
|
* Fix saving moodbar if the URL contains host, ie.: UNC paths for SMB (#1101).
|
||||||
|
* Fix CollectionBackendTest compile error (#1100).
|
||||||
|
* Remove explicitly enabling debug messages (#1106).
|
||||||
|
|
||||||
|
Version 1.0.13 (2023.01.09):
|
||||||
|
|
||||||
|
Bugfixes:
|
||||||
|
* Fixed volume synchronization leading to infinite loop resulting in crash when adjusting volume while playing (#1089).
|
||||||
|
* Fixed incorrect volume.
|
||||||
|
* Fixed collection organizing incorrectly handling slashes inside {} brackets for variables (#1091).
|
||||||
|
* Fixed saving relative playlists to non-existing playlist files (#1092).
|
||||||
|
* Fixed intermittent crash on collection model query (#1095).
|
||||||
|
* Require system icons for fancy tabbar and settings sidebar to be larger than 22x22 (#1084).
|
||||||
|
|
||||||
Version 1.0.12 (2023.01.02):
|
Version 1.0.12 (2023.01.02):
|
||||||
|
|
||||||
Bugfixes:
|
Bugfixes:
|
||||||
|
|||||||
@@ -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/), [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
|
||||||
@@ -73,7 +73,7 @@ To build Strawberry from source you need the following installed on your system
|
|||||||
* [pkg-config](https://www.freedesktop.org/wiki/Software/pkg-config/) or [pkgconf](https://github.com/pkgconf/pkgconf)
|
* [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 6 or Qt 5.9 or higher with components Core, Gui, Widgets, Concurrent, Network and Sql](https://www.qt.io/)
|
* [Qt 6 or Qt 5.12 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/)
|
||||||
@@ -118,4 +118,3 @@ To compile on Windows with Visual Studio 2019 or 2022, see https://github.com/st
|
|||||||
### :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 12)
|
set(STRAWBERRY_VERSION_PATCH 18)
|
||||||
#set(STRAWBERRY_VERSION_PRERELEASE rc1)
|
#set(STRAWBERRY_VERSION_PRERELEASE rc1)
|
||||||
|
|
||||||
set(INCLUDE_GIT_REVISION OFF)
|
set(INCLUDE_GIT_REVISION OFF)
|
||||||
|
|||||||
@@ -7,6 +7,8 @@
|
|||||||
<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/schema-17.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>
|
||||||
|
|||||||
@@ -59,15 +59,31 @@ CREATE TABLE device_%deviceid_songs (
|
|||||||
compilation_off INTEGER NOT NULL DEFAULT 0,
|
compilation_off INTEGER NOT NULL DEFAULT 0,
|
||||||
compilation_effective INTEGER NOT NULL DEFAULT 0,
|
compilation_effective INTEGER NOT NULL DEFAULT 0,
|
||||||
|
|
||||||
|
art_embedded INTEGER DEFAULT 0,
|
||||||
art_automatic TEXT,
|
art_automatic TEXT,
|
||||||
art_manual TEXT,
|
art_manual TEXT,
|
||||||
|
art_unset INTEGER DEFAULT 0,
|
||||||
|
|
||||||
effective_albumartist TEXT,
|
effective_albumartist TEXT,
|
||||||
effective_originalyear INTEGER NOT NULL DEFAULT 0,
|
effective_originalyear INTEGER NOT NULL DEFAULT 0,
|
||||||
|
|
||||||
cue_path TEXT,
|
cue_path TEXT,
|
||||||
|
|
||||||
rating INTEGER DEFAULT -1
|
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 +96,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;
|
||||||
45
data/schema/schema-17.sql
Normal file
45
data/schema/schema-17.sql
Normal file
@@ -0,0 +1,45 @@
|
|||||||
|
ALTER TABLE songs ADD COLUMN art_embedded INTEGER DEFAULT 0;
|
||||||
|
|
||||||
|
ALTER TABLE songs ADD COLUMN art_unset INTEGER DEFAULT 0;
|
||||||
|
|
||||||
|
ALTER TABLE subsonic_songs ADD COLUMN art_embedded INTEGER DEFAULT 0;
|
||||||
|
|
||||||
|
ALTER TABLE subsonic_songs ADD COLUMN art_unset INTEGER DEFAULT 0;
|
||||||
|
|
||||||
|
ALTER TABLE tidal_artists_songs ADD COLUMN art_embedded INTEGER DEFAULT 0;
|
||||||
|
|
||||||
|
ALTER TABLE tidal_artists_songs ADD COLUMN art_unset INTEGER DEFAULT 0;
|
||||||
|
|
||||||
|
ALTER TABLE tidal_albums_songs ADD COLUMN art_embedded INTEGER DEFAULT 0;
|
||||||
|
|
||||||
|
ALTER TABLE tidal_albums_songs ADD COLUMN art_unset INTEGER DEFAULT 0;
|
||||||
|
|
||||||
|
ALTER TABLE tidal_songs ADD COLUMN art_embedded INTEGER DEFAULT 0;
|
||||||
|
|
||||||
|
ALTER TABLE tidal_songs ADD COLUMN art_unset INTEGER DEFAULT 0;
|
||||||
|
|
||||||
|
ALTER TABLE qobuz_artists_songs ADD COLUMN art_embedded INTEGER DEFAULT 0;
|
||||||
|
|
||||||
|
ALTER TABLE qobuz_artists_songs ADD COLUMN art_unset INTEGER DEFAULT 0;
|
||||||
|
|
||||||
|
ALTER TABLE qobuz_albums_songs ADD COLUMN art_embedded INTEGER DEFAULT 0;
|
||||||
|
|
||||||
|
ALTER TABLE qobuz_albums_songs ADD COLUMN art_unset INTEGER DEFAULT 0;
|
||||||
|
|
||||||
|
ALTER TABLE qobuz_songs ADD COLUMN art_embedded INTEGER DEFAULT 0;
|
||||||
|
|
||||||
|
ALTER TABLE qobuz_songs ADD COLUMN art_unset INTEGER DEFAULT 0;
|
||||||
|
|
||||||
|
ALTER TABLE playlist_items ADD COLUMN art_embedded INTEGER DEFAULT 0;
|
||||||
|
|
||||||
|
ALTER TABLE playlist_items ADD COLUMN art_unset INTEGER DEFAULT 0;
|
||||||
|
|
||||||
|
UPDATE songs SET art_embedded = 1 WHERE art_automatic = 'file:(embedded)';
|
||||||
|
|
||||||
|
UPDATE songs SET art_automatic = '' WHERE art_automatic = 'file:(embedded)';
|
||||||
|
|
||||||
|
UPDATE songs SET art_unset = 1 WHERE art_manual = 'file:(unset)';
|
||||||
|
|
||||||
|
UPDATE songs SET art_manual = '' WHERE art_manual = 'file:(unset)';
|
||||||
|
|
||||||
|
UPDATE schema_version SET version=17;
|
||||||
@@ -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 (17);
|
||||||
|
|
||||||
CREATE TABLE IF NOT EXISTS directories (
|
CREATE TABLE IF NOT EXISTS directories (
|
||||||
path TEXT NOT NULL,
|
path TEXT NOT NULL,
|
||||||
@@ -67,15 +67,31 @@ CREATE TABLE IF NOT EXISTS songs (
|
|||||||
compilation_off INTEGER NOT NULL DEFAULT 0,
|
compilation_off INTEGER NOT NULL DEFAULT 0,
|
||||||
compilation_effective INTEGER NOT NULL DEFAULT 0,
|
compilation_effective INTEGER NOT NULL DEFAULT 0,
|
||||||
|
|
||||||
|
art_embedded INTEGER DEFAULT 0,
|
||||||
art_automatic TEXT,
|
art_automatic TEXT,
|
||||||
art_manual TEXT,
|
art_manual TEXT,
|
||||||
|
art_unset INTEGER DEFAULT 0,
|
||||||
|
|
||||||
effective_albumartist TEXT,
|
effective_albumartist TEXT,
|
||||||
effective_originalyear INTEGER NOT NULL DEFAULT 0,
|
effective_originalyear INTEGER NOT NULL DEFAULT 0,
|
||||||
|
|
||||||
cue_path TEXT,
|
cue_path TEXT,
|
||||||
|
|
||||||
rating INTEGER DEFAULT -1
|
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
|
||||||
|
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -129,15 +145,31 @@ CREATE TABLE IF NOT EXISTS subsonic_songs (
|
|||||||
compilation_off INTEGER NOT NULL DEFAULT 0,
|
compilation_off INTEGER NOT NULL DEFAULT 0,
|
||||||
compilation_effective INTEGER NOT NULL DEFAULT 0,
|
compilation_effective INTEGER NOT NULL DEFAULT 0,
|
||||||
|
|
||||||
|
art_embedded INTEGER DEFAULT 0,
|
||||||
art_automatic TEXT,
|
art_automatic TEXT,
|
||||||
art_manual TEXT,
|
art_manual TEXT,
|
||||||
|
art_unset INTEGER DEFAULT 0,
|
||||||
|
|
||||||
effective_albumartist TEXT,
|
effective_albumartist TEXT,
|
||||||
effective_originalyear INTEGER NOT NULL DEFAULT 0,
|
effective_originalyear INTEGER NOT NULL DEFAULT 0,
|
||||||
|
|
||||||
cue_path TEXT,
|
cue_path TEXT,
|
||||||
|
|
||||||
rating INTEGER DEFAULT -1
|
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
|
||||||
|
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -191,15 +223,31 @@ CREATE TABLE IF NOT EXISTS tidal_artists_songs (
|
|||||||
compilation_off INTEGER NOT NULL DEFAULT 0,
|
compilation_off INTEGER NOT NULL DEFAULT 0,
|
||||||
compilation_effective INTEGER NOT NULL DEFAULT 0,
|
compilation_effective INTEGER NOT NULL DEFAULT 0,
|
||||||
|
|
||||||
|
art_embedded INTEGER DEFAULT 0,
|
||||||
art_automatic TEXT,
|
art_automatic TEXT,
|
||||||
art_manual TEXT,
|
art_manual TEXT,
|
||||||
|
art_unset INTEGER DEFAULT 0,
|
||||||
|
|
||||||
effective_albumartist TEXT,
|
effective_albumartist TEXT,
|
||||||
effective_originalyear INTEGER NOT NULL DEFAULT 0,
|
effective_originalyear INTEGER NOT NULL DEFAULT 0,
|
||||||
|
|
||||||
cue_path TEXT,
|
cue_path TEXT,
|
||||||
|
|
||||||
rating INTEGER DEFAULT -1
|
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
|
||||||
|
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -253,15 +301,31 @@ CREATE TABLE IF NOT EXISTS tidal_albums_songs (
|
|||||||
compilation_off INTEGER NOT NULL DEFAULT 0,
|
compilation_off INTEGER NOT NULL DEFAULT 0,
|
||||||
compilation_effective INTEGER NOT NULL DEFAULT 0,
|
compilation_effective INTEGER NOT NULL DEFAULT 0,
|
||||||
|
|
||||||
|
art_embedded INTEGER DEFAULT 0,
|
||||||
art_automatic TEXT,
|
art_automatic TEXT,
|
||||||
art_manual TEXT,
|
art_manual TEXT,
|
||||||
|
art_unset INTEGER DEFAULT 0,
|
||||||
|
|
||||||
effective_albumartist TEXT,
|
effective_albumartist TEXT,
|
||||||
effective_originalyear INTEGER NOT NULL DEFAULT 0,
|
effective_originalyear INTEGER NOT NULL DEFAULT 0,
|
||||||
|
|
||||||
cue_path TEXT,
|
cue_path TEXT,
|
||||||
|
|
||||||
rating INTEGER DEFAULT -1
|
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
|
||||||
|
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -315,15 +379,31 @@ CREATE TABLE IF NOT EXISTS tidal_songs (
|
|||||||
compilation_off INTEGER NOT NULL DEFAULT 0,
|
compilation_off INTEGER NOT NULL DEFAULT 0,
|
||||||
compilation_effective INTEGER NOT NULL DEFAULT 0,
|
compilation_effective INTEGER NOT NULL DEFAULT 0,
|
||||||
|
|
||||||
|
art_embedded INTEGER DEFAULT 0,
|
||||||
art_automatic TEXT,
|
art_automatic TEXT,
|
||||||
art_manual TEXT,
|
art_manual TEXT,
|
||||||
|
art_unset INTEGER DEFAULT 0,
|
||||||
|
|
||||||
effective_albumartist TEXT,
|
effective_albumartist TEXT,
|
||||||
effective_originalyear INTEGER NOT NULL DEFAULT 0,
|
effective_originalyear INTEGER NOT NULL DEFAULT 0,
|
||||||
|
|
||||||
cue_path TEXT,
|
cue_path TEXT,
|
||||||
|
|
||||||
rating INTEGER DEFAULT -1
|
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
|
||||||
|
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -377,15 +457,31 @@ CREATE TABLE IF NOT EXISTS qobuz_artists_songs (
|
|||||||
compilation_off INTEGER NOT NULL DEFAULT 0,
|
compilation_off INTEGER NOT NULL DEFAULT 0,
|
||||||
compilation_effective INTEGER NOT NULL DEFAULT 0,
|
compilation_effective INTEGER NOT NULL DEFAULT 0,
|
||||||
|
|
||||||
|
art_embedded INTEGER DEFAULT 0,
|
||||||
art_automatic TEXT,
|
art_automatic TEXT,
|
||||||
art_manual TEXT,
|
art_manual TEXT,
|
||||||
|
art_unset INTEGER DEFAULT 0,
|
||||||
|
|
||||||
effective_albumartist TEXT,
|
effective_albumartist TEXT,
|
||||||
effective_originalyear INTEGER NOT NULL DEFAULT 0,
|
effective_originalyear INTEGER NOT NULL DEFAULT 0,
|
||||||
|
|
||||||
cue_path TEXT,
|
cue_path TEXT,
|
||||||
|
|
||||||
rating INTEGER DEFAULT -1
|
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
|
||||||
|
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -439,15 +535,31 @@ CREATE TABLE IF NOT EXISTS qobuz_albums_songs (
|
|||||||
compilation_off INTEGER NOT NULL DEFAULT 0,
|
compilation_off INTEGER NOT NULL DEFAULT 0,
|
||||||
compilation_effective INTEGER NOT NULL DEFAULT 0,
|
compilation_effective INTEGER NOT NULL DEFAULT 0,
|
||||||
|
|
||||||
|
art_embedded INTEGER DEFAULT 0,
|
||||||
art_automatic TEXT,
|
art_automatic TEXT,
|
||||||
art_manual TEXT,
|
art_manual TEXT,
|
||||||
|
art_unset INTEGER DEFAULT 0,
|
||||||
|
|
||||||
effective_albumartist TEXT,
|
effective_albumartist TEXT,
|
||||||
effective_originalyear INTEGER NOT NULL DEFAULT 0,
|
effective_originalyear INTEGER NOT NULL DEFAULT 0,
|
||||||
|
|
||||||
cue_path TEXT,
|
cue_path TEXT,
|
||||||
|
|
||||||
rating INTEGER DEFAULT -1
|
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
|
||||||
|
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -501,15 +613,31 @@ CREATE TABLE IF NOT EXISTS qobuz_songs (
|
|||||||
compilation_off INTEGER NOT NULL DEFAULT 0,
|
compilation_off INTEGER NOT NULL DEFAULT 0,
|
||||||
compilation_effective INTEGER NOT NULL DEFAULT 0,
|
compilation_effective INTEGER NOT NULL DEFAULT 0,
|
||||||
|
|
||||||
|
art_embedded INTEGER DEFAULT 0,
|
||||||
art_automatic TEXT,
|
art_automatic TEXT,
|
||||||
art_manual TEXT,
|
art_manual TEXT,
|
||||||
|
art_unset INTEGER DEFAULT 0,
|
||||||
|
|
||||||
effective_albumartist TEXT,
|
effective_albumartist TEXT,
|
||||||
effective_originalyear INTEGER NOT NULL DEFAULT 0,
|
effective_originalyear INTEGER NOT NULL DEFAULT 0,
|
||||||
|
|
||||||
cue_path TEXT,
|
cue_path TEXT,
|
||||||
|
|
||||||
rating INTEGER DEFAULT -1
|
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
|
||||||
|
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -583,15 +711,31 @@ CREATE TABLE IF NOT EXISTS playlist_items (
|
|||||||
compilation_off INTEGER DEFAULT 0,
|
compilation_off INTEGER DEFAULT 0,
|
||||||
compilation_effective INTEGER DEFAULT 0,
|
compilation_effective INTEGER DEFAULT 0,
|
||||||
|
|
||||||
|
art_embedded INTEGER DEFAULT 0,
|
||||||
art_automatic TEXT,
|
art_automatic TEXT,
|
||||||
art_manual TEXT,
|
art_manual TEXT,
|
||||||
|
art_unset INTEGER DEFAULT 0,
|
||||||
|
|
||||||
effective_albumartist TEXT,
|
effective_albumartist TEXT,
|
||||||
effective_originalyear INTEGER,
|
effective_originalyear INTEGER,
|
||||||
|
|
||||||
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
|
||||||
|
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|||||||
@@ -64,11 +64,3 @@ QToolButton::menu-button {
|
|||||||
width: 16px;
|
width: 16px;
|
||||||
border: none;
|
border: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
macos {
|
|
||||||
font-size: 11pt;
|
|
||||||
}
|
|
||||||
|
|
||||||
macos QMenu {
|
|
||||||
font-size: 13pt;
|
|
||||||
}
|
|
||||||
|
|||||||
3
debian/control.in
vendored
3
debian/control.in
vendored
@@ -3,6 +3,7 @@ Section: sound
|
|||||||
Priority: optional
|
Priority: optional
|
||||||
Maintainer: Jonas Kvinge <jonas@jkvinge.net>
|
Maintainer: Jonas Kvinge <jonas@jkvinge.net>
|
||||||
Build-Depends: debhelper (>= 11),
|
Build-Depends: debhelper (>= 11),
|
||||||
|
git,
|
||||||
make,
|
make,
|
||||||
cmake,
|
cmake,
|
||||||
gcc,
|
gcc,
|
||||||
@@ -52,7 +53,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, 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
|
||||||
|
|||||||
10
debian/copyright
vendored
10
debian/copyright
vendored
@@ -25,8 +25,6 @@ Files: src/core/main.h
|
|||||||
src/context/contextview.h
|
src/context/contextview.h
|
||||||
src/context/contextalbum.cpp
|
src/context/contextalbum.cpp
|
||||||
src/context/contextalbum.h
|
src/context/contextalbum.h
|
||||||
src/engine/enginetype.cpp
|
|
||||||
src/engine/enginetype.h
|
|
||||||
src/engine/alsadevicefinder.cpp
|
src/engine/alsadevicefinder.cpp
|
||||||
src/engine/alsadevicefinder.h
|
src/engine/alsadevicefinder.h
|
||||||
src/engine/alsapcmdevicefinder.cpp
|
src/engine/alsapcmdevicefinder.cpp
|
||||||
@@ -37,6 +35,8 @@ Files: src/core/main.h
|
|||||||
src/engine/devicefinder.h
|
src/engine/devicefinder.h
|
||||||
src/engine/enginedevice.cpp
|
src/engine/enginedevice.cpp
|
||||||
src/engine/enginedevice.h
|
src/engine/enginedevice.h
|
||||||
|
src/engine/enginemetadata.cpp
|
||||||
|
src/engine/enginemetadata.h
|
||||||
src/internet/internetservice.cpp
|
src/internet/internetservice.cpp
|
||||||
src/internet/internetservice.h
|
src/internet/internetservice.h
|
||||||
src/internet/internettabsview.cpp
|
src/internet/internettabsview.cpp
|
||||||
@@ -267,8 +267,8 @@ Copyright: 2010, Spotify AB
|
|||||||
2011, Joachim Bengtsson
|
2011, Joachim Bengtsson
|
||||||
License: BSD-3-clause
|
License: BSD-3-clause
|
||||||
|
|
||||||
Files: 3rdparty/singleapplication/*
|
Files: 3rdparty/kdsingleapplication/*
|
||||||
Copyright: 2015-2022, Itay Grudev
|
Copyright: 2019-2023 Klarälvdalens Datakonsult AB, a KDAB Group company <info@kdab.com>
|
||||||
License: MIT
|
License: MIT
|
||||||
|
|
||||||
|
|
||||||
@@ -376,7 +376,7 @@ License: Apache-2.0
|
|||||||
On Debian systems, the complete text of the Apache 2.0 license can be
|
On Debian systems, the complete text of the Apache 2.0 license can be
|
||||||
found in the file
|
found in the file
|
||||||
`/usr/share/common-licenses/Apache-2.0`.
|
`/usr/share/common-licenses/Apache-2.0`.
|
||||||
|
|
||||||
License: BSD-3-clause
|
License: BSD-3-clause
|
||||||
Copyright (c) The Regents of the University of California.
|
Copyright (c) The Regents of the University of California.
|
||||||
All rights reserved.
|
All rights reserved.
|
||||||
|
|||||||
2
dist/macos/Info.plist.in
vendored
2
dist/macos/Info.plist.in
vendored
@@ -33,7 +33,7 @@
|
|||||||
<key>LSApplicationCategoryType</key>
|
<key>LSApplicationCategoryType</key>
|
||||||
<string>public.app-category.music</string>
|
<string>public.app-category.music</string>
|
||||||
<key>LSMinimumSystemVersion</key>
|
<key>LSMinimumSystemVersion</key>
|
||||||
<string>10.13.4</string>
|
<string>11.0</string>
|
||||||
<key>SUFeedURL</key>
|
<key>SUFeedURL</key>
|
||||||
<string>https://www.strawberrymusicplayer.org/sparkle-macos</string>
|
<string>https://www.strawberrymusicplayer.org/sparkle-macos</string>
|
||||||
<key>SUPublicEDKey</key>
|
<key>SUPublicEDKey</key>
|
||||||
|
|||||||
@@ -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, 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>
|
||||||
@@ -50,6 +50,6 @@
|
|||||||
</screenshots>
|
</screenshots>
|
||||||
<update_contact>eclipseo@fedoraproject.org</update_contact>
|
<update_contact>eclipseo@fedoraproject.org</update_contact>
|
||||||
<releases>
|
<releases>
|
||||||
<release version="1.0.0" date="2021-10-16"/>
|
<release version="1.0.18" date="2023-07-02"/>
|
||||||
</releases>
|
</releases>
|
||||||
</component>
|
</component>
|
||||||
|
|||||||
@@ -3,7 +3,9 @@ Version=1.0
|
|||||||
Type=Application
|
Type=Application
|
||||||
Name=Strawberry
|
Name=Strawberry
|
||||||
GenericName=Strawberry Music Player
|
GenericName=Strawberry Music Player
|
||||||
|
GenericName[ru]=Музыкальный проигрыватель Strawberry
|
||||||
Comment=Plays music
|
Comment=Plays music
|
||||||
|
Comment[ru]=Прослушивание музыки
|
||||||
Exec=strawberry %U
|
Exec=strawberry %U
|
||||||
TryExec=strawberry
|
TryExec=strawberry
|
||||||
Icon=strawberry
|
Icon=strawberry
|
||||||
@@ -18,19 +20,24 @@ Actions=Play-Pause;Stop;StopAfterCurrent;Previous;Next;
|
|||||||
[Desktop Action Play-Pause]
|
[Desktop Action Play-Pause]
|
||||||
Name=Play/Pause
|
Name=Play/Pause
|
||||||
Exec=strawberry --play-pause
|
Exec=strawberry --play-pause
|
||||||
|
Name[ru]=Играть/пауза
|
||||||
|
|
||||||
[Desktop Action Stop]
|
[Desktop Action Stop]
|
||||||
Name=Stop
|
Name=Stop
|
||||||
Exec=strawberry --stop
|
Exec=strawberry --stop
|
||||||
|
Name[ru]=Стоп
|
||||||
|
|
||||||
[Desktop Action StopAfterCurrent]
|
[Desktop Action StopAfterCurrent]
|
||||||
Name=Stop after this track
|
Name=Stop after this track
|
||||||
Exec=strawberry --stop-after-current
|
Exec=strawberry --stop-after-current
|
||||||
|
Name[ru]=Стоп после этого трека
|
||||||
|
|
||||||
[Desktop Action Previous]
|
[Desktop Action Previous]
|
||||||
Name=Previous
|
Name=Previous
|
||||||
Exec=strawberry --previous
|
Exec=strawberry --previous
|
||||||
|
Name[ru]=Предыдущий
|
||||||
|
|
||||||
[Desktop Action Next]
|
[Desktop Action Next]
|
||||||
Name=Next
|
Name=Next
|
||||||
Exec=strawberry --next
|
Exec=strawberry --next
|
||||||
|
Name[ru]=Следующий
|
||||||
|
|||||||
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, 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, Genius, Musixmatch, ChartLyrics, lyrics.ovh and lololyrics.com
|
||||||
- Support for multiple backends
|
- Support for multiple backends
|
||||||
- Audio analyzer
|
- Audio analyzer
|
||||||
- Audio equalizer
|
- Audio equalizer
|
||||||
|
|||||||
248
dist/windows/strawberry.nsi.in
vendored
248
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,10 +252,10 @@ 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-12.dll"
|
File "libFLAC-12.dll"
|
||||||
File "libbrotlicommon.dll"
|
File "libbrotlicommon.dll"
|
||||||
File "libbrotlidec.dll"
|
File "libbrotlidec.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"
|
||||||
@@ -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"
|
||||||
@@ -388,12 +439,12 @@ Section "Strawberry" Strawberry
|
|||||||
File "gsttag-1.0-0.dll"
|
File "gsttag-1.0-0.dll"
|
||||||
File "gsturidownloader-1.0-0.dll"
|
File "gsturidownloader-1.0-0.dll"
|
||||||
File "gstvideo-1.0-0.dll"
|
File "gstvideo-1.0-0.dll"
|
||||||
|
File "gstwinrt-1.0-0.dll"
|
||||||
File "harfbuzz.dll"
|
File "harfbuzz.dll"
|
||||||
File "intl-8.dll"
|
File "intl-8.dll"
|
||||||
File "jpeg62.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 +466,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 +480,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"
|
||||||
@@ -437,15 +489,26 @@ Section "Strawberry" Strawberry
|
|||||||
File "zlibd.dll"
|
File "zlibd.dll"
|
||||||
!endif
|
!endif
|
||||||
|
|
||||||
|
; Used by libfftw3-3.dll because fftw is compiled with MinGW.
|
||||||
|
!ifdef arch_x86
|
||||||
|
File "libgcc_s_sjlj-1.dll"
|
||||||
|
File "libwinpthread-1.dll"
|
||||||
|
!endif
|
||||||
|
|
||||||
!endif ; MSVC
|
!endif ; MSVC
|
||||||
|
|
||||||
; Common files
|
; Common files
|
||||||
|
|
||||||
File "icudt72.dll"
|
File "icudt73.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 "icuin72d.dll"
|
File "icuin73d.dll"
|
||||||
File "icuuc72d.dll"
|
File "icuuc73d.dll"
|
||||||
File "Qt6Concurrentd.dll"
|
File "Qt6Concurrentd.dll"
|
||||||
File "Qt6Cored.dll"
|
File "Qt6Cored.dll"
|
||||||
File "Qt6Guid.dll"
|
File "Qt6Guid.dll"
|
||||||
@@ -453,8 +516,8 @@ Section "Strawberry" Strawberry
|
|||||||
File "Qt6Sqld.dll"
|
File "Qt6Sqld.dll"
|
||||||
File "Qt6Widgetsd.dll"
|
File "Qt6Widgetsd.dll"
|
||||||
!else
|
!else
|
||||||
File "icuin72.dll"
|
File "icuin73.dll"
|
||||||
File "icuuc72.dll"
|
File "icuuc73.dll"
|
||||||
File "Qt6Concurrent.dll"
|
File "Qt6Concurrent.dll"
|
||||||
File "Qt6Core.dll"
|
File "Qt6Core.dll"
|
||||||
File "Qt6Gui.dll"
|
File "Qt6Gui.dll"
|
||||||
@@ -677,6 +740,7 @@ Section "Gstreamer plugins" gstreamer-plugins
|
|||||||
File "/oname=gstvolume.dll" "gstreamer-plugins\gstvolume.dll"
|
File "/oname=gstvolume.dll" "gstreamer-plugins\gstvolume.dll"
|
||||||
File "/oname=gstvorbis.dll" "gstreamer-plugins\gstvorbis.dll"
|
File "/oname=gstvorbis.dll" "gstreamer-plugins\gstvorbis.dll"
|
||||||
File "/oname=gstwasapi.dll" "gstreamer-plugins\gstwasapi.dll"
|
File "/oname=gstwasapi.dll" "gstreamer-plugins\gstwasapi.dll"
|
||||||
|
File "/oname=gstwasapi2.dll" "gstreamer-plugins\gstwasapi2.dll"
|
||||||
File "/oname=gstwavenc.dll" "gstreamer-plugins\gstwavenc.dll"
|
File "/oname=gstwavenc.dll" "gstreamer-plugins\gstwavenc.dll"
|
||||||
File "/oname=gstwavpack.dll" "gstreamer-plugins\gstwavpack.dll"
|
File "/oname=gstwavpack.dll" "gstreamer-plugins\gstwavpack.dll"
|
||||||
File "/oname=gstwavparse.dll" "gstreamer-plugins\gstwavparse.dll"
|
File "/oname=gstwavparse.dll" "gstreamer-plugins\gstwavparse.dll"
|
||||||
@@ -743,10 +807,10 @@ 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-12.dll"
|
Delete "$INSTDIR\libFLAC-12.dll"
|
||||||
Delete "$INSTDIR\libbrotlicommon.dll"
|
Delete "$INSTDIR\libbrotlicommon.dll"
|
||||||
Delete "$INSTDIR\libbrotlidec.dll"
|
Delete "$INSTDIR\libbrotlidec.dll"
|
||||||
@@ -803,7 +867,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"
|
||||||
@@ -822,11 +885,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 +939,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"
|
||||||
@@ -890,12 +994,12 @@ Section "Uninstall"
|
|||||||
Delete "$INSTDIR\gsttag-1.0-0.dll"
|
Delete "$INSTDIR\gsttag-1.0-0.dll"
|
||||||
Delete "$INSTDIR\gsturidownloader-1.0-0.dll"
|
Delete "$INSTDIR\gsturidownloader-1.0-0.dll"
|
||||||
Delete "$INSTDIR\gstvideo-1.0-0.dll"
|
Delete "$INSTDIR\gstvideo-1.0-0.dll"
|
||||||
|
Delete "$INSTDIR\gstwinrt-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\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 +1021,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 +1035,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"
|
||||||
@@ -939,15 +1044,25 @@ Section "Uninstall"
|
|||||||
Delete "$INSTDIR\zlibd.dll"
|
Delete "$INSTDIR\zlibd.dll"
|
||||||
!endif
|
!endif
|
||||||
|
|
||||||
|
!ifdef arch_x86
|
||||||
|
Delete "$INSTDIR\libgcc_s_sjlj-1.dll"
|
||||||
|
Delete "$INSTDIR\libwinpthread-1.dll"
|
||||||
|
!endif
|
||||||
|
|
||||||
!endif ; MSVC
|
!endif ; MSVC
|
||||||
|
|
||||||
; Common files
|
; Common files
|
||||||
|
|
||||||
Delete "$INSTDIR\icudt72.dll"
|
Delete "$INSTDIR\icudt73.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\icuin72d.dll"
|
Delete "$INSTDIR\icuin73d.dll"
|
||||||
Delete "$INSTDIR\icuuc72d.dll"
|
Delete "$INSTDIR\icuuc73d.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 +1070,8 @@ Section "Uninstall"
|
|||||||
Delete "$INSTDIR\Qt6Sqld.dll"
|
Delete "$INSTDIR\Qt6Sqld.dll"
|
||||||
Delete "$INSTDIR\Qt6Widgetsd.dll"
|
Delete "$INSTDIR\Qt6Widgetsd.dll"
|
||||||
!else
|
!else
|
||||||
Delete "$INSTDIR\icuin72.dll"
|
Delete "$INSTDIR\icuin73.dll"
|
||||||
Delete "$INSTDIR\icuuc72.dll"
|
Delete "$INSTDIR\icuuc73.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"
|
||||||
@@ -1114,6 +1229,7 @@ Section "Uninstall"
|
|||||||
Delete "$INSTDIR\gstreamer-plugins\gstvolume.dll"
|
Delete "$INSTDIR\gstreamer-plugins\gstvolume.dll"
|
||||||
Delete "$INSTDIR\gstreamer-plugins\gstvorbis.dll"
|
Delete "$INSTDIR\gstreamer-plugins\gstvorbis.dll"
|
||||||
Delete "$INSTDIR\gstreamer-plugins\gstwasapi.dll"
|
Delete "$INSTDIR\gstreamer-plugins\gstwasapi.dll"
|
||||||
|
Delete "$INSTDIR\gstreamer-plugins\gstwasapi2.dll"
|
||||||
Delete "$INSTDIR\gstreamer-plugins\gstwavenc.dll"
|
Delete "$INSTDIR\gstreamer-plugins\gstwavenc.dll"
|
||||||
Delete "$INSTDIR\gstreamer-plugins\gstwavpack.dll"
|
Delete "$INSTDIR\gstreamer-plugins\gstwavpack.dll"
|
||||||
Delete "$INSTDIR\gstreamer-plugins\gstwavparse.dll"
|
Delete "$INSTDIR\gstreamer-plugins\gstwavparse.dll"
|
||||||
|
|||||||
@@ -50,10 +50,14 @@ enum {
|
|||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
#define gst_fastspectrum_parent_class parent_class
|
#define gst_fastspectrum_parent_class parent_class
|
||||||
|
#ifdef __GNUC__
|
||||||
#pragma GCC diagnostic push
|
#pragma GCC diagnostic push
|
||||||
#pragma GCC diagnostic ignored "-Wold-style-cast"
|
#pragma GCC diagnostic ignored "-Wold-style-cast"
|
||||||
|
#endif
|
||||||
G_DEFINE_TYPE(GstFastSpectrum, gst_fastspectrum, GST_TYPE_AUDIO_FILTER)
|
G_DEFINE_TYPE(GstFastSpectrum, gst_fastspectrum, GST_TYPE_AUDIO_FILTER)
|
||||||
|
#ifdef __GNUC__
|
||||||
#pragma GCC diagnostic pop
|
#pragma GCC diagnostic pop
|
||||||
|
#endif
|
||||||
|
|
||||||
static void gst_fastspectrum_finalize(GObject *object);
|
static void gst_fastspectrum_finalize(GObject *object);
|
||||||
static void gst_fastspectrum_set_property(GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec);
|
static void gst_fastspectrum_set_property(GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec);
|
||||||
@@ -82,9 +86,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");
|
||||||
|
|
||||||
|
|||||||
@@ -37,9 +37,7 @@
|
|||||||
#include <QString>
|
#include <QString>
|
||||||
#include <QStringList>
|
#include <QStringList>
|
||||||
#include <QAtomicInt>
|
#include <QAtomicInt>
|
||||||
#if QT_VERSION >= QT_VERSION_CHECK(5, 10, 0)
|
#include <QRandomGenerator>
|
||||||
# include <QRandomGenerator>
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#include "core/logging.h"
|
#include "core/logging.h"
|
||||||
|
|
||||||
@@ -165,10 +163,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()) {
|
||||||
@@ -228,7 +226,7 @@ void WorkerPool<HandlerType>::SetExecutableName(const QString &executable_name)
|
|||||||
|
|
||||||
template<typename HandlerType>
|
template<typename HandlerType>
|
||||||
void WorkerPool<HandlerType>::Start() {
|
void WorkerPool<HandlerType>::Start() {
|
||||||
QMetaObject::invokeMethod(this, "DoStart");
|
QMetaObject::invokeMethod(this, &WorkerPool<HandlerType>::DoStart);
|
||||||
}
|
}
|
||||||
|
|
||||||
template<typename HandlerType>
|
template<typename HandlerType>
|
||||||
@@ -294,11 +292,7 @@ void WorkerPool<HandlerType>::StartOneWorker(Worker *worker) {
|
|||||||
|
|
||||||
// Create a server, find an unused name and start listening
|
// Create a server, find an unused name and start listening
|
||||||
forever {
|
forever {
|
||||||
#if QT_VERSION >= QT_VERSION_CHECK(5, 10, 0)
|
|
||||||
const quint32 unique_number = QRandomGenerator::global()->bounded(static_cast<quint32>(quint64(this) & 0xFFFFFFFF));
|
const quint32 unique_number = QRandomGenerator::global()->bounded(static_cast<quint32>(quint64(this) & 0xFFFFFFFF));
|
||||||
#else
|
|
||||||
const quint32 unique_number = qrand() ^ (static_cast<quint32>(quint64(this) & 0xFFFFFFFF));
|
|
||||||
#endif
|
|
||||||
const QString name = QString("%1_%2").arg(local_server_name_).arg(unique_number);
|
const QString name = QString("%1_%2").arg(local_server_name_).arg(unique_number);
|
||||||
|
|
||||||
if (worker->local_server_->listen(name)) {
|
if (worker->local_server_->listen(name)) {
|
||||||
@@ -423,7 +417,7 @@ WorkerPool<HandlerType>::SendMessageWithReply(MessageType *message) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Wake up the main thread
|
// Wake up the main thread
|
||||||
QMetaObject::invokeMethod(this, "SendQueuedMessages", Qt::QueuedConnection);
|
QMetaObject::invokeMethod(this, &WorkerPool<HandlerType>::SendQueuedMessages, Qt::QueuedConnection);
|
||||||
|
|
||||||
return reply;
|
return reply;
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,15 @@
|
|||||||
cmake_minimum_required(VERSION 3.7)
|
cmake_minimum_required(VERSION 3.7)
|
||||||
|
|
||||||
set(MESSAGES tagreadermessages.proto)
|
# Workaround a bug in protobuf-generate.cmake (https://github.com/protocolbuffers/protobuf/issues/12450)
|
||||||
set(SOURCES tagreaderbase.cpp)
|
if(NOT protobuf_PROTOC_EXE)
|
||||||
|
set(protobuf_PROTOC_EXE "protobuf::protoc")
|
||||||
|
endif()
|
||||||
|
|
||||||
|
if(NOT Protobuf_LIBRARIES)
|
||||||
|
set(Protobuf_LIBRARIES protobuf::libprotobuf protobuf::libprotoc)
|
||||||
|
endif()
|
||||||
|
|
||||||
|
set(SOURCES tagreaderbase.cpp tagreadermessages.proto)
|
||||||
|
|
||||||
if(USE_TAGLIB AND TAGLIB_FOUND)
|
if(USE_TAGLIB AND TAGLIB_FOUND)
|
||||||
list(APPEND SOURCES tagreadertaglib.cpp tagreadergme.cpp)
|
list(APPEND SOURCES tagreadertaglib.cpp tagreadergme.cpp)
|
||||||
@@ -11,8 +19,6 @@ if(USE_TAGPARSER AND TAGPARSER_FOUND)
|
|||||||
list(APPEND SOURCES tagreadertagparser.cpp)
|
list(APPEND SOURCES tagreadertagparser.cpp)
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
protobuf_generate_cpp(PROTO_SOURCES PROTO_HEADERS ${MESSAGES})
|
|
||||||
|
|
||||||
link_directories(
|
link_directories(
|
||||||
${GLIB_LIBRARY_DIRS}
|
${GLIB_LIBRARY_DIRS}
|
||||||
${PROTOBUF_LIBRARY_DIRS}
|
${PROTOBUF_LIBRARY_DIRS}
|
||||||
@@ -43,9 +49,10 @@ 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
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -58,3 +65,5 @@ if(USE_TAGPARSER AND TAGPARSER_FOUND)
|
|||||||
target_include_directories(libstrawberry-tagreader SYSTEM PRIVATE ${TAGPARSER_INCLUDE_DIRS})
|
target_include_directories(libstrawberry-tagreader SYSTEM PRIVATE ${TAGPARSER_INCLUDE_DIRS})
|
||||||
target_link_libraries(libstrawberry-tagreader PRIVATE ${TAGPARSER_LIBRARIES})
|
target_link_libraries(libstrawberry-tagreader PRIVATE ${TAGPARSER_LIBRARIES})
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
|
protobuf_generate(TARGET libstrawberry-tagreader)
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
/* This file is part of Strawberry.
|
/* This file is part of Strawberry.
|
||||||
Copyright 2018-2021, Jonas Kvinge <jonas@jkvinge.net>
|
Copyright 2018-2023, Jonas Kvinge <jonas@jkvinge.net>
|
||||||
|
|
||||||
Strawberry is free software: you can redistribute it and/or modify
|
Strawberry is free software: you can redistribute it and/or modify
|
||||||
it under the terms of the GNU General Public License as published by
|
it under the terms of the GNU General Public License as published by
|
||||||
@@ -19,19 +19,20 @@
|
|||||||
|
|
||||||
#include <string>
|
#include <string>
|
||||||
|
|
||||||
#include "tagreaderbase.h"
|
#include <QByteArray>
|
||||||
|
#include <QString>
|
||||||
|
#include <QIODevice>
|
||||||
|
#include <QFile>
|
||||||
|
#include <QBuffer>
|
||||||
|
#include <QImage>
|
||||||
|
#include <QMimeDatabase>
|
||||||
|
|
||||||
const std::string TagReaderBase::kEmbeddedCover = "(embedded)";
|
#include "core/logging.h"
|
||||||
|
#include "tagreaderbase.h"
|
||||||
|
|
||||||
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 +56,88 @@ int TagReaderBase::ConvertToPOPMRating(const float rating) {
|
|||||||
return 0xFF;
|
return 0xFF;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TagReaderBase::Cover TagReaderBase::LoadCoverFromRequest(const spb::tagreader::SaveFileRequest &request) {
|
||||||
|
|
||||||
|
if (!request.has_save_cover() || !request.save_cover()) {
|
||||||
|
return Cover();
|
||||||
|
}
|
||||||
|
|
||||||
|
const QString song_filename = QString::fromUtf8(request.filename().data(), static_cast<qint64>(request.filename().size()));
|
||||||
|
QString cover_filename;
|
||||||
|
if (request.has_cover_filename()) {
|
||||||
|
cover_filename = QString::fromUtf8(request.cover_filename().data(), static_cast<qint64>(request.cover_filename().size()));
|
||||||
|
}
|
||||||
|
QByteArray cover_data;
|
||||||
|
if (request.has_cover_data()) {
|
||||||
|
cover_data = QByteArray(request.cover_data().data(), static_cast<qint64>(request.cover_data().size()));
|
||||||
|
}
|
||||||
|
QString cover_mime_type;
|
||||||
|
if (request.has_cover_mime_type()) {
|
||||||
|
cover_mime_type = QByteArray(request.cover_mime_type().data(), static_cast<qint64>(request.cover_mime_type().size()));
|
||||||
|
}
|
||||||
|
|
||||||
|
return LoadCoverFromRequest(song_filename, cover_filename, cover_data, cover_mime_type);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
TagReaderBase::Cover TagReaderBase::LoadCoverFromRequest(const spb::tagreader::SaveEmbeddedArtRequest &request) {
|
||||||
|
|
||||||
|
const QString song_filename = QString::fromUtf8(request.filename().data(), static_cast<qint64>(request.filename().size()));
|
||||||
|
QString cover_filename;
|
||||||
|
if (request.has_cover_filename()) {
|
||||||
|
cover_filename = QString::fromUtf8(request.cover_filename().data(), static_cast<qint64>(request.cover_filename().size()));
|
||||||
|
}
|
||||||
|
QByteArray cover_data;
|
||||||
|
if (request.has_cover_data()) {
|
||||||
|
cover_data = QByteArray(request.cover_data().data(), static_cast<qint64>(request.cover_data().size()));
|
||||||
|
}
|
||||||
|
QString cover_mime_type;
|
||||||
|
if (request.has_cover_mime_type()) {
|
||||||
|
cover_mime_type = QByteArray(request.cover_mime_type().data(), static_cast<qint64>(request.cover_mime_type().size()));
|
||||||
|
}
|
||||||
|
|
||||||
|
return LoadCoverFromRequest(song_filename, cover_filename, cover_data, cover_mime_type);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
TagReaderBase::Cover TagReaderBase::LoadCoverFromRequest(const QString &song_filename, const QString &cover_filename, QByteArray cover_data, QString cover_mime_type) {
|
||||||
|
|
||||||
|
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 Cover();
|
||||||
|
}
|
||||||
|
cover_data = file.readAll();
|
||||||
|
file.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!cover_data.isEmpty()) {
|
||||||
|
if (cover_mime_type.isEmpty()) {
|
||||||
|
cover_mime_type = QMimeDatabase().mimeTypeForData(cover_data).name();
|
||||||
|
}
|
||||||
|
if (cover_mime_type == "image/jpeg") {
|
||||||
|
qLog(Debug) << "Using cover from JPEG data for" << song_filename;
|
||||||
|
return Cover(cover_data, cover_mime_type);
|
||||||
|
}
|
||||||
|
if (cover_mime_type == "image/png") {
|
||||||
|
qLog(Debug) << "Using cover from PNG data for" << song_filename;
|
||||||
|
return Cover(cover_data, cover_mime_type);
|
||||||
|
}
|
||||||
|
// 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(cover_data, "image/jpeg");
|
||||||
|
}
|
||||||
|
|
||||||
|
return Cover();
|
||||||
|
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
/* This file is part of Strawberry.
|
/* This file is part of Strawberry.
|
||||||
Copyright 2018-2021, Jonas Kvinge <jonas@jkvinge.net>
|
Copyright 2018-2023, Jonas Kvinge <jonas@jkvinge.net>
|
||||||
|
|
||||||
Strawberry is free software: you can redistribute it and/or modify
|
Strawberry is free software: you can redistribute it and/or modify
|
||||||
it under the terms of the GNU General Public License as published by
|
it under the terms of the GNU General Public License as published by
|
||||||
@@ -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)
|
||||||
@@ -39,24 +36,33 @@ class TagReaderBase {
|
|||||||
explicit TagReaderBase();
|
explicit TagReaderBase();
|
||||||
~TagReaderBase();
|
~TagReaderBase();
|
||||||
|
|
||||||
|
class Cover {
|
||||||
|
public:
|
||||||
|
explicit Cover(const QByteArray &_data = QByteArray(), const QString &_mime_type = QString()) : data(_data), mime_type(_mime_type) {}
|
||||||
|
QByteArray data;
|
||||||
|
QString mime_type;
|
||||||
|
QString error;
|
||||||
|
};
|
||||||
|
|
||||||
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);
|
||||||
|
|
||||||
protected:
|
static Cover LoadCoverFromRequest(const spb::tagreader::SaveFileRequest &request);
|
||||||
static const std::string kEmbeddedCover;
|
static Cover LoadCoverFromRequest(const spb::tagreader::SaveEmbeddedArtRequest &request);
|
||||||
|
|
||||||
|
private:
|
||||||
|
static Cover LoadCoverFromRequest(const QString &song_filename, const QString &cover_filename, QByteArray cover_data, QString cover_mime_type);
|
||||||
|
|
||||||
Q_DISABLE_COPY(TagReaderBase)
|
Q_DISABLE_COPY(TagReaderBase)
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -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());
|
||||||
}
|
}
|
||||||
@@ -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
|
||||||
|
|||||||
@@ -69,29 +69,26 @@ message SongMetadata {
|
|||||||
optional int64 lastplayed = 29;
|
optional int64 lastplayed = 29;
|
||||||
optional int64 lastseen = 30;
|
optional int64 lastseen = 30;
|
||||||
|
|
||||||
optional string art_automatic = 31;
|
optional bool art_embedded = 31;
|
||||||
|
|
||||||
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)
|
||||||
|
|
||||||
message ReadFileRequest {
|
optional bool suspicious_tags = 50;
|
||||||
optional string filename = 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
message ReadFileResponse {
|
|
||||||
optional SongMetadata metadata = 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
message SaveFileRequest {
|
|
||||||
optional string filename = 1;
|
|
||||||
optional SongMetadata metadata = 2;
|
|
||||||
}
|
|
||||||
|
|
||||||
message SaveFileResponse {
|
|
||||||
optional bool success = 1;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
message IsMediaFileRequest {
|
message IsMediaFileRequest {
|
||||||
@@ -100,6 +97,33 @@ message IsMediaFileRequest {
|
|||||||
|
|
||||||
message IsMediaFileResponse {
|
message IsMediaFileResponse {
|
||||||
optional bool success = 1;
|
optional bool success = 1;
|
||||||
|
optional string error = 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
message ReadFileRequest {
|
||||||
|
optional string filename = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
message ReadFileResponse {
|
||||||
|
optional SongMetadata metadata = 1;
|
||||||
|
optional string error = 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
message SaveFileRequest {
|
||||||
|
optional string filename = 1;
|
||||||
|
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 string cover_mime_type = 9;
|
||||||
|
}
|
||||||
|
|
||||||
|
message SaveFileResponse {
|
||||||
|
optional bool success = 1;
|
||||||
|
optional string error = 2;
|
||||||
}
|
}
|
||||||
|
|
||||||
message LoadEmbeddedArtRequest {
|
message LoadEmbeddedArtRequest {
|
||||||
@@ -108,15 +132,19 @@ message LoadEmbeddedArtRequest {
|
|||||||
|
|
||||||
message LoadEmbeddedArtResponse {
|
message LoadEmbeddedArtResponse {
|
||||||
optional bytes data = 1;
|
optional bytes data = 1;
|
||||||
|
optional string error = 2;
|
||||||
}
|
}
|
||||||
|
|
||||||
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 string cover_mime_type = 4;
|
||||||
}
|
}
|
||||||
|
|
||||||
message SaveEmbeddedArtResponse {
|
message SaveEmbeddedArtResponse {
|
||||||
optional bool success = 1;
|
optional bool success = 1;
|
||||||
|
optional string error = 2;
|
||||||
}
|
}
|
||||||
|
|
||||||
message SaveSongPlaycountToFileRequest {
|
message SaveSongPlaycountToFileRequest {
|
||||||
@@ -126,6 +154,7 @@ message SaveSongPlaycountToFileRequest {
|
|||||||
|
|
||||||
message SaveSongPlaycountToFileResponse {
|
message SaveSongPlaycountToFileResponse {
|
||||||
optional bool success = 1;
|
optional bool success = 1;
|
||||||
|
optional string error = 2;
|
||||||
}
|
}
|
||||||
|
|
||||||
message SaveSongRatingToFileRequest {
|
message SaveSongRatingToFileRequest {
|
||||||
@@ -135,6 +164,7 @@ message SaveSongRatingToFileRequest {
|
|||||||
|
|
||||||
message SaveSongRatingToFileResponse {
|
message SaveSongRatingToFileResponse {
|
||||||
optional bool success = 1;
|
optional bool success = 1;
|
||||||
|
optional string error = 2;
|
||||||
}
|
}
|
||||||
|
|
||||||
message Message {
|
message Message {
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -1,6 +1,6 @@
|
|||||||
/* This file is part of Strawberry.
|
/* This file is part of Strawberry.
|
||||||
Copyright 2013, David Sansome <me@davidsansome.com>
|
Copyright 2013, David Sansome <me@davidsansome.com>
|
||||||
Copyright 2018-2021, Jonas Kvinge <jonas@jkvinge.net>
|
Copyright 2018-2023, Jonas Kvinge <jonas@jkvinge.net>
|
||||||
|
|
||||||
Strawberry is free software: you can redistribute it and/or modify
|
Strawberry is free software: you can redistribute it and/or modify
|
||||||
it under the terms of the GNU General Public License as published by
|
it under the terms of the GNU General Public License as published by
|
||||||
@@ -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 QString &mime_type) const;
|
||||||
|
void SetEmbeddedArt(TagLib::Ogg::XiphComment *xiph_comment, const QByteArray &data, const QString &mime_type) const;
|
||||||
|
void SetEmbeddedArt(TagLib::MPEG::File *file_mp3, TagLib::ID3v2::Tag *tag, const QByteArray &data, const QString &mime_type) const;
|
||||||
|
void SetEmbeddedArt(TagLib::MP4::File *aac_file, TagLib::MP4::Tag *tag, const QByteArray &data, const QString &mime_type) 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,16 +103,14 @@ 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);
|
||||||
#if (QT_VERSION >= QT_VERSION_CHECK(5, 10, 0))
|
|
||||||
song->set_ctime(fileinfo.birthTime().isValid() ? std::max(fileinfo.birthTime().toSecsSinceEpoch(), 0LL) : fileinfo.lastModified().isValid() ? std::max(fileinfo.lastModified().toSecsSinceEpoch(), 0LL) : 0LL);
|
song->set_ctime(fileinfo.birthTime().isValid() ? std::max(fileinfo.birthTime().toSecsSinceEpoch(), 0LL) : fileinfo.lastModified().isValid() ? std::max(fileinfo.lastModified().toSecsSinceEpoch(), 0LL) : 0LL);
|
||||||
#else
|
|
||||||
song->set_ctime(fileinfo.created().isValid() ? std::max(fileinfo.created().toSecsSinceEpoch(), 0LL) : fileinfo.lastModified().isValid() ? std::max(fileinfo.lastModified().toSecsSinceEpoch(), 0LL) : 0LL);
|
|
||||||
#endif
|
|
||||||
|
|
||||||
if (song->ctime() <= 0) {
|
if (song->ctime() <= 0) {
|
||||||
song->set_ctime(song->mtime());
|
song->set_ctime(song->mtime());
|
||||||
@@ -154,8 +153,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 +208,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));
|
||||||
@@ -225,7 +224,7 @@ bool TagReaderTagParser::ReadFile(const QString &filename, spb::tagreader::SongM
|
|||||||
song->set_track(tag->value(TagParser::KnownField::TrackPosition).toInteger());
|
song->set_track(tag->value(TagParser::KnownField::TrackPosition).toInteger());
|
||||||
song->set_disc(tag->value(TagParser::KnownField::DiskPosition).toInteger());
|
song->set_disc(tag->value(TagParser::KnownField::DiskPosition).toInteger());
|
||||||
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) {
|
||||||
song->set_art_automatic(kEmbeddedCover);
|
song->set_art_embedded(true);
|
||||||
}
|
}
|
||||||
const float rating = ConvertPOPMRating(tag->value(TagParser::KnownField::Rating));
|
const float rating = ConvertPOPMRating(tag->value(TagParser::KnownField::Rating));
|
||||||
if (song->rating() <= 0 && rating > 0.0 && rating <= 1.0) {
|
if (song->rating() <= 0 && rating > 0.0 && rating <= 1.0) {
|
||||||
@@ -256,11 +255,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 +317,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 +392,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 +413,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 +459,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 +479,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 +528,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)
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -20,9 +20,6 @@
|
|||||||
|
|
||||||
#include <QtGlobal>
|
#include <QtGlobal>
|
||||||
|
|
||||||
#if QT_VERSION < QT_VERSION_CHECK(5, 10, 0)
|
|
||||||
# include <sys/time.h>
|
|
||||||
#endif
|
|
||||||
#include <iostream>
|
#include <iostream>
|
||||||
|
|
||||||
#include <QCoreApplication>
|
#include <QCoreApplication>
|
||||||
@@ -46,13 +43,6 @@ int main(int argc, char **argv) {
|
|||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Seed random number generator
|
|
||||||
#if QT_VERSION < QT_VERSION_CHECK(5, 10, 0)
|
|
||||||
timeval time;
|
|
||||||
gettimeofday(&time, nullptr);
|
|
||||||
qsrand((time.tv_sec * 1000) + (time.tv_usec / 1000));
|
|
||||||
#endif
|
|
||||||
|
|
||||||
logging::Init();
|
logging::Init();
|
||||||
qLog(Info) << "TagReader worker connecting to" << args[1];
|
qLog(Info) << "TagReader worker connecting to" << args[1];
|
||||||
|
|
||||||
@@ -67,4 +57,5 @@ int main(int argc, char **argv) {
|
|||||||
TagReaderWorker worker(&socket);
|
TagReaderWorker worker(&socket);
|
||||||
|
|
||||||
return a.exec();
|
return a.exec();
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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(), static_cast<qint64>(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(), static_cast<qint64>(static_cast<qint64>(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(), static_cast<qint64>(static_cast<qint64>(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(), static_cast<qint64>(static_cast<qint64>(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(), static_cast<qint64>(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;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -55,11 +55,14 @@ set(SOURCES
|
|||||||
utilities/transliterate.cpp
|
utilities/transliterate.cpp
|
||||||
utilities/xmlutils.cpp
|
utilities/xmlutils.cpp
|
||||||
utilities/filemanagerutils.cpp
|
utilities/filemanagerutils.cpp
|
||||||
|
utilities/coverutils.cpp
|
||||||
|
utilities/screenutils.cpp
|
||||||
|
|
||||||
engine/enginetype.cpp
|
|
||||||
engine/enginebase.cpp
|
engine/enginebase.cpp
|
||||||
|
engine/enginedevice.cpp
|
||||||
engine/devicefinders.cpp
|
engine/devicefinders.cpp
|
||||||
engine/devicefinder.cpp
|
engine/devicefinder.cpp
|
||||||
|
engine/enginemetadata.cpp
|
||||||
|
|
||||||
analyzer/fht.cpp
|
analyzer/fht.cpp
|
||||||
analyzer/analyzerbase.cpp
|
analyzer/analyzerbase.cpp
|
||||||
@@ -67,6 +70,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
|
||||||
@@ -82,9 +86,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
|
||||||
@@ -141,6 +147,7 @@ set(SOURCES
|
|||||||
covermanager/albumcovermanager.cpp
|
covermanager/albumcovermanager.cpp
|
||||||
covermanager/albumcovermanagerlist.cpp
|
covermanager/albumcovermanagerlist.cpp
|
||||||
covermanager/albumcoverloader.cpp
|
covermanager/albumcoverloader.cpp
|
||||||
|
covermanager/albumcoverloaderoptions.cpp
|
||||||
covermanager/albumcoverfetcher.cpp
|
covermanager/albumcoverfetcher.cpp
|
||||||
covermanager/albumcoverfetchersearch.cpp
|
covermanager/albumcoverfetchersearch.cpp
|
||||||
covermanager/albumcoversearcher.cpp
|
covermanager/albumcoversearcher.cpp
|
||||||
@@ -165,15 +172,17 @@ 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
|
||||||
lyrics/auddlyricsprovider.cpp
|
|
||||||
lyrics/ovhlyricsprovider.cpp
|
lyrics/ovhlyricsprovider.cpp
|
||||||
lyrics/lololyricsprovider.cpp
|
lyrics/lololyricsprovider.cpp
|
||||||
lyrics/geniuslyricsprovider.cpp
|
lyrics/geniuslyricsprovider.cpp
|
||||||
lyrics/musixmatchlyricsprovider.cpp
|
lyrics/musixmatchlyricsprovider.cpp
|
||||||
lyrics/chartlyricsprovider.cpp
|
lyrics/chartlyricsprovider.cpp
|
||||||
|
lyrics/lyricscomlyricsprovider.cpp
|
||||||
|
|
||||||
providers/musixmatchprovider.cpp
|
providers/musixmatchprovider.cpp
|
||||||
|
|
||||||
@@ -200,6 +209,7 @@ set(SOURCES
|
|||||||
dialogs/userpassdialog.cpp
|
dialogs/userpassdialog.cpp
|
||||||
dialogs/deleteconfirmationdialog.cpp
|
dialogs/deleteconfirmationdialog.cpp
|
||||||
dialogs/lastfmimportdialog.cpp
|
dialogs/lastfmimportdialog.cpp
|
||||||
|
dialogs/messagedialog.cpp
|
||||||
dialogs/snapdialog.cpp
|
dialogs/snapdialog.cpp
|
||||||
dialogs/saveplaylistsdialog.cpp
|
dialogs/saveplaylistsdialog.cpp
|
||||||
|
|
||||||
@@ -259,10 +269,10 @@ set(SOURCES
|
|||||||
radios/radioparadiseservice.cpp
|
radios/radioparadiseservice.cpp
|
||||||
|
|
||||||
scrobbler/audioscrobbler.cpp
|
scrobbler/audioscrobbler.cpp
|
||||||
scrobbler/scrobblerservices.cpp
|
|
||||||
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
|
||||||
@@ -308,6 +318,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
|
||||||
@@ -403,12 +414,12 @@ set(HEADERS
|
|||||||
lyrics/lyricsfetcher.h
|
lyrics/lyricsfetcher.h
|
||||||
lyrics/lyricsfetchersearch.h
|
lyrics/lyricsfetchersearch.h
|
||||||
lyrics/jsonlyricsprovider.h
|
lyrics/jsonlyricsprovider.h
|
||||||
lyrics/auddlyricsprovider.h
|
|
||||||
lyrics/ovhlyricsprovider.h
|
lyrics/ovhlyricsprovider.h
|
||||||
lyrics/lololyricsprovider.h
|
lyrics/lololyricsprovider.h
|
||||||
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
|
||||||
@@ -433,6 +444,7 @@ set(HEADERS
|
|||||||
dialogs/userpassdialog.h
|
dialogs/userpassdialog.h
|
||||||
dialogs/deleteconfirmationdialog.h
|
dialogs/deleteconfirmationdialog.h
|
||||||
dialogs/lastfmimportdialog.h
|
dialogs/lastfmimportdialog.h
|
||||||
|
dialogs/messagedialog.h
|
||||||
dialogs/snapdialog.h
|
dialogs/snapdialog.h
|
||||||
dialogs/saveplaylistsdialog.h
|
dialogs/saveplaylistsdialog.h
|
||||||
|
|
||||||
@@ -490,10 +502,8 @@ set(HEADERS
|
|||||||
radios/radioparadiseservice.h
|
radios/radioparadiseservice.h
|
||||||
|
|
||||||
scrobbler/audioscrobbler.h
|
scrobbler/audioscrobbler.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
|
||||||
@@ -560,7 +570,7 @@ set(UI
|
|||||||
dialogs/addstreamdialog.ui
|
dialogs/addstreamdialog.ui
|
||||||
dialogs/userpassdialog.ui
|
dialogs/userpassdialog.ui
|
||||||
dialogs/lastfmimportdialog.ui
|
dialogs/lastfmimportdialog.ui
|
||||||
dialogs/snapdialog.ui
|
dialogs/messagedialog.ui
|
||||||
dialogs/saveplaylistsdialog.ui
|
dialogs/saveplaylistsdialog.ui
|
||||||
|
|
||||||
widgets/trackslider.ui
|
widgets/trackslider.ui
|
||||||
@@ -828,6 +838,7 @@ optional_source(WIN32
|
|||||||
HEADERS
|
HEADERS
|
||||||
core/windows7thumbbar.h
|
core/windows7thumbbar.h
|
||||||
)
|
)
|
||||||
|
optional_source(MSVC SOURCES engine/uwpdevicefinder.cpp)
|
||||||
|
|
||||||
optional_source(HAVE_SUBSONIC
|
optional_source(HAVE_SUBSONIC
|
||||||
SOURCES
|
SOURCES
|
||||||
@@ -960,8 +971,8 @@ 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}
|
|
||||||
)
|
)
|
||||||
|
|
||||||
if(HAVE_ICU)
|
if(HAVE_ICU)
|
||||||
@@ -1069,7 +1080,6 @@ target_include_directories(strawberry_lib PUBLIC
|
|||||||
${CMAKE_SOURCE_DIR}/ext/libstrawberry-tagreader
|
${CMAKE_SOURCE_DIR}/ext/libstrawberry-tagreader
|
||||||
${CMAKE_BINARY_DIR}/ext/libstrawberry-tagreader
|
${CMAKE_BINARY_DIR}/ext/libstrawberry-tagreader
|
||||||
${SINGLEAPPLICATION_INCLUDE_DIRS}
|
${SINGLEAPPLICATION_INCLUDE_DIRS}
|
||||||
${SINGLECOREAPPLICATION_INCLUDE_DIRS}
|
|
||||||
)
|
)
|
||||||
|
|
||||||
target_link_libraries(strawberry_lib PUBLIC
|
target_link_libraries(strawberry_lib PUBLIC
|
||||||
@@ -1079,8 +1089,8 @@ 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}
|
|
||||||
libstrawberry-common
|
libstrawberry-common
|
||||||
libstrawberry-tagreader
|
libstrawberry-tagreader
|
||||||
)
|
)
|
||||||
@@ -1190,13 +1200,13 @@ endif()
|
|||||||
if(WIN32)
|
if(WIN32)
|
||||||
target_link_libraries(strawberry_lib PRIVATE dsound dwmapi)
|
target_link_libraries(strawberry_lib PRIVATE dsound dwmapi)
|
||||||
if(MSVC)
|
if(MSVC)
|
||||||
target_link_libraries(strawberry_lib PRIVATE sqlite3)
|
target_link_libraries(strawberry_lib PRIVATE WindowsApp)
|
||||||
if (GETOPT_INCLUDE_DIRS)
|
endif()
|
||||||
target_include_directories(strawberry_lib SYSTEM PRIVATE ${GETOPT_INCLUDE_DIRS})
|
if(GETOPT_INCLUDE_DIRS)
|
||||||
endif()
|
target_include_directories(strawberry_lib SYSTEM PRIVATE ${GETOPT_INCLUDE_DIRS})
|
||||||
if(GETOPT_LIBRARIES)
|
endif()
|
||||||
target_link_libraries(strawberry_lib PRIVATE ${GETOPT_LIBRARIES})
|
if(GETOPT_LIBRARIES)
|
||||||
endif()
|
target_link_libraries(strawberry_lib PRIVATE ${GETOPT_LIBRARIES})
|
||||||
endif()
|
endif()
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
|
|||||||
@@ -50,7 +50,7 @@
|
|||||||
// Make an INSTRUCTIONS file
|
// Make an INSTRUCTIONS file
|
||||||
// can't mod scope in analyze you have to use transform for 2D use setErasePixmap Qt function insetead of m_background
|
// can't mod scope in analyze you have to use transform for 2D use setErasePixmap Qt function insetead of m_background
|
||||||
|
|
||||||
Analyzer::Base::Base(QWidget *parent, const uint scopeSize)
|
AnalyzerBase::AnalyzerBase(QWidget *parent, const uint scopeSize)
|
||||||
: QWidget(parent),
|
: QWidget(parent),
|
||||||
fht_(new FHT(scopeSize)),
|
fht_(new FHT(scopeSize)),
|
||||||
engine_(nullptr),
|
engine_(nullptr),
|
||||||
@@ -63,19 +63,19 @@ Analyzer::Base::Base(QWidget *parent, const uint scopeSize)
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Analyzer::Base::~Base() {
|
AnalyzerBase::~AnalyzerBase() {
|
||||||
delete fht_;
|
delete fht_;
|
||||||
}
|
}
|
||||||
|
|
||||||
void Analyzer::Base::showEvent(QShowEvent*) {
|
void AnalyzerBase::showEvent(QShowEvent*) {
|
||||||
timer_.start(timeout(), this);
|
timer_.start(timeout(), this);
|
||||||
}
|
}
|
||||||
|
|
||||||
void Analyzer::Base::hideEvent(QHideEvent*) {
|
void AnalyzerBase::hideEvent(QHideEvent*) {
|
||||||
timer_.stop();
|
timer_.stop();
|
||||||
}
|
}
|
||||||
|
|
||||||
void Analyzer::Base::ChangeTimeout(const int timeout) {
|
void AnalyzerBase::ChangeTimeout(const int timeout) {
|
||||||
|
|
||||||
timeout_ = timeout;
|
timeout_ = timeout;
|
||||||
if (timer_.isActive()) {
|
if (timer_.isActive()) {
|
||||||
@@ -85,7 +85,7 @@ void Analyzer::Base::ChangeTimeout(const int timeout) {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void Analyzer::Base::transform(Scope &scope) {
|
void AnalyzerBase::transform(Scope &scope) {
|
||||||
|
|
||||||
QVector<float> aux(fht_->size());
|
QVector<float> aux(fht_->size());
|
||||||
if (static_cast<unsigned long int>(aux.size()) >= scope.size()) {
|
if (static_cast<unsigned long int>(aux.size()) >= scope.size()) {
|
||||||
@@ -102,14 +102,14 @@ void Analyzer::Base::transform(Scope &scope) {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void Analyzer::Base::paintEvent(QPaintEvent *e) {
|
void AnalyzerBase::paintEvent(QPaintEvent *e) {
|
||||||
|
|
||||||
QPainter p(this);
|
QPainter p(this);
|
||||||
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 EngineBase::State::Playing: {
|
||||||
const Engine::Scope &thescope = engine_->scope(timeout_);
|
const EngineBase::Scope &thescope = engine_->scope(timeout_);
|
||||||
int i = 0;
|
int i = 0;
|
||||||
|
|
||||||
// convert to mono here - our built in analyzers need mono, but the engines provide interleaved pcm
|
// convert to mono here - our built in analyzers need mono, but the engines provide interleaved pcm
|
||||||
@@ -126,7 +126,7 @@ void Analyzer::Base::paintEvent(QPaintEvent *e) {
|
|||||||
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case Engine::Paused:
|
case EngineBase::State::Paused:
|
||||||
is_playing_ = false;
|
is_playing_ = false;
|
||||||
analyze(p, lastscope_, new_frame_);
|
analyze(p, lastscope_, new_frame_);
|
||||||
break;
|
break;
|
||||||
@@ -140,7 +140,7 @@ void Analyzer::Base::paintEvent(QPaintEvent *e) {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
int Analyzer::Base::resizeExponent(int exp) {
|
int AnalyzerBase::resizeExponent(int exp) {
|
||||||
|
|
||||||
if (exp < 3) {
|
if (exp < 3) {
|
||||||
exp = 3;
|
exp = 3;
|
||||||
@@ -157,7 +157,7 @@ int Analyzer::Base::resizeExponent(int exp) {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
int Analyzer::Base::resizeForBands(const int bands) {
|
int AnalyzerBase::resizeForBands(const int bands) {
|
||||||
|
|
||||||
int exp = 0;
|
int exp = 0;
|
||||||
if (bands <= 8) {
|
if (bands <= 8) {
|
||||||
@@ -184,7 +184,7 @@ int Analyzer::Base::resizeForBands(const int bands) {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void Analyzer::Base::demo(QPainter &p) {
|
void AnalyzerBase::demo(QPainter &p) {
|
||||||
|
|
||||||
static int t = 201; // FIXME make static to namespace perhaps
|
static int t = 201; // FIXME make static to namespace perhaps
|
||||||
|
|
||||||
@@ -209,7 +209,7 @@ void Analyzer::Base::demo(QPainter &p) {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void Analyzer::interpolate(const Scope &inVec, Scope &outVec) {
|
void AnalyzerBase::interpolate(const Scope &inVec, Scope &outVec) {
|
||||||
|
|
||||||
double pos = 0.0;
|
double pos = 0.0;
|
||||||
const double step = static_cast<double>(inVec.size()) / static_cast<double>(outVec.size());
|
const double step = static_cast<double>(inVec.size()) / static_cast<double>(outVec.size());
|
||||||
@@ -235,7 +235,7 @@ void Analyzer::interpolate(const Scope &inVec, Scope &outVec) {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void Analyzer::initSin(Scope &v, const uint size) {
|
void AnalyzerBase::initSin(Scope &v, const uint size) {
|
||||||
|
|
||||||
double step = (M_PI * 2) / size;
|
double step = (M_PI * 2) / size;
|
||||||
double radian = 0;
|
double radian = 0;
|
||||||
@@ -247,7 +247,7 @@ void Analyzer::initSin(Scope &v, const uint size) {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void Analyzer::Base::timerEvent(QTimerEvent *e) {
|
void AnalyzerBase::timerEvent(QTimerEvent *e) {
|
||||||
|
|
||||||
QWidget::timerEvent(e);
|
QWidget::timerEvent(e);
|
||||||
if (e->timerId() != timer_.timerId()) {
|
if (e->timerId() != timer_.timerId()) {
|
||||||
|
|||||||
@@ -39,7 +39,6 @@
|
|||||||
#include <QPainter>
|
#include <QPainter>
|
||||||
|
|
||||||
#include "analyzer/fht.h"
|
#include "analyzer/fht.h"
|
||||||
#include "engine/engine_fwd.h"
|
|
||||||
#include "engine/enginebase.h"
|
#include "engine/enginebase.h"
|
||||||
|
|
||||||
class QHideEvent;
|
class QHideEvent;
|
||||||
@@ -47,15 +46,11 @@ class QShowEvent;
|
|||||||
class QPaintEvent;
|
class QPaintEvent;
|
||||||
class QTimerEvent;
|
class QTimerEvent;
|
||||||
|
|
||||||
namespace Analyzer {
|
class AnalyzerBase : public QWidget {
|
||||||
|
|
||||||
using Scope = std::vector<float>;
|
|
||||||
|
|
||||||
class Base : public QWidget {
|
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
|
|
||||||
public:
|
public:
|
||||||
~Base() override;
|
~AnalyzerBase() override;
|
||||||
|
|
||||||
int timeout() const { return timeout_; }
|
int timeout() const { return timeout_; }
|
||||||
|
|
||||||
@@ -66,7 +61,8 @@ class Base : public QWidget {
|
|||||||
virtual void framerateChanged() {}
|
virtual void framerateChanged() {}
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
explicit Base(QWidget*, const uint scopeSize = 7);
|
using Scope = std::vector<float>;
|
||||||
|
explicit AnalyzerBase(QWidget*, const uint scopeSize = 7);
|
||||||
|
|
||||||
void hideEvent(QHideEvent*) override;
|
void hideEvent(QHideEvent*) override;
|
||||||
void showEvent(QShowEvent*) override;
|
void showEvent(QShowEvent*) override;
|
||||||
@@ -80,6 +76,9 @@ class Base : public QWidget {
|
|||||||
virtual void analyze(QPainter &p, const Scope&, const bool new_frame) = 0;
|
virtual void analyze(QPainter &p, const Scope&, const bool new_frame) = 0;
|
||||||
virtual void demo(QPainter &p);
|
virtual void demo(QPainter &p);
|
||||||
|
|
||||||
|
void interpolate(const Scope&, Scope&);
|
||||||
|
void initSin(Scope&, const uint = 6000);
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
QBasicTimer timer_;
|
QBasicTimer timer_;
|
||||||
FHT *fht_;
|
FHT *fht_;
|
||||||
@@ -91,10 +90,5 @@ class Base : public QWidget {
|
|||||||
int timeout_;
|
int timeout_;
|
||||||
};
|
};
|
||||||
|
|
||||||
void interpolate(const Scope&, Scope&);
|
|
||||||
void initSin(Scope&, const uint = 6000);
|
|
||||||
|
|
||||||
} // namespace Analyzer
|
|
||||||
|
|
||||||
#endif // ANALYZERBASE_H
|
#endif // ANALYZERBASE_H
|
||||||
|
|
||||||
|
|||||||
@@ -40,10 +40,10 @@
|
|||||||
#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"
|
||||||
#include "engine/enginetype.h"
|
|
||||||
|
|
||||||
using namespace std::chrono_literals;
|
using namespace std::chrono_literals;
|
||||||
|
|
||||||
@@ -83,8 +83,9 @@ AnalyzerContainer::AnalyzerContainer(QWidget *parent)
|
|||||||
|
|
||||||
AddAnalyzerType<BlockAnalyzer>();
|
AddAnalyzerType<BlockAnalyzer>();
|
||||||
AddAnalyzerType<BoomAnalyzer>();
|
AddAnalyzerType<BoomAnalyzer>();
|
||||||
AddAnalyzerType<Rainbow::NyanCatAnalyzer>();
|
AddAnalyzerType<NyanCatAnalyzer>();
|
||||||
AddAnalyzerType<Rainbow::RainbowDashAnalyzer>();
|
AddAnalyzerType<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);
|
||||||
@@ -102,7 +103,7 @@ AnalyzerContainer::AnalyzerContainer(QWidget *parent)
|
|||||||
|
|
||||||
void AnalyzerContainer::mouseReleaseEvent(QMouseEvent *e) {
|
void AnalyzerContainer::mouseReleaseEvent(QMouseEvent *e) {
|
||||||
|
|
||||||
if (engine_->type() != Engine::EngineType::GStreamer) {
|
if (engine_->type() != EngineBase::Type::GStreamer) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -148,7 +149,7 @@ void AnalyzerContainer::ChangeAnalyzer(const int id) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
delete current_analyzer_;
|
delete current_analyzer_;
|
||||||
current_analyzer_ = qobject_cast<Analyzer::Base*>(instance);
|
current_analyzer_ = qobject_cast<AnalyzerBase*>(instance);
|
||||||
current_analyzer_->set_engine(engine_);
|
current_analyzer_->set_engine(engine_);
|
||||||
// Even if it is not supposed to happen, I don't want to get a dbz error
|
// Even if it is not supposed to happen, I don't want to get a dbz error
|
||||||
current_framerate_ = current_framerate_ == 0 ? kMediumFramerate : current_framerate_;
|
current_framerate_ = current_framerate_ == 0 ? kMediumFramerate : current_framerate_;
|
||||||
|
|||||||
@@ -30,15 +30,13 @@
|
|||||||
#include <QAction>
|
#include <QAction>
|
||||||
#include <QActionGroup>
|
#include <QActionGroup>
|
||||||
|
|
||||||
#include "engine/engine_fwd.h"
|
#include "engine/enginebase.h"
|
||||||
|
|
||||||
class QTimer;
|
class QTimer;
|
||||||
class QMouseEvent;
|
class QMouseEvent;
|
||||||
class QWheelEvent;
|
class QWheelEvent;
|
||||||
|
|
||||||
namespace Analyzer {
|
class AnalyzerBase;
|
||||||
class Base;
|
|
||||||
} // namespace Analyzer
|
|
||||||
|
|
||||||
class AnalyzerContainer : public QWidget {
|
class AnalyzerContainer : public QWidget {
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
@@ -53,7 +51,7 @@ class AnalyzerContainer : public QWidget {
|
|||||||
static const char *kSettingsFramerate;
|
static const char *kSettingsFramerate;
|
||||||
|
|
||||||
signals:
|
signals:
|
||||||
void WheelEvent(int delta);
|
void WheelEvent(const int delta);
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
void mouseReleaseEvent(QMouseEvent*) override;
|
void mouseReleaseEvent(QMouseEvent*) override;
|
||||||
@@ -94,7 +92,7 @@ class AnalyzerContainer : public QWidget {
|
|||||||
QPoint last_click_pos_;
|
QPoint last_click_pos_;
|
||||||
bool ignore_next_click_;
|
bool ignore_next_click_;
|
||||||
|
|
||||||
Analyzer::Base *current_analyzer_;
|
AnalyzerBase *current_analyzer_;
|
||||||
EngineBase *engine_;
|
EngineBase *engine_;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -46,7 +46,7 @@ const int BlockAnalyzer::kFadeSize = 90;
|
|||||||
const char *BlockAnalyzer::kName = QT_TRANSLATE_NOOP("AnalyzerContainer", "Block analyzer");
|
const char *BlockAnalyzer::kName = QT_TRANSLATE_NOOP("AnalyzerContainer", "Block analyzer");
|
||||||
|
|
||||||
BlockAnalyzer::BlockAnalyzer(QWidget *parent)
|
BlockAnalyzer::BlockAnalyzer(QWidget *parent)
|
||||||
: Analyzer::Base(parent, 9),
|
: AnalyzerBase(parent, 9),
|
||||||
columns_(0),
|
columns_(0),
|
||||||
rows_(0),
|
rows_(0),
|
||||||
y_(0),
|
y_(0),
|
||||||
@@ -124,7 +124,7 @@ void BlockAnalyzer::framerateChanged() {
|
|||||||
determineStep();
|
determineStep();
|
||||||
}
|
}
|
||||||
|
|
||||||
void BlockAnalyzer::transform(Analyzer::Scope &s) {
|
void BlockAnalyzer::transform(Scope &s) {
|
||||||
|
|
||||||
for (uint x = 0; x < s.size(); ++x) s[x] *= 2;
|
for (uint x = 0; x < s.size(); ++x) s[x] *= 2;
|
||||||
|
|
||||||
@@ -136,7 +136,7 @@ void BlockAnalyzer::transform(Analyzer::Scope &s) {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void BlockAnalyzer::analyze(QPainter &p, const Analyzer::Scope &s, bool new_frame) {
|
void BlockAnalyzer::analyze(QPainter &p, const Scope &s, bool new_frame) {
|
||||||
|
|
||||||
// y = 2 3 2 1 0 2
|
// y = 2 3 2 1 0 2
|
||||||
// . . . . # .
|
// . . . . # .
|
||||||
@@ -158,7 +158,7 @@ void BlockAnalyzer::analyze(QPainter &p, const Analyzer::Scope &s, bool new_fram
|
|||||||
|
|
||||||
QPainter canvas_painter(&canvas_);
|
QPainter canvas_painter(&canvas_);
|
||||||
|
|
||||||
Analyzer::interpolate(s, scope_);
|
interpolate(s, scope_);
|
||||||
|
|
||||||
// Paint the background
|
// Paint the background
|
||||||
canvas_painter.drawPixmap(0, 0, background_);
|
canvas_painter.drawPixmap(0, 0, background_);
|
||||||
|
|||||||
@@ -37,7 +37,7 @@
|
|||||||
class QWidget;
|
class QWidget;
|
||||||
class QResizeEvent;
|
class QResizeEvent;
|
||||||
|
|
||||||
class BlockAnalyzer : public Analyzer::Base {
|
class BlockAnalyzer : public AnalyzerBase {
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
|
|
||||||
public:
|
public:
|
||||||
@@ -53,8 +53,8 @@ class BlockAnalyzer : public Analyzer::Base {
|
|||||||
static const char *kName;
|
static const char *kName;
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
void transform(Analyzer::Scope&) override;
|
void transform(Scope&) override;
|
||||||
void analyze(QPainter &p, const Analyzer::Scope&, bool new_frame) override;
|
void analyze(QPainter &p, const Scope&, bool new_frame) override;
|
||||||
void resizeEvent(QResizeEvent*) override;
|
void resizeEvent(QResizeEvent*) override;
|
||||||
virtual void paletteChange(const QPalette&);
|
virtual void paletteChange(const QPalette&);
|
||||||
void framerateChanged() override;
|
void framerateChanged() override;
|
||||||
@@ -71,7 +71,7 @@ class BlockAnalyzer : public Analyzer::Base {
|
|||||||
QPixmap topbarpixmap_;
|
QPixmap topbarpixmap_;
|
||||||
QPixmap background_;
|
QPixmap background_;
|
||||||
QPixmap canvas_;
|
QPixmap canvas_;
|
||||||
Analyzer::Scope scope_; // so we don't create a vector every frame
|
Scope scope_; // so we don't create a vector every frame
|
||||||
QVector<double> store_; // current bar heights
|
QVector<double> store_; // current bar heights
|
||||||
QVector<double> yscale_;
|
QVector<double> yscale_;
|
||||||
|
|
||||||
|
|||||||
@@ -32,13 +32,10 @@
|
|||||||
#include <QPalette>
|
#include <QPalette>
|
||||||
#include <QColor>
|
#include <QColor>
|
||||||
|
|
||||||
#include "engine/engine_fwd.h"
|
|
||||||
#include "engine/enginebase.h"
|
#include "engine/enginebase.h"
|
||||||
#include "fht.h"
|
#include "fht.h"
|
||||||
#include "analyzerbase.h"
|
#include "analyzerbase.h"
|
||||||
|
|
||||||
using Analyzer::Scope;
|
|
||||||
|
|
||||||
const int BoomAnalyzer::kColumnWidth = 4;
|
const int BoomAnalyzer::kColumnWidth = 4;
|
||||||
const int BoomAnalyzer::kMaxBandCount = 256;
|
const int BoomAnalyzer::kMaxBandCount = 256;
|
||||||
const int BoomAnalyzer::kMinBandCount = 32;
|
const int BoomAnalyzer::kMinBandCount = 32;
|
||||||
@@ -46,7 +43,7 @@ const int BoomAnalyzer::kMinBandCount = 32;
|
|||||||
const char *BoomAnalyzer::kName = QT_TRANSLATE_NOOP("AnalyzerContainer", "Boom analyzer");
|
const char *BoomAnalyzer::kName = QT_TRANSLATE_NOOP("AnalyzerContainer", "Boom analyzer");
|
||||||
|
|
||||||
BoomAnalyzer::BoomAnalyzer(QWidget *parent)
|
BoomAnalyzer::BoomAnalyzer(QWidget *parent)
|
||||||
: Analyzer::Base(parent, 9),
|
: AnalyzerBase(parent, 9),
|
||||||
bands_(0),
|
bands_(0),
|
||||||
scope_(kMinBandCount),
|
scope_(kMinBandCount),
|
||||||
fg_(palette().color(QPalette::Highlight)),
|
fg_(palette().color(QPalette::Highlight)),
|
||||||
@@ -110,7 +107,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() == EngineBase::State::Paused) {
|
||||||
p.drawPixmap(0, 0, canvas_);
|
p.drawPixmap(0, 0, canvas_);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -120,7 +117,7 @@ void BoomAnalyzer::analyze(QPainter &p, const Scope &scope, const bool new_frame
|
|||||||
QPainter canvas_painter(&canvas_);
|
QPainter canvas_painter(&canvas_);
|
||||||
canvas_.fill(palette().color(QPalette::Window));
|
canvas_.fill(palette().color(QPalette::Window));
|
||||||
|
|
||||||
Analyzer::interpolate(scope, scope_);
|
interpolate(scope, scope_);
|
||||||
|
|
||||||
for (int i = 0, x = 0, y = 0; i < bands_; ++i, x += kColumnWidth + 1) {
|
for (int i = 0, x = 0, y = 0; i < bands_; ++i, x += kColumnWidth + 1) {
|
||||||
h = log10(scope_[i] * 256.0) * F_;
|
h = log10(scope_[i] * 256.0) * F_;
|
||||||
|
|||||||
@@ -36,7 +36,7 @@
|
|||||||
class QWidget;
|
class QWidget;
|
||||||
class QResizeEvent;
|
class QResizeEvent;
|
||||||
|
|
||||||
class BoomAnalyzer : public Analyzer::Base {
|
class BoomAnalyzer : public AnalyzerBase {
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
|
|
||||||
public:
|
public:
|
||||||
@@ -44,8 +44,8 @@ class BoomAnalyzer : public Analyzer::Base {
|
|||||||
|
|
||||||
static const char *kName;
|
static const char *kName;
|
||||||
|
|
||||||
void transform(Analyzer::Scope &s) override;
|
void transform(Scope &s) override;
|
||||||
void analyze(QPainter &p, const Analyzer::Scope&, const bool new_frame) override;
|
void analyze(QPainter &p, const Scope&, const bool new_frame) override;
|
||||||
|
|
||||||
public slots:
|
public slots:
|
||||||
void changeK_barHeight(int);
|
void changeK_barHeight(int);
|
||||||
@@ -59,7 +59,7 @@ class BoomAnalyzer : public Analyzer::Base {
|
|||||||
static const int kMinBandCount;
|
static const int kMinBandCount;
|
||||||
|
|
||||||
int bands_;
|
int bands_;
|
||||||
Analyzer::Scope scope_;
|
Scope scope_;
|
||||||
QColor fg_;
|
QColor fg_;
|
||||||
|
|
||||||
double K_barHeight_, F_peakSpeed_, F_;
|
double K_barHeight_, F_peakSpeed_, F_;
|
||||||
|
|||||||
@@ -41,23 +41,21 @@
|
|||||||
#include "fht.h"
|
#include "fht.h"
|
||||||
#include "analyzerbase.h"
|
#include "analyzerbase.h"
|
||||||
|
|
||||||
using Analyzer::Scope;
|
const int RainbowAnalyzer::kHeight[] = { 21, 33 };
|
||||||
|
const int RainbowAnalyzer::kWidth[] = { 34, 53 };
|
||||||
|
const int RainbowAnalyzer::kFrameCount[] = { 6, 16 };
|
||||||
|
const int RainbowAnalyzer::kRainbowHeight[] = { 21, 16 };
|
||||||
|
const int RainbowAnalyzer::kRainbowOverlap[] = { 13, 15 };
|
||||||
|
const int RainbowAnalyzer::kSleepingHeight[] = { 24, 33 };
|
||||||
|
|
||||||
const int Rainbow::RainbowAnalyzer::kHeight[] = { 21, 33 };
|
const char *NyanCatAnalyzer::kName = "Nyanalyzer Cat";
|
||||||
const int Rainbow::RainbowAnalyzer::kWidth[] = { 34, 53 };
|
const char *RainbowDashAnalyzer::kName = "Rainbow Dash";
|
||||||
const int Rainbow::RainbowAnalyzer::kFrameCount[] = { 6, 16 };
|
const float RainbowAnalyzer::kPixelScale = 0.02F;
|
||||||
const int Rainbow::RainbowAnalyzer::kRainbowHeight[] = { 21, 16 };
|
|
||||||
const int Rainbow::RainbowAnalyzer::kRainbowOverlap[] = { 13, 15 };
|
|
||||||
const int Rainbow::RainbowAnalyzer::kSleepingHeight[] = { 24, 33 };
|
|
||||||
|
|
||||||
const char *Rainbow::NyanCatAnalyzer::kName = "Nyanalyzer Cat";
|
RainbowAnalyzer::RainbowType RainbowAnalyzer::rainbowtype;
|
||||||
const char *Rainbow::RainbowDashAnalyzer::kName = "Rainbow Dash";
|
|
||||||
const float Rainbow::RainbowAnalyzer::kPixelScale = 0.02F;
|
|
||||||
|
|
||||||
Rainbow::RainbowAnalyzer::RainbowType Rainbow::RainbowAnalyzer::rainbowtype;
|
RainbowAnalyzer::RainbowAnalyzer(const RainbowType rbtype, QWidget *parent)
|
||||||
|
: AnalyzerBase(parent, 9),
|
||||||
Rainbow::RainbowAnalyzer::RainbowAnalyzer(const RainbowType rbtype, QWidget *parent)
|
|
||||||
: Analyzer::Base(parent, 9),
|
|
||||||
timer_id_(startTimer(kFrameIntervalMs)),
|
timer_id_(startTimer(kFrameIntervalMs)),
|
||||||
frame_(0),
|
frame_(0),
|
||||||
current_buffer_(0),
|
current_buffer_(0),
|
||||||
@@ -81,20 +79,20 @@ Rainbow::RainbowAnalyzer::RainbowAnalyzer(const RainbowType rbtype, QWidget *par
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void Rainbow::RainbowAnalyzer::transform(Scope &s) { fht_->spectrum(s.data()); }
|
void RainbowAnalyzer::transform(Scope &s) { fht_->spectrum(s.data()); }
|
||||||
|
|
||||||
void Rainbow::RainbowAnalyzer::timerEvent(QTimerEvent *e) {
|
void RainbowAnalyzer::timerEvent(QTimerEvent *e) {
|
||||||
|
|
||||||
if (e->timerId() == timer_id_) {
|
if (e->timerId() == timer_id_) {
|
||||||
frame_ = (frame_ + 1) % kFrameCount[rainbowtype];
|
frame_ = (frame_ + 1) % kFrameCount[rainbowtype];
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
Analyzer::Base::timerEvent(e);
|
AnalyzerBase::timerEvent(e);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void Rainbow::RainbowAnalyzer::resizeEvent(QResizeEvent *e) {
|
void RainbowAnalyzer::resizeEvent(QResizeEvent *e) {
|
||||||
|
|
||||||
Q_UNUSED(e);
|
Q_UNUSED(e);
|
||||||
|
|
||||||
@@ -108,7 +106,7 @@ void Rainbow::RainbowAnalyzer::resizeEvent(QResizeEvent *e) {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void Rainbow::RainbowAnalyzer::analyze(QPainter &p, const Analyzer::Scope &s, bool new_frame) {
|
void RainbowAnalyzer::analyze(QPainter &p, const Scope &s, bool new_frame) {
|
||||||
|
|
||||||
// Discard the second half of the transform
|
// Discard the second half of the transform
|
||||||
const int scope_size = static_cast<int>(s.size() / 2);
|
const int scope_size = static_cast<int>(s.size() / 2);
|
||||||
@@ -203,8 +201,8 @@ void Rainbow::RainbowAnalyzer::analyze(QPainter &p, const Analyzer::Scope &s, bo
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Rainbow::NyanCatAnalyzer::NyanCatAnalyzer(QWidget *parent)
|
NyanCatAnalyzer::NyanCatAnalyzer(QWidget *parent)
|
||||||
: RainbowAnalyzer(Rainbow::RainbowAnalyzer::Nyancat, parent) {}
|
: RainbowAnalyzer(RainbowAnalyzer::Nyancat, parent) {}
|
||||||
|
|
||||||
Rainbow::RainbowDashAnalyzer::RainbowDashAnalyzer(QWidget *parent)
|
RainbowDashAnalyzer::RainbowDashAnalyzer(QWidget *parent)
|
||||||
: RainbowAnalyzer(Rainbow::RainbowAnalyzer::Dash, parent) {}
|
: RainbowAnalyzer(RainbowAnalyzer::Dash, parent) {}
|
||||||
|
|||||||
@@ -40,8 +40,7 @@ class QWidget;
|
|||||||
class QTimerEvent;
|
class QTimerEvent;
|
||||||
class QResizeEvent;
|
class QResizeEvent;
|
||||||
|
|
||||||
namespace Rainbow {
|
class RainbowAnalyzer : public AnalyzerBase {
|
||||||
class RainbowAnalyzer : public Analyzer::Base {
|
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
|
|
||||||
public:
|
public:
|
||||||
@@ -53,8 +52,8 @@ class RainbowAnalyzer : public Analyzer::Base {
|
|||||||
RainbowAnalyzer(const RainbowType rbtype, QWidget *parent);
|
RainbowAnalyzer(const RainbowType rbtype, QWidget *parent);
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
void transform(Analyzer::Scope&) override;
|
void transform(Scope&) override;
|
||||||
void analyze(QPainter &p, const Analyzer::Scope&, bool new_frame) override;
|
void analyze(QPainter &p, const Scope&, bool new_frame) override;
|
||||||
|
|
||||||
void timerEvent(QTimerEvent *e) override;
|
void timerEvent(QTimerEvent *e) override;
|
||||||
void resizeEvent(QResizeEvent *e) override;
|
void resizeEvent(QResizeEvent *e) override;
|
||||||
@@ -142,6 +141,5 @@ class RainbowDashAnalyzer : public RainbowAnalyzer {
|
|||||||
|
|
||||||
static const char *kName;
|
static const char *kName;
|
||||||
};
|
};
|
||||||
} // namespace Rainbow
|
|
||||||
|
|
||||||
#endif // RAINBOWANALYZER_H
|
#endif // RAINBOWANALYZER_H
|
||||||
|
|||||||
94
src/analyzer/sonogram.cpp
Normal file
94
src/analyzer/sonogram.cpp
Normal file
@@ -0,0 +1,94 @@
|
|||||||
|
/*
|
||||||
|
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 "engine/enginebase.h"
|
||||||
|
|
||||||
|
#include "sonogram.h"
|
||||||
|
|
||||||
|
const char *Sonogram::kName = QT_TRANSLATE_NOOP("AnalyzerContainer", "Sonogram");
|
||||||
|
|
||||||
|
Sonogram::Sonogram(QWidget *parent)
|
||||||
|
: AnalyzerBase(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 Scope &s, bool new_frame) {
|
||||||
|
|
||||||
|
if (!new_frame || engine_->state() == EngineBase::State::Paused) {
|
||||||
|
p.drawPixmap(0, 0, canvas_);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
QPainter canvas_painter(&canvas_);
|
||||||
|
canvas_painter.drawPixmap(0, 0, canvas_, 1, 0, width() - 1, -1);
|
||||||
|
|
||||||
|
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(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, 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 AnalyzerBase {
|
||||||
|
Q_OBJECT
|
||||||
|
public:
|
||||||
|
Q_INVOKABLE explicit Sonogram(QWidget *parent);
|
||||||
|
|
||||||
|
static const char *kName;
|
||||||
|
|
||||||
|
protected:
|
||||||
|
void resizeEvent(QResizeEvent *e) override;
|
||||||
|
void analyze(QPainter &p, const Scope &s, bool new_frame) override;
|
||||||
|
void transform(Scope &scope) override;
|
||||||
|
void demo(QPainter &p) override;
|
||||||
|
|
||||||
|
private:
|
||||||
|
QPixmap canvas_;
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif // SONOGRAM_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 "utilities/threadutils.h"
|
|
||||||
|
|
||||||
|
class QThread;
|
||||||
class Application;
|
class Application;
|
||||||
class Thread;
|
class Thread;
|
||||||
class CollectionBackend;
|
class CollectionBackend;
|
||||||
@@ -78,11 +77,11 @@ 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:
|
||||||
void Error(QString);
|
void Error(const QString &error);
|
||||||
void ExitFinished();
|
void ExitFinished();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
@@ -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_;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
* Strawberry Music Player
|
* Strawberry Music Player
|
||||||
* This file was part of Clementine.
|
* This file was part of Clementine.
|
||||||
* Copyright 2010, David Sansome <me@davidsansome.com>
|
* Copyright 2010, David Sansome <me@davidsansome.com>
|
||||||
* Copyright 2018-2021, Jonas Kvinge <jonas@jkvinge.net>
|
* Copyright 2018-2023, Jonas Kvinge <jonas@jkvinge.net>
|
||||||
*
|
*
|
||||||
* Strawberry is free software: you can redistribute it and/or modify
|
* Strawberry is free software: you can redistribute it and/or modify
|
||||||
* it under the terms of the GNU General Public License as published by
|
* it under the terms of the GNU General Public License as published by
|
||||||
@@ -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();
|
||||||
@@ -88,7 +89,7 @@ void CollectionBackend::Close() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void CollectionBackend::ExitAsync() {
|
void CollectionBackend::ExitAsync() {
|
||||||
QMetaObject::invokeMethod(this, "Exit", Qt::QueuedConnection);
|
QMetaObject::invokeMethod(this, &CollectionBackend::Exit, Qt::QueuedConnection);
|
||||||
}
|
}
|
||||||
|
|
||||||
void CollectionBackend::Exit() {
|
void CollectionBackend::Exit() {
|
||||||
@@ -104,31 +105,29 @@ void CollectionBackend::ReportErrors(const CollectionQuery &query) {
|
|||||||
|
|
||||||
const QSqlError sql_error = query.lastError();
|
const QSqlError sql_error = query.lastError();
|
||||||
if (sql_error.isValid()) {
|
if (sql_error.isValid()) {
|
||||||
qLog(Error) << "Unable to execute collection SQL query: " << sql_error;
|
qLog(Error) << "Unable to execute collection SQL query:" << sql_error;
|
||||||
qLog(Error) << "Faulty SQL query: " << query.lastQuery();
|
qLog(Error) << "Failed SQL query:" << query.lastQuery();
|
||||||
qLog(Error) << "Bound SQL values: " << query.boundValues();
|
qLog(Error) << "Bound SQL values:" << query.boundValues();
|
||||||
QString error;
|
emit Error(tr("Unable to execute collection SQL query: %1").arg(sql_error.text()));
|
||||||
error += "Unable to execute collection SQL query: " + sql_error.text() + "<br />";
|
emit Error(tr("Failed SQL query: %1").arg(query.lastQuery()));
|
||||||
error += "Faulty SQL query: " + query.lastQuery();
|
|
||||||
emit Error(error);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void CollectionBackend::LoadDirectoriesAsync() {
|
void CollectionBackend::LoadDirectoriesAsync() {
|
||||||
QMetaObject::invokeMethod(this, "LoadDirectories", Qt::QueuedConnection);
|
QMetaObject::invokeMethod(this, &CollectionBackend::LoadDirectories, Qt::QueuedConnection);
|
||||||
}
|
}
|
||||||
|
|
||||||
void CollectionBackend::UpdateTotalSongCountAsync() {
|
void CollectionBackend::UpdateTotalSongCountAsync() {
|
||||||
QMetaObject::invokeMethod(this, "UpdateTotalSongCount", Qt::QueuedConnection);
|
QMetaObject::invokeMethod(this, &CollectionBackend::UpdateTotalSongCount, Qt::QueuedConnection);
|
||||||
}
|
}
|
||||||
|
|
||||||
void CollectionBackend::UpdateTotalArtistCountAsync() {
|
void CollectionBackend::UpdateTotalArtistCountAsync() {
|
||||||
QMetaObject::invokeMethod(this, "UpdateTotalArtistCount", Qt::QueuedConnection);
|
QMetaObject::invokeMethod(this, &CollectionBackend::UpdateTotalArtistCount, Qt::QueuedConnection);
|
||||||
}
|
}
|
||||||
|
|
||||||
void CollectionBackend::UpdateTotalAlbumCountAsync() {
|
void CollectionBackend::UpdateTotalAlbumCountAsync() {
|
||||||
QMetaObject::invokeMethod(this, "UpdateTotalAlbumCount", Qt::QueuedConnection);
|
QMetaObject::invokeMethod(this, &CollectionBackend::UpdateTotalAlbumCount, Qt::QueuedConnection);
|
||||||
}
|
}
|
||||||
|
|
||||||
void CollectionBackend::IncrementPlayCountAsync(const int id) {
|
void CollectionBackend::IncrementPlayCountAsync(const int id) {
|
||||||
@@ -139,18 +138,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 +210,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 +225,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 +235,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 +243,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 +342,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 +450,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 +716,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 +903,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 +925,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 +970,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 +996,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 +1015,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 +1288,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());
|
||||||
@@ -1426,13 +1429,13 @@ 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());
|
||||||
|
|
||||||
CollectionQuery query(db, songs_table_, fts_table_, opt);
|
CollectionQuery query(db, songs_table_, fts_table_, opt);
|
||||||
query.SetColumnSpec("url, effective_albumartist, album, compilation_effective, art_automatic, art_manual, filetype, cue_path");
|
query.SetColumnSpec("url, filetype, cue_path, effective_albumartist, album, compilation_effective, art_embedded, art_automatic, art_manual, art_unset");
|
||||||
query.SetOrderBy("effective_albumartist, album, url");
|
query.SetOrderBy("effective_albumartist, album, url");
|
||||||
|
|
||||||
if (compilation_required) {
|
if (compilation_required) {
|
||||||
@@ -1450,42 +1453,48 @@ CollectionBackend::AlbumList CollectionBackend::GetAlbums(const QString &artist,
|
|||||||
|
|
||||||
QMap<QString, Album> albums;
|
QMap<QString, Album> albums;
|
||||||
while (query.Next()) {
|
while (query.Next()) {
|
||||||
bool is_compilation = query.Value(3).toBool();
|
|
||||||
|
|
||||||
Album info;
|
Album album_info;
|
||||||
QUrl url = QUrl::fromEncoded(query.Value(0).toByteArray());
|
QUrl url = QUrl::fromEncoded(query.Value(0).toByteArray());
|
||||||
|
|
||||||
|
album_info.filetype = static_cast<Song::FileType>(query.Value(1).toInt());
|
||||||
|
const QString filetype = Song::TextForFiletype(album_info.filetype);
|
||||||
|
album_info.cue_path = query.Value(2).toString();
|
||||||
|
|
||||||
|
const bool is_compilation = query.Value(5).toBool();
|
||||||
if (!is_compilation) {
|
if (!is_compilation) {
|
||||||
info.album_artist = query.Value(1).toString();
|
album_info.album_artist = query.Value(3).toString();
|
||||||
}
|
}
|
||||||
info.album = query.Value(2).toString();
|
|
||||||
|
|
||||||
QString art_automatic = query.Value(4).toString();
|
album_info.album = query.Value(4).toString();
|
||||||
|
|
||||||
|
album_info.art_embedded = query.Value(6).toBool();
|
||||||
|
|
||||||
|
const QString art_automatic = query.Value(7).toString();
|
||||||
if (art_automatic.contains(QRegularExpression("..+:.*"))) {
|
if (art_automatic.contains(QRegularExpression("..+:.*"))) {
|
||||||
info.art_automatic = QUrl::fromEncoded(art_automatic.toUtf8());
|
album_info.art_automatic = QUrl::fromEncoded(art_automatic.toUtf8());
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
info.art_automatic = QUrl::fromLocalFile(art_automatic);
|
album_info.art_automatic = QUrl::fromLocalFile(art_automatic);
|
||||||
}
|
}
|
||||||
|
|
||||||
QString art_manual = query.Value(5).toString();
|
const QString art_manual = query.Value(8).toString();
|
||||||
if (art_manual.contains(QRegularExpression("..+:.*"))) {
|
if (art_manual.contains(QRegularExpression("..+:.*"))) {
|
||||||
info.art_manual = QUrl::fromEncoded(art_manual.toUtf8());
|
album_info.art_manual = QUrl::fromEncoded(art_manual.toUtf8());
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
info.art_manual = QUrl::fromLocalFile(art_manual);
|
album_info.art_manual = QUrl::fromLocalFile(art_manual);
|
||||||
}
|
}
|
||||||
|
|
||||||
info.filetype = static_cast<Song::FileType>(query.Value(6).toInt());
|
album_info.art_unset = query.Value(9).toBool();
|
||||||
QString filetype = Song::TextForFiletype(info.filetype);
|
|
||||||
info.cue_path = query.Value(7).toString();
|
|
||||||
|
|
||||||
QString key;
|
QString key;
|
||||||
if (!info.album_artist.isEmpty()) {
|
if (!album_info.album_artist.isEmpty()) {
|
||||||
key.append(info.album_artist);
|
key.append(album_info.album_artist);
|
||||||
}
|
}
|
||||||
if (!info.album.isEmpty()) {
|
if (!album_info.album.isEmpty()) {
|
||||||
if (!key.isEmpty()) key.append("-");
|
if (!key.isEmpty()) key.append("-");
|
||||||
key.append(info.album);
|
key.append(album_info.album);
|
||||||
}
|
}
|
||||||
if (!filetype.isEmpty()) {
|
if (!filetype.isEmpty()) {
|
||||||
key.append(filetype);
|
key.append(filetype);
|
||||||
@@ -1497,8 +1506,8 @@ CollectionBackend::AlbumList CollectionBackend::GetAlbums(const QString &artist,
|
|||||||
albums[key].urls.append(url);
|
albums[key].urls.append(url);
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
info.urls << url;
|
album_info.urls << url;
|
||||||
albums.insert(key, info);
|
albums.insert(key, album_info);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -1516,8 +1525,8 @@ 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("url, art_embedded, art_automatic, art_manual, art_unset");
|
||||||
if (!effective_albumartist.isEmpty()) {
|
if (!effective_albumartist.isEmpty()) {
|
||||||
query.AddWhere("effective_albumartist", effective_albumartist);
|
query.AddWhere("effective_albumartist", effective_albumartist);
|
||||||
}
|
}
|
||||||
@@ -1529,22 +1538,24 @@ CollectionBackend::Album CollectionBackend::GetAlbumArt(const QString &effective
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (query.Next()) {
|
if (query.Next()) {
|
||||||
ret.art_automatic = QUrl::fromEncoded(query.Value(0).toByteArray());
|
ret.urls << QUrl::fromEncoded(query.Value(0).toByteArray());
|
||||||
ret.art_manual = QUrl::fromEncoded(query.Value(1).toByteArray());
|
ret.art_embedded = query.Value(1).toInt() == 1;
|
||||||
ret.urls << QUrl::fromEncoded(query.Value(2).toByteArray());
|
ret.art_automatic = QUrl::fromEncoded(query.Value(2).toByteArray());
|
||||||
|
ret.art_manual = QUrl::fromEncoded(query.Value(3).toByteArray());
|
||||||
|
ret.art_unset = query.Value(4).toInt() == 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
return ret;
|
return ret;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void CollectionBackend::UpdateManualAlbumArtAsync(const QString &effective_albumartist, const QString &album, const QUrl &cover_url, const bool clear_art_automatic) {
|
void CollectionBackend::UpdateEmbeddedAlbumArtAsync(const QString &effective_albumartist, const QString &album, const bool art_embedded) {
|
||||||
|
|
||||||
QMetaObject::invokeMethod(this, "UpdateManualAlbumArt", Qt::QueuedConnection, Q_ARG(QString, effective_albumartist), Q_ARG(QString, album), Q_ARG(QUrl, cover_url), Q_ARG(bool, clear_art_automatic));
|
QMetaObject::invokeMethod(this, "UpdateEmbeddedAlbumArt", Qt::QueuedConnection, Q_ARG(QString, effective_albumartist), Q_ARG(QString, album), Q_ARG(bool, art_embedded));
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void CollectionBackend::UpdateManualAlbumArt(const QString &effective_albumartist, const QString &album, const QUrl &cover_url, const bool clear_art_automatic) {
|
void CollectionBackend::UpdateEmbeddedAlbumArt(const QString &effective_albumartist, const QString &album, const bool art_embedded) {
|
||||||
|
|
||||||
QMutexLocker l(db_->Mutex());
|
QMutexLocker l(db_->Mutex());
|
||||||
QSqlDatabase db(db_->Connect());
|
QSqlDatabase db(db_->Connect());
|
||||||
@@ -1568,15 +1579,11 @@ void CollectionBackend::UpdateManualAlbumArt(const QString &effective_albumartis
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Update the songs
|
// Update the songs
|
||||||
QString sql = QString("UPDATE %1 SET art_manual = :cover").arg(songs_table_);
|
QString sql = QString("UPDATE %1 SET art_embedded = :art_embedded, art_unset = 0 WHERE effective_albumartist = :effective_albumartist AND album = :album AND unavailable = 0").arg(songs_table_);
|
||||||
if (clear_art_automatic) {
|
|
||||||
sql += ", art_automatic = ''";
|
|
||||||
}
|
|
||||||
sql += " WHERE effective_albumartist = :effective_albumartist AND album = :album AND unavailable = 0";
|
|
||||||
|
|
||||||
SqlQuery q(db);
|
SqlQuery q(db);
|
||||||
q.prepare(sql);
|
q.prepare(sql);
|
||||||
q.BindValue(":cover", cover_url.isValid() ? cover_url.toString(QUrl::FullyEncoded) : "");
|
q.BindValue(":art_embedded", art_embedded ? 1 : 0);
|
||||||
q.BindValue(":effective_albumartist", effective_albumartist);
|
q.BindValue(":effective_albumartist", effective_albumartist);
|
||||||
q.BindValue(":album", album);
|
q.BindValue(":album", album);
|
||||||
|
|
||||||
@@ -1605,18 +1612,17 @@ void CollectionBackend::UpdateManualAlbumArt(const QString &effective_albumartis
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void CollectionBackend::UpdateAutomaticAlbumArtAsync(const QString &effective_albumartist, const QString &album, const QUrl &cover_url, const bool clear_art_manual) {
|
void CollectionBackend::UpdateManualAlbumArtAsync(const QString &effective_albumartist, const QString &album, const QUrl &art_manual) {
|
||||||
|
|
||||||
QMetaObject::invokeMethod(this, "UpdateAutomaticAlbumArt", Qt::QueuedConnection, Q_ARG(QString, effective_albumartist), Q_ARG(QString, album), Q_ARG(QUrl, cover_url), Q_ARG(bool, clear_art_manual));
|
QMetaObject::invokeMethod(this, "UpdateManualAlbumArt", Qt::QueuedConnection, Q_ARG(QString, effective_albumartist), Q_ARG(QString, album), Q_ARG(QUrl, art_manual));
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void CollectionBackend::UpdateAutomaticAlbumArt(const QString &effective_albumartist, const QString &album, const QUrl &cover_url, const bool clear_art_manual) {
|
void CollectionBackend::UpdateManualAlbumArt(const QString &effective_albumartist, const QString &album, const QUrl &art_manual) {
|
||||||
|
|
||||||
QMutexLocker l(db_->Mutex());
|
QMutexLocker l(db_->Mutex());
|
||||||
QSqlDatabase db(db_->Connect());
|
QSqlDatabase db(db_->Connect());
|
||||||
|
|
||||||
// Get the songs before they're updated
|
|
||||||
CollectionQuery query(db, songs_table_, fts_table_);
|
CollectionQuery query(db, songs_table_, fts_table_);
|
||||||
query.SetColumnSpec("ROWID, " + Song::kColumnSpec);
|
query.SetColumnSpec("ROWID, " + Song::kColumnSpec);
|
||||||
query.AddWhere("effective_albumartist", effective_albumartist);
|
query.AddWhere("effective_albumartist", effective_albumartist);
|
||||||
@@ -1634,16 +1640,124 @@ void CollectionBackend::UpdateAutomaticAlbumArt(const QString &effective_albumar
|
|||||||
deleted_songs << song;
|
deleted_songs << song;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update the songs
|
|
||||||
QString sql = QString("UPDATE %1 SET art_automatic = :cover").arg(songs_table_);
|
|
||||||
if (clear_art_manual) {
|
|
||||||
sql += ", art_manual = ''";
|
|
||||||
}
|
|
||||||
sql += " WHERE effective_albumartist = :effective_albumartist AND album = :album AND unavailable = 0";
|
|
||||||
|
|
||||||
SqlQuery q(db);
|
SqlQuery q(db);
|
||||||
q.prepare(sql);
|
q.prepare(QString("UPDATE %1 SET art_manual = :art_manual, art_unset = 0 WHERE effective_albumartist = :effective_albumartist AND album = :album AND unavailable = 0").arg(songs_table_));
|
||||||
q.BindValue(":cover", cover_url.isValid() ? cover_url.toString(QUrl::FullyEncoded) : "");
|
q.BindValue(":art_manual", art_manual.isValid() ? art_manual.toString(QUrl::FullyEncoded) : "");
|
||||||
|
q.BindValue(":effective_albumartist", effective_albumartist);
|
||||||
|
q.BindValue(":album", album);
|
||||||
|
|
||||||
|
if (!q.Exec()) {
|
||||||
|
db_->ReportErrors(q);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!query.Exec()) {
|
||||||
|
ReportErrors(query);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
SongList added_songs;
|
||||||
|
while (query.Next()) {
|
||||||
|
Song song(source_);
|
||||||
|
song.InitFromQuery(query, true);
|
||||||
|
added_songs << song;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!added_songs.isEmpty() || !deleted_songs.isEmpty()) {
|
||||||
|
emit SongsDeleted(deleted_songs);
|
||||||
|
emit SongsDiscovered(added_songs);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
void CollectionBackend::UnsetAlbumArtAsync(const QString &effective_albumartist, const QString &album) {
|
||||||
|
|
||||||
|
QMetaObject::invokeMethod(this, "UnsetAlbumArt", Qt::QueuedConnection, Q_ARG(QString, effective_albumartist), Q_ARG(QString, album));
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
void CollectionBackend::UnsetAlbumArt(const QString &effective_albumartist, const QString &album) {
|
||||||
|
|
||||||
|
QMutexLocker l(db_->Mutex());
|
||||||
|
QSqlDatabase db(db_->Connect());
|
||||||
|
|
||||||
|
CollectionQuery query(db, songs_table_, fts_table_);
|
||||||
|
query.SetColumnSpec("ROWID, " + Song::kColumnSpec);
|
||||||
|
query.AddWhere("effective_albumartist", effective_albumartist);
|
||||||
|
query.AddWhere("album", album);
|
||||||
|
|
||||||
|
if (!query.Exec()) {
|
||||||
|
ReportErrors(query);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
SongList deleted_songs;
|
||||||
|
while (query.Next()) {
|
||||||
|
Song song(source_);
|
||||||
|
song.InitFromQuery(query, true);
|
||||||
|
deleted_songs << song;
|
||||||
|
}
|
||||||
|
|
||||||
|
SqlQuery q(db);
|
||||||
|
q.prepare(QString("UPDATE %1 SET art_unset = 1, art_manual = '', art_automatic = '', art_embedded = '' WHERE effective_albumartist = :effective_albumartist AND album = :album AND unavailable = 0").arg(songs_table_));
|
||||||
|
q.BindValue(":effective_albumartist", effective_albumartist);
|
||||||
|
q.BindValue(":album", album);
|
||||||
|
|
||||||
|
if (!q.Exec()) {
|
||||||
|
db_->ReportErrors(q);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!query.Exec()) {
|
||||||
|
ReportErrors(query);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
SongList added_songs;
|
||||||
|
while (query.Next()) {
|
||||||
|
Song song(source_);
|
||||||
|
song.InitFromQuery(query, true);
|
||||||
|
added_songs << song;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!added_songs.isEmpty() || !deleted_songs.isEmpty()) {
|
||||||
|
emit SongsDeleted(deleted_songs);
|
||||||
|
emit SongsDiscovered(added_songs);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
void CollectionBackend::ClearAlbumArtAsync(const QString &effective_albumartist, const QString &album, const bool unset) {
|
||||||
|
|
||||||
|
QMetaObject::invokeMethod(this, "ClearAlbumArt", Qt::QueuedConnection, Q_ARG(QString, effective_albumartist), Q_ARG(QString, album), Q_ARG(bool, unset));
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
void CollectionBackend::ClearAlbumArt(const QString &effective_albumartist, const QString &album, const bool art_unset) {
|
||||||
|
|
||||||
|
QMutexLocker l(db_->Mutex());
|
||||||
|
QSqlDatabase db(db_->Connect());
|
||||||
|
|
||||||
|
CollectionQuery query(db, songs_table_, fts_table_);
|
||||||
|
query.SetColumnSpec("ROWID, " + Song::kColumnSpec);
|
||||||
|
query.AddWhere("effective_albumartist", effective_albumartist);
|
||||||
|
query.AddWhere("album", album);
|
||||||
|
|
||||||
|
if (!query.Exec()) {
|
||||||
|
ReportErrors(query);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
SongList deleted_songs;
|
||||||
|
while (query.Next()) {
|
||||||
|
Song song(source_);
|
||||||
|
song.InitFromQuery(query, true);
|
||||||
|
deleted_songs << song;
|
||||||
|
}
|
||||||
|
|
||||||
|
SqlQuery q(db);
|
||||||
|
q.prepare(QString("UPDATE %1 SET art_embedded = 0, art_automatic = '', art_manual = '', art_unset = :art_unset WHERE effective_albumartist = :effective_albumartist AND album = :album AND unavailable = 0").arg(songs_table_));
|
||||||
|
q.BindValue(":art_unset", art_unset ? 1 : 0);
|
||||||
q.BindValue(":effective_albumartist", effective_albumartist);
|
q.BindValue(":effective_albumartist", effective_albumartist);
|
||||||
q.BindValue(":album", album);
|
q.BindValue(":album", album);
|
||||||
|
|
||||||
@@ -1652,7 +1766,6 @@ void CollectionBackend::UpdateAutomaticAlbumArt(const QString &effective_albumar
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Now get the updated songs
|
|
||||||
if (!query.Exec()) {
|
if (!query.Exec()) {
|
||||||
ReportErrors(query);
|
ReportErrors(query);
|
||||||
return;
|
return;
|
||||||
@@ -1775,29 +1888,54 @@ 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);
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void CollectionBackend::DeleteAllAsync() {
|
void CollectionBackend::DeleteAllAsync() {
|
||||||
|
|
||||||
QMetaObject::invokeMethod(this, "DeleteAll", Qt::QueuedConnection);
|
QMetaObject::invokeMethod(this, &CollectionBackend::DeleteAll, Qt::QueuedConnection);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1863,7 +2001,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));
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1926,7 +2064,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()) {
|
||||||
@@ -1948,7 +2086,7 @@ void CollectionBackend::UpdatePlayCount(const QString &artist, const QString &ti
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
emit SongsStatisticsChanged(SongList() << songs);
|
emit SongsStatisticsChanged(SongList() << songs, save_tags);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
* Strawberry Music Player
|
* Strawberry Music Player
|
||||||
* This file was part of Clementine.
|
* This file was part of Clementine.
|
||||||
* Copyright 2010, David Sansome <me@davidsansome.com>
|
* Copyright 2010, David Sansome <me@davidsansome.com>
|
||||||
* Copyright 2018-2021, Jonas Kvinge <jonas@jkvinge.net>
|
* Copyright 2018-2023, Jonas Kvinge <jonas@jkvinge.net>
|
||||||
*
|
*
|
||||||
* Strawberry is free software: you can redistribute it and/or modify
|
* Strawberry is free software: you can redistribute it and/or modify
|
||||||
* it under the terms of the GNU General Public License as published by
|
* it under the terms of the GNU General Public License as published by
|
||||||
@@ -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,12 +54,14 @@ class CollectionBackendInterface : public QObject {
|
|||||||
explicit CollectionBackendInterface(QObject *parent = nullptr) : QObject(parent) {}
|
explicit CollectionBackendInterface(QObject *parent = nullptr) : QObject(parent) {}
|
||||||
|
|
||||||
struct Album {
|
struct Album {
|
||||||
Album() {}
|
Album() : art_embedded(false), art_unset(false), 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 bool _art_embedded, const QUrl &_art_automatic, const QUrl &_art_manual, const bool _art_unset, const QList<QUrl> &_urls, const Song::FileType _filetype, const QString &_cue_path)
|
||||||
: album_artist(_album_artist),
|
: album_artist(_album_artist),
|
||||||
album(_album),
|
album(_album),
|
||||||
|
art_embedded(_art_embedded),
|
||||||
art_automatic(_art_automatic),
|
art_automatic(_art_automatic),
|
||||||
art_manual(_art_manual),
|
art_manual(_art_manual),
|
||||||
|
art_unset(_art_unset),
|
||||||
urls(_urls),
|
urls(_urls),
|
||||||
filetype(_filetype),
|
filetype(_filetype),
|
||||||
cue_path(_cue_path) {}
|
cue_path(_cue_path) {}
|
||||||
@@ -66,8 +69,10 @@ class CollectionBackendInterface : public QObject {
|
|||||||
QString album_artist;
|
QString album_artist;
|
||||||
QString album;
|
QString album;
|
||||||
|
|
||||||
|
bool art_embedded;
|
||||||
QUrl art_automatic;
|
QUrl art_automatic;
|
||||||
QUrl art_manual;
|
QUrl art_manual;
|
||||||
|
bool art_unset;
|
||||||
QList<QUrl> urls;
|
QList<QUrl> urls;
|
||||||
Song::FileType filetype;
|
Song::FileType filetype;
|
||||||
QString cue_path;
|
QString cue_path;
|
||||||
@@ -90,26 +95,28 @@ 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 UpdateEmbeddedAlbumArtAsync(const QString &effective_albumartist, const QString &album, const bool art_embedded) = 0;
|
||||||
virtual void UpdateAutomaticAlbumArtAsync(const QString &effective_albumartist, const QString &album, const QUrl &cover_url, const bool clear_art_manual = false) = 0;
|
virtual void UpdateManualAlbumArtAsync(const QString &effective_albumartist, const QString &album, const QUrl &art_manual) = 0;
|
||||||
|
virtual void UnsetAlbumArtAsync(const QString &effective_albumartist, const QString &album) = 0;
|
||||||
|
virtual void ClearAlbumArtAsync(const QString &effective_albumartist, const QString &album, const bool art_unset) = 0;
|
||||||
|
|
||||||
virtual Album GetAlbumArt(const QString &effective_albumartist, const QString &album) = 0;
|
virtual Album GetAlbumArt(const QString &effective_albumartist, const QString &album) = 0;
|
||||||
|
|
||||||
@@ -124,7 +131,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 {
|
||||||
@@ -159,27 +166,29 @@ 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 UpdateEmbeddedAlbumArtAsync(const QString &effective_albumartist, const QString &album, const bool art_embedded) override;
|
||||||
void UpdateAutomaticAlbumArtAsync(const QString &effective_albumartist, const QString &album, const QUrl &cover_url, const bool clear_art_manual = false) override;
|
void UpdateManualAlbumArtAsync(const QString &effective_albumartist, const QString &album, const QUrl &art_manual) override;
|
||||||
|
void UnsetAlbumArtAsync(const QString &effective_albumartist, const QString &album) override;
|
||||||
|
void ClearAlbumArtAsync(const QString &effective_albumartist, const QString &album, const bool art_unset) override;
|
||||||
|
|
||||||
Album GetAlbumArt(const QString &effective_albumartist, const QString &album) override;
|
Album GetAlbumArt(const QString &effective_albumartist, const QString &album) override;
|
||||||
|
|
||||||
@@ -192,14 +201,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();
|
||||||
|
|
||||||
@@ -228,20 +238,24 @@ 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 UpdateEmbeddedAlbumArt(const QString &effective_albumartist, const QString &album, const bool art_embedded);
|
||||||
void UpdateAutomaticAlbumArt(const QString &effective_albumartist, const QString &album, const QUrl &cover_url, const bool clear_art_manual = false);
|
void UpdateManualAlbumArt(const QString &effective_albumartist, const QString &album, const QUrl &art_manual);
|
||||||
|
void UnsetAlbumArt(const QString &effective_albumartist, const QString &album);
|
||||||
|
void ClearAlbumArt(const QString &effective_albumartist, const QString &album, const bool art_unset);
|
||||||
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);
|
||||||
@@ -250,23 +264,23 @@ 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(const CollectionDirectory &dir, const CollectionSubdirectoryList &subdir);
|
||||||
void DirectoryDeleted(Directory);
|
void DirectoryDeleted(const CollectionDirectory &dir);
|
||||||
|
|
||||||
void SongsDiscovered(SongList);
|
void SongsDiscovered(const SongList &songs);
|
||||||
void SongsDeleted(SongList);
|
void SongsDeleted(const SongList &songs);
|
||||||
void SongsStatisticsChanged(SongList);
|
void SongsStatisticsChanged(const SongList &songs, const bool save_tags = false);
|
||||||
|
|
||||||
void DatabaseReset();
|
void DatabaseReset();
|
||||||
|
|
||||||
void TotalSongCountUpdated(int);
|
void TotalSongCountUpdated(const int count);
|
||||||
void TotalArtistCountUpdated(int);
|
void TotalArtistCountUpdated(const int count);
|
||||||
void TotalAlbumCountUpdated(int);
|
void TotalAlbumCountUpdated(const int count);
|
||||||
void SongsRatingChanged(SongList, bool);
|
void SongsRatingChanged(const SongList &songs, const bool save_tags);
|
||||||
|
|
||||||
void ExitFinished();
|
void ExitFinished();
|
||||||
|
|
||||||
void Error(QString);
|
void Error(const QString &error);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
struct CompilationInfo {
|
struct CompilationInfo {
|
||||||
@@ -280,9 +294,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);
|
||||||
@@ -299,7 +313,6 @@ class CollectionBackend : public CollectionBackendInterface {
|
|||||||
QString subdirs_table_;
|
QString subdirs_table_;
|
||||||
QString fts_table_;
|
QString fts_table_;
|
||||||
QThread *original_thread_;
|
QThread *original_thread_;
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif // COLLECTIONBACKEND_H
|
#endif // COLLECTIONBACKEND_H
|
||||||
|
|||||||
@@ -18,43 +18,40 @@
|
|||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#ifndef DIRECTORY_H
|
#ifndef COLLECTIONDIRECTORY_H
|
||||||
#define DIRECTORY_H
|
#define COLLECTIONDIRECTORY_H
|
||||||
|
|
||||||
#include "config.h"
|
#include "config.h"
|
||||||
|
|
||||||
#include <QMetaType>
|
#include <QMetaType>
|
||||||
#include <QList>
|
#include <QList>
|
||||||
#include <QString>
|
#include <QString>
|
||||||
#include <QSqlQuery>
|
|
||||||
|
|
||||||
struct Directory {
|
struct CollectionDirectory {
|
||||||
Directory() : id(-1) {}
|
CollectionDirectory() : id(-1) {}
|
||||||
|
|
||||||
bool operator==(const Directory &other) const {
|
bool operator==(const CollectionDirectory &other) const {
|
||||||
return path == other.path && id == other.id;
|
return path == other.path && id == other.id;
|
||||||
}
|
}
|
||||||
|
|
||||||
QString path;
|
QString path;
|
||||||
int id;
|
int id;
|
||||||
};
|
};
|
||||||
Q_DECLARE_METATYPE(Directory)
|
Q_DECLARE_METATYPE(CollectionDirectory)
|
||||||
|
|
||||||
using DirectoryList = QList<Directory>;
|
using CollectionDirectoryList = QList<CollectionDirectory>;
|
||||||
Q_DECLARE_METATYPE(DirectoryList)
|
Q_DECLARE_METATYPE(CollectionDirectoryList)
|
||||||
|
|
||||||
|
struct CollectionSubdirectory {
|
||||||
struct Subdirectory {
|
CollectionSubdirectory() : directory_id(-1), mtime(0) {}
|
||||||
Subdirectory() : directory_id(-1), mtime(0) {}
|
|
||||||
|
|
||||||
int directory_id;
|
int directory_id;
|
||||||
QString path;
|
QString path;
|
||||||
qint64 mtime;
|
qint64 mtime;
|
||||||
};
|
};
|
||||||
Q_DECLARE_METATYPE(Subdirectory)
|
Q_DECLARE_METATYPE(CollectionSubdirectory)
|
||||||
|
|
||||||
using SubdirectoryList = QList<Subdirectory>;
|
using CollectionSubdirectoryList = QList<CollectionSubdirectory>;
|
||||||
Q_DECLARE_METATYPE(SubdirectoryList)
|
Q_DECLARE_METATYPE(CollectionSubdirectoryList)
|
||||||
|
|
||||||
#endif // DIRECTORY_H
|
|
||||||
|
|
||||||
|
#endif // COLLECTIONDIRECTORY_H
|
||||||
@@ -30,7 +30,7 @@
|
|||||||
#include "core/iconloader.h"
|
#include "core/iconloader.h"
|
||||||
#include "core/musicstorage.h"
|
#include "core/musicstorage.h"
|
||||||
#include "utilities/diskutils.h"
|
#include "utilities/diskutils.h"
|
||||||
#include "directory.h"
|
#include "collectiondirectory.h"
|
||||||
#include "collectionbackend.h"
|
#include "collectionbackend.h"
|
||||||
#include "collectiondirectorymodel.h"
|
#include "collectiondirectorymodel.h"
|
||||||
|
|
||||||
@@ -46,7 +46,7 @@ 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);
|
||||||
@@ -56,7 +56,7 @@ void CollectionDirectoryModel::DirectoryDiscovered(const Directory &dir) {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
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,14 +89,14 @@ 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:
|
||||||
void UpPressed();
|
void UpPressed();
|
||||||
void DownPressed();
|
void DownPressed();
|
||||||
void ReturnPressed();
|
void ReturnPressed();
|
||||||
void Filter(QString text);
|
void Filter(const QString &text);
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
void keyReleaseEvent(QKeyEvent *e) override;
|
void keyReleaseEvent(QKeyEvent *e) override;
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -2,7 +2,7 @@
|
|||||||
* Strawberry Music Player
|
* Strawberry Music Player
|
||||||
* This file was part of Clementine.
|
* This file was part of Clementine.
|
||||||
* Copyright 2010, David Sansome <me@davidsansome.com>
|
* Copyright 2010, David Sansome <me@davidsansome.com>
|
||||||
* Copyright 2018-2021, Jonas Kvinge <jonas@jkvinge.net>
|
* Copyright 2018-2023, Jonas Kvinge <jonas@jkvinge.net>
|
||||||
*
|
*
|
||||||
* Strawberry is free software: you can redistribute it and/or modify
|
* Strawberry is free software: you can redistribute it and/or modify
|
||||||
* it under the terms of the GNU General Public License as published by
|
* it under the terms of the GNU General Public License as published by
|
||||||
@@ -49,9 +49,11 @@
|
|||||||
#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 "collectionquery.h"
|
|
||||||
#include "collectionitem.h"
|
|
||||||
#include "covermanager/albumcoverloaderoptions.h"
|
#include "covermanager/albumcoverloaderoptions.h"
|
||||||
|
#include "collectionfilteroptions.h"
|
||||||
|
#include "collectionquery.h"
|
||||||
|
#include "collectionqueryoptions.h"
|
||||||
|
#include "collectionitem.h"
|
||||||
|
|
||||||
class QSettings;
|
class QSettings;
|
||||||
|
|
||||||
@@ -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; }
|
||||||
|
|
||||||
@@ -197,15 +199,15 @@ class CollectionModel : public SimpleTreeModel<CollectionItem> {
|
|||||||
static QString ContainerKey(const GroupBy group_by, const bool separate_albums_by_grouping, const Song &song);
|
static QString ContainerKey(const GroupBy group_by, const bool separate_albums_by_grouping, const Song &song);
|
||||||
|
|
||||||
signals:
|
signals:
|
||||||
void TotalSongCountUpdated(int count);
|
void TotalSongCountUpdated(const int count);
|
||||||
void TotalArtistCountUpdated(int count);
|
void TotalArtistCountUpdated(const int count);
|
||||||
void TotalAlbumCountUpdated(int count);
|
void TotalAlbumCountUpdated(const int count);
|
||||||
void GroupingChanged(CollectionModel::Grouping g, bool separate_albums_by_grouping);
|
void GroupingChanged(const CollectionModel::Grouping g, const 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);
|
||||||
@@ -264,6 +267,7 @@ class CollectionModel : public SimpleTreeModel<CollectionItem> {
|
|||||||
// Helpers
|
// Helpers
|
||||||
static bool IsCompilationArtistNode(const CollectionItem *node) { return node == node->parent->compilation_artist_node_; }
|
static bool IsCompilationArtistNode(const CollectionItem *node) { return node == node->parent->compilation_artist_node_; }
|
||||||
QString AlbumIconPixmapCacheKey(const QModelIndex &idx) const;
|
QString AlbumIconPixmapCacheKey(const QModelIndex &idx) const;
|
||||||
|
QUrl AlbumIconPixmapDiskCacheKey(const QString &cache_key) const;
|
||||||
QVariant AlbumIcon(const QModelIndex &idx);
|
QVariant AlbumIcon(const QModelIndex &idx);
|
||||||
QVariant data(const CollectionItem *item, const int role) const;
|
QVariant data(const CollectionItem *item, const int role) const;
|
||||||
bool CompareItems(const CollectionItem *a, const CollectionItem *b) const;
|
bool CompareItems(const CollectionItem *a, const CollectionItem *b) const;
|
||||||
@@ -279,7 +283,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_;
|
||||||
|
|
||||||
@@ -306,7 +310,7 @@ class CollectionModel : public SimpleTreeModel<CollectionItem> {
|
|||||||
bool use_disk_cache_;
|
bool use_disk_cache_;
|
||||||
bool use_lazy_loading_;
|
bool use_lazy_loading_;
|
||||||
|
|
||||||
AlbumCoverLoaderOptions cover_loader_options_;
|
AlbumCoverLoaderOptions::Types cover_types_;
|
||||||
|
|
||||||
using ItemAndCacheKey = QPair<CollectionItem*, QString>;
|
using ItemAndCacheKey = QPair<CollectionItem*, QString>;
|
||||||
QMap<quint64, ItemAndCacheKey> pending_art_;
|
QMap<quint64, ItemAndCacheKey> pending_art_;
|
||||||
|
|||||||
@@ -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
|
||||||
@@ -117,7 +117,7 @@ void CollectionView::SaveFocus() {
|
|||||||
|
|
||||||
QModelIndex current = currentIndex();
|
QModelIndex current = currentIndex();
|
||||||
QVariant type = model()->data(current, CollectionModel::Role_Type);
|
QVariant type = model()->data(current, CollectionModel::Role_Type);
|
||||||
if (!type.isValid() || !(type.toInt() == CollectionItem::Type_Song || type.toInt() == CollectionItem::Type_Container || type.toInt() == CollectionItem::Type_Divider)) {
|
if (!type.isValid() || (type.toInt() != CollectionItem::Type_Song && type.toInt() != CollectionItem::Type_Container && type.toInt() != CollectionItem::Type_Divider)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -154,7 +154,7 @@ void CollectionView::SaveContainerPath(const QModelIndex &child) {
|
|||||||
|
|
||||||
QModelIndex current = model()->parent(child);
|
QModelIndex current = model()->parent(child);
|
||||||
QVariant type = model()->data(current, CollectionModel::Role_Type);
|
QVariant type = model()->data(current, CollectionModel::Role_Type);
|
||||||
if (!type.isValid() || !(type.toInt() == CollectionItem::Type_Container || type.toInt() == CollectionItem::Type_Divider)) {
|
if (!type.isValid() || (type.toInt() != CollectionItem::Type_Container && type.toInt() != CollectionItem::Type_Divider)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -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
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -89,7 +89,7 @@ class CollectionView : public AutoExpandingTreeView {
|
|||||||
void TotalSongCountUpdated_();
|
void TotalSongCountUpdated_();
|
||||||
void TotalArtistCountUpdated_();
|
void TotalArtistCountUpdated_();
|
||||||
void TotalAlbumCountUpdated_();
|
void TotalAlbumCountUpdated_();
|
||||||
void Error(QString);
|
void Error(const QString &error);
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
// QWidget
|
// QWidget
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
* Strawberry Music Player
|
* Strawberry Music Player
|
||||||
* This file was part of Clementine.
|
* This file was part of Clementine.
|
||||||
* Copyright 2010, David Sansome <me@davidsansome.com>
|
* Copyright 2010, David Sansome <me@davidsansome.com>
|
||||||
* Copyright 2018-2021, Jonas Kvinge <jonas@jkvinge.net>
|
* Copyright 2018-2023, Jonas Kvinge <jonas@jkvinge.net>
|
||||||
*
|
*
|
||||||
* Strawberry is free software: you can redistribute it and/or modify
|
* Strawberry is free software: you can redistribute it and/or modify
|
||||||
* it under the terms of the GNU General Public License as published by
|
* it under the terms of the GNU General Public License as published by
|
||||||
@@ -50,7 +50,7 @@
|
|||||||
#include "core/taskmanager.h"
|
#include "core/taskmanager.h"
|
||||||
#include "utilities/imageutils.h"
|
#include "utilities/imageutils.h"
|
||||||
#include "utilities/timeconstants.h"
|
#include "utilities/timeconstants.h"
|
||||||
#include "directory.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),
|
||||||
@@ -109,13 +108,14 @@ CollectionWatcher::CollectionWatcher(Song::Source source, QObject *parent)
|
|||||||
|
|
||||||
ReloadSettings();
|
ReloadSettings();
|
||||||
|
|
||||||
|
QObject::connect(fs_watcher_, &FileSystemWatcherInterface::PathChanged, this, &CollectionWatcher::DirectoryChanged, Qt::UniqueConnection);
|
||||||
QObject::connect(rescan_timer_, &QTimer::timeout, this, &CollectionWatcher::RescanPathsNow);
|
QObject::connect(rescan_timer_, &QTimer::timeout, this, &CollectionWatcher::RescanPathsNow);
|
||||||
QObject::connect(periodic_scan_timer_, &QTimer::timeout, this, &CollectionWatcher::IncrementalScanCheck);
|
QObject::connect(periodic_scan_timer_, &QTimer::timeout, this, &CollectionWatcher::IncrementalScanCheck);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void CollectionWatcher::ExitAsync() {
|
void CollectionWatcher::ExitAsync() {
|
||||||
QMetaObject::invokeMethod(this, "Exit", Qt::QueuedConnection);
|
QMetaObject::invokeMethod(this, &CollectionWatcher::Exit, Qt::QueuedConnection);
|
||||||
}
|
}
|
||||||
|
|
||||||
void CollectionWatcher::Exit() {
|
void CollectionWatcher::Exit() {
|
||||||
@@ -131,7 +131,7 @@ void CollectionWatcher::Exit() {
|
|||||||
|
|
||||||
void CollectionWatcher::ReloadSettingsAsync() {
|
void CollectionWatcher::ReloadSettingsAsync() {
|
||||||
|
|
||||||
QMetaObject::invokeMethod(this, "ReloadSettings", Qt::QueuedConnection);
|
QMetaObject::invokeMethod(this, &CollectionWatcher::ReloadSettings, Qt::QueuedConnection);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -143,7 +143,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();
|
||||||
}
|
}
|
||||||
@@ -156,10 +156,10 @@ void CollectionWatcher::ReloadSettings() {
|
|||||||
overwrite_rating_ = s.value("overwrite_rating", false).toBool();
|
overwrite_rating_ = s.value("overwrite_rating", false).toBool();
|
||||||
s.endGroup();
|
s.endGroup();
|
||||||
|
|
||||||
best_image_filters_.clear();
|
best_art_filters_.clear();
|
||||||
for (const QString &filter : filters) {
|
for (const QString &filter : filters) {
|
||||||
QString str = filter.trimmed();
|
QString str = filter.trimmed();
|
||||||
if (!str.isEmpty()) best_image_filters_ << str;
|
if (!str.isEmpty()) best_art_filters_ << str;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!monitor_ && was_monitoring_before) {
|
if (!monitor_ && was_monitoring_before) {
|
||||||
@@ -167,9 +167,9 @@ 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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -239,7 +239,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 +272,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 +281,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 +329,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 +342,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 +363,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 +373,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 +385,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 +395,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 +411,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 +440,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 +463,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();
|
||||||
@@ -539,8 +539,8 @@ void CollectionWatcher::ScanSubdirectory(const QString &path, const Subdirectory
|
|||||||
bool changed = (matching_song.mtime() != qMax(fileinfo.lastModified().toSecsSinceEpoch(), matching_song_cue_mtime)) || cue_deleted || cue_added || cue_changed;
|
bool changed = (matching_song.mtime() != qMax(fileinfo.lastModified().toSecsSinceEpoch(), matching_song_cue_mtime)) || cue_deleted || cue_added || cue_changed;
|
||||||
|
|
||||||
// Also want to look to see whether the album art has changed
|
// Also want to look to see whether the album art has changed
|
||||||
QUrl image = ImageForSong(file, album_art);
|
const QUrl art_automatic = ArtForSong(file, album_art);
|
||||||
if ((matching_song.art_automatic().isEmpty() && !image.isEmpty()) || (!matching_song.art_automatic().isEmpty() && !matching_song.has_embedded_cover() && !QFile::exists(matching_song.art_automatic().toLocalFile()))) {
|
if (matching_song.art_automatic() != art_automatic || (!matching_song.art_automatic().isEmpty() && !matching_song.art_automatic_is_valid())) {
|
||||||
changed = true;
|
changed = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -573,16 +573,16 @@ void CollectionWatcher::ScanSubdirectory(const QString &path, const Subdirectory
|
|||||||
#endif
|
#endif
|
||||||
|
|
||||||
if (new_cue.isEmpty() || new_cue_mtime == 0) { // If no CUE or it's about to lose it.
|
if (new_cue.isEmpty() || new_cue_mtime == 0) { // If no CUE or it's about to lose it.
|
||||||
UpdateNonCueAssociatedSong(file, fingerprint, matching_songs, image, cue_deleted, t);
|
UpdateNonCueAssociatedSong(file, fingerprint, matching_songs, art_automatic, cue_deleted, t);
|
||||||
}
|
}
|
||||||
else { // If CUE associated.
|
else { // If CUE associated.
|
||||||
UpdateCueAssociatedSongs(file, path, fingerprint, new_cue, image, matching_songs, t);
|
UpdateCueAssociatedSongs(file, path, fingerprint, new_cue, art_automatic, matching_songs, t);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Nothing has changed - mark the song available without re-scanning
|
// Nothing has changed - mark the song available without re-scanning
|
||||||
else if (matching_song.is_unavailable()) {
|
else if (matching_song.unavailable()) {
|
||||||
t->readded_songs << matching_songs;
|
t->readded_songs << matching_songs;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -633,13 +633,13 @@ void CollectionWatcher::ScanSubdirectory(const QString &path, const Subdirectory
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Get new album art
|
// Get new album art
|
||||||
QUrl image = ImageForSong(file, album_art);
|
const QUrl art_automatic = ArtForSong(file, album_art);
|
||||||
|
|
||||||
if (new_cue.isEmpty() || new_cue_mtime == 0) { // If no CUE or it's about to lose it.
|
if (new_cue.isEmpty() || new_cue_mtime == 0) { // If no CUE or it's about to lose it.
|
||||||
UpdateNonCueAssociatedSong(file, fingerprint, matching_songs, image, matching_songs_has_cue && new_cue_mtime == 0, t);
|
UpdateNonCueAssociatedSong(file, fingerprint, matching_songs, art_automatic, matching_songs_has_cue && new_cue_mtime == 0, t);
|
||||||
}
|
}
|
||||||
else { // If CUE associated.
|
else { // If CUE associated.
|
||||||
UpdateCueAssociatedSongs(file, path, fingerprint, new_cue, image, matching_songs, t);
|
UpdateCueAssociatedSongs(file, path, fingerprint, new_cue, art_automatic, matching_songs, t);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -653,12 +653,12 @@ void CollectionWatcher::ScanSubdirectory(const QString &path, const Subdirectory
|
|||||||
|
|
||||||
qLog(Debug) << file << "is new.";
|
qLog(Debug) << file << "is new.";
|
||||||
|
|
||||||
// Choose an image for the song(s)
|
// Choose art for the song(s)
|
||||||
QUrl image = ImageForSong(file, album_art);
|
const QUrl art_automatic = ArtForSong(file, album_art);
|
||||||
|
|
||||||
for (Song song : songs) {
|
for (Song song : songs) {
|
||||||
song.set_directory_id(t->dir());
|
song.set_directory_id(t->dir());
|
||||||
if (song.art_automatic().isEmpty()) song.set_art_automatic(image);
|
if (song.art_automatic().isEmpty()) song.set_art_automatic(art_automatic);
|
||||||
t->new_songs << song;
|
t->new_songs << song;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -669,14 +669,14 @@ void CollectionWatcher::ScanSubdirectory(const QString &path, const Subdirectory
|
|||||||
// Look for deleted songs
|
// Look for deleted songs
|
||||||
for (const Song &song : songs_in_db) {
|
for (const Song &song : songs_in_db) {
|
||||||
QString file = song.url().toLocalFile();
|
QString file = song.url().toLocalFile();
|
||||||
if (!song.is_unavailable() && !files_on_disk.contains(file) && !t->files_changed_path_.contains(file)) {
|
if (!song.unavailable() && !files_on_disk.contains(file) && !t->files_changed_path_.contains(file)) {
|
||||||
qLog(Debug) << "Song deleted from disk:" << file;
|
qLog(Debug) << "Song deleted from disk:" << file;
|
||||||
t->deleted_songs << song;
|
t->deleted_songs << song;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 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 +688,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);
|
||||||
}
|
}
|
||||||
@@ -704,7 +704,7 @@ void CollectionWatcher::UpdateCueAssociatedSongs(const QString &file,
|
|||||||
const QString &path,
|
const QString &path,
|
||||||
const QString &fingerprint,
|
const QString &fingerprint,
|
||||||
const QString &matching_cue,
|
const QString &matching_cue,
|
||||||
const QUrl &image,
|
const QUrl &art_automatic,
|
||||||
const SongList &old_cue_songs,
|
const SongList &old_cue_songs,
|
||||||
ScanTransaction *t) {
|
ScanTransaction *t) {
|
||||||
|
|
||||||
@@ -733,7 +733,7 @@ void CollectionWatcher::UpdateCueAssociatedSongs(const QString &file,
|
|||||||
if (sections_map.contains(new_cue_song.beginning_nanosec())) { // Changed section
|
if (sections_map.contains(new_cue_song.beginning_nanosec())) { // Changed section
|
||||||
const Song matching_cue_song = sections_map[new_cue_song.beginning_nanosec()];
|
const Song matching_cue_song = sections_map[new_cue_song.beginning_nanosec()];
|
||||||
new_cue_song.set_id(matching_cue_song.id());
|
new_cue_song.set_id(matching_cue_song.id());
|
||||||
if (!new_cue_song.has_embedded_cover()) new_cue_song.set_art_automatic(image);
|
new_cue_song.set_art_automatic(art_automatic);
|
||||||
new_cue_song.MergeUserSetData(matching_cue_song, true, true);
|
new_cue_song.MergeUserSetData(matching_cue_song, true, true);
|
||||||
AddChangedSong(file, matching_cue_song, new_cue_song, t);
|
AddChangedSong(file, matching_cue_song, new_cue_song, t);
|
||||||
used_ids.insert(matching_cue_song.id());
|
used_ids.insert(matching_cue_song.id());
|
||||||
@@ -755,7 +755,7 @@ void CollectionWatcher::UpdateCueAssociatedSongs(const QString &file,
|
|||||||
void CollectionWatcher::UpdateNonCueAssociatedSong(const QString &file,
|
void CollectionWatcher::UpdateNonCueAssociatedSong(const QString &file,
|
||||||
const QString &fingerprint,
|
const QString &fingerprint,
|
||||||
const SongList &matching_songs,
|
const SongList &matching_songs,
|
||||||
const QUrl &image,
|
const QUrl &art_automatic,
|
||||||
const bool cue_deleted,
|
const bool cue_deleted,
|
||||||
ScanTransaction *t) {
|
ScanTransaction *t) {
|
||||||
|
|
||||||
@@ -763,7 +763,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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -776,7 +776,7 @@ void CollectionWatcher::UpdateNonCueAssociatedSong(const QString &file,
|
|||||||
song_on_disk.set_directory_id(t->dir());
|
song_on_disk.set_directory_id(t->dir());
|
||||||
song_on_disk.set_id(matching_song.id());
|
song_on_disk.set_id(matching_song.id());
|
||||||
song_on_disk.set_fingerprint(fingerprint);
|
song_on_disk.set_fingerprint(fingerprint);
|
||||||
if (!song_on_disk.has_embedded_cover()) song_on_disk.set_art_automatic(image);
|
song_on_disk.set_art_automatic(art_automatic);
|
||||||
song_on_disk.MergeUserSetData(matching_song, !overwrite_playcount_, !overwrite_rating_);
|
song_on_disk.MergeUserSetData(matching_song, !overwrite_playcount_, !overwrite_rating_);
|
||||||
AddChangedSong(file, matching_song, song_on_disk, t);
|
AddChangedSong(file, matching_song, song_on_disk, t);
|
||||||
}
|
}
|
||||||
@@ -838,7 +838,7 @@ void CollectionWatcher::AddChangedSong(const QString &file, const Song &matching
|
|||||||
bool notify_new = false;
|
bool notify_new = false;
|
||||||
QStringList changes;
|
QStringList changes;
|
||||||
|
|
||||||
if (matching_song.is_unavailable()) {
|
if (matching_song.unavailable()) {
|
||||||
qLog(Debug) << "unavailable song" << file << "restored.";
|
qLog(Debug) << "unavailable song" << file << "restored.";
|
||||||
notify_new = true;
|
notify_new = true;
|
||||||
}
|
}
|
||||||
@@ -855,10 +855,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,17 +913,16 @@ 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;
|
||||||
|
|
||||||
QObject::connect(fs_watcher_, &FileSystemWatcherInterface::PathChanged, this, &CollectionWatcher::DirectoryChanged, Qt::UniqueConnection);
|
|
||||||
fs_watcher_->AddPath(path);
|
fs_watcher_->AddPath(path);
|
||||||
subdir_mapping_[path] = dir;
|
subdir_mapping_[path] = dir;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
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;
|
||||||
@@ -1024,20 +1039,20 @@ void CollectionWatcher::RescanPathsNow() {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
QString CollectionWatcher::PickBestImage(const QStringList &images) {
|
QString CollectionWatcher::PickBestArt(const QStringList &art_automatic_list) {
|
||||||
|
|
||||||
// This is used when there is more than one image in a directory.
|
// This is used when there is more than one image in a directory.
|
||||||
// Pick the biggest image that matches the most important filter
|
// Pick the biggest image that matches the most important filter
|
||||||
|
|
||||||
QStringList filtered;
|
QStringList filtered;
|
||||||
|
|
||||||
for (const QString &filter_text : best_image_filters_) {
|
for (const QString &filter_text : best_art_filters_) {
|
||||||
// The images in the images list are represented by a full path, so we need to isolate just the filename
|
// The images in the images list are represented by a full path, so we need to isolate just the filename
|
||||||
for (const QString &image : images) {
|
for (const QString &art_automatic : art_automatic_list) {
|
||||||
QFileInfo fileinfo(image);
|
QFileInfo fileinfo(art_automatic);
|
||||||
QString filename(fileinfo.fileName());
|
QString filename(fileinfo.fileName());
|
||||||
if (filename.contains(filter_text, Qt::CaseInsensitive))
|
if (filename.contains(filter_text, Qt::CaseInsensitive))
|
||||||
filtered << image;
|
filtered << art_automatic;
|
||||||
}
|
}
|
||||||
|
|
||||||
// We assume the filters are give in the order best to worst, so if we've got a result, we go with it.
|
// We assume the filters are give in the order best to worst, so if we've got a result, we go with it.
|
||||||
@@ -1047,7 +1062,7 @@ QString CollectionWatcher::PickBestImage(const QStringList &images) {
|
|||||||
|
|
||||||
if (filtered.isEmpty()) {
|
if (filtered.isEmpty()) {
|
||||||
// The filter was too restrictive, just use the original list
|
// The filter was too restrictive, just use the original list
|
||||||
filtered = images;
|
filtered = art_automatic_list;
|
||||||
}
|
}
|
||||||
|
|
||||||
int biggest_size = 0;
|
int biggest_size = 0;
|
||||||
@@ -1070,20 +1085,21 @@ QString CollectionWatcher::PickBestImage(const QStringList &images) {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
QUrl CollectionWatcher::ImageForSong(const QString &path, QMap<QString, QStringList> &album_art) {
|
QUrl CollectionWatcher::ArtForSong(const QString &path, QMap<QString, QStringList> &art_automatic_list) {
|
||||||
|
|
||||||
QString dir(DirectoryPart(path));
|
QString dir(DirectoryPart(path));
|
||||||
|
|
||||||
if (album_art.contains(dir)) {
|
if (art_automatic_list.contains(dir)) {
|
||||||
if (album_art[dir].count() == 1) {
|
if (art_automatic_list[dir].count() == 1) {
|
||||||
return QUrl::fromLocalFile(album_art[dir][0]);
|
return QUrl::fromLocalFile(art_automatic_list[dir][0]);
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
QString best_image = PickBestImage(album_art[dir]);
|
const QString best_art = PickBestArt(art_automatic_list[dir]);
|
||||||
album_art[dir] = QStringList() << best_image;
|
art_automatic_list[dir] = QStringList() << best_art;
|
||||||
return QUrl::fromLocalFile(best_image);
|
return QUrl::fromLocalFile(best_art);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return QUrl();
|
return QUrl();
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -1103,25 +1119,13 @@ void CollectionWatcher::SetRescanPaused(bool pause) {
|
|||||||
|
|
||||||
void CollectionWatcher::IncrementalScanAsync() {
|
void CollectionWatcher::IncrementalScanAsync() {
|
||||||
|
|
||||||
QMetaObject::invokeMethod(this, "IncrementalScanNow", Qt::QueuedConnection);
|
QMetaObject::invokeMethod(this, &CollectionWatcher::IncrementalScanNow, Qt::QueuedConnection);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void CollectionWatcher::FullScanAsync() {
|
void CollectionWatcher::FullScanAsync() {
|
||||||
|
|
||||||
QMetaObject::invokeMethod(this, "FullScanNow", Qt::QueuedConnection);
|
QMetaObject::invokeMethod(this, &CollectionWatcher::FullScanNow, Qt::QueuedConnection);
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1139,48 +1143,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 +1166,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 +1193,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 +1215,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 +1228,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();
|
||||||
|
|
||||||
|
}
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
* Strawberry Music Player
|
* Strawberry Music Player
|
||||||
* This file was part of Clementine.
|
* This file was part of Clementine.
|
||||||
* Copyright 2010, David Sansome <me@davidsansome.com>
|
* Copyright 2010, David Sansome <me@davidsansome.com>
|
||||||
* Copyright 2018-2021, Jonas Kvinge <jonas@jkvinge.net>
|
* Copyright 2018-2023, Jonas Kvinge <jonas@jkvinge.net>
|
||||||
*
|
*
|
||||||
* Strawberry is free software: you can redistribute it and/or modify
|
* Strawberry is free software: you can redistribute it and/or modify
|
||||||
* it under the terms of the GNU General Public License as published by
|
* it under the terms of the GNU General Public License as published by
|
||||||
@@ -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,23 +67,25 @@ class CollectionWatcher : public QObject {
|
|||||||
|
|
||||||
void ExitAsync();
|
void ExitAsync();
|
||||||
|
|
||||||
|
void RescanSongsAsync(const SongList &songs);
|
||||||
|
|
||||||
signals:
|
signals:
|
||||||
void NewOrUpdatedSongs(SongList);
|
void NewOrUpdatedSongs(const SongList &songs);
|
||||||
void SongsMTimeUpdated(SongList);
|
void SongsMTimeUpdated(const SongList &songs);
|
||||||
void SongsDeleted(SongList);
|
void SongsDeleted(const SongList &songs);
|
||||||
void SongsUnavailable(SongList songs, bool unavailable = true);
|
void SongsUnavailable(const SongList &songs, const bool unavailable = true);
|
||||||
void SongsReadded(SongList songs, bool unavailable = false);
|
void SongsReadded(const SongList &songs, const bool unavailable = false);
|
||||||
void SubdirsDiscovered(SubdirectoryList subdirs);
|
void SubdirsDiscovered(const CollectionSubdirectoryList &subdirs);
|
||||||
void SubdirsMTimeUpdated(SubdirectoryList subdirs);
|
void SubdirsMTimeUpdated(const CollectionSubdirectoryList &subdirs);
|
||||||
void CompilationsNeedUpdating();
|
void CompilationsNeedUpdating();
|
||||||
void UpdateLastSeen(int directory_id, int expire_unavailable_songs_days);
|
void UpdateLastSeen(const int directory_id, const int expire_unavailable_songs_days);
|
||||||
void ExitFinished();
|
void ExitFinished();
|
||||||
|
|
||||||
void ScanStarted(int task_id);
|
void ScanStarted(const 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);
|
||||||
@@ -177,17 +178,17 @@ class CollectionWatcher : public QObject {
|
|||||||
inline static QString NoExtensionPart(const QString &fileName);
|
inline static QString NoExtensionPart(const QString &fileName);
|
||||||
inline static QString ExtensionPart(const QString &fileName);
|
inline static QString ExtensionPart(const QString &fileName);
|
||||||
inline static QString DirectoryPart(const QString &fileName);
|
inline static QString DirectoryPart(const QString &fileName);
|
||||||
QString PickBestImage(const QStringList &images);
|
QString PickBestArt(const QStringList &art_automatic_list);
|
||||||
QUrl ImageForSong(const QString &path, QMap<QString, QStringList> &album_art);
|
QUrl ArtForSong(const QString &path, QMap<QString, QStringList> &art_automatic_list);
|
||||||
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);
|
||||||
|
|
||||||
// Updates the sections of a cue associated and altered (according to mtime) media file during a scan.
|
// Updates the sections of a cue associated and altered (according to mtime) media file during a scan.
|
||||||
void UpdateCueAssociatedSongs(const QString &file, const QString &path, const QString &fingerprint, const QString &matching_cue, const QUrl &image, const SongList &old_cue_songs, ScanTransaction *t);
|
void UpdateCueAssociatedSongs(const QString &file, const QString &path, const QString &fingerprint, const QString &matching_cue, const QUrl &art_automatic, const SongList &old_cue_songs, ScanTransaction *t);
|
||||||
// Updates a single non-cue associated and altered (according to mtime) song during a scan.
|
// Updates a single non-cue associated and altered (according to mtime) song during a scan.
|
||||||
void UpdateNonCueAssociatedSong(const QString &file, const QString &fingerprint, const SongList &matching_songs, const QUrl &image, const bool cue_deleted, ScanTransaction *t);
|
void UpdateNonCueAssociatedSong(const QString &file, const QString &fingerprint, const SongList &matching_songs, const QUrl &art_automatic, const bool cue_deleted, ScanTransaction *t);
|
||||||
// Scans a single media file that's present on the disk but not yet in the collection.
|
// Scans a single media file that's present on the disk but not yet in the collection.
|
||||||
// It may result in a multiple files added to the collection when the media file has many sections (like a CUE related media file).
|
// It may result in a multiple files added to the collection when the media file has many sections (like a CUE related media file).
|
||||||
SongList ScanNewFile(const QString &file, const QString &path, const QString &fingerprint, const QString &matching_cue, QSet<QString> *cues_processed);
|
SongList ScanNewFile(const QString &file, const QString &path, const QString &fingerprint, const QString &matching_cue, QSet<QString> *cues_processed);
|
||||||
@@ -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,11 +208,11 @@ 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 album cover art 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.
|
||||||
QStringList best_image_filters_;
|
QStringList best_art_filters_;
|
||||||
|
|
||||||
bool scan_on_startup_;
|
bool scan_on_startup_;
|
||||||
bool monitor_;
|
bool monitor_;
|
||||||
@@ -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_;
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -74,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);
|
||||||
|
|
||||||
|
|||||||
@@ -49,7 +49,7 @@ class GroupByDialog : public QDialog {
|
|||||||
void accept() override;
|
void accept() override;
|
||||||
|
|
||||||
signals:
|
signals:
|
||||||
void Accepted(CollectionModel::Grouping g, bool separate_albums_by_grouping);
|
void Accepted(const CollectionModel::Grouping g, const bool separate_albums_by_grouping);
|
||||||
|
|
||||||
private slots:
|
private slots:
|
||||||
void Reset();
|
void 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");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user