Compare commits
278 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 |
@@ -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
|
||||||
|
|||||||
969
.github/workflows/build.yml
vendored
969
.github/workflows/build.yml
vendored
File diff suppressed because it is too large
Load Diff
88
.github/workflows/codeql.yml
vendored
88
.github/workflows/codeql.yml
vendored
@@ -1,88 +0,0 @@
|
|||||||
name: CodeQL
|
|
||||||
on: [push, pull_request]
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
codeql:
|
|
||||||
name: CodeQL Analyze
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
|
|
||||||
container:
|
|
||||||
image: opensuse/tumbleweed
|
|
||||||
|
|
||||||
steps:
|
|
||||||
|
|
||||||
- name: Refresh repositories
|
|
||||||
run: zypper -n --gpg-auto-import-keys ref
|
|
||||||
|
|
||||||
- name: Upgrade packages
|
|
||||||
run: zypper -n --gpg-auto-import-keys dup
|
|
||||||
|
|
||||||
- name: Install packages
|
|
||||||
run: >
|
|
||||||
zypper -n --gpg-auto-import-keys in
|
|
||||||
lsb-release
|
|
||||||
rpm-build
|
|
||||||
git
|
|
||||||
tar
|
|
||||||
gcc
|
|
||||||
gcc-c++
|
|
||||||
make
|
|
||||||
cmake
|
|
||||||
gettext-tools
|
|
||||||
glibc-devel
|
|
||||||
libboost_headers-devel
|
|
||||||
boost-devel
|
|
||||||
glib2-devel
|
|
||||||
glib2-tools
|
|
||||||
dbus-1-devel
|
|
||||||
alsa-devel
|
|
||||||
libnotify-devel
|
|
||||||
libgnutls-devel
|
|
||||||
protobuf-devel
|
|
||||||
sqlite3-devel
|
|
||||||
libpulse-devel
|
|
||||||
gstreamer-devel
|
|
||||||
gstreamer-plugins-base-devel
|
|
||||||
vlc-devel
|
|
||||||
taglib-devel
|
|
||||||
libicu-devel
|
|
||||||
libcdio-devel
|
|
||||||
libgpod-devel
|
|
||||||
libmtp-devel
|
|
||||||
libchromaprint-devel
|
|
||||||
qt6-core-devel
|
|
||||||
qt6-gui-devel
|
|
||||||
qt6-widgets-devel
|
|
||||||
qt6-concurrent-devel
|
|
||||||
qt6-network-devel
|
|
||||||
qt6-sql-devel
|
|
||||||
qt6-dbus-devel
|
|
||||||
qt6-test-devel
|
|
||||||
qt6-base-common-devel
|
|
||||||
qt6-sql-sqlite
|
|
||||||
qt6-linguist-devel
|
|
||||||
desktop-file-utils
|
|
||||||
update-desktop-files
|
|
||||||
appstream-glib
|
|
||||||
hicolor-icon-theme
|
|
||||||
|
|
||||||
- name: Checkout repository
|
|
||||||
uses: actions/checkout@v3
|
|
||||||
with:
|
|
||||||
fetch-depth: 0
|
|
||||||
|
|
||||||
- name: Add safe git directory
|
|
||||||
run: git config --global --add safe.directory ${GITHUB_WORKSPACE}
|
|
||||||
|
|
||||||
- name: Initialize CodeQL
|
|
||||||
uses: github/codeql-action/init@v2
|
|
||||||
with:
|
|
||||||
languages: cpp
|
|
||||||
|
|
||||||
- name: Autobuild
|
|
||||||
uses: github/codeql-action/autobuild@v2
|
|
||||||
|
|
||||||
- name: Perform CodeQL Analysis
|
|
||||||
uses: github/codeql-action/analyze@v2
|
|
||||||
with:
|
|
||||||
category: "/language:cpp"
|
|
||||||
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
|
|
||||||
275
3rdparty/singleapplication/singleapplication.cpp
vendored
275
3rdparty/singleapplication/singleapplication.cpp
vendored
@@ -1,275 +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>
|
|
||||||
#if QT_VERSION >= QT_VERSION_CHECK(6, 6, 0)
|
|
||||||
# include <QNativeIpcKey>
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#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.
|
|
||||||
#if QT_VERSION >= QT_VERSION_CHECK(6, 6, 0)
|
|
||||||
d->memory_ = new QSharedMemory(QNativeIpcKey(d->blockServerName_));
|
|
||||||
#else
|
|
||||||
d->memory_ = new QSharedMemory(d->blockServerName_);
|
|
||||||
#endif
|
|
||||||
d->memory_->attach();
|
|
||||||
delete d->memory_;
|
|
||||||
#endif
|
|
||||||
|
|
||||||
// Guarantee thread safe behaviour with a shared memory block.
|
|
||||||
#if QT_VERSION >= QT_VERSION_CHECK(6, 6, 0)
|
|
||||||
d->memory_ = new QSharedMemory(QNativeIpcKey(d->blockServerName_));
|
|
||||||
#else
|
|
||||||
d->memory_ = new QSharedMemory(d->blockServerName_);
|
|
||||||
#endif
|
|
||||||
|
|
||||||
// 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;
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
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 class 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
|
|
||||||
525
3rdparty/singleapplication/singleapplication_p.cpp
vendored
525
3rdparty/singleapplication/singleapplication_p.cpp
vendored
@@ -1,525 +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
|
|
||||||
quint8 connTypeVal = InvalidConnection;
|
|
||||||
readStream >> connTypeVal;
|
|
||||||
const ConnectionType 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
|
|
||||||
275
3rdparty/singleapplication/singlecoreapplication.cpp
vendored
275
3rdparty/singleapplication/singlecoreapplication.cpp
vendored
@@ -1,275 +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>
|
|
||||||
#if QT_VERSION >= QT_VERSION_CHECK(6, 6, 0)
|
|
||||||
# include <QNativeIpcKey>
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#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.
|
|
||||||
#if QT_VERSION >= QT_VERSION_CHECK(6, 6, 0)
|
|
||||||
d->memory_ = new QSharedMemory(QNativeIpcKey(d->blockServerName_));
|
|
||||||
#else
|
|
||||||
d->memory_ = new QSharedMemory(d->blockServerName_);
|
|
||||||
#endif
|
|
||||||
d->memory_->attach();
|
|
||||||
delete d->memory_;
|
|
||||||
#endif
|
|
||||||
|
|
||||||
// Guarantee thread safe behaviour with a shared memory block.
|
|
||||||
#if QT_VERSION >= QT_VERSION_CHECK(6, 6, 0)
|
|
||||||
d->memory_ = new QSharedMemory(QNativeIpcKey(d->blockServerName_));
|
|
||||||
#else
|
|
||||||
d->memory_ = new QSharedMemory(d->blockServerName_);
|
|
||||||
#endif
|
|
||||||
|
|
||||||
// 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;
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
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 class 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,525 +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
|
|
||||||
quint8 connTypeVal = InvalidConnection;
|
|
||||||
readStream >> connTypeVal;
|
|
||||||
const ConnectionType 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.
|
||||||
66
Changelog
66
Changelog
@@ -2,6 +2,72 @@ 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):
|
Version 1.0.15 (2023.03.04):
|
||||||
|
|
||||||
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 15)
|
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
|
||||||
|
|||||||
226
dist/windows/strawberry.nsi.in
vendored
226
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"
|
||||||
@@ -319,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"
|
||||||
@@ -333,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"
|
||||||
@@ -387,6 +439,7 @@ 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"
|
||||||
@@ -413,6 +466,7 @@ 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"
|
||||||
@@ -435,11 +489,17 @@ 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
|
!ifdef debug
|
||||||
File "libprotobufd.dll"
|
File "libprotobufd.dll"
|
||||||
@@ -447,8 +507,8 @@ Section "Strawberry" Strawberry
|
|||||||
File "libprotobuf.dll"
|
File "libprotobuf.dll"
|
||||||
!endif
|
!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"
|
||||||
@@ -456,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"
|
||||||
@@ -680,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"
|
||||||
@@ -746,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"
|
||||||
@@ -824,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"
|
||||||
@@ -838,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"
|
||||||
@@ -892,6 +994,7 @@ 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"
|
||||||
@@ -918,6 +1021,7 @@ 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"
|
||||||
@@ -940,11 +1044,16 @@ 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
|
!ifdef debug
|
||||||
Delete "$INSTDIR\libprotobufd.dll"
|
Delete "$INSTDIR\libprotobufd.dll"
|
||||||
@@ -952,8 +1061,8 @@ Section "Uninstall"
|
|||||||
Delete "$INSTDIR\libprotobuf.dll"
|
Delete "$INSTDIR\libprotobuf.dll"
|
||||||
!endif
|
!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"
|
||||||
@@ -961,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"
|
||||||
@@ -1120,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);
|
||||||
|
|||||||
@@ -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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -107,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>
|
||||||
@@ -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
|
||||||
@@ -144,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
|
||||||
@@ -168,16 +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/stands4lyricsprovider.cpp
|
lyrics/lyricscomlyricsprovider.cpp
|
||||||
|
|
||||||
providers/musixmatchprovider.cpp
|
providers/musixmatchprovider.cpp
|
||||||
|
|
||||||
@@ -204,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
|
||||||
|
|
||||||
@@ -263,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
|
||||||
@@ -408,13 +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/stands4lyricsprovider.h
|
lyrics/lyricscomlyricsprovider.h
|
||||||
|
|
||||||
settings/settingsdialog.h
|
settings/settingsdialog.h
|
||||||
settings/settingspage.h
|
settings/settingspage.h
|
||||||
@@ -439,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
|
||||||
|
|
||||||
@@ -496,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
|
||||||
@@ -566,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
|
||||||
@@ -834,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
|
||||||
@@ -966,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)
|
||||||
@@ -1075,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
|
||||||
@@ -1085,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
|
||||||
)
|
)
|
||||||
@@ -1196,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::State::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::State::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
|
||||||
|
|
||||||
|
|||||||
@@ -44,7 +44,6 @@
|
|||||||
|
|
||||||
#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;
|
||||||
|
|
||||||
@@ -84,8 +83,8 @@ AnalyzerContainer::AnalyzerContainer(QWidget *parent)
|
|||||||
|
|
||||||
AddAnalyzerType<BlockAnalyzer>();
|
AddAnalyzerType<BlockAnalyzer>();
|
||||||
AddAnalyzerType<BoomAnalyzer>();
|
AddAnalyzerType<BoomAnalyzer>();
|
||||||
AddAnalyzerType<Rainbow::NyanCatAnalyzer>();
|
AddAnalyzerType<NyanCatAnalyzer>();
|
||||||
AddAnalyzerType<Rainbow::RainbowDashAnalyzer>();
|
AddAnalyzerType<RainbowDashAnalyzer>();
|
||||||
AddAnalyzerType<Sonogram>();
|
AddAnalyzerType<Sonogram>();
|
||||||
|
|
||||||
disable_action_ = context_menu_->addAction(tr("No analyzer"), this, &AnalyzerContainer::DisableAnalyzer);
|
disable_action_ = context_menu_->addAction(tr("No analyzer"), this, &AnalyzerContainer::DisableAnalyzer);
|
||||||
@@ -104,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;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -150,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::State::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
|
||||||
|
|||||||
@@ -24,14 +24,14 @@
|
|||||||
#include <QPainter>
|
#include <QPainter>
|
||||||
#include <QResizeEvent>
|
#include <QResizeEvent>
|
||||||
|
|
||||||
|
#include "engine/enginebase.h"
|
||||||
|
|
||||||
#include "sonogram.h"
|
#include "sonogram.h"
|
||||||
|
|
||||||
const char *Sonogram::kName = QT_TRANSLATE_NOOP("AnalyzerContainer", "Sonogram");
|
const char *Sonogram::kName = QT_TRANSLATE_NOOP("AnalyzerContainer", "Sonogram");
|
||||||
|
|
||||||
Sonogram::Sonogram(QWidget *parent)
|
Sonogram::Sonogram(QWidget *parent)
|
||||||
: Analyzer::Base(parent, 9) {}
|
: AnalyzerBase(parent, 9) {}
|
||||||
|
|
||||||
Sonogram::~Sonogram() {}
|
|
||||||
|
|
||||||
void Sonogram::resizeEvent(QResizeEvent *e) {
|
void Sonogram::resizeEvent(QResizeEvent *e) {
|
||||||
|
|
||||||
@@ -42,9 +42,9 @@ void Sonogram::resizeEvent(QResizeEvent *e) {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void Sonogram::analyze(QPainter &p, const Analyzer::Scope &s, bool new_frame) {
|
void Sonogram::analyze(QPainter &p, const Scope &s, bool new_frame) {
|
||||||
|
|
||||||
if (!new_frame || engine_->state() == Engine::State::Paused) {
|
if (!new_frame || engine_->state() == EngineBase::State::Paused) {
|
||||||
p.drawPixmap(0, 0, canvas_);
|
p.drawPixmap(0, 0, canvas_);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -52,7 +52,7 @@ void Sonogram::analyze(QPainter &p, const Analyzer::Scope &s, bool new_frame) {
|
|||||||
QPainter canvas_painter(&canvas_);
|
QPainter canvas_painter(&canvas_);
|
||||||
canvas_painter.drawPixmap(0, 0, canvas_, 1, 0, width() - 1, -1);
|
canvas_painter.drawPixmap(0, 0, canvas_, 1, 0, width() - 1, -1);
|
||||||
|
|
||||||
Analyzer::Scope::const_iterator it = s.begin(), end = s.end();
|
Scope::const_iterator it = s.begin(), end = s.end();
|
||||||
|
|
||||||
for (int y = height() - 1; y;) {
|
for (int y = height() - 1; y;) {
|
||||||
QColor c;
|
QColor c;
|
||||||
@@ -81,7 +81,7 @@ void Sonogram::analyze(QPainter &p, const Analyzer::Scope &s, bool new_frame) {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void Sonogram::transform(Analyzer::Scope &scope) {
|
void Sonogram::transform(Scope &scope) {
|
||||||
|
|
||||||
fht_->power2(scope.data());
|
fht_->power2(scope.data());
|
||||||
fht_->scale(scope.data(), 1.0 / 256);
|
fht_->scale(scope.data(), 1.0 / 256);
|
||||||
@@ -90,5 +90,5 @@ void Sonogram::transform(Analyzer::Scope &scope) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void Sonogram::demo(QPainter &p) {
|
void Sonogram::demo(QPainter &p) {
|
||||||
analyze(p, Analyzer::Scope(fht_->size(), 0), new_frame_);
|
analyze(p, Scope(fht_->size(), 0), new_frame_);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -29,18 +29,17 @@
|
|||||||
|
|
||||||
#include "analyzerbase.h"
|
#include "analyzerbase.h"
|
||||||
|
|
||||||
class Sonogram : public Analyzer::Base {
|
class Sonogram : public AnalyzerBase {
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
public:
|
public:
|
||||||
Q_INVOKABLE explicit Sonogram(QWidget *parent);
|
Q_INVOKABLE explicit Sonogram(QWidget *parent);
|
||||||
~Sonogram();
|
|
||||||
|
|
||||||
static const char *kName;
|
static const char *kName;
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
void resizeEvent(QResizeEvent *e) override;
|
void resizeEvent(QResizeEvent *e) override;
|
||||||
void analyze(QPainter &p, const Analyzer::Scope &s, bool new_frame) override;
|
void analyze(QPainter &p, const Scope &s, bool new_frame) override;
|
||||||
void transform(Analyzer::Scope &scope) override;
|
void transform(Scope &scope) override;
|
||||||
void demo(QPainter &p) override;
|
void demo(QPainter &p) override;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
|||||||
@@ -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) {
|
||||||
|
|
||||||
@@ -96,17 +94,13 @@ 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" << static_cast<int>(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", static_cast<int>(Utilities::IoPriority::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();
|
||||||
|
|||||||
@@ -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;
|
||||||
@@ -82,7 +81,7 @@ class SCollection : public QObject {
|
|||||||
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
|
||||||
@@ -89,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() {
|
||||||
@@ -105,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) {
|
||||||
@@ -140,8 +138,12 @@ 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, const bool save_tags) {
|
void CollectionBackend::ResetPlayStatisticsAsync(const int id, const bool save_tags) {
|
||||||
QMetaObject::invokeMethod(this, "ResetStatistics", Qt::QueuedConnection, Q_ARG(int, id), Q_ARG(bool, save_tags));
|
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() {
|
||||||
@@ -714,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.IsAllMetadataEqual(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);
|
||||||
@@ -1433,7 +1435,7 @@ CollectionBackend::AlbumList CollectionBackend::GetAlbums(const QString &artist,
|
|||||||
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) {
|
||||||
@@ -1451,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);
|
||||||
@@ -1498,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);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -1518,7 +1526,7 @@ CollectionBackend::Album CollectionBackend::GetAlbumArt(const QString &effective
|
|||||||
ret.album_artist = effective_albumartist;
|
ret.album_artist = effective_albumartist;
|
||||||
|
|
||||||
CollectionQuery query(db, songs_table_, fts_table_);
|
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);
|
||||||
}
|
}
|
||||||
@@ -1530,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());
|
||||||
@@ -1569,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);
|
||||||
|
|
||||||
@@ -1606,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);
|
||||||
@@ -1635,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);
|
||||||
|
|
||||||
@@ -1653,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;
|
||||||
@@ -1776,29 +1888,54 @@ void CollectionBackend::IncrementSkipCount(const int id, const float progress) {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void CollectionBackend::ResetStatistics(const int id, const bool save_tags) {
|
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, save_tags);
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void CollectionBackend::DeleteAllAsync() {
|
void CollectionBackend::DeleteAllAsync() {
|
||||||
|
|
||||||
QMetaObject::invokeMethod(this, "DeleteAll", Qt::QueuedConnection);
|
QMetaObject::invokeMethod(this, &CollectionBackend::DeleteAll, Qt::QueuedConnection);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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
|
||||||
@@ -54,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() : filetype(Song::FileType::Unknown) {}
|
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) {}
|
||||||
@@ -67,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;
|
||||||
@@ -109,8 +113,10 @@ class CollectionBackendInterface : public QObject {
|
|||||||
virtual AlbumList GetAlbumsByArtist(const QString &artist, const CollectionFilterOptions &opt = CollectionFilterOptions()) = 0;
|
virtual AlbumList GetAlbumsByArtist(const QString &artist, const CollectionFilterOptions &opt = CollectionFilterOptions()) = 0;
|
||||||
virtual AlbumList GetCompilationAlbums(const CollectionFilterOptions &opt = CollectionFilterOptions()) = 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;
|
||||||
|
|
||||||
@@ -179,8 +185,10 @@ class CollectionBackend : public CollectionBackendInterface {
|
|||||||
AlbumList GetCompilationAlbums(const CollectionFilterOptions &opt = CollectionFilterOptions()) override;
|
AlbumList GetCompilationAlbums(const CollectionFilterOptions &opt = CollectionFilterOptions()) override;
|
||||||
AlbumList GetAlbumsByArtist(const QString &artist, const CollectionFilterOptions &opt = CollectionFilterOptions()) 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;
|
||||||
|
|
||||||
@@ -200,7 +208,8 @@ class CollectionBackend : public CollectionBackendInterface {
|
|||||||
|
|
||||||
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, const bool save_tags = false);
|
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();
|
||||||
|
|
||||||
@@ -231,12 +240,16 @@ class CollectionBackend : public CollectionBackendInterface {
|
|||||||
void MarkSongsUnavailable(const SongList &songs, const bool unavailable = true);
|
void MarkSongsUnavailable(const SongList &songs, const bool unavailable = true);
|
||||||
void AddOrUpdateSubdirs(const CollectionSubdirectoryList &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, const bool save_tags);
|
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);
|
||||||
|
|
||||||
@@ -251,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(CollectionDirectory, CollectionSubdirectoryList);
|
void DirectoryDiscovered(const CollectionDirectory &dir, const CollectionSubdirectoryList &subdir);
|
||||||
void DirectoryDeleted(CollectionDirectory);
|
void DirectoryDeleted(const CollectionDirectory &dir);
|
||||||
|
|
||||||
void SongsDiscovered(SongList);
|
void SongsDiscovered(const SongList &songs);
|
||||||
void SongsDeleted(SongList);
|
void SongsDeleted(const SongList &songs);
|
||||||
void SongsStatisticsChanged(SongList, bool = false);
|
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 {
|
||||||
@@ -300,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
|
||||||
|
|||||||
@@ -54,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)
|
||||||
|
|||||||
@@ -96,7 +96,7 @@ class CollectionFilterWidget : public QWidget {
|
|||||||
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;
|
||||||
|
|||||||
@@ -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
|
||||||
@@ -42,7 +42,6 @@
|
|||||||
#include <QMap>
|
#include <QMap>
|
||||||
#include <QMetaType>
|
#include <QMetaType>
|
||||||
#include <QVariant>
|
#include <QVariant>
|
||||||
#include <QByteArray>
|
|
||||||
#include <QString>
|
#include <QString>
|
||||||
#include <QStringList>
|
#include <QStringList>
|
||||||
#include <QUrl>
|
#include <QUrl>
|
||||||
@@ -72,6 +71,7 @@
|
|||||||
#include "covermanager/albumcoverloader.h"
|
#include "covermanager/albumcoverloader.h"
|
||||||
#include "covermanager/albumcoverloaderresult.h"
|
#include "covermanager/albumcoverloaderresult.h"
|
||||||
#include "settings/collectionsettingspage.h"
|
#include "settings/collectionsettingspage.h"
|
||||||
|
#include "settings/coverssettingspage.h"
|
||||||
|
|
||||||
const int CollectionModel::kPrettyCoverSize = 32;
|
const int CollectionModel::kPrettyCoverSize = 32;
|
||||||
const char *CollectionModel::kPixmapDiskCacheDir = "pixmapcache";
|
const char *CollectionModel::kPixmapDiskCacheDir = "pixmapcache";
|
||||||
@@ -102,12 +102,6 @@ CollectionModel::CollectionModel(CollectionBackend *backend, Application *app, Q
|
|||||||
group_by_[1] = GroupBy::AlbumDisc;
|
group_by_[1] = GroupBy::AlbumDisc;
|
||||||
group_by_[2] = GroupBy::None;
|
group_by_[2] = GroupBy::None;
|
||||||
|
|
||||||
cover_loader_options_.get_image_data_ = false;
|
|
||||||
cover_loader_options_.get_image_ = true;
|
|
||||||
cover_loader_options_.scale_output_image_ = true;
|
|
||||||
cover_loader_options_.pad_output_image_ = true;
|
|
||||||
cover_loader_options_.desired_height_ = kPrettyCoverSize;
|
|
||||||
|
|
||||||
if (app_) {
|
if (app_) {
|
||||||
QObject::connect(app_->album_cover_loader(), &AlbumCoverLoader::AlbumCoverLoaded, this, &CollectionModel::AlbumCoverLoaded);
|
QObject::connect(app_->album_cover_loader(), &AlbumCoverLoader::AlbumCoverLoaded, this, &CollectionModel::AlbumCoverLoaded);
|
||||||
}
|
}
|
||||||
@@ -151,6 +145,7 @@ void CollectionModel::set_pretty_covers(const bool use_pretty_covers) {
|
|||||||
use_pretty_covers_ = use_pretty_covers;
|
use_pretty_covers_ = use_pretty_covers;
|
||||||
Reset();
|
Reset();
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void CollectionModel::set_show_dividers(const bool show_dividers) {
|
void CollectionModel::set_show_dividers(const bool show_dividers) {
|
||||||
@@ -178,6 +173,8 @@ void CollectionModel::ReloadSettings() {
|
|||||||
|
|
||||||
s.endGroup();
|
s.endGroup();
|
||||||
|
|
||||||
|
cover_types_ = AlbumCoverLoaderOptions::LoadTypes();
|
||||||
|
|
||||||
if (!use_disk_cache_) {
|
if (!use_disk_cache_) {
|
||||||
ClearDiskCache();
|
ClearDiskCache();
|
||||||
}
|
}
|
||||||
@@ -342,10 +339,10 @@ QString CollectionModel::ContainerKey(const GroupBy group_by, const bool separat
|
|||||||
key = PrettyDisc(song.disc());
|
key = PrettyDisc(song.disc());
|
||||||
break;
|
break;
|
||||||
case GroupBy::Year:
|
case GroupBy::Year:
|
||||||
key = QString::number(qMax(0, song.year()));
|
key = QString::number(std::max(0, song.year()));
|
||||||
break;
|
break;
|
||||||
case GroupBy::OriginalYear:
|
case GroupBy::OriginalYear:
|
||||||
key = QString::number(qMax(0, song.effective_originalyear()));
|
key = QString::number(std::max(0, song.effective_originalyear()));
|
||||||
break;
|
break;
|
||||||
case GroupBy::Genre:
|
case GroupBy::Genre:
|
||||||
key = TextOrUnknown(song.genre());
|
key = TextOrUnknown(song.genre());
|
||||||
@@ -363,13 +360,13 @@ QString CollectionModel::ContainerKey(const GroupBy group_by, const bool separat
|
|||||||
key = song.TextForFiletype();
|
key = song.TextForFiletype();
|
||||||
break;
|
break;
|
||||||
case GroupBy::Samplerate:
|
case GroupBy::Samplerate:
|
||||||
key = QString::number(qMax(0, song.samplerate()));
|
key = QString::number(std::max(0, song.samplerate()));
|
||||||
break;
|
break;
|
||||||
case GroupBy::Bitdepth:
|
case GroupBy::Bitdepth:
|
||||||
key = QString::number(qMax(0, song.bitdepth()));
|
key = QString::number(std::max(0, song.bitdepth()));
|
||||||
break;
|
break;
|
||||||
case GroupBy::Bitrate:
|
case GroupBy::Bitrate:
|
||||||
key = QString::number(qMax(0, song.bitrate()));
|
key = QString::number(std::max(0, song.bitrate()));
|
||||||
break;
|
break;
|
||||||
case GroupBy::Format:
|
case GroupBy::Format:
|
||||||
if (song.samplerate() <= 0) {
|
if (song.samplerate() <= 0) {
|
||||||
@@ -559,7 +556,7 @@ void CollectionModel::SongsDeleted(const SongList &songs) {
|
|||||||
// Remove from pixmap cache
|
// Remove from pixmap cache
|
||||||
const QString cache_key = AlbumIconPixmapCacheKey(ItemToIndex(node));
|
const QString cache_key = AlbumIconPixmapCacheKey(ItemToIndex(node));
|
||||||
QPixmapCache::remove(cache_key);
|
QPixmapCache::remove(cache_key);
|
||||||
if (use_disk_cache_ && sIconCache) sIconCache->remove(QUrl(cache_key));
|
if (use_disk_cache_ && sIconCache) sIconCache->remove(AlbumIconPixmapDiskCacheKey(cache_key));
|
||||||
if (pending_cache_keys_.contains(cache_key)) {
|
if (pending_cache_keys_.contains(cache_key)) {
|
||||||
pending_cache_keys_.remove(cache_key);
|
pending_cache_keys_.remove(cache_key);
|
||||||
}
|
}
|
||||||
@@ -614,6 +611,12 @@ QString CollectionModel::AlbumIconPixmapCacheKey(const QModelIndex &idx) const {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
QUrl CollectionModel::AlbumIconPixmapDiskCacheKey(const QString &cache_key) const {
|
||||||
|
|
||||||
|
return QUrl(QUrl::toPercentEncoding(cache_key));
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
QVariant CollectionModel::AlbumIcon(const QModelIndex &idx) {
|
QVariant CollectionModel::AlbumIcon(const QModelIndex &idx) {
|
||||||
|
|
||||||
CollectionItem *item = IndexToItem(idx);
|
CollectionItem *item = IndexToItem(idx);
|
||||||
@@ -629,10 +632,10 @@ QVariant CollectionModel::AlbumIcon(const QModelIndex &idx) {
|
|||||||
|
|
||||||
// Try to load it from the disk cache
|
// Try to load it from the disk cache
|
||||||
if (use_disk_cache_ && sIconCache) {
|
if (use_disk_cache_ && sIconCache) {
|
||||||
std::unique_ptr<QIODevice> cache(sIconCache->data(QUrl(cache_key)));
|
std::unique_ptr<QIODevice> disk_cache_img(sIconCache->data(AlbumIconPixmapDiskCacheKey(cache_key)));
|
||||||
if (cache) {
|
if (disk_cache_img) {
|
||||||
QImage cached_image;
|
QImage cached_image;
|
||||||
if (cached_image.load(cache.get(), "XPM")) {
|
if (cached_image.load(disk_cache_img.get(), "XPM")) {
|
||||||
QPixmapCache::insert(cache_key, QPixmap::fromImage(cached_image));
|
QPixmapCache::insert(cache_key, QPixmap::fromImage(cached_image));
|
||||||
return QPixmap::fromImage(cached_image);
|
return QPixmap::fromImage(cached_image);
|
||||||
}
|
}
|
||||||
@@ -647,7 +650,10 @@ QVariant CollectionModel::AlbumIcon(const QModelIndex &idx) {
|
|||||||
// No art is cached and we're not loading it already. Load art for the first song in the album.
|
// No art is cached and we're not loading it already. Load art for the first song in the album.
|
||||||
SongList songs = GetChildSongs(idx);
|
SongList songs = GetChildSongs(idx);
|
||||||
if (!songs.isEmpty()) {
|
if (!songs.isEmpty()) {
|
||||||
const quint64 id = app_->album_cover_loader()->LoadImageAsync(cover_loader_options_, songs.first());
|
AlbumCoverLoaderOptions cover_loader_options(AlbumCoverLoaderOptions::Option::ScaledImage | AlbumCoverLoaderOptions::Option::PadScaledImage);
|
||||||
|
cover_loader_options.desired_scaled_size = QSize(kPrettyCoverSize, kPrettyCoverSize);
|
||||||
|
cover_loader_options.types = cover_types_;
|
||||||
|
const quint64 id = app_->album_cover_loader()->LoadImageAsync(cover_loader_options, songs.first());
|
||||||
pending_art_[id] = ItemAndCacheKey(item, cache_key);
|
pending_art_[id] = ItemAndCacheKey(item, cache_key);
|
||||||
pending_cache_keys_.insert(cache_key);
|
pending_cache_keys_.insert(cache_key);
|
||||||
}
|
}
|
||||||
@@ -669,7 +675,7 @@ void CollectionModel::AlbumCoverLoaded(const quint64 id, const AlbumCoverLoaderR
|
|||||||
pending_cache_keys_.remove(cache_key);
|
pending_cache_keys_.remove(cache_key);
|
||||||
|
|
||||||
// Insert this image in the cache.
|
// Insert this image in the cache.
|
||||||
if (!result.success || result.image_scaled.isNull() || result.type == AlbumCoverLoaderResult::Type_ManuallyUnset) {
|
if (!result.success || result.image_scaled.isNull() || result.type == AlbumCoverLoaderResult::Type::Unset) {
|
||||||
// Set the no_cover image so we don't continually try to load art.
|
// Set the no_cover image so we don't continually try to load art.
|
||||||
QPixmapCache::insert(cache_key, no_cover_icon_);
|
QPixmapCache::insert(cache_key, no_cover_icon_);
|
||||||
}
|
}
|
||||||
@@ -681,15 +687,18 @@ void CollectionModel::AlbumCoverLoaded(const quint64 id, const AlbumCoverLoaderR
|
|||||||
|
|
||||||
// If we have a valid cover not already in the disk cache
|
// If we have a valid cover not already in the disk cache
|
||||||
if (use_disk_cache_ && sIconCache && result.success && !result.image_scaled.isNull()) {
|
if (use_disk_cache_ && sIconCache && result.success && !result.image_scaled.isNull()) {
|
||||||
std::unique_ptr<QIODevice> cached_img(sIconCache->data(QUrl(cache_key)));
|
const QUrl disk_cache_key = AlbumIconPixmapDiskCacheKey(cache_key);
|
||||||
if (!cached_img) {
|
std::unique_ptr<QIODevice> disk_cache_img(sIconCache->data(disk_cache_key));
|
||||||
QNetworkCacheMetaData item_metadata;
|
if (!disk_cache_img) {
|
||||||
item_metadata.setSaveToDisk(true);
|
QNetworkCacheMetaData disk_cache_metadata;
|
||||||
item_metadata.setUrl(QUrl(cache_key));
|
disk_cache_metadata.setSaveToDisk(true);
|
||||||
QIODevice *cache = sIconCache->prepare(item_metadata);
|
disk_cache_metadata.setUrl(disk_cache_key);
|
||||||
if (cache) {
|
// Qt 6 now ignores any entry without headers, so add a fake header.
|
||||||
result.image_scaled.save(cache, "XPM");
|
disk_cache_metadata.setRawHeaders(QNetworkCacheMetaData::RawHeaderList() << qMakePair(QByteArray(), QByteArray()));
|
||||||
sIconCache->insert(cache);
|
QIODevice *device_iconcache = sIconCache->prepare(disk_cache_metadata);
|
||||||
|
if (device_iconcache) {
|
||||||
|
result.image_scaled.save(device_iconcache, "XPM");
|
||||||
|
sIconCache->insert(device_iconcache);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1253,7 +1262,7 @@ CollectionItem *CollectionModel::ItemFromQuery(const GroupBy group_by, const boo
|
|||||||
item->metadata.set_grouping(row.value(3).toString());
|
item->metadata.set_grouping(row.value(3).toString());
|
||||||
item->key.append(ContainerKey(group_by, separate_albums_by_grouping, item->metadata));
|
item->key.append(ContainerKey(group_by, separate_albums_by_grouping, item->metadata));
|
||||||
item->display_text = PrettyAlbumDisc(item->metadata.album(), item->metadata.disc());
|
item->display_text = PrettyAlbumDisc(item->metadata.album(), item->metadata.disc());
|
||||||
item->sort_text = item->metadata.album() + SortTextForNumber(qMax(0, item->metadata.disc()));
|
item->sort_text = item->metadata.album() + SortTextForNumber(std::max(0, item->metadata.disc()));
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case GroupBy::YearAlbum:{
|
case GroupBy::YearAlbum:{
|
||||||
@@ -1263,7 +1272,7 @@ CollectionItem *CollectionModel::ItemFromQuery(const GroupBy group_by, const boo
|
|||||||
item->metadata.set_grouping(row.value(3).toString());
|
item->metadata.set_grouping(row.value(3).toString());
|
||||||
item->key.append(ContainerKey(group_by, separate_albums_by_grouping, item->metadata));
|
item->key.append(ContainerKey(group_by, separate_albums_by_grouping, item->metadata));
|
||||||
item->display_text = PrettyYearAlbum(item->metadata.year(), item->metadata.album());
|
item->display_text = PrettyYearAlbum(item->metadata.year(), item->metadata.album());
|
||||||
item->sort_text = SortTextForNumber(qMax(0, item->metadata.year())) + item->metadata.grouping() + item->metadata.album();
|
item->sort_text = SortTextForNumber(std::max(0, item->metadata.year())) + item->metadata.grouping() + item->metadata.album();
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case GroupBy::YearAlbumDisc:{
|
case GroupBy::YearAlbumDisc:{
|
||||||
@@ -1274,7 +1283,7 @@ CollectionItem *CollectionModel::ItemFromQuery(const GroupBy group_by, const boo
|
|||||||
item->metadata.set_grouping(row.value(4).toString());
|
item->metadata.set_grouping(row.value(4).toString());
|
||||||
item->key.append(ContainerKey(group_by, separate_albums_by_grouping, item->metadata));
|
item->key.append(ContainerKey(group_by, separate_albums_by_grouping, item->metadata));
|
||||||
item->display_text = PrettyYearAlbumDisc(item->metadata.year(), item->metadata.album(), item->metadata.disc());
|
item->display_text = PrettyYearAlbumDisc(item->metadata.year(), item->metadata.album(), item->metadata.disc());
|
||||||
item->sort_text = SortTextForNumber(qMax(0, item->metadata.year())) + item->metadata.album() + SortTextForNumber(qMax(0, item->metadata.disc()));
|
item->sort_text = SortTextForNumber(std::max(0, item->metadata.year())) + item->metadata.album() + SortTextForNumber(std::max(0, item->metadata.disc()));
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case GroupBy::OriginalYearAlbum:{
|
case GroupBy::OriginalYearAlbum:{
|
||||||
@@ -1285,7 +1294,7 @@ CollectionItem *CollectionModel::ItemFromQuery(const GroupBy group_by, const boo
|
|||||||
item->metadata.set_grouping(row.value(4).toString());
|
item->metadata.set_grouping(row.value(4).toString());
|
||||||
item->key.append(ContainerKey(group_by, separate_albums_by_grouping, item->metadata));
|
item->key.append(ContainerKey(group_by, separate_albums_by_grouping, item->metadata));
|
||||||
item->display_text = PrettyYearAlbum(item->metadata.effective_originalyear(), item->metadata.album());
|
item->display_text = PrettyYearAlbum(item->metadata.effective_originalyear(), item->metadata.album());
|
||||||
item->sort_text = SortTextForNumber(qMax(0, item->metadata.effective_originalyear())) + item->metadata.grouping() + item->metadata.album();
|
item->sort_text = SortTextForNumber(std::max(0, item->metadata.effective_originalyear())) + item->metadata.grouping() + item->metadata.album();
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case GroupBy::OriginalYearAlbumDisc:{
|
case GroupBy::OriginalYearAlbumDisc:{
|
||||||
@@ -1297,13 +1306,13 @@ CollectionItem *CollectionModel::ItemFromQuery(const GroupBy group_by, const boo
|
|||||||
item->metadata.set_grouping(row.value(5).toString());
|
item->metadata.set_grouping(row.value(5).toString());
|
||||||
item->key.append(ContainerKey(group_by, separate_albums_by_grouping, item->metadata));
|
item->key.append(ContainerKey(group_by, separate_albums_by_grouping, item->metadata));
|
||||||
item->display_text = PrettyYearAlbumDisc(item->metadata.effective_originalyear(), item->metadata.album(), item->metadata.disc());
|
item->display_text = PrettyYearAlbumDisc(item->metadata.effective_originalyear(), item->metadata.album(), item->metadata.disc());
|
||||||
item->sort_text = SortTextForNumber(qMax(0, item->metadata.effective_originalyear())) + item->metadata.album() + SortTextForNumber(qMax(0, item->metadata.disc()));
|
item->sort_text = SortTextForNumber(std::max(0, item->metadata.effective_originalyear())) + item->metadata.album() + SortTextForNumber(std::max(0, item->metadata.disc()));
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case GroupBy::Disc:{
|
case GroupBy::Disc:{
|
||||||
item->metadata.set_disc(row.value(0).toInt());
|
item->metadata.set_disc(row.value(0).toInt());
|
||||||
item->key.append(ContainerKey(group_by, separate_albums_by_grouping, item->metadata));
|
item->key.append(ContainerKey(group_by, separate_albums_by_grouping, item->metadata));
|
||||||
const int disc = qMax(0, row.value(0).toInt());
|
const int disc = std::max(0, row.value(0).toInt());
|
||||||
item->display_text = PrettyDisc(disc);
|
item->display_text = PrettyDisc(disc);
|
||||||
item->sort_text = SortTextForNumber(disc);
|
item->sort_text = SortTextForNumber(disc);
|
||||||
break;
|
break;
|
||||||
@@ -1311,7 +1320,7 @@ CollectionItem *CollectionModel::ItemFromQuery(const GroupBy group_by, const boo
|
|||||||
case GroupBy::Year:{
|
case GroupBy::Year:{
|
||||||
item->metadata.set_year(row.value(0).toInt());
|
item->metadata.set_year(row.value(0).toInt());
|
||||||
item->key.append(ContainerKey(group_by, separate_albums_by_grouping, item->metadata));
|
item->key.append(ContainerKey(group_by, separate_albums_by_grouping, item->metadata));
|
||||||
const int year = qMax(0, item->metadata.year());
|
const int year = std::max(0, item->metadata.year());
|
||||||
item->display_text = QString::number(year);
|
item->display_text = QString::number(year);
|
||||||
item->sort_text = SortTextForNumber(year) + " ";
|
item->sort_text = SortTextForNumber(year) + " ";
|
||||||
break;
|
break;
|
||||||
@@ -1319,7 +1328,7 @@ CollectionItem *CollectionModel::ItemFromQuery(const GroupBy group_by, const boo
|
|||||||
case GroupBy::OriginalYear:{
|
case GroupBy::OriginalYear:{
|
||||||
item->metadata.set_originalyear(row.value(0).toInt());
|
item->metadata.set_originalyear(row.value(0).toInt());
|
||||||
item->key.append(ContainerKey(group_by, separate_albums_by_grouping, item->metadata));
|
item->key.append(ContainerKey(group_by, separate_albums_by_grouping, item->metadata));
|
||||||
const int year = qMax(0, item->metadata.originalyear());
|
const int year = std::max(0, item->metadata.originalyear());
|
||||||
item->display_text = QString::number(year);
|
item->display_text = QString::number(year);
|
||||||
item->sort_text = SortTextForNumber(year) + " ";
|
item->sort_text = SortTextForNumber(year) + " ";
|
||||||
break;
|
break;
|
||||||
@@ -1372,7 +1381,7 @@ CollectionItem *CollectionModel::ItemFromQuery(const GroupBy group_by, const boo
|
|||||||
case GroupBy::Samplerate:{
|
case GroupBy::Samplerate:{
|
||||||
item->metadata.set_samplerate(row.value(0).toInt());
|
item->metadata.set_samplerate(row.value(0).toInt());
|
||||||
item->key.append(ContainerKey(group_by, separate_albums_by_grouping, item->metadata));
|
item->key.append(ContainerKey(group_by, separate_albums_by_grouping, item->metadata));
|
||||||
const int samplerate = qMax(0, item->metadata.samplerate());
|
const int samplerate = std::max(0, item->metadata.samplerate());
|
||||||
item->display_text = QString::number(samplerate);
|
item->display_text = QString::number(samplerate);
|
||||||
item->sort_text = SortTextForNumber(samplerate) + " ";
|
item->sort_text = SortTextForNumber(samplerate) + " ";
|
||||||
break;
|
break;
|
||||||
@@ -1380,7 +1389,7 @@ CollectionItem *CollectionModel::ItemFromQuery(const GroupBy group_by, const boo
|
|||||||
case GroupBy::Bitdepth:{
|
case GroupBy::Bitdepth:{
|
||||||
item->metadata.set_bitdepth(row.value(0).toInt());
|
item->metadata.set_bitdepth(row.value(0).toInt());
|
||||||
item->key.append(ContainerKey(group_by, separate_albums_by_grouping, item->metadata));
|
item->key.append(ContainerKey(group_by, separate_albums_by_grouping, item->metadata));
|
||||||
const int bitdepth = qMax(0, item->metadata.bitdepth());
|
const int bitdepth = std::max(0, item->metadata.bitdepth());
|
||||||
item->display_text = QString::number(bitdepth);
|
item->display_text = QString::number(bitdepth);
|
||||||
item->sort_text = SortTextForNumber(bitdepth) + " ";
|
item->sort_text = SortTextForNumber(bitdepth) + " ";
|
||||||
break;
|
break;
|
||||||
@@ -1388,7 +1397,7 @@ CollectionItem *CollectionModel::ItemFromQuery(const GroupBy group_by, const boo
|
|||||||
case GroupBy::Bitrate:{
|
case GroupBy::Bitrate:{
|
||||||
item->metadata.set_bitrate(row.value(0).toInt());
|
item->metadata.set_bitrate(row.value(0).toInt());
|
||||||
item->key.append(ContainerKey(group_by, separate_albums_by_grouping, item->metadata));
|
item->key.append(ContainerKey(group_by, separate_albums_by_grouping, item->metadata));
|
||||||
const int bitrate = qMax(0, item->metadata.bitrate());
|
const int bitrate = std::max(0, item->metadata.bitrate());
|
||||||
item->display_text = QString::number(bitrate);
|
item->display_text = QString::number(bitrate);
|
||||||
item->sort_text = SortTextForNumber(bitrate) + " ";
|
item->sort_text = SortTextForNumber(bitrate) + " ";
|
||||||
break;
|
break;
|
||||||
@@ -1448,77 +1457,77 @@ CollectionItem *CollectionModel::ItemFromSong(const GroupBy group_by, const bool
|
|||||||
case GroupBy::AlbumDisc:{
|
case GroupBy::AlbumDisc:{
|
||||||
item->metadata.set_album(s.album());
|
item->metadata.set_album(s.album());
|
||||||
item->metadata.set_album_id(s.album_id());
|
item->metadata.set_album_id(s.album_id());
|
||||||
item->metadata.set_disc(s.disc());
|
item->metadata.set_disc(s.disc() <= 0 ? -1 : s.disc());
|
||||||
item->metadata.set_grouping(s.grouping());
|
item->metadata.set_grouping(s.grouping());
|
||||||
item->key.append(ContainerKey(group_by, separate_albums_by_grouping, s));
|
item->key.append(ContainerKey(group_by, separate_albums_by_grouping, s));
|
||||||
item->display_text = PrettyAlbumDisc(s.album(), s.disc());
|
item->display_text = PrettyAlbumDisc(s.album(), s.disc());
|
||||||
item->sort_text = s.album() + SortTextForNumber(qMax(0, s.disc()));
|
item->sort_text = s.album() + SortTextForNumber(std::max(0, s.disc()));
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case GroupBy::YearAlbum:{
|
case GroupBy::YearAlbum:{
|
||||||
item->metadata.set_year(s.year());
|
item->metadata.set_year(s.year() <= 0 ? -1 : s.year());
|
||||||
item->metadata.set_album(s.album());
|
item->metadata.set_album(s.album());
|
||||||
item->metadata.set_album_id(s.album_id());
|
item->metadata.set_album_id(s.album_id());
|
||||||
item->metadata.set_grouping(s.grouping());
|
item->metadata.set_grouping(s.grouping());
|
||||||
item->key.append(ContainerKey(group_by, separate_albums_by_grouping, s));
|
item->key.append(ContainerKey(group_by, separate_albums_by_grouping, s));
|
||||||
item->display_text = PrettyYearAlbum(s.year(), s.album());
|
item->display_text = PrettyYearAlbum(s.year(), s.album());
|
||||||
item->sort_text = SortTextForNumber(qMax(0, s.year())) + s.grouping() + s.album();
|
item->sort_text = SortTextForNumber(std::max(0, s.year())) + s.grouping() + s.album();
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case GroupBy::YearAlbumDisc:{
|
case GroupBy::YearAlbumDisc:{
|
||||||
item->metadata.set_year(s.year());
|
item->metadata.set_year(s.year() <= 0 ? -1 : s.year());
|
||||||
item->metadata.set_album(s.album());
|
item->metadata.set_album(s.album());
|
||||||
item->metadata.set_album_id(s.album_id());
|
item->metadata.set_album_id(s.album_id());
|
||||||
item->metadata.set_disc(s.disc());
|
item->metadata.set_disc(s.disc() <= 0 ? -1 : s.disc());
|
||||||
item->metadata.set_grouping(s.grouping());
|
item->metadata.set_grouping(s.grouping());
|
||||||
item->key.append(ContainerKey(group_by, separate_albums_by_grouping, s));
|
item->key.append(ContainerKey(group_by, separate_albums_by_grouping, s));
|
||||||
item->display_text = PrettyYearAlbumDisc(s.year(), s.album(), s.disc());
|
item->display_text = PrettyYearAlbumDisc(s.year(), s.album(), s.disc());
|
||||||
item->sort_text = SortTextForNumber(qMax(0, s.year())) + s.album() + SortTextForNumber(qMax(0, s.disc()));
|
item->sort_text = SortTextForNumber(std::max(0, s.year())) + s.album() + SortTextForNumber(std::max(0, s.disc()));
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case GroupBy::OriginalYearAlbum:{
|
case GroupBy::OriginalYearAlbum:{
|
||||||
item->metadata.set_year(s.year());
|
item->metadata.set_year(s.year() <= 0 ? -1 : s.year());
|
||||||
item->metadata.set_originalyear(s.originalyear());
|
item->metadata.set_originalyear(s.originalyear() <= 0 ? -1 : s.originalyear());
|
||||||
item->metadata.set_album(s.album());
|
item->metadata.set_album(s.album());
|
||||||
item->metadata.set_album_id(s.album_id());
|
item->metadata.set_album_id(s.album_id());
|
||||||
item->metadata.set_grouping(s.grouping());
|
item->metadata.set_grouping(s.grouping());
|
||||||
item->key.append(ContainerKey(group_by, separate_albums_by_grouping, s));
|
item->key.append(ContainerKey(group_by, separate_albums_by_grouping, s));
|
||||||
item->display_text = PrettyYearAlbum(s.effective_originalyear(), s.album());
|
item->display_text = PrettyYearAlbum(s.effective_originalyear(), s.album());
|
||||||
item->sort_text = SortTextForNumber(qMax(0, s.effective_originalyear())) + s.grouping() + s.album();
|
item->sort_text = SortTextForNumber(std::max(0, s.effective_originalyear())) + s.grouping() + s.album();
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case GroupBy::OriginalYearAlbumDisc:{
|
case GroupBy::OriginalYearAlbumDisc:{
|
||||||
item->metadata.set_year(s.year());
|
item->metadata.set_year(s.year() <= 0 ? -1 : s.year());
|
||||||
item->metadata.set_originalyear(s.originalyear());
|
item->metadata.set_originalyear(s.originalyear() <= 0 ? -1 : s.originalyear());
|
||||||
item->metadata.set_album(s.album());
|
item->metadata.set_album(s.album());
|
||||||
item->metadata.set_album_id(s.album_id());
|
item->metadata.set_album_id(s.album_id());
|
||||||
item->metadata.set_disc(s.disc());
|
item->metadata.set_disc(s.disc() <= 0 ? -1 : s.disc());
|
||||||
item->metadata.set_grouping(s.grouping());
|
item->metadata.set_grouping(s.grouping());
|
||||||
item->key.append(ContainerKey(group_by, separate_albums_by_grouping, s));
|
item->key.append(ContainerKey(group_by, separate_albums_by_grouping, s));
|
||||||
item->display_text = PrettyYearAlbumDisc(s.effective_originalyear(), s.album(), s.disc());
|
item->display_text = PrettyYearAlbumDisc(s.effective_originalyear(), s.album(), s.disc());
|
||||||
item->sort_text = SortTextForNumber(qMax(0, s.effective_originalyear())) + s.album() + SortTextForNumber(qMax(0, s.disc()));
|
item->sort_text = SortTextForNumber(std::max(0, s.effective_originalyear())) + s.album() + SortTextForNumber(std::max(0, s.disc()));
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case GroupBy::Disc:{
|
case GroupBy::Disc:{
|
||||||
item->metadata.set_disc(s.disc());
|
item->metadata.set_disc(s.disc() <= 0 ? -1 : s.disc());
|
||||||
item->key.append(ContainerKey(group_by, separate_albums_by_grouping, s));
|
item->key.append(ContainerKey(group_by, separate_albums_by_grouping, s));
|
||||||
const int disc = qMax(0, s.disc());
|
const int disc = std::max(0, s.disc());
|
||||||
item->display_text = PrettyDisc(disc);
|
item->display_text = PrettyDisc(disc);
|
||||||
item->sort_text = SortTextForNumber(disc);
|
item->sort_text = SortTextForNumber(disc);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case GroupBy::Year:{
|
case GroupBy::Year:{
|
||||||
item->metadata.set_year(s.year());
|
item->metadata.set_year(s.year() <= 0 ? -1 : s.year());
|
||||||
item->key.append(ContainerKey(group_by, separate_albums_by_grouping, s));
|
item->key.append(ContainerKey(group_by, separate_albums_by_grouping, s));
|
||||||
const int year = qMax(0, s.year());
|
const int year = std::max(0, s.year());
|
||||||
item->display_text = QString::number(year);
|
item->display_text = QString::number(year);
|
||||||
item->sort_text = SortTextForNumber(year) + " ";
|
item->sort_text = SortTextForNumber(year) + " ";
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case GroupBy::OriginalYear:{
|
case GroupBy::OriginalYear:{
|
||||||
item->metadata.set_originalyear(s.effective_originalyear());
|
item->metadata.set_originalyear(s.effective_originalyear() <= 0 ? -1 : s.effective_originalyear());
|
||||||
item->key.append(ContainerKey(group_by, separate_albums_by_grouping, s));
|
item->key.append(ContainerKey(group_by, separate_albums_by_grouping, s));
|
||||||
const int year = qMax(0, s.effective_originalyear());
|
const int year = std::max(0, s.effective_originalyear());
|
||||||
item->display_text = QString::number(year);
|
item->display_text = QString::number(year);
|
||||||
item->sort_text = SortTextForNumber(year) + " ";
|
item->sort_text = SortTextForNumber(year) + " ";
|
||||||
break;
|
break;
|
||||||
@@ -1571,7 +1580,7 @@ CollectionItem *CollectionModel::ItemFromSong(const GroupBy group_by, const bool
|
|||||||
case GroupBy::Samplerate:{
|
case GroupBy::Samplerate:{
|
||||||
item->metadata.set_samplerate(s.samplerate());
|
item->metadata.set_samplerate(s.samplerate());
|
||||||
item->key.append(ContainerKey(group_by, separate_albums_by_grouping, s));
|
item->key.append(ContainerKey(group_by, separate_albums_by_grouping, s));
|
||||||
const int samplerate = qMax(0, s.samplerate());
|
const int samplerate = std::max(0, s.samplerate());
|
||||||
item->display_text = QString::number(samplerate);
|
item->display_text = QString::number(samplerate);
|
||||||
item->sort_text = SortTextForNumber(samplerate) + " ";
|
item->sort_text = SortTextForNumber(samplerate) + " ";
|
||||||
break;
|
break;
|
||||||
@@ -1579,7 +1588,7 @@ CollectionItem *CollectionModel::ItemFromSong(const GroupBy group_by, const bool
|
|||||||
case GroupBy::Bitdepth:{
|
case GroupBy::Bitdepth:{
|
||||||
item->metadata.set_bitdepth(s.bitdepth());
|
item->metadata.set_bitdepth(s.bitdepth());
|
||||||
item->key.append(ContainerKey(group_by, separate_albums_by_grouping, s));
|
item->key.append(ContainerKey(group_by, separate_albums_by_grouping, s));
|
||||||
const int bitdepth = qMax(0, s.bitdepth());
|
const int bitdepth = std::max(0, s.bitdepth());
|
||||||
item->display_text = QString::number(bitdepth);
|
item->display_text = QString::number(bitdepth);
|
||||||
item->sort_text = SortTextForNumber(bitdepth) + " ";
|
item->sort_text = SortTextForNumber(bitdepth) + " ";
|
||||||
break;
|
break;
|
||||||
@@ -1587,7 +1596,7 @@ CollectionItem *CollectionModel::ItemFromSong(const GroupBy group_by, const bool
|
|||||||
case GroupBy::Bitrate:{
|
case GroupBy::Bitrate:{
|
||||||
item->metadata.set_bitrate(s.bitrate());
|
item->metadata.set_bitrate(s.bitrate());
|
||||||
item->key.append(ContainerKey(group_by, separate_albums_by_grouping, s));
|
item->key.append(ContainerKey(group_by, separate_albums_by_grouping, s));
|
||||||
const int bitrate = qMax(0, s.bitrate());
|
const int bitrate = std::max(0, s.bitrate());
|
||||||
item->display_text = QString::number(bitrate);
|
item->display_text = QString::number(bitrate);
|
||||||
item->sort_text = SortTextForNumber(bitrate) + " ";
|
item->sort_text = SortTextForNumber(bitrate) + " ";
|
||||||
break;
|
break;
|
||||||
@@ -1686,7 +1695,7 @@ QString CollectionModel::PrettyYearAlbumDisc(const int year, const QString &albu
|
|||||||
|
|
||||||
QString CollectionModel::PrettyDisc(const int disc) {
|
QString CollectionModel::PrettyDisc(const int disc) {
|
||||||
|
|
||||||
return "Disc " + QString::number(qMax(1, disc));
|
return "Disc " + QString::number(std::max(1, disc));
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1741,7 +1750,7 @@ QString CollectionModel::SortTextForBitrate(const int bitrate) {
|
|||||||
|
|
||||||
QString CollectionModel::SortTextForSong(const Song &song) {
|
QString CollectionModel::SortTextForSong(const Song &song) {
|
||||||
|
|
||||||
QString ret = QString::number(qMax(0, song.disc()) * 1000 + qMax(0, song.track()));
|
QString ret = QString::number(std::max(0, song.disc()) * 1000 + std::max(0, song.track()));
|
||||||
ret.prepend(QString("0").repeated(6 - ret.length()));
|
ret.prepend(QString("0").repeated(6 - ret.length()));
|
||||||
ret.append(song.url().toString());
|
ret.append(song.url().toString());
|
||||||
return ret;
|
return ret;
|
||||||
|
|||||||
@@ -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,11 +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 "covermanager/albumcoverloaderoptions.h"
|
||||||
#include "collectionfilteroptions.h"
|
#include "collectionfilteroptions.h"
|
||||||
#include "collectionquery.h"
|
#include "collectionquery.h"
|
||||||
#include "collectionqueryoptions.h"
|
#include "collectionqueryoptions.h"
|
||||||
#include "collectionitem.h"
|
#include "collectionitem.h"
|
||||||
#include "covermanager/albumcoverloaderoptions.h"
|
|
||||||
|
|
||||||
class QSettings;
|
class QSettings;
|
||||||
|
|
||||||
@@ -199,10 +199,10 @@ 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 SetFilterMode(CollectionFilterOptions::FilterMode filter_mode);
|
void SetFilterMode(CollectionFilterOptions::FilterMode filter_mode);
|
||||||
@@ -267,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;
|
||||||
@@ -309,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_;
|
||||||
|
|||||||
@@ -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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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
|
||||||
@@ -84,7 +84,6 @@ CollectionWatcher::CollectionWatcher(Song::Source source, QObject *parent)
|
|||||||
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);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -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) {
|
||||||
@@ -539,8 +539,8 @@ void CollectionWatcher::ScanSubdirectory(const QString &path, const CollectionSu
|
|||||||
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 CollectionSu
|
|||||||
#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 CollectionSu
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 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 CollectionSu
|
|||||||
|
|
||||||
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,7 +669,7 @@ void CollectionWatcher::ScanSubdirectory(const QString &path, const CollectionSu
|
|||||||
// 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;
|
||||||
}
|
}
|
||||||
@@ -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) {
|
||||||
|
|
||||||
@@ -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,18 +855,26 @@ void CollectionWatcher::AddChangedSong(const QString &file, const Song &matching
|
|||||||
changes << "metadata";
|
changes << "metadata";
|
||||||
notify_new = true;
|
notify_new = true;
|
||||||
}
|
}
|
||||||
if (!matching_song.IsStatisticsEqual(new_song)) {
|
if (!matching_song.IsPlayStatisticsEqual(new_song)) {
|
||||||
changes << "statistics";
|
changes << "play statistics";
|
||||||
notify_new = true;
|
notify_new = true;
|
||||||
}
|
}
|
||||||
if (!matching_song.IsRatingEqual(new_song)) {
|
if (!matching_song.IsRatingEqual(new_song)) {
|
||||||
changes << "rating";
|
changes << "rating";
|
||||||
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.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";
|
||||||
}
|
}
|
||||||
@@ -909,7 +917,6 @@ void CollectionWatcher::AddWatch(const CollectionDirectory &dir, const QString &
|
|||||||
|
|
||||||
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;
|
||||||
|
|
||||||
@@ -1032,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.
|
||||||
@@ -1055,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;
|
||||||
@@ -1078,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();
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -1111,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);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1147,34 +1143,6 @@ 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, CollectionSubdirectory(), 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;
|
||||||
@@ -1260,3 +1228,34 @@ quint64 CollectionWatcher::FilesCountForSubdirs(ScanTransaction *t, const Collec
|
|||||||
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
|
||||||
@@ -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,19 +67,21 @@ 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(CollectionSubdirectoryList subdirs);
|
void SubdirsDiscovered(const CollectionSubdirectoryList &subdirs);
|
||||||
void SubdirsMTimeUpdated(CollectionSubdirectoryList 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 CollectionDirectory &dir, const CollectionSubdirectoryList &subdirs);
|
void AddDirectory(const CollectionDirectory &dir, const CollectionSubdirectoryList &subdirs);
|
||||||
@@ -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 CollectionSubdirectory &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 CollectionDirectory &dir, const QString &path);
|
void AddWatch(const CollectionDirectory &dir, const QString &path);
|
||||||
void RemoveWatch(const CollectionDirectory &dir, const CollectionSubdirectory &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);
|
||||||
@@ -209,9 +210,9 @@ class CollectionWatcher : public QObject {
|
|||||||
QThread *original_thread_;
|
QThread *original_thread_;
|
||||||
QHash<QString, CollectionDirectory> 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,7 +224,6 @@ 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, CollectionDirectory> watched_dirs_;
|
QMap<int, CollectionDirectory> watched_dirs_;
|
||||||
QTimer *rescan_timer_;
|
QTimer *rescan_timer_;
|
||||||
@@ -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_;
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -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"
|
||||||
|
|||||||
@@ -55,4 +55,6 @@
|
|||||||
#cmakedefine USE_TAGLIB
|
#cmakedefine USE_TAGLIB
|
||||||
#cmakedefine USE_TAGPARSER
|
#cmakedefine USE_TAGPARSER
|
||||||
|
|
||||||
|
#cmakedefine HAVE_QX11APPLICATION
|
||||||
|
|
||||||
#endif // CONFIG_H_IN
|
#endif // CONFIG_H_IN
|
||||||
|
|||||||
@@ -54,16 +54,14 @@ ContextAlbum::ContextAlbum(QWidget *parent)
|
|||||||
timeline_fade_(new QTimeLine(kFadeTimeLineMs, this)),
|
timeline_fade_(new QTimeLine(kFadeTimeLineMs, this)),
|
||||||
image_strawberry_(":/pictures/strawberry.png"),
|
image_strawberry_(":/pictures/strawberry.png"),
|
||||||
image_original_(image_strawberry_),
|
image_original_(image_strawberry_),
|
||||||
pixmap_current_opacity_(1.0) {
|
pixmap_current_opacity_(1.0),
|
||||||
|
desired_height_(width()) {
|
||||||
|
|
||||||
setObjectName("context-widget-album");
|
setObjectName("context-widget-album");
|
||||||
|
|
||||||
setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed);
|
setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed);
|
||||||
|
|
||||||
cover_loader_options_.desired_height_ = width();
|
QImage image = ImageUtils::ScaleImage(image_strawberry_, QSize(desired_height_, desired_height_), devicePixelRatioF(), true);
|
||||||
cover_loader_options_.pad_output_image_ = true;
|
|
||||||
cover_loader_options_.scale_output_image_ = true;
|
|
||||||
QImage image = ImageUtils::ScaleAndPad(image_strawberry_, cover_loader_options_.scale_output_image_, cover_loader_options_.pad_output_image_, cover_loader_options_.desired_height_);
|
|
||||||
if (!image.isNull()) {
|
if (!image.isNull()) {
|
||||||
pixmap_current_ = QPixmap::fromImage(image);
|
pixmap_current_ = QPixmap::fromImage(image);
|
||||||
}
|
}
|
||||||
@@ -91,7 +89,7 @@ void ContextAlbum::Init(ContextView *context_view, AlbumCoverChoiceController *a
|
|||||||
|
|
||||||
QSize ContextAlbum::sizeHint() const {
|
QSize ContextAlbum::sizeHint() const {
|
||||||
|
|
||||||
return QSize(pixmap_current_.width(), pixmap_current_.height());
|
return QSize(static_cast<int>(pixmap_current_.width() / devicePixelRatioF()), static_cast<int>(pixmap_current_.height() / devicePixelRatioF()));
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -128,8 +126,8 @@ void ContextAlbum::contextMenuEvent(QContextMenuEvent *e) {
|
|||||||
|
|
||||||
void ContextAlbum::UpdateWidth(const int new_width) {
|
void ContextAlbum::UpdateWidth(const int new_width) {
|
||||||
|
|
||||||
if (new_width != cover_loader_options_.desired_height_) {
|
if (new_width != desired_height_) {
|
||||||
cover_loader_options_.desired_height_ = new_width;
|
desired_height_ = new_width;
|
||||||
ScaleCover();
|
ScaleCover();
|
||||||
ScalePreviousCovers();
|
ScalePreviousCovers();
|
||||||
updateGeometry();
|
updateGeometry();
|
||||||
@@ -182,7 +180,7 @@ void ContextAlbum::DrawImage(QPainter *p, const QPixmap &pixmap, const qreal opa
|
|||||||
if (qFuzzyCompare(opacity, static_cast<qreal>(0.0))) return;
|
if (qFuzzyCompare(opacity, static_cast<qreal>(0.0))) return;
|
||||||
|
|
||||||
p->setOpacity(opacity);
|
p->setOpacity(opacity);
|
||||||
p->drawPixmap(0, 0, pixmap.width(), pixmap.height(), pixmap);
|
p->drawPixmap(0, 0, static_cast<int>(pixmap.width() / pixmap.devicePixelRatioF()), static_cast<int>(pixmap.height() / pixmap.devicePixelRatioF()), pixmap);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -235,7 +233,7 @@ void ContextAlbum::FadePreviousCoverFinished(std::shared_ptr<PreviousCover> prev
|
|||||||
|
|
||||||
void ContextAlbum::ScaleCover() {
|
void ContextAlbum::ScaleCover() {
|
||||||
|
|
||||||
QImage image = ImageUtils::ScaleAndPad(image_original_, cover_loader_options_.scale_output_image_, cover_loader_options_.pad_output_image_, cover_loader_options_.desired_height_);
|
const QImage image = ImageUtils::ScaleImage(image_original_, QSize(desired_height_, desired_height_), devicePixelRatioF(), true);
|
||||||
if (image.isNull()) {
|
if (image.isNull()) {
|
||||||
pixmap_current_ = QPixmap();
|
pixmap_current_ = QPixmap();
|
||||||
}
|
}
|
||||||
@@ -248,7 +246,7 @@ void ContextAlbum::ScaleCover() {
|
|||||||
void ContextAlbum::ScalePreviousCovers() {
|
void ContextAlbum::ScalePreviousCovers() {
|
||||||
|
|
||||||
for (std::shared_ptr<PreviousCover> previous_cover : previous_covers_) {
|
for (std::shared_ptr<PreviousCover> previous_cover : previous_covers_) {
|
||||||
QImage image = ImageUtils::ScaleAndPad(previous_cover->image, cover_loader_options_.scale_output_image_, cover_loader_options_.pad_output_image_, cover_loader_options_.desired_height_);
|
QImage image = ImageUtils::ScaleImage(previous_cover->image, QSize(desired_height_, desired_height_), devicePixelRatioF(), true);
|
||||||
if (image.isNull()) {
|
if (image.isNull()) {
|
||||||
previous_cover->pixmap = QPixmap();
|
previous_cover->pixmap = QPixmap();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -33,8 +33,6 @@
|
|||||||
#include <QPixmap>
|
#include <QPixmap>
|
||||||
#include <QMovie>
|
#include <QMovie>
|
||||||
|
|
||||||
#include "covermanager/albumcoverloaderoptions.h"
|
|
||||||
|
|
||||||
class QMenu;
|
class QMenu;
|
||||||
class QTimeLine;
|
class QTimeLine;
|
||||||
class QPainter;
|
class QPainter;
|
||||||
@@ -99,7 +97,6 @@ class ContextAlbum : public QWidget {
|
|||||||
QMenu *menu_;
|
QMenu *menu_;
|
||||||
ContextView *context_view_;
|
ContextView *context_view_;
|
||||||
AlbumCoverChoiceController *album_cover_choice_controller_;
|
AlbumCoverChoiceController *album_cover_choice_controller_;
|
||||||
AlbumCoverLoaderOptions cover_loader_options_;
|
|
||||||
bool downloading_covers_;
|
bool downloading_covers_;
|
||||||
QTimeLine *timeline_fade_;
|
QTimeLine *timeline_fade_;
|
||||||
QImage image_strawberry_;
|
QImage image_strawberry_;
|
||||||
@@ -107,6 +104,7 @@ class ContextAlbum : public QWidget {
|
|||||||
QPixmap pixmap_current_;
|
QPixmap pixmap_current_;
|
||||||
qreal pixmap_current_opacity_;
|
qreal pixmap_current_opacity_;
|
||||||
std::unique_ptr<QMovie> spinner_animation_;
|
std::unique_ptr<QMovie> spinner_animation_;
|
||||||
|
int desired_height_;
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif // CONTEXTALBUM_H
|
#endif // CONTEXTALBUM_H
|
||||||
|
|||||||
@@ -51,15 +51,9 @@
|
|||||||
#include "core/application.h"
|
#include "core/application.h"
|
||||||
#include "core/player.h"
|
#include "core/player.h"
|
||||||
#include "core/song.h"
|
#include "core/song.h"
|
||||||
#include "core/iconloader.h"
|
|
||||||
#include "utilities/strutils.h"
|
#include "utilities/strutils.h"
|
||||||
#include "utilities/timeutils.h"
|
#include "utilities/timeutils.h"
|
||||||
#include "widgets/resizabletextedit.h"
|
#include "widgets/resizabletextedit.h"
|
||||||
#include "engine/engine_fwd.h"
|
|
||||||
#include "engine/enginebase.h"
|
|
||||||
#include "engine/enginetype.h"
|
|
||||||
#include "engine/devicefinders.h"
|
|
||||||
#include "engine/devicefinder.h"
|
|
||||||
#include "collection/collectionbackend.h"
|
#include "collection/collectionbackend.h"
|
||||||
#include "collection/collectionquery.h"
|
#include "collection/collectionquery.h"
|
||||||
#include "collection/collectionview.h"
|
#include "collection/collectionview.h"
|
||||||
@@ -81,7 +75,6 @@ ContextView::ContextView(QWidget *parent)
|
|||||||
menu_options_(new QMenu(this)),
|
menu_options_(new QMenu(this)),
|
||||||
action_show_album_(nullptr),
|
action_show_album_(nullptr),
|
||||||
action_show_data_(nullptr),
|
action_show_data_(nullptr),
|
||||||
action_show_output_(nullptr),
|
|
||||||
action_show_lyrics_(nullptr),
|
action_show_lyrics_(nullptr),
|
||||||
action_search_lyrics_(nullptr),
|
action_search_lyrics_(nullptr),
|
||||||
layout_container_(new QVBoxLayout()),
|
layout_container_(new QVBoxLayout()),
|
||||||
@@ -97,11 +90,8 @@ ContextView::ContextView(QWidget *parent)
|
|||||||
layout_play_(new QVBoxLayout()),
|
layout_play_(new QVBoxLayout()),
|
||||||
label_stop_summary_(new QLabel(this)),
|
label_stop_summary_(new QLabel(this)),
|
||||||
widget_play_data_(new QWidget(this)),
|
widget_play_data_(new QWidget(this)),
|
||||||
widget_play_output_(new QWidget(this)),
|
|
||||||
layout_play_data_(new QGridLayout()),
|
layout_play_data_(new QGridLayout()),
|
||||||
layout_play_output_(new QGridLayout()),
|
|
||||||
textedit_play_lyrics_(new ResizableTextEdit(this)),
|
textedit_play_lyrics_(new ResizableTextEdit(this)),
|
||||||
spacer_play_output_(new QSpacerItem(20, 20, QSizePolicy::Fixed, QSizePolicy::Fixed)),
|
|
||||||
spacer_play_data_(new QSpacerItem(20, 20, QSizePolicy::Fixed, QSizePolicy::Fixed)),
|
spacer_play_data_(new QSpacerItem(20, 20, QSizePolicy::Fixed, QSizePolicy::Fixed)),
|
||||||
label_filetype_title_(new QLabel(this)),
|
label_filetype_title_(new QLabel(this)),
|
||||||
label_length_title_(new QLabel(this)),
|
label_length_title_(new QLabel(this)),
|
||||||
@@ -113,18 +103,8 @@ ContextView::ContextView(QWidget *parent)
|
|||||||
label_samplerate_(new QLabel(this)),
|
label_samplerate_(new QLabel(this)),
|
||||||
label_bitdepth_(new QLabel(this)),
|
label_bitdepth_(new QLabel(this)),
|
||||||
label_bitrate_(new QLabel(this)),
|
label_bitrate_(new QLabel(this)),
|
||||||
label_device_title_(new QLabel(this)),
|
|
||||||
label_engine_title_(new QLabel(this)),
|
|
||||||
label_device_space_(new QLabel(this)),
|
|
||||||
label_engine_space_(new QLabel(this)),
|
|
||||||
label_device_(new QLabel(this)),
|
|
||||||
label_engine_(new QLabel(this)),
|
|
||||||
label_device_icon_(new QLabel(this)),
|
|
||||||
label_engine_icon_(new QLabel(this)),
|
|
||||||
lyrics_tried_(false),
|
lyrics_tried_(false),
|
||||||
lyrics_id_(-1),
|
lyrics_id_(-1) {
|
||||||
font_size_headline_(0),
|
|
||||||
font_size_normal_(0) {
|
|
||||||
|
|
||||||
setLayout(layout_container_);
|
setLayout(layout_container_);
|
||||||
|
|
||||||
@@ -175,36 +155,6 @@ ContextView::ContextView(QWidget *parent)
|
|||||||
|
|
||||||
// Playing
|
// Playing
|
||||||
|
|
||||||
label_engine_title_->setText(tr("Engine"));
|
|
||||||
label_device_title_->setText(tr("Device"));
|
|
||||||
label_engine_title_->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed);
|
|
||||||
label_device_title_->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed);
|
|
||||||
label_engine_space_->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed);
|
|
||||||
label_device_space_->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed);
|
|
||||||
label_engine_space_->setMinimumWidth(24);
|
|
||||||
label_device_space_->setMinimumWidth(24);
|
|
||||||
label_engine_icon_->setMinimumSize(32, 32);
|
|
||||||
label_device_icon_->setMaximumSize(32, 32);
|
|
||||||
label_engine_icon_->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed);
|
|
||||||
label_device_icon_->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed);
|
|
||||||
|
|
||||||
label_engine_->setWordWrap(true);
|
|
||||||
label_device_->setWordWrap(true);
|
|
||||||
|
|
||||||
layout_play_output_->setContentsMargins(0, 0, 0, 0);
|
|
||||||
|
|
||||||
layout_play_output_->addWidget(label_engine_title_, 0, 0);
|
|
||||||
layout_play_output_->addWidget(label_engine_space_, 0, 1);
|
|
||||||
layout_play_output_->addWidget(label_engine_icon_, 0, 2);
|
|
||||||
layout_play_output_->addWidget(label_engine_, 0, 3);
|
|
||||||
|
|
||||||
layout_play_output_->addWidget(label_device_title_, 1, 0);
|
|
||||||
layout_play_output_->addWidget(label_device_space_, 1, 1);
|
|
||||||
layout_play_output_->addWidget(label_device_icon_, 1, 2);
|
|
||||||
layout_play_output_->addWidget(label_device_, 1, 3);
|
|
||||||
|
|
||||||
widget_play_output_->setLayout(layout_play_output_);
|
|
||||||
|
|
||||||
label_filetype_title_->setText(tr("Filetype"));
|
label_filetype_title_->setText(tr("Filetype"));
|
||||||
label_length_title_->setText(tr("Length"));
|
label_length_title_->setText(tr("Length"));
|
||||||
label_samplerate_title_->setText(tr("Samplerate"));
|
label_samplerate_title_->setText(tr("Samplerate"));
|
||||||
@@ -242,26 +192,18 @@ ContextView::ContextView(QWidget *parent)
|
|||||||
textedit_play_lyrics_->hide();
|
textedit_play_lyrics_->hide();
|
||||||
|
|
||||||
layout_play_->setContentsMargins(0, 0, 0, 0);
|
layout_play_->setContentsMargins(0, 0, 0, 0);
|
||||||
layout_play_->addWidget(widget_play_output_);
|
|
||||||
layout_play_->addSpacerItem(spacer_play_output_);
|
|
||||||
layout_play_->addWidget(widget_play_data_);
|
layout_play_->addWidget(widget_play_data_);
|
||||||
layout_play_->addSpacerItem(spacer_play_data_);
|
layout_play_->addSpacerItem(spacer_play_data_);
|
||||||
layout_play_->addWidget(textedit_play_lyrics_);
|
layout_play_->addWidget(textedit_play_lyrics_);
|
||||||
layout_play_->addSpacerItem(new QSpacerItem(20, 20, QSizePolicy::Expanding, QSizePolicy::Expanding));
|
layout_play_->addSpacerItem(new QSpacerItem(20, 20, QSizePolicy::Expanding, QSizePolicy::Expanding));
|
||||||
|
|
||||||
labels_play_ << label_engine_title_
|
labels_play_ << label_filetype_title_
|
||||||
<< label_device_title_
|
|
||||||
<< label_filetype_title_
|
|
||||||
<< label_length_title_
|
<< label_length_title_
|
||||||
<< label_samplerate_title_
|
<< label_samplerate_title_
|
||||||
<< label_bitdepth_title_
|
<< label_bitdepth_title_
|
||||||
<< label_bitrate_title_;
|
<< label_bitrate_title_;
|
||||||
|
|
||||||
labels_play_data_ << label_engine_icon_
|
labels_play_data_ << label_filetype_
|
||||||
<< label_engine_
|
|
||||||
<< label_device_
|
|
||||||
<< label_device_icon_
|
|
||||||
<< label_filetype_
|
|
||||||
<< label_length_
|
<< label_length_
|
||||||
<< label_samplerate_
|
<< label_samplerate_
|
||||||
<< label_bitdepth_
|
<< label_bitdepth_
|
||||||
@@ -303,10 +245,6 @@ void ContextView::AddActions() {
|
|||||||
action_show_data_->setCheckable(true);
|
action_show_data_->setCheckable(true);
|
||||||
action_show_data_->setChecked(true);
|
action_show_data_->setChecked(true);
|
||||||
|
|
||||||
action_show_output_ = new QAction(tr("Show engine and device"), this);
|
|
||||||
action_show_output_->setCheckable(true);
|
|
||||||
action_show_output_->setChecked(true);
|
|
||||||
|
|
||||||
action_show_lyrics_ = new QAction(tr("Show song lyrics"), this);
|
action_show_lyrics_ = new QAction(tr("Show song lyrics"), this);
|
||||||
action_show_lyrics_->setCheckable(true);
|
action_show_lyrics_->setCheckable(true);
|
||||||
action_show_lyrics_->setChecked(true);
|
action_show_lyrics_->setChecked(true);
|
||||||
@@ -317,7 +255,6 @@ void ContextView::AddActions() {
|
|||||||
|
|
||||||
menu_options_->addAction(action_show_album_);
|
menu_options_->addAction(action_show_album_);
|
||||||
menu_options_->addAction(action_show_data_);
|
menu_options_->addAction(action_show_data_);
|
||||||
menu_options_->addAction(action_show_output_);
|
|
||||||
menu_options_->addAction(action_show_lyrics_);
|
menu_options_->addAction(action_show_lyrics_);
|
||||||
menu_options_->addAction(action_search_lyrics_);
|
menu_options_->addAction(action_search_lyrics_);
|
||||||
menu_options_->addSeparator();
|
menu_options_->addSeparator();
|
||||||
@@ -326,7 +263,6 @@ void ContextView::AddActions() {
|
|||||||
|
|
||||||
QObject::connect(action_show_album_, &QAction::triggered, this, &ContextView::ActionShowAlbum);
|
QObject::connect(action_show_album_, &QAction::triggered, this, &ContextView::ActionShowAlbum);
|
||||||
QObject::connect(action_show_data_, &QAction::triggered, this, &ContextView::ActionShowData);
|
QObject::connect(action_show_data_, &QAction::triggered, this, &ContextView::ActionShowData);
|
||||||
QObject::connect(action_show_output_, &QAction::triggered, this, &ContextView::ActionShowOutput);
|
|
||||||
QObject::connect(action_show_lyrics_, &QAction::triggered, this, &ContextView::ActionShowLyrics);
|
QObject::connect(action_show_lyrics_, &QAction::triggered, this, &ContextView::ActionShowLyrics);
|
||||||
QObject::connect(action_search_lyrics_, &QAction::triggered, this, &ContextView::ActionSearchLyrics);
|
QObject::connect(action_search_lyrics_, &QAction::triggered, this, &ContextView::ActionSearchLyrics);
|
||||||
|
|
||||||
@@ -334,19 +270,32 @@ void ContextView::AddActions() {
|
|||||||
|
|
||||||
void ContextView::ReloadSettings() {
|
void ContextView::ReloadSettings() {
|
||||||
|
|
||||||
|
QString default_font;
|
||||||
|
#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
|
||||||
|
if (QFontDatabase::families().contains(ContextSettingsPage::kDefaultFontFamily)) {
|
||||||
|
#else
|
||||||
|
if (QFontDatabase().families().contains(ContextSettingsPage::kDefaultFontFamily)) {
|
||||||
|
#endif
|
||||||
|
default_font = ContextSettingsPage::kDefaultFontFamily;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
default_font = font().family();
|
||||||
|
}
|
||||||
|
|
||||||
QSettings s;
|
QSettings s;
|
||||||
s.beginGroup(ContextSettingsPage::kSettingsGroup);
|
s.beginGroup(ContextSettingsPage::kSettingsGroup);
|
||||||
title_fmt_ = s.value(ContextSettingsPage::kSettingsTitleFmt, "%title% - %artist%").toString();
|
title_fmt_ = s.value(ContextSettingsPage::kSettingsTitleFmt, "%title% - %artist%").toString();
|
||||||
summary_fmt_ = s.value(ContextSettingsPage::kSettingsSummaryFmt, "%album%").toString();
|
summary_fmt_ = s.value(ContextSettingsPage::kSettingsSummaryFmt, "%album%").toString();
|
||||||
action_show_album_->setChecked(s.value(ContextSettingsPage::kSettingsGroupEnable[static_cast<int>(ContextSettingsPage::ContextSettingsOrder::ALBUM)], true).toBool());
|
action_show_album_->setChecked(s.value(ContextSettingsPage::kSettingsGroupEnable[static_cast<int>(ContextSettingsPage::ContextSettingsOrder::ALBUM)], true).toBool());
|
||||||
action_show_data_->setChecked(s.value(ContextSettingsPage::kSettingsGroupEnable[static_cast<int>(ContextSettingsPage::ContextSettingsOrder::TECHNICAL_DATA)], false).toBool());
|
action_show_data_->setChecked(s.value(ContextSettingsPage::kSettingsGroupEnable[static_cast<int>(ContextSettingsPage::ContextSettingsOrder::TECHNICAL_DATA)], false).toBool());
|
||||||
action_show_output_->setChecked(s.value(ContextSettingsPage::kSettingsGroupEnable[static_cast<int>(ContextSettingsPage::ContextSettingsOrder::ENGINE_AND_DEVICE)], false).toBool());
|
|
||||||
action_show_lyrics_->setChecked(s.value(ContextSettingsPage::kSettingsGroupEnable[static_cast<int>(ContextSettingsPage::ContextSettingsOrder::SONG_LYRICS)], true).toBool());
|
action_show_lyrics_->setChecked(s.value(ContextSettingsPage::kSettingsGroupEnable[static_cast<int>(ContextSettingsPage::ContextSettingsOrder::SONG_LYRICS)], true).toBool());
|
||||||
action_search_lyrics_->setChecked(s.value(ContextSettingsPage::kSettingsGroupEnable[static_cast<int>(ContextSettingsPage::ContextSettingsOrder::SEARCH_LYRICS)], true).toBool());
|
action_search_lyrics_->setChecked(s.value(ContextSettingsPage::kSettingsGroupEnable[static_cast<int>(ContextSettingsPage::ContextSettingsOrder::SEARCH_LYRICS)], true).toBool());
|
||||||
font_headline_ = s.value("font_headline", font().family()).toString();
|
font_headline_.setFamily(s.value("font_headline", default_font).toString());
|
||||||
font_normal_ = s.value("font_normal", font().family()).toString();
|
font_headline_.setPointSizeF(s.value("font_size_headline", ContextSettingsPage::kDefaultFontSizeHeadline).toReal());
|
||||||
font_size_headline_ = s.value("font_size_headline", ContextSettingsPage::kDefaultFontSizeHeadline).toReal();
|
font_nosong_.setFamily(font_headline_.family());
|
||||||
font_size_normal_ = s.value("font_size_normal", font().pointSizeF()).toReal();
|
font_nosong_.setPointSizeF(font_headline_.pointSizeF() * 1.6F);
|
||||||
|
font_normal_.setFamily(s.value("font_normal", default_font).toString());
|
||||||
|
font_normal_.setPointSizeF(s.value("font_size_normal", font().pointSizeF()).toReal());
|
||||||
s.endGroup();
|
s.endGroup();
|
||||||
|
|
||||||
UpdateFonts();
|
UpdateFonts();
|
||||||
@@ -407,7 +356,7 @@ void ContextView::SearchLyrics() {
|
|||||||
if (lyrics_.isEmpty() && action_show_lyrics_->isChecked() && action_search_lyrics_->isChecked() && !song_playing_.artist().isEmpty() && !song_playing_.title().isEmpty() && !lyrics_tried_ && lyrics_id_ == -1) {
|
if (lyrics_.isEmpty() && action_show_lyrics_->isChecked() && action_search_lyrics_->isChecked() && !song_playing_.artist().isEmpty() && !song_playing_.title().isEmpty() && !lyrics_tried_ && lyrics_id_ == -1) {
|
||||||
lyrics_fetcher_->Clear();
|
lyrics_fetcher_->Clear();
|
||||||
lyrics_tried_ = true;
|
lyrics_tried_ = true;
|
||||||
lyrics_id_ = static_cast<qint64>(lyrics_fetcher_->Search(song_playing_.effective_albumartist(), song_playing_.album(), song_playing_.title()));
|
lyrics_id_ = static_cast<qint64>(lyrics_fetcher_->Search(song_playing_.effective_albumartist(), song_playing_.artist(), song_playing_.album(), song_playing_.title()));
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -435,7 +384,7 @@ void ContextView::NoSong() {
|
|||||||
widget_album_->show();
|
widget_album_->show();
|
||||||
}
|
}
|
||||||
|
|
||||||
textedit_top_->setFont(QFont(font_headline_, font_size_headline_ * 1.6));
|
textedit_top_->setFont(font_nosong_);
|
||||||
textedit_top_->SetText(tr("No song playing"));
|
textedit_top_->SetText(tr("No song playing"));
|
||||||
|
|
||||||
QString html;
|
QString html;
|
||||||
@@ -451,27 +400,25 @@ void ContextView::NoSong() {
|
|||||||
else html += tr("%1 albums").arg(collectionview_->TotalAlbums());
|
else html += tr("%1 albums").arg(collectionview_->TotalAlbums());
|
||||||
html += "<br />";
|
html += "<br />";
|
||||||
|
|
||||||
label_stop_summary_->setFont(QFont(font_normal_, font_size_normal_));
|
label_stop_summary_->setFont(font_normal_);
|
||||||
label_stop_summary_->setText(html);
|
label_stop_summary_->setText(html);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void ContextView::UpdateFonts() {
|
void ContextView::UpdateFonts() {
|
||||||
|
|
||||||
QFont font(font_normal_, font_size_normal_);
|
|
||||||
font.setBold(false);
|
|
||||||
for (QLabel *l : labels_play_all_) {
|
for (QLabel *l : labels_play_all_) {
|
||||||
l->setFont(font);
|
l->setFont(font_normal_);
|
||||||
}
|
}
|
||||||
for (QTextEdit *e : textedit_play_) {
|
for (QTextEdit *e : textedit_play_) {
|
||||||
e->setFont(font);
|
e->setFont(font_normal_);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void ContextView::SetSong() {
|
void ContextView::SetSong() {
|
||||||
|
|
||||||
textedit_top_->setFont(QFont(font_headline_, font_size_headline_));
|
textedit_top_->setFont(font_headline_);
|
||||||
textedit_top_->SetText(QString("<b>%1</b><br />%2").arg(Utilities::ReplaceMessage(title_fmt_, song_playing_, "<br />", true), Utilities::ReplaceMessage(summary_fmt_, song_playing_, "<br />", true)));
|
textedit_top_->SetText(QString("<b>%1</b><br />%2").arg(Utilities::ReplaceMessage(title_fmt_, song_playing_, "<br />", true), Utilities::ReplaceMessage(summary_fmt_, song_playing_, "<br />", true)));
|
||||||
|
|
||||||
label_stop_summary_->clear();
|
label_stop_summary_->clear();
|
||||||
@@ -542,49 +489,6 @@ void ContextView::SetSong() {
|
|||||||
spacer_play_data_->changeSize(0, 0, QSizePolicy::Fixed);
|
spacer_play_data_->changeSize(0, 0, QSizePolicy::Fixed);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (action_show_output_->isChecked()) {
|
|
||||||
widget_play_output_->show();
|
|
||||||
Engine::EngineType enginetype(Engine::EngineType::None);
|
|
||||||
if (app_->player()->engine()) enginetype = app_->player()->engine()->type();
|
|
||||||
QIcon icon_engine = IconLoader::Load(EngineName(enginetype), true, 32);
|
|
||||||
|
|
||||||
label_engine_icon_->setPixmap(icon_engine.pixmap(QSize(32, 32)));
|
|
||||||
label_engine_->setText(EngineDescription(enginetype));
|
|
||||||
spacer_play_output_->changeSize(20, 20, QSizePolicy::Fixed);
|
|
||||||
|
|
||||||
DeviceFinder::Device device;
|
|
||||||
for (DeviceFinder *f : app_->device_finders()->ListFinders()) {
|
|
||||||
for (const DeviceFinder::Device &d : f->ListDevices()) {
|
|
||||||
if (d.value != app_->player()->engine()->device()) continue;
|
|
||||||
device = d;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (device.value.isValid()) {
|
|
||||||
label_device_title_->show();
|
|
||||||
label_device_icon_->show();
|
|
||||||
label_device_->show();
|
|
||||||
QIcon icon_device = IconLoader::Load(device.iconname, true, 32);
|
|
||||||
label_device_icon_->setPixmap(icon_device.pixmap(QSize(32, 32)));
|
|
||||||
label_device_->setText(device.description);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
label_device_title_->hide();
|
|
||||||
label_device_icon_->hide();
|
|
||||||
label_device_->hide();
|
|
||||||
label_device_icon_->clear();
|
|
||||||
label_device_->clear();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
widget_play_output_->hide();
|
|
||||||
label_engine_icon_->clear();
|
|
||||||
label_engine_->clear();
|
|
||||||
label_device_icon_->clear();
|
|
||||||
label_device_->clear();
|
|
||||||
spacer_play_output_->changeSize(0, 0, QSizePolicy::Fixed);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (action_show_lyrics_->isChecked() && !lyrics_.isEmpty()) {
|
if (action_show_lyrics_->isChecked() && !lyrics_.isEmpty()) {
|
||||||
textedit_play_lyrics_->SetText(lyrics_);
|
textedit_play_lyrics_->SetText(lyrics_);
|
||||||
textedit_play_lyrics_->show();
|
textedit_play_lyrics_->show();
|
||||||
@@ -671,7 +575,6 @@ void ContextView::ResetSong() {
|
|||||||
l->clear();
|
l->clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
widget_play_output_->hide();
|
|
||||||
widget_play_data_->hide();
|
widget_play_data_->hide();
|
||||||
textedit_play_lyrics_->hide();
|
textedit_play_lyrics_->hide();
|
||||||
|
|
||||||
@@ -755,16 +658,6 @@ void ContextView::ActionShowData() {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void ContextView::ActionShowOutput() {
|
|
||||||
|
|
||||||
QSettings s;
|
|
||||||
s.beginGroup(ContextSettingsPage::kSettingsGroup);
|
|
||||||
s.setValue(ContextSettingsPage::kSettingsGroupEnable[static_cast<int>(ContextSettingsPage::ContextSettingsOrder::ENGINE_AND_DEVICE)], action_show_output_->isChecked());
|
|
||||||
s.endGroup();
|
|
||||||
if (song_playing_.is_valid()) SetSong();
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
void ContextView::ActionShowLyrics() {
|
void ContextView::ActionShowLyrics() {
|
||||||
|
|
||||||
QSettings s;
|
QSettings s;
|
||||||
|
|||||||
@@ -86,7 +86,6 @@ class ContextView : public QWidget {
|
|||||||
private slots:
|
private slots:
|
||||||
void ActionShowAlbum();
|
void ActionShowAlbum();
|
||||||
void ActionShowData();
|
void ActionShowData();
|
||||||
void ActionShowOutput();
|
|
||||||
void ActionShowLyrics();
|
void ActionShowLyrics();
|
||||||
void ActionSearchLyrics();
|
void ActionSearchLyrics();
|
||||||
void UpdateNoSong();
|
void UpdateNoSong();
|
||||||
@@ -112,7 +111,6 @@ class ContextView : public QWidget {
|
|||||||
QMenu *menu_options_;
|
QMenu *menu_options_;
|
||||||
QAction *action_show_album_;
|
QAction *action_show_album_;
|
||||||
QAction *action_show_data_;
|
QAction *action_show_data_;
|
||||||
QAction *action_show_output_;
|
|
||||||
QAction *action_show_lyrics_;
|
QAction *action_show_lyrics_;
|
||||||
QAction *action_search_lyrics_;
|
QAction *action_search_lyrics_;
|
||||||
|
|
||||||
@@ -129,12 +127,9 @@ class ContextView : public QWidget {
|
|||||||
QVBoxLayout *layout_play_;
|
QVBoxLayout *layout_play_;
|
||||||
QLabel *label_stop_summary_;
|
QLabel *label_stop_summary_;
|
||||||
QWidget *widget_play_data_;
|
QWidget *widget_play_data_;
|
||||||
QWidget *widget_play_output_;
|
|
||||||
QGridLayout *layout_play_data_;
|
QGridLayout *layout_play_data_;
|
||||||
QGridLayout *layout_play_output_;
|
|
||||||
ResizableTextEdit *textedit_play_lyrics_;
|
ResizableTextEdit *textedit_play_lyrics_;
|
||||||
|
|
||||||
QSpacerItem *spacer_play_output_;
|
|
||||||
QSpacerItem *spacer_play_data_;
|
QSpacerItem *spacer_play_data_;
|
||||||
|
|
||||||
QLabel *label_filetype_title_;
|
QLabel *label_filetype_title_;
|
||||||
@@ -149,15 +144,6 @@ class ContextView : public QWidget {
|
|||||||
QLabel *label_bitdepth_;
|
QLabel *label_bitdepth_;
|
||||||
QLabel *label_bitrate_;
|
QLabel *label_bitrate_;
|
||||||
|
|
||||||
QLabel *label_device_title_;
|
|
||||||
QLabel *label_engine_title_;
|
|
||||||
QLabel *label_device_space_;
|
|
||||||
QLabel *label_engine_space_;
|
|
||||||
QLabel *label_device_;
|
|
||||||
QLabel *label_engine_;
|
|
||||||
QLabel *label_device_icon_;
|
|
||||||
QLabel *label_engine_icon_;
|
|
||||||
|
|
||||||
Song song_playing_;
|
Song song_playing_;
|
||||||
Song song_prev_;
|
Song song_prev_;
|
||||||
QImage image_original_;
|
QImage image_original_;
|
||||||
@@ -166,10 +152,9 @@ class ContextView : public QWidget {
|
|||||||
QString lyrics_;
|
QString lyrics_;
|
||||||
QString title_fmt_;
|
QString title_fmt_;
|
||||||
QString summary_fmt_;
|
QString summary_fmt_;
|
||||||
QString font_headline_;
|
QFont font_headline_;
|
||||||
QString font_normal_;
|
QFont font_normal_;
|
||||||
qreal font_size_headline_;
|
QFont font_nosong_;
|
||||||
qreal font_size_normal_;
|
|
||||||
|
|
||||||
QList<QLabel*> labels_play_;
|
QList<QLabel*> labels_play_;
|
||||||
QList<ResizableTextEdit*> textedit_play_;
|
QList<ResizableTextEdit*> textedit_play_;
|
||||||
|
|||||||
@@ -13,9 +13,11 @@
|
|||||||
SBSystemPreferencesWindow, SBSystemPreferencesPane,
|
SBSystemPreferencesWindow, SBSystemPreferencesPane,
|
||||||
SBSystemPreferencesAnchor;
|
SBSystemPreferencesAnchor;
|
||||||
|
|
||||||
|
#ifdef __GNUC__
|
||||||
#pragma GCC diagnostic push
|
#pragma GCC diagnostic push
|
||||||
#pragma GCC diagnostic ignored "-Wmultichar"
|
#pragma GCC diagnostic ignored "-Wmultichar"
|
||||||
#pragma GCC diagnostic ignored "-Wfour-char-constants"
|
#pragma GCC diagnostic ignored "-Wfour-char-constants"
|
||||||
|
#endif
|
||||||
|
|
||||||
enum SBSystemPreferencesSaveOptions {
|
enum SBSystemPreferencesSaveOptions {
|
||||||
SBSystemPreferencesSaveOptionsYes = 'yes ' /* Save the file. */,
|
SBSystemPreferencesSaveOptionsYes = 'yes ' /* Save the file. */,
|
||||||
@@ -32,7 +34,9 @@ enum SBSystemPreferencesPrintingErrorHandling {
|
|||||||
'lwdt' /* print a detailed report of PostScript errors */
|
'lwdt' /* print a detailed report of PostScript errors */
|
||||||
};
|
};
|
||||||
|
|
||||||
|
#ifdef __GNUC__
|
||||||
#pragma GCC diagnostic pop
|
#pragma GCC diagnostic pop
|
||||||
|
#endif
|
||||||
|
|
||||||
typedef enum SBSystemPreferencesPrintingErrorHandling
|
typedef enum SBSystemPreferencesPrintingErrorHandling
|
||||||
SBSystemPreferencesPrintingErrorHandling;
|
SBSystemPreferencesPrintingErrorHandling;
|
||||||
|
|||||||
@@ -38,6 +38,7 @@
|
|||||||
#include "database.h"
|
#include "database.h"
|
||||||
#include "taskmanager.h"
|
#include "taskmanager.h"
|
||||||
#include "player.h"
|
#include "player.h"
|
||||||
|
#include "networkaccessmanager.h"
|
||||||
|
|
||||||
#include "engine/devicefinders.h"
|
#include "engine/devicefinders.h"
|
||||||
#ifndef Q_OS_WIN
|
#ifndef Q_OS_WIN
|
||||||
@@ -57,16 +58,21 @@
|
|||||||
#include "covermanager/spotifycoverprovider.h"
|
#include "covermanager/spotifycoverprovider.h"
|
||||||
|
|
||||||
#include "lyrics/lyricsproviders.h"
|
#include "lyrics/lyricsproviders.h"
|
||||||
#include "lyrics/auddlyricsprovider.h"
|
|
||||||
#include "lyrics/geniuslyricsprovider.h"
|
#include "lyrics/geniuslyricsprovider.h"
|
||||||
#include "lyrics/ovhlyricsprovider.h"
|
#include "lyrics/ovhlyricsprovider.h"
|
||||||
#include "lyrics/lololyricsprovider.h"
|
#include "lyrics/lololyricsprovider.h"
|
||||||
#include "lyrics/musixmatchlyricsprovider.h"
|
#include "lyrics/musixmatchlyricsprovider.h"
|
||||||
#include "lyrics/chartlyricsprovider.h"
|
#include "lyrics/chartlyricsprovider.h"
|
||||||
#include "lyrics/stands4lyricsprovider.h"
|
#include "lyrics/lyricscomlyricsprovider.h"
|
||||||
|
|
||||||
#include "scrobbler/audioscrobbler.h"
|
#include "scrobbler/audioscrobbler.h"
|
||||||
|
#include "scrobbler/lastfmscrobbler.h"
|
||||||
|
#include "scrobbler/librefmscrobbler.h"
|
||||||
|
#include "scrobbler/listenbrainzscrobbler.h"
|
||||||
#include "scrobbler/lastfmimport.h"
|
#include "scrobbler/lastfmimport.h"
|
||||||
|
#ifdef HAVE_SUBSONIC
|
||||||
|
# include "scrobbler/subsonicscrobbler.h"
|
||||||
|
#endif
|
||||||
|
|
||||||
#include "internet/internetservices.h"
|
#include "internet/internetservices.h"
|
||||||
|
|
||||||
@@ -111,6 +117,7 @@ class ApplicationImpl {
|
|||||||
}),
|
}),
|
||||||
task_manager_([app]() { return new TaskManager(app); }),
|
task_manager_([app]() { return new TaskManager(app); }),
|
||||||
player_([app]() { return new Player(app, app); }),
|
player_([app]() { return new Player(app, app); }),
|
||||||
|
network_([app]() { return new NetworkAccessManager(app); }),
|
||||||
device_finders_([app]() { return new DeviceFinders(app); }),
|
device_finders_([app]() { return new DeviceFinders(app); }),
|
||||||
#ifndef Q_OS_WIN
|
#ifndef Q_OS_WIN
|
||||||
device_manager_([app]() { return new DeviceManager(app, app); }),
|
device_manager_([app]() { return new DeviceManager(app, app); }),
|
||||||
@@ -125,17 +132,17 @@ class ApplicationImpl {
|
|||||||
cover_providers_([app]() {
|
cover_providers_([app]() {
|
||||||
CoverProviders *cover_providers = new CoverProviders(app);
|
CoverProviders *cover_providers = new CoverProviders(app);
|
||||||
// Initialize the repository of cover providers.
|
// Initialize the repository of cover providers.
|
||||||
cover_providers->AddProvider(new LastFmCoverProvider(app, cover_providers->network(), app));
|
cover_providers->AddProvider(new LastFmCoverProvider(app, app->network(), app));
|
||||||
cover_providers->AddProvider(new MusicbrainzCoverProvider(app, cover_providers->network(), app));
|
cover_providers->AddProvider(new MusicbrainzCoverProvider(app, app->network(), app));
|
||||||
cover_providers->AddProvider(new DiscogsCoverProvider(app, cover_providers->network(), app));
|
cover_providers->AddProvider(new DiscogsCoverProvider(app, app->network(), app));
|
||||||
cover_providers->AddProvider(new DeezerCoverProvider(app, cover_providers->network(), app));
|
cover_providers->AddProvider(new DeezerCoverProvider(app, app->network(), app));
|
||||||
cover_providers->AddProvider(new MusixmatchCoverProvider(app, cover_providers->network(), app));
|
cover_providers->AddProvider(new MusixmatchCoverProvider(app, app->network(), app));
|
||||||
cover_providers->AddProvider(new SpotifyCoverProvider(app, cover_providers->network(), app));
|
cover_providers->AddProvider(new SpotifyCoverProvider(app, app->network(), app));
|
||||||
#ifdef HAVE_TIDAL
|
#ifdef HAVE_TIDAL
|
||||||
cover_providers->AddProvider(new TidalCoverProvider(app, cover_providers->network(), app));
|
cover_providers->AddProvider(new TidalCoverProvider(app, app->network(), app));
|
||||||
#endif
|
#endif
|
||||||
#ifdef HAVE_QOBUZ
|
#ifdef HAVE_QOBUZ
|
||||||
cover_providers->AddProvider(new QobuzCoverProvider(app, cover_providers->network(), app));
|
cover_providers->AddProvider(new QobuzCoverProvider(app, app->network(), app));
|
||||||
#endif
|
#endif
|
||||||
cover_providers->ReloadSettings();
|
cover_providers->ReloadSettings();
|
||||||
return cover_providers;
|
return cover_providers;
|
||||||
@@ -149,13 +156,12 @@ class ApplicationImpl {
|
|||||||
lyrics_providers_([app]() {
|
lyrics_providers_([app]() {
|
||||||
LyricsProviders *lyrics_providers = new LyricsProviders(app);
|
LyricsProviders *lyrics_providers = new LyricsProviders(app);
|
||||||
// Initialize the repository of lyrics providers.
|
// Initialize the repository of lyrics providers.
|
||||||
lyrics_providers->AddProvider(new AuddLyricsProvider(lyrics_providers->network(), app));
|
lyrics_providers->AddProvider(new GeniusLyricsProvider(app->network(), app));
|
||||||
lyrics_providers->AddProvider(new GeniusLyricsProvider(lyrics_providers->network(), app));
|
lyrics_providers->AddProvider(new OVHLyricsProvider(app->network(), app));
|
||||||
lyrics_providers->AddProvider(new OVHLyricsProvider(lyrics_providers->network(), app));
|
lyrics_providers->AddProvider(new LoloLyricsProvider(app->network(), app));
|
||||||
lyrics_providers->AddProvider(new LoloLyricsProvider(lyrics_providers->network(), app));
|
lyrics_providers->AddProvider(new MusixmatchLyricsProvider(app->network(), app));
|
||||||
lyrics_providers->AddProvider(new MusixmatchLyricsProvider(lyrics_providers->network(), app));
|
lyrics_providers->AddProvider(new ChartLyricsProvider(app->network(), app));
|
||||||
lyrics_providers->AddProvider(new ChartLyricsProvider(lyrics_providers->network(), app));
|
lyrics_providers->AddProvider(new LyricsComLyricsProvider(app->network(), app));
|
||||||
lyrics_providers->AddProvider(new Stands4LyricsProvider(lyrics_providers->network(), app));
|
|
||||||
lyrics_providers->ReloadSettings();
|
lyrics_providers->ReloadSettings();
|
||||||
return lyrics_providers;
|
return lyrics_providers;
|
||||||
}),
|
}),
|
||||||
@@ -173,18 +179,25 @@ class ApplicationImpl {
|
|||||||
return internet_services;
|
return internet_services;
|
||||||
}),
|
}),
|
||||||
radio_services_([app]() { return new RadioServices(app, app); }),
|
radio_services_([app]() { return new RadioServices(app, app); }),
|
||||||
scrobbler_([app]() { return new AudioScrobbler(app, app); }),
|
scrobbler_([app]() {
|
||||||
|
AudioScrobbler *scrobbler = new AudioScrobbler(app);
|
||||||
|
scrobbler->AddService(new LastFMScrobbler(scrobbler, app->network(), app));
|
||||||
|
scrobbler->AddService(new LibreFMScrobbler(scrobbler, app->network(), app));
|
||||||
|
scrobbler->AddService(new ListenBrainzScrobbler(scrobbler, app->network(), app));
|
||||||
|
return scrobbler;
|
||||||
|
}),
|
||||||
#ifdef HAVE_MOODBAR
|
#ifdef HAVE_MOODBAR
|
||||||
moodbar_loader_([app]() { return new MoodbarLoader(app, app); }),
|
moodbar_loader_([app]() { return new MoodbarLoader(app, app); }),
|
||||||
moodbar_controller_([app]() { return new MoodbarController(app, app); }),
|
moodbar_controller_([app]() { return new MoodbarController(app, app); }),
|
||||||
#endif
|
#endif
|
||||||
lastfm_import_([app]() { return new LastFMImport(app); })
|
lastfm_import_([app]() { return new LastFMImport(app->network(), app); })
|
||||||
{}
|
{}
|
||||||
|
|
||||||
Lazy<TagReaderClient> tag_reader_client_;
|
Lazy<TagReaderClient> tag_reader_client_;
|
||||||
Lazy<Database> database_;
|
Lazy<Database> database_;
|
||||||
Lazy<TaskManager> task_manager_;
|
Lazy<TaskManager> task_manager_;
|
||||||
Lazy<Player> player_;
|
Lazy<Player> player_;
|
||||||
|
Lazy<NetworkAccessManager> network_;
|
||||||
Lazy<DeviceFinders> device_finders_;
|
Lazy<DeviceFinders> device_finders_;
|
||||||
#ifndef Q_OS_WIN
|
#ifndef Q_OS_WIN
|
||||||
Lazy<DeviceManager> device_manager_;
|
Lazy<DeviceManager> device_manager_;
|
||||||
@@ -317,6 +330,7 @@ TagReaderClient *Application::tag_reader_client() const { return p_->tag_reader_
|
|||||||
Database *Application::database() const { return p_->database_.get(); }
|
Database *Application::database() const { return p_->database_.get(); }
|
||||||
TaskManager *Application::task_manager() const { return p_->task_manager_.get(); }
|
TaskManager *Application::task_manager() const { return p_->task_manager_.get(); }
|
||||||
Player *Application::player() const { return p_->player_.get(); }
|
Player *Application::player() const { return p_->player_.get(); }
|
||||||
|
NetworkAccessManager *Application::network() const { return p_->network_.get(); }
|
||||||
DeviceFinders *Application::device_finders() const { return p_->device_finders_.get(); }
|
DeviceFinders *Application::device_finders() const { return p_->device_finders_.get(); }
|
||||||
#ifndef Q_OS_WIN
|
#ifndef Q_OS_WIN
|
||||||
DeviceManager *Application::device_manager() const { return p_->device_manager_.get(); }
|
DeviceManager *Application::device_manager() const { return p_->device_manager_.get(); }
|
||||||
|
|||||||
@@ -41,6 +41,7 @@ class TagReaderClient;
|
|||||||
class Database;
|
class Database;
|
||||||
class DeviceFinders;
|
class DeviceFinders;
|
||||||
class Player;
|
class Player;
|
||||||
|
class NetworkAccessManager;
|
||||||
class SCollection;
|
class SCollection;
|
||||||
class CollectionBackend;
|
class CollectionBackend;
|
||||||
class CollectionModel;
|
class CollectionModel;
|
||||||
@@ -74,6 +75,7 @@ class Application : public QObject {
|
|||||||
Database *database() const;
|
Database *database() const;
|
||||||
TaskManager *task_manager() const;
|
TaskManager *task_manager() const;
|
||||||
Player *player() const;
|
Player *player() const;
|
||||||
|
NetworkAccessManager *network() const;
|
||||||
DeviceFinders *device_finders() const;
|
DeviceFinders *device_finders() const;
|
||||||
#ifndef Q_OS_WIN
|
#ifndef Q_OS_WIN
|
||||||
DeviceManager *device_manager() const;
|
DeviceManager *device_manager() const;
|
||||||
@@ -118,9 +120,9 @@ class Application : public QObject {
|
|||||||
void OpenSettingsDialogAtPage(SettingsDialog::Page page);
|
void OpenSettingsDialogAtPage(SettingsDialog::Page page);
|
||||||
|
|
||||||
signals:
|
signals:
|
||||||
void ErrorAdded(QString message);
|
void ErrorAdded(const QString &message);
|
||||||
void SettingsChanged();
|
void SettingsChanged();
|
||||||
void SettingsDialogRequested(SettingsDialog::Page page);
|
void SettingsDialogRequested(const SettingsDialog::Page page);
|
||||||
void ExitFinished();
|
void ExitFinished();
|
||||||
void ClearPixmapDiskCache();
|
void ClearPixmapDiskCache();
|
||||||
|
|
||||||
|
|||||||
@@ -22,10 +22,14 @@
|
|||||||
#include "version.h"
|
#include "version.h"
|
||||||
|
|
||||||
#include <cstdlib>
|
#include <cstdlib>
|
||||||
#include <getopt.h>
|
|
||||||
#include <iostream>
|
#include <iostream>
|
||||||
|
|
||||||
#include <QtGlobal>
|
#include <QtGlobal>
|
||||||
|
|
||||||
|
#ifdef Q_OS_WIN32
|
||||||
|
# include <windows.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
#include <QObject>
|
#include <QObject>
|
||||||
#include <QIODevice>
|
#include <QIODevice>
|
||||||
#include <QDataStream>
|
#include <QDataStream>
|
||||||
@@ -39,6 +43,8 @@
|
|||||||
#include "commandlineoptions.h"
|
#include "commandlineoptions.h"
|
||||||
#include "core/logging.h"
|
#include "core/logging.h"
|
||||||
|
|
||||||
|
#include <getopt.h>
|
||||||
|
|
||||||
const char *CommandlineOptions::kHelpText =
|
const char *CommandlineOptions::kHelpText =
|
||||||
"%1: strawberry [%2] [%3]\n"
|
"%1: strawberry [%2] [%3]\n"
|
||||||
"\n"
|
"\n"
|
||||||
@@ -80,7 +86,11 @@ const char *CommandlineOptions::kVersionText = "Strawberry %1";
|
|||||||
|
|
||||||
CommandlineOptions::CommandlineOptions(int argc, char **argv)
|
CommandlineOptions::CommandlineOptions(int argc, char **argv)
|
||||||
: argc_(argc),
|
: argc_(argc),
|
||||||
|
#ifdef Q_OS_WIN32
|
||||||
|
argv_(CommandLineToArgvW(GetCommandLineW(), &argc)),
|
||||||
|
#else
|
||||||
argv_(argv),
|
argv_(argv),
|
||||||
|
#endif
|
||||||
url_list_action_(UrlListAction::None),
|
url_list_action_(UrlListAction::None),
|
||||||
player_action_(PlayerAction::None),
|
player_action_(PlayerAction::None),
|
||||||
set_volume_(-1),
|
set_volume_(-1),
|
||||||
@@ -92,6 +102,10 @@ CommandlineOptions::CommandlineOptions(int argc, char **argv)
|
|||||||
toggle_pretty_osd_(false),
|
toggle_pretty_osd_(false),
|
||||||
log_levels_(logging::kDefaultLogLevels) {
|
log_levels_(logging::kDefaultLogLevels) {
|
||||||
|
|
||||||
|
#ifdef Q_OS_WIN32
|
||||||
|
Q_UNUSED(argv);
|
||||||
|
#endif
|
||||||
|
|
||||||
#ifdef Q_OS_MACOS
|
#ifdef Q_OS_MACOS
|
||||||
// Remove -psn_xxx option that Mac passes when opened from Finder.
|
// Remove -psn_xxx option that Mac passes when opened from Finder.
|
||||||
RemoveArg("-psn", 1);
|
RemoveArg("-psn", 1);
|
||||||
@@ -99,12 +113,13 @@ CommandlineOptions::CommandlineOptions(int argc, char **argv)
|
|||||||
|
|
||||||
// Remove the -session option that KDE passes
|
// Remove the -session option that KDE passes
|
||||||
RemoveArg("-session", 2);
|
RemoveArg("-session", 2);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void CommandlineOptions::RemoveArg(const QString &starts_with, int count) {
|
void CommandlineOptions::RemoveArg(const QString &starts_with, int count) {
|
||||||
|
|
||||||
for (int i = 0; i < argc_; ++i) {
|
for (int i = 0; i < argc_; ++i) {
|
||||||
QString opt(argv_[i]);
|
const QString opt = OptArgToString(argv_[i]);
|
||||||
if (opt.startsWith(starts_with)) {
|
if (opt.startsWith(starts_with)) {
|
||||||
for (int j = i; j < argc_ - count + 1; ++j) {
|
for (int j = i; j < argc_ - count + 1; ++j) {
|
||||||
argv_[j] = argv_[j + count];
|
argv_[j] = argv_[j + count];
|
||||||
@@ -119,41 +134,79 @@ void CommandlineOptions::RemoveArg(const QString &starts_with, int count) {
|
|||||||
bool CommandlineOptions::Parse() {
|
bool CommandlineOptions::Parse() {
|
||||||
|
|
||||||
static const struct option kOptions[] = {
|
static const struct option kOptions[] = {
|
||||||
{"help", no_argument, nullptr, 'h'},
|
#ifdef Q_OS_WIN32
|
||||||
{"play", no_argument, nullptr, 'p'},
|
{L"help", no_argument, nullptr, 'h'},
|
||||||
{"play-pause", no_argument, nullptr, 't'},
|
{L"play", no_argument, nullptr, 'p'},
|
||||||
{"pause", no_argument, nullptr, 'u'},
|
{L"play-pause", no_argument, nullptr, 't'},
|
||||||
{"stop", no_argument, nullptr, 's'},
|
{L"pause", no_argument, nullptr, 'u'},
|
||||||
{"stop-after-current", no_argument, nullptr, 'q'},
|
{L"stop", no_argument, nullptr, 's'},
|
||||||
{"previous", no_argument, nullptr, 'r'},
|
{L"stop-after-current", no_argument, nullptr, 'q'},
|
||||||
{"next", no_argument, nullptr, 'f'},
|
{L"previous", no_argument, nullptr, 'r'},
|
||||||
{"volume", required_argument, nullptr, 'v'},
|
{L"next", no_argument, nullptr, 'f'},
|
||||||
{"volume-up", no_argument, nullptr, LongOptions::VolumeUp},
|
{L"volume", required_argument, nullptr, 'v'},
|
||||||
{"volume-down", no_argument, nullptr, LongOptions::VolumeDown},
|
{L"volume-up", no_argument, nullptr, LongOptions::VolumeUp},
|
||||||
{"volume-increase-by", required_argument, nullptr, LongOptions::VolumeIncreaseBy},
|
{L"volume-down", no_argument, nullptr, LongOptions::VolumeDown},
|
||||||
{"volume-decrease-by", required_argument, nullptr, LongOptions::VolumeDecreaseBy},
|
{L"volume-increase-by", required_argument, nullptr, LongOptions::VolumeIncreaseBy},
|
||||||
{"seek-to", required_argument, nullptr, LongOptions::SeekTo},
|
{L"volume-decrease-by", required_argument, nullptr, LongOptions::VolumeDecreaseBy},
|
||||||
{"seek-by", required_argument, nullptr, LongOptions::SeekBy},
|
{L"seek-to", required_argument, nullptr, LongOptions::SeekTo},
|
||||||
{"restart-or-previous", no_argument, nullptr, LongOptions::RestartOrPrevious},
|
{L"seek-by", required_argument, nullptr, LongOptions::SeekBy},
|
||||||
{"create", required_argument, nullptr, 'c'},
|
{L"restart-or-previous", no_argument, nullptr, LongOptions::RestartOrPrevious },
|
||||||
{"append", no_argument, nullptr, 'a'},
|
{L"create", required_argument, nullptr, 'c' },
|
||||||
{"load", no_argument, nullptr, 'l'},
|
{L"append", no_argument, nullptr, 'a' },
|
||||||
{"play-track", required_argument, nullptr, 'k'},
|
{L"load", no_argument, nullptr, 'l'},
|
||||||
{"play-playlist", required_argument, nullptr, 'i'},
|
{L"play-track", required_argument, nullptr, 'k'},
|
||||||
{"show-osd", no_argument, nullptr, 'o'},
|
{L"play-playlist", required_argument, nullptr, 'i'},
|
||||||
{"toggle-pretty-osd", no_argument, nullptr, 'y'},
|
{L"show-osd", no_argument, nullptr, 'o'},
|
||||||
{"language", required_argument, nullptr, 'g'},
|
{L"toggle-pretty-osd", no_argument, nullptr, 'y'},
|
||||||
{"resize-window", required_argument, nullptr, 'w'},
|
{L"language", required_argument, nullptr, 'g'},
|
||||||
{"quiet", no_argument, nullptr, LongOptions::Quiet},
|
{L"resize-window", required_argument, nullptr, 'w'},
|
||||||
{"verbose", no_argument, nullptr, LongOptions::Verbose},
|
{L"quiet", no_argument, nullptr, LongOptions::Quiet},
|
||||||
{"log-levels", required_argument, nullptr, LongOptions::LogLevels},
|
{L"verbose", no_argument, nullptr, LongOptions::Verbose},
|
||||||
{"version", no_argument, nullptr, LongOptions::Version},
|
{L"log-levels", required_argument, nullptr, LongOptions::LogLevels},
|
||||||
{nullptr, 0, nullptr, 0}};
|
{L"version", no_argument, nullptr, LongOptions::Version},
|
||||||
|
{nullptr, 0, nullptr, 0}
|
||||||
|
#else
|
||||||
|
{ "help", no_argument, nullptr, 'h' },
|
||||||
|
{ "play", no_argument, nullptr, 'p' },
|
||||||
|
{ "play-pause", no_argument, nullptr, 't' },
|
||||||
|
{ "pause", no_argument, nullptr, 'u' },
|
||||||
|
{ "stop", no_argument, nullptr, 's' },
|
||||||
|
{ "stop-after-current", no_argument, nullptr, 'q' },
|
||||||
|
{ "previous", no_argument, nullptr, 'r' },
|
||||||
|
{ "next", no_argument, nullptr, 'f' },
|
||||||
|
{ "volume", required_argument, nullptr, 'v' },
|
||||||
|
{ "volume-up", no_argument, nullptr, LongOptions::VolumeUp },
|
||||||
|
{ "volume-down", no_argument, nullptr, LongOptions::VolumeDown },
|
||||||
|
{ "volume-increase-by", required_argument, nullptr, LongOptions::VolumeIncreaseBy },
|
||||||
|
{ "volume-decrease-by", required_argument, nullptr, LongOptions::VolumeDecreaseBy },
|
||||||
|
{ "seek-to", required_argument, nullptr, LongOptions::SeekTo },
|
||||||
|
{ "seek-by", required_argument, nullptr, LongOptions::SeekBy },
|
||||||
|
{ "restart-or-previous", no_argument, nullptr, LongOptions::RestartOrPrevious },
|
||||||
|
{ "create", required_argument, nullptr, 'c' },
|
||||||
|
{ "append", no_argument, nullptr, 'a' },
|
||||||
|
{ "load", no_argument, nullptr, 'l' },
|
||||||
|
{ "play-track", required_argument, nullptr, 'k' },
|
||||||
|
{ "play-playlist", required_argument, nullptr, 'i' },
|
||||||
|
{ "show-osd", no_argument, nullptr, 'o' },
|
||||||
|
{ "toggle-pretty-osd", no_argument, nullptr, 'y' },
|
||||||
|
{ "language", required_argument, nullptr, 'g' },
|
||||||
|
{ "resize-window", required_argument, nullptr, 'w' },
|
||||||
|
{ "quiet", no_argument, nullptr, LongOptions::Quiet },
|
||||||
|
{ "verbose", no_argument, nullptr, LongOptions::Verbose },
|
||||||
|
{ "log-levels", required_argument, nullptr, LongOptions::LogLevels },
|
||||||
|
{ "version", no_argument, nullptr, LongOptions::Version },
|
||||||
|
{ nullptr, 0, nullptr, 0 }
|
||||||
|
#endif
|
||||||
|
};
|
||||||
|
|
||||||
// Parse the arguments
|
// Parse the arguments
|
||||||
bool ok = false;
|
bool ok = false;
|
||||||
forever {
|
forever {
|
||||||
|
#ifdef Q_OS_WIN32
|
||||||
|
int c = getopt_long(argc_, argv_, L"hptusqrfv:c:alk:i:oyg:w:", kOptions, nullptr);
|
||||||
|
#else
|
||||||
int c = getopt_long(argc_, argv_, "hptusqrfv:c:alk:i:oyg:w:", kOptions, nullptr);
|
int c = getopt_long(argc_, argv_, "hptusqrfv:c:alk:i:oyg:w:", kOptions, nullptr);
|
||||||
|
#endif
|
||||||
|
|
||||||
// End of the options
|
// End of the options
|
||||||
if (c == -1) break;
|
if (c == -1) break;
|
||||||
@@ -162,36 +215,36 @@ bool CommandlineOptions::Parse() {
|
|||||||
case 'h': {
|
case 'h': {
|
||||||
QString translated_help_text =
|
QString translated_help_text =
|
||||||
QString(kHelpText)
|
QString(kHelpText)
|
||||||
.arg(tr("Usage"), tr("options"), tr("URL(s)"),
|
.arg(QObject::tr("Usage"), QObject::tr("options"), QObject::tr("URL(s)"),
|
||||||
tr("Player options"),
|
QObject::tr("Player options"),
|
||||||
tr("Start the playlist currently playing"),
|
QObject::tr("Start the playlist currently playing"),
|
||||||
tr("Play if stopped, pause if playing"),
|
QObject::tr("Play if stopped, pause if playing"),
|
||||||
tr("Pause playback"), tr("Stop playback"),
|
QObject::tr("Pause playback"), QObject::tr("Stop playback"),
|
||||||
tr("Stop playback after current track"))
|
QObject::tr("Stop playback after current track"))
|
||||||
.arg(tr("Skip backwards in playlist"),
|
.arg(QObject::tr("Skip backwards in playlist"),
|
||||||
tr("Skip forwards in playlist"),
|
QObject::tr("Skip forwards in playlist"),
|
||||||
tr("Set the volume to <value> percent"),
|
QObject::tr("Set the volume to <value> percent"),
|
||||||
tr("Increase the volume by 4 percent"),
|
QObject::tr("Increase the volume by 4 percent"),
|
||||||
tr("Decrease the volume by 4 percent"),
|
QObject::tr("Decrease the volume by 4 percent"),
|
||||||
tr("Increase the volume by <value> percent"),
|
QObject::tr("Increase the volume by <value> percent"),
|
||||||
tr("Decrease the volume by <value> percent"))
|
QObject::tr("Decrease the volume by <value> percent"))
|
||||||
.arg(tr("Seek the currently playing track to an absolute position"),
|
.arg(QObject::tr("Seek the currently playing track to an absolute position"),
|
||||||
tr("Seek the currently playing track by a relative amount"),
|
QObject::tr("Seek the currently playing track by a relative amount"),
|
||||||
tr("Restart the track, or play the previous track if within 8 seconds of start."),
|
QObject::tr("Restart the track, or play the previous track if within 8 seconds of start."),
|
||||||
tr("Playlist options"),
|
QObject::tr("Playlist options"),
|
||||||
tr("Create a new playlist with files"),
|
QObject::tr("Create a new playlist with files"),
|
||||||
tr("Append files/URLs to the playlist"),
|
QObject::tr("Append files/URLs to the playlist"),
|
||||||
tr("Loads files/URLs, replacing current playlist"),
|
QObject::tr("Loads files/URLs, replacing current playlist"),
|
||||||
tr("Play the <n>th track in the playlist"),
|
QObject::tr("Play the <n>th track in the playlist"),
|
||||||
tr("Play given playlist"))
|
QObject::tr("Play given playlist"))
|
||||||
.arg(tr("Other options"), tr("Display the on-screen-display"),
|
.arg(QObject::tr("Other options"), QObject::tr("Display the on-screen-display"),
|
||||||
tr("Toggle visibility for the pretty on-screen-display"),
|
QObject::tr("Toggle visibility for the pretty on-screen-display"),
|
||||||
tr("Change the language"),
|
QObject::tr("Change the language"),
|
||||||
tr("Resize the window"),
|
QObject::tr("Resize the window"),
|
||||||
tr("Equivalent to --log-levels *:1"),
|
QObject::tr("Equivalent to --log-levels *:1"),
|
||||||
tr("Equivalent to --log-levels *:3"),
|
QObject::tr("Equivalent to --log-levels *:3"),
|
||||||
tr("Comma separated list of class:level, level is 0-3"))
|
QObject::tr("Comma separated list of class:level, level is 0-3"))
|
||||||
.arg(tr("Print out version information"));
|
.arg(QObject::tr("Print out version information"));
|
||||||
|
|
||||||
std::cout << translated_help_text.toLocal8Bit().constData();
|
std::cout << translated_help_text.toLocal8Bit().constData();
|
||||||
return false;
|
return false;
|
||||||
@@ -220,11 +273,11 @@ bool CommandlineOptions::Parse() {
|
|||||||
break;
|
break;
|
||||||
case 'i':
|
case 'i':
|
||||||
player_action_ = PlayerAction::PlayPlaylist;
|
player_action_ = PlayerAction::PlayPlaylist;
|
||||||
playlist_name_ = QString(optarg);
|
playlist_name_ = OptArgToString(optarg);
|
||||||
break;
|
break;
|
||||||
case 'c':
|
case 'c':
|
||||||
url_list_action_ = UrlListAction::CreateNew;
|
url_list_action_ = UrlListAction::CreateNew;
|
||||||
playlist_name_ = QString(optarg);
|
playlist_name_ = OptArgToString(optarg);
|
||||||
break;
|
break;
|
||||||
case 'a':
|
case 'a':
|
||||||
url_list_action_ = UrlListAction::Append;
|
url_list_action_ = UrlListAction::Append;
|
||||||
@@ -239,7 +292,7 @@ bool CommandlineOptions::Parse() {
|
|||||||
toggle_pretty_osd_ = true;
|
toggle_pretty_osd_ = true;
|
||||||
break;
|
break;
|
||||||
case 'g':
|
case 'g':
|
||||||
language_ = QString(optarg);
|
language_ = OptArgToString(optarg);
|
||||||
break;
|
break;
|
||||||
case LongOptions::VolumeUp:
|
case LongOptions::VolumeUp:
|
||||||
volume_modifier_ = +4;
|
volume_modifier_ = +4;
|
||||||
@@ -254,7 +307,7 @@ bool CommandlineOptions::Parse() {
|
|||||||
log_levels_ = "3";
|
log_levels_ = "3";
|
||||||
break;
|
break;
|
||||||
case LongOptions::LogLevels:
|
case LongOptions::LogLevels:
|
||||||
log_levels_ = QString(optarg);
|
log_levels_ = OptArgToString(optarg);
|
||||||
break;
|
break;
|
||||||
case LongOptions::Version: {
|
case LongOptions::Version: {
|
||||||
QString version_text = QString(kVersionText).arg(STRAWBERRY_VERSION_DISPLAY);
|
QString version_text = QString(kVersionText).arg(STRAWBERRY_VERSION_DISPLAY);
|
||||||
@@ -262,27 +315,27 @@ bool CommandlineOptions::Parse() {
|
|||||||
std::exit(0);
|
std::exit(0);
|
||||||
}
|
}
|
||||||
case 'v':
|
case 'v':
|
||||||
set_volume_ = QString(optarg).toInt(&ok);
|
set_volume_ = OptArgToString(optarg).toInt(&ok);
|
||||||
if (!ok) set_volume_ = -1;
|
if (!ok) set_volume_ = -1;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case LongOptions::VolumeIncreaseBy:
|
case LongOptions::VolumeIncreaseBy:
|
||||||
volume_modifier_ = QString(optarg).toInt(&ok);
|
volume_modifier_ = OptArgToString(optarg).toInt(&ok);
|
||||||
if (!ok) volume_modifier_ = 0;
|
if (!ok) volume_modifier_ = 0;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case LongOptions::VolumeDecreaseBy:
|
case LongOptions::VolumeDecreaseBy:
|
||||||
volume_modifier_ = -QString(optarg).toInt(&ok);
|
volume_modifier_ = -OptArgToString(optarg).toInt(&ok);
|
||||||
if (!ok) volume_modifier_ = 0;
|
if (!ok) volume_modifier_ = 0;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case LongOptions::SeekTo:
|
case LongOptions::SeekTo:
|
||||||
seek_to_ = QString(optarg).toInt(&ok);
|
seek_to_ = OptArgToString(optarg).toInt(&ok);
|
||||||
if (!ok) seek_to_ = -1;
|
if (!ok) seek_to_ = -1;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case LongOptions::SeekBy:
|
case LongOptions::SeekBy:
|
||||||
seek_by_ = QString(optarg).toInt(&ok);
|
seek_by_ = OptArgToString(optarg).toInt(&ok);
|
||||||
if (!ok) seek_by_ = 0;
|
if (!ok) seek_by_ = 0;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
@@ -291,12 +344,12 @@ bool CommandlineOptions::Parse() {
|
|||||||
break;
|
break;
|
||||||
|
|
||||||
case 'k':
|
case 'k':
|
||||||
play_track_at_ = QString(optarg).toInt(&ok);
|
play_track_at_ = OptArgToString(optarg).toInt(&ok);
|
||||||
if (!ok) play_track_at_ = -1;
|
if (!ok) play_track_at_ = -1;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 'w':
|
case 'w':
|
||||||
window_size_ = QString(optarg);
|
window_size_ = OptArgToString(optarg);
|
||||||
player_action_ = PlayerAction::ResizeWindow;
|
player_action_ = PlayerAction::ResizeWindow;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
@@ -308,10 +361,10 @@ bool CommandlineOptions::Parse() {
|
|||||||
|
|
||||||
// Get any filenames or URLs following the arguments
|
// Get any filenames or URLs following the arguments
|
||||||
for (int i = optind; i < argc_; ++i) {
|
for (int i = optind; i < argc_; ++i) {
|
||||||
QString value = QFile::decodeName(argv_[i]);
|
const QString value = DecodeName(argv_[i]);
|
||||||
QFileInfo file_info(value);
|
QFileInfo fileinfo(value);
|
||||||
if (file_info.exists()) {
|
if (fileinfo.exists()) {
|
||||||
urls_ << QUrl::fromLocalFile(file_info.canonicalFilePath());
|
urls_ << QUrl::fromLocalFile(fileinfo.canonicalFilePath());
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
urls_ << QUrl::fromUserInput(value);
|
urls_ << QUrl::fromUserInput(value);
|
||||||
@@ -362,10 +415,30 @@ void CommandlineOptions::Load(const QByteArray &serialized) {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
QString CommandlineOptions::tr(const char *source_text) {
|
#ifdef Q_OS_WIN32
|
||||||
return QObject::tr(source_text); // clazy:exclude=tr-non-literal
|
QString CommandlineOptions::OptArgToString(wchar_t *opt) {
|
||||||
|
|
||||||
|
return QString::fromWCharArray(opt);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
QString CommandlineOptions::DecodeName(wchar_t *opt) {
|
||||||
|
|
||||||
|
return QString::fromWCharArray(opt);
|
||||||
|
}
|
||||||
|
#else
|
||||||
|
QString CommandlineOptions::OptArgToString(char *opt) {
|
||||||
|
|
||||||
|
return QString(opt);
|
||||||
|
}
|
||||||
|
|
||||||
|
QString CommandlineOptions::DecodeName(char *opt) {
|
||||||
|
|
||||||
|
return QFile::decodeName(opt);
|
||||||
|
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
QDataStream &operator<<(QDataStream &s, const CommandlineOptions &a) {
|
QDataStream &operator<<(QDataStream &s, const CommandlineOptions &a) {
|
||||||
|
|
||||||
s << static_cast<quint32>(a.player_action_)
|
s << static_cast<quint32>(a.player_action_)
|
||||||
|
|||||||
@@ -23,12 +23,17 @@
|
|||||||
|
|
||||||
#include "config.h"
|
#include "config.h"
|
||||||
|
|
||||||
|
#include <QtGlobal>
|
||||||
#include <QDataStream>
|
#include <QDataStream>
|
||||||
#include <QByteArray>
|
#include <QByteArray>
|
||||||
#include <QList>
|
#include <QList>
|
||||||
#include <QString>
|
#include <QString>
|
||||||
#include <QUrl>
|
#include <QUrl>
|
||||||
|
|
||||||
|
#ifdef Q_OS_WIN32
|
||||||
|
# include <windows.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
class CommandlineOptions {
|
class CommandlineOptions {
|
||||||
friend QDataStream &operator<<(QDataStream &s, const CommandlineOptions &a);
|
friend QDataStream &operator<<(QDataStream &s, const CommandlineOptions &a);
|
||||||
friend QDataStream &operator>>(QDataStream &s, CommandlineOptions &a);
|
friend QDataStream &operator>>(QDataStream &s, CommandlineOptions &a);
|
||||||
@@ -100,12 +105,23 @@ class CommandlineOptions {
|
|||||||
RestartOrPrevious
|
RestartOrPrevious
|
||||||
};
|
};
|
||||||
|
|
||||||
static QString tr(const char *source_text);
|
|
||||||
void RemoveArg(const QString &starts_with, int count);
|
void RemoveArg(const QString &starts_with, int count);
|
||||||
|
|
||||||
|
#ifdef Q_OS_WIN32
|
||||||
|
static QString OptArgToString(wchar_t *opt);
|
||||||
|
static QString DecodeName(wchar_t *opt);
|
||||||
|
#else
|
||||||
|
static QString OptArgToString(char *opt);
|
||||||
|
static QString DecodeName(char *opt);
|
||||||
|
#endif
|
||||||
|
|
||||||
private:
|
private:
|
||||||
int argc_;
|
int argc_;
|
||||||
|
#ifdef Q_OS_WIN32
|
||||||
|
LPWSTR *argv_;
|
||||||
|
#else
|
||||||
char **argv_;
|
char **argv_;
|
||||||
|
#endif
|
||||||
|
|
||||||
UrlListAction url_list_action_;
|
UrlListAction url_list_action_;
|
||||||
PlayerAction player_action_;
|
PlayerAction player_action_;
|
||||||
|
|||||||
@@ -48,7 +48,7 @@
|
|||||||
#include "scopedtransaction.h"
|
#include "scopedtransaction.h"
|
||||||
|
|
||||||
const char *Database::kDatabaseFilename = "strawberry.db";
|
const char *Database::kDatabaseFilename = "strawberry.db";
|
||||||
const int Database::kSchemaVersion = 15;
|
const int Database::kSchemaVersion = 17;
|
||||||
const int Database::kMinSupportedSchemaVersion = 10;
|
const int Database::kMinSupportedSchemaVersion = 10;
|
||||||
const char *Database::kMagicAllSongsTables = "%allsongstables";
|
const char *Database::kMagicAllSongsTables = "%allsongstables";
|
||||||
|
|
||||||
@@ -91,7 +91,7 @@ Database::~Database() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void Database::ExitAsync() {
|
void Database::ExitAsync() {
|
||||||
QMetaObject::invokeMethod(this, "Exit", Qt::QueuedConnection);
|
QMetaObject::invokeMethod(this, &Database::Exit, Qt::QueuedConnection);
|
||||||
}
|
}
|
||||||
|
|
||||||
void Database::Exit() {
|
void Database::Exit() {
|
||||||
@@ -472,12 +472,10 @@ void Database::ReportErrors(const SqlQuery &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 SQL query: " << sql_error;
|
qLog(Error) << "Unable to execute SQL query:" << sql_error;
|
||||||
qLog(Error) << "Failed query: " << query.LastQuery();
|
qLog(Error) << "Failed SQL query:" << query.LastQuery();
|
||||||
QString error;
|
emit Error(tr("Unable to execute SQL query: %1").arg(sql_error.text()));
|
||||||
error += "Unable to execute SQL query: " + sql_error.text() + "<br />";
|
emit Error(tr("Failed SQL query: %1").arg(query.LastQuery()));
|
||||||
error += "Failed query: " + query.LastQuery();
|
|
||||||
emit Error(error);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -488,11 +486,11 @@ bool Database::IntegrityCheck(const QSqlDatabase &db) {
|
|||||||
const int task_id = app_->task_manager()->StartTask(tr("Integrity check"));
|
const int task_id = app_->task_manager()->StartTask(tr("Integrity check"));
|
||||||
|
|
||||||
bool ok = false;
|
bool ok = false;
|
||||||
bool error_reported = false;
|
|
||||||
// Ask for 10 error messages at most.
|
// Ask for 10 error messages at most.
|
||||||
SqlQuery q(db);
|
SqlQuery q(db);
|
||||||
q.prepare("PRAGMA integrity_check(10)");
|
q.prepare("PRAGMA integrity_check(10)");
|
||||||
if (q.Exec()) {
|
if (q.Exec()) {
|
||||||
|
bool error_reported = false;
|
||||||
while (q.next()) {
|
while (q.next()) {
|
||||||
QString message = q.value(0).toString();
|
QString message = q.value(0).toString();
|
||||||
|
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user