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
|
||||
DeriveLineEnding: true
|
||||
DerivePointerAlignment: false
|
||||
DisableFormat: false
|
||||
DisableFormat: true
|
||||
EmptyLineAfterAccessModifier: Never
|
||||
EmptyLineBeforeAccessModifier: LogicalBlock
|
||||
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
|
||||
============================================
|
||||
|
||||
singleapplication
|
||||
KDSingleApplication
|
||||
-----------------
|
||||
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 you dynamically link to your systems version, you'll need two versions, one defined as QApplication and
|
||||
one as a QCoreApplication.
|
||||
It is included here because it is not packed by distros and is also used on macOS and Windows.
|
||||
It is also used to pass command-line options through to the first instance.
|
||||
|
||||
URL: https://github.com/itay-grudev/SingleApplication
|
||||
URL: https://github.com/KDAB/KDSingleApplication/
|
||||
|
||||
|
||||
SPMediaKeyTap
|
||||
@@ -27,4 +25,4 @@ Can safely be deleted on other platforms.
|
||||
|
||||
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})
|
||||
|
||||
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__
|
||||
/**
|
||||
* DISCLAIMER
|
||||
* This file has no copyright assigned and is placed in the Public Domain.
|
||||
* This file is part of the mingw-w64 runtime package.
|
||||
*
|
||||
* The mingw-w64 runtime package and its code is distributed in the hope that it
|
||||
* will be useful but WITHOUT ANY WARRANTY. ALL WARRANTIES, EXPRESSED OR
|
||||
* IMPLIED ARE HEREBY DISCLAIMED. This includes but is not limited to
|
||||
* warranties of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
||||
*/
|
||||
|
||||
#define __GETOPT_H__
|
||||
|
||||
/* All the headers include this file. */
|
||||
#include <crtdefs.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
extern int optind; /* index of first non-option in argv */
|
||||
extern int optopt; /* single option character, as parsed */
|
||||
extern int opterr; /* flag to enable built-in diagnostics... */
|
||||
/* (user may set to zero, to suppress) */
|
||||
|
||||
extern char *optarg; /* pointer to argument of current option */
|
||||
|
||||
extern int getopt(int nargc, char * const *nargv, const char *options);
|
||||
|
||||
#ifdef _BSD_SOURCE
|
||||
/*
|
||||
* BSD adds the non-standard `optreset' feature, for reinitialisation
|
||||
* of `getopt' parsing. We support this feature, for applications which
|
||||
* proclaim their BSD heritage, before including this header; however,
|
||||
* to maintain portability, developers are advised to avoid it.
|
||||
*/
|
||||
# define optreset __mingw_optreset
|
||||
extern int optreset;
|
||||
#endif
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
/*
|
||||
* POSIX requires the `getopt' API to be specified in `unistd.h';
|
||||
* thus, `unistd.h' includes this header. However, we do not want
|
||||
* to expose the `getopt_long' or `getopt_long_only' APIs, when
|
||||
* included in this manner. Thus, close the standard __GETOPT_H__
|
||||
* declarations block, and open an additional __GETOPT_LONG_H__
|
||||
* specific block, only when *not* __UNISTD_H_SOURCED__, in which
|
||||
* to declare the extended API.
|
||||
*/
|
||||
#endif /* !defined(__GETOPT_H__) */
|
||||
|
||||
#if !defined(__UNISTD_H_SOURCED__) && !defined(__GETOPT_LONG_H__)
|
||||
#define __GETOPT_LONG_H__
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
struct option /* specification for a long form option... */
|
||||
{
|
||||
const char *name; /* option name, without leading hyphens */
|
||||
int has_arg; /* does it take an argument? */
|
||||
int *flag; /* where to save its status, or NULL */
|
||||
int val; /* its associated status value */
|
||||
};
|
||||
|
||||
enum /* permitted values for its `has_arg' field... */
|
||||
{
|
||||
no_argument = 0, /* option never takes an argument */
|
||||
required_argument, /* option always requires an argument */
|
||||
optional_argument /* option may take an argument */
|
||||
};
|
||||
|
||||
extern int getopt_long(int nargc, char * const *nargv, const char *options,
|
||||
const struct option *long_options, int *idx);
|
||||
extern int getopt_long_only(int nargc, char * const *nargv, const char *options,
|
||||
const struct option *long_options, int *idx);
|
||||
/*
|
||||
* Previous MinGW implementation had...
|
||||
*/
|
||||
#ifndef HAVE_DECL_GETOPT
|
||||
/*
|
||||
* ...for the long form API only; keep this for compatibility.
|
||||
*/
|
||||
# define HAVE_DECL_GETOPT 1
|
||||
#endif
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif /* !defined(__UNISTD_H_SOURCED__) && !defined(__GETOPT_LONG_H__) */
|
||||
/* 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.
|
||||
*/
|
||||
#ifndef __GETOPT_H_
|
||||
#define __GETOPT_H_
|
||||
|
||||
#ifdef _GETOPT_API
|
||||
# undef _GETOPT_API
|
||||
#endif
|
||||
|
||||
#if defined(EXPORTS_GETOPT) && defined(STATIC_GETOPT)
|
||||
# error "The preprocessor definitions of EXPORTS_GETOPT and STATIC_GETOPT can only be used individually"
|
||||
#elif defined(STATIC_GETOPT)
|
||||
# define _GETOPT_API
|
||||
#elif defined(EXPORTS_GETOPT)
|
||||
# pragma message("Exporting getopt library")
|
||||
# define _GETOPT_API __declspec(dllexport)
|
||||
#else
|
||||
# pragma message("Importing getopt library")
|
||||
# define _GETOPT_API __declspec(dllimport)
|
||||
#endif
|
||||
|
||||
// Change behavior for C\C++
|
||||
#ifdef __cplusplus
|
||||
# define _BEGIN_EXTERN_C extern "C" {
|
||||
# define _END_EXTERN_C }
|
||||
# define _GETOPT_THROW throw()
|
||||
#else
|
||||
# define _BEGIN_EXTERN_C
|
||||
# define _END_EXTERN_C
|
||||
# define _GETOPT_THROW
|
||||
#endif
|
||||
|
||||
// Standard GNU options
|
||||
#define null_argument 0 /*Argument Null*/
|
||||
#define no_argument 0 /*Argument Switch Only*/
|
||||
#define required_argument 1 /*Argument Required*/
|
||||
#define optional_argument 2 /*Argument Optional*/
|
||||
|
||||
// Shorter Options
|
||||
#define ARG_NULL 0 /*Argument Null*/
|
||||
#define ARG_NONE 0 /*Argument Switch Only*/
|
||||
#define ARG_REQ 1 /*Argument Required*/
|
||||
#define ARG_OPT 2 /*Argument Optional*/
|
||||
|
||||
#include <string.h>
|
||||
#include <wchar.h>
|
||||
|
||||
_BEGIN_EXTERN_C
|
||||
|
||||
extern _GETOPT_API int optind;
|
||||
extern _GETOPT_API int opterr;
|
||||
extern _GETOPT_API int optopt;
|
||||
|
||||
// Ansi
|
||||
struct option_a {
|
||||
const char *name;
|
||||
int has_arg;
|
||||
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)
|
||||
|
||||
cmake_policy(SET CMP0054 NEW)
|
||||
if (CMAKE_VERSION VERSION_GREATER_EQUAL 3.12)
|
||||
if(POLICY CMP0054)
|
||||
cmake_policy(SET CMP0054 NEW)
|
||||
endif()
|
||||
if(POLICY CMP0074)
|
||||
cmake_policy(SET CMP0074 NEW)
|
||||
endif()
|
||||
|
||||
@@ -115,9 +117,14 @@ else()
|
||||
find_package(Iconv)
|
||||
endif()
|
||||
find_package(GnuTLS REQUIRED)
|
||||
find_package(Protobuf REQUIRED)
|
||||
if(NOT Protobuf_PROTOC_EXECUTABLE)
|
||||
message(FATAL_ERROR "Missing protobuf compiler.")
|
||||
if(NOT APPLE)
|
||||
find_package(Protobuf CONFIG)
|
||||
endif()
|
||||
if(NOT Protobuf_FOUND)
|
||||
find_package(Protobuf REQUIRED)
|
||||
endif()
|
||||
if(NOT TARGET protobuf::protoc)
|
||||
message(FATAL_ERROR "Missing Protobuf compiler.")
|
||||
endif()
|
||||
if(LINUX)
|
||||
find_package(ALSA REQUIRED)
|
||||
@@ -177,7 +184,7 @@ if(DBUS_FOUND AND NOT WIN32)
|
||||
list(APPEND QT_COMPONENTS DBus)
|
||||
endif()
|
||||
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)
|
||||
set(QT_VERSION_MAJOR 6 CACHE STRING "" FORCE)
|
||||
@@ -274,6 +281,24 @@ if(X11_FOUND)
|
||||
else()
|
||||
message(STATUS "Missing qpa/qplatformnativeinterface.h header.")
|
||||
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)
|
||||
|
||||
option(USE_TAGLIB "Build with TagLib" OFF)
|
||||
@@ -308,10 +333,10 @@ if(NOT TAGLIB_FOUND AND NOT TAGPARSER_FOUND)
|
||||
endif()
|
||||
|
||||
# SingleApplication
|
||||
add_subdirectory(3rdparty/singleapplication)
|
||||
set(SINGLEAPPLICATION_INCLUDE_DIRS ${CMAKE_CURRENT_SOURCE_DIR}/3rdparty/singleapplication)
|
||||
set(SINGLEAPPLICATION_LIBRARIES singleapplication)
|
||||
set(SINGLECOREAPPLICATION_LIBRARIES singlecoreapplication)
|
||||
add_subdirectory(3rdparty/kdsingleapplication)
|
||||
set(SINGLEAPPLICATION_INCLUDE_DIRS ${CMAKE_CURRENT_SOURCE_DIR}/3rdparty/kdsingleapplication)
|
||||
set(SINGLEAPPLICATION_LIBRARIES kdsingleapplication)
|
||||
add_definitions(-DKDSINGLEAPPLICATION_STATIC_BUILD)
|
||||
|
||||
if(APPLE)
|
||||
add_subdirectory(3rdparty/SPMediaKeyTap)
|
||||
@@ -332,10 +357,11 @@ if(WIN32)
|
||||
endif()
|
||||
endif()
|
||||
|
||||
if(WIN32 AND MSVC)
|
||||
if(WIN32)
|
||||
add_subdirectory(3rdparty/getopt)
|
||||
set(GETOPT_INCLUDE_DIRS ${CMAKE_CURRENT_SOURCE_DIR}/3rdparty/getopt)
|
||||
set(GETOPT_LIBRARIES getopt)
|
||||
add_definitions(-DSTATIC_GETOPT -D_UNICODE)
|
||||
endif()
|
||||
|
||||
if(WIN32 AND NOT MSVC)
|
||||
@@ -376,12 +402,12 @@ optional_component(VLC ON "Engine: VLC backend"
|
||||
|
||||
optional_component(SONGFINGERPRINTING ON "Song fingerprinting and tracking"
|
||||
DEPENDS "chromaprint" CHROMAPRINT_FOUND
|
||||
DEPENDS "gstreamer" GSTREAMER_FOUND
|
||||
DEPENDS "gstreamer" HAVE_GSTREAMER
|
||||
)
|
||||
|
||||
optional_component(MUSICBRAINZ ON "MusicBrainz integration"
|
||||
DEPENDS "chromaprint" CHROMAPRINT_FOUND
|
||||
DEPENDS "gstreamer" GSTREAMER_FOUND
|
||||
DEPENDS "gstreamer" HAVE_GSTREAMER
|
||||
)
|
||||
|
||||
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
|
||||
)
|
||||
|
||||
if(BUILD_WITH_QT6 AND (Qt6Core_VERSION VERSION_EQUAL 6.2.0 OR Qt6Core_VERSION VERSION_GREATER 6.2.0))
|
||||
optional_component(X11_GLOBALSHORTCUTS ON "X11 global shortcuts" DEPENDS "X11" X11_FOUND)
|
||||
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
|
||||
)
|
||||
if(HAVE_QX11APPLICATION OR HAVE_X11EXTRAS OR HAVE_QPA_QPLATFORMNATIVEINTERFACE_H)
|
||||
set(X11_GLOBALSHORTCUTS_REQUIREMENT_FOUND ON)
|
||||
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"
|
||||
DEPENDS "libcdio" LIBCDIO_FOUND
|
||||
DEPENDS "gstreamer" GSTREAMER_FOUND
|
||||
DEPENDS "gstreamer" HAVE_GSTREAMER
|
||||
)
|
||||
|
||||
optional_component(UDISKS2 ON "Devices: UDisks2 backend"
|
||||
@@ -505,6 +527,8 @@ if(NOT CMAKE_CROSSCOMPILING)
|
||||
SQLITE_FTS5_TEST
|
||||
)
|
||||
endif()
|
||||
unset(CMAKE_REQUIRED_FLAGS)
|
||||
unset(CMAKE_REQUIRED_LIBRARIES)
|
||||
endif()
|
||||
|
||||
# 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
|
||||
|
||||
Version 1.0.18 (2023.07.02):
|
||||
|
||||
Bugfixes:
|
||||
* Fixed reading disc from QObuz songs (#1168).
|
||||
* Fixed volume being reset on playback with PulseAudio (#1174).
|
||||
* Fixed <br> tags in SQL query error message.
|
||||
* Fixed compile with Qt 6 without XCB (QX11Application).
|
||||
* Fixed smart playlist editor not properly loading search terms (#1172).
|
||||
* Fixed use of fixed icon for playlist favorite star icon (#1178).
|
||||
* Possible fix for collection thumbnails using disk cache having identical covers for albums with hashtag (#) in the album title (#1183).
|
||||
* Fixed listenbrainz scrobbling for songs with multiple artist mbids.
|
||||
* Fixed listenbrainz scrobbling for songs without duration.
|
||||
* Fixed gapless playback sometimes not working.
|
||||
* Fixed writing PNG images as embedded covers (#1209).
|
||||
* Fixed greyscale album covers not working in OSD D-Bus (#1205).
|
||||
* Fixed collection thumbnail disk cache with Qt 6.5.1 and newer.
|
||||
* Fixed moodbar disk cache with Qt 6.5.1 and newer.
|
||||
* Fixed playlist edit tag F2 shortcut only working for title tag (#1210).
|
||||
* Append number to filename if the destination file already exist when transcoding audio (#1200).
|
||||
* Fixed abseil linking issues with protobuf 1.22.0 and newer.
|
||||
* (macOS) Fixed "Show this message" checkbox having no affect on Rosetta warning dialog (#1180).
|
||||
* (macOS) Disable unused D-Bus.
|
||||
* (Windows) Fixed command line options not working with diacritics (#1191).
|
||||
* (Windows) Fixed issue with saving album covers in album directory being saved in temp directory instead.
|
||||
* (Windows) Fixed crash when trying a play a song which doesn't exist, gstreamer issue #1683 (#1214).
|
||||
|
||||
Enhancements:
|
||||
* Reduce memory overhead with album cover handling (#1046).
|
||||
* Improved listenbrainz error handling.
|
||||
* Show error dialog for listenbrainz errors similar to last.fm/libre.fm.
|
||||
* Reduce NetworkAccessManager instances.
|
||||
* Replace SingleApplication with KDSingleApplication.
|
||||
* Require Qt 5.12 or higher.
|
||||
* Add new database fields for art_embedded and art_unset.
|
||||
* Rewrite album cover loader.
|
||||
* Move cover filename settings from collection to covers settings.
|
||||
* Add setting to set priorities for album cover types.
|
||||
* Add rating filtering to playlist search (#1212).
|
||||
* (Windows|MSVC) Add WSAPI2 plugin.
|
||||
|
||||
Version 1.0.17 (2023.03.29):
|
||||
|
||||
Bugfixes:
|
||||
* Fixed over-sized context album cover with device pixel ratio higher than 1.0 (#1166).
|
||||
* Fixed playing widget fading from a blurry previous cover with device pixel ratio higher than 1.0.
|
||||
* Made playlist source icon, album cover manager and OSD pretty cover respect device pixel ratio.
|
||||
|
||||
Version 1.0.16 (2023.03.27):
|
||||
|
||||
Bugfixes:
|
||||
* Fixed lyrics from Musixmatch.
|
||||
* Fixed possible file corruption when saving both tags and embedded cover using the tag editor (#1158).
|
||||
* Fixed compile without GStreamer.
|
||||
* Fixed context and playing now album art rendering on High DPI displays (#1161).
|
||||
* Fixed setting source properties (device, user-agent, ssl-strict) with GStreamer 1.22 (playbin3) and higher (#1148).
|
||||
* Fixed rescan songs feature not ignoring mtime.
|
||||
* Search lyrics by artist instead of album artist by default.
|
||||
|
||||
Code improvements:
|
||||
* Replace use of deprecated QSqlDatabase::exec().
|
||||
|
||||
Added features:
|
||||
* Added backend setting for strict SSL mode.
|
||||
* Read AcoustID and MusicBrainz tags.
|
||||
* Submit MusicBrainz tags with ListenBrainz.
|
||||
|
||||
Version 1.0.15 (2023.03.04):
|
||||
|
||||
Bugfixes:
|
||||
|
||||
@@ -51,7 +51,7 @@ Funding developers is a way to contribute to open source projects you appreciate
|
||||
* Edit tags on audio files
|
||||
* 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/)
|
||||
* 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
|
||||
* Audio analyzer
|
||||
* 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)
|
||||
* [Boost](https://www.boost.org/)
|
||||
* [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)
|
||||
* [Protobuf](https://developers.google.com/protocol-buffers/)
|
||||
* [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
|
||||
|
||||
[](https://repology.org/metapackage/strawberry/versions)
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
set(STRAWBERRY_VERSION_MAJOR 1)
|
||||
set(STRAWBERRY_VERSION_MINOR 0)
|
||||
set(STRAWBERRY_VERSION_PATCH 15)
|
||||
set(STRAWBERRY_VERSION_PATCH 18)
|
||||
#set(STRAWBERRY_VERSION_PRERELEASE rc1)
|
||||
|
||||
set(INCLUDE_GIT_REVISION OFF)
|
||||
|
||||
@@ -7,6 +7,8 @@
|
||||
<file>schema/schema-13.sql</file>
|
||||
<file>schema/schema-14.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>style/strawberry.css</file>
|
||||
<file>style/smartplaylistsearchterm.css</file>
|
||||
|
||||
@@ -59,15 +59,31 @@ CREATE TABLE device_%deviceid_songs (
|
||||
compilation_off INTEGER NOT NULL DEFAULT 0,
|
||||
compilation_effective INTEGER NOT NULL DEFAULT 0,
|
||||
|
||||
art_embedded INTEGER DEFAULT 0,
|
||||
art_automatic TEXT,
|
||||
art_manual TEXT,
|
||||
art_unset INTEGER DEFAULT 0,
|
||||
|
||||
effective_albumartist TEXT,
|
||||
effective_originalyear INTEGER NOT NULL DEFAULT 0,
|
||||
|
||||
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"
|
||||
);
|
||||
|
||||
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;
|
||||
|
||||
INSERT INTO schema_version (version) VALUES (15);
|
||||
INSERT INTO schema_version (version) VALUES (17);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS directories (
|
||||
path TEXT NOT NULL,
|
||||
@@ -67,15 +67,31 @@ CREATE TABLE IF NOT EXISTS songs (
|
||||
compilation_off INTEGER NOT NULL DEFAULT 0,
|
||||
compilation_effective INTEGER NOT NULL DEFAULT 0,
|
||||
|
||||
art_embedded INTEGER DEFAULT 0,
|
||||
art_automatic TEXT,
|
||||
art_manual TEXT,
|
||||
art_unset INTEGER DEFAULT 0,
|
||||
|
||||
effective_albumartist TEXT,
|
||||
effective_originalyear INTEGER NOT NULL DEFAULT 0,
|
||||
|
||||
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_effective INTEGER NOT NULL DEFAULT 0,
|
||||
|
||||
art_embedded INTEGER DEFAULT 0,
|
||||
art_automatic TEXT,
|
||||
art_manual TEXT,
|
||||
art_unset INTEGER DEFAULT 0,
|
||||
|
||||
effective_albumartist TEXT,
|
||||
effective_originalyear INTEGER NOT NULL DEFAULT 0,
|
||||
|
||||
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_effective INTEGER NOT NULL DEFAULT 0,
|
||||
|
||||
art_embedded INTEGER DEFAULT 0,
|
||||
art_automatic TEXT,
|
||||
art_manual TEXT,
|
||||
art_unset INTEGER DEFAULT 0,
|
||||
|
||||
effective_albumartist TEXT,
|
||||
effective_originalyear INTEGER NOT NULL DEFAULT 0,
|
||||
|
||||
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_effective INTEGER NOT NULL DEFAULT 0,
|
||||
|
||||
art_embedded INTEGER DEFAULT 0,
|
||||
art_automatic TEXT,
|
||||
art_manual TEXT,
|
||||
art_unset INTEGER DEFAULT 0,
|
||||
|
||||
effective_albumartist TEXT,
|
||||
effective_originalyear INTEGER NOT NULL DEFAULT 0,
|
||||
|
||||
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_effective INTEGER NOT NULL DEFAULT 0,
|
||||
|
||||
art_embedded INTEGER DEFAULT 0,
|
||||
art_automatic TEXT,
|
||||
art_manual TEXT,
|
||||
art_unset INTEGER DEFAULT 0,
|
||||
|
||||
effective_albumartist TEXT,
|
||||
effective_originalyear INTEGER NOT NULL DEFAULT 0,
|
||||
|
||||
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_effective INTEGER NOT NULL DEFAULT 0,
|
||||
|
||||
art_embedded INTEGER DEFAULT 0,
|
||||
art_automatic TEXT,
|
||||
art_manual TEXT,
|
||||
art_unset INTEGER DEFAULT 0,
|
||||
|
||||
effective_albumartist TEXT,
|
||||
effective_originalyear INTEGER NOT NULL DEFAULT 0,
|
||||
|
||||
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_effective INTEGER NOT NULL DEFAULT 0,
|
||||
|
||||
art_embedded INTEGER DEFAULT 0,
|
||||
art_automatic TEXT,
|
||||
art_manual TEXT,
|
||||
art_unset INTEGER DEFAULT 0,
|
||||
|
||||
effective_albumartist TEXT,
|
||||
effective_originalyear INTEGER NOT NULL DEFAULT 0,
|
||||
|
||||
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_effective INTEGER NOT NULL DEFAULT 0,
|
||||
|
||||
art_embedded INTEGER DEFAULT 0,
|
||||
art_automatic TEXT,
|
||||
art_manual TEXT,
|
||||
art_unset INTEGER DEFAULT 0,
|
||||
|
||||
effective_albumartist TEXT,
|
||||
effective_originalyear INTEGER NOT NULL DEFAULT 0,
|
||||
|
||||
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_effective INTEGER DEFAULT 0,
|
||||
|
||||
art_embedded INTEGER DEFAULT 0,
|
||||
art_automatic TEXT,
|
||||
art_manual TEXT,
|
||||
art_unset INTEGER DEFAULT 0,
|
||||
|
||||
effective_albumartist TEXT,
|
||||
effective_originalyear INTEGER,
|
||||
|
||||
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;
|
||||
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
|
||||
Maintainer: Jonas Kvinge <jonas@jkvinge.net>
|
||||
Build-Depends: debhelper (>= 11),
|
||||
git,
|
||||
make,
|
||||
cmake,
|
||||
gcc,
|
||||
@@ -52,7 +53,7 @@ Description: music player and music collection organizer
|
||||
- Edit tags on audio files
|
||||
- Automatically retrieve tags from MusicBrainz
|
||||
- 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 equalizer
|
||||
- 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/contextalbum.cpp
|
||||
src/context/contextalbum.h
|
||||
src/engine/enginetype.cpp
|
||||
src/engine/enginetype.h
|
||||
src/engine/alsadevicefinder.cpp
|
||||
src/engine/alsadevicefinder.h
|
||||
src/engine/alsapcmdevicefinder.cpp
|
||||
@@ -37,6 +35,8 @@ Files: src/core/main.h
|
||||
src/engine/devicefinder.h
|
||||
src/engine/enginedevice.cpp
|
||||
src/engine/enginedevice.h
|
||||
src/engine/enginemetadata.cpp
|
||||
src/engine/enginemetadata.h
|
||||
src/internet/internetservice.cpp
|
||||
src/internet/internetservice.h
|
||||
src/internet/internettabsview.cpp
|
||||
@@ -267,8 +267,8 @@ Copyright: 2010, Spotify AB
|
||||
2011, Joachim Bengtsson
|
||||
License: BSD-3-clause
|
||||
|
||||
Files: 3rdparty/singleapplication/*
|
||||
Copyright: 2015-2022, Itay Grudev
|
||||
Files: 3rdparty/kdsingleapplication/*
|
||||
Copyright: 2019-2023 Klarälvdalens Datakonsult AB, a KDAB Group company <info@kdab.com>
|
||||
License: MIT
|
||||
|
||||
|
||||
@@ -376,7 +376,7 @@ License: Apache-2.0
|
||||
On Debian systems, the complete text of the Apache 2.0 license can be
|
||||
found in the file
|
||||
`/usr/share/common-licenses/Apache-2.0`.
|
||||
|
||||
|
||||
License: BSD-3-clause
|
||||
Copyright (c) The Regents of the University of California.
|
||||
All rights reserved.
|
||||
|
||||
2
dist/macos/Info.plist.in
vendored
2
dist/macos/Info.plist.in
vendored
@@ -33,7 +33,7 @@
|
||||
<key>LSApplicationCategoryType</key>
|
||||
<string>public.app-category.music</string>
|
||||
<key>LSMinimumSystemVersion</key>
|
||||
<string>10.13.4</string>
|
||||
<string>11.0</string>
|
||||
<key>SUFeedURL</key>
|
||||
<string>https://www.strawberrymusicplayer.org/sparkle-macos</string>
|
||||
<key>SUPublicEDKey</key>
|
||||
|
||||
@@ -29,7 +29,7 @@
|
||||
<li>Edit tags on audio files</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>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>Audio analyzer and equalizer</li>
|
||||
<li>Transfer music to mass-storage USB players, MTP compatible devices and iPod Nano/Classic</li>
|
||||
@@ -50,6 +50,6 @@
|
||||
</screenshots>
|
||||
<update_contact>eclipseo@fedoraproject.org</update_contact>
|
||||
<releases>
|
||||
<release version="1.0.0" date="2021-10-16"/>
|
||||
<release version="1.0.18" date="2023-07-02"/>
|
||||
</releases>
|
||||
</component>
|
||||
|
||||
@@ -3,7 +3,9 @@ Version=1.0
|
||||
Type=Application
|
||||
Name=Strawberry
|
||||
GenericName=Strawberry Music Player
|
||||
GenericName[ru]=Музыкальный проигрыватель Strawberry
|
||||
Comment=Plays music
|
||||
Comment[ru]=Прослушивание музыки
|
||||
Exec=strawberry %U
|
||||
TryExec=strawberry
|
||||
Icon=strawberry
|
||||
@@ -18,19 +20,24 @@ Actions=Play-Pause;Stop;StopAfterCurrent;Previous;Next;
|
||||
[Desktop Action Play-Pause]
|
||||
Name=Play/Pause
|
||||
Exec=strawberry --play-pause
|
||||
Name[ru]=Играть/пауза
|
||||
|
||||
[Desktop Action Stop]
|
||||
Name=Stop
|
||||
Exec=strawberry --stop
|
||||
Name[ru]=Стоп
|
||||
|
||||
[Desktop Action StopAfterCurrent]
|
||||
Name=Stop after this track
|
||||
Exec=strawberry --stop-after-current
|
||||
Name[ru]=Стоп после этого трека
|
||||
|
||||
[Desktop Action Previous]
|
||||
Name=Previous
|
||||
Exec=strawberry --previous
|
||||
Name[ru]=Предыдущий
|
||||
|
||||
[Desktop Action Next]
|
||||
Name=Next
|
||||
Exec=strawberry --next
|
||||
Name[ru]=Следующий
|
||||
|
||||
2
dist/unix/strawberry.1
vendored
2
dist/unix/strawberry.1
vendored
@@ -29,7 +29,7 @@ Features:
|
||||
.br
|
||||
- Album cover art from Last.fm, Musicbrainz, Discogs, Musixmatch, Deezer, Tidal, Qobuz and Spotify
|
||||
.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
|
||||
- Support for multiple backends
|
||||
.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
|
||||
- Automatically retrieve tags from MusicBrainz
|
||||
- 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
|
||||
- Audio analyzer
|
||||
- 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"
|
||||
!define mingw
|
||||
!undef compiler
|
||||
!define compiler "mingw"
|
||||
!endif
|
||||
|
||||
!if "@MSVC@" == "1"
|
||||
!define msvc
|
||||
!undef compiler
|
||||
!define compiler "msvc"
|
||||
!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"
|
||||
!define release
|
||||
!endif
|
||||
|
||||
!if "@CMAKE_BUILD_TYPE@" == "RelWithDebInfo"
|
||||
!else if "@CMAKE_BUILD_TYPE@" == "RelWithDebInfo"
|
||||
!define release
|
||||
!else if "@CMAKE_BUILD_TYPE@" == "Debug"
|
||||
!define debug
|
||||
!endif
|
||||
|
||||
!if "@CMAKE_BUILD_TYPE@" == "Debug"
|
||||
!define debug
|
||||
!undef build_type
|
||||
!ifdef release
|
||||
!define build_type ""
|
||||
!endif
|
||||
|
||||
!ifdef debug
|
||||
!define build_type "-Debug"
|
||||
!endif
|
||||
|
||||
|
||||
!ifndef compiler
|
||||
!error "Missing compiler."
|
||||
!endif
|
||||
|
||||
!ifndef build_type
|
||||
!error "Missing build type."
|
||||
!endif
|
||||
|
||||
!ifndef arch
|
||||
!error "Missing arch."
|
||||
!endif
|
||||
|
||||
|
||||
|
||||
!ifdef debug
|
||||
!define PRODUCT_NAME "Strawberry Music Player Debug"
|
||||
!define PRODUCT_NAME_SHORT "Strawberry"
|
||||
@@ -241,10 +252,10 @@ Section "Strawberry" Strawberry
|
||||
File "libssl-3-x64.dll"
|
||||
!endif
|
||||
|
||||
File "avcodec-59.dll"
|
||||
File "avfilter-8.dll"
|
||||
File "avformat-59.dll"
|
||||
File "avutil-57.dll"
|
||||
File "avcodec-60.dll"
|
||||
File "avfilter-9.dll"
|
||||
File "avformat-60.dll"
|
||||
File "avutil-58.dll"
|
||||
File "libFLAC-12.dll"
|
||||
File "libbrotlicommon.dll"
|
||||
File "libbrotlidec.dll"
|
||||
@@ -319,11 +330,51 @@ Section "Strawberry" Strawberry
|
||||
File "libwinpthread-1.dll"
|
||||
File "libxml2-2.dll"
|
||||
File "libzstd.dll"
|
||||
File "postproc-56.dll"
|
||||
File "postproc-57.dll"
|
||||
File "swresample-4.dll"
|
||||
File "swscale-6.dll"
|
||||
File "swscale-7.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
|
||||
File "gdb.exe"
|
||||
File "libexpat-1.dll"
|
||||
@@ -333,6 +384,7 @@ Section "Strawberry" Strawberry
|
||||
File "libpcre2-16d.dll"
|
||||
File "libreadline8.dll"
|
||||
File "libtermcap.dll"
|
||||
File "libabsl_graphcycles_internal.dll"
|
||||
!else
|
||||
File "libpcre2-8.dll"
|
||||
File "libpcre2-16.dll"
|
||||
@@ -387,6 +439,7 @@ Section "Strawberry" Strawberry
|
||||
File "gsttag-1.0-0.dll"
|
||||
File "gsturidownloader-1.0-0.dll"
|
||||
File "gstvideo-1.0-0.dll"
|
||||
File "gstwinrt-1.0-0.dll"
|
||||
File "harfbuzz.dll"
|
||||
File "intl-8.dll"
|
||||
File "jpeg62.dll"
|
||||
@@ -413,6 +466,7 @@ Section "Strawberry" Strawberry
|
||||
File "vorbis.dll"
|
||||
File "vorbisfile.dll"
|
||||
File "wavpackdll.dll"
|
||||
File "abseil_dll.dll"
|
||||
|
||||
!ifdef release
|
||||
File "freetype.dll"
|
||||
@@ -435,11 +489,17 @@ Section "Strawberry" Strawberry
|
||||
File "zlibd.dll"
|
||||
!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
|
||||
|
||||
; Common files
|
||||
|
||||
File "icudt72.dll"
|
||||
File "icudt73.dll"
|
||||
File "libfftw3-3.dll"
|
||||
!ifdef debug
|
||||
File "libprotobufd.dll"
|
||||
@@ -447,8 +507,8 @@ Section "Strawberry" Strawberry
|
||||
File "libprotobuf.dll"
|
||||
!endif
|
||||
!ifdef msvc && debug
|
||||
File "icuin72d.dll"
|
||||
File "icuuc72d.dll"
|
||||
File "icuin73d.dll"
|
||||
File "icuuc73d.dll"
|
||||
File "Qt6Concurrentd.dll"
|
||||
File "Qt6Cored.dll"
|
||||
File "Qt6Guid.dll"
|
||||
@@ -456,8 +516,8 @@ Section "Strawberry" Strawberry
|
||||
File "Qt6Sqld.dll"
|
||||
File "Qt6Widgetsd.dll"
|
||||
!else
|
||||
File "icuin72.dll"
|
||||
File "icuuc72.dll"
|
||||
File "icuin73.dll"
|
||||
File "icuuc73.dll"
|
||||
File "Qt6Concurrent.dll"
|
||||
File "Qt6Core.dll"
|
||||
File "Qt6Gui.dll"
|
||||
@@ -680,6 +740,7 @@ Section "Gstreamer plugins" gstreamer-plugins
|
||||
File "/oname=gstvolume.dll" "gstreamer-plugins\gstvolume.dll"
|
||||
File "/oname=gstvorbis.dll" "gstreamer-plugins\gstvorbis.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=gstwavpack.dll" "gstreamer-plugins\gstwavpack.dll"
|
||||
File "/oname=gstwavparse.dll" "gstreamer-plugins\gstwavparse.dll"
|
||||
@@ -746,10 +807,10 @@ Section "Uninstall"
|
||||
Delete "$INSTDIR\libssl-3-x64.dll"
|
||||
!endif
|
||||
|
||||
Delete "$INSTDIR\avcodec-59.dll"
|
||||
Delete "$INSTDIR\avfilter-8.dll"
|
||||
Delete "$INSTDIR\avformat-59.dll"
|
||||
Delete "$INSTDIR\avutil-57.dll"
|
||||
Delete "$INSTDIR\avcodec-60.dll"
|
||||
Delete "$INSTDIR\avfilter-9.dll"
|
||||
Delete "$INSTDIR\avformat-60.dll"
|
||||
Delete "$INSTDIR\avutil-58.dll"
|
||||
Delete "$INSTDIR\libFLAC-12.dll"
|
||||
Delete "$INSTDIR\libbrotlicommon.dll"
|
||||
Delete "$INSTDIR\libbrotlidec.dll"
|
||||
@@ -824,11 +885,51 @@ Section "Uninstall"
|
||||
Delete "$INSTDIR\libwinpthread-1.dll"
|
||||
Delete "$INSTDIR\libxml2-2.dll"
|
||||
Delete "$INSTDIR\libzstd.dll"
|
||||
Delete "$INSTDIR\postproc-56.dll"
|
||||
Delete "$INSTDIR\postproc-57.dll"
|
||||
Delete "$INSTDIR\swresample-4.dll"
|
||||
Delete "$INSTDIR\swscale-6.dll"
|
||||
Delete "$INSTDIR\swscale-7.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
|
||||
Delete "$INSTDIR\gdb.exe"
|
||||
Delete "$INSTDIR\libexpat-1.dll"
|
||||
@@ -838,6 +939,7 @@ Section "Uninstall"
|
||||
Delete "$INSTDIR\libpcre2-16d.dll"
|
||||
Delete "$INSTDIR\libreadline8.dll"
|
||||
Delete "$INSTDIR\libtermcap.dll"
|
||||
Delete "$INSTDIR\libabsl_graphcycles_internal.dll"
|
||||
!else
|
||||
Delete "$INSTDIR\libpcre2-8.dll"
|
||||
Delete "$INSTDIR\libpcre2-16.dll"
|
||||
@@ -892,6 +994,7 @@ Section "Uninstall"
|
||||
Delete "$INSTDIR\gsttag-1.0-0.dll"
|
||||
Delete "$INSTDIR\gsturidownloader-1.0-0.dll"
|
||||
Delete "$INSTDIR\gstvideo-1.0-0.dll"
|
||||
Delete "$INSTDIR\gstwinrt-1.0-0.dll"
|
||||
Delete "$INSTDIR\harfbuzz.dll"
|
||||
Delete "$INSTDIR\intl-8.dll"
|
||||
Delete "$INSTDIR\jpeg62.dll"
|
||||
@@ -918,6 +1021,7 @@ Section "Uninstall"
|
||||
Delete "$INSTDIR\vorbis.dll"
|
||||
Delete "$INSTDIR\vorbisfile.dll"
|
||||
Delete "$INSTDIR\wavpackdll.dll"
|
||||
Delete "$INSTDIR\abseil_dll.dll"
|
||||
|
||||
!ifdef release
|
||||
Delete "$INSTDIR\freetype.dll"
|
||||
@@ -940,11 +1044,16 @@ Section "Uninstall"
|
||||
Delete "$INSTDIR\zlibd.dll"
|
||||
!endif
|
||||
|
||||
!ifdef arch_x86
|
||||
Delete "$INSTDIR\libgcc_s_sjlj-1.dll"
|
||||
Delete "$INSTDIR\libwinpthread-1.dll"
|
||||
!endif
|
||||
|
||||
!endif ; MSVC
|
||||
|
||||
; Common files
|
||||
|
||||
Delete "$INSTDIR\icudt72.dll"
|
||||
Delete "$INSTDIR\icudt73.dll"
|
||||
Delete "$INSTDIR\libfftw3-3.dll"
|
||||
!ifdef debug
|
||||
Delete "$INSTDIR\libprotobufd.dll"
|
||||
@@ -952,8 +1061,8 @@ Section "Uninstall"
|
||||
Delete "$INSTDIR\libprotobuf.dll"
|
||||
!endif
|
||||
!ifdef msvc && debug
|
||||
Delete "$INSTDIR\icuin72d.dll"
|
||||
Delete "$INSTDIR\icuuc72d.dll"
|
||||
Delete "$INSTDIR\icuin73d.dll"
|
||||
Delete "$INSTDIR\icuuc73d.dll"
|
||||
Delete "$INSTDIR\Qt6Concurrentd.dll"
|
||||
Delete "$INSTDIR\Qt6Cored.dll"
|
||||
Delete "$INSTDIR\Qt6Guid.dll"
|
||||
@@ -961,8 +1070,8 @@ Section "Uninstall"
|
||||
Delete "$INSTDIR\Qt6Sqld.dll"
|
||||
Delete "$INSTDIR\Qt6Widgetsd.dll"
|
||||
!else
|
||||
Delete "$INSTDIR\icuin72.dll"
|
||||
Delete "$INSTDIR\icuuc72.dll"
|
||||
Delete "$INSTDIR\icuin73.dll"
|
||||
Delete "$INSTDIR\icuuc73.dll"
|
||||
Delete "$INSTDIR\Qt6Concurrent.dll"
|
||||
Delete "$INSTDIR\Qt6Core.dll"
|
||||
Delete "$INSTDIR\Qt6Gui.dll"
|
||||
@@ -1120,6 +1229,7 @@ Section "Uninstall"
|
||||
Delete "$INSTDIR\gstreamer-plugins\gstvolume.dll"
|
||||
Delete "$INSTDIR\gstreamer-plugins\gstvorbis.dll"
|
||||
Delete "$INSTDIR\gstreamer-plugins\gstwasapi.dll"
|
||||
Delete "$INSTDIR\gstreamer-plugins\gstwasapi2.dll"
|
||||
Delete "$INSTDIR\gstreamer-plugins\gstwavenc.dll"
|
||||
Delete "$INSTDIR\gstreamer-plugins\gstwavpack.dll"
|
||||
Delete "$INSTDIR\gstreamer-plugins\gstwavparse.dll"
|
||||
|
||||
@@ -50,10 +50,14 @@ enum {
|
||||
} // namespace
|
||||
|
||||
#define gst_fastspectrum_parent_class parent_class
|
||||
#ifdef __GNUC__
|
||||
#pragma GCC diagnostic push
|
||||
#pragma GCC diagnostic ignored "-Wold-style-cast"
|
||||
#endif
|
||||
G_DEFINE_TYPE(GstFastSpectrum, gst_fastspectrum, GST_TYPE_AUDIO_FILTER)
|
||||
#ifdef __GNUC__
|
||||
#pragma GCC diagnostic pop
|
||||
#endif
|
||||
|
||||
static void gst_fastspectrum_finalize(GObject *object);
|
||||
static void gst_fastspectrum_set_property(GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec);
|
||||
|
||||
@@ -37,9 +37,7 @@
|
||||
#include <QString>
|
||||
#include <QStringList>
|
||||
#include <QAtomicInt>
|
||||
#if QT_VERSION >= QT_VERSION_CHECK(5, 10, 0)
|
||||
# include <QRandomGenerator>
|
||||
#endif
|
||||
#include <QRandomGenerator>
|
||||
|
||||
#include "core/logging.h"
|
||||
|
||||
@@ -165,10 +163,10 @@ class WorkerPool : public _WorkerPoolBase {
|
||||
template<typename HandlerType>
|
||||
WorkerPool<HandlerType>::WorkerPool(QObject *parent)
|
||||
: _WorkerPoolBase(parent),
|
||||
worker_count_(1),
|
||||
next_worker_(0),
|
||||
next_id_(0) {
|
||||
|
||||
worker_count_ = qBound(1, QThread::idealThreadCount() / 2, 4);
|
||||
local_server_name_ = qApp->applicationName().toLower();
|
||||
|
||||
if (local_server_name_.isEmpty()) {
|
||||
@@ -228,7 +226,7 @@ void WorkerPool<HandlerType>::SetExecutableName(const QString &executable_name)
|
||||
|
||||
template<typename HandlerType>
|
||||
void WorkerPool<HandlerType>::Start() {
|
||||
QMetaObject::invokeMethod(this, "DoStart");
|
||||
QMetaObject::invokeMethod(this, &WorkerPool<HandlerType>::DoStart);
|
||||
}
|
||||
|
||||
template<typename HandlerType>
|
||||
@@ -294,11 +292,7 @@ void WorkerPool<HandlerType>::StartOneWorker(Worker *worker) {
|
||||
|
||||
// Create a server, find an unused name and start listening
|
||||
forever {
|
||||
#if QT_VERSION >= QT_VERSION_CHECK(5, 10, 0)
|
||||
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);
|
||||
|
||||
if (worker->local_server_->listen(name)) {
|
||||
@@ -423,7 +417,7 @@ WorkerPool<HandlerType>::SendMessageWithReply(MessageType *message) {
|
||||
}
|
||||
|
||||
// Wake up the main thread
|
||||
QMetaObject::invokeMethod(this, "SendQueuedMessages", Qt::QueuedConnection);
|
||||
QMetaObject::invokeMethod(this, &WorkerPool<HandlerType>::SendQueuedMessages, Qt::QueuedConnection);
|
||||
|
||||
return reply;
|
||||
|
||||
|
||||
@@ -1,7 +1,15 @@
|
||||
cmake_minimum_required(VERSION 3.7)
|
||||
|
||||
set(MESSAGES tagreadermessages.proto)
|
||||
set(SOURCES tagreaderbase.cpp)
|
||||
# Workaround a bug in protobuf-generate.cmake (https://github.com/protocolbuffers/protobuf/issues/12450)
|
||||
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)
|
||||
list(APPEND SOURCES tagreadertaglib.cpp tagreadergme.cpp)
|
||||
@@ -11,8 +19,6 @@ if(USE_TAGPARSER AND TAGPARSER_FOUND)
|
||||
list(APPEND SOURCES tagreadertagparser.cpp)
|
||||
endif()
|
||||
|
||||
protobuf_generate_cpp(PROTO_SOURCES PROTO_HEADERS ${MESSAGES})
|
||||
|
||||
link_directories(
|
||||
${GLIB_LIBRARY_DIRS}
|
||||
${PROTOBUF_LIBRARY_DIRS}
|
||||
@@ -43,9 +49,10 @@ target_include_directories(libstrawberry-tagreader PRIVATE
|
||||
|
||||
target_link_libraries(libstrawberry-tagreader PRIVATE
|
||||
${GLIB_LIBRARIES}
|
||||
${PROTOBUF_LIBRARY}
|
||||
${Protobuf_LIBRARIES}
|
||||
${QtCore_LIBRARIES}
|
||||
${QtNetwork_LIBRARIES}
|
||||
${QtGui_LIBRARIES}
|
||||
libstrawberry-common
|
||||
)
|
||||
|
||||
@@ -58,3 +65,5 @@ if(USE_TAGPARSER AND TAGPARSER_FOUND)
|
||||
target_include_directories(libstrawberry-tagreader SYSTEM PRIVATE ${TAGPARSER_INCLUDE_DIRS})
|
||||
target_link_libraries(libstrawberry-tagreader PRIVATE ${TAGPARSER_LIBRARIES})
|
||||
endif()
|
||||
|
||||
protobuf_generate(TARGET libstrawberry-tagreader)
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/* 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
|
||||
it under the terms of the GNU General Public License as published by
|
||||
@@ -19,19 +19,20 @@
|
||||
|
||||
#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;
|
||||
|
||||
void TagReaderBase::Decode(const QString &tag, std::string *output) {
|
||||
|
||||
output->assign(DataCommaSizeFromQString(tag));
|
||||
|
||||
}
|
||||
|
||||
float TagReaderBase::ConvertPOPMRating(const int POPM_rating) {
|
||||
|
||||
if (POPM_rating < 0x01) return 0.0F;
|
||||
@@ -55,3 +56,88 @@ int TagReaderBase::ConvertToPOPMRating(const float rating) {
|
||||
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.
|
||||
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
|
||||
it under the terms of the GNU General Public License as published by
|
||||
@@ -27,9 +27,6 @@
|
||||
|
||||
#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.
|
||||
* 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();
|
||||
~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 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 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 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 int ConvertToPOPMRating(const float rating);
|
||||
|
||||
protected:
|
||||
static const std::string kEmbeddedCover;
|
||||
static Cover LoadCoverFromRequest(const spb::tagreader::SaveFileRequest &request);
|
||||
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)
|
||||
};
|
||||
|
||||
@@ -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!
|
||||
|
||||
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);
|
||||
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();
|
||||
if (!tag) return;
|
||||
|
||||
TagReaderTagLib::Decode(tag->artist(), song_info->mutable_artist());
|
||||
TagReaderTagLib::Decode(tag->album(), song_info->mutable_album());
|
||||
TagReaderTagLib::Decode(tag->title(), song_info->mutable_title());
|
||||
TagReaderTagLib::Decode(tag->genre(), song_info->mutable_genre());
|
||||
TagReaderTagLib::TStringToStdString(tag->artist(), song_info->mutable_artist());
|
||||
TagReaderTagLib::TStringToStdString(tag->album(), song_info->mutable_album());
|
||||
TagReaderTagLib::TStringToStdString(tag->title(), song_info->mutable_title());
|
||||
TagReaderTagLib::TStringToStdString(tag->genre(), song_info->mutable_genre());
|
||||
song_info->set_track(tag->track());
|
||||
song_info->set_year(tag->year());
|
||||
}
|
||||
@@ -278,7 +279,7 @@ bool TagReaderGME::ReadFile(const QString &filename, spb::tagreader::SongMetadat
|
||||
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;
|
||||
}
|
||||
|
||||
@@ -286,7 +287,7 @@ QByteArray TagReaderGME::LoadEmbeddedArt(const QString&) const {
|
||||
return QByteArray();
|
||||
}
|
||||
|
||||
bool TagReaderGME::SaveEmbeddedArt(const QString&, const QByteArray&) {
|
||||
bool TagReaderGME::SaveEmbeddedArt(const spb::tagreader::SaveEmbeddedArtRequest&) const {
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
@@ -107,13 +107,13 @@ class TagReaderGME : public TagReaderBase {
|
||||
bool IsMediaFile(const QString &filename) 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;
|
||||
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 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 lastseen = 30;
|
||||
|
||||
optional string art_automatic = 31;
|
||||
optional bool art_embedded = 31;
|
||||
|
||||
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 string filename = 1;
|
||||
}
|
||||
optional bool suspicious_tags = 50;
|
||||
|
||||
message ReadFileResponse {
|
||||
optional SongMetadata metadata = 1;
|
||||
}
|
||||
|
||||
message SaveFileRequest {
|
||||
optional string filename = 1;
|
||||
optional SongMetadata metadata = 2;
|
||||
}
|
||||
|
||||
message SaveFileResponse {
|
||||
optional bool success = 1;
|
||||
}
|
||||
|
||||
message IsMediaFileRequest {
|
||||
@@ -100,6 +97,33 @@ message IsMediaFileRequest {
|
||||
|
||||
message IsMediaFileResponse {
|
||||
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 {
|
||||
@@ -108,15 +132,19 @@ message LoadEmbeddedArtRequest {
|
||||
|
||||
message LoadEmbeddedArtResponse {
|
||||
optional bytes data = 1;
|
||||
optional string error = 2;
|
||||
}
|
||||
|
||||
message SaveEmbeddedArtRequest {
|
||||
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 {
|
||||
optional bool success = 1;
|
||||
optional string error = 2;
|
||||
}
|
||||
|
||||
message SaveSongPlaycountToFileRequest {
|
||||
@@ -126,6 +154,7 @@ message SaveSongPlaycountToFileRequest {
|
||||
|
||||
message SaveSongPlaycountToFileResponse {
|
||||
optional bool success = 1;
|
||||
optional string error = 2;
|
||||
}
|
||||
|
||||
message SaveSongRatingToFileRequest {
|
||||
@@ -135,6 +164,7 @@ message SaveSongRatingToFileRequest {
|
||||
|
||||
message SaveSongRatingToFileResponse {
|
||||
optional bool success = 1;
|
||||
optional string error = 2;
|
||||
}
|
||||
|
||||
message Message {
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,6 +1,6 @@
|
||||
/* This file is part of Strawberry.
|
||||
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
|
||||
it under the terms of the GNU General Public License as published by
|
||||
@@ -29,8 +29,12 @@
|
||||
#include <taglib/tstring.h>
|
||||
#include <taglib/fileref.h>
|
||||
#include <taglib/xiphcomment.h>
|
||||
#include <taglib/flacfile.h>
|
||||
#include <taglib/mpegfile.h>
|
||||
#include <taglib/mp4file.h>
|
||||
#include <taglib/apetag.h>
|
||||
#include <taglib/apefile.h>
|
||||
#include <taglib/asffile.h>
|
||||
#include <taglib/id3v2tag.h>
|
||||
#include <taglib/popularimeterframe.h>
|
||||
|
||||
@@ -51,15 +55,15 @@ class TagReaderTagLib : public TagReaderBase {
|
||||
bool IsMediaFile(const QString &filename) 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;
|
||||
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 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:
|
||||
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 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 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);
|
||||
|
||||
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:
|
||||
FileRefFactory *factory_;
|
||||
|
||||
|
||||
@@ -22,6 +22,7 @@
|
||||
#include <string>
|
||||
#include <memory>
|
||||
#include <algorithm>
|
||||
#include <vector>
|
||||
#include <sys/stat.h>
|
||||
|
||||
#include <tagparser/mediafileinfo.h>
|
||||
@@ -79,7 +80,7 @@ bool TagReaderTagParser::IsMediaFile(const QString &filename) const {
|
||||
}
|
||||
|
||||
const auto tracks = taginfo.tracks();
|
||||
for (const auto track : tracks) {
|
||||
for (TagParser::AbstractTrack *track : tracks) {
|
||||
if (track->mediaType() == TagParser::MediaType::Audio) {
|
||||
taginfo.close();
|
||||
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;
|
||||
|
||||
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_filesize(fileinfo.size());
|
||||
|
||||
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);
|
||||
#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) {
|
||||
song->set_ctime(song->mtime());
|
||||
@@ -154,8 +153,8 @@ bool TagReaderTagParser::ReadFile(const QString &filename, spb::tagreader::SongM
|
||||
qLog(Debug) << QString::fromStdString(msg.message());
|
||||
}
|
||||
|
||||
const auto tracks = taginfo.tracks();
|
||||
for (const auto track : tracks) {
|
||||
std::vector<TagParser::AbstractTrack*> tracks = taginfo.tracks();
|
||||
for (TagParser::AbstractTrack *track : tracks) {
|
||||
switch (track->format().general) {
|
||||
case TagParser::GeneralMediaFormat::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;
|
||||
}
|
||||
|
||||
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_artist(tag->value(TagParser::KnownField::Artist).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_disc(tag->value(TagParser::KnownField::DiskPosition).toInteger());
|
||||
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));
|
||||
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 {
|
||||
TagParser::MediaFileInfo taginfo;
|
||||
@@ -295,22 +317,34 @@ bool TagReaderTagParser::SaveFile(const QString &filename, const spb::tagreader:
|
||||
taginfo.createAppropriateTags();
|
||||
}
|
||||
|
||||
for (const auto tag : taginfo.tags()) {
|
||||
tag->setValue(TagParser::KnownField::AlbumArtist, TagParser::TagValue(song.albumartist(), TagParser::TagTextEncoding::Utf8, tag->proposedTextEncoding()));
|
||||
tag->setValue(TagParser::KnownField::Artist, TagParser::TagValue(song.artist(), TagParser::TagTextEncoding::Utf8, tag->proposedTextEncoding()));
|
||||
tag->setValue(TagParser::KnownField::Album, TagParser::TagValue(song.album(), TagParser::TagTextEncoding::Utf8, tag->proposedTextEncoding()));
|
||||
tag->setValue(TagParser::KnownField::Title, TagParser::TagValue(song.title(), TagParser::TagTextEncoding::Utf8, tag->proposedTextEncoding()));
|
||||
tag->setValue(TagParser::KnownField::Genre, TagParser::TagValue(song.genre(), TagParser::TagTextEncoding::Utf8, tag->proposedTextEncoding()));
|
||||
tag->setValue(TagParser::KnownField::Composer, TagParser::TagValue(song.composer(), TagParser::TagTextEncoding::Utf8, tag->proposedTextEncoding()));
|
||||
tag->setValue(TagParser::KnownField::Performers, TagParser::TagValue(song.performer(), TagParser::TagTextEncoding::Utf8, tag->proposedTextEncoding()));
|
||||
tag->setValue(TagParser::KnownField::Grouping, TagParser::TagValue(song.grouping(), TagParser::TagTextEncoding::Utf8, tag->proposedTextEncoding()));
|
||||
tag->setValue(TagParser::KnownField::Comment, TagParser::TagValue(song.comment(), TagParser::TagTextEncoding::Utf8, tag->proposedTextEncoding()));
|
||||
tag->setValue(TagParser::KnownField::Lyrics, TagParser::TagValue(song.lyrics(), TagParser::TagTextEncoding::Utf8, tag->proposedTextEncoding()));
|
||||
tag->setValue(TagParser::KnownField::TrackPosition, TagParser::TagValue(song.track()));
|
||||
tag->setValue(TagParser::KnownField::DiskPosition, TagParser::TagValue(song.disc()));
|
||||
tag->setValue(TagParser::KnownField::RecordDate, TagParser::TagValue(song.year()));
|
||||
tag->setValue(TagParser::KnownField::ReleaseDate, TagParser::TagValue(song.originalyear()));
|
||||
for (TagParser::Tag *tag : taginfo.tags()) {
|
||||
if (save_tags) {
|
||||
tag->setValue(TagParser::KnownField::AlbumArtist, TagParser::TagValue(song.albumartist(), TagParser::TagTextEncoding::Utf8, tag->proposedTextEncoding()));
|
||||
tag->setValue(TagParser::KnownField::Artist, TagParser::TagValue(song.artist(), TagParser::TagTextEncoding::Utf8, tag->proposedTextEncoding()));
|
||||
tag->setValue(TagParser::KnownField::Album, TagParser::TagValue(song.album(), TagParser::TagTextEncoding::Utf8, tag->proposedTextEncoding()));
|
||||
tag->setValue(TagParser::KnownField::Title, TagParser::TagValue(song.title(), TagParser::TagTextEncoding::Utf8, tag->proposedTextEncoding()));
|
||||
tag->setValue(TagParser::KnownField::Genre, TagParser::TagValue(song.genre(), TagParser::TagTextEncoding::Utf8, tag->proposedTextEncoding()));
|
||||
tag->setValue(TagParser::KnownField::Composer, TagParser::TagValue(song.composer(), TagParser::TagTextEncoding::Utf8, tag->proposedTextEncoding()));
|
||||
tag->setValue(TagParser::KnownField::Performers, TagParser::TagValue(song.performer(), TagParser::TagTextEncoding::Utf8, tag->proposedTextEncoding()));
|
||||
tag->setValue(TagParser::KnownField::Grouping, TagParser::TagValue(song.grouping(), TagParser::TagTextEncoding::Utf8, tag->proposedTextEncoding()));
|
||||
tag->setValue(TagParser::KnownField::Comment, TagParser::TagValue(song.comment(), TagParser::TagTextEncoding::Utf8, tag->proposedTextEncoding()));
|
||||
tag->setValue(TagParser::KnownField::Lyrics, TagParser::TagValue(song.lyrics(), TagParser::TagTextEncoding::Utf8, tag->proposedTextEncoding()));
|
||||
tag->setValue(TagParser::KnownField::TrackPosition, TagParser::TagValue(song.track()));
|
||||
tag->setValue(TagParser::KnownField::DiskPosition, TagParser::TagValue(song.disc()));
|
||||
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.close();
|
||||
|
||||
@@ -358,7 +392,7 @@ QByteArray TagReaderTagParser::LoadEmbeddedArt(const QString &filename) const {
|
||||
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) {
|
||||
QByteArray data(tag->value(TagParser::KnownField::Cover).dataPointer(), tag->value(TagParser::KnownField::Cover).dataSize());
|
||||
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;
|
||||
|
||||
const QByteArray cover_data = LoadCoverDataFromRequest(request);
|
||||
|
||||
try {
|
||||
|
||||
TagParser::MediaFileInfo taginfo;
|
||||
@@ -415,8 +459,8 @@ bool TagReaderTagParser::SaveEmbeddedArt(const QString &filename, const QByteArr
|
||||
taginfo.createAppropriateTags();
|
||||
}
|
||||
|
||||
for (const auto tag : taginfo.tags()) {
|
||||
tag->setValue(TagParser::KnownField::Cover, TagParser::TagValue(data.toStdString()));
|
||||
for (TagParser::Tag *tag : taginfo.tags()) {
|
||||
SaveEmbeddedArt(tag, cover_data);
|
||||
}
|
||||
|
||||
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; }
|
||||
|
||||
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 {
|
||||
|
||||
if (filename.isEmpty()) return false;
|
||||
@@ -476,9 +528,10 @@ bool TagReaderTagParser::SaveSongRatingToFile(const QString &filename, const spb
|
||||
taginfo.createAppropriateTags();
|
||||
}
|
||||
|
||||
for (const auto tag : taginfo.tags()) {
|
||||
tag->setValue(TagParser::KnownField::Rating, TagParser::TagValue(ConvertToPOPMRating(song.rating())));
|
||||
for (TagParser::Tag *tag : taginfo.tags()) {
|
||||
SaveSongRatingToFile(tag, song);
|
||||
}
|
||||
|
||||
taginfo.applyChanges(diag, progress);
|
||||
taginfo.close();
|
||||
|
||||
|
||||
@@ -22,6 +22,8 @@
|
||||
|
||||
#include <string>
|
||||
|
||||
#include <tagparser/tag.h>
|
||||
|
||||
#include <QByteArray>
|
||||
#include <QString>
|
||||
|
||||
@@ -40,14 +42,20 @@ class TagReaderTagParser : public TagReaderBase {
|
||||
bool IsMediaFile(const QString &filename) 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;
|
||||
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 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)
|
||||
};
|
||||
|
||||
|
||||
@@ -20,9 +20,6 @@
|
||||
|
||||
#include <QtGlobal>
|
||||
|
||||
#if QT_VERSION < QT_VERSION_CHECK(5, 10, 0)
|
||||
# include <sys/time.h>
|
||||
#endif
|
||||
#include <iostream>
|
||||
|
||||
#include <QCoreApplication>
|
||||
@@ -46,13 +43,6 @@ int main(int argc, char **argv) {
|
||||
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();
|
||||
qLog(Info) << "TagReader worker connecting to" << args[1];
|
||||
|
||||
@@ -67,4 +57,5 @@ int main(int argc, char **argv) {
|
||||
TagReaderWorker worker(&socket);
|
||||
|
||||
return a.exec();
|
||||
|
||||
}
|
||||
|
||||
@@ -56,36 +56,41 @@ void TagReaderWorker::DeviceClosed() {
|
||||
bool TagReaderWorker::HandleMessage(const spb::tagreader::Message &message, spb::tagreader::Message &reply, TagReaderBase *reader) {
|
||||
|
||||
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);
|
||||
return success;
|
||||
}
|
||||
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;
|
||||
}
|
||||
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);
|
||||
return success;
|
||||
}
|
||||
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());
|
||||
return true;
|
||||
}
|
||||
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);
|
||||
return success;
|
||||
}
|
||||
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);
|
||||
return success;
|
||||
}
|
||||
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);
|
||||
return success;
|
||||
}
|
||||
|
||||
@@ -55,11 +55,14 @@ set(SOURCES
|
||||
utilities/transliterate.cpp
|
||||
utilities/xmlutils.cpp
|
||||
utilities/filemanagerutils.cpp
|
||||
utilities/coverutils.cpp
|
||||
utilities/screenutils.cpp
|
||||
|
||||
engine/enginetype.cpp
|
||||
engine/enginebase.cpp
|
||||
engine/enginedevice.cpp
|
||||
engine/devicefinders.cpp
|
||||
engine/devicefinder.cpp
|
||||
engine/enginemetadata.cpp
|
||||
|
||||
analyzer/fht.cpp
|
||||
analyzer/analyzerbase.cpp
|
||||
@@ -144,6 +147,7 @@ set(SOURCES
|
||||
covermanager/albumcovermanager.cpp
|
||||
covermanager/albumcovermanagerlist.cpp
|
||||
covermanager/albumcoverloader.cpp
|
||||
covermanager/albumcoverloaderoptions.cpp
|
||||
covermanager/albumcoverfetcher.cpp
|
||||
covermanager/albumcoverfetchersearch.cpp
|
||||
covermanager/albumcoversearcher.cpp
|
||||
@@ -168,16 +172,17 @@ set(SOURCES
|
||||
|
||||
lyrics/lyricsproviders.cpp
|
||||
lyrics/lyricsprovider.cpp
|
||||
lyrics/lyricssearchrequest.h
|
||||
lyrics/lyricssearchresult.h
|
||||
lyrics/lyricsfetcher.cpp
|
||||
lyrics/lyricsfetchersearch.cpp
|
||||
lyrics/jsonlyricsprovider.cpp
|
||||
lyrics/auddlyricsprovider.cpp
|
||||
lyrics/ovhlyricsprovider.cpp
|
||||
lyrics/lololyricsprovider.cpp
|
||||
lyrics/geniuslyricsprovider.cpp
|
||||
lyrics/musixmatchlyricsprovider.cpp
|
||||
lyrics/chartlyricsprovider.cpp
|
||||
lyrics/stands4lyricsprovider.cpp
|
||||
lyrics/lyricscomlyricsprovider.cpp
|
||||
|
||||
providers/musixmatchprovider.cpp
|
||||
|
||||
@@ -204,6 +209,7 @@ set(SOURCES
|
||||
dialogs/userpassdialog.cpp
|
||||
dialogs/deleteconfirmationdialog.cpp
|
||||
dialogs/lastfmimportdialog.cpp
|
||||
dialogs/messagedialog.cpp
|
||||
dialogs/snapdialog.cpp
|
||||
dialogs/saveplaylistsdialog.cpp
|
||||
|
||||
@@ -263,10 +269,10 @@ set(SOURCES
|
||||
radios/radioparadiseservice.cpp
|
||||
|
||||
scrobbler/audioscrobbler.cpp
|
||||
scrobbler/scrobblerservices.cpp
|
||||
scrobbler/scrobblerservice.cpp
|
||||
scrobbler/scrobblercache.cpp
|
||||
scrobbler/scrobblercacheitem.cpp
|
||||
scrobbler/scrobblemetadata.cpp
|
||||
scrobbler/scrobblingapi20.cpp
|
||||
scrobbler/lastfmscrobbler.cpp
|
||||
scrobbler/librefmscrobbler.cpp
|
||||
@@ -408,13 +414,12 @@ set(HEADERS
|
||||
lyrics/lyricsfetcher.h
|
||||
lyrics/lyricsfetchersearch.h
|
||||
lyrics/jsonlyricsprovider.h
|
||||
lyrics/auddlyricsprovider.h
|
||||
lyrics/ovhlyricsprovider.h
|
||||
lyrics/lololyricsprovider.h
|
||||
lyrics/geniuslyricsprovider.h
|
||||
lyrics/musixmatchlyricsprovider.h
|
||||
lyrics/chartlyricsprovider.h
|
||||
lyrics/stands4lyricsprovider.h
|
||||
lyrics/lyricscomlyricsprovider.h
|
||||
|
||||
settings/settingsdialog.h
|
||||
settings/settingspage.h
|
||||
@@ -439,6 +444,7 @@ set(HEADERS
|
||||
dialogs/userpassdialog.h
|
||||
dialogs/deleteconfirmationdialog.h
|
||||
dialogs/lastfmimportdialog.h
|
||||
dialogs/messagedialog.h
|
||||
dialogs/snapdialog.h
|
||||
dialogs/saveplaylistsdialog.h
|
||||
|
||||
@@ -496,10 +502,8 @@ set(HEADERS
|
||||
radios/radioparadiseservice.h
|
||||
|
||||
scrobbler/audioscrobbler.h
|
||||
scrobbler/scrobblerservices.h
|
||||
scrobbler/scrobblerservice.h
|
||||
scrobbler/scrobblercache.h
|
||||
scrobbler/scrobblercacheitem.h
|
||||
scrobbler/scrobblingapi20.h
|
||||
scrobbler/lastfmscrobbler.h
|
||||
scrobbler/librefmscrobbler.h
|
||||
@@ -566,7 +570,7 @@ set(UI
|
||||
dialogs/addstreamdialog.ui
|
||||
dialogs/userpassdialog.ui
|
||||
dialogs/lastfmimportdialog.ui
|
||||
dialogs/snapdialog.ui
|
||||
dialogs/messagedialog.ui
|
||||
dialogs/saveplaylistsdialog.ui
|
||||
|
||||
widgets/trackslider.ui
|
||||
@@ -834,6 +838,7 @@ optional_source(WIN32
|
||||
HEADERS
|
||||
core/windows7thumbbar.h
|
||||
)
|
||||
optional_source(MSVC SOURCES engine/uwpdevicefinder.cpp)
|
||||
|
||||
optional_source(HAVE_SUBSONIC
|
||||
SOURCES
|
||||
@@ -966,8 +971,8 @@ link_directories(
|
||||
${GOBJECT_LIBRARY_DIRS}
|
||||
${GNUTLS_LIBRARY_DIRS}
|
||||
${SQLITE_LIBRARY_DIRS}
|
||||
${PROTOBUF_LIBRARY_DIRS}
|
||||
${SINGLEAPPLICATION_LIBRARY_DIRS}
|
||||
${SINGLECOREAPPLICATION_LIBRARY_DIRS}
|
||||
)
|
||||
|
||||
if(HAVE_ICU)
|
||||
@@ -1075,7 +1080,6 @@ target_include_directories(strawberry_lib PUBLIC
|
||||
${CMAKE_SOURCE_DIR}/ext/libstrawberry-tagreader
|
||||
${CMAKE_BINARY_DIR}/ext/libstrawberry-tagreader
|
||||
${SINGLEAPPLICATION_INCLUDE_DIRS}
|
||||
${SINGLECOREAPPLICATION_INCLUDE_DIRS}
|
||||
)
|
||||
|
||||
target_link_libraries(strawberry_lib PUBLIC
|
||||
@@ -1085,8 +1089,8 @@ target_link_libraries(strawberry_lib PUBLIC
|
||||
${GNUTLS_LIBRARIES}
|
||||
${SQLITE_LIBRARIES}
|
||||
${QT_LIBRARIES}
|
||||
${Protobuf_LIBRARIES}
|
||||
${SINGLEAPPLICATION_LIBRARIES}
|
||||
${SINGLECOREAPPLICATION_LIBRARIES}
|
||||
libstrawberry-common
|
||||
libstrawberry-tagreader
|
||||
)
|
||||
@@ -1196,13 +1200,13 @@ endif()
|
||||
if(WIN32)
|
||||
target_link_libraries(strawberry_lib PRIVATE dsound dwmapi)
|
||||
if(MSVC)
|
||||
target_link_libraries(strawberry_lib PRIVATE sqlite3)
|
||||
if (GETOPT_INCLUDE_DIRS)
|
||||
target_include_directories(strawberry_lib SYSTEM PRIVATE ${GETOPT_INCLUDE_DIRS})
|
||||
endif()
|
||||
if(GETOPT_LIBRARIES)
|
||||
target_link_libraries(strawberry_lib PRIVATE ${GETOPT_LIBRARIES})
|
||||
endif()
|
||||
target_link_libraries(strawberry_lib PRIVATE WindowsApp)
|
||||
endif()
|
||||
if(GETOPT_INCLUDE_DIRS)
|
||||
target_include_directories(strawberry_lib SYSTEM PRIVATE ${GETOPT_INCLUDE_DIRS})
|
||||
endif()
|
||||
if(GETOPT_LIBRARIES)
|
||||
target_link_libraries(strawberry_lib PRIVATE ${GETOPT_LIBRARIES})
|
||||
endif()
|
||||
endif()
|
||||
|
||||
|
||||
@@ -50,7 +50,7 @@
|
||||
// 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
|
||||
|
||||
Analyzer::Base::Base(QWidget *parent, const uint scopeSize)
|
||||
AnalyzerBase::AnalyzerBase(QWidget *parent, const uint scopeSize)
|
||||
: QWidget(parent),
|
||||
fht_(new FHT(scopeSize)),
|
||||
engine_(nullptr),
|
||||
@@ -63,19 +63,19 @@ Analyzer::Base::Base(QWidget *parent, const uint scopeSize)
|
||||
|
||||
}
|
||||
|
||||
Analyzer::Base::~Base() {
|
||||
AnalyzerBase::~AnalyzerBase() {
|
||||
delete fht_;
|
||||
}
|
||||
|
||||
void Analyzer::Base::showEvent(QShowEvent*) {
|
||||
void AnalyzerBase::showEvent(QShowEvent*) {
|
||||
timer_.start(timeout(), this);
|
||||
}
|
||||
|
||||
void Analyzer::Base::hideEvent(QHideEvent*) {
|
||||
void AnalyzerBase::hideEvent(QHideEvent*) {
|
||||
timer_.stop();
|
||||
}
|
||||
|
||||
void Analyzer::Base::ChangeTimeout(const int timeout) {
|
||||
void AnalyzerBase::ChangeTimeout(const int timeout) {
|
||||
|
||||
timeout_ = timeout;
|
||||
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());
|
||||
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);
|
||||
p.fillRect(e->rect(), palette().color(QPalette::Window));
|
||||
|
||||
switch (engine_->state()) {
|
||||
case Engine::State::Playing: {
|
||||
const Engine::Scope &thescope = engine_->scope(timeout_);
|
||||
case EngineBase::State::Playing: {
|
||||
const EngineBase::Scope &thescope = engine_->scope(timeout_);
|
||||
int i = 0;
|
||||
|
||||
// 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;
|
||||
}
|
||||
case Engine::State::Paused:
|
||||
case EngineBase::State::Paused:
|
||||
is_playing_ = false;
|
||||
analyze(p, lastscope_, new_frame_);
|
||||
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) {
|
||||
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;
|
||||
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
|
||||
|
||||
@@ -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;
|
||||
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 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);
|
||||
if (e->timerId() != timer_.timerId()) {
|
||||
|
||||
@@ -39,7 +39,6 @@
|
||||
#include <QPainter>
|
||||
|
||||
#include "analyzer/fht.h"
|
||||
#include "engine/engine_fwd.h"
|
||||
#include "engine/enginebase.h"
|
||||
|
||||
class QHideEvent;
|
||||
@@ -47,15 +46,11 @@ class QShowEvent;
|
||||
class QPaintEvent;
|
||||
class QTimerEvent;
|
||||
|
||||
namespace Analyzer {
|
||||
|
||||
using Scope = std::vector<float>;
|
||||
|
||||
class Base : public QWidget {
|
||||
class AnalyzerBase : public QWidget {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
~Base() override;
|
||||
~AnalyzerBase() override;
|
||||
|
||||
int timeout() const { return timeout_; }
|
||||
|
||||
@@ -66,7 +61,8 @@ class Base : public QWidget {
|
||||
virtual void framerateChanged() {}
|
||||
|
||||
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 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 demo(QPainter &p);
|
||||
|
||||
void interpolate(const Scope&, Scope&);
|
||||
void initSin(Scope&, const uint = 6000);
|
||||
|
||||
protected:
|
||||
QBasicTimer timer_;
|
||||
FHT *fht_;
|
||||
@@ -91,10 +90,5 @@ class Base : public QWidget {
|
||||
int timeout_;
|
||||
};
|
||||
|
||||
void interpolate(const Scope&, Scope&);
|
||||
void initSin(Scope&, const uint = 6000);
|
||||
|
||||
} // namespace Analyzer
|
||||
|
||||
#endif // ANALYZERBASE_H
|
||||
|
||||
|
||||
@@ -44,7 +44,6 @@
|
||||
|
||||
#include "core/logging.h"
|
||||
#include "engine/enginebase.h"
|
||||
#include "engine/enginetype.h"
|
||||
|
||||
using namespace std::chrono_literals;
|
||||
|
||||
@@ -84,8 +83,8 @@ AnalyzerContainer::AnalyzerContainer(QWidget *parent)
|
||||
|
||||
AddAnalyzerType<BlockAnalyzer>();
|
||||
AddAnalyzerType<BoomAnalyzer>();
|
||||
AddAnalyzerType<Rainbow::NyanCatAnalyzer>();
|
||||
AddAnalyzerType<Rainbow::RainbowDashAnalyzer>();
|
||||
AddAnalyzerType<NyanCatAnalyzer>();
|
||||
AddAnalyzerType<RainbowDashAnalyzer>();
|
||||
AddAnalyzerType<Sonogram>();
|
||||
|
||||
disable_action_ = context_menu_->addAction(tr("No analyzer"), this, &AnalyzerContainer::DisableAnalyzer);
|
||||
@@ -104,7 +103,7 @@ AnalyzerContainer::AnalyzerContainer(QWidget *parent)
|
||||
|
||||
void AnalyzerContainer::mouseReleaseEvent(QMouseEvent *e) {
|
||||
|
||||
if (engine_->type() != Engine::EngineType::GStreamer) {
|
||||
if (engine_->type() != EngineBase::Type::GStreamer) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -150,7 +149,7 @@ void AnalyzerContainer::ChangeAnalyzer(const int id) {
|
||||
}
|
||||
|
||||
delete current_analyzer_;
|
||||
current_analyzer_ = qobject_cast<Analyzer::Base*>(instance);
|
||||
current_analyzer_ = qobject_cast<AnalyzerBase*>(instance);
|
||||
current_analyzer_->set_engine(engine_);
|
||||
// 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_;
|
||||
|
||||
@@ -30,15 +30,13 @@
|
||||
#include <QAction>
|
||||
#include <QActionGroup>
|
||||
|
||||
#include "engine/engine_fwd.h"
|
||||
#include "engine/enginebase.h"
|
||||
|
||||
class QTimer;
|
||||
class QMouseEvent;
|
||||
class QWheelEvent;
|
||||
|
||||
namespace Analyzer {
|
||||
class Base;
|
||||
} // namespace Analyzer
|
||||
class AnalyzerBase;
|
||||
|
||||
class AnalyzerContainer : public QWidget {
|
||||
Q_OBJECT
|
||||
@@ -53,7 +51,7 @@ class AnalyzerContainer : public QWidget {
|
||||
static const char *kSettingsFramerate;
|
||||
|
||||
signals:
|
||||
void WheelEvent(int delta);
|
||||
void WheelEvent(const int delta);
|
||||
|
||||
protected:
|
||||
void mouseReleaseEvent(QMouseEvent*) override;
|
||||
@@ -94,7 +92,7 @@ class AnalyzerContainer : public QWidget {
|
||||
QPoint last_click_pos_;
|
||||
bool ignore_next_click_;
|
||||
|
||||
Analyzer::Base *current_analyzer_;
|
||||
AnalyzerBase *current_analyzer_;
|
||||
EngineBase *engine_;
|
||||
};
|
||||
|
||||
|
||||
@@ -46,7 +46,7 @@ const int BlockAnalyzer::kFadeSize = 90;
|
||||
const char *BlockAnalyzer::kName = QT_TRANSLATE_NOOP("AnalyzerContainer", "Block analyzer");
|
||||
|
||||
BlockAnalyzer::BlockAnalyzer(QWidget *parent)
|
||||
: Analyzer::Base(parent, 9),
|
||||
: AnalyzerBase(parent, 9),
|
||||
columns_(0),
|
||||
rows_(0),
|
||||
y_(0),
|
||||
@@ -124,7 +124,7 @@ void BlockAnalyzer::framerateChanged() {
|
||||
determineStep();
|
||||
}
|
||||
|
||||
void BlockAnalyzer::transform(Analyzer::Scope &s) {
|
||||
void BlockAnalyzer::transform(Scope &s) {
|
||||
|
||||
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
|
||||
// . . . . # .
|
||||
@@ -158,7 +158,7 @@ void BlockAnalyzer::analyze(QPainter &p, const Analyzer::Scope &s, bool new_fram
|
||||
|
||||
QPainter canvas_painter(&canvas_);
|
||||
|
||||
Analyzer::interpolate(s, scope_);
|
||||
interpolate(s, scope_);
|
||||
|
||||
// Paint the background
|
||||
canvas_painter.drawPixmap(0, 0, background_);
|
||||
|
||||
@@ -37,7 +37,7 @@
|
||||
class QWidget;
|
||||
class QResizeEvent;
|
||||
|
||||
class BlockAnalyzer : public Analyzer::Base {
|
||||
class BlockAnalyzer : public AnalyzerBase {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
@@ -53,8 +53,8 @@ class BlockAnalyzer : public Analyzer::Base {
|
||||
static const char *kName;
|
||||
|
||||
protected:
|
||||
void transform(Analyzer::Scope&) override;
|
||||
void analyze(QPainter &p, const Analyzer::Scope&, bool new_frame) override;
|
||||
void transform(Scope&) override;
|
||||
void analyze(QPainter &p, const Scope&, bool new_frame) override;
|
||||
void resizeEvent(QResizeEvent*) override;
|
||||
virtual void paletteChange(const QPalette&);
|
||||
void framerateChanged() override;
|
||||
@@ -71,7 +71,7 @@ class BlockAnalyzer : public Analyzer::Base {
|
||||
QPixmap topbarpixmap_;
|
||||
QPixmap background_;
|
||||
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> yscale_;
|
||||
|
||||
|
||||
@@ -32,13 +32,10 @@
|
||||
#include <QPalette>
|
||||
#include <QColor>
|
||||
|
||||
#include "engine/engine_fwd.h"
|
||||
#include "engine/enginebase.h"
|
||||
#include "fht.h"
|
||||
#include "analyzerbase.h"
|
||||
|
||||
using Analyzer::Scope;
|
||||
|
||||
const int BoomAnalyzer::kColumnWidth = 4;
|
||||
const int BoomAnalyzer::kMaxBandCount = 256;
|
||||
const int BoomAnalyzer::kMinBandCount = 32;
|
||||
@@ -46,7 +43,7 @@ const int BoomAnalyzer::kMinBandCount = 32;
|
||||
const char *BoomAnalyzer::kName = QT_TRANSLATE_NOOP("AnalyzerContainer", "Boom analyzer");
|
||||
|
||||
BoomAnalyzer::BoomAnalyzer(QWidget *parent)
|
||||
: Analyzer::Base(parent, 9),
|
||||
: AnalyzerBase(parent, 9),
|
||||
bands_(0),
|
||||
scope_(kMinBandCount),
|
||||
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) {
|
||||
|
||||
if (!new_frame || engine_->state() == Engine::State::Paused) {
|
||||
if (!new_frame || engine_->state() == EngineBase::State::Paused) {
|
||||
p.drawPixmap(0, 0, canvas_);
|
||||
return;
|
||||
}
|
||||
@@ -120,7 +117,7 @@ void BoomAnalyzer::analyze(QPainter &p, const Scope &scope, const bool new_frame
|
||||
QPainter canvas_painter(&canvas_);
|
||||
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) {
|
||||
h = log10(scope_[i] * 256.0) * F_;
|
||||
|
||||
@@ -36,7 +36,7 @@
|
||||
class QWidget;
|
||||
class QResizeEvent;
|
||||
|
||||
class BoomAnalyzer : public Analyzer::Base {
|
||||
class BoomAnalyzer : public AnalyzerBase {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
@@ -44,8 +44,8 @@ class BoomAnalyzer : public Analyzer::Base {
|
||||
|
||||
static const char *kName;
|
||||
|
||||
void transform(Analyzer::Scope &s) override;
|
||||
void analyze(QPainter &p, const Analyzer::Scope&, const bool new_frame) override;
|
||||
void transform(Scope &s) override;
|
||||
void analyze(QPainter &p, const Scope&, const bool new_frame) override;
|
||||
|
||||
public slots:
|
||||
void changeK_barHeight(int);
|
||||
@@ -59,7 +59,7 @@ class BoomAnalyzer : public Analyzer::Base {
|
||||
static const int kMinBandCount;
|
||||
|
||||
int bands_;
|
||||
Analyzer::Scope scope_;
|
||||
Scope scope_;
|
||||
QColor fg_;
|
||||
|
||||
double K_barHeight_, F_peakSpeed_, F_;
|
||||
|
||||
@@ -41,23 +41,21 @@
|
||||
#include "fht.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 int Rainbow::RainbowAnalyzer::kWidth[] = { 34, 53 };
|
||||
const int Rainbow::RainbowAnalyzer::kFrameCount[] = { 6, 16 };
|
||||
const int Rainbow::RainbowAnalyzer::kRainbowHeight[] = { 21, 16 };
|
||||
const int Rainbow::RainbowAnalyzer::kRainbowOverlap[] = { 13, 15 };
|
||||
const int Rainbow::RainbowAnalyzer::kSleepingHeight[] = { 24, 33 };
|
||||
const char *NyanCatAnalyzer::kName = "Nyanalyzer Cat";
|
||||
const char *RainbowDashAnalyzer::kName = "Rainbow Dash";
|
||||
const float RainbowAnalyzer::kPixelScale = 0.02F;
|
||||
|
||||
const char *Rainbow::NyanCatAnalyzer::kName = "Nyanalyzer Cat";
|
||||
const char *Rainbow::RainbowDashAnalyzer::kName = "Rainbow Dash";
|
||||
const float Rainbow::RainbowAnalyzer::kPixelScale = 0.02F;
|
||||
RainbowAnalyzer::RainbowType RainbowAnalyzer::rainbowtype;
|
||||
|
||||
Rainbow::RainbowAnalyzer::RainbowType Rainbow::RainbowAnalyzer::rainbowtype;
|
||||
|
||||
Rainbow::RainbowAnalyzer::RainbowAnalyzer(const RainbowType rbtype, QWidget *parent)
|
||||
: Analyzer::Base(parent, 9),
|
||||
RainbowAnalyzer::RainbowAnalyzer(const RainbowType rbtype, QWidget *parent)
|
||||
: AnalyzerBase(parent, 9),
|
||||
timer_id_(startTimer(kFrameIntervalMs)),
|
||||
frame_(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_) {
|
||||
frame_ = (frame_ + 1) % kFrameCount[rainbowtype];
|
||||
}
|
||||
else {
|
||||
Analyzer::Base::timerEvent(e);
|
||||
AnalyzerBase::timerEvent(e);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
void Rainbow::RainbowAnalyzer::resizeEvent(QResizeEvent *e) {
|
||||
void RainbowAnalyzer::resizeEvent(QResizeEvent *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
|
||||
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)
|
||||
: RainbowAnalyzer(Rainbow::RainbowAnalyzer::Nyancat, parent) {}
|
||||
NyanCatAnalyzer::NyanCatAnalyzer(QWidget *parent)
|
||||
: RainbowAnalyzer(RainbowAnalyzer::Nyancat, parent) {}
|
||||
|
||||
Rainbow::RainbowDashAnalyzer::RainbowDashAnalyzer(QWidget *parent)
|
||||
: RainbowAnalyzer(Rainbow::RainbowAnalyzer::Dash, parent) {}
|
||||
RainbowDashAnalyzer::RainbowDashAnalyzer(QWidget *parent)
|
||||
: RainbowAnalyzer(RainbowAnalyzer::Dash, parent) {}
|
||||
|
||||
@@ -40,8 +40,7 @@ class QWidget;
|
||||
class QTimerEvent;
|
||||
class QResizeEvent;
|
||||
|
||||
namespace Rainbow {
|
||||
class RainbowAnalyzer : public Analyzer::Base {
|
||||
class RainbowAnalyzer : public AnalyzerBase {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
@@ -53,8 +52,8 @@ class RainbowAnalyzer : public Analyzer::Base {
|
||||
RainbowAnalyzer(const RainbowType rbtype, QWidget *parent);
|
||||
|
||||
protected:
|
||||
void transform(Analyzer::Scope&) override;
|
||||
void analyze(QPainter &p, const Analyzer::Scope&, bool new_frame) override;
|
||||
void transform(Scope&) override;
|
||||
void analyze(QPainter &p, const Scope&, bool new_frame) override;
|
||||
|
||||
void timerEvent(QTimerEvent *e) override;
|
||||
void resizeEvent(QResizeEvent *e) override;
|
||||
@@ -142,6 +141,5 @@ class RainbowDashAnalyzer : public RainbowAnalyzer {
|
||||
|
||||
static const char *kName;
|
||||
};
|
||||
} // namespace Rainbow
|
||||
|
||||
#endif // RAINBOWANALYZER_H
|
||||
|
||||
@@ -24,14 +24,14 @@
|
||||
#include <QPainter>
|
||||
#include <QResizeEvent>
|
||||
|
||||
#include "engine/enginebase.h"
|
||||
|
||||
#include "sonogram.h"
|
||||
|
||||
const char *Sonogram::kName = QT_TRANSLATE_NOOP("AnalyzerContainer", "Sonogram");
|
||||
|
||||
Sonogram::Sonogram(QWidget *parent)
|
||||
: Analyzer::Base(parent, 9) {}
|
||||
|
||||
Sonogram::~Sonogram() {}
|
||||
: AnalyzerBase(parent, 9) {}
|
||||
|
||||
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_);
|
||||
return;
|
||||
}
|
||||
@@ -52,7 +52,7 @@ void Sonogram::analyze(QPainter &p, const Analyzer::Scope &s, bool new_frame) {
|
||||
QPainter canvas_painter(&canvas_);
|
||||
canvas_painter.drawPixmap(0, 0, canvas_, 1, 0, width() - 1, -1);
|
||||
|
||||
Analyzer::Scope::const_iterator it = s.begin(), end = s.end();
|
||||
Scope::const_iterator it = s.begin(), end = s.end();
|
||||
|
||||
for (int y = height() - 1; y;) {
|
||||
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_->scale(scope.data(), 1.0 / 256);
|
||||
@@ -90,5 +90,5 @@ void Sonogram::transform(Analyzer::Scope &scope) {
|
||||
}
|
||||
|
||||
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"
|
||||
|
||||
class Sonogram : public Analyzer::Base {
|
||||
class Sonogram : public AnalyzerBase {
|
||||
Q_OBJECT
|
||||
public:
|
||||
Q_INVOKABLE explicit Sonogram(QWidget *parent);
|
||||
~Sonogram();
|
||||
|
||||
static const char *kName;
|
||||
|
||||
protected:
|
||||
void resizeEvent(QResizeEvent *e) override;
|
||||
void analyze(QPainter &p, const Analyzer::Scope &s, bool new_frame) override;
|
||||
void transform(Analyzer::Scope &scope) override;
|
||||
void analyze(QPainter &p, const Scope &s, bool new_frame) override;
|
||||
void transform(Scope &scope) override;
|
||||
void demo(QPainter &p) override;
|
||||
|
||||
private:
|
||||
|
||||
@@ -58,8 +58,6 @@ SCollection::SCollection(Application *app, QObject *parent)
|
||||
watcher_(nullptr),
|
||||
watcher_thread_(nullptr),
|
||||
original_thread_(nullptr),
|
||||
io_priority_(Utilities::IoPriority::IOPRIO_CLASS_IDLE),
|
||||
thread_priority_(QThread::Priority::IdlePriority),
|
||||
save_playcounts_to_files_(false),
|
||||
save_ratings_to_files_(false) {
|
||||
|
||||
@@ -96,17 +94,13 @@ void SCollection::Init() {
|
||||
watcher_ = new CollectionWatcher(Song::Source::Collection);
|
||||
watcher_thread_ = new Thread(this);
|
||||
|
||||
#ifndef Q_OS_WIN32
|
||||
if (io_priority_ != Utilities::IoPriority::IOPRIO_CLASS_NONE) {
|
||||
watcher_thread_->SetIoPriority(io_priority_);
|
||||
}
|
||||
#endif
|
||||
watcher_thread_->SetIoPriority(Utilities::IoPriority::IOPRIO_CLASS_IDLE);
|
||||
|
||||
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_task_manager(app_->task_manager());
|
||||
@@ -169,7 +163,9 @@ void SCollection::AbortScan() { watcher_->Stop(); }
|
||||
void SCollection::Rescan(const SongList &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;
|
||||
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_ratings_to_files_ = s.value("save_ratings", false).toBool();
|
||||
s.endGroup();
|
||||
|
||||
@@ -28,11 +28,10 @@
|
||||
#include <QList>
|
||||
#include <QHash>
|
||||
#include <QString>
|
||||
#include <QThread>
|
||||
|
||||
#include "core/song.h"
|
||||
#include "utilities/threadutils.h"
|
||||
|
||||
class QThread;
|
||||
class Application;
|
||||
class Thread;
|
||||
class CollectionBackend;
|
||||
@@ -82,7 +81,7 @@ class SCollection : public QObject {
|
||||
void SongsRatingChanged(const SongList &songs, const bool save_tags = false);
|
||||
|
||||
signals:
|
||||
void Error(QString);
|
||||
void Error(const QString &error);
|
||||
void ExitFinished();
|
||||
|
||||
private:
|
||||
@@ -99,8 +98,6 @@ class SCollection : public QObject {
|
||||
|
||||
QList<QObject*> wait_for_exit_;
|
||||
|
||||
Utilities::IoPriority io_priority_;
|
||||
QThread::Priority thread_priority_;
|
||||
bool save_playcounts_to_files_;
|
||||
bool save_ratings_to_files_;
|
||||
};
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
* Strawberry Music Player
|
||||
* This file was part of Clementine.
|
||||
* 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
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
@@ -89,7 +89,7 @@ void CollectionBackend::Close() {
|
||||
}
|
||||
|
||||
void CollectionBackend::ExitAsync() {
|
||||
QMetaObject::invokeMethod(this, "Exit", Qt::QueuedConnection);
|
||||
QMetaObject::invokeMethod(this, &CollectionBackend::Exit, Qt::QueuedConnection);
|
||||
}
|
||||
|
||||
void CollectionBackend::Exit() {
|
||||
@@ -105,31 +105,29 @@ void CollectionBackend::ReportErrors(const CollectionQuery &query) {
|
||||
|
||||
const QSqlError sql_error = query.lastError();
|
||||
if (sql_error.isValid()) {
|
||||
qLog(Error) << "Unable to execute collection SQL query: " << sql_error;
|
||||
qLog(Error) << "Faulty SQL query: " << query.lastQuery();
|
||||
qLog(Error) << "Bound SQL values: " << query.boundValues();
|
||||
QString error;
|
||||
error += "Unable to execute collection SQL query: " + sql_error.text() + "<br />";
|
||||
error += "Faulty SQL query: " + query.lastQuery();
|
||||
emit Error(error);
|
||||
qLog(Error) << "Unable to execute collection SQL query:" << sql_error;
|
||||
qLog(Error) << "Failed SQL query:" << query.lastQuery();
|
||||
qLog(Error) << "Bound SQL values:" << query.boundValues();
|
||||
emit Error(tr("Unable to execute collection SQL query: %1").arg(sql_error.text()));
|
||||
emit Error(tr("Failed SQL query: %1").arg(query.lastQuery()));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
void CollectionBackend::LoadDirectoriesAsync() {
|
||||
QMetaObject::invokeMethod(this, "LoadDirectories", Qt::QueuedConnection);
|
||||
QMetaObject::invokeMethod(this, &CollectionBackend::LoadDirectories, Qt::QueuedConnection);
|
||||
}
|
||||
|
||||
void CollectionBackend::UpdateTotalSongCountAsync() {
|
||||
QMetaObject::invokeMethod(this, "UpdateTotalSongCount", Qt::QueuedConnection);
|
||||
QMetaObject::invokeMethod(this, &CollectionBackend::UpdateTotalSongCount, Qt::QueuedConnection);
|
||||
}
|
||||
|
||||
void CollectionBackend::UpdateTotalArtistCountAsync() {
|
||||
QMetaObject::invokeMethod(this, "UpdateTotalArtistCount", Qt::QueuedConnection);
|
||||
QMetaObject::invokeMethod(this, &CollectionBackend::UpdateTotalArtistCount, Qt::QueuedConnection);
|
||||
}
|
||||
|
||||
void CollectionBackend::UpdateTotalAlbumCountAsync() {
|
||||
QMetaObject::invokeMethod(this, "UpdateTotalAlbumCount", Qt::QueuedConnection);
|
||||
QMetaObject::invokeMethod(this, &CollectionBackend::UpdateTotalAlbumCount, Qt::QueuedConnection);
|
||||
}
|
||||
|
||||
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));
|
||||
}
|
||||
|
||||
void CollectionBackend::ResetStatisticsAsync(const int id, const bool save_tags) {
|
||||
QMetaObject::invokeMethod(this, "ResetStatistics", Qt::QueuedConnection, Q_ARG(int, id), Q_ARG(bool, save_tags));
|
||||
void CollectionBackend::ResetPlayStatisticsAsync(const int id, const 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() {
|
||||
@@ -714,7 +716,7 @@ void CollectionBackend::UpdateSongsBySongID(const SongMap &new_songs) {
|
||||
|
||||
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);
|
||||
@@ -1433,7 +1435,7 @@ CollectionBackend::AlbumList CollectionBackend::GetAlbums(const QString &artist,
|
||||
QSqlDatabase db(db_->Connect());
|
||||
|
||||
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");
|
||||
|
||||
if (compilation_required) {
|
||||
@@ -1451,42 +1453,48 @@ CollectionBackend::AlbumList CollectionBackend::GetAlbums(const QString &artist,
|
||||
|
||||
QMap<QString, Album> albums;
|
||||
while (query.Next()) {
|
||||
bool is_compilation = query.Value(3).toBool();
|
||||
|
||||
Album info;
|
||||
Album album_info;
|
||||
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) {
|
||||
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("..+:.*"))) {
|
||||
info.art_automatic = QUrl::fromEncoded(art_automatic.toUtf8());
|
||||
album_info.art_automatic = QUrl::fromEncoded(art_automatic.toUtf8());
|
||||
}
|
||||
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("..+:.*"))) {
|
||||
info.art_manual = QUrl::fromEncoded(art_manual.toUtf8());
|
||||
album_info.art_manual = QUrl::fromEncoded(art_manual.toUtf8());
|
||||
}
|
||||
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());
|
||||
QString filetype = Song::TextForFiletype(info.filetype);
|
||||
info.cue_path = query.Value(7).toString();
|
||||
album_info.art_unset = query.Value(9).toBool();
|
||||
|
||||
QString key;
|
||||
if (!info.album_artist.isEmpty()) {
|
||||
key.append(info.album_artist);
|
||||
if (!album_info.album_artist.isEmpty()) {
|
||||
key.append(album_info.album_artist);
|
||||
}
|
||||
if (!info.album.isEmpty()) {
|
||||
if (!album_info.album.isEmpty()) {
|
||||
if (!key.isEmpty()) key.append("-");
|
||||
key.append(info.album);
|
||||
key.append(album_info.album);
|
||||
}
|
||||
if (!filetype.isEmpty()) {
|
||||
key.append(filetype);
|
||||
@@ -1498,8 +1506,8 @@ CollectionBackend::AlbumList CollectionBackend::GetAlbums(const QString &artist,
|
||||
albums[key].urls.append(url);
|
||||
}
|
||||
else {
|
||||
info.urls << url;
|
||||
albums.insert(key, info);
|
||||
album_info.urls << url;
|
||||
albums.insert(key, album_info);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1518,7 +1526,7 @@ CollectionBackend::Album CollectionBackend::GetAlbumArt(const QString &effective
|
||||
ret.album_artist = effective_albumartist;
|
||||
|
||||
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()) {
|
||||
query.AddWhere("effective_albumartist", effective_albumartist);
|
||||
}
|
||||
@@ -1530,22 +1538,24 @@ CollectionBackend::Album CollectionBackend::GetAlbumArt(const QString &effective
|
||||
}
|
||||
|
||||
if (query.Next()) {
|
||||
ret.art_automatic = QUrl::fromEncoded(query.Value(0).toByteArray());
|
||||
ret.art_manual = QUrl::fromEncoded(query.Value(1).toByteArray());
|
||||
ret.urls << QUrl::fromEncoded(query.Value(2).toByteArray());
|
||||
ret.urls << QUrl::fromEncoded(query.Value(0).toByteArray());
|
||||
ret.art_embedded = query.Value(1).toInt() == 1;
|
||||
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;
|
||||
|
||||
}
|
||||
|
||||
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());
|
||||
QSqlDatabase db(db_->Connect());
|
||||
@@ -1569,15 +1579,11 @@ void CollectionBackend::UpdateManualAlbumArt(const QString &effective_albumartis
|
||||
}
|
||||
|
||||
// Update the songs
|
||||
QString sql = QString("UPDATE %1 SET art_manual = :cover").arg(songs_table_);
|
||||
if (clear_art_automatic) {
|
||||
sql += ", art_automatic = ''";
|
||||
}
|
||||
sql += " WHERE effective_albumartist = :effective_albumartist AND album = :album AND unavailable = 0";
|
||||
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_);
|
||||
|
||||
SqlQuery q(db);
|
||||
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(":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());
|
||||
QSqlDatabase db(db_->Connect());
|
||||
|
||||
// Get the songs before they're updated
|
||||
CollectionQuery query(db, songs_table_, fts_table_);
|
||||
query.SetColumnSpec("ROWID, " + Song::kColumnSpec);
|
||||
query.AddWhere("effective_albumartist", effective_albumartist);
|
||||
@@ -1635,16 +1640,124 @@ void CollectionBackend::UpdateAutomaticAlbumArt(const QString &effective_albumar
|
||||
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);
|
||||
q.prepare(sql);
|
||||
q.BindValue(":cover", cover_url.isValid() ? cover_url.toString(QUrl::FullyEncoded) : "");
|
||||
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(":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(":album", album);
|
||||
|
||||
@@ -1653,7 +1766,6 @@ void CollectionBackend::UpdateAutomaticAlbumArt(const QString &effective_albumar
|
||||
return;
|
||||
}
|
||||
|
||||
// Now get the updated songs
|
||||
if (!query.Exec()) {
|
||||
ReportErrors(query);
|
||||
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;
|
||||
|
||||
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());
|
||||
QSqlDatabase db(db_->Connect());
|
||||
|
||||
SqlQuery q(db);
|
||||
q.prepare(QString("UPDATE %1 SET playcount = 0, skipcount = 0, lastplayed = -1 WHERE ROWID = :id").arg(songs_table_));
|
||||
q.BindValue(":id", id);
|
||||
q.prepare(QString("UPDATE %1 SET playcount = 0, skipcount = 0, lastplayed = -1 WHERE ROWID IN (:ids)").arg(songs_table_));
|
||||
q.BindValue(":ids", id_str_list.join(","));
|
||||
if (!q.Exec()) {
|
||||
db_->ReportErrors(q);
|
||||
return;
|
||||
return false;
|
||||
}
|
||||
|
||||
Song new_song = GetSongById(id, db);
|
||||
emit SongsStatisticsChanged(SongList() << new_song, save_tags);
|
||||
return true;
|
||||
|
||||
}
|
||||
|
||||
void CollectionBackend::DeleteAllAsync() {
|
||||
|
||||
QMetaObject::invokeMethod(this, "DeleteAll", Qt::QueuedConnection);
|
||||
QMetaObject::invokeMethod(this, &CollectionBackend::DeleteAll, Qt::QueuedConnection);
|
||||
|
||||
}
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
* Strawberry Music Player
|
||||
* This file was part of Clementine.
|
||||
* 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
|
||||
* 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) {}
|
||||
|
||||
struct Album {
|
||||
Album() : filetype(Song::FileType::Unknown) {}
|
||||
Album(const QString &_album_artist, const QString &_album, const QUrl &_art_automatic, const QUrl &_art_manual, const QList<QUrl> &_urls, const Song::FileType _filetype, const QString &_cue_path)
|
||||
Album() : art_embedded(false), art_unset(false), filetype(Song::FileType::Unknown) {}
|
||||
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(_album),
|
||||
art_embedded(_art_embedded),
|
||||
art_automatic(_art_automatic),
|
||||
art_manual(_art_manual),
|
||||
art_unset(_art_unset),
|
||||
urls(_urls),
|
||||
filetype(_filetype),
|
||||
cue_path(_cue_path) {}
|
||||
@@ -67,8 +69,10 @@ class CollectionBackendInterface : public QObject {
|
||||
QString album_artist;
|
||||
QString album;
|
||||
|
||||
bool art_embedded;
|
||||
QUrl art_automatic;
|
||||
QUrl art_manual;
|
||||
bool art_unset;
|
||||
QList<QUrl> urls;
|
||||
Song::FileType filetype;
|
||||
QString cue_path;
|
||||
@@ -109,8 +113,10 @@ class CollectionBackendInterface : public QObject {
|
||||
virtual AlbumList GetAlbumsByArtist(const QString &artist, 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 UpdateAutomaticAlbumArtAsync(const QString &effective_albumartist, const QString &album, const QUrl &cover_url, const bool clear_art_manual = false) = 0;
|
||||
virtual void UpdateEmbeddedAlbumArtAsync(const QString &effective_albumartist, const QString &album, const bool art_embedded) = 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;
|
||||
|
||||
@@ -179,8 +185,10 @@ class CollectionBackend : public CollectionBackendInterface {
|
||||
AlbumList GetCompilationAlbums(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 UpdateAutomaticAlbumArtAsync(const QString &effective_albumartist, const QString &album, const QUrl &cover_url, const bool clear_art_manual = false) override;
|
||||
void UpdateEmbeddedAlbumArtAsync(const QString &effective_albumartist, const QString &album, const bool art_embedded) 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;
|
||||
|
||||
@@ -200,7 +208,8 @@ class CollectionBackend : public CollectionBackendInterface {
|
||||
|
||||
void IncrementPlayCountAsync(const int id);
|
||||
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();
|
||||
|
||||
@@ -231,12 +240,16 @@ class CollectionBackend : public CollectionBackendInterface {
|
||||
void MarkSongsUnavailable(const SongList &songs, const bool unavailable = true);
|
||||
void AddOrUpdateSubdirs(const CollectionSubdirectoryList &subdirs);
|
||||
void CompilationsNeedUpdating();
|
||||
void UpdateManualAlbumArt(const QString &effective_albumartist, const QString &album, const QUrl &cover_url, const bool clear_art_automatic = false);
|
||||
void UpdateAutomaticAlbumArt(const QString &effective_albumartist, const QString &album, const QUrl &cover_url, const bool clear_art_manual = false);
|
||||
void UpdateEmbeddedAlbumArt(const QString &effective_albumartist, const QString &album, const bool art_embedded);
|
||||
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 IncrementPlayCount(const int id);
|
||||
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 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);
|
||||
|
||||
signals:
|
||||
void DirectoryDiscovered(CollectionDirectory, CollectionSubdirectoryList);
|
||||
void DirectoryDeleted(CollectionDirectory);
|
||||
void DirectoryDiscovered(const CollectionDirectory &dir, const CollectionSubdirectoryList &subdir);
|
||||
void DirectoryDeleted(const CollectionDirectory &dir);
|
||||
|
||||
void SongsDiscovered(SongList);
|
||||
void SongsDeleted(SongList);
|
||||
void SongsStatisticsChanged(SongList, bool = false);
|
||||
void SongsDiscovered(const SongList &songs);
|
||||
void SongsDeleted(const SongList &songs);
|
||||
void SongsStatisticsChanged(const SongList &songs, const bool save_tags = false);
|
||||
|
||||
void DatabaseReset();
|
||||
|
||||
void TotalSongCountUpdated(int);
|
||||
void TotalArtistCountUpdated(int);
|
||||
void TotalAlbumCountUpdated(int);
|
||||
void SongsRatingChanged(SongList, bool);
|
||||
void TotalSongCountUpdated(const int count);
|
||||
void TotalArtistCountUpdated(const int count);
|
||||
void TotalAlbumCountUpdated(const int count);
|
||||
void SongsRatingChanged(const SongList &songs, const bool save_tags);
|
||||
|
||||
void ExitFinished();
|
||||
|
||||
void Error(QString);
|
||||
void Error(const QString &error);
|
||||
|
||||
private:
|
||||
struct CompilationInfo {
|
||||
@@ -300,7 +313,6 @@ class CollectionBackend : public CollectionBackendInterface {
|
||||
QString subdirs_table_;
|
||||
QString fts_table_;
|
||||
QThread *original_thread_;
|
||||
|
||||
};
|
||||
|
||||
#endif // COLLECTIONBACKEND_H
|
||||
|
||||
@@ -54,6 +54,7 @@
|
||||
#include "groupbydialog.h"
|
||||
#include "ui_collectionfilterwidget.h"
|
||||
#include "widgets/qsearchfield.h"
|
||||
#include "settings/collectionsettingspage.h"
|
||||
#include "settings/appearancesettingspage.h"
|
||||
|
||||
CollectionFilterWidget::CollectionFilterWidget(QWidget *parent)
|
||||
|
||||
@@ -96,7 +96,7 @@ class CollectionFilterWidget : public QWidget {
|
||||
void UpPressed();
|
||||
void DownPressed();
|
||||
void ReturnPressed();
|
||||
void Filter(QString text);
|
||||
void Filter(const QString &text);
|
||||
|
||||
protected:
|
||||
void keyReleaseEvent(QKeyEvent *e) override;
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
* Strawberry Music Player
|
||||
* This file was part of Clementine.
|
||||
* 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
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
@@ -42,7 +42,6 @@
|
||||
#include <QMap>
|
||||
#include <QMetaType>
|
||||
#include <QVariant>
|
||||
#include <QByteArray>
|
||||
#include <QString>
|
||||
#include <QStringList>
|
||||
#include <QUrl>
|
||||
@@ -72,6 +71,7 @@
|
||||
#include "covermanager/albumcoverloader.h"
|
||||
#include "covermanager/albumcoverloaderresult.h"
|
||||
#include "settings/collectionsettingspage.h"
|
||||
#include "settings/coverssettingspage.h"
|
||||
|
||||
const int CollectionModel::kPrettyCoverSize = 32;
|
||||
const char *CollectionModel::kPixmapDiskCacheDir = "pixmapcache";
|
||||
@@ -102,12 +102,6 @@ CollectionModel::CollectionModel(CollectionBackend *backend, Application *app, Q
|
||||
group_by_[1] = GroupBy::AlbumDisc;
|
||||
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_) {
|
||||
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;
|
||||
Reset();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
void CollectionModel::set_show_dividers(const bool show_dividers) {
|
||||
@@ -178,6 +173,8 @@ void CollectionModel::ReloadSettings() {
|
||||
|
||||
s.endGroup();
|
||||
|
||||
cover_types_ = AlbumCoverLoaderOptions::LoadTypes();
|
||||
|
||||
if (!use_disk_cache_) {
|
||||
ClearDiskCache();
|
||||
}
|
||||
@@ -342,10 +339,10 @@ QString CollectionModel::ContainerKey(const GroupBy group_by, const bool separat
|
||||
key = PrettyDisc(song.disc());
|
||||
break;
|
||||
case GroupBy::Year:
|
||||
key = QString::number(qMax(0, song.year()));
|
||||
key = QString::number(std::max(0, song.year()));
|
||||
break;
|
||||
case GroupBy::OriginalYear:
|
||||
key = QString::number(qMax(0, song.effective_originalyear()));
|
||||
key = QString::number(std::max(0, song.effective_originalyear()));
|
||||
break;
|
||||
case GroupBy::Genre:
|
||||
key = TextOrUnknown(song.genre());
|
||||
@@ -363,13 +360,13 @@ QString CollectionModel::ContainerKey(const GroupBy group_by, const bool separat
|
||||
key = song.TextForFiletype();
|
||||
break;
|
||||
case GroupBy::Samplerate:
|
||||
key = QString::number(qMax(0, song.samplerate()));
|
||||
key = QString::number(std::max(0, song.samplerate()));
|
||||
break;
|
||||
case GroupBy::Bitdepth:
|
||||
key = QString::number(qMax(0, song.bitdepth()));
|
||||
key = QString::number(std::max(0, song.bitdepth()));
|
||||
break;
|
||||
case GroupBy::Bitrate:
|
||||
key = QString::number(qMax(0, song.bitrate()));
|
||||
key = QString::number(std::max(0, song.bitrate()));
|
||||
break;
|
||||
case GroupBy::Format:
|
||||
if (song.samplerate() <= 0) {
|
||||
@@ -559,7 +556,7 @@ void CollectionModel::SongsDeleted(const SongList &songs) {
|
||||
// Remove from pixmap cache
|
||||
const QString cache_key = AlbumIconPixmapCacheKey(ItemToIndex(node));
|
||||
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)) {
|
||||
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) {
|
||||
|
||||
CollectionItem *item = IndexToItem(idx);
|
||||
@@ -629,10 +632,10 @@ QVariant CollectionModel::AlbumIcon(const QModelIndex &idx) {
|
||||
|
||||
// Try to load it from the disk cache
|
||||
if (use_disk_cache_ && sIconCache) {
|
||||
std::unique_ptr<QIODevice> cache(sIconCache->data(QUrl(cache_key)));
|
||||
if (cache) {
|
||||
std::unique_ptr<QIODevice> disk_cache_img(sIconCache->data(AlbumIconPixmapDiskCacheKey(cache_key)));
|
||||
if (disk_cache_img) {
|
||||
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));
|
||||
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.
|
||||
SongList songs = GetChildSongs(idx);
|
||||
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_cache_keys_.insert(cache_key);
|
||||
}
|
||||
@@ -669,7 +675,7 @@ void CollectionModel::AlbumCoverLoaded(const quint64 id, const AlbumCoverLoaderR
|
||||
pending_cache_keys_.remove(cache_key);
|
||||
|
||||
// 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.
|
||||
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 (use_disk_cache_ && sIconCache && result.success && !result.image_scaled.isNull()) {
|
||||
std::unique_ptr<QIODevice> cached_img(sIconCache->data(QUrl(cache_key)));
|
||||
if (!cached_img) {
|
||||
QNetworkCacheMetaData item_metadata;
|
||||
item_metadata.setSaveToDisk(true);
|
||||
item_metadata.setUrl(QUrl(cache_key));
|
||||
QIODevice *cache = sIconCache->prepare(item_metadata);
|
||||
if (cache) {
|
||||
result.image_scaled.save(cache, "XPM");
|
||||
sIconCache->insert(cache);
|
||||
const QUrl disk_cache_key = AlbumIconPixmapDiskCacheKey(cache_key);
|
||||
std::unique_ptr<QIODevice> disk_cache_img(sIconCache->data(disk_cache_key));
|
||||
if (!disk_cache_img) {
|
||||
QNetworkCacheMetaData disk_cache_metadata;
|
||||
disk_cache_metadata.setSaveToDisk(true);
|
||||
disk_cache_metadata.setUrl(disk_cache_key);
|
||||
// Qt 6 now ignores any entry without headers, so add a fake header.
|
||||
disk_cache_metadata.setRawHeaders(QNetworkCacheMetaData::RawHeaderList() << qMakePair(QByteArray(), QByteArray()));
|
||||
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->key.append(ContainerKey(group_by, separate_albums_by_grouping, item->metadata));
|
||||
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;
|
||||
}
|
||||
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->key.append(ContainerKey(group_by, separate_albums_by_grouping, item->metadata));
|
||||
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;
|
||||
}
|
||||
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->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->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;
|
||||
}
|
||||
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->key.append(ContainerKey(group_by, separate_albums_by_grouping, item->metadata));
|
||||
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;
|
||||
}
|
||||
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->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->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;
|
||||
}
|
||||
case GroupBy::Disc:{
|
||||
item->metadata.set_disc(row.value(0).toInt());
|
||||
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->sort_text = SortTextForNumber(disc);
|
||||
break;
|
||||
@@ -1311,7 +1320,7 @@ CollectionItem *CollectionModel::ItemFromQuery(const GroupBy group_by, const boo
|
||||
case GroupBy::Year:{
|
||||
item->metadata.set_year(row.value(0).toInt());
|
||||
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->sort_text = SortTextForNumber(year) + " ";
|
||||
break;
|
||||
@@ -1319,7 +1328,7 @@ CollectionItem *CollectionModel::ItemFromQuery(const GroupBy group_by, const boo
|
||||
case GroupBy::OriginalYear:{
|
||||
item->metadata.set_originalyear(row.value(0).toInt());
|
||||
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->sort_text = SortTextForNumber(year) + " ";
|
||||
break;
|
||||
@@ -1372,7 +1381,7 @@ CollectionItem *CollectionModel::ItemFromQuery(const GroupBy group_by, const boo
|
||||
case GroupBy::Samplerate:{
|
||||
item->metadata.set_samplerate(row.value(0).toInt());
|
||||
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->sort_text = SortTextForNumber(samplerate) + " ";
|
||||
break;
|
||||
@@ -1380,7 +1389,7 @@ CollectionItem *CollectionModel::ItemFromQuery(const GroupBy group_by, const boo
|
||||
case GroupBy::Bitdepth:{
|
||||
item->metadata.set_bitdepth(row.value(0).toInt());
|
||||
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->sort_text = SortTextForNumber(bitdepth) + " ";
|
||||
break;
|
||||
@@ -1388,7 +1397,7 @@ CollectionItem *CollectionModel::ItemFromQuery(const GroupBy group_by, const boo
|
||||
case GroupBy::Bitrate:{
|
||||
item->metadata.set_bitrate(row.value(0).toInt());
|
||||
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->sort_text = SortTextForNumber(bitrate) + " ";
|
||||
break;
|
||||
@@ -1448,77 +1457,77 @@ CollectionItem *CollectionModel::ItemFromSong(const GroupBy group_by, const bool
|
||||
case GroupBy::AlbumDisc:{
|
||||
item->metadata.set_album(s.album());
|
||||
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->key.append(ContainerKey(group_by, separate_albums_by_grouping, s));
|
||||
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;
|
||||
}
|
||||
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_id(s.album_id());
|
||||
item->metadata.set_grouping(s.grouping());
|
||||
item->key.append(ContainerKey(group_by, separate_albums_by_grouping, s));
|
||||
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;
|
||||
}
|
||||
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_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->key.append(ContainerKey(group_by, separate_albums_by_grouping, s));
|
||||
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;
|
||||
}
|
||||
case GroupBy::OriginalYearAlbum:{
|
||||
item->metadata.set_year(s.year());
|
||||
item->metadata.set_originalyear(s.originalyear());
|
||||
item->metadata.set_year(s.year() <= 0 ? -1 : s.year());
|
||||
item->metadata.set_originalyear(s.originalyear() <= 0 ? -1 : s.originalyear());
|
||||
item->metadata.set_album(s.album());
|
||||
item->metadata.set_album_id(s.album_id());
|
||||
item->metadata.set_grouping(s.grouping());
|
||||
item->key.append(ContainerKey(group_by, separate_albums_by_grouping, s));
|
||||
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;
|
||||
}
|
||||
case GroupBy::OriginalYearAlbumDisc:{
|
||||
item->metadata.set_year(s.year());
|
||||
item->metadata.set_originalyear(s.originalyear());
|
||||
item->metadata.set_year(s.year() <= 0 ? -1 : s.year());
|
||||
item->metadata.set_originalyear(s.originalyear() <= 0 ? -1 : s.originalyear());
|
||||
item->metadata.set_album(s.album());
|
||||
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->key.append(ContainerKey(group_by, separate_albums_by_grouping, s));
|
||||
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;
|
||||
}
|
||||
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));
|
||||
const int disc = qMax(0, s.disc());
|
||||
const int disc = std::max(0, s.disc());
|
||||
item->display_text = PrettyDisc(disc);
|
||||
item->sort_text = SortTextForNumber(disc);
|
||||
break;
|
||||
}
|
||||
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));
|
||||
const int year = qMax(0, s.year());
|
||||
const int year = std::max(0, s.year());
|
||||
item->display_text = QString::number(year);
|
||||
item->sort_text = SortTextForNumber(year) + " ";
|
||||
break;
|
||||
}
|
||||
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));
|
||||
const int year = qMax(0, s.effective_originalyear());
|
||||
const int year = std::max(0, s.effective_originalyear());
|
||||
item->display_text = QString::number(year);
|
||||
item->sort_text = SortTextForNumber(year) + " ";
|
||||
break;
|
||||
@@ -1571,7 +1580,7 @@ CollectionItem *CollectionModel::ItemFromSong(const GroupBy group_by, const bool
|
||||
case GroupBy::Samplerate:{
|
||||
item->metadata.set_samplerate(s.samplerate());
|
||||
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->sort_text = SortTextForNumber(samplerate) + " ";
|
||||
break;
|
||||
@@ -1579,7 +1588,7 @@ CollectionItem *CollectionModel::ItemFromSong(const GroupBy group_by, const bool
|
||||
case GroupBy::Bitdepth:{
|
||||
item->metadata.set_bitdepth(s.bitdepth());
|
||||
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->sort_text = SortTextForNumber(bitdepth) + " ";
|
||||
break;
|
||||
@@ -1587,7 +1596,7 @@ CollectionItem *CollectionModel::ItemFromSong(const GroupBy group_by, const bool
|
||||
case GroupBy::Bitrate:{
|
||||
item->metadata.set_bitrate(s.bitrate());
|
||||
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->sort_text = SortTextForNumber(bitrate) + " ";
|
||||
break;
|
||||
@@ -1686,7 +1695,7 @@ QString CollectionModel::PrettyYearAlbumDisc(const int year, const QString &albu
|
||||
|
||||
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 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.append(song.url().toString());
|
||||
return ret;
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
* Strawberry Music Player
|
||||
* This file was part of Clementine.
|
||||
* 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
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
@@ -49,11 +49,11 @@
|
||||
#include "core/song.h"
|
||||
#include "core/sqlrow.h"
|
||||
#include "covermanager/albumcoverloader.h"
|
||||
#include "covermanager/albumcoverloaderoptions.h"
|
||||
#include "collectionfilteroptions.h"
|
||||
#include "collectionquery.h"
|
||||
#include "collectionqueryoptions.h"
|
||||
#include "collectionitem.h"
|
||||
#include "covermanager/albumcoverloaderoptions.h"
|
||||
|
||||
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);
|
||||
|
||||
signals:
|
||||
void TotalSongCountUpdated(int count);
|
||||
void TotalArtistCountUpdated(int count);
|
||||
void TotalAlbumCountUpdated(int count);
|
||||
void GroupingChanged(CollectionModel::Grouping g, bool separate_albums_by_grouping);
|
||||
void TotalSongCountUpdated(const int count);
|
||||
void TotalArtistCountUpdated(const int count);
|
||||
void TotalAlbumCountUpdated(const int count);
|
||||
void GroupingChanged(const CollectionModel::Grouping g, const bool separate_albums_by_grouping);
|
||||
|
||||
public slots:
|
||||
void SetFilterMode(CollectionFilterOptions::FilterMode filter_mode);
|
||||
@@ -267,6 +267,7 @@ class CollectionModel : public SimpleTreeModel<CollectionItem> {
|
||||
// Helpers
|
||||
static bool IsCompilationArtistNode(const CollectionItem *node) { return node == node->parent->compilation_artist_node_; }
|
||||
QString AlbumIconPixmapCacheKey(const QModelIndex &idx) const;
|
||||
QUrl AlbumIconPixmapDiskCacheKey(const QString &cache_key) const;
|
||||
QVariant AlbumIcon(const QModelIndex &idx);
|
||||
QVariant data(const CollectionItem *item, const int role) 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_lazy_loading_;
|
||||
|
||||
AlbumCoverLoaderOptions cover_loader_options_;
|
||||
AlbumCoverLoaderOptions::Types cover_types_;
|
||||
|
||||
using ItemAndCacheKey = QPair<CollectionItem*, QString>;
|
||||
QMap<quint64, ItemAndCacheKey> pending_art_;
|
||||
|
||||
@@ -117,7 +117,7 @@ void CollectionView::SaveFocus() {
|
||||
|
||||
QModelIndex current = currentIndex();
|
||||
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;
|
||||
}
|
||||
|
||||
@@ -154,7 +154,7 @@ void CollectionView::SaveContainerPath(const QModelIndex &child) {
|
||||
|
||||
QModelIndex current = model()->parent(child);
|
||||
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;
|
||||
}
|
||||
|
||||
|
||||
@@ -89,7 +89,7 @@ class CollectionView : public AutoExpandingTreeView {
|
||||
void TotalSongCountUpdated_();
|
||||
void TotalArtistCountUpdated_();
|
||||
void TotalAlbumCountUpdated_();
|
||||
void Error(QString);
|
||||
void Error(const QString &error);
|
||||
|
||||
protected:
|
||||
// QWidget
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
* Strawberry Music Player
|
||||
* This file was part of Clementine.
|
||||
* 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
|
||||
* 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),
|
||||
stop_requested_(false),
|
||||
abort_requested_(false),
|
||||
rescan_in_progress_(false),
|
||||
rescan_timer_(new QTimer(this)),
|
||||
periodic_scan_timer_(new QTimer(this)),
|
||||
rescan_paused_(false),
|
||||
@@ -109,13 +108,14 @@ CollectionWatcher::CollectionWatcher(Song::Source source, QObject *parent)
|
||||
|
||||
ReloadSettings();
|
||||
|
||||
QObject::connect(fs_watcher_, &FileSystemWatcherInterface::PathChanged, this, &CollectionWatcher::DirectoryChanged, Qt::UniqueConnection);
|
||||
QObject::connect(rescan_timer_, &QTimer::timeout, this, &CollectionWatcher::RescanPathsNow);
|
||||
QObject::connect(periodic_scan_timer_, &QTimer::timeout, this, &CollectionWatcher::IncrementalScanCheck);
|
||||
|
||||
}
|
||||
|
||||
void CollectionWatcher::ExitAsync() {
|
||||
QMetaObject::invokeMethod(this, "Exit", Qt::QueuedConnection);
|
||||
QMetaObject::invokeMethod(this, &CollectionWatcher::Exit, Qt::QueuedConnection);
|
||||
}
|
||||
|
||||
void CollectionWatcher::Exit() {
|
||||
@@ -131,7 +131,7 @@ void CollectionWatcher::Exit() {
|
||||
|
||||
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();
|
||||
s.endGroup();
|
||||
|
||||
best_image_filters_.clear();
|
||||
best_art_filters_.clear();
|
||||
for (const QString &filter : filters) {
|
||||
QString str = filter.trimmed();
|
||||
if (!str.isEmpty()) best_image_filters_ << str;
|
||||
if (!str.isEmpty()) best_art_filters_ << str;
|
||||
}
|
||||
|
||||
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;
|
||||
|
||||
// Also want to look to see whether the album art has changed
|
||||
QUrl image = ImageForSong(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()))) {
|
||||
const QUrl art_automatic = ArtForSong(file, album_art);
|
||||
if (matching_song.art_automatic() != art_automatic || (!matching_song.art_automatic().isEmpty() && !matching_song.art_automatic_is_valid())) {
|
||||
changed = true;
|
||||
}
|
||||
|
||||
@@ -573,16 +573,16 @@ void CollectionWatcher::ScanSubdirectory(const QString &path, const CollectionSu
|
||||
#endif
|
||||
|
||||
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.
|
||||
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
|
||||
else if (matching_song.is_unavailable()) {
|
||||
else if (matching_song.unavailable()) {
|
||||
t->readded_songs << matching_songs;
|
||||
}
|
||||
|
||||
@@ -633,13 +633,13 @@ void CollectionWatcher::ScanSubdirectory(const QString &path, const CollectionSu
|
||||
}
|
||||
|
||||
// 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.
|
||||
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.
|
||||
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.";
|
||||
|
||||
// Choose an image for the song(s)
|
||||
QUrl image = ImageForSong(file, album_art);
|
||||
// Choose art for the song(s)
|
||||
const QUrl art_automatic = ArtForSong(file, album_art);
|
||||
|
||||
for (Song song : songs) {
|
||||
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;
|
||||
}
|
||||
}
|
||||
@@ -669,7 +669,7 @@ void CollectionWatcher::ScanSubdirectory(const QString &path, const CollectionSu
|
||||
// Look for deleted songs
|
||||
for (const Song &song : songs_in_db) {
|
||||
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;
|
||||
t->deleted_songs << song;
|
||||
}
|
||||
@@ -704,7 +704,7 @@ void CollectionWatcher::UpdateCueAssociatedSongs(const QString &file,
|
||||
const QString &path,
|
||||
const QString &fingerprint,
|
||||
const QString &matching_cue,
|
||||
const QUrl &image,
|
||||
const QUrl &art_automatic,
|
||||
const SongList &old_cue_songs,
|
||||
ScanTransaction *t) {
|
||||
|
||||
@@ -733,7 +733,7 @@ void CollectionWatcher::UpdateCueAssociatedSongs(const QString &file,
|
||||
if (sections_map.contains(new_cue_song.beginning_nanosec())) { // Changed section
|
||||
const Song matching_cue_song = sections_map[new_cue_song.beginning_nanosec()];
|
||||
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);
|
||||
AddChangedSong(file, matching_cue_song, new_cue_song, t);
|
||||
used_ids.insert(matching_cue_song.id());
|
||||
@@ -755,7 +755,7 @@ void CollectionWatcher::UpdateCueAssociatedSongs(const QString &file,
|
||||
void CollectionWatcher::UpdateNonCueAssociatedSong(const QString &file,
|
||||
const QString &fingerprint,
|
||||
const SongList &matching_songs,
|
||||
const QUrl &image,
|
||||
const QUrl &art_automatic,
|
||||
const bool cue_deleted,
|
||||
ScanTransaction *t) {
|
||||
|
||||
@@ -776,7 +776,7 @@ void CollectionWatcher::UpdateNonCueAssociatedSong(const QString &file,
|
||||
song_on_disk.set_directory_id(t->dir());
|
||||
song_on_disk.set_id(matching_song.id());
|
||||
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_);
|
||||
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;
|
||||
QStringList changes;
|
||||
|
||||
if (matching_song.is_unavailable()) {
|
||||
if (matching_song.unavailable()) {
|
||||
qLog(Debug) << "unavailable song" << file << "restored.";
|
||||
notify_new = true;
|
||||
}
|
||||
@@ -855,18 +855,26 @@ void CollectionWatcher::AddChangedSong(const QString &file, const Song &matching
|
||||
changes << "metadata";
|
||||
notify_new = true;
|
||||
}
|
||||
if (!matching_song.IsStatisticsEqual(new_song)) {
|
||||
changes << "statistics";
|
||||
if (!matching_song.IsPlayStatisticsEqual(new_song)) {
|
||||
changes << "play statistics";
|
||||
notify_new = true;
|
||||
}
|
||||
if (!matching_song.IsRatingEqual(new_song)) {
|
||||
changes << "rating";
|
||||
notify_new = true;
|
||||
}
|
||||
if (matching_song.art_automatic() != new_song.art_automatic() || matching_song.art_manual() != new_song.art_manual()) {
|
||||
if (!matching_song.IsArtEqual(new_song)) {
|
||||
changes << "album art";
|
||||
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()) {
|
||||
changes << "mtime";
|
||||
}
|
||||
@@ -909,7 +917,6 @@ void CollectionWatcher::AddWatch(const CollectionDirectory &dir, const QString &
|
||||
|
||||
if (!QFile::exists(path)) return;
|
||||
|
||||
QObject::connect(fs_watcher_, &FileSystemWatcherInterface::PathChanged, this, &CollectionWatcher::DirectoryChanged, Qt::UniqueConnection);
|
||||
fs_watcher_->AddPath(path);
|
||||
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.
|
||||
// Pick the biggest image that matches the most important filter
|
||||
|
||||
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
|
||||
for (const QString &image : images) {
|
||||
QFileInfo fileinfo(image);
|
||||
for (const QString &art_automatic : art_automatic_list) {
|
||||
QFileInfo fileinfo(art_automatic);
|
||||
QString filename(fileinfo.fileName());
|
||||
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.
|
||||
@@ -1055,7 +1062,7 @@ QString CollectionWatcher::PickBestImage(const QStringList &images) {
|
||||
|
||||
if (filtered.isEmpty()) {
|
||||
// The filter was too restrictive, just use the original list
|
||||
filtered = images;
|
||||
filtered = art_automatic_list;
|
||||
}
|
||||
|
||||
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));
|
||||
|
||||
if (album_art.contains(dir)) {
|
||||
if (album_art[dir].count() == 1) {
|
||||
return QUrl::fromLocalFile(album_art[dir][0]);
|
||||
if (art_automatic_list.contains(dir)) {
|
||||
if (art_automatic_list[dir].count() == 1) {
|
||||
return QUrl::fromLocalFile(art_automatic_list[dir][0]);
|
||||
}
|
||||
else {
|
||||
QString best_image = PickBestImage(album_art[dir]);
|
||||
album_art[dir] = QStringList() << best_image;
|
||||
return QUrl::fromLocalFile(best_image);
|
||||
const QString best_art = PickBestArt(art_automatic_list[dir]);
|
||||
art_automatic_list[dir] = QStringList() << best_art;
|
||||
return QUrl::fromLocalFile(best_art);
|
||||
}
|
||||
}
|
||||
|
||||
return QUrl();
|
||||
|
||||
}
|
||||
@@ -1111,25 +1119,13 @@ void CollectionWatcher::SetRescanPaused(bool pause) {
|
||||
|
||||
void CollectionWatcher::IncrementalScanAsync() {
|
||||
|
||||
QMetaObject::invokeMethod(this, "IncrementalScanNow", Qt::QueuedConnection);
|
||||
QMetaObject::invokeMethod(this, &CollectionWatcher::IncrementalScanNow, Qt::QueuedConnection);
|
||||
|
||||
}
|
||||
|
||||
void CollectionWatcher::FullScanAsync() {
|
||||
|
||||
QMetaObject::invokeMethod(this, "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);
|
||||
}
|
||||
QMetaObject::invokeMethod(this, &CollectionWatcher::FullScanNow, Qt::QueuedConnection);
|
||||
|
||||
}
|
||||
|
||||
@@ -1147,34 +1143,6 @@ void CollectionWatcher::IncrementalScanNow() { PerformScan(true, false); }
|
||||
|
||||
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) {
|
||||
|
||||
stop_requested_ = false;
|
||||
@@ -1260,3 +1228,34 @@ quint64 CollectionWatcher::FilesCountForSubdirs(ScanTransaction *t, const Collec
|
||||
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
|
||||
* This file was part of Clementine.
|
||||
* 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
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
@@ -59,7 +59,6 @@ class CollectionWatcher : public QObject {
|
||||
|
||||
void IncrementalScanAsync();
|
||||
void FullScanAsync();
|
||||
void RescanTracksAsync(const SongList &songs);
|
||||
void SetRescanPausedAsync(const bool pause);
|
||||
void ReloadSettingsAsync();
|
||||
|
||||
@@ -68,19 +67,21 @@ class CollectionWatcher : public QObject {
|
||||
|
||||
void ExitAsync();
|
||||
|
||||
void RescanSongsAsync(const SongList &songs);
|
||||
|
||||
signals:
|
||||
void NewOrUpdatedSongs(SongList);
|
||||
void SongsMTimeUpdated(SongList);
|
||||
void SongsDeleted(SongList);
|
||||
void SongsUnavailable(SongList songs, bool unavailable = true);
|
||||
void SongsReadded(SongList songs, bool unavailable = false);
|
||||
void SubdirsDiscovered(CollectionSubdirectoryList subdirs);
|
||||
void SubdirsMTimeUpdated(CollectionSubdirectoryList subdirs);
|
||||
void NewOrUpdatedSongs(const SongList &songs);
|
||||
void SongsMTimeUpdated(const SongList &songs);
|
||||
void SongsDeleted(const SongList &songs);
|
||||
void SongsUnavailable(const SongList &songs, const bool unavailable = true);
|
||||
void SongsReadded(const SongList &songs, const bool unavailable = false);
|
||||
void SubdirsDiscovered(const CollectionSubdirectoryList &subdirs);
|
||||
void SubdirsMTimeUpdated(const CollectionSubdirectoryList &subdirs);
|
||||
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 ScanStarted(int task_id);
|
||||
void ScanStarted(const int task_id);
|
||||
|
||||
public slots:
|
||||
void AddDirectory(const CollectionDirectory &dir, const CollectionSubdirectoryList &subdirs);
|
||||
@@ -166,9 +167,9 @@ class CollectionWatcher : public QObject {
|
||||
void IncrementalScanCheck();
|
||||
void IncrementalScanNow();
|
||||
void FullScanNow();
|
||||
void RescanTracksNow();
|
||||
void RescanPathsNow();
|
||||
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:
|
||||
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 ExtensionPart(const QString &fileName);
|
||||
inline static QString DirectoryPart(const QString &fileName);
|
||||
QString PickBestImage(const QStringList &images);
|
||||
QUrl ImageForSong(const QString &path, QMap<QString, QStringList> &album_art);
|
||||
QString PickBestArt(const QStringList &art_automatic_list);
|
||||
QUrl ArtForSong(const QString &path, QMap<QString, QStringList> &art_automatic_list);
|
||||
void AddWatch(const CollectionDirectory &dir, const QString &path);
|
||||
void RemoveWatch(const CollectionDirectory &dir, const CollectionSubdirectory &subdir);
|
||||
static quint64 GetMtimeForCue(const QString &cue_path);
|
||||
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.
|
||||
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.
|
||||
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.
|
||||
// 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);
|
||||
@@ -209,9 +210,9 @@ class CollectionWatcher : public QObject {
|
||||
QThread *original_thread_;
|
||||
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.
|
||||
QStringList best_image_filters_;
|
||||
QStringList best_art_filters_;
|
||||
|
||||
bool scan_on_startup_;
|
||||
bool monitor_;
|
||||
@@ -223,7 +224,6 @@ class CollectionWatcher : public QObject {
|
||||
|
||||
bool stop_requested_;
|
||||
bool abort_requested_;
|
||||
bool rescan_in_progress_; // True if RescanTracksNow() has been called and is working.
|
||||
|
||||
QMap<int, CollectionDirectory> watched_dirs_;
|
||||
QTimer *rescan_timer_;
|
||||
@@ -237,8 +237,6 @@ class CollectionWatcher : public QObject {
|
||||
|
||||
static QStringList sValidImages;
|
||||
|
||||
SongList song_rescan_queue_; // Set by UI thread
|
||||
|
||||
qint64 last_scan_time_;
|
||||
|
||||
};
|
||||
|
||||
@@ -49,7 +49,7 @@ class GroupByDialog : public QDialog {
|
||||
void accept() override;
|
||||
|
||||
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:
|
||||
void Reset();
|
||||
|
||||
@@ -36,6 +36,7 @@
|
||||
|
||||
#include "core/logging.h"
|
||||
#include "core/iconloader.h"
|
||||
#include "settings/collectionsettingspage.h"
|
||||
#include "collectionmodel.h"
|
||||
#include "savedgroupingmanager.h"
|
||||
#include "ui_savedgroupingmanager.h"
|
||||
|
||||
@@ -55,4 +55,6 @@
|
||||
#cmakedefine USE_TAGLIB
|
||||
#cmakedefine USE_TAGPARSER
|
||||
|
||||
#cmakedefine HAVE_QX11APPLICATION
|
||||
|
||||
#endif // CONFIG_H_IN
|
||||
|
||||
@@ -54,16 +54,14 @@ ContextAlbum::ContextAlbum(QWidget *parent)
|
||||
timeline_fade_(new QTimeLine(kFadeTimeLineMs, this)),
|
||||
image_strawberry_(":/pictures/strawberry.png"),
|
||||
image_original_(image_strawberry_),
|
||||
pixmap_current_opacity_(1.0) {
|
||||
pixmap_current_opacity_(1.0),
|
||||
desired_height_(width()) {
|
||||
|
||||
setObjectName("context-widget-album");
|
||||
|
||||
setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed);
|
||||
|
||||
cover_loader_options_.desired_height_ = width();
|
||||
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_);
|
||||
QImage image = ImageUtils::ScaleImage(image_strawberry_, QSize(desired_height_, desired_height_), devicePixelRatioF(), true);
|
||||
if (!image.isNull()) {
|
||||
pixmap_current_ = QPixmap::fromImage(image);
|
||||
}
|
||||
@@ -91,7 +89,7 @@ void ContextAlbum::Init(ContextView *context_view, AlbumCoverChoiceController *a
|
||||
|
||||
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) {
|
||||
|
||||
if (new_width != cover_loader_options_.desired_height_) {
|
||||
cover_loader_options_.desired_height_ = new_width;
|
||||
if (new_width != desired_height_) {
|
||||
desired_height_ = new_width;
|
||||
ScaleCover();
|
||||
ScalePreviousCovers();
|
||||
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;
|
||||
|
||||
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() {
|
||||
|
||||
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()) {
|
||||
pixmap_current_ = QPixmap();
|
||||
}
|
||||
@@ -248,7 +246,7 @@ void ContextAlbum::ScaleCover() {
|
||||
void ContextAlbum::ScalePreviousCovers() {
|
||||
|
||||
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()) {
|
||||
previous_cover->pixmap = QPixmap();
|
||||
}
|
||||
|
||||
@@ -33,8 +33,6 @@
|
||||
#include <QPixmap>
|
||||
#include <QMovie>
|
||||
|
||||
#include "covermanager/albumcoverloaderoptions.h"
|
||||
|
||||
class QMenu;
|
||||
class QTimeLine;
|
||||
class QPainter;
|
||||
@@ -99,7 +97,6 @@ class ContextAlbum : public QWidget {
|
||||
QMenu *menu_;
|
||||
ContextView *context_view_;
|
||||
AlbumCoverChoiceController *album_cover_choice_controller_;
|
||||
AlbumCoverLoaderOptions cover_loader_options_;
|
||||
bool downloading_covers_;
|
||||
QTimeLine *timeline_fade_;
|
||||
QImage image_strawberry_;
|
||||
@@ -107,6 +104,7 @@ class ContextAlbum : public QWidget {
|
||||
QPixmap pixmap_current_;
|
||||
qreal pixmap_current_opacity_;
|
||||
std::unique_ptr<QMovie> spinner_animation_;
|
||||
int desired_height_;
|
||||
};
|
||||
|
||||
#endif // CONTEXTALBUM_H
|
||||
|
||||
@@ -51,15 +51,9 @@
|
||||
#include "core/application.h"
|
||||
#include "core/player.h"
|
||||
#include "core/song.h"
|
||||
#include "core/iconloader.h"
|
||||
#include "utilities/strutils.h"
|
||||
#include "utilities/timeutils.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/collectionquery.h"
|
||||
#include "collection/collectionview.h"
|
||||
@@ -81,7 +75,6 @@ ContextView::ContextView(QWidget *parent)
|
||||
menu_options_(new QMenu(this)),
|
||||
action_show_album_(nullptr),
|
||||
action_show_data_(nullptr),
|
||||
action_show_output_(nullptr),
|
||||
action_show_lyrics_(nullptr),
|
||||
action_search_lyrics_(nullptr),
|
||||
layout_container_(new QVBoxLayout()),
|
||||
@@ -97,11 +90,8 @@ ContextView::ContextView(QWidget *parent)
|
||||
layout_play_(new QVBoxLayout()),
|
||||
label_stop_summary_(new QLabel(this)),
|
||||
widget_play_data_(new QWidget(this)),
|
||||
widget_play_output_(new QWidget(this)),
|
||||
layout_play_data_(new QGridLayout()),
|
||||
layout_play_output_(new QGridLayout()),
|
||||
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)),
|
||||
label_filetype_title_(new QLabel(this)),
|
||||
label_length_title_(new QLabel(this)),
|
||||
@@ -113,18 +103,8 @@ ContextView::ContextView(QWidget *parent)
|
||||
label_samplerate_(new QLabel(this)),
|
||||
label_bitdepth_(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_id_(-1),
|
||||
font_size_headline_(0),
|
||||
font_size_normal_(0) {
|
||||
lyrics_id_(-1) {
|
||||
|
||||
setLayout(layout_container_);
|
||||
|
||||
@@ -175,36 +155,6 @@ ContextView::ContextView(QWidget *parent)
|
||||
|
||||
// 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_length_title_->setText(tr("Length"));
|
||||
label_samplerate_title_->setText(tr("Samplerate"));
|
||||
@@ -242,26 +192,18 @@ ContextView::ContextView(QWidget *parent)
|
||||
textedit_play_lyrics_->hide();
|
||||
|
||||
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_->addSpacerItem(spacer_play_data_);
|
||||
layout_play_->addWidget(textedit_play_lyrics_);
|
||||
layout_play_->addSpacerItem(new QSpacerItem(20, 20, QSizePolicy::Expanding, QSizePolicy::Expanding));
|
||||
|
||||
labels_play_ << label_engine_title_
|
||||
<< label_device_title_
|
||||
<< label_filetype_title_
|
||||
labels_play_ << label_filetype_title_
|
||||
<< label_length_title_
|
||||
<< label_samplerate_title_
|
||||
<< label_bitdepth_title_
|
||||
<< label_bitrate_title_;
|
||||
|
||||
labels_play_data_ << label_engine_icon_
|
||||
<< label_engine_
|
||||
<< label_device_
|
||||
<< label_device_icon_
|
||||
<< label_filetype_
|
||||
labels_play_data_ << label_filetype_
|
||||
<< label_length_
|
||||
<< label_samplerate_
|
||||
<< label_bitdepth_
|
||||
@@ -303,10 +245,6 @@ void ContextView::AddActions() {
|
||||
action_show_data_->setCheckable(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_->setCheckable(true);
|
||||
action_show_lyrics_->setChecked(true);
|
||||
@@ -317,7 +255,6 @@ void ContextView::AddActions() {
|
||||
|
||||
menu_options_->addAction(action_show_album_);
|
||||
menu_options_->addAction(action_show_data_);
|
||||
menu_options_->addAction(action_show_output_);
|
||||
menu_options_->addAction(action_show_lyrics_);
|
||||
menu_options_->addAction(action_search_lyrics_);
|
||||
menu_options_->addSeparator();
|
||||
@@ -326,7 +263,6 @@ void ContextView::AddActions() {
|
||||
|
||||
QObject::connect(action_show_album_, &QAction::triggered, this, &ContextView::ActionShowAlbum);
|
||||
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_search_lyrics_, &QAction::triggered, this, &ContextView::ActionSearchLyrics);
|
||||
|
||||
@@ -334,19 +270,32 @@ void ContextView::AddActions() {
|
||||
|
||||
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;
|
||||
s.beginGroup(ContextSettingsPage::kSettingsGroup);
|
||||
title_fmt_ = s.value(ContextSettingsPage::kSettingsTitleFmt, "%title% - %artist%").toString();
|
||||
summary_fmt_ = s.value(ContextSettingsPage::kSettingsSummaryFmt, "%album%").toString();
|
||||
action_show_album_->setChecked(s.value(ContextSettingsPage::kSettingsGroupEnable[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_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_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_normal_ = s.value("font_normal", font().family()).toString();
|
||||
font_size_headline_ = s.value("font_size_headline", ContextSettingsPage::kDefaultFontSizeHeadline).toReal();
|
||||
font_size_normal_ = s.value("font_size_normal", font().pointSizeF()).toReal();
|
||||
font_headline_.setFamily(s.value("font_headline", default_font).toString());
|
||||
font_headline_.setPointSizeF(s.value("font_size_headline", ContextSettingsPage::kDefaultFontSizeHeadline).toReal());
|
||||
font_nosong_.setFamily(font_headline_.family());
|
||||
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();
|
||||
|
||||
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) {
|
||||
lyrics_fetcher_->Clear();
|
||||
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();
|
||||
}
|
||||
|
||||
textedit_top_->setFont(QFont(font_headline_, font_size_headline_ * 1.6));
|
||||
textedit_top_->setFont(font_nosong_);
|
||||
textedit_top_->SetText(tr("No song playing"));
|
||||
|
||||
QString html;
|
||||
@@ -451,27 +400,25 @@ void ContextView::NoSong() {
|
||||
else html += tr("%1 albums").arg(collectionview_->TotalAlbums());
|
||||
html += "<br />";
|
||||
|
||||
label_stop_summary_->setFont(QFont(font_normal_, font_size_normal_));
|
||||
label_stop_summary_->setFont(font_normal_);
|
||||
label_stop_summary_->setText(html);
|
||||
|
||||
}
|
||||
|
||||
void ContextView::UpdateFonts() {
|
||||
|
||||
QFont font(font_normal_, font_size_normal_);
|
||||
font.setBold(false);
|
||||
for (QLabel *l : labels_play_all_) {
|
||||
l->setFont(font);
|
||||
l->setFont(font_normal_);
|
||||
}
|
||||
for (QTextEdit *e : textedit_play_) {
|
||||
e->setFont(font);
|
||||
e->setFont(font_normal_);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
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)));
|
||||
|
||||
label_stop_summary_->clear();
|
||||
@@ -542,49 +489,6 @@ void ContextView::SetSong() {
|
||||
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()) {
|
||||
textedit_play_lyrics_->SetText(lyrics_);
|
||||
textedit_play_lyrics_->show();
|
||||
@@ -671,7 +575,6 @@ void ContextView::ResetSong() {
|
||||
l->clear();
|
||||
}
|
||||
|
||||
widget_play_output_->hide();
|
||||
widget_play_data_->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() {
|
||||
|
||||
QSettings s;
|
||||
|
||||
@@ -86,7 +86,6 @@ class ContextView : public QWidget {
|
||||
private slots:
|
||||
void ActionShowAlbum();
|
||||
void ActionShowData();
|
||||
void ActionShowOutput();
|
||||
void ActionShowLyrics();
|
||||
void ActionSearchLyrics();
|
||||
void UpdateNoSong();
|
||||
@@ -112,7 +111,6 @@ class ContextView : public QWidget {
|
||||
QMenu *menu_options_;
|
||||
QAction *action_show_album_;
|
||||
QAction *action_show_data_;
|
||||
QAction *action_show_output_;
|
||||
QAction *action_show_lyrics_;
|
||||
QAction *action_search_lyrics_;
|
||||
|
||||
@@ -129,12 +127,9 @@ class ContextView : public QWidget {
|
||||
QVBoxLayout *layout_play_;
|
||||
QLabel *label_stop_summary_;
|
||||
QWidget *widget_play_data_;
|
||||
QWidget *widget_play_output_;
|
||||
QGridLayout *layout_play_data_;
|
||||
QGridLayout *layout_play_output_;
|
||||
ResizableTextEdit *textedit_play_lyrics_;
|
||||
|
||||
QSpacerItem *spacer_play_output_;
|
||||
QSpacerItem *spacer_play_data_;
|
||||
|
||||
QLabel *label_filetype_title_;
|
||||
@@ -149,15 +144,6 @@ class ContextView : public QWidget {
|
||||
QLabel *label_bitdepth_;
|
||||
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_prev_;
|
||||
QImage image_original_;
|
||||
@@ -166,10 +152,9 @@ class ContextView : public QWidget {
|
||||
QString lyrics_;
|
||||
QString title_fmt_;
|
||||
QString summary_fmt_;
|
||||
QString font_headline_;
|
||||
QString font_normal_;
|
||||
qreal font_size_headline_;
|
||||
qreal font_size_normal_;
|
||||
QFont font_headline_;
|
||||
QFont font_normal_;
|
||||
QFont font_nosong_;
|
||||
|
||||
QList<QLabel*> labels_play_;
|
||||
QList<ResizableTextEdit*> textedit_play_;
|
||||
|
||||
@@ -13,9 +13,11 @@
|
||||
SBSystemPreferencesWindow, SBSystemPreferencesPane,
|
||||
SBSystemPreferencesAnchor;
|
||||
|
||||
#ifdef __GNUC__
|
||||
#pragma GCC diagnostic push
|
||||
#pragma GCC diagnostic ignored "-Wmultichar"
|
||||
#pragma GCC diagnostic ignored "-Wfour-char-constants"
|
||||
#endif
|
||||
|
||||
enum SBSystemPreferencesSaveOptions {
|
||||
SBSystemPreferencesSaveOptionsYes = 'yes ' /* Save the file. */,
|
||||
@@ -32,7 +34,9 @@ enum SBSystemPreferencesPrintingErrorHandling {
|
||||
'lwdt' /* print a detailed report of PostScript errors */
|
||||
};
|
||||
|
||||
#ifdef __GNUC__
|
||||
#pragma GCC diagnostic pop
|
||||
#endif
|
||||
|
||||
typedef enum SBSystemPreferencesPrintingErrorHandling
|
||||
SBSystemPreferencesPrintingErrorHandling;
|
||||
|
||||
@@ -38,6 +38,7 @@
|
||||
#include "database.h"
|
||||
#include "taskmanager.h"
|
||||
#include "player.h"
|
||||
#include "networkaccessmanager.h"
|
||||
|
||||
#include "engine/devicefinders.h"
|
||||
#ifndef Q_OS_WIN
|
||||
@@ -57,16 +58,21 @@
|
||||
#include "covermanager/spotifycoverprovider.h"
|
||||
|
||||
#include "lyrics/lyricsproviders.h"
|
||||
#include "lyrics/auddlyricsprovider.h"
|
||||
#include "lyrics/geniuslyricsprovider.h"
|
||||
#include "lyrics/ovhlyricsprovider.h"
|
||||
#include "lyrics/lololyricsprovider.h"
|
||||
#include "lyrics/musixmatchlyricsprovider.h"
|
||||
#include "lyrics/chartlyricsprovider.h"
|
||||
#include "lyrics/stands4lyricsprovider.h"
|
||||
#include "lyrics/lyricscomlyricsprovider.h"
|
||||
|
||||
#include "scrobbler/audioscrobbler.h"
|
||||
#include "scrobbler/lastfmscrobbler.h"
|
||||
#include "scrobbler/librefmscrobbler.h"
|
||||
#include "scrobbler/listenbrainzscrobbler.h"
|
||||
#include "scrobbler/lastfmimport.h"
|
||||
#ifdef HAVE_SUBSONIC
|
||||
# include "scrobbler/subsonicscrobbler.h"
|
||||
#endif
|
||||
|
||||
#include "internet/internetservices.h"
|
||||
|
||||
@@ -111,6 +117,7 @@ class ApplicationImpl {
|
||||
}),
|
||||
task_manager_([app]() { return new TaskManager(app); }),
|
||||
player_([app]() { return new Player(app, app); }),
|
||||
network_([app]() { return new NetworkAccessManager(app); }),
|
||||
device_finders_([app]() { return new DeviceFinders(app); }),
|
||||
#ifndef Q_OS_WIN
|
||||
device_manager_([app]() { return new DeviceManager(app, app); }),
|
||||
@@ -125,17 +132,17 @@ class ApplicationImpl {
|
||||
cover_providers_([app]() {
|
||||
CoverProviders *cover_providers = new CoverProviders(app);
|
||||
// Initialize the repository of cover providers.
|
||||
cover_providers->AddProvider(new LastFmCoverProvider(app, cover_providers->network(), app));
|
||||
cover_providers->AddProvider(new MusicbrainzCoverProvider(app, cover_providers->network(), app));
|
||||
cover_providers->AddProvider(new DiscogsCoverProvider(app, cover_providers->network(), app));
|
||||
cover_providers->AddProvider(new DeezerCoverProvider(app, cover_providers->network(), app));
|
||||
cover_providers->AddProvider(new MusixmatchCoverProvider(app, cover_providers->network(), app));
|
||||
cover_providers->AddProvider(new SpotifyCoverProvider(app, cover_providers->network(), app));
|
||||
cover_providers->AddProvider(new LastFmCoverProvider(app, app->network(), app));
|
||||
cover_providers->AddProvider(new MusicbrainzCoverProvider(app, app->network(), app));
|
||||
cover_providers->AddProvider(new DiscogsCoverProvider(app, app->network(), app));
|
||||
cover_providers->AddProvider(new DeezerCoverProvider(app, app->network(), app));
|
||||
cover_providers->AddProvider(new MusixmatchCoverProvider(app, app->network(), app));
|
||||
cover_providers->AddProvider(new SpotifyCoverProvider(app, app->network(), app));
|
||||
#ifdef HAVE_TIDAL
|
||||
cover_providers->AddProvider(new TidalCoverProvider(app, cover_providers->network(), app));
|
||||
cover_providers->AddProvider(new TidalCoverProvider(app, app->network(), app));
|
||||
#endif
|
||||
#ifdef HAVE_QOBUZ
|
||||
cover_providers->AddProvider(new QobuzCoverProvider(app, cover_providers->network(), app));
|
||||
cover_providers->AddProvider(new QobuzCoverProvider(app, app->network(), app));
|
||||
#endif
|
||||
cover_providers->ReloadSettings();
|
||||
return cover_providers;
|
||||
@@ -149,13 +156,12 @@ class ApplicationImpl {
|
||||
lyrics_providers_([app]() {
|
||||
LyricsProviders *lyrics_providers = new LyricsProviders(app);
|
||||
// Initialize the repository of lyrics providers.
|
||||
lyrics_providers->AddProvider(new AuddLyricsProvider(lyrics_providers->network(), app));
|
||||
lyrics_providers->AddProvider(new GeniusLyricsProvider(lyrics_providers->network(), app));
|
||||
lyrics_providers->AddProvider(new OVHLyricsProvider(lyrics_providers->network(), app));
|
||||
lyrics_providers->AddProvider(new LoloLyricsProvider(lyrics_providers->network(), app));
|
||||
lyrics_providers->AddProvider(new MusixmatchLyricsProvider(lyrics_providers->network(), app));
|
||||
lyrics_providers->AddProvider(new ChartLyricsProvider(lyrics_providers->network(), app));
|
||||
lyrics_providers->AddProvider(new Stands4LyricsProvider(lyrics_providers->network(), app));
|
||||
lyrics_providers->AddProvider(new GeniusLyricsProvider(app->network(), app));
|
||||
lyrics_providers->AddProvider(new OVHLyricsProvider(app->network(), app));
|
||||
lyrics_providers->AddProvider(new LoloLyricsProvider(app->network(), app));
|
||||
lyrics_providers->AddProvider(new MusixmatchLyricsProvider(app->network(), app));
|
||||
lyrics_providers->AddProvider(new ChartLyricsProvider(app->network(), app));
|
||||
lyrics_providers->AddProvider(new LyricsComLyricsProvider(app->network(), app));
|
||||
lyrics_providers->ReloadSettings();
|
||||
return lyrics_providers;
|
||||
}),
|
||||
@@ -173,18 +179,25 @@ class ApplicationImpl {
|
||||
return internet_services;
|
||||
}),
|
||||
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
|
||||
moodbar_loader_([app]() { return new MoodbarLoader(app, app); }),
|
||||
moodbar_controller_([app]() { return new MoodbarController(app, app); }),
|
||||
#endif
|
||||
lastfm_import_([app]() { return new LastFMImport(app); })
|
||||
lastfm_import_([app]() { return new LastFMImport(app->network(), app); })
|
||||
{}
|
||||
|
||||
Lazy<TagReaderClient> tag_reader_client_;
|
||||
Lazy<Database> database_;
|
||||
Lazy<TaskManager> task_manager_;
|
||||
Lazy<Player> player_;
|
||||
Lazy<NetworkAccessManager> network_;
|
||||
Lazy<DeviceFinders> device_finders_;
|
||||
#ifndef Q_OS_WIN
|
||||
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(); }
|
||||
TaskManager *Application::task_manager() const { return p_->task_manager_.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(); }
|
||||
#ifndef Q_OS_WIN
|
||||
DeviceManager *Application::device_manager() const { return p_->device_manager_.get(); }
|
||||
|
||||
@@ -41,6 +41,7 @@ class TagReaderClient;
|
||||
class Database;
|
||||
class DeviceFinders;
|
||||
class Player;
|
||||
class NetworkAccessManager;
|
||||
class SCollection;
|
||||
class CollectionBackend;
|
||||
class CollectionModel;
|
||||
@@ -74,6 +75,7 @@ class Application : public QObject {
|
||||
Database *database() const;
|
||||
TaskManager *task_manager() const;
|
||||
Player *player() const;
|
||||
NetworkAccessManager *network() const;
|
||||
DeviceFinders *device_finders() const;
|
||||
#ifndef Q_OS_WIN
|
||||
DeviceManager *device_manager() const;
|
||||
@@ -118,9 +120,9 @@ class Application : public QObject {
|
||||
void OpenSettingsDialogAtPage(SettingsDialog::Page page);
|
||||
|
||||
signals:
|
||||
void ErrorAdded(QString message);
|
||||
void ErrorAdded(const QString &message);
|
||||
void SettingsChanged();
|
||||
void SettingsDialogRequested(SettingsDialog::Page page);
|
||||
void SettingsDialogRequested(const SettingsDialog::Page page);
|
||||
void ExitFinished();
|
||||
void ClearPixmapDiskCache();
|
||||
|
||||
|
||||
@@ -22,10 +22,14 @@
|
||||
#include "version.h"
|
||||
|
||||
#include <cstdlib>
|
||||
#include <getopt.h>
|
||||
#include <iostream>
|
||||
|
||||
#include <QtGlobal>
|
||||
|
||||
#ifdef Q_OS_WIN32
|
||||
# include <windows.h>
|
||||
#endif
|
||||
|
||||
#include <QObject>
|
||||
#include <QIODevice>
|
||||
#include <QDataStream>
|
||||
@@ -39,6 +43,8 @@
|
||||
#include "commandlineoptions.h"
|
||||
#include "core/logging.h"
|
||||
|
||||
#include <getopt.h>
|
||||
|
||||
const char *CommandlineOptions::kHelpText =
|
||||
"%1: strawberry [%2] [%3]\n"
|
||||
"\n"
|
||||
@@ -80,7 +86,11 @@ const char *CommandlineOptions::kVersionText = "Strawberry %1";
|
||||
|
||||
CommandlineOptions::CommandlineOptions(int argc, char **argv)
|
||||
: argc_(argc),
|
||||
#ifdef Q_OS_WIN32
|
||||
argv_(CommandLineToArgvW(GetCommandLineW(), &argc)),
|
||||
#else
|
||||
argv_(argv),
|
||||
#endif
|
||||
url_list_action_(UrlListAction::None),
|
||||
player_action_(PlayerAction::None),
|
||||
set_volume_(-1),
|
||||
@@ -92,6 +102,10 @@ CommandlineOptions::CommandlineOptions(int argc, char **argv)
|
||||
toggle_pretty_osd_(false),
|
||||
log_levels_(logging::kDefaultLogLevels) {
|
||||
|
||||
#ifdef Q_OS_WIN32
|
||||
Q_UNUSED(argv);
|
||||
#endif
|
||||
|
||||
#ifdef Q_OS_MACOS
|
||||
// Remove -psn_xxx option that Mac passes when opened from Finder.
|
||||
RemoveArg("-psn", 1);
|
||||
@@ -99,12 +113,13 @@ CommandlineOptions::CommandlineOptions(int argc, char **argv)
|
||||
|
||||
// Remove the -session option that KDE passes
|
||||
RemoveArg("-session", 2);
|
||||
|
||||
}
|
||||
|
||||
void CommandlineOptions::RemoveArg(const QString &starts_with, int count) {
|
||||
|
||||
for (int i = 0; i < argc_; ++i) {
|
||||
QString opt(argv_[i]);
|
||||
const QString opt = OptArgToString(argv_[i]);
|
||||
if (opt.startsWith(starts_with)) {
|
||||
for (int j = i; j < argc_ - count + 1; ++j) {
|
||||
argv_[j] = argv_[j + count];
|
||||
@@ -119,41 +134,79 @@ void CommandlineOptions::RemoveArg(const QString &starts_with, int count) {
|
||||
bool CommandlineOptions::Parse() {
|
||||
|
||||
static const struct option kOptions[] = {
|
||||
{"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}};
|
||||
#ifdef Q_OS_WIN32
|
||||
{L"help", no_argument, nullptr, 'h'},
|
||||
{L"play", no_argument, nullptr, 'p'},
|
||||
{L"play-pause", no_argument, nullptr, 't'},
|
||||
{L"pause", no_argument, nullptr, 'u'},
|
||||
{L"stop", no_argument, nullptr, 's'},
|
||||
{L"stop-after-current", no_argument, nullptr, 'q'},
|
||||
{L"previous", no_argument, nullptr, 'r'},
|
||||
{L"next", no_argument, nullptr, 'f'},
|
||||
{L"volume", required_argument, nullptr, 'v'},
|
||||
{L"volume-up", no_argument, nullptr, LongOptions::VolumeUp},
|
||||
{L"volume-down", no_argument, nullptr, LongOptions::VolumeDown},
|
||||
{L"volume-increase-by", required_argument, nullptr, LongOptions::VolumeIncreaseBy},
|
||||
{L"volume-decrease-by", required_argument, nullptr, LongOptions::VolumeDecreaseBy},
|
||||
{L"seek-to", required_argument, nullptr, LongOptions::SeekTo},
|
||||
{L"seek-by", required_argument, nullptr, LongOptions::SeekBy},
|
||||
{L"restart-or-previous", no_argument, nullptr, LongOptions::RestartOrPrevious },
|
||||
{L"create", required_argument, nullptr, 'c' },
|
||||
{L"append", no_argument, nullptr, 'a' },
|
||||
{L"load", no_argument, nullptr, 'l'},
|
||||
{L"play-track", required_argument, nullptr, 'k'},
|
||||
{L"play-playlist", required_argument, nullptr, 'i'},
|
||||
{L"show-osd", no_argument, nullptr, 'o'},
|
||||
{L"toggle-pretty-osd", no_argument, nullptr, 'y'},
|
||||
{L"language", required_argument, nullptr, 'g'},
|
||||
{L"resize-window", required_argument, nullptr, 'w'},
|
||||
{L"quiet", no_argument, nullptr, LongOptions::Quiet},
|
||||
{L"verbose", no_argument, nullptr, LongOptions::Verbose},
|
||||
{L"log-levels", required_argument, nullptr, LongOptions::LogLevels},
|
||||
{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
|
||||
bool ok = false;
|
||||
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);
|
||||
#endif
|
||||
|
||||
// End of the options
|
||||
if (c == -1) break;
|
||||
@@ -162,36 +215,36 @@ bool CommandlineOptions::Parse() {
|
||||
case 'h': {
|
||||
QString translated_help_text =
|
||||
QString(kHelpText)
|
||||
.arg(tr("Usage"), tr("options"), tr("URL(s)"),
|
||||
tr("Player options"),
|
||||
tr("Start the playlist currently playing"),
|
||||
tr("Play if stopped, pause if playing"),
|
||||
tr("Pause playback"), tr("Stop playback"),
|
||||
tr("Stop playback after current track"))
|
||||
.arg(tr("Skip backwards in playlist"),
|
||||
tr("Skip forwards in playlist"),
|
||||
tr("Set the volume to <value> percent"),
|
||||
tr("Increase the volume by 4 percent"),
|
||||
tr("Decrease the volume by 4 percent"),
|
||||
tr("Increase the volume by <value> percent"),
|
||||
tr("Decrease the volume by <value> percent"))
|
||||
.arg(tr("Seek the currently playing track to an absolute position"),
|
||||
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."),
|
||||
tr("Playlist options"),
|
||||
tr("Create a new playlist with files"),
|
||||
tr("Append files/URLs to the playlist"),
|
||||
tr("Loads files/URLs, replacing current playlist"),
|
||||
tr("Play the <n>th track in the playlist"),
|
||||
tr("Play given playlist"))
|
||||
.arg(tr("Other options"), tr("Display the on-screen-display"),
|
||||
tr("Toggle visibility for the pretty on-screen-display"),
|
||||
tr("Change the language"),
|
||||
tr("Resize the window"),
|
||||
tr("Equivalent to --log-levels *:1"),
|
||||
tr("Equivalent to --log-levels *:3"),
|
||||
tr("Comma separated list of class:level, level is 0-3"))
|
||||
.arg(tr("Print out version information"));
|
||||
.arg(QObject::tr("Usage"), QObject::tr("options"), QObject::tr("URL(s)"),
|
||||
QObject::tr("Player options"),
|
||||
QObject::tr("Start the playlist currently playing"),
|
||||
QObject::tr("Play if stopped, pause if playing"),
|
||||
QObject::tr("Pause playback"), QObject::tr("Stop playback"),
|
||||
QObject::tr("Stop playback after current track"))
|
||||
.arg(QObject::tr("Skip backwards in playlist"),
|
||||
QObject::tr("Skip forwards in playlist"),
|
||||
QObject::tr("Set the volume to <value> percent"),
|
||||
QObject::tr("Increase the volume by 4 percent"),
|
||||
QObject::tr("Decrease the volume by 4 percent"),
|
||||
QObject::tr("Increase the volume by <value> percent"),
|
||||
QObject::tr("Decrease the volume by <value> percent"))
|
||||
.arg(QObject::tr("Seek the currently playing track to an absolute position"),
|
||||
QObject::tr("Seek the currently playing track by a relative amount"),
|
||||
QObject::tr("Restart the track, or play the previous track if within 8 seconds of start."),
|
||||
QObject::tr("Playlist options"),
|
||||
QObject::tr("Create a new playlist with files"),
|
||||
QObject::tr("Append files/URLs to the playlist"),
|
||||
QObject::tr("Loads files/URLs, replacing current playlist"),
|
||||
QObject::tr("Play the <n>th track in the playlist"),
|
||||
QObject::tr("Play given playlist"))
|
||||
.arg(QObject::tr("Other options"), QObject::tr("Display the on-screen-display"),
|
||||
QObject::tr("Toggle visibility for the pretty on-screen-display"),
|
||||
QObject::tr("Change the language"),
|
||||
QObject::tr("Resize the window"),
|
||||
QObject::tr("Equivalent to --log-levels *:1"),
|
||||
QObject::tr("Equivalent to --log-levels *:3"),
|
||||
QObject::tr("Comma separated list of class:level, level is 0-3"))
|
||||
.arg(QObject::tr("Print out version information"));
|
||||
|
||||
std::cout << translated_help_text.toLocal8Bit().constData();
|
||||
return false;
|
||||
@@ -220,11 +273,11 @@ bool CommandlineOptions::Parse() {
|
||||
break;
|
||||
case 'i':
|
||||
player_action_ = PlayerAction::PlayPlaylist;
|
||||
playlist_name_ = QString(optarg);
|
||||
playlist_name_ = OptArgToString(optarg);
|
||||
break;
|
||||
case 'c':
|
||||
url_list_action_ = UrlListAction::CreateNew;
|
||||
playlist_name_ = QString(optarg);
|
||||
playlist_name_ = OptArgToString(optarg);
|
||||
break;
|
||||
case 'a':
|
||||
url_list_action_ = UrlListAction::Append;
|
||||
@@ -239,7 +292,7 @@ bool CommandlineOptions::Parse() {
|
||||
toggle_pretty_osd_ = true;
|
||||
break;
|
||||
case 'g':
|
||||
language_ = QString(optarg);
|
||||
language_ = OptArgToString(optarg);
|
||||
break;
|
||||
case LongOptions::VolumeUp:
|
||||
volume_modifier_ = +4;
|
||||
@@ -254,7 +307,7 @@ bool CommandlineOptions::Parse() {
|
||||
log_levels_ = "3";
|
||||
break;
|
||||
case LongOptions::LogLevels:
|
||||
log_levels_ = QString(optarg);
|
||||
log_levels_ = OptArgToString(optarg);
|
||||
break;
|
||||
case LongOptions::Version: {
|
||||
QString version_text = QString(kVersionText).arg(STRAWBERRY_VERSION_DISPLAY);
|
||||
@@ -262,27 +315,27 @@ bool CommandlineOptions::Parse() {
|
||||
std::exit(0);
|
||||
}
|
||||
case 'v':
|
||||
set_volume_ = QString(optarg).toInt(&ok);
|
||||
set_volume_ = OptArgToString(optarg).toInt(&ok);
|
||||
if (!ok) set_volume_ = -1;
|
||||
break;
|
||||
|
||||
case LongOptions::VolumeIncreaseBy:
|
||||
volume_modifier_ = QString(optarg).toInt(&ok);
|
||||
volume_modifier_ = OptArgToString(optarg).toInt(&ok);
|
||||
if (!ok) volume_modifier_ = 0;
|
||||
break;
|
||||
|
||||
case LongOptions::VolumeDecreaseBy:
|
||||
volume_modifier_ = -QString(optarg).toInt(&ok);
|
||||
volume_modifier_ = -OptArgToString(optarg).toInt(&ok);
|
||||
if (!ok) volume_modifier_ = 0;
|
||||
break;
|
||||
|
||||
case LongOptions::SeekTo:
|
||||
seek_to_ = QString(optarg).toInt(&ok);
|
||||
seek_to_ = OptArgToString(optarg).toInt(&ok);
|
||||
if (!ok) seek_to_ = -1;
|
||||
break;
|
||||
|
||||
case LongOptions::SeekBy:
|
||||
seek_by_ = QString(optarg).toInt(&ok);
|
||||
seek_by_ = OptArgToString(optarg).toInt(&ok);
|
||||
if (!ok) seek_by_ = 0;
|
||||
break;
|
||||
|
||||
@@ -291,12 +344,12 @@ bool CommandlineOptions::Parse() {
|
||||
break;
|
||||
|
||||
case 'k':
|
||||
play_track_at_ = QString(optarg).toInt(&ok);
|
||||
play_track_at_ = OptArgToString(optarg).toInt(&ok);
|
||||
if (!ok) play_track_at_ = -1;
|
||||
break;
|
||||
|
||||
case 'w':
|
||||
window_size_ = QString(optarg);
|
||||
window_size_ = OptArgToString(optarg);
|
||||
player_action_ = PlayerAction::ResizeWindow;
|
||||
break;
|
||||
|
||||
@@ -308,10 +361,10 @@ bool CommandlineOptions::Parse() {
|
||||
|
||||
// Get any filenames or URLs following the arguments
|
||||
for (int i = optind; i < argc_; ++i) {
|
||||
QString value = QFile::decodeName(argv_[i]);
|
||||
QFileInfo file_info(value);
|
||||
if (file_info.exists()) {
|
||||
urls_ << QUrl::fromLocalFile(file_info.canonicalFilePath());
|
||||
const QString value = DecodeName(argv_[i]);
|
||||
QFileInfo fileinfo(value);
|
||||
if (fileinfo.exists()) {
|
||||
urls_ << QUrl::fromLocalFile(fileinfo.canonicalFilePath());
|
||||
}
|
||||
else {
|
||||
urls_ << QUrl::fromUserInput(value);
|
||||
@@ -362,10 +415,30 @@ void CommandlineOptions::Load(const QByteArray &serialized) {
|
||||
|
||||
}
|
||||
|
||||
QString CommandlineOptions::tr(const char *source_text) {
|
||||
return QObject::tr(source_text); // clazy:exclude=tr-non-literal
|
||||
#ifdef Q_OS_WIN32
|
||||
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) {
|
||||
|
||||
s << static_cast<quint32>(a.player_action_)
|
||||
|
||||
@@ -23,12 +23,17 @@
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#include <QtGlobal>
|
||||
#include <QDataStream>
|
||||
#include <QByteArray>
|
||||
#include <QList>
|
||||
#include <QString>
|
||||
#include <QUrl>
|
||||
|
||||
#ifdef Q_OS_WIN32
|
||||
# include <windows.h>
|
||||
#endif
|
||||
|
||||
class CommandlineOptions {
|
||||
friend QDataStream &operator<<(QDataStream &s, const CommandlineOptions &a);
|
||||
friend QDataStream &operator>>(QDataStream &s, CommandlineOptions &a);
|
||||
@@ -100,12 +105,23 @@ class CommandlineOptions {
|
||||
RestartOrPrevious
|
||||
};
|
||||
|
||||
static QString tr(const char *source_text);
|
||||
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:
|
||||
int argc_;
|
||||
#ifdef Q_OS_WIN32
|
||||
LPWSTR *argv_;
|
||||
#else
|
||||
char **argv_;
|
||||
#endif
|
||||
|
||||
UrlListAction url_list_action_;
|
||||
PlayerAction player_action_;
|
||||
|
||||
@@ -48,7 +48,7 @@
|
||||
#include "scopedtransaction.h"
|
||||
|
||||
const char *Database::kDatabaseFilename = "strawberry.db";
|
||||
const int Database::kSchemaVersion = 15;
|
||||
const int Database::kSchemaVersion = 17;
|
||||
const int Database::kMinSupportedSchemaVersion = 10;
|
||||
const char *Database::kMagicAllSongsTables = "%allsongstables";
|
||||
|
||||
@@ -91,7 +91,7 @@ Database::~Database() {
|
||||
}
|
||||
|
||||
void Database::ExitAsync() {
|
||||
QMetaObject::invokeMethod(this, "Exit", Qt::QueuedConnection);
|
||||
QMetaObject::invokeMethod(this, &Database::Exit, Qt::QueuedConnection);
|
||||
}
|
||||
|
||||
void Database::Exit() {
|
||||
@@ -472,12 +472,10 @@ void Database::ReportErrors(const SqlQuery &query) {
|
||||
|
||||
const QSqlError sql_error = query.lastError();
|
||||
if (sql_error.isValid()) {
|
||||
qLog(Error) << "Unable to execute SQL query: " << sql_error;
|
||||
qLog(Error) << "Failed query: " << query.LastQuery();
|
||||
QString error;
|
||||
error += "Unable to execute SQL query: " + sql_error.text() + "<br />";
|
||||
error += "Failed query: " + query.LastQuery();
|
||||
emit Error(error);
|
||||
qLog(Error) << "Unable to execute SQL query:" << sql_error;
|
||||
qLog(Error) << "Failed SQL query:" << query.LastQuery();
|
||||
emit Error(tr("Unable to execute SQL query: %1").arg(sql_error.text()));
|
||||
emit Error(tr("Failed SQL query: %1").arg(query.LastQuery()));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -488,11 +486,11 @@ bool Database::IntegrityCheck(const QSqlDatabase &db) {
|
||||
const int task_id = app_->task_manager()->StartTask(tr("Integrity check"));
|
||||
|
||||
bool ok = false;
|
||||
bool error_reported = false;
|
||||
// Ask for 10 error messages at most.
|
||||
SqlQuery q(db);
|
||||
q.prepare("PRAGMA integrity_check(10)");
|
||||
if (q.Exec()) {
|
||||
bool error_reported = false;
|
||||
while (q.next()) {
|
||||
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