Compare commits
244 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
782921f6e0 | ||
|
|
0694aabb45 | ||
|
|
604b35dfde | ||
|
|
4c308bf5d0 | ||
|
|
7a53ca7f8e | ||
|
|
7c51f04140 | ||
|
|
488b326e0f | ||
|
|
1e0c0a35ba | ||
|
|
bf044c1ccc | ||
|
|
c9caa7f034 | ||
|
|
1b8966b3a4 | ||
|
|
01d4d404c0 | ||
|
|
354bbf820f | ||
|
|
7620f9ebbe | ||
|
|
9c2c668a04 | ||
|
|
b6f5a5712b | ||
|
|
9e9df6eb16 | ||
|
|
f510c993a6 | ||
|
|
8ddcbaac27 | ||
|
|
3fcab72900 | ||
|
|
70aa2233e2 | ||
|
|
8123a2924d | ||
|
|
dd3c308b09 | ||
|
|
e56ca2ffd6 | ||
|
|
57c98c363c | ||
|
|
2211508061 | ||
|
|
848e41919d | ||
|
|
dac613a8bb | ||
|
|
a1735278df | ||
|
|
3730bd04a4 | ||
|
|
eee3445d2f | ||
|
|
8bf473dc3a | ||
|
|
e106dda794 | ||
|
|
fc35d2a35c | ||
|
|
aef9ff0d38 | ||
|
|
192fb46d1d | ||
|
|
1dd4eb05f2 | ||
|
|
79f0c494fa | ||
|
|
7caeb47637 | ||
|
|
ffef339ebd | ||
|
|
817153b20b | ||
|
|
686eb2e786 | ||
|
|
53d5192477 | ||
|
|
a172f5fe85 | ||
|
|
288408795b | ||
|
|
4b5a811b08 | ||
|
|
c1259d8b6e | ||
|
|
3acbe431f7 | ||
|
|
40f381257d | ||
|
|
beec983f21 | ||
|
|
d20750012e | ||
|
|
e31c9d74fa | ||
|
|
78adc388df | ||
|
|
609413cda4 | ||
|
|
63e5d6a94a | ||
|
|
fd5970b647 | ||
|
|
af38b8f92b | ||
|
|
e676e65f9e | ||
|
|
4c479301ff | ||
|
|
98178947ae | ||
|
|
0dbf3b462b | ||
|
|
cd9f8b569b | ||
|
|
6b23728efa | ||
|
|
ff0f7ee483 | ||
|
|
74498c3ac9 | ||
|
|
7a61e740e8 | ||
|
|
81e6b55c39 | ||
|
|
e439ac0e0e | ||
|
|
7b9d784a64 | ||
|
|
56b2630a1c | ||
|
|
6d5597a732 | ||
|
|
c0a9345358 | ||
|
|
2e4ad81fff | ||
|
|
1aad85a4f9 | ||
|
|
fbd2993239 | ||
|
|
203228bd05 | ||
|
|
da7edebe90 | ||
|
|
c7444a189e | ||
|
|
29235c5fa6 | ||
|
|
bf8374ff9f | ||
|
|
ba05e85e48 | ||
|
|
a9aab0702c | ||
|
|
b5cf83d501 | ||
|
|
1cc2e6303a | ||
|
|
4509a07fff | ||
|
|
ea419f6d40 | ||
|
|
c9b6707468 | ||
|
|
cf512a35a7 | ||
|
|
bdd3e5343d | ||
|
|
80fd8cd338 | ||
|
|
eb48592083 | ||
|
|
a45cb25002 | ||
|
|
21ec116769 | ||
|
|
1d4775a94b | ||
|
|
4cde9a7e9e | ||
|
|
709ba72777 | ||
|
|
0e56b8c575 | ||
|
|
558a087e37 | ||
|
|
7d96fe8b65 | ||
|
|
2f3e8986ab | ||
|
|
df359ba0fa | ||
|
|
31fa60884f | ||
|
|
15be227920 | ||
|
|
fdedfd54c7 | ||
|
|
7d55eb4b44 | ||
|
|
61ac7ae31e | ||
|
|
353e141036 | ||
|
|
cb4332842f | ||
|
|
004d219c97 | ||
|
|
94561e6815 | ||
|
|
ce3af4961b | ||
|
|
bbd81e7d9c | ||
|
|
8b1d198efd | ||
|
|
7a3bafc961 | ||
|
|
512eed9b04 | ||
|
|
38adf640e9 | ||
|
|
f4d8d73970 | ||
|
|
5a50285dee | ||
|
|
ca4ea0719f | ||
|
|
4c6251bf28 | ||
|
|
22c12c7366 | ||
|
|
8f49d1591f | ||
|
|
a97dbab2ae | ||
|
|
4cb8261d3b | ||
|
|
059e2d740f | ||
|
|
587d1c9d74 | ||
|
|
ab56c96170 | ||
|
|
efa4270429 | ||
|
|
0fbe5d888a | ||
|
|
b68f81179e | ||
|
|
6a8c1af5f9 | ||
|
|
98f287559b | ||
|
|
1a53d85f6a | ||
|
|
05b168aa04 | ||
|
|
a68d856074 | ||
|
|
d5dac9c6cc | ||
|
|
e9f7f42c03 | ||
|
|
7b8265d4a3 | ||
|
|
5715f4c2cb | ||
|
|
350debb66c | ||
|
|
267cd3660b | ||
|
|
bc1c0c3c6d | ||
|
|
c420f69da8 | ||
|
|
2151d96303 | ||
|
|
c48c65d0ce | ||
|
|
1c0b706b94 | ||
|
|
2dca3d6f5a | ||
|
|
0992636f8c | ||
|
|
86d3f2b4ed | ||
|
|
b78b4038f6 | ||
|
|
84c14349dc | ||
|
|
9916e34006 | ||
|
|
a729b42e5d | ||
|
|
01f8129ed0 | ||
|
|
fd85763fb4 | ||
|
|
c278ffe2cb | ||
|
|
902c76a781 | ||
|
|
4daed0070a | ||
|
|
a4d055b3ac | ||
|
|
e6ae6c6f04 | ||
|
|
de54ceeb40 | ||
|
|
77fe00df30 | ||
|
|
4ae3e63dea | ||
|
|
3329839dbe | ||
|
|
ca10920bb5 | ||
|
|
b41957ce5c | ||
|
|
3db6699018 | ||
|
|
5284ca90ef | ||
|
|
628cdff4fa | ||
|
|
af0c6f9233 | ||
|
|
7697bbfa4e | ||
|
|
642a455a9c | ||
|
|
6abfa2859b | ||
|
|
20c2225d8f | ||
|
|
5e7759b697 | ||
|
|
1ce8d2b31d | ||
|
|
9fbecb5af6 | ||
|
|
1223469be9 | ||
|
|
5eae3ddd8a | ||
|
|
3d0b6e6ea1 | ||
|
|
c6c53548ac | ||
|
|
3efe2d1e05 | ||
|
|
7c87b53517 | ||
|
|
a461c97bcf | ||
|
|
67a6d6c1e3 | ||
|
|
8b8e427a2b | ||
|
|
7d5c263ab2 | ||
|
|
79ac53b2d9 | ||
|
|
a704412dee | ||
|
|
24e2338769 | ||
|
|
bc240f82ef | ||
|
|
a2ad68406d | ||
|
|
3d807d2331 | ||
|
|
e6a7b484ba | ||
|
|
a62371829f | ||
|
|
fb98336713 | ||
|
|
ebadb4db0a | ||
|
|
9a480eb710 | ||
|
|
03ecde8b83 | ||
|
|
0291b78bfa | ||
|
|
377e6fad25 | ||
|
|
e75c955a68 | ||
|
|
6df356327d | ||
|
|
7a57586f90 | ||
|
|
1e2bad270d | ||
|
|
688d983b25 | ||
|
|
fa834a76ef | ||
|
|
5cb88efc38 | ||
|
|
6b64c43851 | ||
|
|
704e6c5448 | ||
|
|
072d7379df | ||
|
|
df1b756a43 | ||
|
|
30269d9d95 | ||
|
|
d29a1de980 | ||
|
|
57f5ccff81 | ||
|
|
69374bfa11 | ||
|
|
d9fd330216 | ||
|
|
312f62d98f | ||
|
|
0d408055b2 | ||
|
|
496ae42d72 | ||
|
|
3ab86543ad | ||
|
|
ce7926cfa4 | ||
|
|
48c81b188c | ||
|
|
21b241bbbe | ||
|
|
934d6fc267 | ||
|
|
a0ce2daa2e | ||
|
|
006d77239b | ||
|
|
303d31bde7 | ||
|
|
737fbeccde | ||
|
|
ec17dc9830 | ||
|
|
0f710ea3be | ||
|
|
bd4eec4527 | ||
|
|
c78252e0d5 | ||
|
|
b1f70982bf | ||
|
|
6128fb4f19 | ||
|
|
ee915254e7 | ||
|
|
d835d4aae6 | ||
|
|
5945d0ebee | ||
|
|
3d3aacdcb1 | ||
|
|
c3ce6cff72 | ||
|
|
6d7a01fb4e | ||
|
|
8582e09e73 | ||
|
|
90744f2965 | ||
|
|
03d0776fdc |
1179
.github/workflows/ccpp.yml
vendored
1179
.github/workflows/ccpp.yml
vendored
File diff suppressed because it is too large
Load Diff
5
.gitignore
vendored
5
.gitignore
vendored
@@ -119,3 +119,8 @@ stage/
|
||||
*.snap
|
||||
/snap/.snapcraft/
|
||||
/*_source.tar.bz2
|
||||
|
||||
# MSVC
|
||||
CMakeSettings.json
|
||||
/.vs/
|
||||
/out/
|
||||
|
||||
5
3rdparty/README.md
vendored
5
3rdparty/README.md
vendored
@@ -23,3 +23,8 @@ macdeployqt
|
||||
A modified version of Qt's official macdeployqt utility that fixes some issues,
|
||||
this version also deploys gstreamer plugins.
|
||||
Can safely be deleted on other platforms.
|
||||
|
||||
|
||||
getopt
|
||||
------
|
||||
getopt included only when compiling with MSVC on Windows.
|
||||
|
||||
2
3rdparty/getopt/CMakeLists.txt
vendored
Normal file
2
3rdparty/getopt/CMakeLists.txt
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
add_library(getopt STATIC getopt.c)
|
||||
target_include_directories(getopt PRIVATE ${CMAKE_CURRENT_SOURCE_DIR})
|
||||
562
3rdparty/getopt/getopt.c
vendored
Normal file
562
3rdparty/getopt/getopt.c
vendored
Normal file
@@ -0,0 +1,562 @@
|
||||
/* $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));
|
||||
}
|
||||
95
3rdparty/getopt/getopt.h
vendored
Normal file
95
3rdparty/getopt/getopt.h
vendored
Normal file
@@ -0,0 +1,95 @@
|
||||
#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__) */
|
||||
2
3rdparty/macdeployqt/main.cpp
vendored
2
3rdparty/macdeployqt/main.cpp
vendored
@@ -87,7 +87,7 @@ int main(int argc, char **argv)
|
||||
|
||||
appBundlePath = QDir::cleanPath(appBundlePath);
|
||||
|
||||
if (QDir().exists(appBundlePath) == false) {
|
||||
if (!QDir(appBundlePath).exists()) {
|
||||
qDebug() << "Error: Could not find app bundle" << appBundlePath;
|
||||
return 1;
|
||||
}
|
||||
|
||||
12
3rdparty/macdeployqt/shared.cpp
vendored
12
3rdparty/macdeployqt/shared.cpp
vendored
@@ -104,7 +104,7 @@ inline QDebug operator<<(QDebug debug, const ApplicationBundleInfo &info)
|
||||
|
||||
bool copyFilePrintStatus(const QString &from, const QString &to)
|
||||
{
|
||||
if (QFile(to).exists()) {
|
||||
if (QFile::exists(to)) {
|
||||
if (alwaysOwerwriteEnabled) {
|
||||
QFile(to).remove();
|
||||
} else {
|
||||
@@ -141,7 +141,7 @@ bool copyFilePrintStatus(const QString &from, const QString &to)
|
||||
|
||||
bool linkFilePrintStatus(const QString &file, const QString &link)
|
||||
{
|
||||
if (QFile(link).exists()) {
|
||||
if (QFile::exists(link)) {
|
||||
if (QFile(link).symLinkTarget().isEmpty())
|
||||
LogError() << link << "exists but it's a file.";
|
||||
else
|
||||
@@ -754,7 +754,7 @@ QString copyDylib(const FrameworkInfo &framework, const QString path)
|
||||
}
|
||||
|
||||
// Return if the dylib has already been deployed
|
||||
if (QFileInfo(dylibDestinationBinaryPath).exists() && !alwaysOwerwriteEnabled)
|
||||
if (QFileInfo::exists(dylibDestinationBinaryPath) && !alwaysOwerwriteEnabled)
|
||||
return dylibDestinationBinaryPath;
|
||||
|
||||
// Copy dylib binary
|
||||
@@ -821,7 +821,7 @@ QString copyFramework(const FrameworkInfo &framework, const QString path)
|
||||
// Contents/Info.plist should be Versions/5/Resources/Info.plist
|
||||
const QString legacyInfoPlistPath = framework.frameworkPath + "/Contents/Info.plist";
|
||||
const QString correctInfoPlistPath = frameworkDestinationDirectory + "/Resources/Info.plist";
|
||||
if (QFile(legacyInfoPlistPath).exists()) {
|
||||
if (QFile::exists(legacyInfoPlistPath)) {
|
||||
copyFilePrintStatus(legacyInfoPlistPath, correctInfoPlistPath);
|
||||
patch_debugInInfoPlist(correctInfoPlistPath);
|
||||
}
|
||||
@@ -1443,11 +1443,11 @@ bool deployQmlImports(const QString &appBundlePath, DeploymentInfo deploymentInf
|
||||
#endif
|
||||
|
||||
// Fallback: Look relative to the macdeployqt binary
|
||||
if (!QFile(qmlImportScannerPath).exists())
|
||||
if (!QFile::exists(qmlImportScannerPath))
|
||||
qmlImportScannerPath = QCoreApplication::applicationDirPath() + "/qmlimportscanner";
|
||||
|
||||
// Verify that we found a qmlimportscanner binary
|
||||
if (!QFile(qmlImportScannerPath).exists()) {
|
||||
if (!QFile::exists(qmlImportScannerPath)) {
|
||||
LogError() << "qmlimportscanner not found at" << qmlImportScannerPath;
|
||||
LogError() << "Rebuild qtdeclarative/tools/qmlimportscanner";
|
||||
return false;
|
||||
|
||||
6
3rdparty/singleapplication/CMakeLists.txt
vendored
6
3rdparty/singleapplication/CMakeLists.txt
vendored
@@ -3,10 +3,8 @@ cmake_minimum_required(VERSION 3.0)
|
||||
include(CheckIncludeFiles)
|
||||
include(CheckFunctionExists)
|
||||
|
||||
if(CMAKE_VERSION VERSION_GREATER 3.0)
|
||||
check_function_exists(geteuid HAVE_GETEUID)
|
||||
check_function_exists(getpwuid HAVE_GETPWUID)
|
||||
endif()
|
||||
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)
|
||||
|
||||
2
3rdparty/singleapplication/LICENSE
vendored
2
3rdparty/singleapplication/LICENSE
vendored
@@ -1,6 +1,6 @@
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) Itay Grudev 2015 - 2016
|
||||
Copyright (c) Itay Grudev 2015 - 2020
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
|
||||
86
3rdparty/singleapplication/README.md
vendored
86
3rdparty/singleapplication/README.md
vendored
@@ -1,7 +1,8 @@
|
||||
SingleApplication
|
||||
=================
|
||||
[](https://github.com/itay-grudev/SingleApplication/actions)
|
||||
|
||||
This is a replacement of the QtSingleApplication for `Qt5`.
|
||||
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)
|
||||
@@ -15,18 +16,6 @@ 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.
|
||||
|
||||
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.
|
||||
|
||||
You can use the library as if you use any other `QCoreApplication` derived
|
||||
class:
|
||||
|
||||
@@ -43,24 +32,49 @@ int main( int argc, char* argv[] )
|
||||
```
|
||||
|
||||
To include the library files I would recommend that you add it as a git
|
||||
submodule to your project and include it's contents with a `.pri` file. Here is
|
||||
how:
|
||||
submodule to your project. Here is how:
|
||||
|
||||
```bash
|
||||
git submodule add git@github.com:itay-grudev/SingleApplication.git singleapplication
|
||||
git submodule add https://github.com/itay-grudev/SingleApplication.git singleapplication
|
||||
```
|
||||
|
||||
Then include the `singleapplication.pri` file in your `.pro` project file. Also
|
||||
don't forget to specify which `QCoreApplication` class your app is using if it
|
||||
is not `QCoreApplication`.
|
||||
**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
|
||||
@@ -125,13 +139,22 @@ 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 )
|
||||
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
|
||||
@@ -140,7 +163,7 @@ 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.
|
||||
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.*
|
||||
@@ -159,7 +182,8 @@ 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
|
||||
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`.
|
||||
|
||||
---
|
||||
|
||||
@@ -192,6 +216,22 @@ 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
|
||||
|
||||
44
3rdparty/singleapplication/singleapplication.cpp
vendored
44
3rdparty/singleapplication/singleapplication.cpp
vendored
@@ -54,7 +54,7 @@
|
||||
* @param options Optional flags to toggle specific behaviour
|
||||
* @param timeout Maximum time blocking functions are allowed during app load
|
||||
*/
|
||||
SingleApplication::SingleApplication(int &argc, char *argv[], bool allowSecondary, Options options, int timeout)
|
||||
SingleApplication::SingleApplication(int &argc, char *argv[], const bool allowSecondary, const Options options, const int timeout)
|
||||
: app_t(argc, argv),
|
||||
d_ptr(new SingleApplicationPrivate(this)) {
|
||||
|
||||
@@ -67,7 +67,7 @@ SingleApplication::SingleApplication(int &argc, char *argv[], bool allowSecondar
|
||||
d->genBlockServerName();
|
||||
|
||||
// To mitigate QSharedMemory issues with large amount of processes attempting to attach at the same time
|
||||
d->randomSleep();
|
||||
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.
|
||||
@@ -106,14 +106,14 @@ SingleApplication::SingleApplication(int &argc, char *argv[], bool allowSecondar
|
||||
}
|
||||
}
|
||||
|
||||
InstancesInfo *inst = static_cast<InstancesInfo*>(d->memory_->data());
|
||||
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() == inst->checksum) break;
|
||||
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) {
|
||||
@@ -127,14 +127,14 @@ SingleApplication::SingleApplication(int &argc, char *argv[], bool allowSecondar
|
||||
qDebug() << "SingleApplication: Unable to unlock memory for random wait.";
|
||||
qDebug() << d->memory_->errorString();
|
||||
}
|
||||
d->randomSleep();
|
||||
SingleApplicationPrivate::randomSleep();
|
||||
if (!d->memory_->lock()) {
|
||||
qCritical() << "SingleApplication: Unable to lock memory after random wait.";
|
||||
abortSafely();
|
||||
}
|
||||
}
|
||||
|
||||
if (!inst->primary) {
|
||||
if (!instance->primary) {
|
||||
d->startPrimary();
|
||||
if (!d->memory_->unlock()) {
|
||||
qDebug() << "SingleApplication: Unable to unlock memory after primary start.";
|
||||
@@ -178,8 +178,8 @@ SingleApplication::~SingleApplication() {
|
||||
* Checks if the current application instance is primary.
|
||||
* @return Returns true if the instance is primary, false otherwise.
|
||||
*/
|
||||
bool SingleApplication::isPrimary() {
|
||||
Q_D(SingleApplication);
|
||||
bool SingleApplication::isPrimary() const {
|
||||
Q_D(const SingleApplication);
|
||||
return d->server_ != nullptr;
|
||||
}
|
||||
|
||||
@@ -187,8 +187,8 @@ bool SingleApplication::isPrimary() {
|
||||
* Checks if the current application instance is secondary.
|
||||
* @return Returns true if the instance is secondary, false otherwise.
|
||||
*/
|
||||
bool SingleApplication::isSecondary() {
|
||||
Q_D(SingleApplication);
|
||||
bool SingleApplication::isSecondary() const {
|
||||
Q_D(const SingleApplication);
|
||||
return d->server_ == nullptr;
|
||||
}
|
||||
|
||||
@@ -197,8 +197,8 @@ bool SingleApplication::isSecondary() {
|
||||
* 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() {
|
||||
Q_D(SingleApplication);
|
||||
quint32 SingleApplication::instanceId() const {
|
||||
Q_D(const SingleApplication);
|
||||
return d->instanceNumber_;
|
||||
}
|
||||
|
||||
@@ -207,8 +207,8 @@ quint32 SingleApplication::instanceId() {
|
||||
* Especially useful when SingleApplication is coupled with OS. specific APIs.
|
||||
* @return Returns the primary instance PID.
|
||||
*/
|
||||
qint64 SingleApplication::primaryPid() {
|
||||
Q_D(SingleApplication);
|
||||
qint64 SingleApplication::primaryPid() const {
|
||||
Q_D(const SingleApplication);
|
||||
return d->primaryPid();
|
||||
}
|
||||
|
||||
@@ -216,8 +216,8 @@ qint64 SingleApplication::primaryPid() {
|
||||
* Returns the username the primary instance is running as.
|
||||
* @return Returns the username the primary instance is running as.
|
||||
*/
|
||||
QString SingleApplication::primaryUser() {
|
||||
Q_D(SingleApplication);
|
||||
QString SingleApplication::primaryUser() const {
|
||||
Q_D(const SingleApplication);
|
||||
return d->primaryUser();
|
||||
}
|
||||
|
||||
@@ -225,9 +225,8 @@ QString SingleApplication::primaryUser() {
|
||||
* Returns the username the current instance is running as.
|
||||
* @return Returns the username the current instance is running as.
|
||||
*/
|
||||
QString SingleApplication::currentUser() {
|
||||
Q_D(SingleApplication);
|
||||
return d->getUsername();
|
||||
QString SingleApplication::currentUser() const {
|
||||
return SingleApplicationPrivate::getUsername();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -248,11 +247,7 @@ bool SingleApplication::sendMessage(const QByteArray &message, const int timeout
|
||||
return false;
|
||||
}
|
||||
|
||||
d->socket_->write(message);
|
||||
const bool dataWritten = d->socket_->waitForBytesWritten(timeout);
|
||||
d->socket_->flush();
|
||||
return dataWritten;
|
||||
|
||||
return d->writeConfirmedMessage(timeout, message);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -265,6 +260,7 @@ void SingleApplication::abortSafely() {
|
||||
|
||||
qCritical() << "SingleApplication: " << d->memory_->error() << d->memory_->errorString();
|
||||
delete d;
|
||||
|
||||
::exit(EXIT_FAILURE);
|
||||
|
||||
}
|
||||
|
||||
17
3rdparty/singleapplication/singleapplication.h
vendored
17
3rdparty/singleapplication/singleapplication.h
vendored
@@ -48,7 +48,7 @@ class SingleApplicationPrivate;
|
||||
class SingleApplication : public QApplication { // clazy:exclude=ctor-missing-parent-argument
|
||||
Q_OBJECT
|
||||
|
||||
typedef QApplication app_t;
|
||||
using app_t = QApplication;
|
||||
|
||||
public:
|
||||
/**
|
||||
@@ -88,46 +88,45 @@ class SingleApplication : public QApplication { // clazy:exclude=ctor-missing-p
|
||||
* 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.
|
||||
* @see See the corresponding QAPPLICATION_CLASS constructor for reference
|
||||
*/
|
||||
explicit SingleApplication(int &argc, char *argv[], bool allowSecondary = false, Options options = Mode::User, int timeout = 1000);
|
||||
explicit SingleApplication(int &argc, char *argv[], 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();
|
||||
bool isPrimary() const;
|
||||
|
||||
/**
|
||||
* @brief Returns if the instance is a secondary instance
|
||||
* @returns {bool}
|
||||
*/
|
||||
bool isSecondary();
|
||||
bool isSecondary() const;
|
||||
|
||||
/**
|
||||
* @brief Returns a unique identifier for the current instance
|
||||
* @returns {qint32}
|
||||
*/
|
||||
quint32 instanceId();
|
||||
quint32 instanceId() const;
|
||||
|
||||
/**
|
||||
* @brief Returns the process ID (PID) of the primary instance
|
||||
* @returns {qint64}
|
||||
*/
|
||||
qint64 primaryPid();
|
||||
qint64 primaryPid() const;
|
||||
|
||||
/**
|
||||
* @brief Returns the username of the user running the primary instance
|
||||
* @returns {QString}
|
||||
*/
|
||||
QString primaryUser();
|
||||
QString primaryUser() const;
|
||||
|
||||
/**
|
||||
* @brief Returns the username of the current user
|
||||
* @returns {QString}
|
||||
*/
|
||||
QString currentUser();
|
||||
QString currentUser() const;
|
||||
|
||||
/**
|
||||
* @brief Sends a message to the primary instance. Returns true on success.
|
||||
|
||||
191
3rdparty/singleapplication/singleapplication_p.cpp
vendored
191
3rdparty/singleapplication/singleapplication_p.cpp
vendored
@@ -44,6 +44,14 @@
|
||||
# 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>
|
||||
@@ -53,7 +61,6 @@
|
||||
#include <QCryptographicHash>
|
||||
#include <QLocalServer>
|
||||
#include <QLocalSocket>
|
||||
#include <QDir>
|
||||
#include <QElapsedTimer>
|
||||
#if QT_VERSION >= QT_VERSION_CHECK(5, 10, 0)
|
||||
# include <QRandomGenerator>
|
||||
@@ -64,11 +71,6 @@
|
||||
#include "singleapplication.h"
|
||||
#include "singleapplication_p.h"
|
||||
|
||||
#ifdef Q_OS_WIN
|
||||
# include <windows.h>
|
||||
# include <lmcons.h>
|
||||
#endif
|
||||
|
||||
SingleApplicationPrivate::SingleApplicationPrivate(SingleApplication *ptr)
|
||||
: q_ptr(ptr),
|
||||
memory_(nullptr),
|
||||
@@ -86,14 +88,14 @@ SingleApplicationPrivate::~SingleApplicationPrivate() {
|
||||
|
||||
if (memory_ != nullptr) {
|
||||
memory_->lock();
|
||||
InstancesInfo *inst = static_cast<InstancesInfo*>(memory_->data());
|
||||
InstancesInfo *instance = static_cast<InstancesInfo*>(memory_->data());
|
||||
if (server_ != nullptr) {
|
||||
server_->close();
|
||||
delete server_;
|
||||
inst->primary = false;
|
||||
inst->primaryPid = -1;
|
||||
inst->primaryUser[0] = '\0';
|
||||
inst->checksum = blockChecksum();
|
||||
instance->primary = false;
|
||||
instance->primaryPid = -1;
|
||||
instance->primaryUser[0] = '\0';
|
||||
instance->checksum = blockChecksum();
|
||||
}
|
||||
memory_->unlock();
|
||||
|
||||
@@ -142,7 +144,7 @@ QString SingleApplicationPrivate::getUsername() {
|
||||
void SingleApplicationPrivate::genBlockServerName() {
|
||||
|
||||
QCryptographicHash appData(QCryptographicHash::Sha256);
|
||||
appData.addData("SingleApplication", 17);
|
||||
appData.addData("SingleApplication");
|
||||
appData.addData(SingleApplication::app_t::applicationName().toUtf8());
|
||||
appData.addData(SingleApplication::app_t::organizationName().toUtf8());
|
||||
appData.addData(SingleApplication::app_t::organizationDomain().toUtf8());
|
||||
@@ -152,7 +154,15 @@ void SingleApplicationPrivate::genBlockServerName() {
|
||||
}
|
||||
|
||||
if (!(options_ & SingleApplication::Mode::ExcludeAppPath)) {
|
||||
#ifdef Q_OS_WIN
|
||||
#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());
|
||||
@@ -171,26 +181,24 @@ void SingleApplicationPrivate::genBlockServerName() {
|
||||
|
||||
void SingleApplicationPrivate::initializeMemoryBlock() const {
|
||||
|
||||
InstancesInfo *inst = static_cast<InstancesInfo*>(memory_->data());
|
||||
inst->primary = false;
|
||||
inst->secondary = 0;
|
||||
inst->primaryPid = -1;
|
||||
inst->primaryUser[0] = '\0';
|
||||
inst->checksum = blockChecksum();
|
||||
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() {
|
||||
|
||||
Q_Q(SingleApplication);
|
||||
|
||||
// Reset the number of connections
|
||||
InstancesInfo *inst = static_cast<InstancesInfo*>(memory_->data());
|
||||
InstancesInfo *instance = static_cast<InstancesInfo*>(memory_->data());
|
||||
|
||||
inst->primary = true;
|
||||
inst->primaryPid = q->applicationPid();
|
||||
qstrncpy(inst->primaryUser, getUsername().toUtf8().data(), sizeof(inst->primaryUser));
|
||||
inst->checksum = blockChecksum();
|
||||
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
|
||||
@@ -212,11 +220,11 @@ void SingleApplicationPrivate::startPrimary() {
|
||||
|
||||
void SingleApplicationPrivate::startSecondary() {
|
||||
|
||||
InstancesInfo *inst = static_cast<InstancesInfo*>(memory_->data());
|
||||
InstancesInfo *instance = static_cast<InstancesInfo*>(memory_->data());
|
||||
|
||||
inst->secondary += 1;
|
||||
inst->checksum = blockChecksum();
|
||||
instanceNumber_ = inst->secondary;
|
||||
instance->secondary += 1;
|
||||
instance->checksum = blockChecksum();
|
||||
instanceNumber_ = instance->secondary;
|
||||
|
||||
}
|
||||
|
||||
@@ -263,25 +271,53 @@ bool SingleApplicationPrivate::connectToPrimary(const int timeout, const Connect
|
||||
writeStream << instanceNumber_;
|
||||
|
||||
#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
|
||||
quint16 checksum = qChecksum(QByteArray(initMsg, static_cast<quint32>(initMsg.length())));
|
||||
quint16 checksum = qChecksum(QByteArray(initMsg, static_cast<quint32>(initMsg.length())));
|
||||
#else
|
||||
quint16 checksum = qChecksum(initMsg.constData(), static_cast<quint32>(initMsg.length()));
|
||||
#endif
|
||||
|
||||
writeStream << checksum;
|
||||
|
||||
// The header indicates the message length that follows
|
||||
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>(initMsg.length());
|
||||
headerStream << static_cast<quint64>(msg.length());
|
||||
|
||||
socket_->write(header);
|
||||
socket_->write(initMsg);
|
||||
bool result = socket_->waitForBytesWritten(static_cast<int>(timeout - time.elapsed()));
|
||||
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();
|
||||
|
||||
return result;
|
||||
bool result = socket_->waitForReadyRead(timeout);
|
||||
if (result) {
|
||||
socket_->read(1);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
|
||||
}
|
||||
|
||||
@@ -300,8 +336,8 @@ quint16 SingleApplicationPrivate::blockChecksum() const {
|
||||
qint64 SingleApplicationPrivate::primaryPid() const {
|
||||
|
||||
memory_->lock();
|
||||
InstancesInfo *inst = static_cast<InstancesInfo*>(memory_->data());
|
||||
qint64 pid = inst->primaryPid;
|
||||
InstancesInfo *instance = static_cast<InstancesInfo*>(memory_->data());
|
||||
qint64 pid = instance->primaryPid;
|
||||
memory_->unlock();
|
||||
|
||||
return pid;
|
||||
@@ -311,8 +347,8 @@ qint64 SingleApplicationPrivate::primaryPid() const {
|
||||
QString SingleApplicationPrivate::primaryUser() const {
|
||||
|
||||
memory_->lock();
|
||||
InstancesInfo *inst = static_cast<InstancesInfo*>(memory_->data());
|
||||
QByteArray username = inst->primaryUser;
|
||||
InstancesInfo *instance = static_cast<InstancesInfo*>(memory_->data());
|
||||
QByteArray username = instance->primaryUser;
|
||||
memory_->unlock();
|
||||
|
||||
return QString::fromUtf8(username);
|
||||
@@ -328,26 +364,30 @@ void SingleApplicationPrivate::slotConnectionEstablished() {
|
||||
connectionMap_.insert(nextConnSocket, ConnectionInfo());
|
||||
|
||||
QObject::connect(nextConnSocket, &QLocalSocket::aboutToClose, this, [nextConnSocket, this]() {
|
||||
const ConnectionInfo info = connectionMap_[nextConnSocket];
|
||||
const ConnectionInfo &info = connectionMap_[nextConnSocket];
|
||||
slotClientConnectionClosed(nextConnSocket, info.instanceId);
|
||||
});
|
||||
|
||||
QObject::connect(nextConnSocket, &QLocalSocket::disconnected, this, [nextConnSocket, this]() {
|
||||
QObject::connect(nextConnSocket, &QLocalSocket::disconnected, nextConnSocket, &QLocalSocket::deleteLater);
|
||||
|
||||
QObject::connect(nextConnSocket, &QLocalSocket::destroyed, this, [nextConnSocket, this]() {
|
||||
connectionMap_.remove(nextConnSocket);
|
||||
nextConnSocket->deleteLater();
|
||||
});
|
||||
|
||||
QObject::connect(nextConnSocket, &QLocalSocket::readyRead, this, [nextConnSocket, this]() {
|
||||
const ConnectionInfo info = connectionMap_[nextConnSocket];
|
||||
const ConnectionInfo &info = connectionMap_[nextConnSocket];
|
||||
switch (info.stage) {
|
||||
case StageHeader:
|
||||
readInitMessageHeader(nextConnSocket);
|
||||
case StageInitHeader:
|
||||
readMessageHeader(nextConnSocket, StageInitBody);
|
||||
break;
|
||||
case StageBody:
|
||||
case StageInitBody:
|
||||
readInitMessageBody(nextConnSocket);
|
||||
break;
|
||||
case StageConnected:
|
||||
slotDataAvailable(nextConnSocket, info.instanceId);
|
||||
case StageConnectedHeader:
|
||||
readMessageHeader(nextConnSocket, StageConnectedBody);
|
||||
break;
|
||||
case StageConnectedBody:
|
||||
this->slotDataAvailable(nextConnSocket, info.instanceId);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
@@ -356,7 +396,7 @@ void SingleApplicationPrivate::slotConnectionEstablished() {
|
||||
|
||||
}
|
||||
|
||||
void SingleApplicationPrivate::readInitMessageHeader(QLocalSocket *sock) {
|
||||
void SingleApplicationPrivate::readMessageHeader(QLocalSocket *sock, const SingleApplicationPrivate::ConnectionStage nextStage) {
|
||||
|
||||
if (!connectionMap_.contains(sock)) {
|
||||
return;
|
||||
@@ -373,30 +413,34 @@ void SingleApplicationPrivate::readInitMessageHeader(QLocalSocket *sock) {
|
||||
quint64 msgLen = 0;
|
||||
headerStream >> msgLen;
|
||||
ConnectionInfo &info = connectionMap_[sock];
|
||||
info.stage = StageBody;
|
||||
info.stage = nextStage;
|
||||
info.msgLen = msgLen;
|
||||
|
||||
if (sock->bytesAvailable() >= static_cast<qint64>(msgLen)) {
|
||||
readInitMessageBody(sock);
|
||||
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 (!connectionMap_.contains(sock)) {
|
||||
return;
|
||||
}
|
||||
|
||||
ConnectionInfo &info = connectionMap_[sock];
|
||||
if (sock->bytesAvailable() < static_cast<qint64>(info.msgLen)) {
|
||||
if (!isFrameComplete(sock)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Read the message body
|
||||
QByteArray msgBytes = sock->read(info.msgLen);
|
||||
QByteArray msgBytes = sock->readAll();
|
||||
QDataStream readStream(msgBytes);
|
||||
readStream.setVersion(QDataStream::Qt_5_8);
|
||||
|
||||
@@ -431,23 +475,34 @@ void SingleApplicationPrivate::readInitMessageBody(QLocalSocket *sock) {
|
||||
return;
|
||||
}
|
||||
|
||||
ConnectionInfo &info = connectionMap_[sock];
|
||||
info.instanceId = instanceId;
|
||||
info.stage = StageConnected;
|
||||
info.stage = StageConnectedHeader;
|
||||
|
||||
if (connectionType == NewInstance || (connectionType == SecondaryInstance && options_ & SingleApplication::Mode::SecondaryNotification)) {
|
||||
Q_EMIT q->instanceStarted();
|
||||
emit q->instanceStarted();
|
||||
}
|
||||
|
||||
if (sock->bytesAvailable() > 0) {
|
||||
slotDataAvailable(sock, instanceId);
|
||||
}
|
||||
writeAck(sock);
|
||||
|
||||
}
|
||||
|
||||
void SingleApplicationPrivate::slotDataAvailable(QLocalSocket *dataSocket, const quint32 instanceId) {
|
||||
|
||||
Q_Q(SingleApplication);
|
||||
Q_EMIT q->receivedMessage(instanceId, dataSocket->readAll());
|
||||
|
||||
if (!isFrameComplete(dataSocket)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const QByteArray message = dataSocket->readAll();
|
||||
|
||||
writeAck(dataSocket);
|
||||
|
||||
ConnectionInfo &info = connectionMap_[dataSocket];
|
||||
info.stage = StageConnectedHeader;
|
||||
|
||||
emit q->receivedMessage(instanceId, message);
|
||||
|
||||
}
|
||||
|
||||
@@ -465,7 +520,7 @@ void SingleApplicationPrivate::randomSleep() {
|
||||
QThread::msleep(QRandomGenerator::global()->bounded(8U, 18U));
|
||||
#else
|
||||
qsrand(QDateTime::currentMSecsSinceEpoch() % std::numeric_limits<uint>::max());
|
||||
QThread::msleep(8 + static_cast<unsigned long>(static_cast<float>(qrand()) / RAND_MAX * 10));
|
||||
QThread::msleep(qrand() % 11 + 8);
|
||||
#endif
|
||||
|
||||
}
|
||||
|
||||
13
3rdparty/singleapplication/singleapplication_p.h
vendored
13
3rdparty/singleapplication/singleapplication_p.h
vendored
@@ -71,9 +71,10 @@ class SingleApplicationPrivate : public QObject {
|
||||
Reconnect = 3
|
||||
};
|
||||
enum ConnectionStage : quint8 {
|
||||
StageHeader = 0,
|
||||
StageBody = 1,
|
||||
StageConnected = 2,
|
||||
StageInitHeader = 0,
|
||||
StageInitBody = 1,
|
||||
StageConnectedHeader = 2,
|
||||
StageConnectedBody = 3,
|
||||
};
|
||||
Q_DECLARE_PUBLIC(SingleApplication)
|
||||
|
||||
@@ -89,8 +90,12 @@ class SingleApplicationPrivate : public QObject {
|
||||
quint16 blockChecksum() const;
|
||||
qint64 primaryPid() const;
|
||||
QString primaryUser() const;
|
||||
void readInitMessageHeader(QLocalSocket *socket);
|
||||
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;
|
||||
|
||||
@@ -54,7 +54,7 @@
|
||||
* @param options Optional flags to toggle specific behaviour
|
||||
* @param timeout Maximum time blocking functions are allowed during app load
|
||||
*/
|
||||
SingleCoreApplication::SingleCoreApplication(int &argc, char *argv[], bool allowSecondary, Options options, int timeout)
|
||||
SingleCoreApplication::SingleCoreApplication(int &argc, char *argv[], const bool allowSecondary, const Options options, const int timeout)
|
||||
: app_t(argc, argv),
|
||||
d_ptr(new SingleCoreApplicationPrivate(this)) {
|
||||
|
||||
@@ -67,7 +67,7 @@ SingleCoreApplication::SingleCoreApplication(int &argc, char *argv[], bool allow
|
||||
d->genBlockServerName();
|
||||
|
||||
// To mitigate QSharedMemory issues with large amount of processes attempting to attach at the same time
|
||||
d->randomSleep();
|
||||
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.
|
||||
@@ -106,14 +106,14 @@ SingleCoreApplication::SingleCoreApplication(int &argc, char *argv[], bool allow
|
||||
}
|
||||
}
|
||||
|
||||
InstancesInfo *inst = static_cast<InstancesInfo*>(d->memory_->data());
|
||||
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() == inst->checksum) break;
|
||||
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) {
|
||||
@@ -127,14 +127,14 @@ SingleCoreApplication::SingleCoreApplication(int &argc, char *argv[], bool allow
|
||||
qDebug() << "SingleCoreApplication: Unable to unlock memory for random wait.";
|
||||
qDebug() << d->memory_->errorString();
|
||||
}
|
||||
d->randomSleep();
|
||||
SingleCoreApplicationPrivate::randomSleep();
|
||||
if (!d->memory_->lock()) {
|
||||
qCritical() << "SingleCoreApplication: Unable to lock memory after random wait.";
|
||||
abortSafely();
|
||||
}
|
||||
}
|
||||
|
||||
if (!inst->primary) {
|
||||
if (!instance->primary) {
|
||||
d->startPrimary();
|
||||
if (!d->memory_->unlock()) {
|
||||
qDebug() << "SingleCoreApplication: Unable to unlock memory after primary start.";
|
||||
@@ -178,8 +178,8 @@ SingleCoreApplication::~SingleCoreApplication() {
|
||||
* Checks if the current application instance is primary.
|
||||
* @return Returns true if the instance is primary, false otherwise.
|
||||
*/
|
||||
bool SingleCoreApplication::isPrimary() {
|
||||
Q_D(SingleCoreApplication);
|
||||
bool SingleCoreApplication::isPrimary() const {
|
||||
Q_D(const SingleCoreApplication);
|
||||
return d->server_ != nullptr;
|
||||
}
|
||||
|
||||
@@ -187,8 +187,8 @@ bool SingleCoreApplication::isPrimary() {
|
||||
* Checks if the current application instance is secondary.
|
||||
* @return Returns true if the instance is secondary, false otherwise.
|
||||
*/
|
||||
bool SingleCoreApplication::isSecondary() {
|
||||
Q_D(SingleCoreApplication);
|
||||
bool SingleCoreApplication::isSecondary() const {
|
||||
Q_D(const SingleCoreApplication);
|
||||
return d->server_ == nullptr;
|
||||
}
|
||||
|
||||
@@ -197,8 +197,8 @@ bool SingleCoreApplication::isSecondary() {
|
||||
* 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() {
|
||||
Q_D(SingleCoreApplication);
|
||||
quint32 SingleCoreApplication::instanceId() const {
|
||||
Q_D(const SingleCoreApplication);
|
||||
return d->instanceNumber_;
|
||||
}
|
||||
|
||||
@@ -207,8 +207,8 @@ quint32 SingleCoreApplication::instanceId() {
|
||||
* Especially useful when SingleCoreApplication is coupled with OS. specific APIs.
|
||||
* @return Returns the primary instance PID.
|
||||
*/
|
||||
qint64 SingleCoreApplication::primaryPid() {
|
||||
Q_D(SingleCoreApplication);
|
||||
qint64 SingleCoreApplication::primaryPid() const {
|
||||
Q_D(const SingleCoreApplication);
|
||||
return d->primaryPid();
|
||||
}
|
||||
|
||||
@@ -216,8 +216,8 @@ qint64 SingleCoreApplication::primaryPid() {
|
||||
* Returns the username the primary instance is running as.
|
||||
* @return Returns the username the primary instance is running as.
|
||||
*/
|
||||
QString SingleCoreApplication::primaryUser() {
|
||||
Q_D(SingleCoreApplication);
|
||||
QString SingleCoreApplication::primaryUser() const {
|
||||
Q_D(const SingleCoreApplication);
|
||||
return d->primaryUser();
|
||||
}
|
||||
|
||||
@@ -225,9 +225,8 @@ QString SingleCoreApplication::primaryUser() {
|
||||
* Returns the username the current instance is running as.
|
||||
* @return Returns the username the current instance is running as.
|
||||
*/
|
||||
QString SingleCoreApplication::currentUser() {
|
||||
Q_D(SingleCoreApplication);
|
||||
return d->getUsername();
|
||||
QString SingleCoreApplication::currentUser() const {
|
||||
return SingleCoreApplicationPrivate::getUsername();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -248,11 +247,7 @@ bool SingleCoreApplication::sendMessage(const QByteArray &message, const int tim
|
||||
return false;
|
||||
}
|
||||
|
||||
d->socket_->write(message);
|
||||
const bool dataWritten = d->socket_->waitForBytesWritten(timeout);
|
||||
d->socket_->flush();
|
||||
return dataWritten;
|
||||
|
||||
return d->writeConfirmedMessage(timeout, message);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -265,6 +260,7 @@ void SingleCoreApplication::abortSafely() {
|
||||
|
||||
qCritical() << "SingleCoreApplication: " << d->memory_->error() << d->memory_->errorString();
|
||||
delete d;
|
||||
|
||||
::exit(EXIT_FAILURE);
|
||||
|
||||
}
|
||||
|
||||
@@ -45,10 +45,10 @@ class SingleCoreApplicationPrivate;
|
||||
* @brief The SingleCoreApplication class handles multiple instances of the same Application
|
||||
* @see QCoreApplication
|
||||
*/
|
||||
class SingleCoreApplication : public QCoreApplication {
|
||||
class SingleCoreApplication : public QCoreApplication { // clazy:exclude=ctor-missing-parent-argument
|
||||
Q_OBJECT
|
||||
|
||||
typedef QCoreApplication app_t;
|
||||
using app_t = QCoreApplication;
|
||||
|
||||
public:
|
||||
/**
|
||||
@@ -96,37 +96,37 @@ class SingleCoreApplication : public QCoreApplication {
|
||||
* @brief Returns if the instance is the primary instance
|
||||
* @returns {bool}
|
||||
*/
|
||||
bool isPrimary();
|
||||
bool isPrimary() const;
|
||||
|
||||
/**
|
||||
* @brief Returns if the instance is a secondary instance
|
||||
* @returns {bool}
|
||||
*/
|
||||
bool isSecondary();
|
||||
bool isSecondary() const;
|
||||
|
||||
/**
|
||||
* @brief Returns a unique identifier for the current instance
|
||||
* @returns {qint32}
|
||||
*/
|
||||
quint32 instanceId();
|
||||
quint32 instanceId() const;
|
||||
|
||||
/**
|
||||
* @brief Returns the process ID (PID) of the primary instance
|
||||
* @returns {qint64}
|
||||
*/
|
||||
qint64 primaryPid();
|
||||
qint64 primaryPid() const;
|
||||
|
||||
/**
|
||||
* @brief Returns the username of the user running the primary instance
|
||||
* @returns {QString}
|
||||
*/
|
||||
QString primaryUser();
|
||||
QString primaryUser() const;
|
||||
|
||||
/**
|
||||
* @brief Returns the username of the current user
|
||||
* @returns {QString}
|
||||
*/
|
||||
QString currentUser();
|
||||
QString currentUser() const;
|
||||
|
||||
/**
|
||||
* @brief Sends a message to the primary instance. Returns true on success.
|
||||
|
||||
@@ -44,6 +44,14 @@
|
||||
# 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>
|
||||
@@ -53,7 +61,6 @@
|
||||
#include <QCryptographicHash>
|
||||
#include <QLocalServer>
|
||||
#include <QLocalSocket>
|
||||
#include <QDir>
|
||||
#include <QElapsedTimer>
|
||||
#if QT_VERSION >= QT_VERSION_CHECK(5, 10, 0)
|
||||
# include <QRandomGenerator>
|
||||
@@ -64,11 +71,6 @@
|
||||
#include "singlecoreapplication.h"
|
||||
#include "singlecoreapplication_p.h"
|
||||
|
||||
#ifdef Q_OS_WIN
|
||||
# include <windows.h>
|
||||
# include <lmcons.h>
|
||||
#endif
|
||||
|
||||
SingleCoreApplicationPrivate::SingleCoreApplicationPrivate(SingleCoreApplication *ptr)
|
||||
: q_ptr(ptr),
|
||||
memory_(nullptr),
|
||||
@@ -86,14 +88,14 @@ SingleCoreApplicationPrivate::~SingleCoreApplicationPrivate() {
|
||||
|
||||
if (memory_ != nullptr) {
|
||||
memory_->lock();
|
||||
InstancesInfo *inst = static_cast<InstancesInfo*>(memory_->data());
|
||||
InstancesInfo *instance = static_cast<InstancesInfo*>(memory_->data());
|
||||
if (server_ != nullptr) {
|
||||
server_->close();
|
||||
delete server_;
|
||||
inst->primary = false;
|
||||
inst->primaryPid = -1;
|
||||
inst->primaryUser[0] = '\0';
|
||||
inst->checksum = blockChecksum();
|
||||
instance->primary = false;
|
||||
instance->primaryPid = -1;
|
||||
instance->primaryUser[0] = '\0';
|
||||
instance->checksum = blockChecksum();
|
||||
}
|
||||
memory_->unlock();
|
||||
|
||||
@@ -142,7 +144,7 @@ QString SingleCoreApplicationPrivate::getUsername() {
|
||||
void SingleCoreApplicationPrivate::genBlockServerName() {
|
||||
|
||||
QCryptographicHash appData(QCryptographicHash::Sha256);
|
||||
appData.addData("SingleApplication", 17);
|
||||
appData.addData("SingleApplication");
|
||||
appData.addData(SingleCoreApplication::app_t::applicationName().toUtf8());
|
||||
appData.addData(SingleCoreApplication::app_t::organizationName().toUtf8());
|
||||
appData.addData(SingleCoreApplication::app_t::organizationDomain().toUtf8());
|
||||
@@ -152,7 +154,15 @@ void SingleCoreApplicationPrivate::genBlockServerName() {
|
||||
}
|
||||
|
||||
if (!(options_ & SingleCoreApplication::Mode::ExcludeAppPath)) {
|
||||
#ifdef Q_OS_WIN
|
||||
#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());
|
||||
@@ -171,26 +181,24 @@ void SingleCoreApplicationPrivate::genBlockServerName() {
|
||||
|
||||
void SingleCoreApplicationPrivate::initializeMemoryBlock() const {
|
||||
|
||||
InstancesInfo *inst = static_cast<InstancesInfo*>(memory_->data());
|
||||
inst->primary = false;
|
||||
inst->secondary = 0;
|
||||
inst->primaryPid = -1;
|
||||
inst->primaryUser[0] = '\0';
|
||||
inst->checksum = blockChecksum();
|
||||
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() {
|
||||
|
||||
Q_Q(SingleCoreApplication);
|
||||
|
||||
// Reset the number of connections
|
||||
InstancesInfo *inst = static_cast<InstancesInfo*>(memory_->data());
|
||||
InstancesInfo *instance = static_cast<InstancesInfo*>(memory_->data());
|
||||
|
||||
inst->primary = true;
|
||||
inst->primaryPid = q->applicationPid();
|
||||
qstrncpy(inst->primaryUser, getUsername().toUtf8().data(), sizeof(inst->primaryUser));
|
||||
inst->checksum = blockChecksum();
|
||||
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
|
||||
@@ -212,11 +220,11 @@ void SingleCoreApplicationPrivate::startPrimary() {
|
||||
|
||||
void SingleCoreApplicationPrivate::startSecondary() {
|
||||
|
||||
InstancesInfo *inst = static_cast<InstancesInfo*>(memory_->data());
|
||||
InstancesInfo *instance = static_cast<InstancesInfo*>(memory_->data());
|
||||
|
||||
inst->secondary += 1;
|
||||
inst->checksum = blockChecksum();
|
||||
instanceNumber_ = inst->secondary;
|
||||
instance->secondary += 1;
|
||||
instance->checksum = blockChecksum();
|
||||
instanceNumber_ = instance->secondary;
|
||||
|
||||
}
|
||||
|
||||
@@ -263,25 +271,53 @@ bool SingleCoreApplicationPrivate::connectToPrimary(const int timeout, const Con
|
||||
writeStream << instanceNumber_;
|
||||
|
||||
#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
|
||||
quint16 checksum = qChecksum(QByteArray(initMsg, static_cast<quint32>(initMsg.length())));
|
||||
quint16 checksum = qChecksum(QByteArray(initMsg, static_cast<quint32>(initMsg.length())));
|
||||
#else
|
||||
quint16 checksum = qChecksum(initMsg.constData(), static_cast<quint32>(initMsg.length()));
|
||||
#endif
|
||||
|
||||
writeStream << checksum;
|
||||
|
||||
// The header indicates the message length that follows
|
||||
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>(initMsg.length());
|
||||
headerStream << static_cast<quint64>(msg.length());
|
||||
|
||||
socket_->write(header);
|
||||
socket_->write(initMsg);
|
||||
bool result = socket_->waitForBytesWritten(timeout - static_cast<int>(time.elapsed()));
|
||||
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();
|
||||
|
||||
return result;
|
||||
bool result = socket_->waitForReadyRead(timeout);
|
||||
if (result) {
|
||||
socket_->read(1);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
|
||||
}
|
||||
|
||||
@@ -300,8 +336,8 @@ quint16 SingleCoreApplicationPrivate::blockChecksum() const {
|
||||
qint64 SingleCoreApplicationPrivate::primaryPid() const {
|
||||
|
||||
memory_->lock();
|
||||
InstancesInfo *inst = static_cast<InstancesInfo*>(memory_->data());
|
||||
qint64 pid = inst->primaryPid;
|
||||
InstancesInfo *instance = static_cast<InstancesInfo*>(memory_->data());
|
||||
qint64 pid = instance->primaryPid;
|
||||
memory_->unlock();
|
||||
|
||||
return pid;
|
||||
@@ -311,8 +347,8 @@ qint64 SingleCoreApplicationPrivate::primaryPid() const {
|
||||
QString SingleCoreApplicationPrivate::primaryUser() const {
|
||||
|
||||
memory_->lock();
|
||||
InstancesInfo *inst = static_cast<InstancesInfo*>(memory_->data());
|
||||
QByteArray username = inst->primaryUser;
|
||||
InstancesInfo *instance = static_cast<InstancesInfo*>(memory_->data());
|
||||
QByteArray username = instance->primaryUser;
|
||||
memory_->unlock();
|
||||
|
||||
return QString::fromUtf8(username);
|
||||
@@ -328,26 +364,30 @@ void SingleCoreApplicationPrivate::slotConnectionEstablished() {
|
||||
connectionMap_.insert(nextConnSocket, ConnectionInfo());
|
||||
|
||||
QObject::connect(nextConnSocket, &QLocalSocket::aboutToClose, this, [nextConnSocket, this]() {
|
||||
const ConnectionInfo info = connectionMap_[nextConnSocket];
|
||||
const ConnectionInfo &info = connectionMap_[nextConnSocket];
|
||||
slotClientConnectionClosed(nextConnSocket, info.instanceId);
|
||||
});
|
||||
|
||||
QObject::connect(nextConnSocket, &QLocalSocket::disconnected, this, [nextConnSocket, this]() {
|
||||
QObject::connect(nextConnSocket, &QLocalSocket::disconnected, nextConnSocket, &QLocalSocket::deleteLater);
|
||||
|
||||
QObject::connect(nextConnSocket, &QLocalSocket::destroyed, this, [nextConnSocket, this]() {
|
||||
connectionMap_.remove(nextConnSocket);
|
||||
nextConnSocket->deleteLater();
|
||||
});
|
||||
|
||||
QObject::connect(nextConnSocket, &QLocalSocket::readyRead, this, [nextConnSocket, this]() {
|
||||
const ConnectionInfo info = connectionMap_[nextConnSocket];
|
||||
const ConnectionInfo &info = connectionMap_[nextConnSocket];
|
||||
switch (info.stage) {
|
||||
case StageHeader:
|
||||
readInitMessageHeader(nextConnSocket);
|
||||
case StageInitHeader:
|
||||
readMessageHeader(nextConnSocket, StageInitBody);
|
||||
break;
|
||||
case StageBody:
|
||||
case StageInitBody:
|
||||
readInitMessageBody(nextConnSocket);
|
||||
break;
|
||||
case StageConnected:
|
||||
slotDataAvailable(nextConnSocket, info.instanceId);
|
||||
case StageConnectedHeader:
|
||||
readMessageHeader(nextConnSocket, StageConnectedBody);
|
||||
break;
|
||||
case StageConnectedBody:
|
||||
this->slotDataAvailable(nextConnSocket, info.instanceId);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
@@ -356,7 +396,7 @@ void SingleCoreApplicationPrivate::slotConnectionEstablished() {
|
||||
|
||||
}
|
||||
|
||||
void SingleCoreApplicationPrivate::readInitMessageHeader(QLocalSocket *sock) {
|
||||
void SingleCoreApplicationPrivate::readMessageHeader(QLocalSocket *sock, SingleCoreApplicationPrivate::ConnectionStage nextStage) {
|
||||
|
||||
if (!connectionMap_.contains(sock)) {
|
||||
return;
|
||||
@@ -373,30 +413,34 @@ void SingleCoreApplicationPrivate::readInitMessageHeader(QLocalSocket *sock) {
|
||||
quint64 msgLen = 0;
|
||||
headerStream >> msgLen;
|
||||
ConnectionInfo &info = connectionMap_[sock];
|
||||
info.stage = StageBody;
|
||||
info.stage = nextStage;
|
||||
info.msgLen = msgLen;
|
||||
|
||||
if (sock->bytesAvailable() >= static_cast<qint64>(msgLen)) {
|
||||
readInitMessageBody(sock);
|
||||
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 (!connectionMap_.contains(sock)) {
|
||||
return;
|
||||
}
|
||||
|
||||
ConnectionInfo &info = connectionMap_[sock];
|
||||
if (sock->bytesAvailable() < static_cast<qint64>(info.msgLen)) {
|
||||
if (!isFrameComplete(sock)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Read the message body
|
||||
QByteArray msgBytes = sock->read(info.msgLen);
|
||||
QByteArray msgBytes = sock->readAll();
|
||||
QDataStream readStream(msgBytes);
|
||||
readStream.setVersion(QDataStream::Qt_5_8);
|
||||
|
||||
@@ -431,23 +475,34 @@ void SingleCoreApplicationPrivate::readInitMessageBody(QLocalSocket *sock) {
|
||||
return;
|
||||
}
|
||||
|
||||
ConnectionInfo &info = connectionMap_[sock];
|
||||
info.instanceId = instanceId;
|
||||
info.stage = StageConnected;
|
||||
info.stage = StageConnectedHeader;
|
||||
|
||||
if (connectionType == NewInstance || (connectionType == SecondaryInstance && options_ & SingleCoreApplication::Mode::SecondaryNotification)) {
|
||||
Q_EMIT q->instanceStarted();
|
||||
emit q->instanceStarted();
|
||||
}
|
||||
|
||||
if (sock->bytesAvailable() > 0) {
|
||||
slotDataAvailable(sock, instanceId);
|
||||
}
|
||||
writeAck(sock);
|
||||
|
||||
}
|
||||
|
||||
void SingleCoreApplicationPrivate::slotDataAvailable(QLocalSocket *dataSocket, const quint32 instanceId) {
|
||||
|
||||
Q_Q(SingleCoreApplication);
|
||||
Q_EMIT q->receivedMessage(instanceId, dataSocket->readAll());
|
||||
|
||||
if (!isFrameComplete(dataSocket)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const QByteArray message = dataSocket->readAll();
|
||||
|
||||
writeAck(dataSocket);
|
||||
|
||||
ConnectionInfo &info = connectionMap_[dataSocket];
|
||||
info.stage = StageConnectedHeader;
|
||||
|
||||
emit q->receivedMessage(instanceId, message);
|
||||
|
||||
}
|
||||
|
||||
@@ -465,7 +520,7 @@ void SingleCoreApplicationPrivate::randomSleep() {
|
||||
QThread::msleep(QRandomGenerator::global()->bounded(8U, 18U));
|
||||
#else
|
||||
qsrand(QDateTime::currentMSecsSinceEpoch() % std::numeric_limits<uint>::max());
|
||||
QThread::msleep(8 + static_cast<unsigned long>(static_cast<float>(qrand()) / RAND_MAX * 10));
|
||||
QThread::msleep(qrand() % 11 + 8);
|
||||
#endif
|
||||
|
||||
}
|
||||
|
||||
@@ -55,7 +55,7 @@ struct InstancesInfo {
|
||||
|
||||
struct ConnectionInfo {
|
||||
explicit ConnectionInfo() : msgLen(0), instanceId(0), stage(0) {}
|
||||
qint64 msgLen;
|
||||
quint64 msgLen;
|
||||
quint32 instanceId;
|
||||
quint8 stage;
|
||||
};
|
||||
@@ -71,9 +71,10 @@ class SingleCoreApplicationPrivate : public QObject {
|
||||
Reconnect = 3
|
||||
};
|
||||
enum ConnectionStage : quint8 {
|
||||
StageHeader = 0,
|
||||
StageBody = 1,
|
||||
StageConnected = 2,
|
||||
StageInitHeader = 0,
|
||||
StageInitBody = 1,
|
||||
StageConnectedHeader = 2,
|
||||
StageConnectedBody = 3,
|
||||
};
|
||||
Q_DECLARE_PUBLIC(SingleCoreApplication)
|
||||
|
||||
@@ -89,8 +90,12 @@ class SingleCoreApplicationPrivate : public QObject {
|
||||
quint16 blockChecksum() const;
|
||||
qint64 primaryPid() const;
|
||||
QString primaryUser() const;
|
||||
void readInitMessageHeader(QLocalSocket *socket);
|
||||
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;
|
||||
|
||||
@@ -117,6 +117,9 @@ endif()
|
||||
pkg_check_modules(GLIB REQUIRED glib-2.0)
|
||||
pkg_check_modules(GOBJECT REQUIRED gobject-2.0)
|
||||
pkg_check_modules(GIO REQUIRED gio-2.0)
|
||||
if(UNIX)
|
||||
pkg_check_modules(GIO_UNIX gio-unix-2.0)
|
||||
endif()
|
||||
pkg_check_modules(LIBCDIO libcdio)
|
||||
pkg_check_modules(GSTREAMER gstreamer-1.0)
|
||||
pkg_check_modules(GSTREAMER_BASE gstreamer-base-1.0)
|
||||
@@ -155,7 +158,7 @@ if(DBUS_FOUND AND NOT WIN32)
|
||||
list(APPEND QT_COMPONENTS DBus)
|
||||
endif()
|
||||
set(QT_OPTIONAL_COMPONENTS Test)
|
||||
set(QT_MIN_VERSION 5.8)
|
||||
set(QT_MIN_VERSION 5.9)
|
||||
|
||||
if(BUILD_WITH_QT6 OR QT_VERSION_MAJOR EQUAL 6)
|
||||
set(QT_VERSION_MAJOR 6 CACHE STRING "" FORCE)
|
||||
@@ -292,7 +295,6 @@ set(SINGLEAPPLICATION_LIBRARIES singleapplication)
|
||||
set(SINGLECOREAPPLICATION_LIBRARIES singlecoreapplication)
|
||||
|
||||
if(APPLE)
|
||||
find_library(SPARKLE Sparkle PATHS "/usr/local/opt/sparkle")
|
||||
add_subdirectory(3rdparty/SPMediaKeyTap)
|
||||
set(SPMEDIAKEYTAP_INCLUDE_DIRS ${CMAKE_CURRENT_SOURCE_DIR}/3rdparty/SPMediaKeyTap)
|
||||
set(SPMEDIAKEYTAP_LIBRARIES SPMediaKeyTap)
|
||||
@@ -300,7 +302,7 @@ if(APPLE)
|
||||
add_subdirectory(ext/macdeploycheck)
|
||||
endif()
|
||||
|
||||
if(NOT SPARKLE AND (APPLE OR WIN32))
|
||||
if(WIN32)
|
||||
if(BUILD_WITH_QT6)
|
||||
pkg_check_modules(QTSPARKLE qtsparkle-qt6)
|
||||
else()
|
||||
@@ -311,6 +313,12 @@ if(NOT SPARKLE AND (APPLE OR WIN32))
|
||||
endif()
|
||||
endif()
|
||||
|
||||
if(WIN32 AND MSVC)
|
||||
add_subdirectory(3rdparty/getopt)
|
||||
set(GETOPT_INCLUDE_DIRS ${CMAKE_CURRENT_SOURCE_DIR}/3rdparty/getopt)
|
||||
set(GETOPT_LIBRARIES getopt)
|
||||
endif()
|
||||
|
||||
if(WIN32 AND NOT MSVC)
|
||||
# RC compiler
|
||||
string(REPLACE "gcc" "windres" CMAKE_RC_COMPILER_INIT ${CMAKE_C_COMPILER})
|
||||
@@ -391,6 +399,11 @@ optional_component(GIO ON "Devices: GIO device backend"
|
||||
DEPENDS "Unix or Windows" "NOT APPLE"
|
||||
)
|
||||
|
||||
optional_component(GIO_UNIX ON "Devices: GIO device backend (Unix support)"
|
||||
DEPENDS "libgio-unix" GIO_UNIX_FOUND
|
||||
DEPENDS "Unix" "UNIX"
|
||||
)
|
||||
|
||||
optional_component(LIBGPOD ON "Devices: iPod classic support"
|
||||
DEPENDS "libgpod" LIBGPOD_FOUND
|
||||
DEPENDS "gdk-pixbuf" GDK_PIXBUF_FOUND
|
||||
@@ -400,11 +413,6 @@ optional_component(LIBMTP ON "Devices: MTP support"
|
||||
DEPENDS "libmtp" LIBMTP_FOUND
|
||||
)
|
||||
|
||||
optional_component(SPARKLE ON "Sparkle integration"
|
||||
DEPENDS "macOS" APPLE
|
||||
DEPENDS "Sparkle" SPARKLE
|
||||
)
|
||||
|
||||
if(BUILD_WITH_QT6)
|
||||
optional_component(TRANSLATIONS ON "Translations"
|
||||
DEPENDS "gettext" GETTEXT_FOUND
|
||||
@@ -523,6 +531,10 @@ elseif(NOT HAVE_GSTREAMER)
|
||||
message(WARNING "GStreamer is the only engine that is fully implemented. Using other engines is possible but not recommended.")
|
||||
endif()
|
||||
|
||||
if(QT_VERSION_MAJOR EQUAL 5)
|
||||
message(WARNING "It is detected that Strawberry is being built with Qt 5. There are no bugfix releases for the latest minor LTS version of Qt 5 available to open-source users, only commercial users. Therefore Strawberry should be built with Qt 6 when possible. Building with Qt 6 will also take advantage of improvements and new features not available in Qt 5. To build with Qt 6 specify -DBUILD_WITH_QT6=ON to automatically detect Qt 6, or for example -DCMAKE_PREFIX_PATH=/usr/local/lib64/cmake to manually specify the Qt 6 directory.")
|
||||
endif()
|
||||
|
||||
if(NOT CMAKE_CROSSCOMPILING)
|
||||
if(QT_SQLITE_TEST)
|
||||
if(NOT SQLITE_FTS5_TEST)
|
||||
|
||||
190
Changelog
190
Changelog
@@ -2,7 +2,76 @@ Strawberry Music Player
|
||||
=======================
|
||||
ChangeLog
|
||||
|
||||
1.0.0:
|
||||
Version 1.0.2 (2022.02.20)
|
||||
|
||||
Bugfixes:
|
||||
* Fixed showing menu when clicking icon for collection and internet search tool buttons
|
||||
* Fixed ignoring devices with system mounts as defined by GIO (#410).
|
||||
* Fixed updating database when deleting songs from filesystem devices.
|
||||
* Fixed unregistered metatype when listing songs from MTP devices with Qt 6.
|
||||
* Fixed using entered password when testing Subsonic settings before pressing save (#879).
|
||||
* Fixed downloading Subsonic album covers.
|
||||
* Fixed subsonic album covers downloaded several times for each album when MD5 authentication was enabled (#885).
|
||||
* Fixed volume going to 100% when pressing volume down with MRPIS2 and global shortcuts (#884).
|
||||
* Fixed incorrect rounding when setting volume through MPRIS2 (#894).
|
||||
* Fixed delete from disk not showing up in the menu when one or more CUE songs were selected.
|
||||
* Fixed possible crashes when switching songs when fading is enabled (#890).
|
||||
* Fixed X11 global shortcuts not working unless window was in focus with Qt 6.2 and higher (#893).
|
||||
* Fixed scrobbler re-sending scrobbles to fast on error (#898).
|
||||
|
||||
Enhancements
|
||||
* Log Qt version on startup.
|
||||
* Added button for deleting existing Subsonic songs (#883).
|
||||
* Make collection watcher ignore files with "qt_temp" filename and tmp extension.
|
||||
* Require Qt 5.9 or higher.
|
||||
* Added scrollbars to edit tag dialog (#888).
|
||||
* Added advanced settings for configuring collection watcher.
|
||||
* Disable open audio CD menu when compiled without audio CD support.
|
||||
* Replaced use of deprecated QMouseEvent constructor as of Qt 6.4.
|
||||
* Replaced use of deprecated QCryptographicHash::addData overload as of Qt 6.4.
|
||||
|
||||
Removed features:
|
||||
* Removed broken "nomedia" / "nomusic" file handling.
|
||||
|
||||
Version 1.0.1 (2022.01.08)
|
||||
|
||||
Bugfixes:
|
||||
* Fixed collection and internet search filter tool button menu arrow overlap (#796).
|
||||
* Fixed stop after this track button with Qt 6 (#795).
|
||||
* Fixed not updating the URL when songs were moved on disk when the fingerprinting feature is enabled.
|
||||
* Fixed SQL query error for songs with an invalid modification time (#815).
|
||||
* Fixed blocky rendering of the currently playing track with high resolution screens (#794).
|
||||
* Fixed incorrect playlist column filesize for radio streams.
|
||||
* Fixed deleting embedded album cover from Ogg songs.
|
||||
* Fixed parsing of Cue tracks with 1-digit minutes (#836).
|
||||
* Fixed updating of playlist summary after reloading items when adding songs from files outside of the collection (#848).
|
||||
* Fixed always saving metadata when saving playlists for Tidal, Qobuz and Subsonic songs independent of playlist setting (#851).
|
||||
* Fixed setting media shortcuts when using kglobalaccel (#849).
|
||||
* Fixed parsing of Genius lyrics when they are sometimes received in a different HTML format.
|
||||
* Fixed saving MP4 specific tags as UTF-8 (#830).
|
||||
* Fixed clearing "manually set" cover when saving album covers embedded from outside of the tag editor (#858).
|
||||
* Fixed aborting collection scan when Strawberry exists to avoid hang on exit.
|
||||
* Fixed resuming collection scan when adding a new directory after collection scan was aborted.
|
||||
* Fixed excluding hidden songs from the collection.
|
||||
* Disabled moodbar for CUE songs since they can not be supported properly (#865).
|
||||
* (Windows) Added gstreamer gstxingmux plugin to fix transcoding to MP3 (#856).
|
||||
|
||||
Enhancements:
|
||||
* Made playlist header column text elided (#801).
|
||||
* Added support for reading and writing playcounts and ratings from/to tags.
|
||||
* Added support for setting rating using the edit tag dialog.
|
||||
* Added setting to enable/disable playlist toolbar (#809).
|
||||
* Added component type, content_rating type and releases to AppStream data file (#806).
|
||||
* Removed unused "mark as listened" option in organize dialog.
|
||||
* Fixed some clazy warnings and narrowing conversions in the source code.
|
||||
* Replaced uses of macros in the source code.
|
||||
* Added a more user-friendly error message when receiving encrypted streams from Tidal (#824).
|
||||
* Added support for port-pattern entered in the device textbox when using Jack as output (#828).
|
||||
* Added Spanish (Spain) translation.
|
||||
* Added support for more CUE filenames (#835).
|
||||
* (Windows) Add gstreamer dash plugin.
|
||||
|
||||
Version 1.0.0 (2021.10.14)
|
||||
|
||||
Bugfixes:
|
||||
* Fix updating temporary metadata when reloading songs outside of the collection.
|
||||
@@ -56,7 +125,8 @@ ChangeLog
|
||||
* Add support for native global shortcuts on MATE.
|
||||
* Add radios view with channels from Radio Paradise and SomaFM.
|
||||
|
||||
0.9.3:
|
||||
|
||||
Version 0.9.3 (2021.04.18)
|
||||
|
||||
Bugfixes:
|
||||
* Fix "Show in file browser" to work with thunar.
|
||||
@@ -79,7 +149,8 @@ ChangeLog
|
||||
* (macOS) Make macdeployqt work with Qt 5 too.
|
||||
* (macOS) Show keep running option in behaviour settings.
|
||||
|
||||
0.9.2:
|
||||
|
||||
Version 0.9.2 (2021.03.25)
|
||||
|
||||
Bugfixes:
|
||||
* Fix marking songs available.
|
||||
@@ -90,7 +161,8 @@ ChangeLog
|
||||
* (macOS) Fix crash when opening cover manager.
|
||||
* (macOS) Fix broken Qt plugins resulting in album covers not showing.
|
||||
|
||||
0.9.1:
|
||||
|
||||
Version 0.9.1 (2021.03.13)
|
||||
|
||||
Bugfixes:
|
||||
* Fix duplicating songs in the DB when organizing songs between 2 different collection directories.
|
||||
@@ -125,7 +197,8 @@ ChangeLog
|
||||
New features:
|
||||
* Add option and support for saving embedded covers for FLAC, Ogg Vorbis, MP3 and MP4/AAC.
|
||||
|
||||
0.8.5:
|
||||
|
||||
Version 0.8.5 (2020.12.19)
|
||||
|
||||
Bugfixes:
|
||||
* Fix return type of SmartPlaylistQueryWizardPlugin::type().
|
||||
@@ -144,7 +217,8 @@ ChangeLog
|
||||
* Add command line option to play a playlist based on name.
|
||||
* Change double-click behaviour in cover manager to open fullsize cover.
|
||||
|
||||
0.8.4:
|
||||
|
||||
Version 0.8.4 (2020.11.15)
|
||||
|
||||
Bugfixes:
|
||||
* Fix preventing session logout when window is maxmimized.
|
||||
@@ -170,7 +244,8 @@ ChangeLog
|
||||
* Remove remaining uses of QTextCodec.
|
||||
* Remove Core5Compat dependency.
|
||||
|
||||
0.8.3:
|
||||
|
||||
Version 0.8.3 (2020.10.24)
|
||||
|
||||
Bugfixes:
|
||||
* Fixed updating playing widget song details in small cover mode.
|
||||
@@ -185,7 +260,8 @@ ChangeLog
|
||||
Enhancements:
|
||||
* (Windows) Added WASAPI plugin.
|
||||
|
||||
0.8.2:
|
||||
|
||||
Version 0.8.2 (2020.10.13)
|
||||
|
||||
Bugfixes:
|
||||
* Fixed broken transition to next song for CUE files with certain audio formats (regression since version 0.6.13).
|
||||
@@ -197,7 +273,8 @@ ChangeLog
|
||||
* Removed use of HTML in system tray icon tooltip for all desktop environments instead of just KDE and Cinnamon.
|
||||
* (Windows) Ignore "IDirectSoundBuffer_GetStatus The operation completed successfully" false error when switching device while playing.
|
||||
|
||||
0.8.1:
|
||||
|
||||
Version 0.8.1 (2020.10.09)
|
||||
|
||||
Bugfixes:
|
||||
* Fixed engine selection in backend settings with Qt 6.
|
||||
@@ -244,14 +321,15 @@ ChangeLog
|
||||
* Added Subsonic server side scrobbling support.
|
||||
* Load thumbnails from iPods to show under device collection.
|
||||
|
||||
0.7.2:
|
||||
Version 0.7.2 (2020.08.15)
|
||||
|
||||
Bugfixes:
|
||||
* Fixed installation directory for translations.
|
||||
* Fixed collection sorting for non-ASCII characters.
|
||||
* Fixed closing connected devices on exit.
|
||||
|
||||
0.7.1:
|
||||
|
||||
Version 0.7.1 (2020.08.15)
|
||||
|
||||
Bugfixes:
|
||||
* Fixed incorrectly mapped global shortcuts keys "2" and "3".
|
||||
@@ -290,7 +368,8 @@ ChangeLog
|
||||
* Removed Xine engine support.
|
||||
* Removed broken imobiledevice (iPhone) support.
|
||||
|
||||
0.6.13:
|
||||
|
||||
Version 0.6.13 (2020.07.13)
|
||||
|
||||
Bugfixes:
|
||||
* Fixed cut-off text in about dialog.
|
||||
@@ -316,7 +395,8 @@ ChangeLog
|
||||
* Fixed unit test for testing playlist model.
|
||||
* Added new unit tests for tagreader.
|
||||
|
||||
0.6.12:
|
||||
|
||||
Version 0.6.12 (2020.06.07)
|
||||
|
||||
Bugfixes:
|
||||
* Fixed height of about dialog.
|
||||
@@ -329,7 +409,8 @@ ChangeLog
|
||||
* Sort folders added from file view.
|
||||
* Changed default collection grouping to album - disc.
|
||||
|
||||
0.6.11:
|
||||
|
||||
Version 0.6.11 (2020.05.16)
|
||||
|
||||
Bugfixes:
|
||||
* Fixed MPRIS missing art url when playing albums with embedded cover.
|
||||
@@ -355,7 +436,8 @@ ChangeLog
|
||||
* Added album covers from Musixmatch and Spotify.
|
||||
* Added lyrics from Genius, Musixmatch and ChartLyrics.
|
||||
|
||||
0.6.10:
|
||||
|
||||
Version 0.6.10 (2020.05.01)
|
||||
|
||||
Bugfixes:
|
||||
* Fixed Subsonic album covers not working for albums with non ASCII characters.
|
||||
@@ -395,7 +477,8 @@ ChangeLog
|
||||
Removed features:
|
||||
* Removed Phonon engine support.
|
||||
|
||||
Version 0.6.9:
|
||||
|
||||
Version 0.6.9 (2020.03.09)
|
||||
|
||||
BugFixes:
|
||||
* Fixed playlist metadata updating interfering with manual tag editing.
|
||||
@@ -427,7 +510,8 @@ Version 0.6.9:
|
||||
* Tidal support (No agreement).
|
||||
* QObuz support (No agreement).
|
||||
|
||||
Version 0.6.8:
|
||||
|
||||
Version 0.6.8 (2020.01.05)
|
||||
|
||||
* Fixed stuck tabbar and collection GUI with some themes.
|
||||
* Fixed possible crashes related to QProxyStyle.
|
||||
@@ -444,7 +528,8 @@ Version 0.6.8:
|
||||
* (macOS) Fixed filesystem watcher to correctly pick up changed collection directories.
|
||||
* (Windows) Fixed translations not being included.
|
||||
|
||||
Version 0.6.7:
|
||||
|
||||
Version 0.6.7 (2019.11.27)
|
||||
|
||||
* Fixed crash when cancelling scrobbler authentication
|
||||
* Fixed "Double clicking a song in the playlist" behaviour setting
|
||||
@@ -461,7 +546,8 @@ Version 0.6.7:
|
||||
* Removed left click on analyzer to popup menu
|
||||
* (Windows) Added killproc executable to terminate running process before uninstalling
|
||||
|
||||
Version 0.6.6:
|
||||
|
||||
Version 0.6.6 (2019.11.09)
|
||||
|
||||
* Fixed lowercased album artist in playlist column
|
||||
* Fixed compiling with different optional features turned off
|
||||
@@ -479,7 +565,8 @@ Version 0.6.6:
|
||||
* Added option to automatically select current playing track
|
||||
* (Windows) Added support for WASAPI
|
||||
|
||||
Version 0.6.5:
|
||||
|
||||
Version 0.6.5 (2019.09.30)
|
||||
|
||||
* Fixed scrobbler not to send scrobbles multiple times when metadata is updated
|
||||
* Fixed Listenbrainz scrobbler not don't send "various artists" as album artist
|
||||
@@ -488,7 +575,8 @@ Version 0.6.5:
|
||||
* Fixed OSD pretty positioning on Windows on screens with negative geometry
|
||||
* Fixed appdata file to pass full validation
|
||||
|
||||
Version 0.6.4:
|
||||
|
||||
Version 0.6.4 (2019.09.25)
|
||||
|
||||
* Added setting for fancy tabbar background color
|
||||
* Added setting to make marking songs unavailable optional
|
||||
@@ -515,18 +603,21 @@ Version 0.6.4:
|
||||
* Fixed restoring to original window size when restoring from system tray
|
||||
* Updated 3rdparty taglib
|
||||
|
||||
Version 0.6.3:
|
||||
|
||||
Version 0.6.3 (2019.08.05)
|
||||
|
||||
* Fixed crash when using internet services.
|
||||
* Fixed musicbrainz tagfetcher only showing 1 result per song.
|
||||
* Fixed collection watcher to unwatch deleted directories.
|
||||
* Added "album - disc" grouping.
|
||||
|
||||
Version 0.6.2:
|
||||
|
||||
Version 0.6.2 (2019.08.03)
|
||||
|
||||
* Disabled fatal error for FTS5 cmake test.
|
||||
|
||||
Version 0.6.1:
|
||||
|
||||
Version 0.6.1 (2019.08.03)
|
||||
|
||||
* Compare artist and album case-insensitive when generating score for album covers.
|
||||
* Fixed broken return value of sendMessage() in SingleApplication causing application to be started twice.
|
||||
@@ -572,11 +663,13 @@ Version 0.6.1:
|
||||
* Fixed certain cases where the playing widget gets stuck when switching fast between context and other widgets.
|
||||
* Removed ChartLyrics provider (service have been down for a long time).
|
||||
|
||||
Version 0.5.5:
|
||||
|
||||
Version 0.5.5 (2019.05.05)
|
||||
|
||||
* Fixed Tidal API url
|
||||
|
||||
Version 0.5.4:
|
||||
|
||||
Version 0.5.4 (2019.05.05)
|
||||
|
||||
* Changed description for offline mode scrobbling for less confusion
|
||||
* Fixed scrobbler to not send "playing now" when in offline mode
|
||||
@@ -607,7 +700,8 @@ Version 0.5.4:
|
||||
* Fixed and improved snap including upgrading to core18 and adding proper alsa support
|
||||
* Fixed resume playback on startup not working for other than the first playlist
|
||||
|
||||
Version 0.5.3:
|
||||
|
||||
Version 0.5.3 (2019.03.02)
|
||||
|
||||
* Changed default tagging to albumartist in organise dialog
|
||||
* Removed support for older taglib in tagreader
|
||||
@@ -647,7 +741,8 @@ Version 0.5.3:
|
||||
* Added group by format
|
||||
* Fixed gstreamer leaks
|
||||
|
||||
Version 0.5.2:
|
||||
|
||||
Version 0.5.2 (2019.01.26)
|
||||
|
||||
* Added error handling and message for URL handler
|
||||
* Added SingleCoreApplication secondary check
|
||||
@@ -664,7 +759,8 @@ Version 0.5.2:
|
||||
* Added option to copy album cover in organise dialog (filesystem and libgpod devices)
|
||||
* Added raise() to make sure window is on top when strawberry is started twice
|
||||
|
||||
Version 0.5.1:
|
||||
|
||||
Version 0.5.1 (2019.01.12)
|
||||
|
||||
* Added scrobbler with support for Last.fm, Libre.fm and ListenBrainz
|
||||
* Fixed key up causing playback to reset
|
||||
@@ -695,7 +791,8 @@ Version 0.5.1:
|
||||
* Added debian copyright file
|
||||
* Fixed some compile errors
|
||||
|
||||
Version 0.4.2:
|
||||
|
||||
Version 0.4.2 (2018.11.28)
|
||||
|
||||
* Updated AppStream data file to newer specifications
|
||||
* Fixed Deezer engine to use quality setting
|
||||
@@ -709,7 +806,8 @@ Version 0.4.2:
|
||||
* (Windows) Corrected uninstalled files on x64 installer
|
||||
* (macOS) Fixed poor performance
|
||||
|
||||
Version 0.4.1:
|
||||
|
||||
Version 0.4.1 (2018.11.01)
|
||||
|
||||
* Fixed crash in analyzer
|
||||
* Fixed trying to use systray even if the desktop had no systray
|
||||
@@ -727,11 +825,13 @@ Version 0.4.1:
|
||||
* Added AppStream data file
|
||||
* Fixed compiling with Qt 5 versions of system QtSingleApplication and Qxt library
|
||||
|
||||
Version 0.3.3:
|
||||
|
||||
Version 0.3.3 (2018.09.24)
|
||||
|
||||
* Fixed Tidal login
|
||||
|
||||
Version 0.3.2:
|
||||
|
||||
Version 0.3.2 (2018.09.24)
|
||||
|
||||
* Fixed search error not shown in Tidal search
|
||||
* Added URL handler for Tidal, now retrieving URL's when playing instead of when searching
|
||||
@@ -743,7 +843,8 @@ Version 0.3.2:
|
||||
* Added encoding of Tidal token in the source code
|
||||
* Added encoding of Tidal password in the configuration
|
||||
|
||||
Version 0.3.1:
|
||||
|
||||
Version 0.3.1 (2018.09.15)
|
||||
|
||||
* Added new lyrics provider with lyrics from AudD and API Seeds
|
||||
* New improved context widget with albums and lyrics
|
||||
@@ -765,7 +866,8 @@ Version 0.3.1:
|
||||
* Added support for reading/writing lyrics to tags
|
||||
* Fixed saving tags (APE) for WavPack files
|
||||
|
||||
Version 0.2.1:
|
||||
|
||||
Version 0.2.1 (2018.07.05)
|
||||
|
||||
* Fixed crash with newer Qt
|
||||
* Fixed setting output/device for Xine and VLC backend
|
||||
@@ -775,19 +877,23 @@ Version 0.2.1:
|
||||
* Fixed device selection on macOS
|
||||
* Added xine on to windows build
|
||||
|
||||
Version 0.1.6:
|
||||
|
||||
Version 0.1.6 (2018.06.07)
|
||||
* Fixed crash on exit caused by NVIDIA driver
|
||||
* Fixed PulseAudio device selection
|
||||
* Improvements to device selection
|
||||
|
||||
Version 0.1.5:
|
||||
|
||||
Version 0.1.5 (2018.05.16)
|
||||
* Makefile fixes for building
|
||||
|
||||
Version 0.1.4:
|
||||
|
||||
Version 0.1.4 (2018.05.14)
|
||||
* Fixed compliation with clang compiler
|
||||
* This release is mainly to get it working on openbsd and freebsd.
|
||||
|
||||
Version 0.1.3:
|
||||
|
||||
Version 0.1.3 (2018.05.12)
|
||||
* Audio file detection by content
|
||||
* Added builtin taglib to 3rdparty to support detecting audio by content instead of just file extension
|
||||
* Removed unneeded qsqlite from 3rdparty
|
||||
@@ -795,7 +901,8 @@ Version 0.1.3:
|
||||
* Replaced incorrect DLL libgstdirectsoundsink.dll (from gst 1.12.4) instead of libgstdirectsound.dll (from gst 1.14.0) for windows build
|
||||
* Fixed git versioning
|
||||
|
||||
Version 0.1.2:
|
||||
|
||||
Version 0.1.2 (2018.05.02)
|
||||
* Fixed playback of WavPack files
|
||||
* Fixed musicbrainz tagfetcher
|
||||
* Use common regex (Song::kCoverRemoveDisc) for removing Disc/CD from album
|
||||
@@ -805,5 +912,6 @@ Version 0.1.2:
|
||||
* Fixed problems with windows build missing some DLL's, only supplying required gstreamer-plugins now
|
||||
* Removed redundant code
|
||||
|
||||
Version 0.1.1:
|
||||
|
||||
Version 0.1.1 (2018.04.07)
|
||||
* Initial release
|
||||
|
||||
@@ -71,7 +71,7 @@ To build Strawberry from source you need the following installed on your system
|
||||
* [Boost](https://www.boost.org/)
|
||||
* [GLib](https://developer.gnome.org/glib/)
|
||||
* [Protobuf](https://developers.google.com/protocol-buffers/)
|
||||
* [Qt 5.8 or higher (or Qt 6) with components Core, Gui, Widgets, Concurrent, Network and Sql](https://www.qt.io/)
|
||||
* [Qt 5.9 or higher (or Qt 6) with components Core, Gui, Widgets, Concurrent, Network and Sql](https://www.qt.io/)
|
||||
* [SQLite 3.9 or newer with FTS5](https://www.sqlite.org)
|
||||
* [ALSA (Linux required)](https://www.alsa-project.org/)
|
||||
* [D-Bus (Linux required)](https://www.freedesktop.org/wiki/Software/dbus/)
|
||||
|
||||
@@ -12,10 +12,17 @@ if (LSB_RELEASE_EXEC AND RPMBUILD_EXEC)
|
||||
OUTPUT_VARIABLE DIST_RELEASE
|
||||
OUTPUT_STRIP_TRAILING_WHITESPACE
|
||||
)
|
||||
execute_process(COMMAND /bin/sh "-c" "${LSB_RELEASE_EXEC} -ds | tr '[:upper:]' '[:lower:]' | sed 's/\"//g' | sed 's/\\.//g' | cut -d' ' -f3"
|
||||
OUTPUT_VARIABLE DIST_VERSION
|
||||
OUTPUT_STRIP_TRAILING_WHITESPACE
|
||||
)
|
||||
if (${DIST_NAME} STREQUAL "openmandrivalinux")
|
||||
execute_process(COMMAND /bin/sh "-c" "${LSB_RELEASE_EXEC} -ds | tr '[:upper:]' '[:lower:]' | sed 's/\"//g' | sed 's/\\./0/g' | cut -d' ' -f3"
|
||||
OUTPUT_VARIABLE DIST_VERSION
|
||||
OUTPUT_STRIP_TRAILING_WHITESPACE
|
||||
)
|
||||
else()
|
||||
execute_process(COMMAND /bin/sh "-c" "${LSB_RELEASE_EXEC} -ds | tr '[:upper:]' '[:lower:]' | sed 's/\"//g' | sed 's/\\.//g' | cut -d' ' -f3"
|
||||
OUTPUT_VARIABLE DIST_VERSION
|
||||
OUTPUT_STRIP_TRAILING_WHITESPACE
|
||||
)
|
||||
endif()
|
||||
if (DIST_NAME)
|
||||
|
||||
message(STATUS "Distro Name: ${DIST_NAME}")
|
||||
@@ -44,6 +51,8 @@ if (LSB_RELEASE_EXEC AND RPMBUILD_EXEC)
|
||||
set(RPM_DISTRO "el${DIST_VERSION}")
|
||||
elseif (${DIST_NAME} STREQUAL "mageia" AND DIST_RELEASE)
|
||||
set(RPM_DISTRO "mga${DIST_RELEASE}")
|
||||
elseif (${DIST_NAME} STREQUAL "openmandrivalinux" AND DIST_VERSION)
|
||||
set(RPM_DISTRO "omv${DIST_VERSION}")
|
||||
endif()
|
||||
|
||||
if(NOT RPM_DISTRO)
|
||||
|
||||
@@ -24,7 +24,7 @@ macro(summary_show)
|
||||
list(SORT summary_willbuild)
|
||||
list(SORT summary_willnotbuild)
|
||||
message("")
|
||||
message("Building strawberry version: ${STRAWBERRY_VERSION_DISPLAY}")
|
||||
message("Building strawberry version: ${STRAWBERRY_VERSION_DISPLAY}, Qt version ${Qt${QT_VERSION_MAJOR}Core_VERSION}")
|
||||
summary_show_part(summary_willbuild "The following components will be built:")
|
||||
summary_show_part(summary_willnotbuild "The following components WILL NOT be built:")
|
||||
message("")
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
set(STRAWBERRY_VERSION_MAJOR 1)
|
||||
set(STRAWBERRY_VERSION_MINOR 0)
|
||||
set(STRAWBERRY_VERSION_PATCH 0)
|
||||
set(STRAWBERRY_VERSION_PATCH 2)
|
||||
#set(STRAWBERRY_VERSION_PRERELEASE rc1)
|
||||
|
||||
set(INCLUDE_GIT_REVISION OFF)
|
||||
|
||||
@@ -95,6 +95,7 @@
|
||||
<file>icons/128x128/radio.png</file>
|
||||
<file>icons/128x128/somafm.png</file>
|
||||
<file>icons/128x128/radioparadise.png</file>
|
||||
<file>icons/128x128/musicbrainz.png</file>
|
||||
<file>icons/64x64/albums.png</file>
|
||||
<file>icons/64x64/alsa.png</file>
|
||||
<file>icons/64x64/application-exit.png</file>
|
||||
@@ -191,6 +192,7 @@
|
||||
<file>icons/64x64/radio.png</file>
|
||||
<file>icons/64x64/somafm.png</file>
|
||||
<file>icons/64x64/radioparadise.png</file>
|
||||
<file>icons/64x64/musicbrainz.png</file>
|
||||
<file>icons/48x48/albums.png</file>
|
||||
<file>icons/48x48/alsa.png</file>
|
||||
<file>icons/48x48/application-exit.png</file>
|
||||
@@ -291,6 +293,7 @@
|
||||
<file>icons/48x48/radio.png</file>
|
||||
<file>icons/48x48/somafm.png</file>
|
||||
<file>icons/48x48/radioparadise.png</file>
|
||||
<file>icons/48x48/musicbrainz.png</file>
|
||||
<file>icons/32x32/albums.png</file>
|
||||
<file>icons/32x32/alsa.png</file>
|
||||
<file>icons/32x32/application-exit.png</file>
|
||||
@@ -391,6 +394,7 @@
|
||||
<file>icons/32x32/radio.png</file>
|
||||
<file>icons/32x32/somafm.png</file>
|
||||
<file>icons/32x32/radioparadise.png</file>
|
||||
<file>icons/32x32/musicbrainz.png</file>
|
||||
<file>icons/22x22/albums.png</file>
|
||||
<file>icons/22x22/alsa.png</file>
|
||||
<file>icons/22x22/application-exit.png</file>
|
||||
@@ -491,5 +495,6 @@
|
||||
<file>icons/22x22/radio.png</file>
|
||||
<file>icons/22x22/somafm.png</file>
|
||||
<file>icons/22x22/radioparadise.png</file>
|
||||
<file>icons/22x22/musicbrainz.png</file>
|
||||
</qresource>
|
||||
</RCC>
|
||||
|
||||
BIN
data/icons/128x128/musicbrainz.png
Normal file
BIN
data/icons/128x128/musicbrainz.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 6.1 KiB |
BIN
data/icons/22x22/musicbrainz.png
Normal file
BIN
data/icons/22x22/musicbrainz.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.0 KiB |
BIN
data/icons/32x32/musicbrainz.png
Normal file
BIN
data/icons/32x32/musicbrainz.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 947 B |
BIN
data/icons/48x48/musicbrainz.png
Normal file
BIN
data/icons/48x48/musicbrainz.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.3 KiB |
BIN
data/icons/64x64/musicbrainz.png
Normal file
BIN
data/icons/64x64/musicbrainz.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 3.8 KiB |
BIN
data/icons/full/musicbrainz.png
Normal file
BIN
data/icons/full/musicbrainz.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 13 KiB |
@@ -19,47 +19,6 @@
|
||||
background-clip: content;
|
||||
}
|
||||
|
||||
QToolButton {
|
||||
border: 2px solid transparent;
|
||||
border-radius: 3px;
|
||||
padding: 1px;
|
||||
}
|
||||
|
||||
QToolButton::menu-button {
|
||||
width: 16px;
|
||||
border: none;
|
||||
}
|
||||
|
||||
QToolButton[popupMode="1"] {
|
||||
padding-right: 16px;
|
||||
}
|
||||
|
||||
QToolButton:hover {
|
||||
border: 2px solid %palette-highlight;
|
||||
background-color: %palette-highlight-lighter;
|
||||
}
|
||||
|
||||
QToolButton:hover[popupMode="1"] {
|
||||
padding-right: 16px;
|
||||
}
|
||||
|
||||
QToolButton:pressed {
|
||||
border: 2px solid %palette-highlight-darker;
|
||||
background-color: %palette-highlight-lighter;
|
||||
}
|
||||
|
||||
QToolButton:pressed[popupMode="1"] {
|
||||
padding-right: 16px;
|
||||
}
|
||||
|
||||
macos {
|
||||
font-size: 11pt;
|
||||
}
|
||||
|
||||
macos QMenu {
|
||||
font-size: 13pt;
|
||||
}
|
||||
|
||||
#context-layout-container {
|
||||
background-color: %palette-base;
|
||||
}
|
||||
@@ -71,3 +30,41 @@ macos QMenu {
|
||||
#context-layout-scrollarea {
|
||||
background-color: %palette-base;
|
||||
}
|
||||
|
||||
QToolButton {
|
||||
border: 2px solid transparent;
|
||||
border-radius: 3px;
|
||||
padding: 1px;
|
||||
}
|
||||
|
||||
QToolButton:hover {
|
||||
border: 2px solid %palette-highlight;
|
||||
background-color: %palette-highlight-lighter;
|
||||
}
|
||||
|
||||
QToolButton:pressed {
|
||||
border: 2px solid %palette-highlight-darker;
|
||||
background-color: %palette-highlight-lighter;
|
||||
}
|
||||
|
||||
QToolButton[popupMode="MenuButtonPopup"], QToolButton:hover[popupMode="MenuButtonPopup"], QToolButton:pressed[popupMode="MenuButtonPopup"] {
|
||||
padding-right: 16px;
|
||||
}
|
||||
|
||||
/* For backwards compatibility with Qt 5 as it does not support property name */
|
||||
QToolButton[popupMode="1"], QToolButton:hover[popupMode="1"], QToolButton:pressed[popupMode="1"] {
|
||||
padding-right: 16px;
|
||||
}
|
||||
|
||||
QToolButton::menu-button {
|
||||
width: 16px;
|
||||
border: none;
|
||||
}
|
||||
|
||||
macos {
|
||||
font-size: 11pt;
|
||||
}
|
||||
|
||||
macos QMenu {
|
||||
font-size: 13pt;
|
||||
}
|
||||
|
||||
179
dist/scripts/import-from-clementine.sh
vendored
Executable file
179
dist/scripts/import-from-clementine.sh
vendored
Executable file
@@ -0,0 +1,179 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
# Strawberry Music Player
|
||||
# Copyright 2020, Jonas Kvinge <jonas@jkvinge.net>
|
||||
# 2021 Alexey Vazhnov
|
||||
#
|
||||
# Strawberry is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# Strawberry is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with Strawberry. If not, see <http://www.gnu.org/licenses/>.
|
||||
# SPDX-License-Identifier: GPL-3.0-only
|
||||
|
||||
|
||||
# Based on https://github.com/strawberrymusicplayer/strawberry/wiki/Import-collection-library-and-playlists-data-from-Clementine
|
||||
|
||||
set -o nounset
|
||||
set -o errexit
|
||||
set -o pipefail
|
||||
shopt -s dotglob
|
||||
|
||||
# Use hardcoded path if no parameters. No need in quotes here! See `man bash`, "Parameter Expansion":
|
||||
FILE_SRC=${1:-~/.config/Clementine/clementine.db}
|
||||
FILE_DST=${2:-~/.local/share/strawberry/strawberry/strawberry.db}
|
||||
|
||||
test -f "$FILE_SRC" || { echo "No such file: $FILE_SRC"; exit 1; }
|
||||
test -f "$FILE_DST" || { echo "No such file: $FILE_DST"; exit 1; }
|
||||
|
||||
echo "Will try to copy information from $FILE_SRC to $FILE_DST."
|
||||
echo
|
||||
echo 'This script will **delete all information** from Strawberry database!'
|
||||
read -r -p 'Do you want to continue? (the only YES is accepted) ' answer
|
||||
if [ "$answer" != "YES" ]; then exit 1; fi
|
||||
|
||||
# 'heredoc' with substitution of variables, see `man bash`, "Here Documents":
|
||||
sqlite3 -batch << EOF
|
||||
.echo on
|
||||
ATTACH '$FILE_DST' AS strawberry;
|
||||
ATTACH '$FILE_SRC' AS clementine;
|
||||
.bail on
|
||||
.databases
|
||||
|
||||
/* This must be done when importing all data from Clementine because playlists are based on ROWIDs */
|
||||
|
||||
DELETE FROM strawberry.directories;
|
||||
DELETE FROM strawberry.subdirectories;
|
||||
DELETE FROM strawberry.songs;
|
||||
DELETE FROM strawberry.playlists;
|
||||
DELETE FROM strawberry.playlist_items;
|
||||
|
||||
/* Import all data from the collection library songs */
|
||||
|
||||
INSERT INTO strawberry.directories (path, subdirs) SELECT path, subdirs FROM clementine.directories;
|
||||
INSERT INTO strawberry.subdirectories (directory_id, path, mtime) SELECT directory, path, mtime FROM clementine.subdirectories;
|
||||
INSERT INTO strawberry.songs (ROWID, title, album, artist, albumartist, track, disc, year, originalyear, genre, compilation, composer, performer, grouping, comment, lyrics, beginning, length, bitrate, samplerate, directory_id, url, filetype, filesize, mtime, ctime, unavailable, playcount, skipcount, lastplayed, compilation_detected, compilation_on, compilation_off, compilation_effective, art_automatic, art_manual, effective_albumartist, effective_originalyear, cue_path, rating)
|
||||
SELECT ROWID, title, album, artist, albumartist, track, disc, year, originalyear, genre, compilation, composer, performer, grouping, comment, lyrics, beginning, length, bitrate, samplerate, directory, filename, filetype, filesize, mtime, ctime, unavailable, playcount, skipcount, lastplayed, sampler, forced_compilation_on, forced_compilation_off, effective_compilation, art_automatic, art_manual, effective_albumartist, effective_originalyear, cue_path, rating FROM clementine.songs WHERE unavailable = 0;
|
||||
UPDATE strawberry.songs SET source = 2;
|
||||
UPDATE strawberry.songs SET artist_id = "";
|
||||
UPDATE strawberry.songs SET album_id = "";
|
||||
UPDATE strawberry.songs SET song_id = "";
|
||||
|
||||
/* Import playlists */
|
||||
|
||||
INSERT INTO strawberry.playlists (ROWID, name, last_played, special_type, ui_path, is_favorite, dynamic_playlist_type, dynamic_playlist_data, dynamic_playlist_backend)
|
||||
SELECT ROWID, name, last_played, special_type, ui_path, is_favorite, dynamic_playlist_type, dynamic_playlist_data, dynamic_playlist_backend FROM clementine.playlists WHERE dynamic_playlist_type ISNULL;
|
||||
|
||||
/* Import playlist items */
|
||||
|
||||
INSERT INTO strawberry.playlist_items
|
||||
(ROWID,
|
||||
playlist,
|
||||
collection_id,
|
||||
title,
|
||||
album,
|
||||
artist,
|
||||
albumartist,
|
||||
track,
|
||||
disc,
|
||||
year,
|
||||
originalyear,
|
||||
genre,
|
||||
compilation,
|
||||
composer,
|
||||
performer,
|
||||
grouping,
|
||||
comment,
|
||||
lyrics,
|
||||
beginning,
|
||||
length,
|
||||
bitrate,
|
||||
samplerate,
|
||||
directory_id,
|
||||
url,
|
||||
filetype,
|
||||
filesize,
|
||||
mtime,
|
||||
ctime,
|
||||
unavailable,
|
||||
playcount,
|
||||
skipcount,
|
||||
lastplayed,
|
||||
compilation_detected,
|
||||
compilation_on,
|
||||
compilation_off,
|
||||
compilation_effective,
|
||||
art_automatic,
|
||||
art_manual,
|
||||
effective_albumartist,
|
||||
effective_originalyear,
|
||||
cue_path,
|
||||
rating
|
||||
)
|
||||
SELECT ROWID,
|
||||
playlist,
|
||||
library_id,
|
||||
title,
|
||||
album,
|
||||
artist,
|
||||
albumartist,
|
||||
track,
|
||||
disc,
|
||||
year,
|
||||
originalyear,
|
||||
genre,
|
||||
compilation,
|
||||
composer,
|
||||
performer,
|
||||
grouping,
|
||||
comment,
|
||||
lyrics,
|
||||
beginning,
|
||||
length,
|
||||
bitrate,
|
||||
samplerate,
|
||||
directory,
|
||||
filename,
|
||||
filetype,
|
||||
filesize,
|
||||
mtime,
|
||||
ctime,
|
||||
unavailable,
|
||||
playcount,
|
||||
skipcount,
|
||||
lastplayed,
|
||||
sampler,
|
||||
forced_compilation_on,
|
||||
forced_compilation_off,
|
||||
effective_compilation,
|
||||
art_automatic,
|
||||
art_manual,
|
||||
effective_albumartist,
|
||||
effective_originalyear,
|
||||
cue_path,
|
||||
rating FROM clementine.playlist_items WHERE type = 'Library';
|
||||
|
||||
UPDATE strawberry.playlist_items SET source = 2;
|
||||
UPDATE strawberry.playlist_items SET type = 2;
|
||||
UPDATE strawberry.playlist_items SET artist_id = "";
|
||||
UPDATE strawberry.playlist_items SET album_id = "";
|
||||
UPDATE strawberry.playlist_items SET song_id = "";
|
||||
|
||||
/* Recreate the FTS tables */
|
||||
|
||||
DELETE FROM strawberry.songs_fts;
|
||||
INSERT INTO strawberry.songs_fts (ROWID, ftstitle, ftsalbum, ftsartist, ftsalbumartist, ftscomposer, ftsperformer, ftsgrouping, ftsgenre, ftscomment)
|
||||
SELECT ROWID, title, album, artist, albumartist, composer, performer, grouping, genre, comment
|
||||
FROM strawberry.songs;
|
||||
|
||||
EOF
|
||||
|
||||
# To be sure script didn't exit because of any error (because we use `set -o errexit`):
|
||||
echo "Script finished"
|
||||
25
dist/scripts/maketarball.sh.in
vendored
25
dist/scripts/maketarball.sh.in
vendored
@@ -11,10 +11,33 @@ if ! [ "$gitrev" = "ON" ]; then
|
||||
exclude_vcs="--exclude-vcs"
|
||||
fi
|
||||
|
||||
cmds="gtar tar"
|
||||
for cmd in $cmds
|
||||
do
|
||||
which $cmd >/dev/null 2>&1
|
||||
if [ ! $? -eq 0 ]; then
|
||||
continue
|
||||
fi
|
||||
result=$($cmd --version 2>&1 | head -n1)
|
||||
if [ ! $? -eq 0 ]; then
|
||||
continue
|
||||
fi
|
||||
echo "$result" | grep "^.* (GNU tar) .*$" >/dev/null 2>&1
|
||||
if [ ! $? -eq 0 ]; then
|
||||
continue
|
||||
fi
|
||||
TAR=$cmd
|
||||
done
|
||||
|
||||
if [ "$TAR" = "" ]; then
|
||||
echo "ERROR: Missing GNU Tar"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "Creating $name-$version.tar.xz..."
|
||||
|
||||
rm -f "$name-$version.tar.xz"
|
||||
tar -cJf $name-$version.tar.xz \
|
||||
${TAR} -cJf $name-$version.tar.xz \
|
||||
--transform "s,^$rootnoslash,$name-$version," $exclude_vcs \
|
||||
--exclude=".directory" \
|
||||
--exclude="*.tar" \
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<component>
|
||||
<component type="desktop-application">
|
||||
<id>org.strawberrymusicplayer.strawberry</id>
|
||||
<launchable type="desktop-id">org.strawberrymusicplayer.strawberry.desktop</launchable>
|
||||
<metadata_license>CC0-1.0</metadata_license>
|
||||
@@ -13,6 +13,7 @@
|
||||
<url type="homepage">https://www.strawberrymusicplayer.org/</url>
|
||||
<url type="bugtracker">https://github.com/strawberrymusicplayer/strawberry/</url>
|
||||
<translation type="qt">strawberry</translation>
|
||||
<content_rating type="oars-1.1" />
|
||||
<description>
|
||||
<p>
|
||||
Strawberry is a music player and music collection organizer. It is aimed at music collectors and audiophiles. With Strawberry you can play and manage your digital music collection, or stream your favorite radios. It also has unofficial streaming support for Tidal and Qobuz. Strawberry is free software released under GPL. The source code is available on GitHub. It's written in C++ using the Qt toolkit and GStreamer. Strawberry is compatible with both Qt version 5 and 6.
|
||||
@@ -48,4 +49,7 @@
|
||||
</screenshot>
|
||||
</screenshots>
|
||||
<update_contact>eclipseo@fedoraproject.org</update_contact>
|
||||
<releases>
|
||||
<release version="1.0.0" date="2021-10-16"/>
|
||||
</releases>
|
||||
</component>
|
||||
|
||||
6
dist/unix/strawberry.spec.in
vendored
6
dist/unix/strawberry.spec.in
vendored
@@ -137,8 +137,8 @@ Features:
|
||||
%if 0%{?fedora} || 0%{?rhel_version} || 0%{?centos}
|
||||
export CXXFLAGS="-fPIC $RPM_OPT_FLAGS"
|
||||
%endif
|
||||
%{cmake} -DCMAKE_BUILD_TYPE:STRING=Release -DQT_VERSION_MAJOR=@QT_VERSION_MAJOR@
|
||||
%if 0%{?centos} || (0%{?mageia} && 0%{?mageia} <= 7)
|
||||
%{cmake} -DQT_VERSION_MAJOR=@QT_VERSION_MAJOR@
|
||||
%if 0%{?centos} || (0%{?mageia} && 0%{?mageia} <= 7) || "%{?_vendor}" == "openmandriva"
|
||||
%make_build
|
||||
%else
|
||||
%cmake_build
|
||||
@@ -148,7 +148,7 @@ Features:
|
||||
%if 0%{?centos}
|
||||
%make_install
|
||||
%else
|
||||
%if 0%{?mageia}
|
||||
%if 0%{?mageia} || "%{?_vendor}" == "openmandriva"
|
||||
%make_install -C build
|
||||
%else
|
||||
%cmake_install
|
||||
|
||||
611
dist/windows/strawberry.nsi.in
vendored
611
dist/windows/strawberry.nsi.in
vendored
@@ -1,25 +1,47 @@
|
||||
!if "@ARCH@" == x86
|
||||
!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
|
||||
!if "@ARCH@" == "i686-w64-mingw32.shared"
|
||||
!define arch_x86
|
||||
!undef arch
|
||||
!define arch "x86"
|
||||
!endif
|
||||
|
||||
!if "@ARCH@" == x86_64
|
||||
!if "@ARCH@" == "x86_64"
|
||||
!define arch_x64
|
||||
!undef arch
|
||||
!define arch "x64"
|
||||
!endif
|
||||
|
||||
!if "@ARCH@" == x86_64-w64-mingw32.shared
|
||||
!if "@ARCH@" == "x86_64-w64-mingw32.shared"
|
||||
!define arch_x64
|
||||
!undef arch
|
||||
!define arch "x64"
|
||||
!endif
|
||||
|
||||
!if "@CMAKE_BUILD_TYPE@" == Debug
|
||||
!if "@MINGW@" == "1"
|
||||
!define mingw
|
||||
!undef compiler
|
||||
!define compiler "mingw"
|
||||
!endif
|
||||
|
||||
!if "@MSVC@" == "1"
|
||||
!define msvc
|
||||
!undef compiler
|
||||
!define compiler "msvc"
|
||||
!endif
|
||||
|
||||
!if "@CMAKE_BUILD_TYPE@" == "Debug"
|
||||
!define debug
|
||||
!endif
|
||||
|
||||
!if "@BUILD_WITH_QT6@" == "ON"
|
||||
!define with_qt6
|
||||
!undef build_type
|
||||
!define build_type "-Debug"
|
||||
!endif
|
||||
|
||||
!ifdef debug
|
||||
@@ -80,8 +102,16 @@ SetCompressor /SOLID lzma
|
||||
|
||||
!define MUI_COMPONENTSPAGE_SMALLDESC
|
||||
|
||||
ReserveFile "${NSISDIR}/Plugins/x86-unicode/LockedList.dll"
|
||||
ReserveFile "${NSISDIR}/Plugins/LockedList64.dll"
|
||||
!ifdef mingw
|
||||
ReserveFile "${NSISDIR}/Plugins/x86-unicode/LockedList.dll"
|
||||
ReserveFile "${NSISDIR}/Plugins/LockedList64.dll"
|
||||
!endif
|
||||
|
||||
!ifdef msvc
|
||||
ReserveFile "${NSISDIR}\Plugins\x86-unicode\LockedList.dll"
|
||||
ReserveFile "${NSISDIR}\Plugins\LockedList64.dll"
|
||||
!endif
|
||||
|
||||
!define LockedListPATH $InstallDir
|
||||
|
||||
; Installer pages
|
||||
@@ -101,37 +131,8 @@ UninstPage custom un.LockedListPageShow
|
||||
!insertmacro MUI_LANGUAGE "English" ;first language is the default language
|
||||
|
||||
Name "${PRODUCT_NAME}"
|
||||
!ifdef arch_x86
|
||||
!ifdef debug
|
||||
!ifdef with_qt6
|
||||
OutFile "${PRODUCT_NAME_SHORT}Setup-${PRODUCT_DISPLAY_VERSION}-Qt6-Debug-x86.exe"
|
||||
!else
|
||||
OutFile "${PRODUCT_NAME_SHORT}Setup-${PRODUCT_DISPLAY_VERSION}-Qt5-Debug-x86.exe"
|
||||
!endif
|
||||
!else
|
||||
!ifdef with_qt6
|
||||
OutFile "${PRODUCT_NAME_SHORT}Setup-${PRODUCT_DISPLAY_VERSION}-Qt6-x86.exe"
|
||||
!else
|
||||
OutFile "${PRODUCT_NAME_SHORT}Setup-${PRODUCT_DISPLAY_VERSION}-Qt5-x86.exe"
|
||||
!endif
|
||||
!endif
|
||||
!endif
|
||||
|
||||
!ifdef arch_x64
|
||||
!ifdef debug
|
||||
!ifdef with_qt6
|
||||
OutFile "${PRODUCT_NAME_SHORT}Setup-${PRODUCT_DISPLAY_VERSION}-Qt6-Debug-x64.exe"
|
||||
!else
|
||||
OutFile "${PRODUCT_NAME_SHORT}Setup-${PRODUCT_DISPLAY_VERSION}-Qt5-Debug-x64.exe"
|
||||
!endif
|
||||
!else
|
||||
!ifdef with_qt6
|
||||
OutFile "${PRODUCT_NAME_SHORT}Setup-${PRODUCT_DISPLAY_VERSION}-Qt6-x64.exe"
|
||||
!else
|
||||
OutFile "${PRODUCT_NAME_SHORT}Setup-${PRODUCT_DISPLAY_VERSION}-Qt5-x64.exe"
|
||||
!endif
|
||||
!endif
|
||||
!endif
|
||||
OutFile "${PRODUCT_NAME_SHORT}Setup-${PRODUCT_DISPLAY_VERSION}${build_type}-${compiler}-${arch}.exe"
|
||||
|
||||
InstallDir "${PRODUCT_INSTALL_DIR}"
|
||||
|
||||
@@ -186,29 +187,36 @@ SectionEnd
|
||||
Section "Strawberry" Strawberry
|
||||
SetOutPath "$INSTDIR"
|
||||
|
||||
; Common executables
|
||||
|
||||
File "strawberry.exe"
|
||||
File "strawberry-tagreader.exe"
|
||||
File "strawberry.ico"
|
||||
!ifdef mingw
|
||||
File "sqlite3.exe"
|
||||
!endif
|
||||
File "gst-launch-1.0.exe"
|
||||
File "gst-discoverer-1.0.exe"
|
||||
|
||||
; MinGW specific files
|
||||
|
||||
!ifdef mingw
|
||||
|
||||
!ifdef arch_x86
|
||||
File "libgcc_s_sjlj-1.dll"
|
||||
File "libcrypto-3.dll"
|
||||
File "libssl-3.dll"
|
||||
!endif
|
||||
|
||||
!ifdef arch_x64
|
||||
File "libgcc_s_seh-1.dll"
|
||||
File "libcrypto-3-x64.dll"
|
||||
File "libssl-3-x64.dll"
|
||||
!endif
|
||||
|
||||
File "avcodec-58.dll"
|
||||
File "avfilter-7.dll"
|
||||
File "avformat-58.dll"
|
||||
File "avutil-56.dll"
|
||||
File "avcodec-59.dll"
|
||||
File "avfilter-8.dll"
|
||||
File "avformat-59.dll"
|
||||
File "avutil-57.dll"
|
||||
File "libbrotlicommon.dll"
|
||||
File "libbrotlidec.dll"
|
||||
File "libbz2.dll"
|
||||
@@ -217,9 +225,7 @@ Section "Strawberry" Strawberry
|
||||
File "libdl.dll"
|
||||
File "libfaac-0.dll"
|
||||
File "libfaad-2.dll"
|
||||
File "libffi-7.dll"
|
||||
File "libfftw3-3.dll"
|
||||
File "libFLAC-8.dll"
|
||||
File "libffi-8.dll"
|
||||
File "libfreetype-6.dll"
|
||||
File "libgio-2.0-0.dll"
|
||||
File "libglib-2.0-0.dll"
|
||||
@@ -227,11 +233,13 @@ Section "Strawberry" Strawberry
|
||||
File "libgmp-10.dll"
|
||||
File "libgnutls-30.dll"
|
||||
File "libgobject-2.0-0.dll"
|
||||
File "libgstadaptivedemux-1.0-0.dll"
|
||||
File "libgstapp-1.0-0.dll"
|
||||
File "libgstaudio-1.0-0.dll"
|
||||
File "libgstbadaudio-1.0-0.dll"
|
||||
File "libgstbase-1.0-0.dll"
|
||||
File "libgstfft-1.0-0.dll"
|
||||
File "libgstisoff-1.0-0.dll"
|
||||
File "libgstnet-1.0-0.dll"
|
||||
File "libgstpbutils-1.0-0.dll"
|
||||
File "libgstreamer-1.0-0.dll"
|
||||
@@ -240,63 +248,40 @@ Section "Strawberry" Strawberry
|
||||
File "libgstrtsp-1.0-0.dll"
|
||||
File "libgstsdp-1.0-0.dll"
|
||||
File "libgsttag-1.0-0.dll"
|
||||
File "libgsturidownloader-1.0-0.dll"
|
||||
File "libgstvideo-1.0-0.dll"
|
||||
File "libharfbuzz-0.dll"
|
||||
File "libhogweed-6.dll"
|
||||
File "libiconv-2.dll"
|
||||
File "libidn2-0.dll"
|
||||
File "libintl-8.dll"
|
||||
File "libjpeg-9.dll"
|
||||
File "liblzma-5.dll"
|
||||
File "libmp3lame-0.dll"
|
||||
File "libnettle-8.dll"
|
||||
File "libogg-0.dll"
|
||||
File "libnghttp2.dll"
|
||||
File "libopenmpt-0.dll"
|
||||
File "libopus-0.dll"
|
||||
File "liborc-0.4-0.dll"
|
||||
File "libpcre-1.dll"
|
||||
File "libpcre2-16-0.dll"
|
||||
File "libpng16-16.dll"
|
||||
File "libprotobuf-29.dll"
|
||||
File "libprotobuf-30.dll"
|
||||
File "libpsl-5.dll"
|
||||
File "libsoup-2.4-1.dll"
|
||||
File "libspeex-1.dll"
|
||||
File "libqtsparkle-qt6.dll"
|
||||
File "libsoup-3.0-0.dll"
|
||||
File "libsqlite3-0.dll"
|
||||
File "libssp-0.dll"
|
||||
File "libstdc++-6.dll"
|
||||
File "libtag.dll"
|
||||
File "libtasn1-6.dll"
|
||||
File "libunistring-2.dll"
|
||||
File "libvorbis-0.dll"
|
||||
File "libvorbisenc-2.dll"
|
||||
File "libvorbisfile-3.dll"
|
||||
File "libwavpack-1.dll"
|
||||
File "libwinpthread-1.dll"
|
||||
File "libxml2-2.dll"
|
||||
File "libzstd.dll"
|
||||
File "postproc-55.dll"
|
||||
File "swresample-3.dll"
|
||||
File "swscale-5.dll"
|
||||
File "postproc-56.dll"
|
||||
File "swresample-4.dll"
|
||||
File "swscale-6.dll"
|
||||
File "zlib1.dll"
|
||||
|
||||
!ifdef with_qt6
|
||||
File "Qt6Concurrent.dll"
|
||||
File "Qt6Core.dll"
|
||||
File "Qt6Gui.dll"
|
||||
File "Qt6Network.dll"
|
||||
File "Qt6Sql.dll"
|
||||
File "Qt6Widgets.dll"
|
||||
File "libqtsparkle-qt6.dll"
|
||||
!else
|
||||
File "Qt5Concurrent.dll"
|
||||
File "Qt5Core.dll"
|
||||
File "Qt5Gui.dll"
|
||||
File "Qt5Network.dll"
|
||||
File "Qt5Sql.dll"
|
||||
File "Qt5Widgets.dll"
|
||||
File "libqtsparkle-qt5.dll"
|
||||
!endif
|
||||
|
||||
!ifdef debug
|
||||
File "gdb.exe"
|
||||
File "libexpat-1.dll"
|
||||
@@ -306,6 +291,82 @@ Section "Strawberry" Strawberry
|
||||
File "libtermcap.dll"
|
||||
!endif
|
||||
|
||||
!endif ; MinGW
|
||||
|
||||
; MSVC specific files
|
||||
|
||||
!ifdef msvc
|
||||
|
||||
!ifdef arch_x86
|
||||
File "libcrypto-1_1.dll"
|
||||
File "libssl-1_1.dll"
|
||||
!endif
|
||||
!ifdef arch_x64
|
||||
File "libcrypto-1_1-x64.dll"
|
||||
File "libssl-1_1-x64.dll"
|
||||
!endif
|
||||
|
||||
File "avcodec-58.dll"
|
||||
File "avfilter-7.dll"
|
||||
File "avformat-58.dll"
|
||||
File "avutil-56.dll"
|
||||
File "bz2.dll"
|
||||
File "chromaprint.dll"
|
||||
File "ffi-7.dll"
|
||||
File "gio-2.0-0.dll"
|
||||
File "glib-2.0-0.dll"
|
||||
File "gmodule-2.0-0.dll"
|
||||
File "gnutls.dll"
|
||||
File "gobject-2.0-0.dll"
|
||||
File "gstadaptivedemux-1.0-0.dll"
|
||||
File "gstapp-1.0-0.dll"
|
||||
File "gstaudio-1.0-0.dll"
|
||||
File "gstbase-1.0-0.dll"
|
||||
File "gstfft-1.0-0.dll"
|
||||
File "gstisoff-1.0-0.dll"
|
||||
File "gstnet-1.0-0.dll"
|
||||
File "gstpbutils-1.0-0.dll"
|
||||
File "gstreamer-1.0-0.dll"
|
||||
File "gstriff-1.0-0.dll"
|
||||
File "gstrtp-1.0-0.dll"
|
||||
File "gstrtsp-1.0-0.dll"
|
||||
File "gstsdp-1.0-0.dll"
|
||||
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 "intl-8.dll"
|
||||
File "libprotobuf.dll"
|
||||
File "opus-0.dll"
|
||||
File "orc-0.4-0.dll"
|
||||
File "psl-5.dll"
|
||||
File "soup-2.4-1.dll"
|
||||
File "sqlite3-0.dll"
|
||||
File "swresample-3.dll"
|
||||
File "tag.dll"
|
||||
File "z-1.dll"
|
||||
|
||||
!endif ; MSVC
|
||||
|
||||
; Common files
|
||||
|
||||
File "libfftw3-3.dll"
|
||||
File "libFLAC-8.dll"
|
||||
File "libiconv-2.dll"
|
||||
File "libmp3lame-0.dll"
|
||||
File "libogg-0.dll"
|
||||
File "libspeex-1.dll"
|
||||
File "libvorbis-0.dll"
|
||||
File "libvorbisenc-2.dll"
|
||||
File "libwinpthread-1.dll"
|
||||
File "libxml2-2.dll"
|
||||
File "Qt6Concurrent.dll"
|
||||
File "Qt6Core.dll"
|
||||
File "Qt6Gui.dll"
|
||||
File "Qt6Network.dll"
|
||||
File "Qt6Sql.dll"
|
||||
File "Qt6Widgets.dll"
|
||||
|
||||
; Register Strawberry with Default Programs
|
||||
Var /GLOBAL AppIcon
|
||||
Var /GLOBAL AppExe
|
||||
@@ -340,7 +401,12 @@ SectionEnd
|
||||
|
||||
Section "GIO modules" gio-modules
|
||||
SetOutPath "$INSTDIR\gio-modules"
|
||||
!ifdef mingw
|
||||
File "/oname=libgiognutls.dll" "gio-modules\libgiognutls.dll"
|
||||
!endif
|
||||
!ifdef msvc
|
||||
File "/oname=giognutls.dll" "gio-modules\giognutls.dll"
|
||||
!endif
|
||||
SectionEnd
|
||||
|
||||
Section "Qt Platform plugins" platforms
|
||||
@@ -353,6 +419,16 @@ Section "Qt styles" styles
|
||||
File "/oname=qwindowsvistastyle.dll" "styles\qwindowsvistastyle.dll"
|
||||
SectionEnd
|
||||
|
||||
Section "Qt TLS plugins" tls
|
||||
SetOutPath "$INSTDIR\tls"
|
||||
File "/oname=qopensslbackend.dll" "tls\qopensslbackend.dll"
|
||||
SectionEnd
|
||||
|
||||
Section "Qt SQL Drivers" sqldrivers
|
||||
SetOutPath "$INSTDIR\sqldrivers"
|
||||
File "/oname=qsqlite.dll" "sqldrivers\qsqlite.dll"
|
||||
SectionEnd
|
||||
|
||||
Section "Qt imageformats" imageformats
|
||||
SetOutPath "$INSTDIR\imageformats"
|
||||
File "/oname=qgif.dll" "imageformats\qgif.dll"
|
||||
@@ -360,23 +436,15 @@ Section "Qt imageformats" imageformats
|
||||
File "/oname=qjpeg.dll" "imageformats\qjpeg.dll"
|
||||
SectionEnd
|
||||
|
||||
!ifdef with_qt6
|
||||
Section "Qt TLS plugins" tls
|
||||
SetOutPath "$INSTDIR\tls"
|
||||
File "/oname=qopensslbackend.dll" "tls\qopensslbackend.dll"
|
||||
SectionEnd
|
||||
!endif
|
||||
|
||||
Section "Qt SQL Drivers" sqldrivers
|
||||
SetOutPath "$INSTDIR\sqldrivers"
|
||||
File "/oname=qsqlite.dll" "sqldrivers\qsqlite.dll"
|
||||
SectionEnd
|
||||
|
||||
Section "Gstreamer plugins" gstreamer-plugins
|
||||
SetOutPath "$INSTDIR\gstreamer-plugins"
|
||||
|
||||
!ifdef mingw
|
||||
File "/oname=libgstaiff.dll" "gstreamer-plugins\libgstaiff.dll"
|
||||
File "/oname=libgstapetag.dll" "gstreamer-plugins\libgstapetag.dll"
|
||||
File "/oname=libgstapp.dll" "gstreamer-plugins\libgstapp.dll"
|
||||
File "/oname=libgstcoreelements.dll" "gstreamer-plugins\libgstcoreelements.dll"
|
||||
File "/oname=libgstasf.dll" "gstreamer-plugins\libgstasf.dll"
|
||||
File "/oname=libgstasfmux.dll" "gstreamer-plugins\libgstasfmux.dll"
|
||||
File "/oname=libgstaudioconvert.dll" "gstreamer-plugins\libgstaudioconvert.dll"
|
||||
File "/oname=libgstaudiofx.dll" "gstreamer-plugins\libgstaudiofx.dll"
|
||||
File "/oname=libgstaudiomixer.dll" "gstreamer-plugins\libgstaudiomixer.dll"
|
||||
@@ -385,43 +453,90 @@ Section "Gstreamer plugins" gstreamer-plugins
|
||||
File "/oname=libgstaudioresample.dll" "gstreamer-plugins\libgstaudioresample.dll"
|
||||
File "/oname=libgstaudiotestsrc.dll" "gstreamer-plugins\libgstaudiotestsrc.dll"
|
||||
File "/oname=libgstautodetect.dll" "gstreamer-plugins\libgstautodetect.dll"
|
||||
File "/oname=libgstplayback.dll" "gstreamer-plugins\libgstplayback.dll"
|
||||
File "/oname=libgstvolume.dll" "gstreamer-plugins\libgstvolume.dll"
|
||||
File "/oname=libgstspectrum.dll" "gstreamer-plugins\libgstspectrum.dll"
|
||||
File "/oname=libgstequalizer.dll" "gstreamer-plugins\libgstequalizer.dll"
|
||||
File "/oname=libgstreplaygain.dll" "gstreamer-plugins\libgstreplaygain.dll"
|
||||
File "/oname=libgsttypefindfunctions.dll" "gstreamer-plugins\libgsttypefindfunctions.dll"
|
||||
File "/oname=libgstgio.dll" "gstreamer-plugins\libgstgio.dll"
|
||||
File "/oname=libgstdirectsound.dll" "gstreamer-plugins\libgstdirectsound.dll"
|
||||
File "/oname=libgstwasapi.dll" "gstreamer-plugins\libgstwasapi.dll"
|
||||
File "/oname=libgstapetag.dll" "gstreamer-plugins\libgstapetag.dll"
|
||||
File "/oname=libgsticydemux.dll" "gstreamer-plugins\libgsticydemux.dll"
|
||||
File "/oname=libgstid3demux.dll" "gstreamer-plugins\libgstid3demux.dll"
|
||||
File "/oname=libgsttaglib.dll" "gstreamer-plugins\libgsttaglib.dll"
|
||||
File "/oname=libgsttcp.dll" "gstreamer-plugins\libgsttcp.dll"
|
||||
File "/oname=libgstudp.dll" "gstreamer-plugins\libgstudp.dll"
|
||||
File "/oname=libgstsoup.dll" "gstreamer-plugins\libgstsoup.dll"
|
||||
File "/oname=libgstcdio.dll" "gstreamer-plugins\libgstcdio.dll"
|
||||
File "/oname=libgstcoreelements.dll" "gstreamer-plugins\libgstcoreelements.dll"
|
||||
File "/oname=libgstdash.dll" "gstreamer-plugins\libgstdash.dll"
|
||||
File "/oname=libgstdirectsound.dll" "gstreamer-plugins\libgstdirectsound.dll"
|
||||
File "/oname=libgstequalizer.dll" "gstreamer-plugins\libgstequalizer.dll"
|
||||
File "/oname=libgstflac.dll" "gstreamer-plugins\libgstflac.dll"
|
||||
File "/oname=libgstwavparse.dll" "gstreamer-plugins\libgstwavparse.dll"
|
||||
File "/oname=libgstwavpack.dll" "gstreamer-plugins\libgstwavpack.dll"
|
||||
File "/oname=libgstogg.dll" "gstreamer-plugins\libgstogg.dll"
|
||||
File "/oname=libgstvorbis.dll" "gstreamer-plugins\libgstvorbis.dll"
|
||||
File "/oname=libgstopus.dll" "gstreamer-plugins\libgstopus.dll"
|
||||
File "/oname=libgstopusparse.dll" "gstreamer-plugins\libgstopusparse.dll"
|
||||
File "/oname=libgstspeex.dll" "gstreamer-plugins\libgstspeex.dll"
|
||||
File "/oname=libgstlame.dll" "gstreamer-plugins\libgstlame.dll"
|
||||
File "/oname=libgstaiff.dll" "gstreamer-plugins\libgstaiff.dll"
|
||||
File "/oname=libgstfaac.dll" "gstreamer-plugins\libgstfaac.dll"
|
||||
File "/oname=libgstfaad.dll" "gstreamer-plugins\libgstfaad.dll"
|
||||
File "/oname=libgstgio.dll" "gstreamer-plugins\libgstgio.dll"
|
||||
File "/oname=libgsticydemux.dll" "gstreamer-plugins\libgsticydemux.dll"
|
||||
File "/oname=libgstid3demux.dll" "gstreamer-plugins\libgstid3demux.dll"
|
||||
File "/oname=libgstisomp4.dll" "gstreamer-plugins\libgstisomp4.dll"
|
||||
File "/oname=libgstasf.dll" "gstreamer-plugins\libgstasf.dll"
|
||||
File "/oname=libgstasfmux.dll" "gstreamer-plugins\libgstasfmux.dll"
|
||||
File "/oname=libgstlame.dll" "gstreamer-plugins\libgstlame.dll"
|
||||
File "/oname=libgstlibav.dll" "gstreamer-plugins\libgstlibav.dll"
|
||||
File "/oname=libgstogg.dll" "gstreamer-plugins\libgstogg.dll"
|
||||
File "/oname=libgstopenmpt.dll" "gstreamer-plugins\libgstopenmpt.dll"
|
||||
File "/oname=libgstopus.dll" "gstreamer-plugins\libgstopus.dll"
|
||||
File "/oname=libgstopusparse.dll" "gstreamer-plugins\libgstopusparse.dll"
|
||||
File "/oname=libgstpbtypes.dll" "gstreamer-plugins\libgstpbtypes.dll"
|
||||
File "/oname=libgstplayback.dll" "gstreamer-plugins\libgstplayback.dll"
|
||||
File "/oname=libgstreplaygain.dll" "gstreamer-plugins\libgstreplaygain.dll"
|
||||
File "/oname=libgstrtp.dll" "gstreamer-plugins\libgstrtp.dll"
|
||||
File "/oname=libgstrtsp.dll" "gstreamer-plugins\libgstrtsp.dll"
|
||||
File "/oname=libgstopenmpt.dll" "gstreamer-plugins\libgstopenmpt.dll"
|
||||
File "/oname=libgstsoup.dll" "gstreamer-plugins\libgstsoup.dll"
|
||||
File "/oname=libgstspectrum.dll" "gstreamer-plugins\libgstspectrum.dll"
|
||||
File "/oname=libgstspeex.dll" "gstreamer-plugins\libgstspeex.dll"
|
||||
File "/oname=libgsttaglib.dll" "gstreamer-plugins\libgsttaglib.dll"
|
||||
File "/oname=libgsttcp.dll" "gstreamer-plugins\libgsttcp.dll"
|
||||
File "/oname=libgsttypefindfunctions.dll" "gstreamer-plugins\libgsttypefindfunctions.dll"
|
||||
File "/oname=libgstudp.dll" "gstreamer-plugins\libgstudp.dll"
|
||||
File "/oname=libgstvolume.dll" "gstreamer-plugins\libgstvolume.dll"
|
||||
File "/oname=libgstvorbis.dll" "gstreamer-plugins\libgstvorbis.dll"
|
||||
File "/oname=libgstwasapi.dll" "gstreamer-plugins\libgstwasapi.dll"
|
||||
File "/oname=libgstwavpack.dll" "gstreamer-plugins\libgstwavpack.dll"
|
||||
File "/oname=libgstwavparse.dll" "gstreamer-plugins\libgstwavparse.dll"
|
||||
File "/oname=libgstxingmux.dll" "gstreamer-plugins\libgstxingmux.dll"
|
||||
!endif ; MinGW
|
||||
|
||||
!ifdef msvc
|
||||
File "/oname=gstaiff.dll" "gstreamer-plugins\gstaiff.dll"
|
||||
File "/oname=gstapetag.dll" "gstreamer-plugins\gstapetag.dll"
|
||||
File "/oname=gstapp.dll" "gstreamer-plugins\gstapp.dll"
|
||||
File "/oname=gstasf.dll" "gstreamer-plugins\gstasf.dll"
|
||||
File "/oname=gstasfmux.dll" "gstreamer-plugins\gstasfmux.dll"
|
||||
File "/oname=gstaudioconvert.dll" "gstreamer-plugins\gstaudioconvert.dll"
|
||||
File "/oname=gstaudiofx.dll" "gstreamer-plugins\gstaudiofx.dll"
|
||||
File "/oname=gstaudiomixer.dll" "gstreamer-plugins\gstaudiomixer.dll"
|
||||
File "/oname=gstaudioparsers.dll" "gstreamer-plugins\gstaudioparsers.dll"
|
||||
File "/oname=gstaudiorate.dll" "gstreamer-plugins\gstaudiorate.dll"
|
||||
File "/oname=gstaudioresample.dll" "gstreamer-plugins\gstaudioresample.dll"
|
||||
File "/oname=gstaudiotestsrc.dll" "gstreamer-plugins\gstaudiotestsrc.dll"
|
||||
File "/oname=gstautodetect.dll" "gstreamer-plugins\gstautodetect.dll"
|
||||
File "/oname=gstcoreelements.dll" "gstreamer-plugins\gstcoreelements.dll"
|
||||
File "/oname=gstdash.dll" "gstreamer-plugins\gstdash.dll"
|
||||
File "/oname=gstdirectsound.dll" "gstreamer-plugins\gstdirectsound.dll"
|
||||
File "/oname=gstequalizer.dll" "gstreamer-plugins\gstequalizer.dll"
|
||||
File "/oname=gstflac.dll" "gstreamer-plugins\gstflac.dll"
|
||||
File "/oname=gstgio.dll" "gstreamer-plugins\gstgio.dll"
|
||||
File "/oname=gsticydemux.dll" "gstreamer-plugins\gsticydemux.dll"
|
||||
File "/oname=gstid3demux.dll" "gstreamer-plugins\gstid3demux.dll"
|
||||
File "/oname=gstisomp4.dll" "gstreamer-plugins\gstisomp4.dll"
|
||||
File "/oname=gstlame.dll" "gstreamer-plugins\gstlame.dll"
|
||||
File "/oname=gstlibav.dll" "gstreamer-plugins\gstlibav.dll"
|
||||
File "/oname=gstogg.dll" "gstreamer-plugins\gstogg.dll"
|
||||
File "/oname=gstopus.dll" "gstreamer-plugins\gstopus.dll"
|
||||
File "/oname=gstopusparse.dll" "gstreamer-plugins\gstopusparse.dll"
|
||||
File "/oname=gstplayback.dll" "gstreamer-plugins\gstplayback.dll"
|
||||
File "/oname=gstreplaygain.dll" "gstreamer-plugins\gstreplaygain.dll"
|
||||
File "/oname=gstrtp.dll" "gstreamer-plugins\gstrtp.dll"
|
||||
File "/oname=gstrtsp.dll" "gstreamer-plugins\gstrtsp.dll"
|
||||
File "/oname=gstsoup.dll" "gstreamer-plugins\gstsoup.dll"
|
||||
File "/oname=gstspectrum.dll" "gstreamer-plugins\gstspectrum.dll"
|
||||
File "/oname=gstspeex.dll" "gstreamer-plugins\gstspeex.dll"
|
||||
File "/oname=gsttcp.dll" "gstreamer-plugins\gsttcp.dll"
|
||||
File "/oname=gsttypefindfunctions.dll" "gstreamer-plugins\gsttypefindfunctions.dll"
|
||||
File "/oname=gstudp.dll" "gstreamer-plugins\gstudp.dll"
|
||||
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=gstwavpack.dll" "gstreamer-plugins\gstwavpack.dll"
|
||||
File "/oname=gstwavparse.dll" "gstreamer-plugins\gstwavparse.dll"
|
||||
File "/oname=gstxingmux.dll" "gstreamer-plugins\gstxingmux.dll"
|
||||
!endif ; MSVC
|
||||
|
||||
SectionEnd
|
||||
|
||||
@@ -460,13 +575,19 @@ Section "Uninstall"
|
||||
|
||||
; Delete all the files
|
||||
|
||||
Delete "$INSTDIR\strawberry.ico"
|
||||
Delete "$INSTDIR\strawberry.exe"
|
||||
Delete "$INSTDIR\strawberry-tagreader.exe"
|
||||
Delete "$INSTDIR\strawberry.ico"
|
||||
!ifdef mingw
|
||||
Delete "$INSTDIR\sqlite3.exe"
|
||||
!endif
|
||||
Delete "$INSTDIR\gst-launch-1.0.exe"
|
||||
Delete "$INSTDIR\gst-discoverer-1.0.exe"
|
||||
|
||||
; MinGW specific files
|
||||
|
||||
!ifdef mingw
|
||||
|
||||
!ifdef arch_x86
|
||||
Delete "$INSTDIR\libgcc_s_sjlj-1.dll"
|
||||
Delete "$INSTDIR\libcrypto-3.dll"
|
||||
@@ -479,10 +600,10 @@ Section "Uninstall"
|
||||
Delete "$INSTDIR\libssl-3-x64.dll"
|
||||
!endif
|
||||
|
||||
Delete "$INSTDIR\avcodec-58.dll"
|
||||
Delete "$INSTDIR\avfilter-7.dll"
|
||||
Delete "$INSTDIR\avformat-58.dll"
|
||||
Delete "$INSTDIR\avutil-56.dll"
|
||||
Delete "$INSTDIR\avcodec-59.dll"
|
||||
Delete "$INSTDIR\avfilter-8.dll"
|
||||
Delete "$INSTDIR\avformat-59.dll"
|
||||
Delete "$INSTDIR\avutil-57.dll"
|
||||
Delete "$INSTDIR\libbrotlicommon.dll"
|
||||
Delete "$INSTDIR\libbrotlidec.dll"
|
||||
Delete "$INSTDIR\libbz2.dll"
|
||||
@@ -491,9 +612,7 @@ Section "Uninstall"
|
||||
Delete "$INSTDIR\libdl.dll"
|
||||
Delete "$INSTDIR\libfaac-0.dll"
|
||||
Delete "$INSTDIR\libfaad-2.dll"
|
||||
Delete "$INSTDIR\libffi-7.dll"
|
||||
Delete "$INSTDIR\libfftw3-3.dll"
|
||||
Delete "$INSTDIR\libFLAC-8.dll"
|
||||
Delete "$INSTDIR\libffi-8.dll"
|
||||
Delete "$INSTDIR\libfreetype-6.dll"
|
||||
Delete "$INSTDIR\libgio-2.0-0.dll"
|
||||
Delete "$INSTDIR\libglib-2.0-0.dll"
|
||||
@@ -501,11 +620,13 @@ Section "Uninstall"
|
||||
Delete "$INSTDIR\libgmp-10.dll"
|
||||
Delete "$INSTDIR\libgnutls-30.dll"
|
||||
Delete "$INSTDIR\libgobject-2.0-0.dll"
|
||||
Delete "$INSTDIR\libgstadaptivedemux-1.0-0.dll"
|
||||
Delete "$INSTDIR\libgstapp-1.0-0.dll"
|
||||
Delete "$INSTDIR\libgstaudio-1.0-0.dll"
|
||||
Delete "$INSTDIR\libgstbadaudio-1.0-0.dll"
|
||||
Delete "$INSTDIR\libgstbase-1.0-0.dll"
|
||||
Delete "$INSTDIR\libgstfft-1.0-0.dll"
|
||||
Delete "$INSTDIR\libgstisoff-1.0-0.dll"
|
||||
Delete "$INSTDIR\libgstnet-1.0-0.dll"
|
||||
Delete "$INSTDIR\libgstpbutils-1.0-0.dll"
|
||||
Delete "$INSTDIR\libgstreamer-1.0-0.dll"
|
||||
@@ -514,57 +635,38 @@ Section "Uninstall"
|
||||
Delete "$INSTDIR\libgstrtsp-1.0-0.dll"
|
||||
Delete "$INSTDIR\libgstsdp-1.0-0.dll"
|
||||
Delete "$INSTDIR\libgsttag-1.0-0.dll"
|
||||
Delete "$INSTDIR\libgsturidownloader-1.0-0.dll"
|
||||
Delete "$INSTDIR\libgstvideo-1.0-0.dll"
|
||||
Delete "$INSTDIR\libharfbuzz-0.dll"
|
||||
Delete "$INSTDIR\libhogweed-6.dll"
|
||||
Delete "$INSTDIR\libiconv-2.dll"
|
||||
Delete "$INSTDIR\libidn2-0.dll"
|
||||
Delete "$INSTDIR\libintl-8.dll"
|
||||
Delete "$INSTDIR\libjpeg-9.dll"
|
||||
Delete "$INSTDIR\liblzma-5.dll"
|
||||
Delete "$INSTDIR\libmp3lame-0.dll"
|
||||
Delete "$INSTDIR\libnettle-8.dll"
|
||||
Delete "$INSTDIR\libogg-0.dll"
|
||||
Delete "$INSTDIR\libnghttp2.dll"
|
||||
Delete "$INSTDIR\libopenmpt-0.dll"
|
||||
Delete "$INSTDIR\libopus-0.dll"
|
||||
Delete "$INSTDIR\liborc-0.4-0.dll"
|
||||
Delete "$INSTDIR\libpcre-1.dll"
|
||||
Delete "$INSTDIR\libpcre2-16-0.dll"
|
||||
Delete "$INSTDIR\libpng16-16.dll"
|
||||
Delete "$INSTDIR\libprotobuf-29.dll"
|
||||
Delete "$INSTDIR\libprotobuf-30.dll"
|
||||
Delete "$INSTDIR\libpsl-5.dll"
|
||||
Delete "$INSTDIR\libqtsparkle-qt5.dll"
|
||||
Delete "$INSTDIR\libqtsparkle-qt6.dll"
|
||||
Delete "$INSTDIR\libsoup-2.4-1.dll"
|
||||
Delete "$INSTDIR\libspeex-1.dll"
|
||||
Delete "$INSTDIR\libsoup-3.0-0.dll"
|
||||
Delete "$INSTDIR\libsqlite3-0.dll"
|
||||
Delete "$INSTDIR\libssp-0.dll"
|
||||
Delete "$INSTDIR\libstdc++-6.dll"
|
||||
Delete "$INSTDIR\libtag.dll"
|
||||
Delete "$INSTDIR\libtasn1-6.dll"
|
||||
Delete "$INSTDIR\libunistring-2.dll"
|
||||
Delete "$INSTDIR\libvorbis-0.dll"
|
||||
Delete "$INSTDIR\libvorbisenc-2.dll"
|
||||
Delete "$INSTDIR\libvorbisfile-3.dll"
|
||||
Delete "$INSTDIR\libwavpack-1.dll"
|
||||
Delete "$INSTDIR\libwinpthread-1.dll"
|
||||
Delete "$INSTDIR\libxml2-2.dll"
|
||||
Delete "$INSTDIR\libzstd.dll"
|
||||
Delete "$INSTDIR\postproc-55.dll"
|
||||
Delete "$INSTDIR\Qt5Concurrent.dll"
|
||||
Delete "$INSTDIR\Qt5Core.dll"
|
||||
Delete "$INSTDIR\Qt5Gui.dll"
|
||||
Delete "$INSTDIR\Qt5Network.dll"
|
||||
Delete "$INSTDIR\Qt5Sql.dll"
|
||||
Delete "$INSTDIR\Qt5Widgets.dll"
|
||||
Delete "$INSTDIR\Qt6Concurrent.dll"
|
||||
Delete "$INSTDIR\Qt6Core.dll"
|
||||
Delete "$INSTDIR\Qt6Gui.dll"
|
||||
Delete "$INSTDIR\Qt6Network.dll"
|
||||
Delete "$INSTDIR\Qt6Sql.dll"
|
||||
Delete "$INSTDIR\Qt6Widgets.dll"
|
||||
Delete "$INSTDIR\swresample-3.dll"
|
||||
Delete "$INSTDIR\swscale-5.dll"
|
||||
Delete "$INSTDIR\postproc-56.dll"
|
||||
Delete "$INSTDIR\swresample-4.dll"
|
||||
Delete "$INSTDIR\swscale-6.dll"
|
||||
Delete "$INSTDIR\zlib1.dll"
|
||||
|
||||
!ifdef debug
|
||||
@@ -576,20 +678,106 @@ Section "Uninstall"
|
||||
Delete "$INSTDIR\libtermcap.dll"
|
||||
!endif
|
||||
|
||||
!endif ; MinGW
|
||||
|
||||
; MSVC specific files
|
||||
|
||||
!ifdef msvc
|
||||
|
||||
!ifdef arch_x86
|
||||
Delete "$INSTDIR\libcrypto-1_1.dll"
|
||||
Delete "$INSTDIR\libssl-1_1.dll"
|
||||
!endif
|
||||
!ifdef arch_x64
|
||||
Delete "$INSTDIR\libcrypto-1_1-x64.dll"
|
||||
Delete "$INSTDIR\libssl-1_1-x64.dll"
|
||||
!endif
|
||||
|
||||
Delete "$INSTDIR\avcodec-58.dll"
|
||||
Delete "$INSTDIR\avfilter-7.dll"
|
||||
Delete "$INSTDIR\avformat-58.dll"
|
||||
Delete "$INSTDIR\avutil-56.dll"
|
||||
Delete "$INSTDIR\bz2.dll"
|
||||
Delete "$INSTDIR\chromaprint.dll"
|
||||
Delete "$INSTDIR\ffi-7.dll"
|
||||
Delete "$INSTDIR\gio-2.0-0.dll"
|
||||
Delete "$INSTDIR\glib-2.0-0.dll"
|
||||
Delete "$INSTDIR\gmodule-2.0-0.dll"
|
||||
Delete "$INSTDIR\gnutls.dll"
|
||||
Delete "$INSTDIR\gobject-2.0-0.dll"
|
||||
Delete "$INSTDIR\gstadaptivedemux-1.0-0.dll"
|
||||
Delete "$INSTDIR\gstapp-1.0-0.dll"
|
||||
Delete "$INSTDIR\gstaudio-1.0-0.dll"
|
||||
Delete "$INSTDIR\gstbase-1.0-0.dll"
|
||||
Delete "$INSTDIR\gstfft-1.0-0.dll"
|
||||
Delete "$INSTDIR\gstisoff-1.0-0.dll"
|
||||
Delete "$INSTDIR\gstnet-1.0-0.dll"
|
||||
Delete "$INSTDIR\gstpbutils-1.0-0.dll"
|
||||
Delete "$INSTDIR\gstreamer-1.0-0.dll"
|
||||
Delete "$INSTDIR\gstriff-1.0-0.dll"
|
||||
Delete "$INSTDIR\gstrtp-1.0-0.dll"
|
||||
Delete "$INSTDIR\gstrtsp-1.0-0.dll"
|
||||
Delete "$INSTDIR\gstsdp-1.0-0.dll"
|
||||
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\intl-8.dll"
|
||||
Delete "$INSTDIR\libprotobuf.dll"
|
||||
Delete "$INSTDIR\opus-0.dll"
|
||||
Delete "$INSTDIR\orc-0.4-0.dll"
|
||||
Delete "$INSTDIR\psl-5.dll"
|
||||
Delete "$INSTDIR\soup-2.4-1.dll"
|
||||
Delete "$INSTDIR\sqlite3-0.dll"
|
||||
Delete "$INSTDIR\swresample-3.dll"
|
||||
Delete "$INSTDIR\tag.dll"
|
||||
Delete "$INSTDIR\z-1.dll"
|
||||
|
||||
!endif ; MSVC
|
||||
|
||||
; Common files
|
||||
|
||||
Delete "$INSTDIR\libfftw3-3.dll"
|
||||
Delete "$INSTDIR\libFLAC-8.dll"
|
||||
Delete "$INSTDIR\libiconv-2.dll"
|
||||
Delete "$INSTDIR\libmp3lame-0.dll"
|
||||
Delete "$INSTDIR\libogg-0.dll"
|
||||
Delete "$INSTDIR\libspeex-1.dll"
|
||||
Delete "$INSTDIR\libvorbis-0.dll"
|
||||
Delete "$INSTDIR\libvorbisenc-2.dll"
|
||||
Delete "$INSTDIR\libwinpthread-1.dll"
|
||||
Delete "$INSTDIR\libxml2-2.dll"
|
||||
Delete "$INSTDIR\Qt6Concurrent.dll"
|
||||
Delete "$INSTDIR\Qt6Core.dll"
|
||||
Delete "$INSTDIR\Qt6Gui.dll"
|
||||
Delete "$INSTDIR\Qt6Network.dll"
|
||||
Delete "$INSTDIR\Qt6Sql.dll"
|
||||
Delete "$INSTDIR\Qt6Widgets.dll"
|
||||
|
||||
!ifdef mingw
|
||||
Delete "$INSTDIR\gio-modules\libgiognutls.dll"
|
||||
!endif
|
||||
!ifdef msvc
|
||||
Delete "$INSTDIR\gio-modules\giognutls.dll"
|
||||
!endif
|
||||
|
||||
Delete "$INSTDIR\platforms\qwindows.dll"
|
||||
Delete "$INSTDIR\styles\qwindowsvistastyle.dll"
|
||||
!ifdef with_qt6
|
||||
Delete "$INSTDIR\tls\qopensslbackend.dll"
|
||||
!endif
|
||||
Delete "$INSTDIR\sqldrivers\qsqlite.dll"
|
||||
|
||||
Delete "$INSTDIR\imageformats\qgif.dll"
|
||||
Delete "$INSTDIR\imageformats\qico.dll"
|
||||
Delete "$INSTDIR\imageformats\qjpeg.dll"
|
||||
|
||||
; MinGW GStreamer plugins
|
||||
|
||||
!ifdef mingw
|
||||
Delete "$INSTDIR\gstreamer-plugins\libgstaiff.dll"
|
||||
Delete "$INSTDIR\gstreamer-plugins\libgstapetag.dll"
|
||||
Delete "$INSTDIR\gstreamer-plugins\libgstapp.dll"
|
||||
Delete "$INSTDIR\gstreamer-plugins\libgstcoreelements.dll"
|
||||
Delete "$INSTDIR\gstreamer-plugins\libgstasf.dll"
|
||||
Delete "$INSTDIR\gstreamer-plugins\libgstasfmux.dll"
|
||||
Delete "$INSTDIR\gstreamer-plugins\libgstaudioconvert.dll"
|
||||
Delete "$INSTDIR\gstreamer-plugins\libgstaudiofx.dll"
|
||||
Delete "$INSTDIR\gstreamer-plugins\libgstaudiomixer.dll"
|
||||
@@ -598,45 +786,94 @@ Section "Uninstall"
|
||||
Delete "$INSTDIR\gstreamer-plugins\libgstaudioresample.dll"
|
||||
Delete "$INSTDIR\gstreamer-plugins\libgstaudiotestsrc.dll"
|
||||
Delete "$INSTDIR\gstreamer-plugins\libgstautodetect.dll"
|
||||
Delete "$INSTDIR\gstreamer-plugins\libgstplayback.dll"
|
||||
Delete "$INSTDIR\gstreamer-plugins\libgstvolume.dll"
|
||||
Delete "$INSTDIR\gstreamer-plugins\libgstspectrum.dll"
|
||||
Delete "$INSTDIR\gstreamer-plugins\libgstequalizer.dll"
|
||||
Delete "$INSTDIR\gstreamer-plugins\libgstreplaygain.dll"
|
||||
Delete "$INSTDIR\gstreamer-plugins\libgsttypefindfunctions.dll"
|
||||
Delete "$INSTDIR\gstreamer-plugins\libgstgio.dll"
|
||||
Delete "$INSTDIR\gstreamer-plugins\libgstdirectsound.dll"
|
||||
Delete "$INSTDIR\gstreamer-plugins\libgstwasapi.dll"
|
||||
Delete "$INSTDIR\gstreamer-plugins\libgstapetag.dll"
|
||||
Delete "$INSTDIR\gstreamer-plugins\libgsticydemux.dll"
|
||||
Delete "$INSTDIR\gstreamer-plugins\libgstid3demux.dll"
|
||||
Delete "$INSTDIR\gstreamer-plugins\libgsttaglib.dll"
|
||||
Delete "$INSTDIR\gstreamer-plugins\libgsttcp.dll"
|
||||
Delete "$INSTDIR\gstreamer-plugins\libgstudp.dll"
|
||||
Delete "$INSTDIR\gstreamer-plugins\libgstsoup.dll"
|
||||
Delete "$INSTDIR\gstreamer-plugins\libgstcdio.dll"
|
||||
Delete "$INSTDIR\gstreamer-plugins\libgstcoreelements.dll"
|
||||
Delete "$INSTDIR\gstreamer-plugins\libgstdash.dll"
|
||||
Delete "$INSTDIR\gstreamer-plugins\libgstdirectsound.dll"
|
||||
Delete "$INSTDIR\gstreamer-plugins\libgstequalizer.dll"
|
||||
Delete "$INSTDIR\gstreamer-plugins\libgstflac.dll"
|
||||
Delete "$INSTDIR\gstreamer-plugins\libgstwavparse.dll"
|
||||
Delete "$INSTDIR\gstreamer-plugins\libgstwavpack.dll"
|
||||
Delete "$INSTDIR\gstreamer-plugins\libgstogg.dll"
|
||||
Delete "$INSTDIR\gstreamer-plugins\libgstvorbis.dll"
|
||||
Delete "$INSTDIR\gstreamer-plugins\libgstopus.dll"
|
||||
Delete "$INSTDIR\gstreamer-plugins\libgstopusparse.dll"
|
||||
Delete "$INSTDIR\gstreamer-plugins\libgstspeex.dll"
|
||||
Delete "$INSTDIR\gstreamer-plugins\libgstlame.dll"
|
||||
Delete "$INSTDIR\gstreamer-plugins\libgstaiff.dll"
|
||||
Delete "$INSTDIR\gstreamer-plugins\libgstfaac.dll"
|
||||
Delete "$INSTDIR\gstreamer-plugins\libgstfaad.dll"
|
||||
Delete "$INSTDIR\gstreamer-plugins\libgstgio.dll"
|
||||
Delete "$INSTDIR\gstreamer-plugins\libgsticydemux.dll"
|
||||
Delete "$INSTDIR\gstreamer-plugins\libgstid3demux.dll"
|
||||
Delete "$INSTDIR\gstreamer-plugins\libgstisomp4.dll"
|
||||
Delete "$INSTDIR\gstreamer-plugins\libgstasf.dll"
|
||||
Delete "$INSTDIR\gstreamer-plugins\libgstasfmux.dll"
|
||||
Delete "$INSTDIR\gstreamer-plugins\libgstlame.dll"
|
||||
Delete "$INSTDIR\gstreamer-plugins\libgstlibav.dll"
|
||||
Delete "$INSTDIR\gstreamer-plugins\libgstogg.dll"
|
||||
Delete "$INSTDIR\gstreamer-plugins\libgstopenmpt.dll"
|
||||
Delete "$INSTDIR\gstreamer-plugins\libgstopus.dll"
|
||||
Delete "$INSTDIR\gstreamer-plugins\libgstopusparse.dll"
|
||||
Delete "$INSTDIR\gstreamer-plugins\libgstpbtypes.dll"
|
||||
Delete "$INSTDIR\gstreamer-plugins\libgstplayback.dll"
|
||||
Delete "$INSTDIR\gstreamer-plugins\libgstreplaygain.dll"
|
||||
Delete "$INSTDIR\gstreamer-plugins\libgstrtp.dll"
|
||||
Delete "$INSTDIR\gstreamer-plugins\libgstrtsp.dll"
|
||||
Delete "$INSTDIR\gstreamer-plugins\libgstopenmpt.dll"
|
||||
Delete "$INSTDIR\gstreamer-plugins\libgstsoup.dll"
|
||||
Delete "$INSTDIR\gstreamer-plugins\libgstspectrum.dll"
|
||||
Delete "$INSTDIR\gstreamer-plugins\libgstspeex.dll"
|
||||
Delete "$INSTDIR\gstreamer-plugins\libgsttaglib.dll"
|
||||
Delete "$INSTDIR\gstreamer-plugins\libgsttcp.dll"
|
||||
Delete "$INSTDIR\gstreamer-plugins\libgsttypefindfunctions.dll"
|
||||
Delete "$INSTDIR\gstreamer-plugins\libgstudp.dll"
|
||||
Delete "$INSTDIR\gstreamer-plugins\libgstvolume.dll"
|
||||
Delete "$INSTDIR\gstreamer-plugins\libgstvorbis.dll"
|
||||
Delete "$INSTDIR\gstreamer-plugins\libgstwasapi.dll"
|
||||
Delete "$INSTDIR\gstreamer-plugins\libgstwavpack.dll"
|
||||
Delete "$INSTDIR\gstreamer-plugins\libgstwavparse.dll"
|
||||
Delete "$INSTDIR\gstreamer-plugins\libgstxingmux.dll"
|
||||
!endif ; mingw
|
||||
|
||||
Delete "$INSTDIR\Uninstall.exe"
|
||||
; MSVC GStreamer plugins
|
||||
|
||||
!ifdef msvc
|
||||
Delete "$INSTDIR\gstreamer-plugins\gstaiff.dll"
|
||||
Delete "$INSTDIR\gstreamer-plugins\gstapetag.dll"
|
||||
Delete "$INSTDIR\gstreamer-plugins\gstapp.dll"
|
||||
Delete "$INSTDIR\gstreamer-plugins\gstasf.dll"
|
||||
Delete "$INSTDIR\gstreamer-plugins\gstasfmux.dll"
|
||||
Delete "$INSTDIR\gstreamer-plugins\gstaudioconvert.dll"
|
||||
Delete "$INSTDIR\gstreamer-plugins\gstaudiofx.dll"
|
||||
Delete "$INSTDIR\gstreamer-plugins\gstaudiomixer.dll"
|
||||
Delete "$INSTDIR\gstreamer-plugins\gstaudioparsers.dll"
|
||||
Delete "$INSTDIR\gstreamer-plugins\gstaudiorate.dll"
|
||||
Delete "$INSTDIR\gstreamer-plugins\gstaudioresample.dll"
|
||||
Delete "$INSTDIR\gstreamer-plugins\gstaudiotestsrc.dll"
|
||||
Delete "$INSTDIR\gstreamer-plugins\gstautodetect.dll"
|
||||
Delete "$INSTDIR\gstreamer-plugins\gstcoreelements.dll"
|
||||
Delete "$INSTDIR\gstreamer-plugins\gstdash.dll"
|
||||
Delete "$INSTDIR\gstreamer-plugins\gstdirectsound.dll"
|
||||
Delete "$INSTDIR\gstreamer-plugins\gstequalizer.dll"
|
||||
Delete "$INSTDIR\gstreamer-plugins\gstflac.dll"
|
||||
Delete "$INSTDIR\gstreamer-plugins\gstgio.dll"
|
||||
Delete "$INSTDIR\gstreamer-plugins\gsticydemux.dll"
|
||||
Delete "$INSTDIR\gstreamer-plugins\gstid3demux.dll"
|
||||
Delete "$INSTDIR\gstreamer-plugins\gstisomp4.dll"
|
||||
Delete "$INSTDIR\gstreamer-plugins\gstlame.dll"
|
||||
Delete "$INSTDIR\gstreamer-plugins\gstlibav.dll"
|
||||
Delete "$INSTDIR\gstreamer-plugins\gstogg.dll"
|
||||
Delete "$INSTDIR\gstreamer-plugins\gstopus.dll"
|
||||
Delete "$INSTDIR\gstreamer-plugins\gstopusparse.dll"
|
||||
Delete "$INSTDIR\gstreamer-plugins\gstplayback.dll"
|
||||
Delete "$INSTDIR\gstreamer-plugins\gstreplaygain.dll"
|
||||
Delete "$INSTDIR\gstreamer-plugins\gstrtp.dll"
|
||||
Delete "$INSTDIR\gstreamer-plugins\gstrtsp.dll"
|
||||
Delete "$INSTDIR\gstreamer-plugins\gstsoup.dll"
|
||||
Delete "$INSTDIR\gstreamer-plugins\gstspectrum.dll"
|
||||
Delete "$INSTDIR\gstreamer-plugins\gstspeex.dll"
|
||||
Delete "$INSTDIR\gstreamer-plugins\gsttcp.dll"
|
||||
Delete "$INSTDIR\gstreamer-plugins\gsttypefindfunctions.dll"
|
||||
Delete "$INSTDIR\gstreamer-plugins\gstudp.dll"
|
||||
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\gstwavpack.dll"
|
||||
Delete "$INSTDIR\gstreamer-plugins\gstwavparse.dll"
|
||||
Delete "$INSTDIR\gstreamer-plugins\gstxingmux.dll"
|
||||
!endif ; msvc
|
||||
|
||||
Delete $INSTDIR\Uninstall.exe"
|
||||
|
||||
; Remove the installation folders.
|
||||
RMDir "$INSTDIR\platforms"
|
||||
|
||||
@@ -33,24 +33,13 @@
|
||||
|
||||
#include "gstfastspectrum.h"
|
||||
|
||||
GST_DEBUG_CATEGORY_STATIC (gst_fastspectrum_debug);
|
||||
#define GST_CAT_DEFAULT gst_fastspectrum_debug
|
||||
GST_DEBUG_CATEGORY_STATIC(gst_fastspectrum_debug);
|
||||
|
||||
/* elementfactory information */
|
||||
#if G_BYTE_ORDER == G_LITTLE_ENDIAN
|
||||
# define FORMATS "{ S16LE, S24LE, S32LE, F32LE, F64LE }"
|
||||
#else
|
||||
# define FORMATS "{ S16BE, S24BE, S32BE, F32BE, F64BE }"
|
||||
#endif
|
||||
namespace {
|
||||
|
||||
#define ALLOWED_CAPS \
|
||||
GST_AUDIO_CAPS_MAKE (FORMATS) ", " \
|
||||
"layout = (string) interleaved, " \
|
||||
"channels = 1"
|
||||
|
||||
/* Spectrum properties */
|
||||
#define DEFAULT_INTERVAL (GST_SECOND / 10)
|
||||
#define DEFAULT_BANDS 128
|
||||
// Spectrum properties
|
||||
constexpr auto DEFAULT_INTERVAL = (GST_SECOND / 10);
|
||||
constexpr auto DEFAULT_BANDS = 128;
|
||||
|
||||
enum {
|
||||
PROP_0,
|
||||
@@ -58,93 +47,97 @@ enum {
|
||||
PROP_BANDS
|
||||
};
|
||||
|
||||
} // namespace
|
||||
|
||||
#define gst_fastspectrum_parent_class parent_class
|
||||
G_DEFINE_TYPE (GstFastSpectrum, gst_fastspectrum, GST_TYPE_AUDIO_FILTER)
|
||||
G_DEFINE_TYPE(GstFastSpectrum, gst_fastspectrum, GST_TYPE_AUDIO_FILTER)
|
||||
|
||||
static void gst_fastspectrum_finalize (GObject * object);
|
||||
static void gst_fastspectrum_set_property (GObject * object, guint prop_id, const GValue * value, GParamSpec * pspec);
|
||||
static void gst_fastspectrum_get_property (GObject * object, guint prop_id, GValue * value, GParamSpec * pspec);
|
||||
static gboolean gst_fastspectrum_start (GstBaseTransform * trans);
|
||||
static gboolean gst_fastspectrum_stop (GstBaseTransform * trans);
|
||||
static GstFlowReturn gst_fastspectrum_transform_ip (GstBaseTransform *trans, GstBuffer *buffer);
|
||||
static gboolean gst_fastspectrum_setup (GstAudioFilter * base, const GstAudioInfo * info);
|
||||
static void gst_fastspectrum_finalize(GObject *object);
|
||||
static void gst_fastspectrum_set_property(GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec);
|
||||
static void gst_fastspectrum_get_property(GObject *object, guint prop_id, GValue *value, GParamSpec *pspec);
|
||||
static gboolean gst_fastspectrum_start(GstBaseTransform *trans);
|
||||
static gboolean gst_fastspectrum_stop(GstBaseTransform *trans);
|
||||
static GstFlowReturn gst_fastspectrum_transform_ip(GstBaseTransform *trans, GstBuffer *buffer);
|
||||
static gboolean gst_fastspectrum_setup(GstAudioFilter *base, const GstAudioInfo *info);
|
||||
|
||||
static void gst_fastspectrum_class_init (GstFastSpectrumClass * klass) {
|
||||
static void gst_fastspectrum_class_init(GstFastSpectrumClass *klass) {
|
||||
|
||||
GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
|
||||
GstElementClass *element_class = GST_ELEMENT_CLASS (klass);
|
||||
GstBaseTransformClass *trans_class = GST_BASE_TRANSFORM_CLASS (klass);
|
||||
GstAudioFilterClass *filter_class = GST_AUDIO_FILTER_CLASS (klass);
|
||||
GObjectClass *gobject_class = G_OBJECT_CLASS(klass);
|
||||
GstElementClass *element_class = GST_ELEMENT_CLASS(klass);
|
||||
GstBaseTransformClass *trans_class = GST_BASE_TRANSFORM_CLASS(klass);
|
||||
GstAudioFilterClass *filter_class = GST_AUDIO_FILTER_CLASS(klass);
|
||||
GstCaps *caps = nullptr;
|
||||
|
||||
gobject_class->set_property = gst_fastspectrum_set_property;
|
||||
gobject_class->get_property = gst_fastspectrum_get_property;
|
||||
gobject_class->finalize = gst_fastspectrum_finalize;
|
||||
|
||||
trans_class->start = GST_DEBUG_FUNCPTR (gst_fastspectrum_start);
|
||||
trans_class->stop = GST_DEBUG_FUNCPTR (gst_fastspectrum_stop);
|
||||
trans_class->transform_ip = GST_DEBUG_FUNCPTR (gst_fastspectrum_transform_ip);
|
||||
trans_class->start = GST_DEBUG_FUNCPTR(gst_fastspectrum_start);
|
||||
trans_class->stop = GST_DEBUG_FUNCPTR(gst_fastspectrum_stop);
|
||||
trans_class->transform_ip = GST_DEBUG_FUNCPTR(gst_fastspectrum_transform_ip);
|
||||
trans_class->passthrough_on_same_caps = TRUE;
|
||||
|
||||
filter_class->setup = GST_DEBUG_FUNCPTR (gst_fastspectrum_setup);
|
||||
filter_class->setup = GST_DEBUG_FUNCPTR(gst_fastspectrum_setup);
|
||||
|
||||
g_object_class_install_property (gobject_class, PROP_INTERVAL,
|
||||
g_param_spec_uint64 ("interval", "Interval", "Interval of time between message posts (in nanoseconds)", 1, G_MAXUINT64, DEFAULT_INTERVAL, GParamFlags(G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)));
|
||||
g_object_class_install_property(gobject_class, PROP_INTERVAL, g_param_spec_uint64("interval", "Interval", "Interval of time between message posts (in nanoseconds)", 1, G_MAXUINT64, DEFAULT_INTERVAL, GParamFlags(G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)));
|
||||
|
||||
g_object_class_install_property (gobject_class, PROP_BANDS, g_param_spec_uint ("bands", "Bands", "Number of frequency bands", 0, G_MAXUINT, DEFAULT_BANDS, GParamFlags(G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)));
|
||||
g_object_class_install_property(gobject_class, PROP_BANDS, g_param_spec_uint("bands", "Bands", "Number of frequency bands", 0, G_MAXUINT, DEFAULT_BANDS, GParamFlags(G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)));
|
||||
|
||||
GST_DEBUG_CATEGORY_INIT (gst_fastspectrum_debug, "spectrum", 0,
|
||||
"audio spectrum analyser element");
|
||||
GST_DEBUG_CATEGORY_INIT(gst_fastspectrum_debug, "spectrum", 0, "audio spectrum analyser element");
|
||||
|
||||
gst_element_class_set_static_metadata (element_class, "Spectrum analyzer",
|
||||
"Filter/Analyzer/Audio",
|
||||
"Run an FFT on the audio signal, output spectrum data",
|
||||
"Erik Walthinsen <omega@cse.ogi.edu>, "
|
||||
"Stefan Kost <ensonic@users.sf.net>, "
|
||||
"Sebastian Dröge <sebastian.droege@collabora.co.uk>");
|
||||
gst_element_class_set_static_metadata(element_class, "Spectrum analyzer",
|
||||
"Filter/Analyzer/Audio",
|
||||
"Run an FFT on the audio signal, output spectrum data",
|
||||
"Erik Walthinsen <omega@cse.ogi.edu>, "
|
||||
"Stefan Kost <ensonic@users.sf.net>, "
|
||||
"Sebastian Dröge <sebastian.droege@collabora.co.uk>");
|
||||
|
||||
caps = gst_caps_from_string (ALLOWED_CAPS);
|
||||
gst_audio_filter_class_add_pad_templates (filter_class, caps);
|
||||
gst_caps_unref (caps);
|
||||
#if G_BYTE_ORDER == G_LITTLE_ENDIAN
|
||||
caps = gst_caps_from_string(GST_AUDIO_CAPS_MAKE("{ S16LE, S24LE, S32LE, F32LE, F64LE }") ", layout = (string) interleaved, channels = 1");
|
||||
#else
|
||||
caps = gst_caps_from_string(GST_AUDIO_CAPS_MAKE("{ S16BE, S24BE, S32BE, F32BE, F64BE }") ", layout = (string) interleaved, channels = 1");
|
||||
#endif
|
||||
|
||||
gst_audio_filter_class_add_pad_templates(filter_class, caps);
|
||||
gst_caps_unref(caps);
|
||||
|
||||
klass->fftw_lock = new QMutex;
|
||||
|
||||
}
|
||||
|
||||
static void gst_fastspectrum_init (GstFastSpectrum * spectrum) {
|
||||
static void gst_fastspectrum_init(GstFastSpectrum *spectrum) {
|
||||
|
||||
spectrum->interval = DEFAULT_INTERVAL;
|
||||
spectrum->bands = DEFAULT_BANDS;
|
||||
|
||||
spectrum->channel_data_initialized = false;
|
||||
|
||||
g_mutex_init (&spectrum->lock);
|
||||
g_mutex_init(&spectrum->lock);
|
||||
|
||||
}
|
||||
|
||||
static void gst_fastspectrum_alloc_channel_data (GstFastSpectrum * spectrum) {
|
||||
static void gst_fastspectrum_alloc_channel_data(GstFastSpectrum *spectrum) {
|
||||
|
||||
guint bands = spectrum->bands;
|
||||
guint nfft = 2 * bands - 2;
|
||||
|
||||
spectrum->input_ring_buffer = new double[nfft];
|
||||
spectrum->fft_input = reinterpret_cast<double*>( fftw_malloc(sizeof(double) * nfft));
|
||||
spectrum->fft_output =reinterpret_cast<fftw_complex*>( fftw_malloc(sizeof(fftw_complex) * (nfft/2+1)));
|
||||
spectrum->fft_input = reinterpret_cast<double*>(fftw_malloc(sizeof(double) * nfft));
|
||||
spectrum->fft_output = reinterpret_cast<fftw_complex*>(fftw_malloc(sizeof(fftw_complex) * (nfft / 2 + 1)));
|
||||
|
||||
spectrum->spect_magnitude = new double[bands]{};
|
||||
spectrum->spect_magnitude = new double[bands] {};
|
||||
|
||||
GstFastSpectrumClass* klass = reinterpret_cast<GstFastSpectrumClass*>(G_OBJECT_GET_CLASS(spectrum));
|
||||
GstFastSpectrumClass *klass = reinterpret_cast<GstFastSpectrumClass*>(G_OBJECT_GET_CLASS(spectrum));
|
||||
{
|
||||
QMutexLocker l(klass->fftw_lock);
|
||||
spectrum->plan = fftw_plan_dft_r2c_1d(nfft, spectrum->fft_input, spectrum->fft_output, FFTW_ESTIMATE);
|
||||
spectrum->plan = fftw_plan_dft_r2c_1d(static_cast<int>(nfft), spectrum->fft_input, spectrum->fft_output, FFTW_ESTIMATE);
|
||||
}
|
||||
spectrum->channel_data_initialized = true;
|
||||
|
||||
}
|
||||
|
||||
static void gst_fastspectrum_free_channel_data (GstFastSpectrum * spectrum) {
|
||||
static void gst_fastspectrum_free_channel_data(GstFastSpectrum *spectrum) {
|
||||
|
||||
GstFastSpectrumClass* klass = reinterpret_cast<GstFastSpectrumClass*>(G_OBJECT_GET_CLASS(spectrum));
|
||||
GstFastSpectrumClass *klass = reinterpret_cast<GstFastSpectrumClass*>(G_OBJECT_GET_CLASS(spectrum));
|
||||
if (spectrum->channel_data_initialized) {
|
||||
{
|
||||
QMutexLocker l(klass->fftw_lock);
|
||||
@@ -160,7 +153,7 @@ static void gst_fastspectrum_free_channel_data (GstFastSpectrum * spectrum) {
|
||||
|
||||
}
|
||||
|
||||
static void gst_fastspectrum_flush (GstFastSpectrum * spectrum) {
|
||||
static void gst_fastspectrum_flush(GstFastSpectrum *spectrum) {
|
||||
|
||||
spectrum->num_frames = 0;
|
||||
spectrum->num_fft = 0;
|
||||
@@ -169,147 +162,145 @@ static void gst_fastspectrum_flush (GstFastSpectrum * spectrum) {
|
||||
|
||||
}
|
||||
|
||||
static void gst_fastspectrum_reset_state (GstFastSpectrum * spectrum) {
|
||||
static void gst_fastspectrum_reset_state(GstFastSpectrum *spectrum) {
|
||||
|
||||
GST_DEBUG_OBJECT (spectrum, "resetting state");
|
||||
GST_DEBUG_OBJECT(spectrum, "resetting state");
|
||||
|
||||
gst_fastspectrum_free_channel_data (spectrum);
|
||||
gst_fastspectrum_flush (spectrum);
|
||||
gst_fastspectrum_free_channel_data(spectrum);
|
||||
gst_fastspectrum_flush(spectrum);
|
||||
|
||||
}
|
||||
|
||||
static void gst_fastspectrum_finalize (GObject * object) {
|
||||
static void gst_fastspectrum_finalize(GObject *object) {
|
||||
|
||||
GstFastSpectrum *spectrum = reinterpret_cast<GstFastSpectrum*>(object);
|
||||
|
||||
gst_fastspectrum_reset_state (spectrum);
|
||||
g_mutex_clear (&spectrum->lock);
|
||||
gst_fastspectrum_reset_state(spectrum);
|
||||
g_mutex_clear(&spectrum->lock);
|
||||
|
||||
G_OBJECT_CLASS (parent_class)->finalize (object);
|
||||
G_OBJECT_CLASS(parent_class)->finalize(object);
|
||||
|
||||
}
|
||||
|
||||
static void gst_fastspectrum_set_property (GObject * object, guint prop_id, const GValue * value, GParamSpec * pspec) {
|
||||
static void gst_fastspectrum_set_property(GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec) {
|
||||
|
||||
GstFastSpectrum *filter = reinterpret_cast<GstFastSpectrum*>(object);
|
||||
|
||||
switch (prop_id) {
|
||||
case PROP_INTERVAL:{
|
||||
guint64 interval = g_value_get_uint64 (value);
|
||||
g_mutex_lock (&filter->lock);
|
||||
case PROP_INTERVAL: {
|
||||
guint64 interval = g_value_get_uint64(value);
|
||||
g_mutex_lock(&filter->lock);
|
||||
if (filter->interval != interval) {
|
||||
filter->interval = interval;
|
||||
gst_fastspectrum_reset_state (filter);
|
||||
gst_fastspectrum_reset_state(filter);
|
||||
}
|
||||
g_mutex_unlock (&filter->lock);
|
||||
g_mutex_unlock(&filter->lock);
|
||||
break;
|
||||
}
|
||||
case PROP_BANDS:{
|
||||
guint bands = g_value_get_uint (value);
|
||||
g_mutex_lock (&filter->lock);
|
||||
case PROP_BANDS: {
|
||||
guint bands = g_value_get_uint(value);
|
||||
g_mutex_lock(&filter->lock);
|
||||
if (filter->bands != bands) {
|
||||
filter->bands = bands;
|
||||
gst_fastspectrum_reset_state (filter);
|
||||
gst_fastspectrum_reset_state(filter);
|
||||
}
|
||||
g_mutex_unlock (&filter->lock);
|
||||
g_mutex_unlock(&filter->lock);
|
||||
break;
|
||||
}
|
||||
default:
|
||||
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
|
||||
G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
|
||||
break;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
static void gst_fastspectrum_get_property (GObject * object, guint prop_id, GValue * value, GParamSpec * pspec) {
|
||||
static void gst_fastspectrum_get_property(GObject *object, guint prop_id, GValue *value, GParamSpec *pspec) {
|
||||
|
||||
GstFastSpectrum *filter = reinterpret_cast<GstFastSpectrum*>(object);
|
||||
|
||||
switch (prop_id) {
|
||||
case PROP_INTERVAL:
|
||||
g_value_set_uint64 (value, filter->interval);
|
||||
g_value_set_uint64(value, filter->interval);
|
||||
break;
|
||||
case PROP_BANDS:
|
||||
g_value_set_uint (value, filter->bands);
|
||||
g_value_set_uint(value, filter->bands);
|
||||
break;
|
||||
default:
|
||||
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
|
||||
G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
|
||||
break;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
static gboolean gst_fastspectrum_start (GstBaseTransform * trans) {
|
||||
static gboolean gst_fastspectrum_start(GstBaseTransform *trans) {
|
||||
|
||||
GstFastSpectrum *spectrum = reinterpret_cast<GstFastSpectrum*>(trans);
|
||||
|
||||
gst_fastspectrum_reset_state (spectrum);
|
||||
gst_fastspectrum_reset_state(spectrum);
|
||||
|
||||
return TRUE;
|
||||
|
||||
}
|
||||
|
||||
static gboolean gst_fastspectrum_stop (GstBaseTransform * trans) {
|
||||
static gboolean gst_fastspectrum_stop(GstBaseTransform *trans) {
|
||||
|
||||
GstFastSpectrum *spectrum = reinterpret_cast<GstFastSpectrum*>(trans);
|
||||
|
||||
gst_fastspectrum_reset_state (spectrum);
|
||||
gst_fastspectrum_reset_state(spectrum);
|
||||
|
||||
return TRUE;
|
||||
|
||||
}
|
||||
|
||||
/* mixing data readers */
|
||||
// Mixing data readers
|
||||
|
||||
static void input_data_mixed_float(const guint8* _in, double* out, guint len, double max_value, guint op, guint nfft) {
|
||||
static void input_data_mixed_float(const guint8 *_in, double *out, guint len, double max_value, guint op, guint nfft) {
|
||||
|
||||
Q_UNUSED(max_value);
|
||||
|
||||
guint j = 0, ip = 0;
|
||||
const gfloat *in = reinterpret_cast<const gfloat*>(_in);
|
||||
guint ip = 0;
|
||||
|
||||
for (j = 0; j < len; j++) {
|
||||
for (guint j = 0; j < len; j++) {
|
||||
out[op] = in[ip++];
|
||||
op = (op + 1) % nfft;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
static void input_data_mixed_double (const guint8 * _in, double* out, guint len, double max_value, guint op, guint nfft) {
|
||||
static void input_data_mixed_double(const guint8 *_in, double *out, guint len, double max_value, guint op, guint nfft) {
|
||||
|
||||
Q_UNUSED(max_value);
|
||||
|
||||
guint j = 0, ip = 0;
|
||||
const gdouble *in = reinterpret_cast<const gdouble*>(_in);
|
||||
guint ip = 0;
|
||||
|
||||
for (j = 0; j < len; j++) {
|
||||
for (guint j = 0; j < len; j++) {
|
||||
out[op] = in[ip++];
|
||||
op = (op + 1) % nfft;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
static void input_data_mixed_int32_max (const guint8 * _in, double* out, guint len, double max_value, guint op, guint nfft) {
|
||||
static void input_data_mixed_int32_max(const guint8 *_in, double *out, guint len, double max_value, guint op, guint nfft) {
|
||||
|
||||
guint j = 0, ip = 0;
|
||||
const gint32 *in = reinterpret_cast<const gint32*>(_in);
|
||||
guint ip = 0;
|
||||
|
||||
for (j = 0; j < len; j++) {
|
||||
for (guint j = 0; j < len; j++) {
|
||||
out[op] = in[ip++] / max_value;
|
||||
op = (op + 1) % nfft;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
static void input_data_mixed_int24_max (const guint8 * _in, double* out, guint len, double max_value, guint op, guint nfft) {
|
||||
static void input_data_mixed_int24_max(const guint8 *_in, double *out, guint len, double max_value, guint op, guint nfft) {
|
||||
|
||||
guint j = 0;
|
||||
|
||||
for (j = 0; j < len; j++) {
|
||||
for (guint j = 0; j < len; j++) {
|
||||
#if G_BYTE_ORDER == G_BIG_ENDIAN
|
||||
gint32 value = GST_READ_UINT24_BE (_in);
|
||||
guint32 value = GST_READ_UINT24_BE(_in);
|
||||
#else
|
||||
gint32 value = GST_READ_UINT24_LE (_in);
|
||||
guint32 value = GST_READ_UINT24_LE(_in);
|
||||
#endif
|
||||
if (value & 0x00800000) {
|
||||
value |= 0xff000000;
|
||||
@@ -322,25 +313,25 @@ static void input_data_mixed_int24_max (const guint8 * _in, double* out, guint l
|
||||
|
||||
}
|
||||
|
||||
static void input_data_mixed_int16_max (const guint8 * _in, double * out, guint len, double max_value, guint op, guint nfft) {
|
||||
static void input_data_mixed_int16_max(const guint8 *_in, double *out, guint len, double max_value, guint op, guint nfft) {
|
||||
|
||||
guint j = 0, ip = 0;
|
||||
const gint16 *in = reinterpret_cast<const gint16*>(_in);
|
||||
guint ip = 0;
|
||||
|
||||
for (j = 0; j < len; j++) {
|
||||
for (guint j = 0; j < len; j++) {
|
||||
out[op] = in[ip++] / max_value;
|
||||
op = (op + 1) % nfft;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
static gboolean gst_fastspectrum_setup (GstAudioFilter * base, const GstAudioInfo * info) {
|
||||
static gboolean gst_fastspectrum_setup(GstAudioFilter *base, const GstAudioInfo *info) {
|
||||
|
||||
GstFastSpectrum *spectrum = reinterpret_cast<GstFastSpectrum*>(base);
|
||||
GstFastSpectrumInputData input_data = nullptr;
|
||||
|
||||
g_mutex_lock (&spectrum->lock);
|
||||
switch (GST_AUDIO_INFO_FORMAT (info)) {
|
||||
g_mutex_lock(&spectrum->lock);
|
||||
switch (GST_AUDIO_INFO_FORMAT(info)) {
|
||||
case GST_AUDIO_FORMAT_S16:
|
||||
input_data = input_data_mixed_int16_max;
|
||||
break;
|
||||
@@ -357,35 +348,33 @@ static gboolean gst_fastspectrum_setup (GstAudioFilter * base, const GstAudioInf
|
||||
input_data = input_data_mixed_double;
|
||||
break;
|
||||
default:
|
||||
g_assert_not_reached ();
|
||||
g_assert_not_reached();
|
||||
break;
|
||||
}
|
||||
spectrum->input_data = input_data;
|
||||
|
||||
gst_fastspectrum_reset_state (spectrum);
|
||||
g_mutex_unlock (&spectrum->lock);
|
||||
gst_fastspectrum_reset_state(spectrum);
|
||||
g_mutex_unlock(&spectrum->lock);
|
||||
|
||||
return TRUE;
|
||||
|
||||
}
|
||||
|
||||
static void gst_fastspectrum_run_fft (GstFastSpectrum * spectrum, guint input_pos) {
|
||||
static void gst_fastspectrum_run_fft(GstFastSpectrum *spectrum, guint input_pos) {
|
||||
|
||||
guint i = 0;
|
||||
guint bands = spectrum->bands;
|
||||
guint nfft = 2 * bands - 2;
|
||||
|
||||
for (i = 0; i < nfft; i++) {
|
||||
for (guint i = 0; i < nfft; i++) {
|
||||
spectrum->fft_input[i] = spectrum->input_ring_buffer[(input_pos + i) % nfft];
|
||||
}
|
||||
|
||||
// Should be safe to execute the same plan multiple times in parallel.
|
||||
fftw_execute(spectrum->plan);
|
||||
|
||||
gdouble val = 0.0;
|
||||
/* Calculate magnitude in db */
|
||||
for (i = 0; i < bands; i++) {
|
||||
val = spectrum->fft_output[i][0] * spectrum->fft_output[i][0];
|
||||
// Calculate magnitude in db
|
||||
for (guint i = 0; i < bands; i++) {
|
||||
gdouble val = spectrum->fft_output[i][0] * spectrum->fft_output[i][0];
|
||||
val += spectrum->fft_output[i][1] * spectrum->fft_output[i][1];
|
||||
val /= nfft * nfft;
|
||||
spectrum->spect_magnitude[i] += val;
|
||||
@@ -393,79 +382,68 @@ static void gst_fastspectrum_run_fft (GstFastSpectrum * spectrum, guint input_po
|
||||
|
||||
}
|
||||
|
||||
static GstFlowReturn gst_fastspectrum_transform_ip (GstBaseTransform *trans, GstBuffer *buffer) {
|
||||
static GstFlowReturn gst_fastspectrum_transform_ip(GstBaseTransform *trans, GstBuffer *buffer) {
|
||||
|
||||
GstFastSpectrum *spectrum = reinterpret_cast<GstFastSpectrum*>(trans);
|
||||
guint rate = GST_AUDIO_FILTER_RATE (spectrum);
|
||||
guint bps = GST_AUDIO_FILTER_BPS (spectrum);
|
||||
guint bpf = GST_AUDIO_FILTER_BPF (spectrum);
|
||||
double max_value = (1UL << ((bps << 3) - 1)) - 1;
|
||||
guint rate = GST_AUDIO_FILTER_RATE(spectrum);
|
||||
guint bps = GST_AUDIO_FILTER_BPS(spectrum);
|
||||
guint bpf = GST_AUDIO_FILTER_BPF(spectrum);
|
||||
double max_value = static_cast<double>((1UL << ((bps << 3) - 1)) - 1);
|
||||
guint bands = spectrum->bands;
|
||||
guint nfft = 2 * bands - 2;
|
||||
guint input_pos = 0;
|
||||
GstMapInfo map;
|
||||
const guint8 *data = nullptr;
|
||||
gsize size = 0;
|
||||
guint fft_todo = 0, msg_todo = 0, block_size = 0;
|
||||
gboolean have_full_interval = false;
|
||||
GstFastSpectrumInputData input_data = nullptr;
|
||||
|
||||
g_mutex_lock (&spectrum->lock);
|
||||
gst_buffer_map (buffer, &map, GST_MAP_READ);
|
||||
g_mutex_lock(&spectrum->lock);
|
||||
gst_buffer_map(buffer, &map, GST_MAP_READ);
|
||||
data = map.data;
|
||||
size = map.size;
|
||||
|
||||
GST_LOG_OBJECT (spectrum, "input size: %" G_GSIZE_FORMAT " bytes", size);
|
||||
GST_LOG_OBJECT(spectrum, "input size: %" G_GSIZE_FORMAT " bytes", size);
|
||||
|
||||
if (GST_BUFFER_IS_DISCONT (buffer)) {
|
||||
GST_DEBUG_OBJECT (spectrum, "Discontinuity detected -- flushing");
|
||||
gst_fastspectrum_flush (spectrum);
|
||||
if (GST_BUFFER_IS_DISCONT(buffer)) {
|
||||
GST_DEBUG_OBJECT(spectrum, "Discontinuity detected -- flushing");
|
||||
gst_fastspectrum_flush(spectrum);
|
||||
}
|
||||
|
||||
/* If we don't have a FFT context yet (or it was reset due to parameter
|
||||
* changes) get one and allocate memory for everything
|
||||
*/
|
||||
// If we don't have a FFT context yet (or it was reset due to parameter changes) get one and allocate memory for everything
|
||||
if (!spectrum->channel_data_initialized) {
|
||||
GST_DEBUG_OBJECT (spectrum, "allocating for bands %u", bands);
|
||||
GST_DEBUG_OBJECT(spectrum, "allocating for bands %u", bands);
|
||||
|
||||
gst_fastspectrum_alloc_channel_data (spectrum);
|
||||
gst_fastspectrum_alloc_channel_data(spectrum);
|
||||
|
||||
/* number of sample frames we process before posting a message
|
||||
* interval is in ns */
|
||||
spectrum->frames_per_interval = gst_util_uint64_scale (spectrum->interval, rate, GST_SECOND);
|
||||
// Number of sample frames we process before posting a message interval is in ns
|
||||
spectrum->frames_per_interval = gst_util_uint64_scale(spectrum->interval, rate, GST_SECOND);
|
||||
spectrum->frames_todo = spectrum->frames_per_interval;
|
||||
/* rounding error for frames_per_interval in ns,
|
||||
* aggregated it in accumulated_error */
|
||||
// Rounding error for frames_per_interval in ns, aggregated it in accumulated_error
|
||||
spectrum->error_per_interval = (spectrum->interval * rate) % GST_SECOND;
|
||||
if (spectrum->frames_per_interval == 0) {
|
||||
spectrum->frames_per_interval = 1;
|
||||
}
|
||||
|
||||
GST_INFO_OBJECT (spectrum, "interval %" GST_TIME_FORMAT ", fpi %"
|
||||
G_GUINT64_FORMAT ", error %" GST_TIME_FORMAT,
|
||||
GST_TIME_ARGS (spectrum->interval), spectrum->frames_per_interval,
|
||||
GST_TIME_ARGS (spectrum->error_per_interval));
|
||||
GST_INFO_OBJECT(spectrum, "interval %" GST_TIME_FORMAT ", fpi %" G_GUINT64_FORMAT ", error %" GST_TIME_FORMAT, GST_TIME_ARGS(spectrum->interval), spectrum->frames_per_interval, GST_TIME_ARGS(spectrum->error_per_interval));
|
||||
|
||||
spectrum->input_pos = 0;
|
||||
|
||||
gst_fastspectrum_flush (spectrum);
|
||||
gst_fastspectrum_flush(spectrum);
|
||||
}
|
||||
|
||||
if (spectrum->num_frames == 0) {
|
||||
spectrum->message_ts = GST_BUFFER_TIMESTAMP (buffer);
|
||||
spectrum->message_ts = GST_BUFFER_TIMESTAMP(buffer);
|
||||
}
|
||||
|
||||
input_pos = spectrum->input_pos;
|
||||
input_data = spectrum->input_data;
|
||||
|
||||
while (size >= bpf) {
|
||||
/* run input_data for a chunk of data */
|
||||
fft_todo = nfft - (spectrum->num_frames % nfft);
|
||||
msg_todo = spectrum->frames_todo - spectrum->num_frames;
|
||||
GST_LOG_OBJECT (spectrum,
|
||||
"message frames todo: %u, fft frames todo: %u, input frames %"
|
||||
G_GSIZE_FORMAT, msg_todo, fft_todo, (size / bpf));
|
||||
block_size = msg_todo;
|
||||
// Run input_data for a chunk of data
|
||||
guint fft_todo = nfft - (spectrum->num_frames % nfft);
|
||||
guint msg_todo = spectrum->frames_todo - spectrum->num_frames;
|
||||
GST_LOG_OBJECT(spectrum, "message frames todo: %u, fft frames todo: %u, input frames %" G_GSIZE_FORMAT, msg_todo, fft_todo, (size / bpf));
|
||||
guint block_size = msg_todo;
|
||||
if (block_size > (size / bpf)) {
|
||||
block_size = (size / bpf);
|
||||
}
|
||||
@@ -473,7 +451,7 @@ static GstFlowReturn gst_fastspectrum_transform_ip (GstBaseTransform *trans, Gst
|
||||
block_size = fft_todo;
|
||||
}
|
||||
|
||||
/* Move the current frames into our ringbuffers */
|
||||
// Move the current frames into our ringbuffers
|
||||
input_data(data, spectrum->input_ring_buffer, block_size, max_value, input_pos, nfft);
|
||||
|
||||
data += block_size * bpf;
|
||||
@@ -481,25 +459,19 @@ static GstFlowReturn gst_fastspectrum_transform_ip (GstBaseTransform *trans, Gst
|
||||
input_pos = (input_pos + block_size) % nfft;
|
||||
spectrum->num_frames += block_size;
|
||||
|
||||
have_full_interval = (spectrum->num_frames == spectrum->frames_todo);
|
||||
gboolean have_full_interval = (spectrum->num_frames == spectrum->frames_todo);
|
||||
|
||||
GST_LOG_OBJECT (spectrum,
|
||||
"size: %" G_GSIZE_FORMAT ", do-fft = %d, do-message = %d", size,
|
||||
(spectrum->num_frames % nfft == 0), have_full_interval);
|
||||
GST_LOG_OBJECT(spectrum, "size: %" G_GSIZE_FORMAT ", do-fft = %d, do-message = %d", size, (spectrum->num_frames % nfft == 0), have_full_interval);
|
||||
|
||||
/* If we have enough frames for an FFT or we have all frames required for
|
||||
* the interval and we haven't run a FFT, then run an FFT */
|
||||
// If we have enough frames for an FFT or we have all frames required for the interval and we haven't run a FFT, then run an FFT
|
||||
if ((spectrum->num_frames % nfft == 0) || (have_full_interval && !spectrum->num_fft)) {
|
||||
gst_fastspectrum_run_fft (spectrum, input_pos);
|
||||
gst_fastspectrum_run_fft(spectrum, input_pos);
|
||||
spectrum->num_fft++;
|
||||
}
|
||||
|
||||
/* Do we have the FFTs for one interval? */
|
||||
// Do we have the FFTs for one interval?
|
||||
if (have_full_interval) {
|
||||
GST_DEBUG_OBJECT (spectrum, "nfft: %u frames: %" G_GUINT64_FORMAT
|
||||
" fpi: %" G_GUINT64_FORMAT " error: %" GST_TIME_FORMAT, nfft,
|
||||
spectrum->num_frames, spectrum->frames_per_interval,
|
||||
GST_TIME_ARGS (spectrum->accumulated_error));
|
||||
GST_DEBUG_OBJECT(spectrum, "nfft: %u frames: %" G_GUINT64_FORMAT " fpi: %" G_GUINT64_FORMAT " error: %" GST_TIME_FORMAT, nfft, spectrum->num_frames, spectrum->frames_per_interval, GST_TIME_ARGS(spectrum->accumulated_error));
|
||||
|
||||
spectrum->frames_todo = spectrum->frames_per_interval;
|
||||
if (spectrum->accumulated_error >= GST_SECOND) {
|
||||
@@ -511,17 +483,17 @@ static GstFlowReturn gst_fastspectrum_transform_ip (GstBaseTransform *trans, Gst
|
||||
if (spectrum->output_callback) {
|
||||
// Calculate average
|
||||
for (guint i = 0; i < spectrum->bands; i++) {
|
||||
spectrum->spect_magnitude[i] /= spectrum->num_fft;
|
||||
spectrum->spect_magnitude[i] /= static_cast<double>(spectrum->num_fft);
|
||||
}
|
||||
|
||||
spectrum->output_callback(spectrum->spect_magnitude, spectrum->bands);
|
||||
spectrum->output_callback(spectrum->spect_magnitude, static_cast<int>(spectrum->bands));
|
||||
|
||||
// Reset spectrum accumulators
|
||||
memset(spectrum->spect_magnitude, 0, spectrum->bands * sizeof(double));
|
||||
}
|
||||
|
||||
if (GST_CLOCK_TIME_IS_VALID (spectrum->message_ts)) {
|
||||
spectrum->message_ts += gst_util_uint64_scale (spectrum->num_frames, GST_SECOND, rate);
|
||||
if (GST_CLOCK_TIME_IS_VALID(spectrum->message_ts)) {
|
||||
spectrum->message_ts += gst_util_uint64_scale(spectrum->num_frames, GST_SECOND, rate);
|
||||
}
|
||||
|
||||
spectrum->num_frames = 0;
|
||||
@@ -531,10 +503,10 @@ static GstFlowReturn gst_fastspectrum_transform_ip (GstBaseTransform *trans, Gst
|
||||
|
||||
spectrum->input_pos = input_pos;
|
||||
|
||||
gst_buffer_unmap (buffer, &map);
|
||||
g_mutex_unlock (&spectrum->lock);
|
||||
gst_buffer_unmap(buffer, &map);
|
||||
g_mutex_unlock(&spectrum->lock);
|
||||
|
||||
g_assert (size == 0);
|
||||
g_assert(size == 0);
|
||||
|
||||
return GST_FLOW_OK;
|
||||
|
||||
|
||||
@@ -38,38 +38,37 @@
|
||||
G_BEGIN_DECLS
|
||||
|
||||
#define GST_TYPE_FASTSPECTRUM (gst_fastspectrum_get_type())
|
||||
#define GST_FASTSPECTRUM(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj),GST_TYPE_FASTSPECTRUM,GstFastSpectrum))
|
||||
#define GST_IS_FASTSPECTRUM(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj),GST_TYPE_FASTSPECTRUM))
|
||||
#define GST_FASTSPECTRUM_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST((klass), GST_TYPE_FASTSPECTRUM,GstFastSpectrumClass))
|
||||
#define GST_FASTSPECTRUM(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj), GST_TYPE_FASTSPECTRUM, GstFastSpectrum))
|
||||
#define GST_IS_FASTSPECTRUM(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj), GST_TYPE_FASTSPECTRUM))
|
||||
#define GST_FASTSPECTRUM_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST((klass), GST_TYPE_FASTSPECTRUM, GstFastSpectrumClass))
|
||||
#define GST_IS_FASTSPECTRUM_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass), GST_TYPE_FASTSPECTRUM))
|
||||
|
||||
class QMutex;
|
||||
|
||||
typedef void (*GstFastSpectrumInputData)(const guint8* in, double* out, guint len, double max_value, guint op, guint nfft);
|
||||
typedef void (*GstFastSpectrumInputData)(const guint8 *in, double *out, guint len, double max_value, guint op, guint nfft);
|
||||
|
||||
typedef std::function<void(double* magnitudes, int size)> OutputCallback;
|
||||
typedef std::function<void(double *magnitudes, int size)> OutputCallback;
|
||||
|
||||
struct GstFastSpectrum {
|
||||
GstAudioFilter parent;
|
||||
|
||||
/* properties */
|
||||
guint64 interval; /* how many nanoseconds between emits */
|
||||
guint64 frames_per_interval; /* how many frames per interval */
|
||||
// Properties
|
||||
guint64 interval; // How many nanoseconds between emits
|
||||
guint64 frames_per_interval; // How many frames per interval
|
||||
guint64 frames_todo;
|
||||
guint bands; /* number of spectrum bands */
|
||||
gboolean multi_channel; /* send separate channel results */
|
||||
guint bands; // Number of spectrum bands
|
||||
gboolean multi_channel; // Send separate channel results
|
||||
|
||||
guint64 num_frames; /* frame count (1 sample per channel)
|
||||
* since last emit */
|
||||
guint64 num_fft; /* number of FFTs since last emit */
|
||||
GstClockTime message_ts; /* starttime for next message */
|
||||
guint64 num_frames; // Frame count (1 sample per channel) since last emit
|
||||
guint64 num_fft; // Number of FFTs since last emit
|
||||
GstClockTime message_ts; // Starttime for next message
|
||||
|
||||
/* <private> */
|
||||
// <private>
|
||||
bool channel_data_initialized;
|
||||
double* input_ring_buffer;
|
||||
double* fft_input;
|
||||
fftw_complex* fft_output;
|
||||
double* spect_magnitude;
|
||||
double *input_ring_buffer;
|
||||
double *fft_input;
|
||||
fftw_complex *fft_output;
|
||||
double *spect_magnitude;
|
||||
fftw_plan plan;
|
||||
|
||||
guint input_pos;
|
||||
@@ -87,11 +86,11 @@ struct GstFastSpectrumClass {
|
||||
GstAudioFilterClass parent_class;
|
||||
|
||||
// Static lock for creating & destroying FFTW plans.
|
||||
QMutex* fftw_lock;
|
||||
QMutex *fftw_lock;
|
||||
};
|
||||
|
||||
GType gst_fastspectrum_get_type (void);
|
||||
GType gst_fastspectrum_get_type(void);
|
||||
|
||||
G_END_DECLS
|
||||
|
||||
#endif // GST_MOODBAR_FASTSPECTRUM_H
|
||||
#endif // GST_MOODBAR_FASTSPECTRUM_H
|
||||
|
||||
@@ -30,7 +30,6 @@ static gboolean gst_moodbar_plugin_init(GstPlugin *plugin) {
|
||||
}
|
||||
|
||||
return TRUE;
|
||||
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
@@ -19,7 +19,7 @@
|
||||
#define GST_MOODBAR_PLUGIN_H
|
||||
|
||||
extern "C" {
|
||||
int gstfastspectrum_register_static();
|
||||
int gstfastspectrum_register_static();
|
||||
}
|
||||
|
||||
#endif // GST_MOODBAR_PLUGIN_H
|
||||
|
||||
@@ -52,18 +52,17 @@
|
||||
namespace logging {
|
||||
|
||||
static Level sDefaultLevel = Level_Debug;
|
||||
static QMap<QString, Level>* sClassLevels = nullptr;
|
||||
static QMap<QString, Level> *sClassLevels = nullptr;
|
||||
static QIODevice *sNullDevice = nullptr;
|
||||
|
||||
//const char* kDefaultLogLevels = "*:3";
|
||||
const char *kDefaultLogLevels = "GstEnginePipeline:2,*:3";
|
||||
const char *kDefaultLogLevels = "*:3";
|
||||
|
||||
static const char *kMessageHandlerMagic = "__logging_message__";
|
||||
static const size_t kMessageHandlerMagicLength = strlen(kMessageHandlerMagic);
|
||||
static QtMessageHandler sOriginalMessageHandler = nullptr;
|
||||
|
||||
template <class T>
|
||||
static T CreateLogger(Level level, const QString& class_name, int line, const char* category);
|
||||
static T CreateLogger(Level level, const QString &class_name, int line, const char *category);
|
||||
|
||||
void GLog(const char *domain, int level, const char *message, void*) {
|
||||
|
||||
@@ -94,8 +93,8 @@ class DebugBase : public QDebug {
|
||||
public:
|
||||
DebugBase() : QDebug(sNullDevice) {}
|
||||
explicit DebugBase(QtMsgType t) : QDebug(t) {}
|
||||
T& space() { return static_cast<T&>(QDebug::space()); }
|
||||
T& noSpace() { return static_cast<T&>(QDebug::nospace()); }
|
||||
T &space() { return static_cast<T&>(QDebug::space()); }
|
||||
T &nospace() { return static_cast<T&>(QDebug::nospace()); }
|
||||
};
|
||||
|
||||
// Debug message will be stored in a buffer.
|
||||
@@ -112,7 +111,7 @@ class BufferedDebug : public DebugBase<BufferedDebug> {
|
||||
|
||||
// Delete function for the buffer. Since a base class is holding a reference to the raw pointer,
|
||||
// it shouldn't be deleted until after the deletion of this object is complete.
|
||||
static void later_deleter(QBuffer* b) { b->deleteLater(); }
|
||||
static void later_deleter(QBuffer *b) { b->deleteLater(); }
|
||||
|
||||
std::shared_ptr<QBuffer> buf_;
|
||||
};
|
||||
@@ -126,8 +125,9 @@ class LoggedDebug : public DebugBase<LoggedDebug> {
|
||||
|
||||
static void MessageHandler(QtMsgType type, const QMessageLogContext&, const QString &message) {
|
||||
|
||||
if (strncmp(kMessageHandlerMagic, message.toLocal8Bit().data(), kMessageHandlerMagicLength) == 0) {
|
||||
fprintf(stderr, "%s\n", message.toLocal8Bit().data() + kMessageHandlerMagicLength);
|
||||
if (message.startsWith(kMessageHandlerMagic)) {
|
||||
fprintf(type == QtCriticalMsg || type == QtFatalMsg ? stderr : stdout, "%s\n", message.toUtf8().data() + kMessageHandlerMagicLength);
|
||||
fflush(type == QtCriticalMsg || type == QtFatalMsg ? stderr : stdout);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -146,12 +146,13 @@ static void MessageHandler(QtMsgType type, const QMessageLogContext&, const QStr
|
||||
break;
|
||||
}
|
||||
|
||||
for (const QString& line : message.split('\n')) {
|
||||
for (const QString &line : message.split('\n')) {
|
||||
BufferedDebug d = CreateLogger<BufferedDebug>(level, "unknown", -1, nullptr);
|
||||
d << line.toLocal8Bit().constData();
|
||||
if (d.buf_) {
|
||||
d.buf_->close();
|
||||
fprintf(stderr, "%s\n", d.buf_->buffer().data());
|
||||
fprintf(type == QtCriticalMsg || type == QtFatalMsg ? stderr : stdout, "%s\n", d.buf_->buffer().data());
|
||||
fflush(type == QtCriticalMsg || type == QtFatalMsg ? stderr : stdout);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -174,6 +175,7 @@ void Init() {
|
||||
if (!sOriginalMessageHandler) {
|
||||
sOriginalMessageHandler = qInstallMessageHandler(MessageHandler);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
void SetLevels(const QString &levels) {
|
||||
@@ -213,9 +215,9 @@ static QString ParsePrettyFunction(const char *pretty_function) {
|
||||
|
||||
// Get the class name out of the function name.
|
||||
QString class_name = pretty_function;
|
||||
const int paren = class_name.indexOf('(');
|
||||
const qint64 paren = class_name.indexOf('(');
|
||||
if (paren != -1) {
|
||||
const int colons = class_name.lastIndexOf("::", paren);
|
||||
const qint64 colons = class_name.lastIndexOf("::", paren);
|
||||
if (colons != -1) {
|
||||
class_name = class_name.left(colons);
|
||||
}
|
||||
@@ -224,16 +226,17 @@ static QString ParsePrettyFunction(const char *pretty_function) {
|
||||
}
|
||||
}
|
||||
|
||||
const int space = class_name.lastIndexOf(' ');
|
||||
const qint64 space = class_name.lastIndexOf(' ');
|
||||
if (space != -1) {
|
||||
class_name = class_name.mid(space + 1);
|
||||
}
|
||||
|
||||
return class_name;
|
||||
|
||||
}
|
||||
|
||||
template <class T>
|
||||
static T CreateLogger(Level level, const QString &class_name, int line, const char* category) {
|
||||
static T CreateLogger(Level level, const QString &class_name, int line, const char *category) {
|
||||
|
||||
// Map the level to a string
|
||||
const char *level_name = nullptr;
|
||||
@@ -270,11 +273,10 @@ static T CreateLogger(Level level, const QString &class_name, int line, const ch
|
||||
}
|
||||
|
||||
T ret(type);
|
||||
ret.nospace() << QDateTime::currentDateTime().toString("hh:mm:ss.zzz").toLatin1().constData()
|
||||
<< level_name
|
||||
<< function_line.leftJustified(32).toLatin1().constData();
|
||||
ret.nospace() << QDateTime::currentDateTime().toString("hh:mm:ss.zzz").toLatin1().constData() << level_name << function_line.leftJustified(32).toLatin1().constData();
|
||||
|
||||
return ret.space();
|
||||
|
||||
}
|
||||
|
||||
#ifdef Q_OS_UNIX
|
||||
@@ -304,6 +306,7 @@ QString LinuxDemangle(const QString &symbol) {
|
||||
}
|
||||
QString mangled_function = match.captured(1);
|
||||
return CXXDemangle(mangled_function);
|
||||
|
||||
}
|
||||
#endif // Q_OS_LINUX
|
||||
|
||||
@@ -324,6 +327,7 @@ QString DarwinDemangle(const QString &symbol) {
|
||||
|
||||
QString DemangleSymbol(const QString &symbol);
|
||||
QString DemangleSymbol(const QString &symbol) {
|
||||
|
||||
#ifdef Q_OS_MACOS
|
||||
return DarwinDemangle(symbol);
|
||||
#elif defined(Q_OS_LINUX)
|
||||
@@ -331,9 +335,11 @@ QString DemangleSymbol(const QString &symbol) {
|
||||
#else
|
||||
return symbol;
|
||||
#endif
|
||||
|
||||
}
|
||||
|
||||
void DumpStackTrace() {
|
||||
|
||||
#ifdef HAVE_BACKTRACE
|
||||
void *callstack[128];
|
||||
int callstack_size = backtrace(reinterpret_cast<void**>(&callstack), sizeof(callstack));
|
||||
@@ -346,11 +352,11 @@ void DumpStackTrace() {
|
||||
#else
|
||||
qLog(Debug) << "FIXME: Implement printing stack traces on this platform";
|
||||
#endif
|
||||
|
||||
}
|
||||
|
||||
// These are the functions that create loggers for the rest of Clementine.
|
||||
// It's okay that the LoggedDebug instance is copied to a QDebug in these. It
|
||||
// doesn't override any behavior that should be needed after return.
|
||||
// These are the functions that create loggers for the rest of Strawberry.
|
||||
// It's okay that the LoggedDebug instance is copied to a QDebug in these. It doesn't override any behavior that should be needed after return.
|
||||
#define qCreateLogger(line, pretty_function, category, level) logging::CreateLogger<LoggedDebug>(logging::Level_##level, logging::ParsePrettyFunction(pretty_function), line, category)
|
||||
|
||||
QDebug CreateLoggerInfo(int line, const char *pretty_function, const char *category) { return qCreateLogger(line, pretty_function, category, Info); }
|
||||
@@ -360,13 +366,13 @@ QDebug CreateLoggerError(int line, const char *pretty_function, const char *cate
|
||||
#ifdef QT_NO_WARNING_OUTPUT
|
||||
QNoDebug CreateLoggerWarning(int, const char*, const char*) { return QNoDebug(); }
|
||||
#else
|
||||
QDebug CreateLoggerWarning(int line, const char *pretty_function, const char* category) { return qCreateLogger(line, pretty_function, category, Warning); }
|
||||
QDebug CreateLoggerWarning(int line, const char *pretty_function, const char *category) { return qCreateLogger(line, pretty_function, category, Warning); }
|
||||
#endif // QT_NO_WARNING_OUTPUT
|
||||
|
||||
#ifdef QT_NO_DEBUG_OUTPUT
|
||||
QNoDebug CreateLoggerDebug(int, const char*, const char*) { return QNoDebug(); }
|
||||
#else
|
||||
QDebug CreateLoggerDebug(int line, const char *pretty_function, const char* category) { return qCreateLogger(line, pretty_function, category, Debug); }
|
||||
QDebug CreateLoggerDebug(int line, const char *pretty_function, const char *category) { return qCreateLogger(line, pretty_function, category, Debug); }
|
||||
#endif // QT_NO_DEBUG_OUTPUT
|
||||
|
||||
} // namespace logging
|
||||
@@ -384,4 +390,3 @@ QDebug operator<<(QDebug dbg, std::chrono::seconds secs) {
|
||||
dbg.nospace() << print_duration(secs, "s");
|
||||
return dbg.space();
|
||||
}
|
||||
|
||||
|
||||
@@ -68,28 +68,27 @@ enum Level {
|
||||
};
|
||||
|
||||
void Init();
|
||||
void SetLevels(const QString& levels);
|
||||
void SetLevels(const QString &levels);
|
||||
|
||||
void DumpStackTrace();
|
||||
|
||||
QDebug CreateLoggerInfo(int line, const char *pretty_function, const char* category);
|
||||
QDebug CreateLoggerFatal(int line, const char *pretty_function, const char* category);
|
||||
QDebug CreateLoggerError(int line, const char *pretty_function, const char* category);
|
||||
QDebug CreateLoggerInfo(int line, const char *pretty_function, const char *category);
|
||||
QDebug CreateLoggerFatal(int line, const char *pretty_function, const char *category);
|
||||
QDebug CreateLoggerError(int line, const char *pretty_function, const char *category);
|
||||
|
||||
#ifdef QT_NO_WARNING_OUTPUT
|
||||
QNoDebug CreateLoggerWarning(int, const char*, const char*);
|
||||
#else
|
||||
QDebug CreateLoggerWarning(int line, const char *pretty_function, const char* category);
|
||||
QDebug CreateLoggerWarning(int line, const char *pretty_function, const char *category);
|
||||
#endif // QT_NO_WARNING_OUTPUT
|
||||
|
||||
#ifdef QT_NO_DEBUG_OUTPUT
|
||||
QNoDebug CreateLoggerDebug(int, const char*, const char*);
|
||||
#else
|
||||
QDebug CreateLoggerDebug(int line, const char *pretty_function, const char* category);
|
||||
QDebug CreateLoggerDebug(int line, const char *pretty_function, const char *category);
|
||||
#endif // QT_NO_DEBUG_OUTPUT
|
||||
|
||||
|
||||
void GLog(const char* domain, int level, const char* message, void* user_data);
|
||||
void GLog(const char *domain, int level, const char *message, void *user_data);
|
||||
|
||||
extern const char *kDefaultLogLevels;
|
||||
|
||||
|
||||
@@ -99,7 +99,7 @@ void _MessageHandlerBase::WriteMessage(const QByteArray &data) {
|
||||
|
||||
QDataStream s(device_);
|
||||
s << quint32(data.length());
|
||||
s.writeRawData(data.data(), data.length());
|
||||
s.writeRawData(data.data(), static_cast<int>(data.length()));
|
||||
|
||||
// Sorry.
|
||||
if (flush_abstract_socket_) {
|
||||
|
||||
@@ -19,6 +19,9 @@
|
||||
#ifndef WORKERPOOL_H
|
||||
#define WORKERPOOL_H
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#include <cstdio>
|
||||
#include <cstddef>
|
||||
|
||||
#include <QtGlobal>
|
||||
@@ -56,6 +59,8 @@ class _WorkerPoolBase : public QObject {
|
||||
protected slots:
|
||||
virtual void DoStart() {}
|
||||
virtual void NewConnection() {}
|
||||
virtual void ProcessReadyReadStandardOutput() {}
|
||||
virtual void ProcessReadyReadStandardError() {}
|
||||
virtual void ProcessError(QProcess::ProcessError) {}
|
||||
virtual void SendQueuedMessages() {}
|
||||
};
|
||||
@@ -98,6 +103,8 @@ class WorkerPool : public _WorkerPoolBase {
|
||||
// These are all reimplemented slots, they are called on the WorkerPool's thread.
|
||||
void DoStart() override;
|
||||
void NewConnection() override;
|
||||
void ProcessReadyReadStandardOutput() override;
|
||||
void ProcessReadyReadStandardError() override;
|
||||
void ProcessError(QProcess::ProcessError error) override;
|
||||
void SendQueuedMessages() override;
|
||||
|
||||
@@ -176,6 +183,8 @@ WorkerPool<HandlerType>::~WorkerPool() {
|
||||
for (const Worker &worker : workers_) {
|
||||
if (worker.local_socket_ && worker.process_) {
|
||||
QObject::disconnect(worker.process_, &QProcess::errorOccurred, this, &WorkerPool::ProcessError);
|
||||
QObject::disconnect(worker.process_, &QProcess::readyReadStandardOutput, this, &WorkerPool::ProcessReadyReadStandardOutput);
|
||||
QObject::disconnect(worker.process_, &QProcess::readyReadStandardError, this, &WorkerPool::ProcessReadyReadStandardError);
|
||||
|
||||
// The worker is connected. Close his socket and wait for him to exit.
|
||||
qLog(Debug) << "Closing worker socket";
|
||||
@@ -276,6 +285,8 @@ void WorkerPool<HandlerType>::StartOneWorker(Worker *worker) {
|
||||
|
||||
QObject::connect(worker->local_server_, &QLocalServer::newConnection, this, &WorkerPool::NewConnection);
|
||||
QObject::connect(worker->process_, &QProcess::errorOccurred, this, &WorkerPool::ProcessError);
|
||||
QObject::connect(worker->process_, &QProcess::readyReadStandardOutput, this, &WorkerPool::ProcessReadyReadStandardOutput);
|
||||
QObject::connect(worker->process_, &QProcess::readyReadStandardError, this, &WorkerPool::ProcessReadyReadStandardError);
|
||||
|
||||
// Create a server, find an unused name and start listening
|
||||
forever {
|
||||
@@ -293,12 +304,12 @@ void WorkerPool<HandlerType>::StartOneWorker(Worker *worker) {
|
||||
|
||||
qLog(Debug) << "Starting worker" << worker << executable_path_ << worker->local_server_->fullServerName();
|
||||
|
||||
// Start the process
|
||||
#if defined(Q_OS_WIN32) && defined(QT_NO_DEBUG_OUTPUT) && !defined(ENABLE_WIN32_CONSOLE)
|
||||
#ifdef Q_OS_WIN32
|
||||
worker->process_->setProcessChannelMode(QProcess::SeparateChannels);
|
||||
#else
|
||||
worker->process_->setProcessChannelMode(QProcess::ForwardedChannels);
|
||||
#endif
|
||||
|
||||
worker->process_->start(executable_path_, QStringList() << worker->local_server_->fullServerName());
|
||||
|
||||
}
|
||||
@@ -328,6 +339,7 @@ void WorkerPool<HandlerType>::NewConnection() {
|
||||
worker->handler_ = new HandlerType(worker->local_socket_, this);
|
||||
|
||||
SendQueuedMessages();
|
||||
|
||||
}
|
||||
|
||||
template <typename HandlerType>
|
||||
@@ -358,6 +370,32 @@ void WorkerPool<HandlerType>::ProcessError(QProcess::ProcessError error) {
|
||||
|
||||
}
|
||||
|
||||
template <typename HandlerType>
|
||||
void WorkerPool<HandlerType>::ProcessReadyReadStandardOutput() {
|
||||
|
||||
Q_ASSERT(QThread::currentThread() == thread());
|
||||
|
||||
QProcess *process = qobject_cast<QProcess*>(sender());
|
||||
QByteArray data = process->readAllStandardOutput();
|
||||
|
||||
fprintf(stdout, "%s", data.data());
|
||||
fflush(stdout);
|
||||
|
||||
}
|
||||
|
||||
template <typename HandlerType>
|
||||
void WorkerPool<HandlerType>::ProcessReadyReadStandardError() {
|
||||
|
||||
Q_ASSERT(QThread::currentThread() == thread());
|
||||
|
||||
QProcess *process = qobject_cast<QProcess*>(sender());
|
||||
QByteArray data = process->readAllStandardError();
|
||||
|
||||
fprintf(stderr, "%s", data.data());
|
||||
fflush(stderr);
|
||||
|
||||
}
|
||||
|
||||
template <typename HandlerType>
|
||||
typename WorkerPool<HandlerType>::ReplyType*
|
||||
WorkerPool<HandlerType>::NewReply(MessageType *message) {
|
||||
|
||||
@@ -44,6 +44,9 @@ class TagReaderBase {
|
||||
virtual QByteArray LoadEmbeddedArt(const QString &filename) const = 0;
|
||||
virtual bool SaveEmbeddedArt(const QString &filename, const QByteArray &data) = 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;
|
||||
|
||||
protected:
|
||||
static const std::string kEmbeddedCover;
|
||||
|
||||
|
||||
@@ -58,17 +58,20 @@ message SongMetadata {
|
||||
optional string url = 21;
|
||||
optional string basefilename = 22;
|
||||
optional FileType filetype = 23;
|
||||
optional int32 filesize = 24;
|
||||
optional int64 filesize = 24;
|
||||
optional int64 mtime = 25;
|
||||
optional int64 ctime = 26;
|
||||
|
||||
optional int32 playcount = 27;
|
||||
optional int32 skipcount = 28;
|
||||
optional uint32 playcount = 27;
|
||||
optional uint32 skipcount = 28;
|
||||
optional int64 lastplayed = 29;
|
||||
optional int64 lastseen = 30;
|
||||
|
||||
optional bool suspicious_tags = 31;
|
||||
optional string art_automatic = 32;
|
||||
optional string art_automatic = 31;
|
||||
|
||||
optional float rating = 32;
|
||||
|
||||
optional bool suspicious_tags = 40;
|
||||
|
||||
}
|
||||
|
||||
@@ -114,6 +117,24 @@ message SaveEmbeddedArtResponse {
|
||||
optional bool success = 1;
|
||||
}
|
||||
|
||||
message SaveSongPlaycountToFileRequest {
|
||||
optional string filename = 1;
|
||||
optional SongMetadata metadata = 2;
|
||||
}
|
||||
|
||||
message SaveSongPlaycountToFileResponse {
|
||||
optional bool success = 1;
|
||||
}
|
||||
|
||||
message SaveSongRatingToFileRequest {
|
||||
optional string filename = 1;
|
||||
optional SongMetadata metadata = 2;
|
||||
}
|
||||
|
||||
message SaveSongRatingToFileResponse {
|
||||
optional bool success = 1;
|
||||
}
|
||||
|
||||
message Message {
|
||||
optional int32 id = 1;
|
||||
|
||||
@@ -132,4 +153,10 @@ message Message {
|
||||
optional SaveEmbeddedArtRequest save_embedded_art_request = 10;
|
||||
optional SaveEmbeddedArtResponse save_embedded_art_response = 11;
|
||||
|
||||
optional SaveSongPlaycountToFileRequest save_song_playcount_to_file_request = 12;
|
||||
optional SaveSongPlaycountToFileResponse save_song_playcount_to_file_response = 13;
|
||||
|
||||
optional SaveSongRatingToFileRequest save_song_rating_to_file_request = 14;
|
||||
optional SaveSongRatingToFileResponse save_song_rating_to_file_response = 15;
|
||||
|
||||
}
|
||||
|
||||
@@ -35,6 +35,7 @@
|
||||
#include <taglib/attachedpictureframe.h>
|
||||
#include <taglib/textidentificationframe.h>
|
||||
#include <taglib/unsynchronizedlyricsframe.h>
|
||||
#include <taglib/popularimeterframe.h>
|
||||
#include <taglib/xiphcomment.h>
|
||||
#include <taglib/commentsframe.h>
|
||||
#include <taglib/tag.h>
|
||||
@@ -129,10 +130,11 @@ TagLib::String QStringToTaglibString(const QString &s) {
|
||||
} // namespace
|
||||
|
||||
namespace {
|
||||
// Tags containing the year the album was originally released (in contrast to other tags that contain the release year of the current edition)
|
||||
const char *kMP4_OriginalYear_ID = "----:com.apple.iTunes:ORIGINAL YEAR";
|
||||
const char *kASF_OriginalDate_ID = "WM/OriginalReleaseTime";
|
||||
const char *kASF_OriginalYear_ID = "WM/OriginalReleaseYear";
|
||||
const char *kMP4_OriginalYear_ID = "----:com.apple.iTunes:ORIGINAL YEAR";
|
||||
const char *kMP4_FMPS_Playcount_ID = "----:com.apple.iTunes:FMPS_Playcount";
|
||||
const char *kMP4_FMPS_Rating_ID = "----:com.apple.iTunes:FMPS_Rating";
|
||||
const char *kASF_OriginalDate_ID = "WM/OriginalReleaseTime";
|
||||
const char *kASF_OriginalYear_ID = "WM/OriginalReleaseYear";
|
||||
} // namespace
|
||||
|
||||
|
||||
@@ -185,18 +187,18 @@ spb::tagreader::SongMetadata_FileType TagReaderTagLib::GuessFileType(TagLib::Fil
|
||||
void TagReaderTagLib::ReadFile(const QString &filename, spb::tagreader::SongMetadata *song) const {
|
||||
|
||||
const QByteArray url(QUrl::fromLocalFile(filename).toEncoded());
|
||||
const QFileInfo info(filename);
|
||||
const QFileInfo fileinfo(filename);
|
||||
|
||||
qLog(Debug) << "Reading tags from" << filename;
|
||||
|
||||
song->set_basefilename(DataCommaSizeFromQString(info.fileName()));
|
||||
song->set_basefilename(DataCommaSizeFromQString(fileinfo.fileName()));
|
||||
song->set_url(url.constData(), url.size());
|
||||
song->set_filesize(info.size());
|
||||
song->set_mtime(info.lastModified().toSecsSinceEpoch());
|
||||
song->set_filesize(fileinfo.size());
|
||||
song->set_mtime(fileinfo.lastModified().isValid() ? fileinfo.lastModified().toSecsSinceEpoch() : 0);
|
||||
#if (QT_VERSION >= QT_VERSION_CHECK(5, 10, 0))
|
||||
song->set_ctime(info.birthTime().isValid() ? info.birthTime().toSecsSinceEpoch() : info.lastModified().toSecsSinceEpoch());
|
||||
song->set_ctime(fileinfo.birthTime().isValid() ? fileinfo.birthTime().toSecsSinceEpoch() : fileinfo.lastModified().isValid() ? fileinfo.lastModified().toSecsSinceEpoch() : 0);
|
||||
#else
|
||||
song->set_ctime(info.created().toSecsSinceEpoch());
|
||||
song->set_ctime(fileinfo.created().isValid() ? fileinfo.created().toSecsSinceEpoch() : fileinfo.lastModified().isValid() ? fileinfo.lastModified().toSecsSinceEpoch() : 0);
|
||||
#endif
|
||||
song->set_lastseen(QDateTime::currentDateTime().toSecsSinceEpoch());
|
||||
|
||||
@@ -220,8 +222,8 @@ void TagReaderTagLib::ReadFile(const QString &filename, spb::tagreader::SongMeta
|
||||
Decode(tag->artist(), song->mutable_artist()); // TPE1
|
||||
Decode(tag->album(), song->mutable_album());
|
||||
Decode(tag->genre(), song->mutable_genre());
|
||||
song->set_year(tag->year());
|
||||
song->set_track(tag->track());
|
||||
song->set_year(static_cast<int>(tag->year()));
|
||||
song->set_track(static_cast<int>(tag->track()));
|
||||
song->set_valid(true);
|
||||
}
|
||||
|
||||
@@ -231,10 +233,16 @@ void TagReaderTagLib::ReadFile(const QString &filename, spb::tagreader::SongMeta
|
||||
|
||||
// Handle all the files which have VorbisComments (Ogg, OPUS, ...) in the same way;
|
||||
// apart, so we keep specific behavior for some formats by adding another "else if" block below.
|
||||
if (TagLib::Ogg::XiphComment *tag_ogg = dynamic_cast<TagLib::Ogg::XiphComment*>(fileref->file()->tag())) {
|
||||
ParseOggTag(tag_ogg->fieldListMap(), &disc, &compilation, song);
|
||||
if (!tag_ogg->pictureList().isEmpty()) {
|
||||
song->set_art_automatic(kEmbeddedCover);
|
||||
if (TagLib::Ogg::XiphComment *xiph_comment = dynamic_cast<TagLib::Ogg::XiphComment*>(fileref->file()->tag())) {
|
||||
ParseOggTag(xiph_comment->fieldListMap(), &disc, &compilation, song);
|
||||
TagLib::List<TagLib::FLAC::Picture*> pictures = xiph_comment->pictureList();
|
||||
if (!pictures.isEmpty()) {
|
||||
for (TagLib::FLAC::Picture *picture : pictures) {
|
||||
if (picture->type() == TagLib::FLAC::Picture::FrontCover && picture->data().size() > 0) {
|
||||
song->set_art_automatic(kEmbeddedCover);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -244,8 +252,14 @@ void TagReaderTagLib::ReadFile(const QString &filename, spb::tagreader::SongMeta
|
||||
|
||||
if (file_flac->xiphComment()) {
|
||||
ParseOggTag(file_flac->xiphComment()->fieldListMap(), &disc, &compilation, song);
|
||||
if (!file_flac->pictureList().isEmpty()) {
|
||||
song->set_art_automatic(kEmbeddedCover);
|
||||
TagLib::List<TagLib::FLAC::Picture*> pictures = file_flac->pictureList();
|
||||
if (!pictures.isEmpty()) {
|
||||
for (TagLib::FLAC::Picture *picture : pictures) {
|
||||
if (picture->type() == TagLib::FLAC::Picture::FrontCover && picture->data().size() > 0) {
|
||||
song->set_art_automatic(kEmbeddedCover);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (tag) Decode(tag->comment(), song->mutable_comment());
|
||||
@@ -316,6 +330,28 @@ void TagReaderTagLib::ReadFile(const QString &filename, spb::tagreader::SongMeta
|
||||
}
|
||||
}
|
||||
|
||||
if (TagLib::ID3v2::UserTextIdentificationFrame *frame_fmps_rating = TagLib::ID3v2::UserTextIdentificationFrame::find(file_mpeg->ID3v2Tag(), "FMPS_Rating")) {
|
||||
TagLib::StringList frame_field_list = frame_fmps_rating->fieldList();
|
||||
if (frame_field_list.size() > 1) {
|
||||
float rating = TStringToQString(frame_field_list[1]).toFloat();
|
||||
if (song->rating() <= 0 && rating > 0 && rating <= 1.0) {
|
||||
song->set_rating(rating);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!map["POPM"].isEmpty()) {
|
||||
const TagLib::ID3v2::PopularimeterFrame *frame = dynamic_cast<const TagLib::ID3v2::PopularimeterFrame*>(map["POPM"].front());
|
||||
if (frame) {
|
||||
if (song->playcount() <= 0 && frame->counter() > 0) {
|
||||
song->set_playcount(frame->counter());
|
||||
}
|
||||
if (song->rating() <= 0 && frame->rating() > 0) {
|
||||
song->set_rating(ConvertPOPMRating(frame->rating()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -357,6 +393,26 @@ void TagReaderTagLib::ReadFile(const QString &filename, spb::tagreader::SongMeta
|
||||
song->set_originalyear(TStringToQString(mp4_tag->item(kMP4_OriginalYear_ID).toStringList().toString('\n')).left(4).toInt());
|
||||
}
|
||||
|
||||
{
|
||||
TagLib::MP4::Item item = mp4_tag->item(kMP4_FMPS_Playcount_ID);
|
||||
if (item.isValid()) {
|
||||
const int playcount = TStringToQString(item.toStringList().toString('\n')).toInt();
|
||||
if (song->playcount() <= 0 && playcount > 0) {
|
||||
song->set_playcount(static_cast<uint>(playcount));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
TagLib::MP4::Item item = mp4_tag->item(kMP4_FMPS_Rating_ID);
|
||||
if (item.isValid()) {
|
||||
const float rating = TStringToQString(item.toStringList().toString('\n')).toFloat();
|
||||
if (song->rating() <= 0 && rating > 0) {
|
||||
song->set_rating(rating);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Decode(mp4_tag->comment(), song->mutable_comment());
|
||||
}
|
||||
}
|
||||
@@ -367,22 +423,44 @@ void TagReaderTagLib::ReadFile(const QString &filename, spb::tagreader::SongMeta
|
||||
|
||||
if (file_asf->tag()) {
|
||||
Decode(file_asf->tag()->comment(), song->mutable_comment());
|
||||
|
||||
const TagLib::ASF::AttributeListMap &attributes_map = file_asf->tag()->attributeListMap();
|
||||
|
||||
if (attributes_map.contains(kASF_OriginalDate_ID)) {
|
||||
const TagLib::ASF::AttributeList &attributes = attributes_map[kASF_OriginalDate_ID];
|
||||
if (!attributes.isEmpty()) {
|
||||
song->set_originalyear(TStringToQString(attributes.front().toString()).left(4).toInt());
|
||||
}
|
||||
}
|
||||
else if (attributes_map.contains(kASF_OriginalYear_ID)) {
|
||||
const TagLib::ASF::AttributeList &attributes = attributes_map[kASF_OriginalYear_ID];
|
||||
if (!attributes.isEmpty()) {
|
||||
song->set_originalyear(TStringToQString(attributes.front().toString()).left(4).toInt());
|
||||
}
|
||||
}
|
||||
|
||||
if (attributes_map.contains("FMPS/Playcount")) {
|
||||
const TagLib::ASF::AttributeList &attributes = attributes_map["FMPS/Playcount"];
|
||||
if (!attributes.isEmpty()) {
|
||||
int playcount = TStringToQString(attributes.front().toString()).toInt();
|
||||
if (song->playcount() <= 0 && playcount > 0) {
|
||||
song->set_playcount(static_cast<uint>(playcount));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (attributes_map.contains("FMPS/Rating")) {
|
||||
const TagLib::ASF::AttributeList& attributes = attributes_map["FMPS/Rating"];
|
||||
if (!attributes.isEmpty()) {
|
||||
float rating = TStringToQString(attributes.front().toString()).toFloat();
|
||||
if (song->rating() <= 0 && rating > 0) {
|
||||
song->set_rating(rating);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
const TagLib::ASF::AttributeListMap &attributes_map = file_asf->tag()->attributeListMap();
|
||||
|
||||
if (attributes_map.contains(kASF_OriginalDate_ID)) {
|
||||
const TagLib::ASF::AttributeList &attributes = attributes_map[kASF_OriginalDate_ID];
|
||||
if (!attributes.isEmpty()) {
|
||||
song->set_originalyear(TStringToQString(attributes.front().toString()).left(4).toInt());
|
||||
}
|
||||
}
|
||||
else if (attributes_map.contains(kASF_OriginalYear_ID)) {
|
||||
const TagLib::ASF::AttributeList &attributes = attributes_map[kASF_OriginalYear_ID];
|
||||
if (!attributes.isEmpty()) {
|
||||
song->set_originalyear(TStringToQString(attributes.front().toString()).left(4).toInt());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
else if (TagLib::MPC::File *file_mpc = dynamic_cast<TagLib::MPC::File*>(fileref->file())) {
|
||||
@@ -397,7 +475,7 @@ void TagReaderTagLib::ReadFile(const QString &filename, spb::tagreader::SongMeta
|
||||
}
|
||||
|
||||
if (!disc.isEmpty()) {
|
||||
const int i = disc.indexOf('/');
|
||||
const qint64 i = disc.indexOf('/');
|
||||
if (i != -1) {
|
||||
// disc.right( i ).toInt() is total number of discs, we don't use this at the moment
|
||||
song->set_disc(disc.left(i).toInt());
|
||||
@@ -420,16 +498,15 @@ void TagReaderTagLib::ReadFile(const QString &filename, spb::tagreader::SongMeta
|
||||
if (!lyrics.isEmpty()) song->set_lyrics(lyrics.toStdString());
|
||||
|
||||
// Set integer fields to -1 if they're not valid
|
||||
#define SetDefault(field) if (song->field() <= 0) { song->set_##field(-1); }
|
||||
SetDefault(track);
|
||||
SetDefault(disc);
|
||||
SetDefault(year);
|
||||
SetDefault(originalyear);
|
||||
SetDefault(bitrate);
|
||||
SetDefault(samplerate);
|
||||
SetDefault(bitdepth);
|
||||
SetDefault(lastplayed);
|
||||
#undef SetDefault
|
||||
|
||||
if (song->track() <= 0) { song->set_track(-1); }
|
||||
if (song->disc() <= 0) { song->set_disc(-1); }
|
||||
if (song->year() <= 0) { song->set_year(-1); }
|
||||
if (song->originalyear() <= 0) { song->set_originalyear(-1); }
|
||||
if (song->samplerate() <= 0) { song->set_samplerate(-1); }
|
||||
if (song->bitdepth() <= 0) { song->set_bitdepth(-1); }
|
||||
if (song->bitrate() <= 0) { song->set_bitrate(-1); }
|
||||
if (song->lastplayed() <= 0) { song->set_lastplayed(-1); }
|
||||
|
||||
}
|
||||
|
||||
@@ -464,7 +541,11 @@ void TagReaderTagLib::ParseOggTag(const TagLib::Ogg::FieldListMap &map, QString
|
||||
if (!map["COVERART"].isEmpty()) song->set_art_automatic(kEmbeddedCover);
|
||||
if (!map["METADATA_BLOCK_PICTURE"].isEmpty()) song->set_art_automatic(kEmbeddedCover);
|
||||
|
||||
if (!map["FMPS_PLAYCOUNT"].isEmpty() && song->playcount() <= 0) song->set_playcount(TStringToQString( map["FMPS_PLAYCOUNT"].front() ).trimmed().toFloat());
|
||||
if (!map["FMPS_PLAYCOUNT"].isEmpty() && song->playcount() <= 0) {
|
||||
const int playcount = TStringToQString(map["FMPS_PLAYCOUNT"].front()).trimmed().toInt();
|
||||
song->set_playcount(static_cast<uint>(playcount));
|
||||
}
|
||||
if (!map["FMPS_RATING"].isEmpty() && song->rating() <= 0) song->set_rating(TStringToQString(map["FMPS_RATING"].front()).trimmed().toFloat());
|
||||
|
||||
if (!map["LYRICS"].isEmpty()) Decode(map["LYRICS"].front(), song->mutable_lyrics());
|
||||
else if (!map["UNSYNCEDLYRICS"].isEmpty()) Decode(map["UNSYNCEDLYRICS"].front(), song->mutable_lyrics());
|
||||
@@ -507,9 +588,16 @@ void TagReaderTagLib::ParseAPETag(const TagLib::APE::ItemListMap &map, QString *
|
||||
}
|
||||
|
||||
if (map.contains("FMPS_PLAYCOUNT")) {
|
||||
int playcount = TStringToQString(map["FMPS_PLAYCOUNT"].toString()).toFloat();
|
||||
const int playcount = TStringToQString(map["FMPS_PLAYCOUNT"].toString()).toInt();
|
||||
if (song->playcount() <= 0 && playcount > 0) {
|
||||
song->set_playcount(playcount);
|
||||
song->set_playcount(static_cast<uint>(playcount));
|
||||
}
|
||||
}
|
||||
|
||||
if (map.contains("FMPS_RATING")) {
|
||||
const float rating = TStringToQString(map["FMPS_RATING"].toString()).toFloat();
|
||||
if (song->rating() <= 0 && rating > 0) {
|
||||
song->set_rating(rating);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -552,7 +640,8 @@ bool TagReaderTagLib::SaveFile(const QString &filename, const spb::tagreader::So
|
||||
bool result = false;
|
||||
|
||||
if (TagLib::FLAC::File *file_flac = dynamic_cast<TagLib::FLAC::File*>(fileref->file())) {
|
||||
TagLib::Ogg::XiphComment *tag = file_flac->xiphComment();
|
||||
TagLib::Ogg::XiphComment *tag = file_flac->xiphComment(true);
|
||||
if (!tag) return false;
|
||||
SetVorbisComments(tag, song);
|
||||
}
|
||||
|
||||
@@ -589,18 +678,19 @@ bool TagReaderTagLib::SaveFile(const QString &filename, const spb::tagreader::So
|
||||
|
||||
else if (TagLib::MP4::File *file_mp4 = dynamic_cast<TagLib::MP4::File*>(fileref->file())) {
|
||||
TagLib::MP4::Tag *tag = file_mp4->tag();
|
||||
if (!tag) return false;
|
||||
tag->setItem("disk", TagLib::MP4::Item(song.disc() <= 0 -1 ? 0 : song.disc(), 0));
|
||||
tag->setItem("\251wrt", TagLib::StringList(song.composer().c_str()));
|
||||
tag->setItem("\251grp", TagLib::StringList(song.grouping().c_str()));
|
||||
tag->setItem("\251lyr", TagLib::StringList(song.lyrics().c_str()));
|
||||
tag->setItem("aART", TagLib::StringList(song.albumartist().c_str()));
|
||||
tag->setItem("\251wrt", TagLib::StringList(TagLib::String(song.composer(), TagLib::String::UTF8)));
|
||||
tag->setItem("\251grp", TagLib::StringList(TagLib::String(song.grouping(), TagLib::String::UTF8)));
|
||||
tag->setItem("\251lyr", TagLib::StringList(TagLib::String(song.lyrics(), TagLib::String::UTF8)));
|
||||
tag->setItem("aART", TagLib::StringList(TagLib::String(song.albumartist(), TagLib::String::UTF8)));
|
||||
tag->setItem("cpil", TagLib::StringList(song.compilation() ? "1" : "0"));
|
||||
}
|
||||
|
||||
// Handle all the files which have VorbisComments (Ogg, OPUS, ...) in the same way;
|
||||
// apart, so we keep specific behavior for some formats by adding another "else if" block above.
|
||||
if (TagLib::Ogg::XiphComment *tag = dynamic_cast<TagLib::Ogg::XiphComment*>(fileref->file()->tag())) {
|
||||
SetVorbisComments(tag, song);
|
||||
if (TagLib::Ogg::XiphComment *xiph_comment = dynamic_cast<TagLib::Ogg::XiphComment*>(fileref->file()->tag())) {
|
||||
SetVorbisComments(xiph_comment, song);
|
||||
}
|
||||
|
||||
result = fileref->save();
|
||||
@@ -664,6 +754,30 @@ void TagReaderTagLib::SetTextFrame(const char *id, const std::string &value, Tag
|
||||
|
||||
}
|
||||
|
||||
void TagReaderTagLib::SetUserTextFrame(const QString &description, const QString &value, TagLib::ID3v2::Tag *tag) const {
|
||||
|
||||
const QByteArray descr_utf8(description.toUtf8());
|
||||
const QByteArray value_utf8(value.toUtf8());
|
||||
SetUserTextFrame(std::string(descr_utf8.constData(), descr_utf8.length()), std::string(value_utf8.constData(), value_utf8.length()), tag);
|
||||
|
||||
}
|
||||
|
||||
void TagReaderTagLib::SetUserTextFrame(const std::string &description, const std::string &value, TagLib::ID3v2::Tag *tag) const {
|
||||
|
||||
const TagLib::String t_description = StdStringToTaglibString(description);
|
||||
TagLib::ID3v2::UserTextIdentificationFrame *frame = TagLib::ID3v2::UserTextIdentificationFrame::find(tag, t_description);
|
||||
if (frame) {
|
||||
tag->removeFrame(frame);
|
||||
}
|
||||
|
||||
// Create and add a new frame
|
||||
frame = new TagLib::ID3v2::UserTextIdentificationFrame(TagLib::String::UTF8);
|
||||
frame->setDescription(t_description);
|
||||
frame->setText(StdStringToTaglibString(value));
|
||||
tag->addFrame(frame);
|
||||
|
||||
}
|
||||
|
||||
void TagReaderTagLib::SetUnsyncLyricsFrame(const std::string &value, TagLib::ID3v2::Tag *tag) const {
|
||||
|
||||
TagLib::ByteVector id_vector("USLT");
|
||||
@@ -703,86 +817,93 @@ QByteArray TagReaderTagLib::LoadEmbeddedArt(const QString &filename) const {
|
||||
qLog(Debug) << "Loading art from" << filename;
|
||||
|
||||
#ifdef Q_OS_WIN32
|
||||
TagLib::FileRef ref(filename.toStdWString().c_str());
|
||||
TagLib::FileRef fileref(filename.toStdWString().c_str());
|
||||
#else
|
||||
TagLib::FileRef ref(QFile::encodeName(filename).constData());
|
||||
TagLib::FileRef fileref(QFile::encodeName(filename).constData());
|
||||
#endif
|
||||
|
||||
if (ref.isNull() || !ref.file()) return QByteArray();
|
||||
if (fileref.isNull() || !fileref.file()) return QByteArray();
|
||||
|
||||
// FLAC
|
||||
if (TagLib::FLAC::File *flac_file = dynamic_cast<TagLib::FLAC::File*>(ref.file())) {
|
||||
if (TagLib::FLAC::File *flac_file = dynamic_cast<TagLib::FLAC::File*>(fileref.file())) {
|
||||
if (flac_file->xiphComment()) {
|
||||
TagLib::List<TagLib::FLAC::Picture*> pics = flac_file->pictureList();
|
||||
if (!pics.isEmpty()) {
|
||||
TagLib::FLAC::Picture *picture = nullptr;
|
||||
for (std::list<TagLib::FLAC::Picture*>::iterator it = pics.begin() ; it != pics.end() ; ++it) {
|
||||
picture = *it;
|
||||
if (picture->type() == TagLib::FLAC::Picture::FrontCover) {
|
||||
break;
|
||||
TagLib::List<TagLib::FLAC::Picture*> pictures = flac_file->pictureList();
|
||||
if (!pictures.isEmpty()) {
|
||||
for (TagLib::FLAC::Picture *picture : pictures) {
|
||||
if (picture->type() == TagLib::FLAC::Picture::FrontCover && picture->data().size() > 0) {
|
||||
QByteArray data(picture->data().data(), picture->data().size());
|
||||
if (!data.isEmpty()) {
|
||||
return data;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (picture) return QByteArray(picture->data().data(), picture->data().size());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// WavPack
|
||||
if (TagLib::WavPack::File *wavpack_file = dynamic_cast<TagLib::WavPack::File*>(ref.file())) {
|
||||
return LoadEmbeddedAPEArt(wavpack_file->APETag()->itemListMap());
|
||||
if (TagLib::WavPack::File *wavpack_file = dynamic_cast<TagLib::WavPack::File*>(fileref.file())) {
|
||||
if (wavpack_file->APETag()) {
|
||||
return LoadEmbeddedAPEArt(wavpack_file->APETag()->itemListMap());
|
||||
}
|
||||
}
|
||||
|
||||
// APE
|
||||
if (TagLib::APE::File *ape_file = dynamic_cast<TagLib::APE::File*>(ref.file())) {
|
||||
return LoadEmbeddedAPEArt(ape_file->APETag()->itemListMap());
|
||||
if (TagLib::APE::File *ape_file = dynamic_cast<TagLib::APE::File*>(fileref.file())) {
|
||||
if (ape_file->APETag()) {
|
||||
return LoadEmbeddedAPEArt(ape_file->APETag()->itemListMap());
|
||||
}
|
||||
}
|
||||
|
||||
// MPC
|
||||
if (TagLib::MPC::File *mpc_file = dynamic_cast<TagLib::MPC::File*>(ref.file())) {
|
||||
return LoadEmbeddedAPEArt(mpc_file->APETag()->itemListMap());
|
||||
if (TagLib::MPC::File *mpc_file = dynamic_cast<TagLib::MPC::File*>(fileref.file())) {
|
||||
if (mpc_file->APETag()) {
|
||||
return LoadEmbeddedAPEArt(mpc_file->APETag()->itemListMap());
|
||||
}
|
||||
}
|
||||
|
||||
// Ogg Vorbis / Speex
|
||||
if (TagLib::Ogg::XiphComment *xiph_comment = dynamic_cast<TagLib::Ogg::XiphComment*>(ref.file()->tag())) {
|
||||
// Ogg Vorbis / Opus / Speex
|
||||
if (TagLib::Ogg::XiphComment *xiph_comment = dynamic_cast<TagLib::Ogg::XiphComment*>(fileref.file()->tag())) {
|
||||
TagLib::Ogg::FieldListMap map = xiph_comment->fieldListMap();
|
||||
|
||||
TagLib::List<TagLib::FLAC::Picture*> pics = xiph_comment->pictureList();
|
||||
if (!pics.isEmpty()) {
|
||||
for (auto p : pics) {
|
||||
if (p->type() == TagLib::FLAC::Picture::FrontCover)
|
||||
return QByteArray(p->data().data(), p->data().size());
|
||||
TagLib::List<TagLib::FLAC::Picture*> pictures = xiph_comment->pictureList();
|
||||
if (!pictures.isEmpty()) {
|
||||
for (TagLib::FLAC::Picture *picture : pictures) {
|
||||
if (picture->type() == TagLib::FLAC::Picture::FrontCover && picture->data().size() > 0) {
|
||||
QByteArray data(picture->data().data(), picture->data().size());
|
||||
if (!data.isEmpty()) {
|
||||
return data;
|
||||
}
|
||||
}
|
||||
}
|
||||
// If there was no specific front cover, just take the first picture
|
||||
std::list<TagLib::FLAC::Picture*>::iterator it = pics.begin();
|
||||
TagLib::FLAC::Picture *picture = *it;
|
||||
|
||||
return QByteArray(picture->data().data(), picture->data().size());
|
||||
}
|
||||
|
||||
// Ogg lacks a definitive standard for embedding cover art, but it seems b64 encoding a field called COVERART is the general convention
|
||||
if (map.contains("COVERART"))
|
||||
if (map.contains("COVERART")) {
|
||||
return QByteArray::fromBase64(map["COVERART"].toString().toCString());
|
||||
}
|
||||
|
||||
return QByteArray();
|
||||
}
|
||||
|
||||
// MP3
|
||||
if (TagLib::MPEG::File *file_mp3 = dynamic_cast<TagLib::MPEG::File*>(ref.file())) {
|
||||
if (TagLib::MPEG::File *file_mp3 = dynamic_cast<TagLib::MPEG::File*>(fileref.file())) {
|
||||
if (file_mp3->ID3v2Tag()) {
|
||||
TagLib::ID3v2::FrameList apic_frames = file_mp3->ID3v2Tag()->frameListMap()["APIC"];
|
||||
if (apic_frames.isEmpty())
|
||||
if (apic_frames.isEmpty()) {
|
||||
return QByteArray();
|
||||
}
|
||||
|
||||
TagLib::ID3v2::AttachedPictureFrame *pic = static_cast<TagLib::ID3v2::AttachedPictureFrame*>(apic_frames.front());
|
||||
TagLib::ID3v2::AttachedPictureFrame *picture = static_cast<TagLib::ID3v2::AttachedPictureFrame*>(apic_frames.front());
|
||||
|
||||
return QByteArray(reinterpret_cast<const char*>(pic->picture().data()), pic->picture().size());
|
||||
return QByteArray(reinterpret_cast<const char*>(picture->picture().data()), picture->picture().size());
|
||||
}
|
||||
}
|
||||
|
||||
// MP4/AAC
|
||||
if (TagLib::MP4::File *aac_file = dynamic_cast<TagLib::MP4::File*>(ref.file())) {
|
||||
if (TagLib::MP4::File *aac_file = dynamic_cast<TagLib::MP4::File*>(fileref.file())) {
|
||||
TagLib::MP4::Tag *tag = aac_file->tag();
|
||||
if (tag->item("covr").isValid()) {
|
||||
if (tag && tag->item("covr").isValid()) {
|
||||
const TagLib::MP4::CoverArtList &art_list = tag->item("covr").toCoverArtList();
|
||||
|
||||
if (!art_list.isEmpty()) {
|
||||
@@ -822,78 +943,292 @@ bool TagReaderTagLib::SaveEmbeddedArt(const QString &filename, const QByteArray
|
||||
qLog(Debug) << "Saving art to" << filename;
|
||||
|
||||
#ifdef Q_OS_WIN32
|
||||
TagLib::FileRef ref(filename.toStdWString().c_str());
|
||||
TagLib::FileRef fileref(filename.toStdWString().c_str());
|
||||
#else
|
||||
TagLib::FileRef ref(QFile::encodeName(filename).constData());
|
||||
TagLib::FileRef fileref(QFile::encodeName(filename).constData());
|
||||
#endif
|
||||
|
||||
if (ref.isNull() || !ref.file()) return false;
|
||||
if (fileref.isNull() || !fileref.file()) return false;
|
||||
|
||||
// FLAC
|
||||
if (TagLib::FLAC::File *flac_file = dynamic_cast<TagLib::FLAC::File*>(ref.file())) {
|
||||
if (flac_file->xiphComment()) {
|
||||
flac_file->removePictures();
|
||||
if (!data.isEmpty()) {
|
||||
TagLib::FLAC::Picture *picture = new TagLib::FLAC::Picture();
|
||||
picture->setType(TagLib::FLAC::Picture::FrontCover);
|
||||
picture->setMimeType("image/jpeg");
|
||||
picture->setData(TagLib::ByteVector(data.constData(), data.size()));
|
||||
flac_file->addPicture(picture);
|
||||
}
|
||||
if (TagLib::FLAC::File *flac_file = dynamic_cast<TagLib::FLAC::File*>(fileref.file())) {
|
||||
TagLib::Ogg::XiphComment *vorbis_comments = flac_file->xiphComment(true);
|
||||
if (!vorbis_comments) return false;
|
||||
flac_file->removePictures();
|
||||
if (!data.isEmpty()) {
|
||||
TagLib::FLAC::Picture *picture = new TagLib::FLAC::Picture();
|
||||
picture->setType(TagLib::FLAC::Picture::FrontCover);
|
||||
picture->setMimeType("image/jpeg");
|
||||
picture->setData(TagLib::ByteVector(data.constData(), data.size()));
|
||||
flac_file->addPicture(picture);
|
||||
}
|
||||
}
|
||||
|
||||
// Ogg Vorbis / Speex
|
||||
else if (TagLib::Ogg::XiphComment *xiph_comment = dynamic_cast<TagLib::Ogg::XiphComment*>(ref.file()->tag())) {
|
||||
TagLib::FLAC::Picture *picture = new TagLib::FLAC::Picture();
|
||||
picture->setType(TagLib::FLAC::Picture::FrontCover);
|
||||
picture->setMimeType("image/jpeg");
|
||||
picture->setData(TagLib::ByteVector(data.constData(), data.size()));
|
||||
xiph_comment->addPicture(picture);
|
||||
// Ogg Vorbis / Opus / Speex
|
||||
else if (TagLib::Ogg::XiphComment *xiph_comment = dynamic_cast<TagLib::Ogg::XiphComment*>(fileref.file()->tag())) {
|
||||
xiph_comment->removeAllPictures();
|
||||
if (!data.isEmpty()) {
|
||||
TagLib::FLAC::Picture *picture = new TagLib::FLAC::Picture();
|
||||
picture->setType(TagLib::FLAC::Picture::FrontCover);
|
||||
picture->setMimeType("image/jpeg");
|
||||
picture->setData(TagLib::ByteVector(data.constData(), data.size()));
|
||||
xiph_comment->addPicture(picture);
|
||||
}
|
||||
}
|
||||
|
||||
// MP3
|
||||
else if (TagLib::MPEG::File *file_mp3 = dynamic_cast<TagLib::MPEG::File*>(ref.file())) {
|
||||
if (file_mp3->ID3v2Tag()) {
|
||||
TagLib::ID3v2::Tag *tag = file_mp3->ID3v2Tag();
|
||||
else if (TagLib::MPEG::File *file_mp3 = dynamic_cast<TagLib::MPEG::File*>(fileref.file())) {
|
||||
TagLib::ID3v2::Tag *tag = file_mp3->ID3v2Tag();
|
||||
if (!tag) return false;
|
||||
|
||||
// Remove existing covers
|
||||
TagLib::ID3v2::FrameList apiclist = tag->frameListMap()["APIC"];
|
||||
for (TagLib::ID3v2::FrameList::ConstIterator it = apiclist.begin() ; it != apiclist.end() ; ++it ) {
|
||||
TagLib::ID3v2::AttachedPictureFrame *frame = dynamic_cast<TagLib::ID3v2::AttachedPictureFrame*>(*it);
|
||||
tag->removeFrame(frame, false);
|
||||
}
|
||||
// Remove existing covers
|
||||
TagLib::ID3v2::FrameList apiclist = tag->frameListMap()["APIC"];
|
||||
for (TagLib::ID3v2::FrameList::ConstIterator it = apiclist.begin() ; it != apiclist.end() ; ++it ) {
|
||||
TagLib::ID3v2::AttachedPictureFrame *frame = dynamic_cast<TagLib::ID3v2::AttachedPictureFrame*>(*it);
|
||||
tag->removeFrame(frame, false);
|
||||
}
|
||||
|
||||
if (!data.isEmpty()) {
|
||||
// Add new cover
|
||||
TagLib::ID3v2::AttachedPictureFrame *frontcover = nullptr;
|
||||
frontcover = new TagLib::ID3v2::AttachedPictureFrame("APIC");
|
||||
frontcover->setType(TagLib::ID3v2::AttachedPictureFrame::FrontCover);
|
||||
frontcover->setMimeType("image/jpeg");
|
||||
frontcover->setPicture(TagLib::ByteVector(data.constData(), data.count()));
|
||||
tag->addFrame(frontcover);
|
||||
}
|
||||
if (!data.isEmpty()) {
|
||||
// Add new cover
|
||||
TagLib::ID3v2::AttachedPictureFrame *frontcover = nullptr;
|
||||
frontcover = new TagLib::ID3v2::AttachedPictureFrame("APIC");
|
||||
frontcover->setType(TagLib::ID3v2::AttachedPictureFrame::FrontCover);
|
||||
frontcover->setMimeType("image/jpeg");
|
||||
frontcover->setPicture(TagLib::ByteVector(data.constData(), data.count()));
|
||||
tag->addFrame(frontcover);
|
||||
}
|
||||
}
|
||||
|
||||
// MP4/AAC
|
||||
else if (TagLib::MP4::File *aac_file = dynamic_cast<TagLib::MP4::File*>(ref.file())) {
|
||||
else if (TagLib::MP4::File *aac_file = dynamic_cast<TagLib::MP4::File*>(fileref.file())) {
|
||||
TagLib::MP4::Tag *tag = aac_file->tag();
|
||||
if (!tag) return false;
|
||||
TagLib::MP4::CoverArtList covers;
|
||||
if (tag) {
|
||||
if (data.isEmpty()) {
|
||||
if (tag->contains("covr")) tag->removeItem("covr");
|
||||
}
|
||||
else {
|
||||
covers.append(TagLib::MP4::CoverArt(TagLib::MP4::CoverArt::JPEG, TagLib::ByteVector(data.constData(), data.count())));
|
||||
tag->setItem("covr", covers);
|
||||
}
|
||||
if (data.isEmpty()) {
|
||||
if (tag->contains("covr")) tag->removeItem("covr");
|
||||
}
|
||||
else {
|
||||
covers.append(TagLib::MP4::CoverArt(TagLib::MP4::CoverArt::JPEG, TagLib::ByteVector(data.constData(), data.count())));
|
||||
tag->setItem("covr", covers);
|
||||
}
|
||||
}
|
||||
|
||||
// Not supported.
|
||||
else return false;
|
||||
|
||||
return ref.file()->save();
|
||||
return fileref.file()->save();
|
||||
|
||||
}
|
||||
|
||||
TagLib::ID3v2::PopularimeterFrame *TagReaderTagLib::GetPOPMFrameFromTag(TagLib::ID3v2::Tag *tag) {
|
||||
|
||||
TagLib::ID3v2::PopularimeterFrame *frame = nullptr;
|
||||
|
||||
const TagLib::ID3v2::FrameListMap &map = tag->frameListMap();
|
||||
if (!map["POPM"].isEmpty()) {
|
||||
frame = dynamic_cast<TagLib::ID3v2::PopularimeterFrame*>(map["POPM"].front());
|
||||
}
|
||||
|
||||
if (!frame) {
|
||||
frame = new TagLib::ID3v2::PopularimeterFrame();
|
||||
tag->addFrame(frame);
|
||||
}
|
||||
|
||||
return frame;
|
||||
|
||||
}
|
||||
|
||||
float TagReaderTagLib::ConvertPOPMRating(const int POPM_rating) {
|
||||
|
||||
if (POPM_rating < 0x01) return 0.0F;
|
||||
else if (POPM_rating < 0x40) return 0.20F;
|
||||
else if (POPM_rating < 0x80) return 0.40F;
|
||||
else if (POPM_rating < 0xC0) return 0.60F;
|
||||
else if (POPM_rating < 0xFC) return 0.80F;
|
||||
|
||||
return 1.0F;
|
||||
|
||||
}
|
||||
|
||||
int TagReaderTagLib::ConvertToPOPMRating(const float rating) {
|
||||
|
||||
if (rating < 0.20) return 0x00;
|
||||
else if (rating < 0.40) return 0x01;
|
||||
else if (rating < 0.60) return 0x40;
|
||||
else if (rating < 0.80) return 0x80;
|
||||
else if (rating < 1.0) return 0xC0;
|
||||
|
||||
return 0xFF;
|
||||
|
||||
}
|
||||
|
||||
bool TagReaderTagLib::SaveSongPlaycountToFile(const QString &filename, const spb::tagreader::SongMetadata &song) const {
|
||||
|
||||
if (filename.isEmpty()) return false;
|
||||
|
||||
qLog(Debug) << "Saving song playcount to" << filename;
|
||||
|
||||
std::unique_ptr<TagLib::FileRef> fileref(factory_->GetFileRef(filename));
|
||||
if (!fileref || fileref->isNull()) return false;
|
||||
|
||||
if (TagLib::FLAC::File *flac_file = dynamic_cast<TagLib::FLAC::File*>(fileref->file())) {
|
||||
TagLib::Ogg::XiphComment *vorbis_comments = flac_file->xiphComment(true);
|
||||
if (!vorbis_comments) return false;
|
||||
if (song.playcount() > 0) {
|
||||
vorbis_comments->addField("FMPS_PLAYCOUNT", TagLib::String::number(static_cast<int>(song.playcount())), true);
|
||||
}
|
||||
else {
|
||||
vorbis_comments->removeFields("FMPS_PLAYCOUNT");
|
||||
}
|
||||
}
|
||||
else if (TagLib::WavPack::File *wavpack_file = dynamic_cast<TagLib::WavPack::File*>(fileref->file())) {
|
||||
TagLib::APE::Tag *tag = wavpack_file->APETag(true);
|
||||
if (!tag) return false;
|
||||
if (song.playcount() > 0) {
|
||||
tag->setItem("FMPS_PlayCount", TagLib::APE::Item("FMPS_PlayCount", TagLib::String::number(static_cast<int>(song.playcount()))));
|
||||
}
|
||||
}
|
||||
else if (TagLib::APE::File *ape_file = dynamic_cast<TagLib::APE::File*>(fileref->file())) {
|
||||
TagLib::APE::Tag *tag = ape_file->APETag(true);
|
||||
if (!tag) return false;
|
||||
if (song.playcount() > 0) {
|
||||
tag->setItem("FMPS_PlayCount", TagLib::APE::Item("FMPS_PlayCount", TagLib::String::number(static_cast<int>(song.playcount()))));
|
||||
}
|
||||
}
|
||||
else if (TagLib::Ogg::XiphComment *xiph_comment = dynamic_cast<TagLib::Ogg::XiphComment*>(fileref->file()->tag())) {
|
||||
if (song.playcount() > 0) {
|
||||
xiph_comment->addField("FMPS_PLAYCOUNT", TagLib::String::number(static_cast<int>(song.playcount())), true);
|
||||
}
|
||||
else {
|
||||
xiph_comment->removeFields("FMPS_PLAYCOUNT");
|
||||
}
|
||||
}
|
||||
else if (TagLib::MPEG::File *mpeg_file = dynamic_cast<TagLib::MPEG::File*>(fileref->file())) {
|
||||
TagLib::ID3v2::Tag *tag = mpeg_file->ID3v2Tag(true);
|
||||
if (!tag) return false;
|
||||
if (song.playcount() > 0) {
|
||||
SetUserTextFrame("FMPS_PlayCount", QString::number(song.playcount()), tag);
|
||||
TagLib::ID3v2::PopularimeterFrame *frame = GetPOPMFrameFromTag(tag);
|
||||
if (frame) {
|
||||
frame->setCounter(song.playcount());
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
else if (TagLib::MP4::File *mp4_file = dynamic_cast<TagLib::MP4::File*>(fileref->file())) {
|
||||
TagLib::MP4::Tag *tag = mp4_file->tag();
|
||||
if (!tag) return false;
|
||||
if (song.playcount() > 0) {
|
||||
tag->setItem(kMP4_FMPS_Playcount_ID, TagLib::MP4::Item(TagLib::String::number(static_cast<int>(song.playcount()))));
|
||||
}
|
||||
}
|
||||
else if (TagLib::MPC::File *mpc_file = dynamic_cast<TagLib::MPC::File*>(fileref->file())) {
|
||||
TagLib::APE::Tag *tag = mpc_file->APETag(true);
|
||||
if (!tag) return false;
|
||||
if (song.playcount() > 0) {
|
||||
tag->setItem("FMPS_PlayCount", TagLib::APE::Item("FMPS_PlayCount", TagLib::String::number(static_cast<int>(song.playcount()))));
|
||||
}
|
||||
}
|
||||
else if (TagLib::ASF::File *asf_file = dynamic_cast<TagLib::ASF::File*>(fileref->file())) {
|
||||
TagLib::ASF::Tag *tag = asf_file->tag();
|
||||
if (!tag) return false;
|
||||
if (song.playcount() > 0) {
|
||||
tag->addAttribute("FMPS/Playcount", TagLib::ASF::Attribute(QStringToTaglibString(QString::number(song.playcount()))));
|
||||
}
|
||||
}
|
||||
else {
|
||||
return true;
|
||||
}
|
||||
|
||||
bool ret = fileref->save();
|
||||
#ifdef Q_OS_LINUX
|
||||
if (ret) {
|
||||
// Linux: inotify doesn't seem to notice the change to the file unless we change the timestamps as well. (this is what touch does)
|
||||
utimensat(0, QFile::encodeName(filename).constData(), nullptr, 0);
|
||||
}
|
||||
#endif // Q_OS_LINUX
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
bool TagReaderTagLib::SaveSongRatingToFile(const QString &filename, const spb::tagreader::SongMetadata &song) const {
|
||||
|
||||
if (filename.isNull()) return false;
|
||||
|
||||
qLog(Debug) << "Saving song rating to" << filename;
|
||||
|
||||
if (song.rating() < 0) {
|
||||
return true;
|
||||
}
|
||||
|
||||
std::unique_ptr<TagLib::FileRef> fileref(factory_->GetFileRef(filename));
|
||||
|
||||
if (!fileref || fileref->isNull()) return false;
|
||||
|
||||
if (TagLib::FLAC::File *flac_file = dynamic_cast<TagLib::FLAC::File*>(fileref->file())) {
|
||||
TagLib::Ogg::XiphComment *vorbis_comments = flac_file->xiphComment(true);
|
||||
if (!vorbis_comments) return false;
|
||||
if (song.rating() > 0) {
|
||||
vorbis_comments->addField("FMPS_RATING", QStringToTaglibString(QString::number(song.rating())), true);
|
||||
}
|
||||
else {
|
||||
vorbis_comments->removeFields("FMPS_RATING");
|
||||
}
|
||||
}
|
||||
else if (TagLib::WavPack::File *wavpack_file = dynamic_cast<TagLib::WavPack::File*>(fileref->file())) {
|
||||
TagLib::APE::Tag *tag = wavpack_file->APETag(true);
|
||||
if (!tag) return false;
|
||||
tag->setItem("FMPS_Rating", TagLib::APE::Item("FMPS_Rating", TagLib::StringList(QStringToTaglibString(QString::number(song.rating())))));
|
||||
}
|
||||
else if (TagLib::APE::File *ape_file = dynamic_cast<TagLib::APE::File*>(fileref->file())) {
|
||||
TagLib::APE::Tag *tag = ape_file->APETag(true);
|
||||
if (!tag) return false;
|
||||
tag->setItem("FMPS_Rating", TagLib::APE::Item("FMPS_Rating", TagLib::StringList(QStringToTaglibString(QString::number(song.rating())))));
|
||||
}
|
||||
else if (TagLib::Ogg::XiphComment *xiph_comment = dynamic_cast<TagLib::Ogg::XiphComment*>(fileref->file()->tag())) {
|
||||
if (song.rating() > 0) {
|
||||
xiph_comment->addField("FMPS_RATING", QStringToTaglibString(QString::number(song.rating())), true);
|
||||
}
|
||||
else {
|
||||
xiph_comment->removeFields("FMPS_RATING");
|
||||
}
|
||||
}
|
||||
else if (TagLib::MPEG::File *mpeg_file = dynamic_cast<TagLib::MPEG::File*>(fileref->file())) {
|
||||
TagLib::ID3v2::Tag *tag = mpeg_file->ID3v2Tag(true);
|
||||
if (!tag) return false;
|
||||
SetUserTextFrame("FMPS_Rating", QString::number(song.rating()), tag);
|
||||
TagLib::ID3v2::PopularimeterFrame *frame = GetPOPMFrameFromTag(tag);
|
||||
if (frame) {
|
||||
frame->setRating(ConvertToPOPMRating(song.rating()));
|
||||
}
|
||||
}
|
||||
else if (TagLib::MP4::File *mp4_file = dynamic_cast<TagLib::MP4::File*>(fileref->file())) {
|
||||
TagLib::MP4::Tag *tag = mp4_file->tag();
|
||||
if (!tag) return false;
|
||||
tag->setItem(kMP4_FMPS_Rating_ID, TagLib::StringList(QStringToTaglibString(QString::number(song.rating()))));
|
||||
}
|
||||
else if (TagLib::ASF::File *asf_file = dynamic_cast<TagLib::ASF::File*>(fileref->file())) {
|
||||
TagLib::ASF::Tag *tag = asf_file->tag();
|
||||
if (!tag) return false;
|
||||
tag->addAttribute("FMPS/Rating", TagLib::ASF::Attribute(QStringToTaglibString(QString::number(song.rating()))));
|
||||
}
|
||||
else if (TagLib::MPC::File *mpc_file = dynamic_cast<TagLib::MPC::File*>(fileref->file())) {
|
||||
TagLib::APE::Tag *tag = mpc_file->APETag(true);
|
||||
if (!tag) return false;
|
||||
tag->setItem("FMPS_Rating", TagLib::APE::Item("FMPS_Rating", TagLib::StringList(QStringToTaglibString(QString::number(song.rating())))));
|
||||
}
|
||||
else {
|
||||
return true;
|
||||
}
|
||||
|
||||
bool ret = fileref->save();
|
||||
#ifdef Q_OS_LINUX
|
||||
if (ret) {
|
||||
// Linux: inotify doesn't seem to notice the change to the file unless we change the timestamps as well. (this is what touch does)
|
||||
utimensat(0, QFile::encodeName(filename).constData(), nullptr, 0);
|
||||
}
|
||||
#endif // Q_OS_LINUX
|
||||
|
||||
return ret;
|
||||
|
||||
}
|
||||
|
||||
@@ -32,6 +32,7 @@
|
||||
#include <taglib/apetag.h>
|
||||
#include <taglib/apefile.h>
|
||||
#include <taglib/id3v2tag.h>
|
||||
#include <taglib/popularimeterframe.h>
|
||||
|
||||
#include "tagreaderbase.h"
|
||||
#include "tagreadermessages.pb.h"
|
||||
@@ -55,6 +56,9 @@ class TagReaderTagLib : public TagReaderBase {
|
||||
QByteArray LoadEmbeddedArt(const QString &filename) const override;
|
||||
bool SaveEmbeddedArt(const QString &filename, const QByteArray &data) 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:
|
||||
spb::tagreader::SongMetadata_FileType GuessFileType(TagLib::FileRef *fileref) const;
|
||||
|
||||
@@ -69,10 +73,16 @@ class TagReaderTagLib : public TagReaderBase {
|
||||
|
||||
void SetTextFrame(const char *id, const QString &value, TagLib::ID3v2::Tag *tag) const;
|
||||
void SetTextFrame(const char *id, const std::string &value, TagLib::ID3v2::Tag *tag) const;
|
||||
void SetUserTextFrame(const QString &description, const QString &value, TagLib::ID3v2::Tag *tag) const;
|
||||
void SetUserTextFrame(const std::string &description, const std::string &value, TagLib::ID3v2::Tag *tag) const;
|
||||
void SetUnsyncLyricsFrame(const std::string& value, TagLib::ID3v2::Tag* tag) const;
|
||||
|
||||
QByteArray LoadEmbeddedAPEArt(const TagLib::APE::ItemListMap &map) const;
|
||||
|
||||
static float ConvertPOPMRating(const int POPM_rating);
|
||||
static int ConvertToPOPMRating(const float rating);
|
||||
static TagLib::ID3v2::PopularimeterFrame *GetPOPMFrameFromTag(TagLib::ID3v2::Tag* tag);
|
||||
|
||||
private:
|
||||
FileRefFactory *factory_;
|
||||
|
||||
|
||||
@@ -106,11 +106,11 @@ void TagReaderTagParser::ReadFile(const QString &filename, spb::tagreader::SongM
|
||||
song->set_basefilename(DataCommaSizeFromQString(fileinfo.fileName()));
|
||||
song->set_url(url.constData(), url.size());
|
||||
song->set_filesize(fileinfo.size());
|
||||
song->set_mtime(fileinfo.lastModified().toSecsSinceEpoch());
|
||||
song->set_mtime(fileinfo.lastModified().isValid() ? fileinfo.lastModified().toSecsSinceEpoch() : 0);
|
||||
#if (QT_VERSION >= QT_VERSION_CHECK(5, 10, 0))
|
||||
song->set_ctime(fileinfo.birthTime().isValid() ? fileinfo.birthTime().toSecsSinceEpoch() : fileinfo.lastModified().toSecsSinceEpoch());
|
||||
song->set_ctime(fileinfo.birthTime().isValid() ? fileinfo.birthTime().toSecsSinceEpoch() : fileinfo.lastModified().isValid() ? fileinfo.lastModified().toSecsSinceEpoch() : 0);
|
||||
#else
|
||||
song->set_ctime(fileinfo.created().toSecsSinceEpoch());
|
||||
song->set_ctime(fileinfo.created().isValid() ? fileinfo.created().toSecsSinceEpoch() : fileinfo.lastModified().isValid() ? fileinfo.lastModified().toSecsSinceEpoch() : 0);
|
||||
#endif
|
||||
song->set_lastseen(QDateTime::currentDateTime().toSecsSinceEpoch());
|
||||
|
||||
@@ -225,20 +225,19 @@ void TagReaderTagParser::ReadFile(const QString &filename, spb::tagreader::SongM
|
||||
}
|
||||
|
||||
// Set integer fields to -1 if they're not valid
|
||||
#define SetDefault(field) if (song->field() <= 0) { song->set_##field(-1); }
|
||||
SetDefault(track);
|
||||
SetDefault(disc);
|
||||
SetDefault(year);
|
||||
SetDefault(originalyear);
|
||||
SetDefault(bitrate);
|
||||
SetDefault(samplerate);
|
||||
SetDefault(bitdepth);
|
||||
SetDefault(lastplayed);
|
||||
#undef SetDefault
|
||||
if (song->track() <= 0) { song->set_track(-1); }
|
||||
if (song->disc() <= 0) { song->set_disc(-1); }
|
||||
if (song->year() <= 0) { song->set_year(-1); }
|
||||
if (song->originalyear() <= 0) { song->set_originalyear(-1); }
|
||||
if (song->samplerate() <= 0) { song->set_samplerate(-1); }
|
||||
if (song->bitdepth() <= 0) { song->set_bitdepth(-1); }
|
||||
if (song->bitrate() <= 0) { song->set_bitrate(-1); }
|
||||
if (song->lastplayed() <= 0) { song->set_lastplayed(-1); }
|
||||
|
||||
song->set_valid(true);
|
||||
|
||||
taginfo.close();
|
||||
|
||||
}
|
||||
catch(...) {}
|
||||
|
||||
@@ -422,3 +421,62 @@ bool TagReaderTagParser::SaveEmbeddedArt(const QString &filename, const QByteArr
|
||||
return false;
|
||||
|
||||
}
|
||||
|
||||
bool TagReaderTagParser::SaveSongPlaycountToFile(const QString&, const spb::tagreader::SongMetadata&) const { return false; }
|
||||
|
||||
bool TagReaderTagParser::SaveSongRatingToFile(const QString &filename, const spb::tagreader::SongMetadata &song) const {
|
||||
|
||||
if (filename.isEmpty()) return false;
|
||||
|
||||
qLog(Debug) << "Saving song rating to" << filename;
|
||||
|
||||
try {
|
||||
TagParser::MediaFileInfo taginfo;
|
||||
TagParser::Diagnostics diag;
|
||||
TagParser::AbortableProgressFeedback progress;
|
||||
#ifdef Q_OS_WIN32
|
||||
taginfo.setPath(filename.toStdWString().toStdString());
|
||||
#else
|
||||
taginfo.setPath(QFile::encodeName(filename).toStdString());
|
||||
#endif
|
||||
taginfo.open(false);
|
||||
|
||||
taginfo.parseContainerFormat(diag, progress);
|
||||
if (progress.isAborted()) {
|
||||
taginfo.close();
|
||||
return false;
|
||||
}
|
||||
|
||||
taginfo.parseTracks(diag, progress);
|
||||
if (progress.isAborted()) {
|
||||
taginfo.close();
|
||||
return false;
|
||||
}
|
||||
|
||||
taginfo.parseTags(diag, progress);
|
||||
if (progress.isAborted()) {
|
||||
taginfo.close();
|
||||
return false;
|
||||
}
|
||||
|
||||
if (taginfo.tags().size() <= 0) {
|
||||
taginfo.createAppropriateTags();
|
||||
}
|
||||
|
||||
for (const auto tag : taginfo.tags()) {
|
||||
tag->setValue(TagParser::KnownField::Rating, TagParser::TagValue(song.rating()));
|
||||
}
|
||||
taginfo.applyChanges(diag, progress);
|
||||
taginfo.close();
|
||||
|
||||
for (const TagParser::DiagMessage &msg : diag) {
|
||||
qLog(Debug) << QString::fromStdString(msg.message());
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
catch(...) {}
|
||||
|
||||
return false;
|
||||
|
||||
}
|
||||
|
||||
@@ -45,6 +45,9 @@ class TagReaderTagParser : public TagReaderBase {
|
||||
QByteArray LoadEmbeddedArt(const QString &filename) const override;
|
||||
bool SaveEmbeddedArt(const QString &filename, const QByteArray &data) 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;
|
||||
|
||||
Q_DISABLE_COPY(TagReaderTagParser)
|
||||
};
|
||||
|
||||
|
||||
@@ -34,22 +34,28 @@ void TagReaderWorker::MessageArrived(const spb::tagreader::Message &message) {
|
||||
|
||||
spb::tagreader::Message reply;
|
||||
|
||||
if (message.has_read_file_request()) {
|
||||
if (message.has_is_media_file_request()) {
|
||||
reply.mutable_is_media_file_response()->set_success(tag_reader_.IsMediaFile(QStringFromStdString(message.is_media_file_request().filename())));
|
||||
}
|
||||
else if (message.has_read_file_request()) {
|
||||
tag_reader_.ReadFile(QStringFromStdString(message.read_file_request().filename()), reply.mutable_read_file_response()->mutable_metadata());
|
||||
}
|
||||
else if (message.has_save_file_request()) {
|
||||
reply.mutable_save_file_response()->set_success(tag_reader_.SaveFile(QStringFromStdString(message.save_file_request().filename()), message.save_file_request().metadata()));
|
||||
}
|
||||
|
||||
else if (message.has_is_media_file_request()) {
|
||||
reply.mutable_is_media_file_response()->set_success(tag_reader_.IsMediaFile(QStringFromStdString(message.is_media_file_request().filename())));
|
||||
}
|
||||
else if (message.has_load_embedded_art_request()) {
|
||||
QByteArray data = tag_reader_.LoadEmbeddedArt(QStringFromStdString(message.load_embedded_art_request().filename()));
|
||||
reply.mutable_load_embedded_art_response()->set_data(data.constData(), data.size());
|
||||
}
|
||||
else if (message.has_save_embedded_art_request()) {
|
||||
reply.mutable_save_embedded_art_response()->set_success(tag_reader_.SaveEmbeddedArt(QStringFromStdString(message.save_embedded_art_request().filename()), QByteArray(message.save_embedded_art_request().data().data(), message.save_embedded_art_request().data().size())));
|
||||
reply.mutable_save_embedded_art_response()->set_success(tag_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()))));
|
||||
}
|
||||
|
||||
else if (message.has_save_song_playcount_to_file_request()) {
|
||||
reply.mutable_save_song_playcount_to_file_response()->set_success(tag_reader_.SaveSongPlaycountToFile(QStringFromStdString(message.save_song_playcount_to_file_request().filename()), message.save_song_playcount_to_file_request().metadata()));
|
||||
}
|
||||
else if (message.has_save_song_rating_to_file_request()) {
|
||||
reply.mutable_save_song_rating_to_file_response()->set_success(tag_reader_.SaveSongRatingToFile(QStringFromStdString(message.save_song_rating_to_file_request().filename()), message.save_song_rating_to_file_request().metadata()));
|
||||
}
|
||||
|
||||
SendReply(message, &reply);
|
||||
|
||||
@@ -635,34 +635,6 @@ if(UNIX AND HAVE_DBUS)
|
||||
qt_add_dbus_interface(SOURCES dbus/org.kde.KGlobalAccel.xml dbus/kglobalaccel)
|
||||
qt_add_dbus_interface(SOURCES dbus/org.kde.KGlobalAccel.Component.xml dbus/kglobalaccelcomponent)
|
||||
|
||||
# org.freedesktop.Avahi.Server interface
|
||||
add_custom_command(
|
||||
OUTPUT
|
||||
${CMAKE_CURRENT_BINARY_DIR}/dbus/avahiserver.cpp
|
||||
${CMAKE_CURRENT_BINARY_DIR}/dbus/avahiserver.h
|
||||
COMMAND ${QT_DBUSXML2CPP_EXECUTABLE}
|
||||
dbus/org.freedesktop.Avahi.Server.xml
|
||||
-p ${CMAKE_CURRENT_BINARY_DIR}/dbus/avahiserver
|
||||
-i dbus/metatypes.h
|
||||
DEPENDS dbus/org.freedesktop.Avahi.Server.xml
|
||||
WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR})
|
||||
list(APPEND HEADERS ${CMAKE_CURRENT_BINARY_DIR}/dbus/avahiserver.h)
|
||||
list(APPEND SOURCES ${CMAKE_CURRENT_BINARY_DIR}/dbus/avahiserver.cpp)
|
||||
|
||||
# org.freedesktop.Avahi.EntryGroup interface
|
||||
add_custom_command(
|
||||
OUTPUT
|
||||
${CMAKE_CURRENT_BINARY_DIR}/dbus/avahientrygroup.cpp
|
||||
${CMAKE_CURRENT_BINARY_DIR}/dbus/avahientrygroup.h
|
||||
COMMAND ${QT_DBUSXML2CPP_EXECUTABLE}
|
||||
dbus/org.freedesktop.Avahi.EntryGroup.xml
|
||||
-p ${CMAKE_CURRENT_BINARY_DIR}/dbus/avahientrygroup
|
||||
-i dbus/metatypes.h
|
||||
DEPENDS dbus/org.freedesktop.Avahi.EntryGroup.xml
|
||||
WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR})
|
||||
list(APPEND HEADERS ${CMAKE_CURRENT_BINARY_DIR}/dbus/avahientrygroup.h)
|
||||
list(APPEND SOURCES ${CMAKE_CURRENT_BINARY_DIR}/dbus/avahientrygroup.cpp)
|
||||
|
||||
if(HAVE_UDISKS2)
|
||||
set_source_files_properties(dbus/org.freedesktop.DBus.ObjectManager.xml PROPERTIES NO_NAMESPACE dbus/objectmanager INCLUDE dbus/metatypes.h)
|
||||
set_source_files_properties(dbus/org.freedesktop.UDisks2.Filesystem.xml PROPERTIES NO_NAMESPACE dbus/udisks2filesystem INCLUDE dbus/metatypes.h)
|
||||
@@ -971,7 +943,6 @@ link_directories(
|
||||
${SQLITE_LIBRARY_DIRS}
|
||||
${SINGLEAPPLICATION_LIBRARY_DIRS}
|
||||
${SINGLECOREAPPLICATION_LIBRARY_DIRS}
|
||||
${QTSPARKLE_LIBRARY_DIRS}
|
||||
${Iconv_LIBRARY_DIRS}
|
||||
)
|
||||
|
||||
@@ -1014,6 +985,10 @@ if(HAVE_GIO)
|
||||
link_directories(${GIO_LIBRARY_DIRS})
|
||||
endif()
|
||||
|
||||
if(HAVE_GIO_UNIX)
|
||||
link_directories(${GIO_UNIX_LIBRARY_DIRS})
|
||||
endif()
|
||||
|
||||
if(HAVE_AUDIOCD)
|
||||
link_directories(${LIBCDIO_LIBRARY_DIRS})
|
||||
endif()
|
||||
@@ -1034,6 +1009,10 @@ if(USE_TAGPARSER AND TAGPARSER_FOUND)
|
||||
link_directories(${TAGPARSER_LIBRARY_DIRS})
|
||||
endif()
|
||||
|
||||
if(HAVE_QTSPARKLE)
|
||||
link_directories(${QTSPARKLE_LIBRARY_DIRS})
|
||||
endif()
|
||||
|
||||
add_library(strawberry_lib STATIC
|
||||
${SOURCES}
|
||||
${MOC}
|
||||
@@ -1050,6 +1029,7 @@ target_include_directories(strawberry_lib SYSTEM PUBLIC
|
||||
${GOBJECT_INCLUDE_DIRS}
|
||||
${GNUTLS_INCLUDE_DIRS}
|
||||
${SQLITE_INCLUDE_DIRS}
|
||||
${PROTOBUF_INCLUDE_DIRS}
|
||||
)
|
||||
|
||||
if(HAVE_QPA_QPLATFORMNATIVEINTERFACE_H)
|
||||
@@ -1077,7 +1057,6 @@ target_link_libraries(strawberry_lib PUBLIC
|
||||
${QT_LIBRARIES}
|
||||
${SINGLEAPPLICATION_LIBRARIES}
|
||||
${SINGLECOREAPPLICATION_LIBRARIES}
|
||||
${QTSPARKLE_LIBRARIES}
|
||||
${Iconv_LIBRARIES}
|
||||
libstrawberry-common
|
||||
libstrawberry-tagreader
|
||||
@@ -1141,6 +1120,11 @@ if(HAVE_GIO)
|
||||
target_link_libraries(strawberry_lib PRIVATE ${GIO_LIBRARIES})
|
||||
endif()
|
||||
|
||||
if(HAVE_GIO_UNIX)
|
||||
target_include_directories(strawberry_lib SYSTEM PRIVATE ${GIO_UNIX_INCLUDE_DIRS})
|
||||
target_link_libraries(strawberry_lib PRIVATE ${GIO_UNIX_LIBRARIES})
|
||||
endif()
|
||||
|
||||
if(HAVE_AUDIOCD)
|
||||
target_include_directories(strawberry_lib SYSTEM PRIVATE ${LIBCDIO_INCLUDE_DIRS})
|
||||
target_link_libraries(strawberry_lib PRIVATE ${LIBCDIO_LIBRARIES})
|
||||
@@ -1171,19 +1155,25 @@ if(APPLE)
|
||||
"-framework ScriptingBridge"
|
||||
)
|
||||
target_link_libraries(strawberry_lib PRIVATE ${SPMEDIAKEYTAP_LIBRARIES})
|
||||
if(HAVE_SPARKLE)
|
||||
target_include_directories(strawberry_lib SYSTEM PRIVATE ${SPARKLE}/Headers)
|
||||
target_link_libraries(strawberry_lib PRIVATE ${SPARKLE})
|
||||
endif()
|
||||
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()
|
||||
endif()
|
||||
endif()
|
||||
|
||||
if(HAVE_QTSPARKLE)
|
||||
target_include_directories(strawberry_lib SYSTEM PRIVATE ${QTSPARKLE_INCLUDE_DIRS})
|
||||
target_link_libraries(strawberry_lib PRIVATE ${QTSPARKLE_LIBRARIES})
|
||||
endif()
|
||||
|
||||
###############################################################################
|
||||
|
||||
|
||||
@@ -47,14 +47,7 @@
|
||||
//
|
||||
// TODO:
|
||||
// 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
|
||||
|
||||
// make the linker happy only for gcc < 4.0
|
||||
#if !(__GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 0)) && \
|
||||
!defined(Q_OS_WIN32)
|
||||
template class Analyzer::Base<QWidget>;
|
||||
#endif
|
||||
// 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)
|
||||
: QWidget(parent),
|
||||
@@ -80,7 +73,7 @@ void Analyzer::Base::transform(Scope &scope) {
|
||||
}
|
||||
|
||||
fht_->logSpectrum(scope.data(), aux.data());
|
||||
fht_->scale(scope.data(), 1.0 / 20);
|
||||
fht_->scale(scope.data(), 1.0F / 20);
|
||||
|
||||
scope.resize(fht_->size() / 2); // second half of values are rubbish
|
||||
|
||||
@@ -180,7 +173,7 @@ void Analyzer::Base::demo(QPainter &p) {
|
||||
|
||||
const double dt = static_cast<double>(t) / 200;
|
||||
for (uint i = 0; i < s.size(); ++i) {
|
||||
s[i] = dt * (sin(M_PI + (i * M_PI) / s.size()) + 1.0);
|
||||
s[i] = static_cast<float>(dt * (sin(M_PI + (i * M_PI) / static_cast<double>(s.size())) + 1.0));
|
||||
}
|
||||
|
||||
analyze(p, s, new_frame_);
|
||||
@@ -200,7 +193,7 @@ void Analyzer::Base::polishEvent() {
|
||||
void Analyzer::interpolate(const Scope &inVec, Scope &outVec) {
|
||||
|
||||
double pos = 0.0;
|
||||
const double step = static_cast<double>(inVec.size()) / outVec.size();
|
||||
const double step = static_cast<double>(inVec.size()) / static_cast<double>(outVec.size());
|
||||
|
||||
for (uint i = 0; i < outVec.size(); ++i, pos += step) {
|
||||
const double error = pos - std::floor(pos);
|
||||
@@ -218,7 +211,7 @@ void Analyzer::interpolate(const Scope &inVec, Scope &outVec) {
|
||||
indexRight = inVec.size() - 1;
|
||||
}
|
||||
|
||||
outVec[i] = inVec[indexLeft] * (1.0 - error) + inVec[indexRight] * error;
|
||||
outVec[i] = inVec[indexLeft] * (1.0F - static_cast<float>(error)) + inVec[indexRight] * static_cast<float>(error);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -229,7 +222,7 @@ void Analyzer::initSin(Scope &v, const uint size) {
|
||||
double radian = 0;
|
||||
|
||||
for (uint i = 0; i < size; i++) {
|
||||
v.push_back(sin(radian));
|
||||
v.push_back(static_cast<float>(sin(radian)));
|
||||
radian += step;
|
||||
}
|
||||
|
||||
|
||||
@@ -130,7 +130,7 @@ void BlockAnalyzer::transform(Analyzer::Scope &s) {
|
||||
for (uint x = 0; x < s.size(); ++x) s[x] *= 2;
|
||||
|
||||
fht_->spectrum(s.data());
|
||||
fht_->scale(s.data(), 1.0 / 20);
|
||||
fht_->scale(s.data(), 1.0F / 20);
|
||||
|
||||
// the second half is pretty dull, so only show it if the user has a large analyzer by setting to scope_.size() if large we prevent interpolation of large analyzers, this is good!
|
||||
s.resize(scope_.size() <= kMaxColumns / 2 ? kMaxColumns / 2 : scope_.size());
|
||||
|
||||
@@ -75,7 +75,7 @@ void BoomAnalyzer::resizeEvent(QResizeEvent *e) {
|
||||
|
||||
QWidget::resizeEvent(e);
|
||||
|
||||
const uint HEIGHT = height() - 2;
|
||||
const int HEIGHT = height() - 2;
|
||||
const double h = 1.2 / HEIGHT;
|
||||
|
||||
bands_ = qMin(static_cast<int>(static_cast<double>(width() + 1) / (kColumnWidth + 1)) + 1, kMaxBandCount);
|
||||
@@ -88,7 +88,7 @@ void BoomAnalyzer::resizeEvent(QResizeEvent *e) {
|
||||
canvas_.fill(palette().color(QPalette::Window));
|
||||
|
||||
QPainter p(&barPixmap_);
|
||||
for (uint y = 0; y < HEIGHT; ++y) {
|
||||
for (int y = 0; y < HEIGHT; ++y) {
|
||||
const double F = static_cast<double>(y) * h;
|
||||
|
||||
p.setPen(QColor(qMax(0, 255 - static_cast<int>(229.0 * F)),
|
||||
@@ -102,7 +102,7 @@ void BoomAnalyzer::resizeEvent(QResizeEvent *e) {
|
||||
void BoomAnalyzer::transform(Scope &s) {
|
||||
|
||||
fht_->spectrum(s.data());
|
||||
fht_->scale(s.data(), 1.0 / 50);
|
||||
fht_->scale(s.data(), 1.0F / 50);
|
||||
|
||||
s.resize(scope_.size() <= static_cast<quint64>(kMaxBandCount) / 2 ? kMaxBandCount / 2 : scope_.size());
|
||||
|
||||
|
||||
@@ -53,8 +53,8 @@ void FHT::makeCasTable(void) {
|
||||
float *sintab = tab_() + num_ / 2 + 1;
|
||||
|
||||
for (int ul = 0; ul < num_; ul++) {
|
||||
float d = M_PI * ul / (num_ / 2); // NOLINT(bugprone-integer-division)
|
||||
*costab = *sintab = cos(d);
|
||||
double d = M_PI * static_cast<double>(ul) / (static_cast<double>(num_) / 2.0);
|
||||
*costab = *sintab = static_cast<float>(cos(d));
|
||||
|
||||
costab += 2;
|
||||
sintab += 2;
|
||||
@@ -73,25 +73,25 @@ void FHT::ewma(float *d, float *s, float w) const {
|
||||
|
||||
void FHT::logSpectrum(float *out, float *p) {
|
||||
|
||||
int n = num_ / 2, i = 0, j = 0, k = 0, *r = nullptr;
|
||||
int n = num_ / 2, i = 0, k = 0, *r = nullptr;
|
||||
if (log_vector_.size() < n) {
|
||||
log_vector_.resize(n);
|
||||
float f = n / log10(static_cast<double>(n));
|
||||
float f = static_cast<float>(n) / static_cast<float>(log10(static_cast<double>(n)));
|
||||
for (i = 0, r = log_(); i < n; i++, r++) {
|
||||
j = static_cast<int>(rint(log10(i + 1.0) * f));
|
||||
int j = static_cast<int>(rint(log10(i + 1.0) * f));
|
||||
*r = j >= n ? n - 1 : j;
|
||||
}
|
||||
}
|
||||
semiLogSpectrum(p);
|
||||
*out++ = *p = *p / 100;
|
||||
for (k = i = 1, r = log_(); i < n; i++) {
|
||||
j = *r++;
|
||||
int j = *r++;
|
||||
if (i == j) {
|
||||
*out++ = p[i];
|
||||
}
|
||||
else {
|
||||
float base = p[k - 1];
|
||||
float step = (p[j] - base) / (j - (k - 1));
|
||||
float step = (p[j] - base) / static_cast<float>(j - (k - 1));
|
||||
for (float corr = 0; k <= j; k++, corr += step) *out++ = base + corr;
|
||||
}
|
||||
}
|
||||
@@ -102,7 +102,7 @@ void FHT::semiLogSpectrum(float *p) {
|
||||
|
||||
power2(p);
|
||||
for (int i = 0; i < (num_ / 2); i++, p++) {
|
||||
float e = 10.0 * log10(sqrt(*p / 2));
|
||||
float e = 10.0F * static_cast<float>(log10(sqrt(*p / static_cast<float>(2))));
|
||||
*p = e < 0 ? 0 : e;
|
||||
}
|
||||
|
||||
@@ -158,8 +158,8 @@ void FHT::transform8(float *p) {
|
||||
|
||||
a = *p++, b = *p++, c = *p++, d = *p++;
|
||||
e = *p++, f = *p++, g = *p++, h = *p;
|
||||
b_f2 = (b - f) * M_SQRT2;
|
||||
d_h2 = (d - h) * M_SQRT2;
|
||||
b_f2 = (b - f) * static_cast<float>(M_SQRT2);
|
||||
d_h2 = (d - h) * static_cast<float>(M_SQRT2);
|
||||
|
||||
a_c_eg = a - c - e + g;
|
||||
a_ce_g = a - c + e - g;
|
||||
|
||||
@@ -76,7 +76,7 @@ Rainbow::RainbowAnalyzer::RainbowAnalyzer(const RainbowType rbtype, QWidget *par
|
||||
|
||||
// pow constants computed so that
|
||||
// | band_scale(0) | ~= .5 and | band_scale(5) | ~= 32
|
||||
band_scale_[i] = -std::cos(M_PI * i / (kRainbowBands - 1)) * 0.5 * std::pow(2.3, i);
|
||||
band_scale_[i] = -static_cast<float>(std::cos(M_PI * i / (kRainbowBands - 1))) * 0.5F * static_cast<float>(std::pow(2.3, i));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -103,7 +103,7 @@ void Rainbow::RainbowAnalyzer::resizeEvent(QResizeEvent *e) {
|
||||
buffer_[1] = QPixmap();
|
||||
|
||||
available_rainbow_width_ = width() - kWidth[rainbowtype] + kRainbowOverlap[rainbowtype];
|
||||
px_per_frame_ = static_cast<float>(available_rainbow_width_) / (kHistorySize - 1) + 1;
|
||||
px_per_frame_ = available_rainbow_width_ / (kHistorySize - 1) + 1;
|
||||
x_offset_ = px_per_frame_ * (kHistorySize - 1) - available_rainbow_width_;
|
||||
|
||||
}
|
||||
@@ -144,7 +144,7 @@ void Rainbow::RainbowAnalyzer::analyze(QPainter &p, const Analyzer::Scope &s, bo
|
||||
const float top_of = static_cast<float>(height()) / 2 - static_cast<float>(kRainbowHeight[rainbowtype]) / 2;
|
||||
for (int band = 0; band < kRainbowBands; ++band) {
|
||||
// Calculate the Y position of this band.
|
||||
const float y = static_cast<float>(kRainbowHeight[rainbowtype]) / (kRainbowBands + 1) * (band + 0.5) + top_of;
|
||||
const float y = static_cast<float>(kRainbowHeight[rainbowtype]) / static_cast<float>(kRainbowBands + 1) * (static_cast<float>(band) + 0.5F) + top_of;
|
||||
|
||||
// Add each point in the line.
|
||||
for (int x = 0; x < kHistorySize; ++x) {
|
||||
|
||||
@@ -25,9 +25,12 @@
|
||||
#include <QObject>
|
||||
#include <QThread>
|
||||
#include <QList>
|
||||
#include <QSettings>
|
||||
#include <QtConcurrentRun>
|
||||
#include <QtDebug>
|
||||
|
||||
#include "core/application.h"
|
||||
#include "core/taskmanager.h"
|
||||
#include "core/database.h"
|
||||
#include "core/player.h"
|
||||
#include "core/tagreaderclient.h"
|
||||
@@ -41,6 +44,7 @@
|
||||
#include "collectionmodel.h"
|
||||
#include "playlist/playlistmanager.h"
|
||||
#include "scrobbler/lastfmimport.h"
|
||||
#include "settings/collectionsettingspage.h"
|
||||
|
||||
const char *SCollection::kSongsTable = "songs";
|
||||
const char *SCollection::kFtsTable = "songs_fts";
|
||||
@@ -54,7 +58,11 @@ SCollection::SCollection(Application *app, QObject *parent)
|
||||
model_(nullptr),
|
||||
watcher_(nullptr),
|
||||
watcher_thread_(nullptr),
|
||||
original_thread_(nullptr) {
|
||||
original_thread_(nullptr),
|
||||
io_priority_(Utilities::IoPriority::IOPRIO_CLASS_IDLE),
|
||||
thread_priority_(QThread::Priority::IdlePriority),
|
||||
save_playcounts_to_files_(false),
|
||||
save_ratings_to_files_(false) {
|
||||
|
||||
original_thread_ = thread();
|
||||
|
||||
@@ -88,11 +96,18 @@ void SCollection::Init() {
|
||||
|
||||
watcher_ = new CollectionWatcher(Song::Source_Collection);
|
||||
watcher_thread_ = new Thread(this);
|
||||
watcher_thread_->SetIoPriority(Utilities::IOPRIO_CLASS_IDLE);
|
||||
|
||||
#ifndef Q_OS_WIN32
|
||||
if (io_priority_ != Utilities::IoPriority::IOPRIO_CLASS_NONE) {
|
||||
watcher_thread_->SetIoPriority(io_priority_);
|
||||
}
|
||||
#endif
|
||||
|
||||
watcher_->moveToThread(watcher_thread_);
|
||||
watcher_thread_->start(QThread::IdlePriority);
|
||||
qLog(Debug) << watcher_ << "moved to thread" << watcher_thread_;
|
||||
|
||||
qLog(Debug) << watcher_ << "moved to thread" << watcher_thread_ << "with I/O priority" << io_priority_ << "and thread priority" << thread_priority_;
|
||||
|
||||
watcher_thread_->start(thread_priority_);
|
||||
|
||||
watcher_->set_backend(backend_);
|
||||
watcher_->set_task_manager(app_->task_manager());
|
||||
@@ -100,6 +115,9 @@ void SCollection::Init() {
|
||||
QObject::connect(backend_, &CollectionBackend::Error, this, &SCollection::Error);
|
||||
QObject::connect(backend_, &CollectionBackend::DirectoryDiscovered, watcher_, &CollectionWatcher::AddDirectory);
|
||||
QObject::connect(backend_, &CollectionBackend::DirectoryDeleted, watcher_, &CollectionWatcher::RemoveDirectory);
|
||||
QObject::connect(backend_, &CollectionBackend::SongsRatingChanged, this, &SCollection::SongsRatingChanged);
|
||||
QObject::connect(backend_, &CollectionBackend::SongsStatisticsChanged, this, &SCollection::SongsPlaycountChanged);
|
||||
|
||||
QObject::connect(watcher_, &CollectionWatcher::NewOrUpdatedSongs, backend_, &CollectionBackend::AddOrUpdateSongs);
|
||||
QObject::connect(watcher_, &CollectionWatcher::SongsMTimeUpdated, backend_, &CollectionBackend::UpdateMTimesOnly);
|
||||
QObject::connect(watcher_, &CollectionWatcher::SongsDeleted, backend_, &CollectionBackend::DeleteSongs);
|
||||
@@ -128,6 +146,7 @@ void SCollection::Exit() {
|
||||
QObject::connect(backend_, &CollectionBackend::ExitFinished, this, &SCollection::ExitReceived);
|
||||
QObject::connect(watcher_, &CollectionWatcher::ExitFinished, this, &SCollection::ExitReceived);
|
||||
backend_->ExitAsync();
|
||||
watcher_->Abort();
|
||||
watcher_->ExitAsync();
|
||||
|
||||
}
|
||||
@@ -164,4 +183,55 @@ void SCollection::ReloadSettings() {
|
||||
watcher_->ReloadSettingsAsync();
|
||||
model_->ReloadSettings();
|
||||
|
||||
QSettings s;
|
||||
s.beginGroup(CollectionSettingsPage::kSettingsGroup);
|
||||
io_priority_ = static_cast<Utilities::IoPriority>(s.value("io_priority", Utilities::IOPRIO_CLASS_IDLE).toInt());
|
||||
thread_priority_ = static_cast<QThread::Priority>(s.value("thread_priority", QThread::Priority::IdlePriority).toInt());
|
||||
save_playcounts_to_files_ = s.value("save_playcounts", false).toBool();
|
||||
save_ratings_to_files_ = s.value("save_ratings", false).toBool();
|
||||
s.endGroup();
|
||||
|
||||
}
|
||||
|
||||
void SCollection::SyncPlaycountAndRatingToFilesAsync() {
|
||||
|
||||
#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
|
||||
(void)QtConcurrent::run(&SCollection::SyncPlaycountAndRatingToFiles, this);
|
||||
#else
|
||||
(void)QtConcurrent::run(this, &SCollection::SyncPlaycountAndRatingToFiles);
|
||||
#endif
|
||||
|
||||
}
|
||||
|
||||
void SCollection::SyncPlaycountAndRatingToFiles() {
|
||||
|
||||
const int task_id = app_->task_manager()->StartTask(tr("Saving playcounts and ratings"));
|
||||
app_->task_manager()->SetTaskBlocksCollectionScans(task_id);
|
||||
|
||||
const SongList songs = backend_->GetAllSongs();
|
||||
const qint64 nb_songs = songs.size();
|
||||
int i = 0;
|
||||
for (const Song &song : songs) {
|
||||
TagReaderClient::Instance()->UpdateSongPlaycountBlocking(song);
|
||||
TagReaderClient::Instance()->UpdateSongRatingBlocking(song);
|
||||
app_->task_manager()->SetTaskProgress(task_id, ++i, nb_songs);
|
||||
}
|
||||
app_->task_manager()->SetTaskFinished(task_id);
|
||||
|
||||
}
|
||||
|
||||
void SCollection::SongsPlaycountChanged(const SongList &songs) {
|
||||
|
||||
if (save_playcounts_to_files_) {
|
||||
app_->tag_reader_client()->UpdateSongsPlaycount(songs);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
void SCollection::SongsRatingChanged(const SongList &songs, const bool save_tags) {
|
||||
|
||||
if (save_tags || save_ratings_to_files_) {
|
||||
app_->tag_reader_client()->UpdateSongsRating(songs);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -28,10 +28,10 @@
|
||||
#include <QList>
|
||||
#include <QHash>
|
||||
#include <QString>
|
||||
#include <QThread>
|
||||
|
||||
#include "core/song.h"
|
||||
|
||||
class QThread;
|
||||
#include "core/utilities.h"
|
||||
|
||||
class Application;
|
||||
class Thread;
|
||||
@@ -59,9 +59,10 @@ class SCollection : public QObject {
|
||||
|
||||
QString full_rescan_reason(int schema_version) const { return full_rescan_revisions_.value(schema_version, QString()); }
|
||||
|
||||
int Total_Albums = 0;
|
||||
int total_songs_ = 0;
|
||||
int Total_Artists = 0;
|
||||
void SyncPlaycountAndRatingToFilesAsync();
|
||||
|
||||
private:
|
||||
void SyncPlaycountAndRatingToFiles();
|
||||
|
||||
public slots:
|
||||
void ReloadSettings();
|
||||
@@ -77,6 +78,8 @@ class SCollection : public QObject {
|
||||
|
||||
private slots:
|
||||
void ExitReceived();
|
||||
void SongsPlaycountChanged(const SongList &songs);
|
||||
void SongsRatingChanged(const SongList &songs, const bool save_tags = false);
|
||||
|
||||
signals:
|
||||
void Error(QString);
|
||||
@@ -95,6 +98,11 @@ class SCollection : public QObject {
|
||||
QHash<int, QString> full_rescan_revisions_;
|
||||
|
||||
QList<QObject*> wait_for_exit_;
|
||||
|
||||
Utilities::IoPriority io_priority_;
|
||||
QThread::Priority thread_priority_;
|
||||
bool save_playcounts_to_files_;
|
||||
bool save_ratings_to_files_;
|
||||
};
|
||||
|
||||
#endif
|
||||
|
||||
@@ -177,7 +177,7 @@ void CollectionBackend::ChangeDirPath(const int id, const QString &old_path, con
|
||||
const QByteArray old_url = QUrl::fromLocalFile(old_path).toEncoded();
|
||||
const QByteArray new_url = QUrl::fromLocalFile(new_path).toEncoded();
|
||||
|
||||
const int path_len = old_url.length();
|
||||
const qint64 path_len = old_url.length();
|
||||
|
||||
// Do the subdirs table
|
||||
{
|
||||
@@ -509,6 +509,28 @@ void CollectionBackend::AddOrUpdateSubdirs(const SubdirectoryList &subdirs) {
|
||||
|
||||
}
|
||||
|
||||
SongList CollectionBackend::GetAllSongs() {
|
||||
|
||||
QMutexLocker l(db_->Mutex());
|
||||
QSqlDatabase db(db_->Connect());
|
||||
|
||||
SqlQuery q(db);
|
||||
q.prepare(QString("SELECT ROWID, " + Song::kColumnSpec + " FROM %1").arg(songs_table_));
|
||||
if (!q.Exec()) {
|
||||
db_->ReportErrors(q);
|
||||
return SongList();
|
||||
}
|
||||
|
||||
SongList songs;
|
||||
while (q.next()) {
|
||||
Song song;
|
||||
song.InitFromQuery(q, true);
|
||||
songs << song;
|
||||
}
|
||||
return songs;
|
||||
|
||||
}
|
||||
|
||||
void CollectionBackend::AddOrUpdateSongsAsync(const SongList &songs) {
|
||||
QMetaObject::invokeMethod(this, "AddOrUpdateSongs", Qt::QueuedConnection, Q_ARG(SongList, songs));
|
||||
}
|
||||
@@ -687,7 +709,8 @@ void CollectionBackend::UpdateSongsBySongID(const SongMap &new_songs) {
|
||||
}
|
||||
|
||||
// Add or update songs.
|
||||
for (const Song &new_song : new_songs) {
|
||||
QList new_songs_list = new_songs.values();
|
||||
for (const Song &new_song : new_songs_list) {
|
||||
if (old_songs.contains(new_song.song_id())) {
|
||||
|
||||
Song old_song = old_songs[new_song.song_id()];
|
||||
@@ -757,7 +780,8 @@ void CollectionBackend::UpdateSongsBySongID(const SongMap &new_songs) {
|
||||
}
|
||||
|
||||
// Delete songs
|
||||
for (const Song &old_song : old_songs) {
|
||||
QList old_songs_list = old_songs.values();
|
||||
for (const Song &old_song : old_songs_list) {
|
||||
if (!new_songs.contains(old_song.song_id())) {
|
||||
{
|
||||
SqlQuery q(db);
|
||||
@@ -1087,7 +1111,7 @@ SongList CollectionBackend::GetSongsByForeignId(const QStringList &ids, const QS
|
||||
QVector<Song> ret(ids.count());
|
||||
while (q.next()) {
|
||||
const QString foreign_id = q.value(static_cast<int>(Song::kColumns.count()) + 1).toString();
|
||||
const int index = ids.indexOf(foreign_id);
|
||||
const qint64 index = ids.indexOf(foreign_id);
|
||||
if (index == -1) continue;
|
||||
|
||||
ret[index].InitFromQuery(q, true);
|
||||
@@ -1550,7 +1574,7 @@ void CollectionBackend::UpdateManualAlbumArt(const QString &effective_albumartis
|
||||
}
|
||||
|
||||
// Update the songs
|
||||
QString sql(QString("UPDATE %1 SET art_manual = :cover").arg(songs_table_));
|
||||
QString sql = QString("UPDATE %1 SET art_manual = :cover").arg(songs_table_);
|
||||
if (clear_art_automatic) {
|
||||
sql += ", art_automatic = ''";
|
||||
}
|
||||
@@ -1587,13 +1611,13 @@ void CollectionBackend::UpdateManualAlbumArt(const QString &effective_albumartis
|
||||
|
||||
}
|
||||
|
||||
void CollectionBackend::UpdateAutomaticAlbumArtAsync(const QString &effective_albumartist, const QString &album, const QUrl &cover_url) {
|
||||
void CollectionBackend::UpdateAutomaticAlbumArtAsync(const QString &effective_albumartist, const QString &album, const QUrl &cover_url, const bool clear_art_manual) {
|
||||
|
||||
QMetaObject::invokeMethod(this, "UpdateAutomaticAlbumArt", Qt::QueuedConnection, Q_ARG(QString, effective_albumartist), Q_ARG(QString, album), Q_ARG(QUrl, cover_url));
|
||||
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));
|
||||
|
||||
}
|
||||
|
||||
void CollectionBackend::UpdateAutomaticAlbumArt(const QString &effective_albumartist, const QString &album, const QUrl &cover_url) {
|
||||
void CollectionBackend::UpdateAutomaticAlbumArt(const QString &effective_albumartist, const QString &album, const QUrl &cover_url, const bool clear_art_manual) {
|
||||
|
||||
QMutexLocker l(db_->Mutex());
|
||||
QSqlDatabase db(db_->Connect());
|
||||
@@ -1617,7 +1641,11 @@ void CollectionBackend::UpdateAutomaticAlbumArt(const QString &effective_albumar
|
||||
}
|
||||
|
||||
// Update the songs
|
||||
QString sql(QString("UPDATE %1 SET art_automatic = :cover WHERE effective_albumartist = :effective_albumartist AND album = :album AND unavailable = 0").arg(songs_table_));
|
||||
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);
|
||||
@@ -1773,6 +1801,12 @@ void CollectionBackend::ResetStatistics(const int id) {
|
||||
|
||||
}
|
||||
|
||||
void CollectionBackend::DeleteAllAsync() {
|
||||
|
||||
QMetaObject::invokeMethod(this, "DeleteAll", Qt::QueuedConnection);
|
||||
|
||||
}
|
||||
|
||||
void CollectionBackend::DeleteAll() {
|
||||
|
||||
{
|
||||
@@ -1924,17 +1958,15 @@ void CollectionBackend::UpdatePlayCount(const QString &artist, const QString &ti
|
||||
|
||||
}
|
||||
|
||||
void CollectionBackend::UpdateSongRating(const int id, const double rating) {
|
||||
void CollectionBackend::UpdateSongRating(const int id, const float rating, const bool save_tags) {
|
||||
|
||||
if (id == -1) return;
|
||||
|
||||
QList<int> id_list;
|
||||
id_list << id;
|
||||
UpdateSongsRating(id_list, rating);
|
||||
UpdateSongsRating(QList<int>() << id, rating, save_tags);
|
||||
|
||||
}
|
||||
|
||||
void CollectionBackend::UpdateSongsRating(const QList<int> &id_list, const double rating) {
|
||||
void CollectionBackend::UpdateSongsRating(const QList<int> &id_list, const float rating, const bool save_tags) {
|
||||
|
||||
if (id_list.isEmpty()) return;
|
||||
|
||||
@@ -1957,16 +1989,16 @@ void CollectionBackend::UpdateSongsRating(const QList<int> &id_list, const doubl
|
||||
|
||||
SongList new_song_list = GetSongsById(id_str_list, db);
|
||||
|
||||
emit SongsRatingChanged(new_song_list);
|
||||
emit SongsRatingChanged(new_song_list, save_tags);
|
||||
|
||||
}
|
||||
|
||||
void CollectionBackend::UpdateSongRatingAsync(const int id, const double rating) {
|
||||
QMetaObject::invokeMethod(this, "UpdateSongRating", Qt::QueuedConnection, Q_ARG(int, id), Q_ARG(double, rating));
|
||||
void CollectionBackend::UpdateSongRatingAsync(const int id, const float rating, const bool save_tags) {
|
||||
QMetaObject::invokeMethod(this, "UpdateSongRating", Qt::QueuedConnection, Q_ARG(int, id), Q_ARG(float, rating), Q_ARG(bool, save_tags));
|
||||
}
|
||||
|
||||
void CollectionBackend::UpdateSongsRatingAsync(const QList<int> &ids, const double rating) {
|
||||
QMetaObject::invokeMethod(this, "UpdateSongsRating", Qt::QueuedConnection, Q_ARG(QList<int>, ids), Q_ARG(double, rating));
|
||||
void CollectionBackend::UpdateSongsRatingAsync(const QList<int> &ids, const float rating, const bool save_tags) {
|
||||
QMetaObject::invokeMethod(this, "UpdateSongsRating", Qt::QueuedConnection, Q_ARG(QList<int>, ids), Q_ARG(float, rating), Q_ARG(bool, save_tags));
|
||||
}
|
||||
|
||||
void CollectionBackend::UpdateLastSeen(const int directory_id, const int expire_unavailable_songs_days) {
|
||||
@@ -2014,3 +2046,4 @@ void CollectionBackend::ExpireSongs(const int directory_id, const int expire_una
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -92,6 +92,8 @@ class CollectionBackendInterface : public QObject {
|
||||
virtual DirectoryList GetAllDirectories() = 0;
|
||||
virtual void ChangeDirPath(const int id, const QString &old_path, const QString &new_path) = 0;
|
||||
|
||||
virtual SongList GetAllSongs() = 0;
|
||||
|
||||
virtual QStringList GetAllArtists(const QueryOptions &opt = QueryOptions()) = 0;
|
||||
virtual QStringList GetAllArtistsWithAlbums(const QueryOptions &opt = QueryOptions()) = 0;
|
||||
virtual SongList GetArtistSongs(const QString &effective_albumartist, const QueryOptions &opt = QueryOptions()) = 0;
|
||||
@@ -105,7 +107,7 @@ class CollectionBackendInterface : public QObject {
|
||||
virtual AlbumList GetCompilationAlbums(const QueryOptions &opt = QueryOptions()) = 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) = 0;
|
||||
virtual void UpdateAutomaticAlbumArtAsync(const QString &effective_albumartist, const QString &album, const QUrl &cover_url, const bool clear_art_manual = false) = 0;
|
||||
|
||||
virtual Album GetAlbumArt(const QString &effective_albumartist, const QString &album) = 0;
|
||||
|
||||
@@ -157,6 +159,8 @@ class CollectionBackend : public CollectionBackendInterface {
|
||||
DirectoryList GetAllDirectories() override;
|
||||
void ChangeDirPath(const int id, const QString &old_path, const QString &new_path) override;
|
||||
|
||||
SongList GetAllSongs() override;
|
||||
|
||||
QStringList GetAll(const QString &column, const QueryOptions &opt = QueryOptions());
|
||||
QStringList GetAllArtists(const QueryOptions &opt = QueryOptions()) override;
|
||||
QStringList GetAllArtistsWithAlbums(const QueryOptions &opt = QueryOptions()) override;
|
||||
@@ -171,7 +175,7 @@ class CollectionBackend : public CollectionBackendInterface {
|
||||
AlbumList GetAlbumsByArtist(const QString &artist, const QueryOptions &opt = QueryOptions()) 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) override;
|
||||
void UpdateAutomaticAlbumArtAsync(const QString &effective_albumartist, const QString &album, const QUrl &cover_url, const bool clear_art_manual = false) override;
|
||||
|
||||
Album GetAlbumArt(const QString &effective_albumartist, const QString &album) override;
|
||||
|
||||
@@ -193,7 +197,7 @@ class CollectionBackend : public CollectionBackendInterface {
|
||||
void IncrementSkipCountAsync(const int id, const float progress);
|
||||
void ResetStatisticsAsync(const int id);
|
||||
|
||||
void DeleteAll();
|
||||
void DeleteAllAsync();
|
||||
|
||||
Song GetSongBySongId(const QString &song_id);
|
||||
SongList GetSongsBySongId(const QStringList &song_ids);
|
||||
@@ -208,8 +212,8 @@ class CollectionBackend : public CollectionBackendInterface {
|
||||
void AddOrUpdateSongsAsync(const SongList &songs);
|
||||
void UpdateSongsBySongIDAsync(const SongMap &new_songs);
|
||||
|
||||
void UpdateSongRatingAsync(const int id, const double rating);
|
||||
void UpdateSongsRatingAsync(const QList<int> &ids, const double rating);
|
||||
void UpdateSongRatingAsync(const int id, const float rating, const bool save_tags = false);
|
||||
void UpdateSongsRatingAsync(const QList<int> &ids, const float rating, const bool save_tags = false);
|
||||
|
||||
public slots:
|
||||
void Exit();
|
||||
@@ -225,19 +229,20 @@ class CollectionBackend : public CollectionBackendInterface {
|
||||
void AddOrUpdateSubdirs(const SubdirectoryList &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);
|
||||
void UpdateAutomaticAlbumArt(const QString &effective_albumartist, const QString &album, const QUrl &cover_url, const bool clear_art_manual = false);
|
||||
void ForceCompilation(const QString &album, const QList<QString> &artists, const bool on);
|
||||
void IncrementPlayCount(const int id);
|
||||
void IncrementSkipCount(const int id, const float progress);
|
||||
void ResetStatistics(const int id);
|
||||
void DeleteAll();
|
||||
void SongPathChanged(const Song &song, const QFileInfo &new_file, const std::optional<int> new_collection_directory_id);
|
||||
|
||||
SongList GetSongsBy(const QString &artist, const QString &album, const QString &title);
|
||||
void UpdateLastPlayed(const QString &artist, const QString &album, const QString &title, const qint64 lastplayed);
|
||||
void UpdatePlayCount(const QString &artist, const QString &title, const int playcount);
|
||||
|
||||
void UpdateSongRating(const int id, const double rating);
|
||||
void UpdateSongsRating(const QList<int> &id_list, const double rating);
|
||||
void UpdateSongRating(const int id, const float rating, const bool save_tags = false);
|
||||
void UpdateSongsRating(const QList<int> &id_list, const float rating, const bool save_tags = false);
|
||||
|
||||
void UpdateLastSeen(const int directory_id, const int expire_unavailable_songs_days);
|
||||
void ExpireSongs(const int directory_id, const int expire_unavailable_songs_days);
|
||||
@@ -255,7 +260,7 @@ class CollectionBackend : public CollectionBackendInterface {
|
||||
void TotalSongCountUpdated(int);
|
||||
void TotalArtistCountUpdated(int);
|
||||
void TotalAlbumCountUpdated(int);
|
||||
void SongsRatingChanged(SongList);
|
||||
void SongsRatingChanged(SongList, bool);
|
||||
|
||||
void ExitFinished();
|
||||
|
||||
|
||||
@@ -136,6 +136,7 @@ CollectionFilterWidget::CollectionFilterWidget(QWidget *parent)
|
||||
ui_->options->setMenu(collection_menu_);
|
||||
|
||||
QObject::connect(ui_->search_field, &QSearchField::textChanged, this, &CollectionFilterWidget::FilterTextChanged);
|
||||
QObject::connect(ui_->options, &QToolButton::clicked, ui_->options, &QToolButton::showMenu);
|
||||
|
||||
ReloadSettings();
|
||||
|
||||
|
||||
@@ -45,7 +45,10 @@
|
||||
</size>
|
||||
</property>
|
||||
<property name="popupMode">
|
||||
<enum>QToolButton::InstantPopup</enum>
|
||||
<enum>QToolButton::MenuButtonPopup</enum>
|
||||
</property>
|
||||
<property name="autoRaise">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
|
||||
@@ -295,7 +295,7 @@ CollectionItem *CollectionModel::CreateCompilationArtistNode(const bool signal,
|
||||
|
||||
Q_ASSERT(parent->compilation_artist_node_ == nullptr);
|
||||
|
||||
if (signal) beginInsertRows(ItemToIndex(parent), parent->children.count(), parent->children.count());
|
||||
if (signal) beginInsertRows(ItemToIndex(parent), static_cast<int>(parent->children.count()), static_cast<int>(parent->children.count()));
|
||||
|
||||
parent->compilation_artist_node_ = new CollectionItem(CollectionItem::Type_Container, parent);
|
||||
parent->compilation_artist_node_->compilation_artist_node_ = nullptr;
|
||||
@@ -808,7 +808,10 @@ QVariant CollectionModel::data(const CollectionItem *item, const int role) const
|
||||
|
||||
case Role_SortText:
|
||||
return item->SortText();
|
||||
default:
|
||||
return QVariant();
|
||||
}
|
||||
|
||||
return QVariant();
|
||||
|
||||
}
|
||||
@@ -1176,7 +1179,7 @@ CollectionItem *CollectionModel::InitItem(const GroupBy type, const bool signal,
|
||||
|
||||
CollectionItem::Type item_type = type == GroupBy_None ? CollectionItem::Type_Song : CollectionItem::Type_Container;
|
||||
|
||||
if (signal) beginInsertRows(ItemToIndex(parent), parent->children.count(), parent->children.count());
|
||||
if (signal) beginInsertRows(ItemToIndex(parent), static_cast<int>(parent->children.count()), static_cast<int>(parent->children.count()));
|
||||
|
||||
// Initialize the item depending on what type it's meant to be
|
||||
CollectionItem *item = new CollectionItem(item_type, parent);
|
||||
@@ -1597,7 +1600,7 @@ void CollectionModel::FinishItem(const GroupBy type, const bool signal, const bo
|
||||
|
||||
if (!divider_key.isEmpty() && !divider_nodes_.contains(divider_key)) {
|
||||
if (signal) {
|
||||
beginInsertRows(ItemToIndex(parent), parent->children.count(), parent->children.count());
|
||||
beginInsertRows(ItemToIndex(parent), static_cast<int>(parent->children.count()), static_cast<int>(parent->children.count()));
|
||||
}
|
||||
|
||||
CollectionItem *divider = new CollectionItem(CollectionItem::Type_Divider, root_);
|
||||
@@ -1676,7 +1679,7 @@ QString CollectionModel::SortTextForArtist(QString artist) {
|
||||
|
||||
for (const auto &i : Song::kArticles) {
|
||||
if (artist.startsWith(i)) {
|
||||
int ilen = i.length();
|
||||
qint64 ilen = i.length();
|
||||
artist = artist.right(artist.length() - ilen) + ", " + i.left(ilen - 1);
|
||||
break;
|
||||
}
|
||||
@@ -1871,6 +1874,7 @@ const CollectionModel::GroupBy &CollectionModel::Grouping::operator[](const int
|
||||
case 0: return first;
|
||||
case 1: return second;
|
||||
case 2: return third;
|
||||
default: break;
|
||||
}
|
||||
qLog(Error) << "CollectionModel::Grouping[] index out of range" << i;
|
||||
return first;
|
||||
@@ -1883,6 +1887,7 @@ CollectionModel::GroupBy &CollectionModel::Grouping::operator[](const int i) {
|
||||
case 0: return first;
|
||||
case 1: return second;
|
||||
case 2: return third;
|
||||
default: break;
|
||||
}
|
||||
qLog(Error) << "CollectionModel::Grouping[] index out of range" << i;
|
||||
|
||||
|
||||
@@ -425,7 +425,7 @@ void CollectionView::contextMenuEvent(QContextMenuEvent *e) {
|
||||
#endif
|
||||
|
||||
#if QT_VERSION >= QT_VERSION_CHECK(5, 15, 0)
|
||||
action_delete_files_->setVisible(regular_elements == regular_editable && delete_files_);
|
||||
action_delete_files_->setVisible(delete_files_);
|
||||
#else
|
||||
action_delete_files_->setVisible(false);
|
||||
#endif
|
||||
@@ -440,7 +440,7 @@ void CollectionView::contextMenuEvent(QContextMenuEvent *e) {
|
||||
#endif
|
||||
|
||||
#if QT_VERSION >= QT_VERSION_CHECK(5, 15, 0)
|
||||
action_delete_files_->setEnabled(regular_elements == regular_editable && delete_files_);
|
||||
action_delete_files_->setEnabled(delete_files_);
|
||||
#else
|
||||
action_delete_files_->setEnabled(false);
|
||||
#endif
|
||||
@@ -671,10 +671,17 @@ void CollectionView::Delete() {
|
||||
if (!delete_files_) return;
|
||||
|
||||
SongList selected_songs = GetSelectedSongs();
|
||||
|
||||
SongList songs;
|
||||
QStringList files;
|
||||
songs.reserve(selected_songs.count());
|
||||
files.reserve(selected_songs.count());
|
||||
for (const Song &song : selected_songs) {
|
||||
files << song.url().toString();
|
||||
QString filename = song.url().toLocalFile();
|
||||
if (!files.contains(filename)) {
|
||||
songs << song;
|
||||
files << filename;
|
||||
}
|
||||
}
|
||||
if (DeleteConfirmationDialog::warning(files) != QDialogButtonBox::Yes) return;
|
||||
|
||||
@@ -683,7 +690,7 @@ void CollectionView::Delete() {
|
||||
|
||||
DeleteFiles *delete_files = new DeleteFiles(app_->task_manager(), storage, true);
|
||||
QObject::connect(delete_files, &DeleteFiles::Finished, this, &CollectionView::DeleteFilesFinished);
|
||||
delete_files->Start(selected_songs);
|
||||
delete_files->Start(songs);
|
||||
|
||||
}
|
||||
|
||||
|
||||
@@ -68,11 +68,6 @@
|
||||
|
||||
using namespace std::chrono_literals;
|
||||
|
||||
namespace {
|
||||
static const char *kNoMediaFile = ".nomedia";
|
||||
static const char *kNoMusicFile = ".nomusic";
|
||||
} // namespace
|
||||
|
||||
QStringList CollectionWatcher::sValidImages = QStringList() << "jpg" << "png" << "gif" << "jpeg";
|
||||
|
||||
CollectionWatcher::CollectionWatcher(Song::Source source, QObject *parent)
|
||||
@@ -84,10 +79,12 @@ CollectionWatcher::CollectionWatcher(Song::Source source, QObject *parent)
|
||||
original_thread_(nullptr),
|
||||
scan_on_startup_(true),
|
||||
monitor_(true),
|
||||
song_tracking_(true),
|
||||
mark_songs_unavailable_(true),
|
||||
song_tracking_(false),
|
||||
mark_songs_unavailable_(source_ == Song::Source_Collection),
|
||||
expire_unavailable_songs_days_(60),
|
||||
overwrite_rating_(false),
|
||||
stop_requested_(false),
|
||||
abort_requested_(false),
|
||||
rescan_in_progress_(false),
|
||||
rescan_timer_(new QTimer(this)),
|
||||
periodic_scan_timer_(new QTimer(this)),
|
||||
@@ -147,9 +144,16 @@ void CollectionWatcher::ReloadSettings() {
|
||||
scan_on_startup_ = s.value("startup_scan", true).toBool();
|
||||
monitor_ = s.value("monitor", true).toBool();
|
||||
QStringList filters = s.value("cover_art_patterns", QStringList() << "front" << "cover").toStringList();
|
||||
song_tracking_ = s.value("song_tracking", false).toBool();
|
||||
mark_songs_unavailable_ = song_tracking_ ? true : s.value("mark_songs_unavailable", true).toBool();
|
||||
if (source_ == Song::Source_Collection) {
|
||||
song_tracking_ = s.value("song_tracking", false).toBool();
|
||||
mark_songs_unavailable_ = song_tracking_ ? true : s.value("mark_songs_unavailable", true).toBool();
|
||||
}
|
||||
else {
|
||||
song_tracking_ = false;
|
||||
mark_songs_unavailable_ = false;
|
||||
}
|
||||
expire_unavailable_songs_days_ = s.value("expire_unavailable_songs", 60).toInt();
|
||||
overwrite_rating_ = s.value("overwrite_rating", false).toBool();
|
||||
s.endGroup();
|
||||
|
||||
best_image_filters_.clear();
|
||||
@@ -210,7 +214,7 @@ CollectionWatcher::ScanTransaction::ScanTransaction(CollectionWatcher *watcher,
|
||||
CollectionWatcher::ScanTransaction::~ScanTransaction() {
|
||||
|
||||
// If we're stopping then don't commit the transaction
|
||||
if (!watcher_->stop_requested_) {
|
||||
if (!watcher_->stop_requested_ && !watcher_->abort_requested_) {
|
||||
CommitNewOrUpdatedSongs();
|
||||
}
|
||||
|
||||
@@ -235,7 +239,7 @@ void CollectionWatcher::ScanTransaction::AddToProgressMax(const quint64 n) {
|
||||
void CollectionWatcher::ScanTransaction::CommitNewOrUpdatedSongs() {
|
||||
|
||||
if (!deleted_songs.isEmpty()) {
|
||||
if (mark_songs_unavailable_) {
|
||||
if (mark_songs_unavailable_ && watcher_->source() == Song::Source_Collection) {
|
||||
emit watcher_->SongsUnavailable(deleted_songs);
|
||||
}
|
||||
else {
|
||||
@@ -371,6 +375,8 @@ SubdirectoryList CollectionWatcher::ScanTransaction::GetAllSubdirs() {
|
||||
|
||||
void CollectionWatcher::AddDirectory(const Directory &dir, const SubdirectoryList &subdirs) {
|
||||
|
||||
stop_requested_ = false;
|
||||
|
||||
watched_dirs_[dir.id] = dir;
|
||||
|
||||
if (subdirs.isEmpty()) {
|
||||
@@ -390,7 +396,7 @@ void CollectionWatcher::AddDirectory(const Directory &dir, const SubdirectoryLis
|
||||
transaction.SetKnownSubdirs(subdirs);
|
||||
transaction.AddToProgressMax(files_count);
|
||||
for (const Subdirectory &subdir : subdirs) {
|
||||
if (stop_requested_) break;
|
||||
if (stop_requested_ || abort_requested_) break;
|
||||
|
||||
if (scan_on_startup_) ScanSubdirectory(subdir.path, subdir, subdir_files_count[subdir.path], &transaction);
|
||||
|
||||
@@ -408,7 +414,6 @@ void CollectionWatcher::AddDirectory(const Directory &dir, const SubdirectoryLis
|
||||
void CollectionWatcher::ScanSubdirectory(const QString &path, const Subdirectory &subdir, const quint64 files_count, ScanTransaction *t, const bool force_noincremental) {
|
||||
|
||||
QFileInfo path_info(path);
|
||||
QDir path_dir(path);
|
||||
|
||||
// Do not scan symlinked dirs that are already in collection
|
||||
if (path_info.isSymLink()) {
|
||||
@@ -420,11 +425,6 @@ void CollectionWatcher::ScanSubdirectory(const QString &path, const Subdirectory
|
||||
}
|
||||
}
|
||||
|
||||
// Do not scan directories containing a .nomedia or .nomusic file
|
||||
if (path_dir.exists(kNoMediaFile) || path_dir.exists(kNoMusicFile)) {
|
||||
return;
|
||||
}
|
||||
|
||||
bool songs_missing_fingerprint = false;
|
||||
#ifdef HAVE_SONGFINGERPRINTING
|
||||
if (song_tracking_) {
|
||||
@@ -452,16 +452,16 @@ void CollectionWatcher::ScanSubdirectory(const QString &path, const Subdirectory
|
||||
}
|
||||
|
||||
// First we "quickly" get a list of the files in the directory that we think might be music. While we're here, we also look for new subdirectories and possible album artwork.
|
||||
QDirIterator it(path, QDir::Dirs | QDir::Files | QDir::Hidden | QDir::NoDotAndDotDot);
|
||||
QDirIterator it(path, QDir::Dirs | QDir::Files | QDir::NoDotAndDotDot);
|
||||
while (it.hasNext()) {
|
||||
|
||||
if (stop_requested_) return;
|
||||
if (stop_requested_ || abort_requested_) return;
|
||||
|
||||
QString child(it.next());
|
||||
QFileInfo child_info(child);
|
||||
|
||||
if (child_info.isDir()) {
|
||||
if (!child_info.isHidden() && !t->HasSeenSubdir(child)) {
|
||||
if (!t->HasSeenSubdir(child)) {
|
||||
// We haven't seen this subdirectory before - add it to a list and later we'll tell the backend about it and scan it.
|
||||
Subdirectory new_subdir;
|
||||
new_subdir.directory_id = -1;
|
||||
@@ -474,7 +474,10 @@ void CollectionWatcher::ScanSubdirectory(const QString &path, const Subdirectory
|
||||
else {
|
||||
QString ext_part(ExtensionPart(child));
|
||||
QString dir_part(DirectoryPart(child));
|
||||
if (sValidImages.contains(ext_part)) {
|
||||
if (child_info.suffix() == "tmp" || child_info.baseName() == "qt_temp") {
|
||||
t->AddToProgress(1);
|
||||
}
|
||||
else if (sValidImages.contains(ext_part)) {
|
||||
album_art[dir_part] << child;
|
||||
t->AddToProgress(1);
|
||||
}
|
||||
@@ -487,7 +490,7 @@ void CollectionWatcher::ScanSubdirectory(const QString &path, const Subdirectory
|
||||
}
|
||||
}
|
||||
|
||||
if (stop_requested_) return;
|
||||
if (stop_requested_ || abort_requested_) return;
|
||||
|
||||
// Ask the database for a list of files in this directory
|
||||
SongList songs_in_db = t->FindSongsInSubdirectory(path);
|
||||
@@ -498,10 +501,10 @@ void CollectionWatcher::ScanSubdirectory(const QString &path, const Subdirectory
|
||||
QStringList files_on_disk_copy = files_on_disk;
|
||||
for (const QString &file : files_on_disk_copy) {
|
||||
|
||||
if (stop_requested_) return;
|
||||
if (stop_requested_ || abort_requested_) return;
|
||||
|
||||
// Associated CUE
|
||||
QString new_cue = NoExtensionPart(file) + ".cue";
|
||||
QString new_cue = CueParser::FindCueFilename(file);
|
||||
|
||||
SongList matching_songs;
|
||||
if (FindSongsByPath(songs_in_db, file, &matching_songs)) { // Found matching song in DB by path.
|
||||
@@ -510,9 +513,9 @@ void CollectionWatcher::ScanSubdirectory(const QString &path, const Subdirectory
|
||||
|
||||
// The song is in the database and still on disk.
|
||||
// Check the mtime to see if it's been changed since it was added.
|
||||
QFileInfo file_info(file);
|
||||
QFileInfo fileinfo(file);
|
||||
|
||||
if (!file_info.exists()) {
|
||||
if (!fileinfo.exists()) {
|
||||
// Partially fixes race condition - if file was removed between being added to the list and now.
|
||||
files_on_disk.removeAll(file);
|
||||
t->AddToProgress(1);
|
||||
@@ -520,16 +523,20 @@ void CollectionWatcher::ScanSubdirectory(const QString &path, const Subdirectory
|
||||
}
|
||||
|
||||
// CUE sheet's path from collection (if any).
|
||||
qint64 matching_song_cue_mtime = GetMtimeForCue(matching_song.cue_path());
|
||||
qint64 matching_song_cue_mtime = static_cast<qint64>(GetMtimeForCue(matching_song.cue_path()));
|
||||
|
||||
// CUE sheet's path from this file (if any).
|
||||
qint64 new_cue_mtime = GetMtimeForCue(new_cue);
|
||||
qint64 new_cue_mtime = 0;
|
||||
if (!new_cue.isEmpty()) {
|
||||
new_cue_mtime = static_cast<qint64>(GetMtimeForCue(new_cue));
|
||||
}
|
||||
|
||||
bool cue_added = new_cue_mtime != 0 && !matching_song.has_cue();
|
||||
bool cue_deleted = matching_song_cue_mtime == 0 && matching_song.has_cue();
|
||||
const bool cue_added = new_cue_mtime != 0 && !matching_song.has_cue();
|
||||
const bool cue_changed = new_cue_mtime != 0 && matching_song.has_cue() && new_cue != matching_song.cue_path();
|
||||
const bool cue_deleted = matching_song.has_cue() && new_cue_mtime == 0;
|
||||
|
||||
// Watch out for CUE songs which have their mtime equal to qMax(media_file_mtime, cue_sheet_mtime)
|
||||
bool changed = (matching_song.mtime() != qMax(file_info.lastModified().toSecsSinceEpoch(), matching_song_cue_mtime)) || cue_deleted || cue_added;
|
||||
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);
|
||||
@@ -565,12 +572,13 @@ void CollectionWatcher::ScanSubdirectory(const QString &path, const Subdirectory
|
||||
}
|
||||
#endif
|
||||
|
||||
if (!cue_deleted && (matching_song.has_cue() || cue_added)) { // If CUE associated.
|
||||
UpdateCueAssociatedSongs(file, path, fingerprint, new_cue, image, matching_songs, t);
|
||||
}
|
||||
else { // If no CUE or it's about to lose it.
|
||||
if (new_cue.isEmpty() || new_cue_mtime == 0) { // If no CUE or it's about to lose it.
|
||||
UpdateNonCueAssociatedSong(file, fingerprint, matching_songs, image, cue_deleted, t);
|
||||
}
|
||||
else { // If CUE associated.
|
||||
UpdateCueAssociatedSongs(file, path, fingerprint, new_cue, image, matching_songs, t);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// Nothing has changed - mark the song available without re-scanning
|
||||
@@ -594,15 +602,15 @@ void CollectionWatcher::ScanSubdirectory(const QString &path, const Subdirectory
|
||||
|
||||
// The song is in the database and still on disk.
|
||||
// Check the mtime to see if it's been changed since it was added.
|
||||
QFileInfo file_info(file);
|
||||
if (!file_info.exists()) {
|
||||
QFileInfo fileinfo(file);
|
||||
if (!fileinfo.exists()) {
|
||||
// Partially fixes race condition - if file was removed between being added to the list and now.
|
||||
files_on_disk.removeAll(file);
|
||||
t->AddToProgress(1);
|
||||
continue;
|
||||
}
|
||||
|
||||
// Make sure the songs aren't deleted, as they still exist elsewhere with a different fingerprint.
|
||||
// Make sure the songs aren't deleted, as they still exist elsewhere with a different file path.
|
||||
bool matching_songs_has_cue = false;
|
||||
for (const Song &matching_song : matching_songs) {
|
||||
QString matching_filename = matching_song.url().toLocalFile();
|
||||
@@ -619,19 +627,19 @@ void CollectionWatcher::ScanSubdirectory(const QString &path, const Subdirectory
|
||||
}
|
||||
|
||||
// CUE sheet's path from this file (if any).
|
||||
const qint64 new_cue_mtime = GetMtimeForCue(new_cue);
|
||||
|
||||
const bool cue_deleted = new_cue_mtime == 0 && matching_songs_has_cue;
|
||||
const bool cue_added = new_cue_mtime != 0 && !matching_songs_has_cue;
|
||||
qint64 new_cue_mtime = 0;
|
||||
if (!new_cue.isEmpty()) {
|
||||
new_cue_mtime = static_cast<qint64>(GetMtimeForCue(new_cue));
|
||||
}
|
||||
|
||||
// Get new album art
|
||||
QUrl image = ImageForSong(file, album_art);
|
||||
|
||||
if (!cue_deleted && (matching_songs_has_cue || cue_added)) { // CUE associated.
|
||||
UpdateCueAssociatedSongs(file, path, fingerprint, new_cue, image, matching_songs, t);
|
||||
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);
|
||||
}
|
||||
else { // If no CUE or it's about to lose it.
|
||||
UpdateNonCueAssociatedSong(file, fingerprint, matching_songs, image, cue_deleted, t);
|
||||
else { // If CUE associated.
|
||||
UpdateCueAssociatedSongs(file, path, fingerprint, new_cue, image, matching_songs, t);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -686,7 +694,7 @@ void CollectionWatcher::ScanSubdirectory(const QString &path, const Subdirectory
|
||||
|
||||
// Recurse into the new subdirs that we found
|
||||
for (const Subdirectory &my_new_subdir : my_new_subdirs) {
|
||||
if (stop_requested_) return;
|
||||
if (stop_requested_ || abort_requested_) return;
|
||||
ScanSubdirectory(my_new_subdir.path, my_new_subdir, 0, t, true);
|
||||
}
|
||||
|
||||
@@ -726,7 +734,7 @@ void CollectionWatcher::UpdateCueAssociatedSongs(const QString &file,
|
||||
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.MergeUserSetData(matching_cue_song);
|
||||
new_cue_song.MergeUserSetData(matching_cue_song, true);
|
||||
AddChangedSong(file, matching_cue_song, new_cue_song, t);
|
||||
used_ids.insert(matching_cue_song.id());
|
||||
}
|
||||
@@ -769,7 +777,7 @@ void CollectionWatcher::UpdateNonCueAssociatedSong(const QString &file,
|
||||
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.MergeUserSetData(matching_song);
|
||||
song_on_disk.MergeUserSetData(matching_song, !overwrite_rating_);
|
||||
AddChangedSong(file, matching_song, song_on_disk, t);
|
||||
}
|
||||
|
||||
@@ -827,28 +835,47 @@ SongList CollectionWatcher::ScanNewFile(const QString &file, const QString &path
|
||||
|
||||
void CollectionWatcher::AddChangedSong(const QString &file, const Song &matching_song, const Song &new_song, ScanTransaction *t) {
|
||||
|
||||
bool notify_new = false;
|
||||
QStringList changes;
|
||||
|
||||
if (matching_song.is_unavailable()) {
|
||||
qLog(Debug) << file << "unavailable song restored.";
|
||||
t->new_songs << new_song;
|
||||
}
|
||||
else if (!matching_song.IsMetadataEqual(new_song)) {
|
||||
qLog(Debug) << file << "metadata changed.";
|
||||
t->new_songs << new_song;
|
||||
}
|
||||
else if (matching_song.fingerprint() != new_song.fingerprint()) {
|
||||
qLog(Debug) << file << "fingerprint changed.";
|
||||
t->new_songs << new_song;
|
||||
}
|
||||
else if (matching_song.art_automatic() != new_song.art_automatic() || matching_song.art_manual() != new_song.art_manual()) {
|
||||
qLog(Debug) << file << "art changed.";
|
||||
t->new_songs << new_song;
|
||||
}
|
||||
else if (matching_song.mtime() != new_song.mtime()) {
|
||||
qLog(Debug) << file << "mtime changed.";
|
||||
t->touched_songs << new_song;
|
||||
qLog(Debug) << "unavailable song" << file << "restored.";
|
||||
notify_new = true;
|
||||
}
|
||||
else {
|
||||
if (matching_song.url() != new_song.url()) {
|
||||
changes << "file path";
|
||||
notify_new = true;
|
||||
}
|
||||
if (matching_song.fingerprint() != new_song.fingerprint()) {
|
||||
changes << "fingerprint";
|
||||
notify_new = true;
|
||||
}
|
||||
if (!matching_song.IsMetadataEqual(new_song)) {
|
||||
changes << "metadata";
|
||||
notify_new = true;
|
||||
}
|
||||
if (matching_song.art_automatic() != new_song.art_automatic() || matching_song.art_manual() != new_song.art_manual()) {
|
||||
changes << "album art";
|
||||
notify_new = true;
|
||||
}
|
||||
if (matching_song.mtime() != new_song.mtime()) {
|
||||
changes << "mtime";
|
||||
}
|
||||
|
||||
if (changes.isEmpty()) {
|
||||
qLog(Debug) << "Song" << file << "unchanged.";
|
||||
}
|
||||
else {
|
||||
qLog(Debug) << "Song" << file << changes.join(", ") << "changed.";
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
if (notify_new) {
|
||||
t->new_songs << new_song;
|
||||
}
|
||||
else {
|
||||
qLog(Debug) << file << "unchanged.";
|
||||
t->touched_songs << new_song;
|
||||
}
|
||||
|
||||
@@ -860,12 +887,12 @@ quint64 CollectionWatcher::GetMtimeForCue(const QString &cue_path) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
const QFileInfo file_info(cue_path);
|
||||
if (!file_info.exists()) {
|
||||
const QFileInfo fileinfo(cue_path);
|
||||
if (!fileinfo.exists()) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
const QDateTime cue_last_modified = file_info.lastModified();
|
||||
const QDateTime cue_last_modified = fileinfo.lastModified();
|
||||
|
||||
return cue_last_modified.isValid() ? cue_last_modified.toSecsSinceEpoch() : 0;
|
||||
}
|
||||
@@ -971,7 +998,7 @@ void CollectionWatcher::RescanPathsNow() {
|
||||
|
||||
QList<int> dirs = rescan_queue_.keys();
|
||||
for (const int dir : dirs) {
|
||||
if (stop_requested_) break;
|
||||
if (stop_requested_ || abort_requested_) break;
|
||||
ScanTransaction transaction(this, dir, false, false, mark_songs_unavailable_);
|
||||
|
||||
QMap<QString, quint64> subdir_files_count;
|
||||
@@ -982,7 +1009,7 @@ void CollectionWatcher::RescanPathsNow() {
|
||||
}
|
||||
|
||||
for (const QString &path : rescan_queue_[dir]) {
|
||||
if (stop_requested_) break;
|
||||
if (stop_requested_ || abort_requested_) break;
|
||||
Subdirectory subdir;
|
||||
subdir.directory_id = dir;
|
||||
subdir.mtime = 0;
|
||||
@@ -1007,8 +1034,8 @@ QString CollectionWatcher::PickBestImage(const QStringList &images) {
|
||||
for (const QString &filter_text : best_image_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 file_info(image);
|
||||
QString filename(file_info.fileName());
|
||||
QFileInfo fileinfo(image);
|
||||
QString filename(fileinfo.fileName());
|
||||
if (filename.contains(filter_text, Qt::CaseInsensitive))
|
||||
filtered << image;
|
||||
}
|
||||
@@ -1027,7 +1054,7 @@ QString CollectionWatcher::PickBestImage(const QStringList &images) {
|
||||
QString biggest_path;
|
||||
|
||||
for (const QString &path : filtered) {
|
||||
if (stop_requested_) break;
|
||||
if (stop_requested_ || abort_requested_) break;
|
||||
|
||||
QImage image(path);
|
||||
if (image.isNull()) continue;
|
||||
@@ -1120,7 +1147,7 @@ void CollectionWatcher::RescanTracksNow() {
|
||||
// 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_) break;
|
||||
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)) {
|
||||
@@ -1146,7 +1173,7 @@ void CollectionWatcher::PerformScan(const bool incremental, const bool ignore_mt
|
||||
|
||||
for (const Directory &dir : std::as_const(watched_dirs_)) {
|
||||
|
||||
if (stop_requested_) break;
|
||||
if (stop_requested_ || abort_requested_) break;
|
||||
|
||||
ScanTransaction transaction(this, dir.id, incremental, ignore_mtimes, mark_songs_unavailable_);
|
||||
SubdirectoryList subdirs(transaction.GetAllSubdirs());
|
||||
@@ -1164,7 +1191,7 @@ void CollectionWatcher::PerformScan(const bool incremental, const bool ignore_mt
|
||||
transaction.AddToProgressMax(files_count);
|
||||
|
||||
for (const Subdirectory &subdir : subdirs) {
|
||||
if (stop_requested_) break;
|
||||
if (stop_requested_ || abort_requested_) break;
|
||||
ScanSubdirectory(subdir.path, subdir, subdir_files_count[subdir.path], &transaction);
|
||||
}
|
||||
|
||||
@@ -1179,18 +1206,15 @@ void CollectionWatcher::PerformScan(const bool incremental, const bool ignore_mt
|
||||
quint64 CollectionWatcher::FilesCountForPath(ScanTransaction *t, const QString &path) {
|
||||
|
||||
quint64 i = 0;
|
||||
QDirIterator it(path, QDir::Dirs | QDir::Files | QDir::Hidden | QDir::NoDotAndDotDot);
|
||||
QDirIterator it(path, QDir::Dirs | QDir::Files | QDir::NoDotAndDotDot);
|
||||
while (it.hasNext()) {
|
||||
|
||||
if (stop_requested_) break;
|
||||
if (stop_requested_ || abort_requested_) break;
|
||||
|
||||
QString child = it.next();
|
||||
QFileInfo path_info(child);
|
||||
|
||||
if (path_info.isDir()) {
|
||||
if (path_info.exists(kNoMediaFile) || path_info.exists(kNoMusicFile)) {
|
||||
continue;
|
||||
}
|
||||
if (path_info.isSymLink()) {
|
||||
QString real_path = path_info.symLinkTarget();
|
||||
for (const Directory &dir : std::as_const(watched_dirs_)) {
|
||||
@@ -1219,7 +1243,7 @@ quint64 CollectionWatcher::FilesCountForSubdirs(ScanTransaction *t, const Subdir
|
||||
|
||||
quint64 i = 0;
|
||||
for (const Subdirectory &subdir : subdirs) {
|
||||
if (stop_requested_) break;
|
||||
if (stop_requested_ || abort_requested_) break;
|
||||
const quint64 files_count = FilesCountForPath(t, subdir.path);
|
||||
subdir_files_count[subdir.path] = files_count;
|
||||
i += files_count;
|
||||
|
||||
@@ -51,6 +51,8 @@ class CollectionWatcher : public QObject {
|
||||
public:
|
||||
explicit CollectionWatcher(Song::Source source, QObject *parent = nullptr);
|
||||
|
||||
Song::Source source() { return source_; }
|
||||
|
||||
void set_backend(CollectionBackend *backend) { backend_ = backend; }
|
||||
void set_task_manager(TaskManager *task_manager) { task_manager_ = task_manager; }
|
||||
void set_device_name(const QString &device_name) { device_name_ = device_name; }
|
||||
@@ -62,6 +64,7 @@ class CollectionWatcher : public QObject {
|
||||
void ReloadSettingsAsync();
|
||||
|
||||
void Stop() { stop_requested_ = true; }
|
||||
void Abort() { abort_requested_ = true; }
|
||||
|
||||
void ExitAsync();
|
||||
|
||||
@@ -194,6 +197,8 @@ class CollectionWatcher : public QObject {
|
||||
quint64 FilesCountForPath(ScanTransaction *t, const QString &path);
|
||||
quint64 FilesCountForSubdirs(ScanTransaction *t, const SubdirectoryList &subdirs, QMap<QString, quint64> &subdir_files_count);
|
||||
|
||||
QString FindCueFilename(const QString &filename);
|
||||
|
||||
private:
|
||||
Song::Source source_;
|
||||
CollectionBackend *backend_;
|
||||
@@ -213,8 +218,10 @@ class CollectionWatcher : public QObject {
|
||||
bool song_tracking_;
|
||||
bool mark_songs_unavailable_;
|
||||
int expire_unavailable_songs_days_;
|
||||
bool overwrite_rating_;
|
||||
|
||||
bool stop_requested_;
|
||||
bool abort_requested_;
|
||||
bool rescan_in_progress_; // True if RescanTracksNow() has been called and is working.
|
||||
|
||||
QMap<int, Directory> watched_dirs_;
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
/*
|
||||
* Strawberry Music Player
|
||||
* This file was part of Clementine.
|
||||
* Copyright 2010, David Sansome <me@davidsansome.com>
|
||||
* Copyright 2018-2021, Jonas Kvinge <jonas@jkvinge.net>
|
||||
*
|
||||
* Strawberry is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
@@ -21,20 +20,74 @@
|
||||
#include "config.h"
|
||||
|
||||
#include <QVariant>
|
||||
#include <QString>
|
||||
#include <QUrl>
|
||||
#include <QSqlQuery>
|
||||
#include <QSqlRecord>
|
||||
|
||||
#include "sqlrow.h"
|
||||
|
||||
#include "collectionquery.h"
|
||||
|
||||
SqlRow::SqlRow(const QSqlQuery &query) { Init(query); }
|
||||
|
||||
void SqlRow::Init(const QSqlQuery &query) {
|
||||
|
||||
int rows = query.record().count();
|
||||
for (int i = 0; i < rows; ++i) {
|
||||
columns_ << query.value(i);
|
||||
const QSqlRecord r = query.record();
|
||||
for (int i = 0; i < r.count(); ++i) {
|
||||
columns_by_number_.insert(i, query.value(i));
|
||||
const QString field_name = r.fieldName(i);
|
||||
if (!columns_by_name_.contains(field_name) || columns_by_name_[field_name].isNull()) {
|
||||
columns_by_name_.insert(field_name, query.value(i));
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
const QVariant SqlRow::value(const int number) const {
|
||||
|
||||
if (columns_by_number_.contains(number)) {
|
||||
return columns_by_number_[number];
|
||||
}
|
||||
else {
|
||||
return QVariant();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
const QVariant SqlRow::value(const QString &name) const {
|
||||
|
||||
if (columns_by_name_.contains(name)) {
|
||||
return columns_by_name_[name];
|
||||
}
|
||||
else {
|
||||
return QVariant();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
QString SqlRow::ValueToString(const QString &n) const {
|
||||
return value(n).isNull() ? QString() : value(n).toString();
|
||||
}
|
||||
|
||||
QUrl SqlRow::ValueToUrl(const QString &n) const {
|
||||
return value(n).isNull() ? QUrl() : QUrl(value(n).toString());
|
||||
}
|
||||
|
||||
int SqlRow::ValueToInt(const QString &n) const {
|
||||
return value(n).isNull() ? -1 : value(n).toInt();
|
||||
}
|
||||
|
||||
uint SqlRow::ValueToUInt(const QString &n) const {
|
||||
return value(n).isNull() || value(n).toInt() < 0 ? 0 : value(n).toInt();
|
||||
}
|
||||
|
||||
qint64 SqlRow::ValueToLongLong(const QString &n) const {
|
||||
return value(n).isNull() ? -1 : value(n).toLongLong();
|
||||
}
|
||||
|
||||
float SqlRow::ValueToFloat(const QString &n) const {
|
||||
return value(n).isNull() ? -1.0F : value(n).toFloat();
|
||||
}
|
||||
|
||||
bool SqlRow::ValueToBool(const QString &n) const {
|
||||
return !value(n).isNull() && value(n).toInt() == 1;
|
||||
}
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
/*
|
||||
* Strawberry Music Player
|
||||
* This file was part of Clementine.
|
||||
* Copyright 2010, David Sansome <me@davidsansome.com>
|
||||
* Copyright 2018-2021, Jonas Kvinge <jonas@jkvinge.net>
|
||||
*
|
||||
* Strawberry is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
@@ -25,24 +24,33 @@
|
||||
|
||||
#include <QList>
|
||||
#include <QVariant>
|
||||
#include <QUrl>
|
||||
#include <QSqlQuery>
|
||||
|
||||
class CollectionQuery;
|
||||
|
||||
class SqlRow {
|
||||
|
||||
public:
|
||||
SqlRow(const QSqlQuery &query);
|
||||
|
||||
const QVariant &value(int i) const { return columns_[i]; }
|
||||
const QVariant value(const int number) const;
|
||||
const QVariant value(const QString &name) const;
|
||||
|
||||
QList<QVariant> columns_;
|
||||
QString ValueToString(const QString &n) const;
|
||||
QUrl ValueToUrl(const QString &n) const;
|
||||
int ValueToInt(const QString &n) const;
|
||||
uint ValueToUInt(const QString &n) const;
|
||||
qint64 ValueToLongLong(const QString &n) const;
|
||||
float ValueToFloat(const QString &n) const;
|
||||
bool ValueToBool(const QString& n) const;
|
||||
|
||||
private:
|
||||
SqlRow();
|
||||
|
||||
void Init(const QSqlQuery &query);
|
||||
|
||||
QMap<int, QVariant> columns_by_number_;
|
||||
QMap<QString, QVariant> columns_by_name_;
|
||||
|
||||
};
|
||||
|
||||
typedef QList<SqlRow> SqlRowList;
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
|
||||
#cmakedefine HAVE_BACKTRACE
|
||||
#cmakedefine HAVE_GIO
|
||||
#cmakedefine HAVE_GIO_UNIX
|
||||
#cmakedefine HAVE_DBUS
|
||||
#cmakedefine HAVE_X11
|
||||
#cmakedefine HAVE_UDISKS2
|
||||
@@ -15,7 +16,6 @@
|
||||
#cmakedefine HAVE_LIBGPOD
|
||||
#cmakedefine HAVE_LIBMTP
|
||||
#cmakedefine HAVE_LIBPULSE
|
||||
#cmakedefine HAVE_SPARKLE
|
||||
#cmakedefine HAVE_QTSPARKLE
|
||||
#cmakedefine HAVE_SONGFINGERPRINTING
|
||||
#cmakedefine HAVE_MUSICBRAINZ
|
||||
|
||||
@@ -46,7 +46,6 @@
|
||||
#include "collection/collectionbackend.h"
|
||||
#include "collection/collectionmodel.h"
|
||||
#include "collection/collectionitem.h"
|
||||
#include "collection/sqlrow.h"
|
||||
#include "playlist/playlistmanager.h"
|
||||
#include "playlist/songmimedata.h"
|
||||
#include "covermanager/albumcoverloader.h"
|
||||
@@ -236,14 +235,18 @@ QVariant ContextAlbumsModel::data(const CollectionItem *item, int role) const {
|
||||
|
||||
case Role_SortText:
|
||||
return item->SortText();
|
||||
|
||||
default:
|
||||
return QVariant();
|
||||
}
|
||||
|
||||
return QVariant();
|
||||
|
||||
}
|
||||
|
||||
void ContextAlbumsModel::Reset() {
|
||||
|
||||
for (QMap<QString, CollectionItem*>::iterator it = container_nodes_.begin(); it != container_nodes_.end(); ++it) {
|
||||
for (QMap<QString, CollectionItem*>::const_iterator it = container_nodes_.constBegin(); it != container_nodes_.constEnd(); ++it) {
|
||||
const QString cache_key = AlbumIconPixmapCacheKey(ItemToIndex(it.value()));
|
||||
QPixmapCache::remove(cache_key);
|
||||
}
|
||||
@@ -263,7 +266,7 @@ void ContextAlbumsModel::Reset() {
|
||||
|
||||
CollectionItem *ContextAlbumsModel::ItemFromSong(CollectionItem::Type item_type, const bool signal, CollectionItem *parent, const Song &s, const int container_level) {
|
||||
|
||||
if (signal) beginInsertRows(ItemToIndex(parent), parent->children.count(), parent->children.count());
|
||||
if (signal) beginInsertRows(ItemToIndex(parent), static_cast<int>(parent->children.count()), static_cast<int>(parent->children.count()));
|
||||
|
||||
CollectionItem *item = new CollectionItem(item_type, parent);
|
||||
item->container_level = container_level;
|
||||
|
||||
@@ -43,7 +43,6 @@
|
||||
#include "core/song.h"
|
||||
#include "collection/collectionquery.h"
|
||||
#include "collection/collectionitem.h"
|
||||
#include "collection/sqlrow.h"
|
||||
#include "covermanager/albumcoverloaderoptions.h"
|
||||
#include "covermanager/albumcoverloaderresult.h"
|
||||
|
||||
@@ -72,11 +71,6 @@ class ContextAlbumsModel : public SimpleTreeModel<CollectionItem> {
|
||||
LastRole
|
||||
};
|
||||
|
||||
struct QueryResult {
|
||||
QueryResult() {}
|
||||
SqlRowList rows;
|
||||
};
|
||||
|
||||
void GetChildSongs(CollectionItem *item, QList<QUrl> *urls, SongList *songs, QSet<int> *song_ids) const;
|
||||
SongList GetChildSongs(const QModelIndex &idx) const;
|
||||
SongList GetChildSongs(const QModelIndexList &indexes) const;
|
||||
|
||||
@@ -294,7 +294,10 @@ ContextView::ContextView(QWidget *parent)
|
||||
|
||||
void ContextView::resizeEvent(QResizeEvent*) {
|
||||
|
||||
widget_album_->setFixedSize(width() - 15, width());
|
||||
if (width() != prev_width_) {
|
||||
widget_album_->setFixedSize(width() - 15, width());
|
||||
prev_width_ = width();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -428,7 +431,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_ = 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_.album(), song_playing_.title()));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -22,9 +22,7 @@
|
||||
#include "version.h"
|
||||
|
||||
#include <cstdlib>
|
||||
#ifndef _MSC_VER
|
||||
# include <getopt.h>
|
||||
#endif
|
||||
#include <getopt.h>
|
||||
#include <iostream>
|
||||
#include <type_traits>
|
||||
|
||||
@@ -121,7 +119,6 @@ void CommandlineOptions::RemoveArg(const QString &starts_with, int count) {
|
||||
|
||||
bool CommandlineOptions::Parse() {
|
||||
|
||||
#ifndef _MSC_VER // TODO: Consider QCommandLineOption.
|
||||
static const struct option kOptions[] = {
|
||||
{"help", no_argument, nullptr, 'h'},
|
||||
{"play", no_argument, nullptr, 'p'},
|
||||
@@ -322,8 +319,6 @@ bool CommandlineOptions::Parse() {
|
||||
}
|
||||
}
|
||||
|
||||
#endif // _MSC_VER
|
||||
|
||||
return true;
|
||||
|
||||
}
|
||||
|
||||
@@ -108,7 +108,7 @@ void DeleteFiles::ProcessSomeFiles() {
|
||||
|
||||
// We process files in batches so we can be cancelled part-way through.
|
||||
|
||||
const int n = qMin(songs_.count(), progress_ + kBatchSize);
|
||||
const qint64 n = qMin(static_cast<qint64>(songs_.count()), static_cast<qint64>(progress_ + kBatchSize));
|
||||
for (; progress_ < n; ++progress_) {
|
||||
task_manager_->SetTaskProgress(task_id_, progress_, songs_.count());
|
||||
|
||||
|
||||
@@ -80,13 +80,11 @@ bool FilesystemMusicStorage::CopyToStorage(const CopyJob &job) {
|
||||
QFile::rename(cover_src.absoluteFilePath(), cover_dest.absoluteFilePath());
|
||||
}
|
||||
// Remove empty directories.
|
||||
#if (QT_VERSION >= QT_VERSION_CHECK(5, 9, 0))
|
||||
QDir remove_dir(src.absolutePath(), QString(), QDir::Name, QDir::NoDotAndDotDot);
|
||||
while (remove_dir.isEmpty()) {
|
||||
if (!QDir().rmdir(remove_dir.absolutePath())) break;
|
||||
remove_dir.cdUp();
|
||||
}
|
||||
#endif
|
||||
}
|
||||
else {
|
||||
if (dest.exists() && !job.overwrite_) {
|
||||
|
||||
@@ -133,6 +133,10 @@ QIcon IconLoader::Load(const QString &name, const int fixed_size, const int min_
|
||||
if (QFile::exists(filename)) ret.addFile(filename, QSize(s, s));
|
||||
}
|
||||
|
||||
if (ret.isNull() && !system_icons_ && !custom_icons_) {
|
||||
qLog(Error) << "Couldn't load icon" << name;
|
||||
}
|
||||
|
||||
return ret;
|
||||
|
||||
}
|
||||
|
||||
@@ -56,7 +56,7 @@ static const QMap<QString, IconProperties> iconmapper_ = { // clazy:exclude=non
|
||||
{ "dialog-ok-apply", { {}} },
|
||||
{ "dialog-password", { {}} },
|
||||
{ "dialog-warning", { {}} },
|
||||
{ "document-download", { {}} },
|
||||
{ "document-download", { {"download"}} },
|
||||
{ "document-new", { {}} },
|
||||
{ "document-open-folder", { {}} },
|
||||
{ "document-open", { {}} },
|
||||
@@ -64,8 +64,8 @@ static const QMap<QString, IconProperties> iconmapper_ = { // clazy:exclude=non
|
||||
{ "document-search", { {}} },
|
||||
{ "document-open-remote", { {}} },
|
||||
{ "download", { {"applications-internet", "network-workgroup"}} },
|
||||
{ "edit-clear-list", { {}} },
|
||||
{ "edit-clear-locationbar-ltr", { {}, 0, 0, false } },
|
||||
{ "edit-clear-list", { {"edit-clear-list","edit-clear-all"}} },
|
||||
{ "edit-clear-locationbar-ltr", { {"edit-clear-locationbar-ltr"}} },
|
||||
{ "edit-copy", { {}} },
|
||||
{ "edit-delete", { {}} },
|
||||
{ "edit-find", { {}} },
|
||||
@@ -111,7 +111,7 @@ static const QMap<QString, IconProperties> iconmapper_ = { // clazy:exclude=non
|
||||
{ "pulseaudio", { {}} },
|
||||
{ "realtek", { {}} },
|
||||
{ "scrobble-disabled", { {}} },
|
||||
{ "scrobble", { {}} },
|
||||
{ "scrobble", { {"love"}} },
|
||||
{ "search", { {}} },
|
||||
{ "soundcard", { {"audiocard", "audio-card"}} },
|
||||
{ "speaker", { {}} },
|
||||
|
||||
@@ -29,12 +29,6 @@ namespace mac {
|
||||
void MacMain();
|
||||
void SetShortcutHandler(GlobalShortcutsBackendMacOS *handler);
|
||||
void SetApplicationHandler(PlatformInterface *handler);
|
||||
void CheckForUpdates();
|
||||
|
||||
QString GetBundlePath();
|
||||
QString GetResourcesPath();
|
||||
QString GetApplicationSupportPath();
|
||||
QString GetMusicDirectory();
|
||||
|
||||
void EnableFullScreen(const QWidget &main_window);
|
||||
|
||||
|
||||
@@ -55,10 +55,6 @@
|
||||
#include "globalshortcuts/globalshortcutsmanager.h"
|
||||
#include "globalshortcuts/globalshortcutsbackend-macos.h"
|
||||
|
||||
#ifdef HAVE_SPARKLE
|
||||
# import <SUUpdater.h>
|
||||
#endif
|
||||
|
||||
#include <QApplication>
|
||||
#include <QCoreApplication>
|
||||
#include <QWidget>
|
||||
@@ -288,10 +284,6 @@ void MacMain() {
|
||||
ScopedNSAutoreleasePool pool;
|
||||
// Creates and sets the magic global variable so QApplication will find it.
|
||||
[MacApplication sharedApplication];
|
||||
#ifdef HAVE_SPARKLE
|
||||
// Creates and sets the magic global variable for Sparkle.
|
||||
[ [SUUpdater sharedUpdater] setDelegate:NSApp];
|
||||
#endif
|
||||
|
||||
}
|
||||
|
||||
@@ -303,61 +295,6 @@ void SetApplicationHandler(PlatformInterface *handler) {
|
||||
[NSApp SetApplicationHandler:handler];
|
||||
}
|
||||
|
||||
void CheckForUpdates() {
|
||||
#ifdef HAVE_SPARKLE
|
||||
[ [SUUpdater sharedUpdater] checkForUpdates:NSApp];
|
||||
#endif
|
||||
}
|
||||
|
||||
QString GetBundlePath() {
|
||||
|
||||
ScopedCFTypeRef<CFURLRef> app_url(CFBundleCopyBundleURL(CFBundleGetMainBundle()));
|
||||
ScopedCFTypeRef<CFStringRef> mac_path(CFURLCopyFileSystemPath(app_url.get(), kCFURLPOSIXPathStyle));
|
||||
const char *path = CFStringGetCStringPtr(mac_path.get(), CFStringGetSystemEncoding());
|
||||
QString bundle_path = QString::fromUtf8(path);
|
||||
return bundle_path;
|
||||
|
||||
}
|
||||
|
||||
QString GetResourcesPath() {
|
||||
|
||||
QString bundle_path = GetBundlePath();
|
||||
return bundle_path + "/Contents/Resources";
|
||||
|
||||
}
|
||||
|
||||
QString GetApplicationSupportPath() {
|
||||
|
||||
ScopedNSAutoreleasePool pool;
|
||||
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSApplicationSupportDirectory, NSUserDomainMask, YES);
|
||||
QString ret;
|
||||
if ([paths count] > 0) {
|
||||
NSString *user_path = [paths objectAtIndex:0];
|
||||
ret = QString::fromUtf8([user_path UTF8String]);
|
||||
}
|
||||
else {
|
||||
ret = "~/Library/Application Support";
|
||||
}
|
||||
return ret;
|
||||
|
||||
}
|
||||
|
||||
QString GetMusicDirectory() {
|
||||
|
||||
ScopedNSAutoreleasePool pool;
|
||||
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSMusicDirectory, NSUserDomainMask, YES);
|
||||
QString ret;
|
||||
if ([paths count] > 0) {
|
||||
NSString *user_path = [paths objectAtIndex:0];
|
||||
ret = QString::fromUtf8([user_path UTF8String]);
|
||||
}
|
||||
else {
|
||||
ret = "~/Music";
|
||||
}
|
||||
return ret;
|
||||
|
||||
}
|
||||
|
||||
static int MapFunctionKey(int keycode) {
|
||||
|
||||
switch (keycode) {
|
||||
|
||||
@@ -380,7 +380,7 @@ MainWindow::MainWindow(Application *app, std::shared_ptr<SystemTrayIcon> tray_ic
|
||||
app_->player()->SetEqualizer(equalizer_.get());
|
||||
app_->player()->Init();
|
||||
EngineChanged(app_->player()->engine()->type());
|
||||
int volume = app_->player()->GetVolume();
|
||||
int volume = static_cast<int>(app_->player()->GetVolume());
|
||||
ui_->volume->setValue(volume);
|
||||
VolumeChanged(volume);
|
||||
|
||||
@@ -457,8 +457,12 @@ MainWindow::MainWindow(Application *app, std::shared_ptr<SystemTrayIcon> tray_ic
|
||||
ui_->action_transcoder->setIcon(IconLoader::Load("tools-wizard"));
|
||||
ui_->action_update_collection->setIcon(IconLoader::Load("view-refresh"));
|
||||
ui_->action_full_collection_scan->setIcon(IconLoader::Load("view-refresh"));
|
||||
ui_->action_abort_collection_scan->setIcon(IconLoader::Load("dialog-error"));
|
||||
ui_->action_settings->setIcon(IconLoader::Load("configure"));
|
||||
ui_->action_import_data_from_last_fm->setIcon(IconLoader::Load("scrobble"));
|
||||
ui_->action_console->setIcon(IconLoader::Load("keyboard"));
|
||||
ui_->action_toggle_show_sidebar->setIcon(IconLoader::Load("view-choose"));
|
||||
ui_->action_auto_complete_tags->setIcon(IconLoader::Load("musicbrainz"));
|
||||
|
||||
// Scrobble
|
||||
|
||||
@@ -775,10 +779,9 @@ MainWindow::MainWindow(Application *app, std::shared_ptr<SystemTrayIcon> tray_ic
|
||||
thumbbar_->SetActions(QList<QAction*>() << ui_->action_previous_track << ui_->action_play_pause << ui_->action_stop << ui_->action_next_track << nullptr << ui_->action_love);
|
||||
#endif
|
||||
|
||||
#if defined(HAVE_SPARKLE) || defined(HAVE_QTSPARKLE)
|
||||
#if defined(HAVE_QTSPARKLE)
|
||||
QAction *check_updates = ui_->menu_tools->addAction(tr("Check for updates..."));
|
||||
check_updates->setMenuRole(QAction::ApplicationSpecificRole);
|
||||
QObject::connect(check_updates, &QAction::triggered, this, &MainWindow::CheckForUpdates);
|
||||
#endif
|
||||
|
||||
#ifdef HAVE_GLOBALSHORTCUTS
|
||||
@@ -1241,11 +1244,11 @@ void MainWindow::ExitFinished() {
|
||||
void MainWindow::EngineChanged(Engine::EngineType enginetype) {
|
||||
|
||||
ui_->action_equalizer->setEnabled(enginetype == Engine::EngineType::GStreamer);
|
||||
#ifdef Q_OS_WIN
|
||||
#if defined(HAVE_AUDIOCD) && !defined(Q_OS_WIN)
|
||||
ui_->action_open_cd->setEnabled(enginetype == Engine::EngineType::GStreamer);
|
||||
#else
|
||||
ui_->action_open_cd->setEnabled(false);
|
||||
ui_->action_open_cd->setVisible(false);
|
||||
#else
|
||||
ui_->action_open_cd->setEnabled(enginetype == Engine::EngineType::GStreamer);
|
||||
#endif
|
||||
|
||||
}
|
||||
@@ -1375,7 +1378,7 @@ void MainWindow::TrackSkipped(PlaylistItemPtr item) {
|
||||
const qint64 seconds_left = (length - position) / kNsecPerSec;
|
||||
const qint64 seconds_total = length / kNsecPerSec;
|
||||
|
||||
if (((0.05 * seconds_total > 60 && percentage < 0.98) || percentage < 0.95) && seconds_left > 5) { // Never count the skip if under 5 seconds left
|
||||
if (((0.05 * static_cast<double>(seconds_total) > 60.0 && percentage < 0.98) || percentage < 0.95) && seconds_left > 5) { // Never count the skip if under 5 seconds left
|
||||
app_->collection_backend()->IncrementSkipCountAsync(song.id(), percentage);
|
||||
}
|
||||
}
|
||||
@@ -1630,7 +1633,7 @@ void MainWindow::Seeked(const qint64 microseconds) {
|
||||
|
||||
const qint64 position = microseconds / kUsecPerSec;
|
||||
const qint64 length = app_->player()->GetCurrentItem()->Metadata().length_nanosec() / kNsecPerSec;
|
||||
tray_icon_->SetProgress(static_cast<int>(static_cast<double>(position) / length * 100));
|
||||
tray_icon_->SetProgress(static_cast<int>(static_cast<double>(position) / static_cast<double>(length) * 100.0));
|
||||
|
||||
}
|
||||
|
||||
@@ -1641,10 +1644,10 @@ void MainWindow::UpdateTrackPosition() {
|
||||
|
||||
const qint64 length = (item->Metadata().length_nanosec() / kNsecPerSec);
|
||||
if (length <= 0) return;
|
||||
const int position = std::floor(static_cast<float>(app_->player()->engine()->position_nanosec()) / kNsecPerSec + 0.5);
|
||||
const int position = std::floor(static_cast<float>(app_->player()->engine()->position_nanosec()) / static_cast<float>(kNsecPerSec) + 0.5);
|
||||
|
||||
// Update the tray icon every 10 seconds
|
||||
if (position % 10 == 0) tray_icon_->SetProgress(static_cast<int>(static_cast<double>(position) / length * 100));
|
||||
if (position % 10 == 0) tray_icon_->SetProgress(static_cast<int>(static_cast<double>(position) / static_cast<double>(length) * 100.0));
|
||||
|
||||
// Send Scrobble
|
||||
if (app_->scrobbler()->IsEnabled() && item->Metadata().is_metadata_good()) {
|
||||
@@ -1816,7 +1819,7 @@ void MainWindow::PlaylistRightClick(const QPoint global_pos, const QModelIndex &
|
||||
// Are any of the selected songs editable or queued?
|
||||
QModelIndexList selection = ui_->playlist->view()->selectionModel()->selectedRows();
|
||||
bool cue_selected = false;
|
||||
int selected = ui_->playlist->view()->selectionModel()->selectedRows().count();
|
||||
qint64 selected = ui_->playlist->view()->selectionModel()->selectedRows().count();
|
||||
int editable = 0;
|
||||
int in_queue = 0;
|
||||
int not_in_queue = 0;
|
||||
@@ -2433,7 +2436,7 @@ void MainWindow::CommandlineOptionsReceived(const CommandlineOptions &options) {
|
||||
if (options.set_volume() != -1) app_->player()->SetVolume(options.set_volume());
|
||||
|
||||
if (options.volume_modifier() != 0) {
|
||||
app_->player()->SetVolume(app_->player()->GetVolume() +options.volume_modifier());
|
||||
app_->player()->SetVolume(app_->player()->GetVolume() + options.volume_modifier());
|
||||
}
|
||||
|
||||
if (options.seek_to() != -1) {
|
||||
@@ -2488,12 +2491,6 @@ bool MainWindow::LoadUrl(const QString &url) {
|
||||
|
||||
}
|
||||
|
||||
void MainWindow::CheckForUpdates() {
|
||||
#if defined(Q_OS_MACOS)
|
||||
mac::CheckForUpdates();
|
||||
#endif
|
||||
}
|
||||
|
||||
void MainWindow::PlaylistUndoRedoChanged(QAction *undo, QAction *redo) {
|
||||
|
||||
playlist_menu_->insertAction(playlist_undoredo_, undo);
|
||||
@@ -3138,8 +3135,10 @@ void MainWindow::PlaylistDelete() {
|
||||
QModelIndex source_idx = app_->playlist_manager()->current()->proxy()->mapToSource(proxy_idx);
|
||||
PlaylistItemPtr item = app_->playlist_manager()->current()->item_at(source_idx.row());
|
||||
if (!item || !item->Metadata().url().isLocalFile()) continue;
|
||||
QString filename = item->Metadata().url().toLocalFile();
|
||||
if (files.contains(filename)) continue;
|
||||
selected_songs << item->Metadata();
|
||||
files << item->Metadata().url().toLocalFile();
|
||||
files << filename;
|
||||
if (item == app_->player()->GetCurrentItem()) is_current_item = true;
|
||||
}
|
||||
if (selected_songs.isEmpty()) return;
|
||||
@@ -3183,8 +3182,8 @@ void MainWindow::FocusSearchField() {
|
||||
qobuz_view_->FocusSearchField();
|
||||
}
|
||||
#endif
|
||||
else if (!ui_->playlist->SearchFieldHasFocus()) {
|
||||
ui_->playlist->FocusSearchField();
|
||||
}
|
||||
else if (!ui_->playlist->SearchFieldHasFocus()) {
|
||||
ui_->playlist->FocusSearchField();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -219,8 +219,6 @@ class MainWindow : public QMainWindow, public PlatformInterface {
|
||||
void AddStream();
|
||||
void AddStreamAccepted();
|
||||
|
||||
void CheckForUpdates();
|
||||
|
||||
void PlayingWidgetPositionChanged(const bool above_status_bar);
|
||||
|
||||
void SongSaveComplete(TagReaderReply *reply, const QPersistentModelIndex &idx);
|
||||
|
||||
@@ -448,7 +448,7 @@
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>1131</width>
|
||||
<height>24</height>
|
||||
<height>26</height>
|
||||
</rect>
|
||||
</property>
|
||||
<widget class="QMenu" name="menu_music">
|
||||
@@ -792,10 +792,6 @@
|
||||
</property>
|
||||
</action>
|
||||
<action name="action_auto_complete_tags">
|
||||
<property name="icon">
|
||||
<iconset resource="../../data/data.qrc">
|
||||
<normaloff>:/pictures/musicbrainz.png</normaloff>:/pictures/musicbrainz.png</iconset>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Complete tags automatically...</string>
|
||||
</property>
|
||||
|
||||
@@ -74,6 +74,10 @@
|
||||
|
||||
#include "radios/radiochannel.h"
|
||||
|
||||
#ifdef HAVE_LIBMTP
|
||||
#include "device/mtpconnection.h"
|
||||
#endif
|
||||
|
||||
void RegisterMetaTypes() {
|
||||
|
||||
qRegisterMetaType<const char*>("const char*");
|
||||
@@ -113,8 +117,8 @@ void RegisterMetaTypes() {
|
||||
qRegisterMetaType<GstElement*>("GstElement*");
|
||||
qRegisterMetaType<GstEnginePipeline*>("GstEnginePipeline*");
|
||||
#endif
|
||||
qRegisterMetaType<PlaylistItemList>("PlaylistItemList");
|
||||
qRegisterMetaType<PlaylistItemPtr>("PlaylistItemPtr");
|
||||
qRegisterMetaType<PlaylistItemList>("PlaylistItemList");
|
||||
qRegisterMetaType<QList<PlaylistItemPtr>>("QList<PlaylistItemPtr>");
|
||||
qRegisterMetaType<PlaylistSequence::RepeatMode>("PlaylistSequence::RepeatMode");
|
||||
qRegisterMetaType<PlaylistSequence::ShuffleMode>("PlaylistSequence::ShuffleMode");
|
||||
@@ -147,4 +151,8 @@ void RegisterMetaTypes() {
|
||||
|
||||
qRegisterMetaType<RadioChannelList>("RadioChannelList");
|
||||
|
||||
#ifdef HAVE_LIBMTP
|
||||
qRegisterMetaType<MtpConnection*>("MtpConnection*");
|
||||
#endif
|
||||
|
||||
}
|
||||
|
||||
@@ -410,9 +410,13 @@ void Mpris2::AlbumCoverLoaded(const Song &song, const AlbumCoverLoaderResult &re
|
||||
|
||||
}
|
||||
|
||||
double Mpris2::Volume() const { return app_->player()->GetVolume() / 100.0; }
|
||||
double Mpris2::Volume() const {
|
||||
return app_->player()->GetVolume() / 100.0;
|
||||
}
|
||||
|
||||
void Mpris2::SetVolume(double value) { app_->player()->SetVolume(static_cast<int>(value * 100)); }
|
||||
void Mpris2::SetVolume(const double value) {
|
||||
app_->player()->SetVolume(static_cast<uint>(std::max(std::min(lround(value * 100.0), 100L), 0L)));
|
||||
}
|
||||
|
||||
qint64 Mpris2::Position() const {
|
||||
return app_->player()->engine()->position_nanosec() / kNsecPerUsec;
|
||||
|
||||
@@ -145,7 +145,7 @@ class Mpris2 : public QObject {
|
||||
void SetShuffle(bool enable);
|
||||
QVariantMap Metadata() const;
|
||||
double Volume() const;
|
||||
void SetVolume(double value);
|
||||
void SetVolume(const double value);
|
||||
qint64 Position() const;
|
||||
double MaximumRate() const;
|
||||
double MinimumRate() const;
|
||||
|
||||
@@ -58,12 +58,11 @@ class MusicStorage {
|
||||
typedef std::function<void(float progress)> ProgressFunction;
|
||||
|
||||
struct CopyJob {
|
||||
CopyJob() : overwrite_(false), mark_as_listened_(false), remove_original_(false), albumcover_(false) {}
|
||||
CopyJob() : overwrite_(false), remove_original_(false), albumcover_(false) {}
|
||||
QString source_;
|
||||
QString destination_;
|
||||
Song metadata_;
|
||||
bool overwrite_;
|
||||
bool mark_as_listened_;
|
||||
bool remove_original_;
|
||||
bool albumcover_;
|
||||
QString cover_source_;
|
||||
|
||||
@@ -37,10 +37,7 @@
|
||||
NetworkAccessManager::NetworkAccessManager(QObject *parent)
|
||||
: QNetworkAccessManager(parent) {
|
||||
|
||||
#if (QT_VERSION >= QT_VERSION_CHECK(5, 9, 0))
|
||||
setRedirectPolicy(QNetworkRequest::NoLessSafeRedirectPolicy);
|
||||
#endif
|
||||
|
||||
setCache(new ThreadSafeNetworkDiskCache(this));
|
||||
|
||||
}
|
||||
@@ -56,11 +53,7 @@ QNetworkReply *NetworkAccessManager::createRequest(Operation op, const QNetworkR
|
||||
}
|
||||
|
||||
QNetworkRequest new_request(request);
|
||||
#if QT_VERSION >= QT_VERSION_CHECK(5, 9, 0)
|
||||
new_request.setAttribute(QNetworkRequest::RedirectPolicyAttribute, QNetworkRequest::NoLessSafeRedirectPolicy);
|
||||
#else
|
||||
new_request.setAttribute(QNetworkRequest::FollowRedirectsAttribute, true);
|
||||
#endif
|
||||
new_request.setRawHeader("User-Agent", user_agent);
|
||||
|
||||
if (op == QNetworkAccessManager::PostOperation && !new_request.header(QNetworkRequest::ContentTypeHeader).isValid()) {
|
||||
|
||||
@@ -23,6 +23,7 @@
|
||||
|
||||
#include "player.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <memory>
|
||||
|
||||
#include <QtGlobal>
|
||||
@@ -632,11 +633,17 @@ void Player::EngineStateChanged(const Engine::State state) {
|
||||
|
||||
}
|
||||
|
||||
void Player::SetVolume(const int value) {
|
||||
uint Player::GetVolume() const {
|
||||
|
||||
int old_volume = engine_->volume();
|
||||
return engine_->volume();
|
||||
|
||||
int volume = qBound(0, value, 100);
|
||||
}
|
||||
|
||||
void Player::SetVolume(const uint value) {
|
||||
|
||||
uint old_volume = engine_->volume();
|
||||
|
||||
uint volume = qBound(0U, value, 100U);
|
||||
settings_.setValue("volume", volume);
|
||||
engine_->SetVolume(volume);
|
||||
|
||||
@@ -646,9 +653,25 @@ void Player::SetVolume(const int value) {
|
||||
|
||||
}
|
||||
|
||||
int Player::GetVolume() const { return engine_->volume(); }
|
||||
void Player::VolumeUp() {
|
||||
|
||||
void Player::PlayAt(const int index, const qint64 offset_nanosec, Engine::TrackChangeFlags change, const Playlist::AutoScroll autoscroll, const bool reshuffle, const bool force_inform) {
|
||||
uint old_volume = GetVolume();
|
||||
uint new_volume = std::min(old_volume + 5, static_cast<uint>(100));
|
||||
if (new_volume == old_volume) return;
|
||||
SetVolume(new_volume);
|
||||
|
||||
}
|
||||
|
||||
void Player::VolumeDown() {
|
||||
|
||||
uint old_volume = GetVolume();
|
||||
uint new_volume = static_cast<uint>(std::max(static_cast<int>(old_volume) - 5, 0));
|
||||
if (new_volume == old_volume) return;
|
||||
SetVolume(new_volume);
|
||||
|
||||
}
|
||||
|
||||
void Player::PlayAt(const int index, const quint64 offset_nanosec, Engine::TrackChangeFlags change, const Playlist::AutoScroll autoscroll, const bool reshuffle, const bool force_inform) {
|
||||
|
||||
pause_time_ = QDateTime();
|
||||
play_offset_nanosec_ = offset_nanosec;
|
||||
@@ -696,7 +719,7 @@ void Player::CurrentMetadataChanged(const Song &metadata) {
|
||||
|
||||
}
|
||||
|
||||
void Player::SeekTo(const qint64 seconds) {
|
||||
void Player::SeekTo(const quint64 seconds) {
|
||||
|
||||
const qint64 length_nanosec = engine_->length_nanosec();
|
||||
|
||||
@@ -705,7 +728,7 @@ void Player::SeekTo(const qint64 seconds) {
|
||||
return;
|
||||
}
|
||||
|
||||
const qint64 nanosec = qBound(0LL, seconds * kNsecPerSec, length_nanosec);
|
||||
const qint64 nanosec = qBound(0LL, static_cast<qint64>(seconds) * kNsecPerSec, length_nanosec);
|
||||
engine_->Seek(nanosec);
|
||||
|
||||
qLog(Debug) << "Track seeked to" << nanosec << "ns - updating scrobble point";
|
||||
@@ -766,7 +789,7 @@ void Player::Mute() {
|
||||
|
||||
if (!volume_control_) return;
|
||||
|
||||
const int current_volume = engine_->volume();
|
||||
const uint current_volume = engine_->volume();
|
||||
|
||||
if (current_volume == 0) {
|
||||
SetVolume(volume_before_mute_);
|
||||
|
||||
@@ -61,7 +61,7 @@ class PlayerInterface : public QObject {
|
||||
|
||||
virtual EngineBase *engine() const = 0;
|
||||
virtual Engine::State GetState() const = 0;
|
||||
virtual int GetVolume() const = 0;
|
||||
virtual uint GetVolume() const = 0;
|
||||
|
||||
virtual PlaylistItemPtr GetCurrentItem() const = 0;
|
||||
virtual PlaylistItemPtr GetItemAt(const int pos) const = 0;
|
||||
@@ -73,7 +73,7 @@ class PlayerInterface : public QObject {
|
||||
virtual void ReloadSettings() = 0;
|
||||
|
||||
// Manual track change to the specified track
|
||||
virtual void PlayAt(const int index, const qint64 offset_nanosec, Engine::TrackChangeFlags change, const Playlist::AutoScroll autoscroll, const bool reshuffle, const bool force_inform = false) = 0;
|
||||
virtual void PlayAt(const int index, const quint64 offset_nanosec, Engine::TrackChangeFlags change, const Playlist::AutoScroll autoscroll, const bool reshuffle, const bool force_inform = false) = 0;
|
||||
|
||||
// If there's currently a song playing, pause it, otherwise play the track that was playing last, or the first one on the playlist
|
||||
virtual void PlayPause(const quint64 offset_nanosec = 0, const Playlist::AutoScroll autoscroll = Playlist::AutoScroll_Always) = 0;
|
||||
@@ -84,10 +84,10 @@ class PlayerInterface : public QObject {
|
||||
virtual void Next() = 0;
|
||||
virtual void Previous() = 0;
|
||||
virtual void PlayPlaylist(const QString &playlist_name) = 0;
|
||||
virtual void SetVolume(const int value) = 0;
|
||||
virtual void SetVolume(const uint value) = 0;
|
||||
virtual void VolumeUp() = 0;
|
||||
virtual void VolumeDown() = 0;
|
||||
virtual void SeekTo(const qint64 seconds) = 0;
|
||||
virtual void SeekTo(const quint64 seconds) = 0;
|
||||
// Moves the position of the currently playing song five seconds forward.
|
||||
virtual void SeekForward() = 0;
|
||||
// Moves the position of the currently playing song five seconds backwards.
|
||||
@@ -111,7 +111,7 @@ class PlayerInterface : public QObject {
|
||||
void Error(QString message = QString());
|
||||
void PlaylistFinished();
|
||||
void VolumeEnabled(bool);
|
||||
void VolumeChanged(int volume);
|
||||
void VolumeChanged(uint volume);
|
||||
void TrackSkipped(PlaylistItemPtr old_track);
|
||||
// Emitted when there's a manual change to the current's track position.
|
||||
void Seeked(qint64 microseconds);
|
||||
@@ -141,7 +141,7 @@ class Player : public PlayerInterface {
|
||||
|
||||
EngineBase *engine() const override { return engine_.get(); }
|
||||
Engine::State GetState() const override { return last_state_; }
|
||||
int GetVolume() const override;
|
||||
uint GetVolume() const override;
|
||||
|
||||
PlaylistItemPtr GetCurrentItem() const override { return current_item_; }
|
||||
PlaylistItemPtr GetItemAt(const int pos) const override;
|
||||
@@ -159,17 +159,17 @@ class Player : public PlayerInterface {
|
||||
public slots:
|
||||
void ReloadSettings() override;
|
||||
|
||||
void PlayAt(const int index, const qint64 offset_nanosec, Engine::TrackChangeFlags change, const Playlist::AutoScroll autoscroll, const bool reshuffle, const bool force_inform = false) override;
|
||||
void PlayAt(const int index, const quint64 offset_nanosec, Engine::TrackChangeFlags change, const Playlist::AutoScroll autoscroll, const bool reshuffle, const bool force_inform = false) override;
|
||||
void PlayPause(const quint64 offset_nanosec = 0, const Playlist::AutoScroll autoscroll = Playlist::AutoScroll_Always) override;
|
||||
void PlayPauseHelper() override { PlayPause(play_offset_nanosec_); }
|
||||
void RestartOrPrevious() override;
|
||||
void Next() override;
|
||||
void Previous() override;
|
||||
void PlayPlaylist(const QString &playlist_name) override;
|
||||
void SetVolume(const int value) override;
|
||||
void VolumeUp() override { SetVolume(GetVolume() + 5); }
|
||||
void VolumeDown() override { SetVolume(GetVolume() - 5); }
|
||||
void SeekTo(const qint64 seconds) override;
|
||||
void SetVolume(const uint value) override;
|
||||
void VolumeUp() override;
|
||||
void VolumeDown() override;
|
||||
void SeekTo(const quint64 seconds) override;
|
||||
void SeekForward() override;
|
||||
void SeekBackward() override;
|
||||
|
||||
@@ -235,7 +235,7 @@ class Player : public PlayerInterface {
|
||||
QMap<QString, UrlHandler*> url_handlers_;
|
||||
|
||||
QList<QUrl> loading_async_;
|
||||
int volume_before_mute_;
|
||||
uint volume_before_mute_;
|
||||
QDateTime last_pressed_previous_;
|
||||
|
||||
bool continue_on_error_;
|
||||
|
||||
@@ -202,15 +202,15 @@ struct Song::Private : public QSharedData {
|
||||
QString basefilename_;
|
||||
QUrl url_;
|
||||
FileType filetype_;
|
||||
int filesize_;
|
||||
qint64 filesize_;
|
||||
qint64 mtime_;
|
||||
qint64 ctime_;
|
||||
bool unavailable_;
|
||||
|
||||
QString fingerprint_;
|
||||
|
||||
int playcount_;
|
||||
int skipcount_;
|
||||
uint playcount_;
|
||||
uint skipcount_;
|
||||
qint64 lastplayed_;
|
||||
qint64 lastseen_;
|
||||
|
||||
@@ -224,7 +224,7 @@ struct Song::Private : public QSharedData {
|
||||
|
||||
QString cue_path_; // If the song has a CUE, this contains it's path.
|
||||
|
||||
double rating_; // Database rating, not read from tags.
|
||||
float rating_; // Database rating, initial rating read from tag.
|
||||
|
||||
QUrl stream_url_; // Temporary stream url set by url handler.
|
||||
QImage image_; // Album Cover image set by album cover loader.
|
||||
@@ -331,14 +331,14 @@ int Song::directory_id() const { return d->directory_id_; }
|
||||
const QUrl &Song::url() const { return d->url_; }
|
||||
const QString &Song::basefilename() const { return d->basefilename_; }
|
||||
Song::FileType Song::filetype() const { return d->filetype_; }
|
||||
int Song::filesize() const { return d->filesize_; }
|
||||
qint64 Song::filesize() const { return d->filesize_; }
|
||||
qint64 Song::mtime() const { return d->mtime_; }
|
||||
qint64 Song::ctime() const { return d->ctime_; }
|
||||
|
||||
QString Song::fingerprint() const { return d->fingerprint_; }
|
||||
|
||||
int Song::playcount() const { return d->playcount_; }
|
||||
int Song::skipcount() const { return d->skipcount_; }
|
||||
uint Song::playcount() const { return d->playcount_; }
|
||||
uint Song::skipcount() const { return d->skipcount_; }
|
||||
qint64 Song::lastplayed() const { return d->lastplayed_; }
|
||||
qint64 Song::lastseen() const { return d->lastseen_; }
|
||||
|
||||
@@ -360,6 +360,7 @@ bool Song::save_embedded_cover_supported(const FileType filetype) {
|
||||
|
||||
return filetype == FileType_FLAC ||
|
||||
filetype == FileType_OggVorbis ||
|
||||
filetype == FileType_OggOpus ||
|
||||
filetype == FileType_MPEG ||
|
||||
filetype == FileType_MP4;
|
||||
|
||||
@@ -373,7 +374,7 @@ bool Song::init_from_file() const { return d->init_from_file_; }
|
||||
const QString &Song::cue_path() const { return d->cue_path_; }
|
||||
bool Song::has_cue() const { return !d->cue_path_.isEmpty(); }
|
||||
|
||||
double Song::rating() const { return d->rating_; }
|
||||
float Song::rating() const { return d->rating_; }
|
||||
|
||||
bool Song::is_collection_song() const { return d->source_ == Source_Collection; }
|
||||
bool Song::is_metadata_good() const { return !d->url_.isEmpty() && !d->artist_.isEmpty() && !d->title_.isEmpty(); }
|
||||
@@ -408,8 +409,8 @@ bool Song::art_manual_is_valid() const {
|
||||
|
||||
bool Song::has_valid_art() const { return art_automatic_is_valid() || art_manual_is_valid(); }
|
||||
|
||||
void Song::set_id(int id) { d->id_ = id; }
|
||||
void Song::set_valid(bool v) { d->valid_ = v; }
|
||||
void Song::set_id(const int id) { d->id_ = id; }
|
||||
void Song::set_valid(const bool v) { d->valid_ = v; }
|
||||
|
||||
void Song::set_artist_id(const QString &v) { d->artist_id_ = v; }
|
||||
void Song::set_album_id(const QString &v) { d->album_id_ = v; }
|
||||
@@ -421,7 +422,7 @@ QString Song::sortable(const QString &v) {
|
||||
|
||||
for (const auto &i : kArticles) {
|
||||
if (copy.startsWith(i)) {
|
||||
int ilen = i.length();
|
||||
qint64 ilen = i.length();
|
||||
return copy.right(copy.length() - ilen) + ", " + copy.left(ilen - 1);
|
||||
}
|
||||
}
|
||||
@@ -433,10 +434,10 @@ void Song::set_title(const QString &v) { d->title_sortable_ = sortable(v); d->ti
|
||||
void Song::set_album(const QString &v) { d->album_sortable_ = sortable(v); d->album_ = v; }
|
||||
void Song::set_artist(const QString &v) { d->artist_sortable_ = sortable(v); d->artist_ = v; }
|
||||
void Song::set_albumartist(const QString &v) { d->albumartist_sortable_ = sortable(v); d->albumartist_ = v; }
|
||||
void Song::set_track(int v) { d->track_ = v; }
|
||||
void Song::set_disc(int v) { d->disc_ = v; }
|
||||
void Song::set_year(int v) { d->year_ = v; }
|
||||
void Song::set_originalyear(int v) { d->originalyear_ = v; }
|
||||
void Song::set_track(const int v) { d->track_ = v; }
|
||||
void Song::set_disc(const int v) { d->disc_ = v; }
|
||||
void Song::set_year(const int v) { d->year_ = v; }
|
||||
void Song::set_originalyear(const int v) { d->originalyear_ = v; }
|
||||
void Song::set_genre(const QString &v) { d->genre_ = v; }
|
||||
void Song::set_compilation(bool v) { d->compilation_ = v; }
|
||||
void Song::set_composer(const QString &v) { d->composer_ = v; }
|
||||
@@ -445,40 +446,40 @@ void Song::set_grouping(const QString &v) { d->grouping_ = v; }
|
||||
void Song::set_comment(const QString &v) { d->comment_ = v; }
|
||||
void Song::set_lyrics(const QString &v) { d->lyrics_ = v; }
|
||||
|
||||
void Song::set_beginning_nanosec(qint64 v) { d->beginning_ = qMax(0LL, v); }
|
||||
void Song::set_end_nanosec(qint64 v) { d->end_ = v; }
|
||||
void Song::set_length_nanosec(qint64 v) { d->end_ = d->beginning_ + v; }
|
||||
void Song::set_beginning_nanosec(const qint64 v) { d->beginning_ = qMax(0LL, v); }
|
||||
void Song::set_end_nanosec(const qint64 v) { d->end_ = v; }
|
||||
void Song::set_length_nanosec(const qint64 v) { d->end_ = d->beginning_ + v; }
|
||||
|
||||
void Song::set_bitrate(int v) { d->bitrate_ = v; }
|
||||
void Song::set_samplerate(int v) { d->samplerate_ = v; }
|
||||
void Song::set_bitdepth(int v) { d->bitdepth_ = v; }
|
||||
void Song::set_bitrate(const int v) { d->bitrate_ = v; }
|
||||
void Song::set_samplerate(const int v) { d->samplerate_ = v; }
|
||||
void Song::set_bitdepth(const int v) { d->bitdepth_ = v; }
|
||||
|
||||
void Song::set_source(Source v) { d->source_ = v; }
|
||||
void Song::set_directory_id(int v) { d->directory_id_ = v; }
|
||||
void Song::set_source(const Source v) { d->source_ = v; }
|
||||
void Song::set_directory_id(const int v) { d->directory_id_ = v; }
|
||||
void Song::set_url(const QUrl &v) { d->url_ = v; }
|
||||
void Song::set_basefilename(const QString &v) { d->basefilename_ = v; }
|
||||
void Song::set_filetype(FileType v) { d->filetype_ = v; }
|
||||
void Song::set_filesize(int v) { d->filesize_ = v; }
|
||||
void Song::set_mtime(qint64 v) { d->mtime_ = v; }
|
||||
void Song::set_ctime(qint64 v) { d->ctime_ = v; }
|
||||
void Song::set_unavailable(bool v) { d->unavailable_ = v; }
|
||||
void Song::set_filetype(const FileType v) { d->filetype_ = v; }
|
||||
void Song::set_filesize(const qint64 v) { d->filesize_ = v; }
|
||||
void Song::set_mtime(const qint64 v) { d->mtime_ = v; }
|
||||
void Song::set_ctime(const qint64 v) { d->ctime_ = v; }
|
||||
void Song::set_unavailable(const bool v) { d->unavailable_ = v; }
|
||||
|
||||
void Song::set_fingerprint(const QString &v) { d->fingerprint_ = v; }
|
||||
|
||||
void Song::set_playcount(int v) { d->playcount_ = v; }
|
||||
void Song::set_skipcount(int v) { d->skipcount_ = v; }
|
||||
void Song::set_lastplayed(qint64 v) { d->lastplayed_ = v; }
|
||||
void Song::set_lastseen(qint64 v) { d->lastseen_ = v; }
|
||||
void Song::set_playcount(const uint v) { d->playcount_ = v; }
|
||||
void Song::set_skipcount(const uint v) { d->skipcount_ = v; }
|
||||
void Song::set_lastplayed(const qint64 v) { d->lastplayed_ = v; }
|
||||
void Song::set_lastseen(const qint64 v) { d->lastseen_ = v; }
|
||||
|
||||
void Song::set_compilation_detected(bool v) { d->compilation_detected_ = v; }
|
||||
void Song::set_compilation_on(bool v) { d->compilation_on_ = v; }
|
||||
void Song::set_compilation_off(bool v) { d->compilation_off_ = v; }
|
||||
void Song::set_compilation_detected(const bool v) { d->compilation_detected_ = v; }
|
||||
void Song::set_compilation_on(const bool v) { d->compilation_on_ = v; }
|
||||
void Song::set_compilation_off(const bool v) { d->compilation_off_ = v; }
|
||||
|
||||
void Song::set_art_automatic(const QUrl &v) { d->art_automatic_ = v; }
|
||||
void Song::set_art_manual(const QUrl &v) { d->art_manual_ = v; }
|
||||
void Song::set_cue_path(const QString &v) { d->cue_path_ = v; }
|
||||
|
||||
void Song::set_rating(double v) { d->rating_ = v; }
|
||||
void Song::set_rating(const float v) { d->rating_ = v; }
|
||||
|
||||
void Song::set_stream_url(const QUrl &v) { d->stream_url_ = v; }
|
||||
void Song::set_image(const QImage &i) { d->image_ = i; }
|
||||
@@ -846,11 +847,11 @@ void Song::InitFromProtobuf(const spb::tagreader::SongMetadata &pb) {
|
||||
d->grouping_ = QStringFromStdString(pb.grouping());
|
||||
d->comment_ = QStringFromStdString(pb.comment());
|
||||
d->lyrics_ = QStringFromStdString(pb.lyrics());
|
||||
set_length_nanosec(pb.length_nanosec());
|
||||
set_length_nanosec(static_cast<qint64>(pb.length_nanosec()));
|
||||
d->bitrate_ = pb.bitrate();
|
||||
d->samplerate_ = pb.samplerate();
|
||||
d->bitdepth_ = pb.bitdepth();
|
||||
set_url(QUrl::fromEncoded(QByteArray(pb.url().data(), pb.url().size())));
|
||||
set_url(QUrl::fromEncoded(QByteArray(pb.url().data(), static_cast<qint64>(pb.url().size()))));
|
||||
d->basefilename_ = QStringFromStdString(pb.basefilename());
|
||||
d->filetype_ = static_cast<FileType>(pb.filetype());
|
||||
d->filesize_ = pb.filesize();
|
||||
@@ -859,17 +860,21 @@ void Song::InitFromProtobuf(const spb::tagreader::SongMetadata &pb) {
|
||||
d->skipcount_ = pb.skipcount();
|
||||
d->lastplayed_ = pb.lastplayed();
|
||||
d->lastseen_ = pb.lastseen();
|
||||
d->suspicious_tags_ = pb.suspicious_tags();
|
||||
|
||||
if (pb.has_playcount()) {
|
||||
d->playcount_ = pb.playcount();
|
||||
}
|
||||
if (pb.has_rating()) {
|
||||
d->rating_ = pb.rating();
|
||||
}
|
||||
|
||||
if (pb.has_art_automatic()) {
|
||||
QByteArray art_automatic(pb.art_automatic().data(), pb.art_automatic().size());
|
||||
QByteArray art_automatic(pb.art_automatic().data(), static_cast<qint64>(pb.art_automatic().size()));
|
||||
if (!art_automatic.isEmpty()) set_art_automatic(QUrl::fromLocalFile(art_automatic));
|
||||
}
|
||||
|
||||
d->suspicious_tags_ = pb.suspicious_tags();
|
||||
|
||||
InitArtManual();
|
||||
|
||||
}
|
||||
@@ -884,233 +889,108 @@ void Song::ToProtobuf(spb::tagreader::SongMetadata *pb) const {
|
||||
pb->set_album(DataCommaSizeFromQString(d->album_));
|
||||
pb->set_artist(DataCommaSizeFromQString(d->artist_));
|
||||
pb->set_albumartist(DataCommaSizeFromQString(d->albumartist_));
|
||||
pb->set_composer(DataCommaSizeFromQString(d->composer_));
|
||||
pb->set_performer(DataCommaSizeFromQString(d->performer_));
|
||||
pb->set_grouping(DataCommaSizeFromQString(d->grouping_));
|
||||
pb->set_lyrics(DataCommaSizeFromQString(d->lyrics_));
|
||||
pb->set_track(d->track_);
|
||||
pb->set_disc(d->disc_);
|
||||
pb->set_year(d->year_);
|
||||
pb->set_originalyear(d->originalyear_);
|
||||
pb->set_genre(DataCommaSizeFromQString(d->genre_));
|
||||
pb->set_comment(DataCommaSizeFromQString(d->comment_));
|
||||
pb->set_compilation(d->compilation_);
|
||||
pb->set_playcount(d->playcount_);
|
||||
pb->set_skipcount(d->skipcount_);
|
||||
pb->set_lastplayed(d->lastplayed_);
|
||||
pb->set_lastseen(d->lastseen_);
|
||||
pb->set_composer(DataCommaSizeFromQString(d->composer_));
|
||||
pb->set_performer(DataCommaSizeFromQString(d->performer_));
|
||||
pb->set_grouping(DataCommaSizeFromQString(d->grouping_));
|
||||
pb->set_comment(DataCommaSizeFromQString(d->comment_));
|
||||
pb->set_lyrics(DataCommaSizeFromQString(d->lyrics_));
|
||||
pb->set_length_nanosec(length_nanosec());
|
||||
pb->set_bitrate(d->bitrate_);
|
||||
pb->set_samplerate(d->samplerate_);
|
||||
pb->set_bitdepth(d->bitdepth_);
|
||||
pb->set_url(url.constData(), url.size());
|
||||
pb->set_basefilename(DataCommaSizeFromQString(d->basefilename_));
|
||||
pb->set_filetype(static_cast<spb::tagreader::SongMetadata_FileType>(d->filetype_));
|
||||
pb->set_filesize(d->filesize_);
|
||||
pb->set_mtime(d->mtime_);
|
||||
pb->set_ctime(d->ctime_);
|
||||
pb->set_filesize(d->filesize_);
|
||||
pb->set_suspicious_tags(d->suspicious_tags_);
|
||||
pb->set_playcount(d->playcount_);
|
||||
pb->set_skipcount(d->skipcount_);
|
||||
pb->set_lastplayed(d->lastplayed_);
|
||||
pb->set_lastseen(d->lastseen_);
|
||||
pb->set_art_automatic(art_automatic.constData(), art_automatic.size());
|
||||
pb->set_filetype(static_cast<spb::tagreader::SongMetadata_FileType>(d->filetype_));
|
||||
pb->set_rating(d->rating_);
|
||||
pb->set_suspicious_tags(d->suspicious_tags_);
|
||||
|
||||
}
|
||||
|
||||
#define tostr(n) (q.value(n).isNull() ? QString() : q.value(n).toString())
|
||||
#define toint(n) (q.value(n).isNull() ? -1 : q.value(n).toInt())
|
||||
#define tolonglong(n) (q.value(n).isNull() ? -1 : q.value(n).toLongLong())
|
||||
#define todouble(n) (q.value(n).isNull() ? -1 : q.value(n).toDouble())
|
||||
void Song::InitFromQuery(const SqlRow &q, const bool reliable_metadata) {
|
||||
|
||||
void Song::InitFromQuery(const SqlRow &q, bool reliable_metadata, int col) {
|
||||
d->id_ = q.value("rowid").isNull() ? -1 : q.value("rowid").toInt();
|
||||
|
||||
//qLog(Debug) << "Song::kColumns.size():" << Song::kColumns.size() << "q.columns_.size():" << q.columns_.size() << "col:" << col;
|
||||
|
||||
int x = col;
|
||||
d->id_ = toint(col);
|
||||
|
||||
for (int i = 0; i < Song::kColumns.size(); i++) {
|
||||
x++;
|
||||
|
||||
if (x >= q.columns_.size()) {
|
||||
qLog(Error) << "Skipping" << Song::kColumns.value(i);
|
||||
break;
|
||||
set_title(q.ValueToString("title"));
|
||||
set_album(q.ValueToString("album"));
|
||||
set_artist(q.ValueToString("artist"));
|
||||
set_albumartist(q.ValueToString("albumartist"));
|
||||
d->track_ = q.ValueToInt("track");
|
||||
d->disc_ = q.ValueToInt("disc");
|
||||
d->year_ = q.ValueToInt("year");
|
||||
d->originalyear_ = q.ValueToInt("originalyear");
|
||||
d->genre_ = q.ValueToString("genre");
|
||||
d->compilation_ = q.value("compilation").toBool();
|
||||
d->composer_ = q.ValueToString("composer");
|
||||
d->performer_ = q.ValueToString("performer");
|
||||
d->grouping_ = q.ValueToString("grouping");
|
||||
d->comment_ = q.ValueToString("comment");
|
||||
d->lyrics_ = q.ValueToString("lyrics");
|
||||
d->artist_id_ = q.ValueToString("artist_id");
|
||||
d->album_id_ = q.ValueToString("album_id");
|
||||
d->song_id_ = q.ValueToString("song_id");
|
||||
d->beginning_ = q.value("beginning").isNull() ? 0 : q.value("beginning").toLongLong();
|
||||
set_length_nanosec(q.ValueToLongLong("length"));
|
||||
d->bitrate_ = q.ValueToInt("bitrate");
|
||||
d->samplerate_ = q.ValueToInt("samplerate");
|
||||
d->bitdepth_ = q.ValueToInt("bitdepth");
|
||||
d->source_ = Source(q.value("source").isNull() ? 0 : q.value("source").toInt());
|
||||
d->directory_id_ = q.ValueToInt("directory_id");
|
||||
set_url(QUrl::fromEncoded(q.ValueToString("url").toUtf8()));
|
||||
d->basefilename_ = QFileInfo(d->url_.toLocalFile()).fileName();
|
||||
d->filetype_ = FileType(q.value("filetype").isNull() ? 0 : q.value("filetype").toInt());
|
||||
d->filesize_ = q.ValueToLongLong("filesize");
|
||||
d->mtime_ = q.ValueToLongLong("mtime");
|
||||
d->ctime_ = q.ValueToLongLong("ctime");
|
||||
d->unavailable_ = q.value("unavailable").toBool();
|
||||
d->fingerprint_ = q.ValueToString("fingerprint");
|
||||
d->playcount_ = q.ValueToUInt("playcount");
|
||||
d->skipcount_ = q.ValueToUInt("skipcount");
|
||||
d->lastplayed_ = q.ValueToLongLong("lastplayed");
|
||||
d->lastseen_ = q.ValueToLongLong("lastseen");
|
||||
d->compilation_detected_ = q.ValueToBool("compilation_detected");
|
||||
d->compilation_on_ = q.ValueToBool("compilation_on");
|
||||
d->compilation_off_ = q.ValueToBool("compilation_off");
|
||||
QString art_automatic = q.ValueToString("art_automatic");
|
||||
if (!art_automatic.isEmpty()) {
|
||||
if (art_automatic.contains(QRegularExpression("..+:.*"))) {
|
||||
set_art_automatic(QUrl::fromEncoded(art_automatic.toUtf8()));
|
||||
}
|
||||
|
||||
//qLog(Debug) << "Index:" << i << x << Song::kColumns.value(i) << q.value(x).toString();
|
||||
|
||||
if (Song::kColumns.value(i) == "title") {
|
||||
set_title(tostr(x));
|
||||
}
|
||||
else if (Song::kColumns.value(i) == "album") {
|
||||
set_album(tostr(x));
|
||||
}
|
||||
else if (Song::kColumns.value(i) == "artist") {
|
||||
set_artist(tostr(x));
|
||||
}
|
||||
else if (Song::kColumns.value(i) == "albumartist") {
|
||||
set_albumartist(tostr(x));
|
||||
}
|
||||
else if (Song::kColumns.value(i) == "track") {
|
||||
d->track_ = toint(x);
|
||||
}
|
||||
else if (Song::kColumns.value(i) == "disc") {
|
||||
d->disc_ = toint(x);
|
||||
}
|
||||
else if (Song::kColumns.value(i) == "year") {
|
||||
d->year_ = toint(x);
|
||||
}
|
||||
else if (Song::kColumns.value(i) == "originalyear") {
|
||||
d->originalyear_ = toint(x);
|
||||
}
|
||||
else if (Song::kColumns.value(i) == "genre") {
|
||||
d->genre_ = tostr(x);
|
||||
}
|
||||
else if (Song::kColumns.value(i) == "compilation") {
|
||||
d->compilation_ = q.value(x).toBool();
|
||||
}
|
||||
else if (Song::kColumns.value(i) == "composer") {
|
||||
d->composer_ = tostr(x);
|
||||
}
|
||||
else if (Song::kColumns.value(i) == "performer") {
|
||||
d->performer_ = tostr(x);
|
||||
}
|
||||
else if (Song::kColumns.value(i) == "grouping") {
|
||||
d->grouping_ = tostr(x);
|
||||
}
|
||||
else if (Song::kColumns.value(i) == "comment") {
|
||||
d->comment_ = tostr(x);
|
||||
}
|
||||
else if (Song::kColumns.value(i) == "lyrics") {
|
||||
d->lyrics_ = tostr(x);
|
||||
}
|
||||
|
||||
else if (Song::kColumns.value(i) == "artist_id") {
|
||||
d->artist_id_ = tostr(x);
|
||||
}
|
||||
else if (Song::kColumns.value(i) == "album_id") {
|
||||
d->album_id_ = tostr(x);
|
||||
}
|
||||
else if (Song::kColumns.value(i) == "song_id") {
|
||||
d->song_id_ = tostr(x);
|
||||
}
|
||||
|
||||
else if (Song::kColumns.value(i) == "beginning") {
|
||||
d->beginning_ = q.value(x).isNull() ? 0 : q.value(x).toLongLong();
|
||||
}
|
||||
else if (Song::kColumns.value(i) == "length") {
|
||||
set_length_nanosec(tolonglong(x));
|
||||
}
|
||||
|
||||
else if (Song::kColumns.value(i) == "bitrate") {
|
||||
d->bitrate_ = toint(x);
|
||||
}
|
||||
else if (Song::kColumns.value(i) == "samplerate") {
|
||||
d->samplerate_ = toint(x);
|
||||
}
|
||||
else if (Song::kColumns.value(i) == "bitdepth") {
|
||||
d->bitdepth_ = toint(x);
|
||||
}
|
||||
|
||||
else if (Song::kColumns.value(i) == "source") {
|
||||
d->source_ = Source(q.value(x).toInt());
|
||||
}
|
||||
else if (Song::kColumns.value(i) == "directory_id") {
|
||||
d->directory_id_ = toint(x);
|
||||
}
|
||||
else if (Song::kColumns.value(i) == "url") {
|
||||
set_url(QUrl::fromEncoded(tostr(x).toUtf8()));
|
||||
d->basefilename_ = QFileInfo(d->url_.toLocalFile()).fileName();
|
||||
}
|
||||
else if (Song::kColumns.value(i) == "filetype") {
|
||||
d->filetype_ = FileType(q.value(x).toInt());
|
||||
}
|
||||
else if (Song::kColumns.value(i) == "filesize") {
|
||||
d->filesize_ = toint(x);
|
||||
}
|
||||
else if (Song::kColumns.value(i) == "mtime") {
|
||||
d->mtime_ = tolonglong(x);
|
||||
}
|
||||
else if (Song::kColumns.value(i) == "ctime") {
|
||||
d->ctime_ = tolonglong(x);
|
||||
}
|
||||
else if (Song::kColumns.value(i) == "unavailable") {
|
||||
d->unavailable_ = q.value(x).toBool();
|
||||
}
|
||||
|
||||
else if (Song::kColumns.value(i) == "fingerprint") {
|
||||
d->fingerprint_ = tostr(x);
|
||||
}
|
||||
|
||||
else if (Song::kColumns.value(i) == "playcount") {
|
||||
d->playcount_ = q.value(x).isNull() ? 0 : q.value(x).toInt();
|
||||
}
|
||||
else if (Song::kColumns.value(i) == "skipcount") {
|
||||
d->skipcount_ = q.value(x).isNull() ? 0 : q.value(x).toInt();
|
||||
}
|
||||
else if (Song::kColumns.value(i) == "lastplayed") {
|
||||
d->lastplayed_ = tolonglong(x);
|
||||
}
|
||||
else if (Song::kColumns.value(i) == "lastseen") {
|
||||
d->lastseen_ = tolonglong(x);
|
||||
}
|
||||
|
||||
else if (Song::kColumns.value(i) == "compilation_detected") {
|
||||
d->compilation_detected_ = q.value(x).toBool();
|
||||
}
|
||||
else if (Song::kColumns.value(i) == "compilation_on") {
|
||||
d->compilation_on_ = q.value(x).toBool();
|
||||
}
|
||||
else if (Song::kColumns.value(i) == "compilation_off") {
|
||||
d->compilation_off_ = q.value(x).toBool();
|
||||
}
|
||||
else if (Song::kColumns.value(i) == "compilation_effective") {
|
||||
}
|
||||
|
||||
else if (Song::kColumns.value(i) == "art_automatic") {
|
||||
QString art_automatic = tostr(x);
|
||||
if (art_automatic.contains(QRegularExpression("..+:.*"))) {
|
||||
set_art_automatic(QUrl::fromEncoded(art_automatic.toUtf8()));
|
||||
}
|
||||
else {
|
||||
set_art_automatic(QUrl::fromLocalFile(art_automatic));
|
||||
}
|
||||
}
|
||||
else if (Song::kColumns.value(i) == "art_manual") {
|
||||
QString art_manual = tostr(x);
|
||||
if (art_manual.contains(QRegularExpression("..+:.*"))) {
|
||||
set_art_manual(QUrl::fromEncoded(art_manual.toUtf8()));
|
||||
}
|
||||
else {
|
||||
set_art_manual(QUrl::fromLocalFile(art_manual));
|
||||
}
|
||||
}
|
||||
|
||||
else if (Song::kColumns.value(i) == "effective_albumartist") {
|
||||
}
|
||||
else if (Song::kColumns.value(i) == "effective_originalyear") {
|
||||
}
|
||||
|
||||
else if (Song::kColumns.value(i) == "cue_path") {
|
||||
d->cue_path_ = tostr(x);
|
||||
}
|
||||
|
||||
else if (Song::kColumns.value(i) == "rating") {
|
||||
d->rating_ = todouble(x);
|
||||
}
|
||||
|
||||
else {
|
||||
qLog(Error) << "Forgot to handle" << Song::kColumns.value(i);
|
||||
set_art_automatic(QUrl::fromLocalFile(art_automatic));
|
||||
}
|
||||
}
|
||||
QString art_manual = q.ValueToString("art_manual");
|
||||
if (!art_manual.isEmpty()) {
|
||||
if (art_manual.contains(QRegularExpression("..+:.*"))) {
|
||||
set_art_manual(QUrl::fromEncoded(art_manual.toUtf8()));
|
||||
}
|
||||
else {
|
||||
set_art_manual(QUrl::fromLocalFile(art_manual));
|
||||
}
|
||||
}
|
||||
|
||||
d->cue_path_ = q.ValueToString("cue_path");
|
||||
d->rating_ = q.ValueToFloat("rating");
|
||||
|
||||
d->valid_ = true;
|
||||
d->init_from_file_ = reliable_metadata;
|
||||
|
||||
InitArtManual();
|
||||
|
||||
#undef tostr
|
||||
#undef toint
|
||||
#undef tolonglong
|
||||
#undef todouble
|
||||
|
||||
}
|
||||
|
||||
void Song::InitFromFilePartial(const QString &filename, const QFileInfo &fileinfo) {
|
||||
@@ -1195,8 +1075,8 @@ void Song::InitFromItdb(Itdb_Track *track, const QString &prefix) {
|
||||
d->mtime_ = track->time_modified;
|
||||
d->ctime_ = track->time_added;
|
||||
|
||||
d->playcount_ = track->playcount;
|
||||
d->skipcount_ = track->skipcount;
|
||||
d->playcount_ = static_cast<int>(track->playcount);
|
||||
d->skipcount_ = static_cast<int>(track->skipcount);
|
||||
d->lastplayed_ = track->time_played;
|
||||
|
||||
if (itdb_track_has_thumbnails(track) && !d->artist_.isEmpty() && !d->title_.isEmpty()) {
|
||||
@@ -1231,7 +1111,7 @@ void Song::ToItdb(Itdb_Track *track) const {
|
||||
track->grouping = strdup(d->grouping_.toUtf8().constData());
|
||||
track->comment = strdup(d->comment_.toUtf8().constData());
|
||||
|
||||
track->tracklen = length_nanosec() / kNsecPerMsec;
|
||||
track->tracklen = static_cast<int>(length_nanosec() / kNsecPerMsec);
|
||||
|
||||
track->bitrate = d->bitrate_;
|
||||
track->samplerate = d->samplerate_;
|
||||
@@ -1239,7 +1119,7 @@ void Song::ToItdb(Itdb_Track *track) const {
|
||||
track->type1 = (d->filetype_ == FileType_MPEG ? 1 : 0);
|
||||
track->type2 = (d->filetype_ == FileType_MPEG ? 1 : 0);
|
||||
track->mediatype = 1; // Audio
|
||||
track->size = d->filesize_;
|
||||
track->size = static_cast<uint>(d->filesize_);
|
||||
track->time_modified = d->mtime_;
|
||||
track->time_added = d->ctime_;
|
||||
|
||||
@@ -1265,15 +1145,15 @@ void Song::InitFromMTP(const LIBMTP_track_t *track, const QString &host) {
|
||||
|
||||
d->url_ = QUrl(QString("mtp://%1/%2").arg(host, QString::number(track->item_id)));
|
||||
d->basefilename_ = QString::number(track->item_id);
|
||||
d->filesize_ = track->filesize;
|
||||
d->filesize_ = static_cast<qint64>(track->filesize);
|
||||
d->mtime_ = track->modificationdate;
|
||||
d->ctime_ = track->modificationdate;
|
||||
|
||||
set_length_nanosec(track->duration * kNsecPerMsec);
|
||||
|
||||
d->samplerate_ = track->samplerate;
|
||||
d->samplerate_ = static_cast<int>(track->samplerate);
|
||||
d->bitdepth_ = 0;
|
||||
d->bitrate_ = track->bitrate;
|
||||
d->bitrate_ = static_cast<int>(track->bitrate);
|
||||
|
||||
d->playcount_ = track->usecount;
|
||||
|
||||
@@ -1314,7 +1194,7 @@ void Song::ToMTP(LIBMTP_track_t *track) const {
|
||||
|
||||
track->filename = strdup(d->basefilename_.toUtf8().constData());
|
||||
|
||||
track->filesize = d->filesize_;
|
||||
track->filesize = static_cast<quint64>(d->filesize_);
|
||||
track->modificationdate = d->mtime_;
|
||||
|
||||
track->duration = length_nanosec() / kNsecPerMsec;
|
||||
@@ -1398,73 +1278,65 @@ bool Song::MergeFromSimpleMetaBundle(const Engine::SimpleMetaBundle &bundle) {
|
||||
|
||||
void Song::BindToQuery(SqlQuery *query) const {
|
||||
|
||||
#define strval(x) ((x).isNull() ? "" : (x))
|
||||
#define intval(x) ((x) <= 0 ? -1 : (x))
|
||||
#define notnullintval(x) ((x) == -1 ? QVariant() : (x))
|
||||
|
||||
// Remember to bind these in the same order as kBindSpec
|
||||
|
||||
query->BindValue(":title", strval(d->title_));
|
||||
query->BindValue(":album", strval(d->album_));
|
||||
query->BindValue(":artist", strval(d->artist_));
|
||||
query->BindValue(":albumartist", strval(d->albumartist_));
|
||||
query->BindValue(":track", intval(d->track_));
|
||||
query->BindValue(":disc", intval(d->disc_));
|
||||
query->BindValue(":year", intval(d->year_));
|
||||
query->BindValue(":originalyear", intval(d->originalyear_));
|
||||
query->BindValue(":genre", strval(d->genre_));
|
||||
query->BindValue(":compilation", d->compilation_ ? 1 : 0);
|
||||
query->BindValue(":composer", strval(d->composer_));
|
||||
query->BindValue(":performer", strval(d->performer_));
|
||||
query->BindValue(":grouping", strval(d->grouping_));
|
||||
query->BindValue(":comment", strval(d->comment_));
|
||||
query->BindValue(":lyrics", strval(d->lyrics_));
|
||||
query->BindStringValue(":title", d->title_);
|
||||
query->BindStringValue(":album", d->album_);
|
||||
query->BindStringValue(":artist", d->artist_);
|
||||
query->BindStringValue(":albumartist", d->albumartist_);
|
||||
query->BindIntValue(":track", d->track_);
|
||||
query->BindIntValue(":disc", d->disc_);
|
||||
query->BindIntValue(":year", d->year_);
|
||||
query->BindIntValue(":originalyear", d->originalyear_);
|
||||
query->BindStringValue(":genre", d->genre_);
|
||||
query->BindBoolValue(":compilation", d->compilation_);
|
||||
query->BindStringValue(":composer", d->composer_);
|
||||
query->BindStringValue(":performer", d->performer_);
|
||||
query->BindStringValue(":grouping", d->grouping_);
|
||||
query->BindStringValue(":comment", d->comment_);
|
||||
query->BindStringValue(":lyrics", d->lyrics_);
|
||||
|
||||
query->BindValue(":artist_id", strval(d->artist_id_));
|
||||
query->BindValue(":album_id", strval(d->album_id_));
|
||||
query->BindValue(":song_id", strval(d->song_id_));
|
||||
query->BindStringValue(":artist_id", d->artist_id_);
|
||||
query->BindStringValue(":album_id", d->album_id_);
|
||||
query->BindStringValue(":song_id", d->song_id_);
|
||||
|
||||
query->BindValue(":beginning", d->beginning_);
|
||||
query->BindValue(":length", intval(length_nanosec()));
|
||||
query->BindLongLongValue(":length", length_nanosec());
|
||||
|
||||
query->BindValue(":bitrate", intval(d->bitrate_));
|
||||
query->BindValue(":samplerate", intval(d->samplerate_));
|
||||
query->BindValue(":bitdepth", intval(d->bitdepth_));
|
||||
query->BindIntValue(":bitrate", d->bitrate_);
|
||||
query->BindIntValue(":samplerate", d->samplerate_);
|
||||
query->BindIntValue(":bitdepth", d->bitdepth_);
|
||||
|
||||
query->BindValue(":source", d->source_);
|
||||
query->BindValue(":directory_id", notnullintval(d->directory_id_));
|
||||
query->BindValue(":url", d->url_.toString(QUrl::FullyEncoded));
|
||||
query->BindNotNullIntValue(":directory_id", d->directory_id_);
|
||||
query->BindUrlValue(":url", d->url_);
|
||||
query->BindValue(":filetype", d->filetype_);
|
||||
query->BindValue(":filesize", notnullintval(d->filesize_));
|
||||
query->BindValue(":mtime", notnullintval(d->mtime_));
|
||||
query->BindValue(":ctime", notnullintval(d->ctime_));
|
||||
query->BindValue(":unavailable", d->unavailable_ ? 1 : 0);
|
||||
query->BindNotNullLongLongValue(":filesize", d->filesize_);
|
||||
query->BindNotNullLongLongValue(":mtime", d->mtime_);
|
||||
query->BindNotNullLongLongValue(":ctime", d->ctime_);
|
||||
query->BindBoolValue(":unavailable", d->unavailable_);
|
||||
|
||||
query->BindValue(":fingerprint", strval(d->fingerprint_));
|
||||
query->BindStringValue(":fingerprint", d->fingerprint_);
|
||||
|
||||
query->BindValue(":playcount", d->playcount_);
|
||||
query->BindValue(":skipcount", d->skipcount_);
|
||||
query->BindValue(":lastplayed", intval(d->lastplayed_));
|
||||
query->BindValue(":lastseen", intval(d->lastseen_));
|
||||
query->BindLongLongValue(":lastplayed", d->lastplayed_);
|
||||
query->BindLongLongValue(":lastseen", d->lastseen_);
|
||||
|
||||
query->BindValue(":compilation_detected", d->compilation_detected_ ? 1 : 0);
|
||||
query->BindValue(":compilation_on", d->compilation_on_ ? 1 : 0);
|
||||
query->BindValue(":compilation_off", d->compilation_off_ ? 1 : 0);
|
||||
query->BindValue(":compilation_effective", is_compilation() ? 1 : 0);
|
||||
query->BindBoolValue(":compilation_detected", d->compilation_detected_);
|
||||
query->BindBoolValue(":compilation_on", d->compilation_on_);
|
||||
query->BindBoolValue(":compilation_off", d->compilation_off_);
|
||||
query->BindBoolValue(":compilation_effective", is_compilation());
|
||||
|
||||
query->BindValue(":art_automatic", d->art_automatic_.isValid() ? d->art_automatic_.toString(QUrl::FullyEncoded) : "");
|
||||
query->BindValue(":art_manual", d->art_manual_.isValid() ? d->art_manual_.toString(QUrl::FullyEncoded) : "");
|
||||
query->BindUrlValue(":art_automatic", d->art_automatic_);
|
||||
query->BindUrlValue(":art_manual", d->art_manual_);
|
||||
|
||||
query->BindValue(":effective_albumartist", strval(effective_albumartist()));
|
||||
query->BindValue(":effective_originalyear", intval(effective_originalyear()));
|
||||
query->BindStringValue(":effective_albumartist", effective_albumartist());
|
||||
query->BindIntValue(":effective_originalyear", effective_originalyear());
|
||||
|
||||
query->BindValue(":cue_path", d->cue_path_);
|
||||
|
||||
query->BindValue(":rating", intval(d->rating_));
|
||||
|
||||
#undef intval
|
||||
#undef notnullintval
|
||||
#undef strval
|
||||
query->BindFloatValue(":rating", d->rating_);
|
||||
|
||||
}
|
||||
|
||||
@@ -1550,7 +1422,7 @@ QString Song::SampleRateBitDepthToText() const {
|
||||
|
||||
QString Song::PrettyRating() const {
|
||||
|
||||
double rating = d->rating_;
|
||||
float rating = d->rating_;
|
||||
|
||||
if (rating == -1.0F) return "0";
|
||||
|
||||
@@ -1583,7 +1455,9 @@ bool Song::IsMetadataEqual(const Song &other) const {
|
||||
d->bitrate_ == other.d->bitrate_ &&
|
||||
d->samplerate_ == other.d->samplerate_ &&
|
||||
d->bitdepth_ == other.d->bitdepth_ &&
|
||||
d->cue_path_ == other.d->cue_path_;
|
||||
d->cue_path_ == other.d->cue_path_ &&
|
||||
d->playcount_ == other.d->playcount_ &&
|
||||
d->rating_ == other.d->rating_;
|
||||
}
|
||||
|
||||
bool Song::IsMetadataAndMoreEqual(const Song &other) const {
|
||||
@@ -1596,12 +1470,7 @@ bool Song::IsMetadataAndMoreEqual(const Song &other) const {
|
||||
}
|
||||
|
||||
bool Song::IsEditable() const {
|
||||
|
||||
return d->valid_ &&
|
||||
!d->url_.isEmpty() &&
|
||||
(d->url_.isLocalFile() || d->source_ == Source_Stream) &&
|
||||
!has_cue();
|
||||
|
||||
return d->valid_ && d->url_.isValid() && (d->url_.isLocalFile() || d->source_ == Source_Stream) && !has_cue();
|
||||
}
|
||||
|
||||
bool Song::operator==(const Song &other) const {
|
||||
@@ -1661,7 +1530,7 @@ void Song::ToXesam(QVariantMap *map) const {
|
||||
AddMetadataAsList("xesam:artist", artist(), map);
|
||||
AddMetadata("xesam:album", album(), map);
|
||||
AddMetadataAsList("xesam:albumArtist", albumartist(), map);
|
||||
AddMetadata("mpris:length", length_nanosec() / kNsecPerUsec, map);
|
||||
AddMetadata("mpris:length", (length_nanosec() / kNsecPerUsec), map);
|
||||
AddMetadata("xesam:trackNumber", track(), map);
|
||||
AddMetadataAsList("xesam:genre", genre(), map);
|
||||
AddMetadata("xesam:discNumber", disc(), map);
|
||||
@@ -1669,18 +1538,24 @@ void Song::ToXesam(QVariantMap *map) const {
|
||||
AddMetadata("xesam:contentCreated", AsMPRISDateTimeType(ctime()), map);
|
||||
AddMetadata("xesam:lastUsed", AsMPRISDateTimeType(lastplayed()), map);
|
||||
AddMetadataAsList("xesam:composer", composer(), map);
|
||||
AddMetadata("xesam:useCount", playcount(), map);
|
||||
AddMetadata("xesam:useCount", static_cast<int>(playcount()), map);
|
||||
|
||||
}
|
||||
|
||||
void Song::MergeUserSetData(const Song &other) {
|
||||
void Song::MergeUserSetData(const Song &other, const bool merge_rating) {
|
||||
|
||||
if (other.playcount() > 0) {
|
||||
set_playcount(other.playcount());
|
||||
}
|
||||
|
||||
if (merge_rating && other.rating() > 0.0F) {
|
||||
set_rating(other.rating());
|
||||
}
|
||||
|
||||
set_playcount(other.playcount());
|
||||
set_skipcount(other.skipcount());
|
||||
set_lastplayed(other.lastplayed());
|
||||
set_art_manual(other.art_manual());
|
||||
set_compilation_on(other.compilation_on());
|
||||
set_compilation_off(other.compilation_off());
|
||||
set_rating(other.rating());
|
||||
|
||||
}
|
||||
|
||||
@@ -168,7 +168,7 @@ class Song {
|
||||
void Init(const QString &title, const QString &artist, const QString &album, qint64 length_nanosec);
|
||||
void Init(const QString &title, const QString &artist, const QString &album, qint64 beginning, qint64 end);
|
||||
void InitFromProtobuf(const spb::tagreader::SongMetadata &pb);
|
||||
void InitFromQuery(const SqlRow &query, bool reliable_metadata, int col = 0);
|
||||
void InitFromQuery(const SqlRow &query, const bool reliable_metadata);
|
||||
void InitFromFilePartial(const QString &filename, const QFileInfo &fileinfo);
|
||||
void InitArtManual();
|
||||
void InitArtAutomatic();
|
||||
@@ -187,7 +187,7 @@ class Song {
|
||||
|
||||
// Copies important statistics from the other song to this one, overwriting any data that already exists.
|
||||
// Useful when you want updated tags from disk but you want to keep user stats.
|
||||
void MergeUserSetData(const Song &other);
|
||||
void MergeUserSetData(const Song &other, const bool merge_rating);
|
||||
|
||||
// Save
|
||||
void BindToQuery(SqlQuery *query) const;
|
||||
@@ -237,14 +237,14 @@ class Song {
|
||||
const QUrl &url() const;
|
||||
const QString &basefilename() const;
|
||||
FileType filetype() const;
|
||||
int filesize() const;
|
||||
qint64 filesize() const;
|
||||
qint64 mtime() const;
|
||||
qint64 ctime() const;
|
||||
|
||||
QString fingerprint() const;
|
||||
|
||||
int playcount() const;
|
||||
int skipcount() const;
|
||||
uint playcount() const;
|
||||
uint skipcount() const;
|
||||
qint64 lastplayed() const;
|
||||
qint64 lastseen() const;
|
||||
|
||||
@@ -258,7 +258,7 @@ class Song {
|
||||
const QString &cue_path() const;
|
||||
bool has_cue() const;
|
||||
|
||||
double rating() const;
|
||||
float rating() const;
|
||||
|
||||
const QString &effective_album() const;
|
||||
int effective_originalyear() const;
|
||||
@@ -318,16 +318,16 @@ class Song {
|
||||
// Setters
|
||||
bool IsEditable() const;
|
||||
|
||||
void set_id(int id);
|
||||
void set_valid(bool v);
|
||||
void set_id(const int id);
|
||||
void set_valid(const bool v);
|
||||
|
||||
void set_title(const QString &v);
|
||||
void set_album(const QString &v);
|
||||
void set_artist(const QString &v);
|
||||
void set_albumartist(const QString &v);
|
||||
void set_track(int v);
|
||||
void set_disc(int v);
|
||||
void set_year(int v);
|
||||
void set_track(const int v);
|
||||
void set_disc(const int v);
|
||||
void set_year(const int v);
|
||||
void set_originalyear(int v);
|
||||
void set_genre(const QString &v);
|
||||
void set_compilation(bool v);
|
||||
@@ -345,37 +345,37 @@ class Song {
|
||||
void set_end_nanosec(qint64 v);
|
||||
void set_length_nanosec(qint64 v);
|
||||
|
||||
void set_bitrate(int v);
|
||||
void set_samplerate(int v);
|
||||
void set_bitdepth(int v);
|
||||
void set_bitrate(const int v);
|
||||
void set_samplerate(const int v);
|
||||
void set_bitdepth(const int v);
|
||||
|
||||
void set_source(Source v);
|
||||
void set_directory_id(int v);
|
||||
void set_source(const Source v);
|
||||
void set_directory_id(const int v);
|
||||
void set_url(const QUrl &v);
|
||||
void set_basefilename(const QString &v);
|
||||
void set_filetype(FileType v);
|
||||
void set_filesize(int v);
|
||||
void set_mtime(qint64 v);
|
||||
void set_ctime(qint64 v);
|
||||
void set_unavailable(bool v);
|
||||
void set_filetype(const FileType v);
|
||||
void set_filesize(const qint64 v);
|
||||
void set_mtime(const qint64 v);
|
||||
void set_ctime(const qint64 v);
|
||||
void set_unavailable(const bool v);
|
||||
|
||||
void set_fingerprint(const QString &v);
|
||||
|
||||
void set_playcount(int v);
|
||||
void set_skipcount(int v);
|
||||
void set_lastplayed(qint64 v);
|
||||
void set_lastseen(qint64 v);
|
||||
void set_playcount(const uint v);
|
||||
void set_skipcount(const uint v);
|
||||
void set_lastplayed(const qint64 v);
|
||||
void set_lastseen(const qint64 v);
|
||||
|
||||
void set_compilation_detected(bool v);
|
||||
void set_compilation_on(bool v);
|
||||
void set_compilation_off(bool v);
|
||||
void set_compilation_detected(const bool v);
|
||||
void set_compilation_on(const bool v);
|
||||
void set_compilation_off(const bool v);
|
||||
|
||||
void set_art_automatic(const QUrl &v);
|
||||
void set_art_manual(const QUrl &v);
|
||||
|
||||
void set_cue_path(const QString &v);
|
||||
|
||||
void set_rating(const double v);
|
||||
void set_rating(const float v);
|
||||
|
||||
void set_stream_url(const QUrl &v);
|
||||
void set_image(const QImage &i);
|
||||
|
||||
@@ -300,7 +300,7 @@ SongLoader::Result SongLoader::LoadLocalAsync(const QString &filename) {
|
||||
}
|
||||
|
||||
// Check if it's a CUE file
|
||||
QString matching_cue = filename.section('.', 0, -2) + ".cue";
|
||||
QString matching_cue = CueParser::FindCueFilename(filename);
|
||||
if (QFile::exists(matching_cue)) {
|
||||
// It's a CUE - create virtual tracks
|
||||
QFile cue(matching_cue);
|
||||
@@ -552,7 +552,7 @@ GstPadProbeReturn SongLoader::DataReady(GstPad*, GstPadProbeInfo *info, gpointer
|
||||
gst_buffer_map(buffer, &map, GST_MAP_READ);
|
||||
|
||||
// Append the data to the buffer
|
||||
instance->buffer_.append(reinterpret_cast<const char*>(map.data), map.size);
|
||||
instance->buffer_.append(reinterpret_cast<const char*>(map.data), static_cast<qint64>(map.size));
|
||||
qLog(Debug) << "Received total" << instance->buffer_.size() << "bytes";
|
||||
gst_buffer_unmap(buffer, &map);
|
||||
|
||||
@@ -619,7 +619,7 @@ void SongLoader::ErrorMessageReceived(GstMessage *msg) {
|
||||
QString message_str = error->message;
|
||||
|
||||
g_error_free(error);
|
||||
free(debugs);
|
||||
g_free(debugs);
|
||||
|
||||
if (state_ == WaitingForType && message_str == gst_error_get_message(GST_STREAM_ERROR, GST_STREAM_ERROR_TYPE_NOT_FOUND)) {
|
||||
// Don't give up - assume it's a playlist and see if one of our parsers can read it.
|
||||
|
||||
@@ -22,8 +22,9 @@
|
||||
#include <QSqlQuery>
|
||||
#include <QMap>
|
||||
#include <QVariant>
|
||||
#include <QString>
|
||||
#include <QVariantList>
|
||||
#include <QString>
|
||||
#include <QUrl>
|
||||
|
||||
#include "sqlquery.h"
|
||||
|
||||
@@ -37,13 +38,61 @@ void SqlQuery::BindValue(const QString &placeholder, const QVariant &value) {
|
||||
|
||||
}
|
||||
|
||||
void SqlQuery::BindStringValue(const QString &placeholder, const QString &value) {
|
||||
|
||||
BindValue(placeholder, value.isNull() ? "" : value);
|
||||
|
||||
}
|
||||
|
||||
void SqlQuery::BindUrlValue(const QString &placeholder, const QUrl &value) {
|
||||
|
||||
BindValue(placeholder, value.isValid() ? value.toString(QUrl::FullyEncoded) : "");
|
||||
|
||||
}
|
||||
|
||||
void SqlQuery::BindIntValue(const QString &placeholder, const int value) {
|
||||
|
||||
BindValue(placeholder, value <= 0 ? -1 : value);
|
||||
|
||||
}
|
||||
|
||||
void SqlQuery::BindLongLongValue(const QString &placeholder, const qint64 value) {
|
||||
|
||||
BindValue(placeholder, value <= 0 ? -1 : value);
|
||||
|
||||
}
|
||||
|
||||
void SqlQuery::BindFloatValue(const QString &placeholder, const float value) {
|
||||
|
||||
BindValue(placeholder, value <= 0 ? -1 : value);
|
||||
|
||||
}
|
||||
|
||||
void SqlQuery::BindBoolValue(const QString &placeholder, const bool value) {
|
||||
|
||||
BindValue(placeholder, value ? 1 : 0);
|
||||
|
||||
}
|
||||
|
||||
void SqlQuery::BindNotNullIntValue(const QString &placeholder, const int value) {
|
||||
|
||||
BindValue(placeholder, value == -1 ? QVariant() : value);
|
||||
|
||||
}
|
||||
|
||||
void SqlQuery::BindNotNullLongLongValue(const QString &placeholder, const qint64 value) {
|
||||
|
||||
BindValue(placeholder, value == -1 ? QVariant() : value);
|
||||
|
||||
}
|
||||
|
||||
bool SqlQuery::Exec() {
|
||||
|
||||
bool success = exec();
|
||||
last_query_ = executedQuery();
|
||||
|
||||
#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
|
||||
for (QMap<QString, QVariant>::const_iterator it = bound_values_.begin(); it != bound_values_.end(); ++it) {
|
||||
for (QMap<QString, QVariant>::const_iterator it = bound_values_.constBegin(); it != bound_values_.constEnd(); ++it) {
|
||||
last_query_.replace(it.key(), it.value().toString());
|
||||
}
|
||||
bound_values_.clear();
|
||||
|
||||
@@ -31,9 +31,18 @@
|
||||
class SqlQuery : public QSqlQuery {
|
||||
|
||||
public:
|
||||
explicit SqlQuery(QSqlDatabase db) : QSqlQuery(db) {}
|
||||
explicit SqlQuery(const QSqlDatabase &db) : QSqlQuery(db) {}
|
||||
|
||||
void BindValue(const QString &placeholder, const QVariant &value);
|
||||
void BindStringValue(const QString &placeholder, const QString &value);
|
||||
void BindUrlValue(const QString &placeholder, const QUrl &value);
|
||||
void BindIntValue(const QString &placeholder, const int value);
|
||||
void BindLongLongValue(const QString &placeholder, const qint64 value);
|
||||
void BindFloatValue(const QString &placeholder, const float value);
|
||||
void BindBoolValue(const QString &placeholder, const bool value);
|
||||
void BindNotNullIntValue(const QString &placeholder, const int value);
|
||||
void BindNotNullLongLongValue(const QString &placeholder, const qint64 value);
|
||||
|
||||
bool Exec();
|
||||
QString LastQuery() const;
|
||||
|
||||
|
||||
@@ -91,7 +91,7 @@ QColor StyleHelper::notTooBrightHighlightColor() {
|
||||
|
||||
QColor highlightColor = QApplication::palette().highlight().color();
|
||||
if (0.5 * highlightColor.saturationF() + 0.75 - highlightColor.valueF() < 0) {
|
||||
highlightColor.setHsvF(highlightColor.hsvHueF(), 0.1 + highlightColor.saturationF() * 2.0, highlightColor.valueF());
|
||||
highlightColor.setHsvF(highlightColor.hsvHueF(), 0.1F + highlightColor.saturationF() * 2.0F, highlightColor.valueF());
|
||||
}
|
||||
return highlightColor;
|
||||
|
||||
@@ -133,10 +133,10 @@ QColor StyleHelper::highlightColor(bool lightColored) {
|
||||
|
||||
QColor result = baseColor(lightColored);
|
||||
if (lightColored) {
|
||||
result.setHsv(result.hue(), clamp(result.saturation()), clamp(result.value() * 1.06));
|
||||
result.setHsv(result.hue(), clamp(static_cast<float>(result.saturation())), clamp(static_cast<float>(result.value()) * 1.06F));
|
||||
}
|
||||
else {
|
||||
result.setHsv(result.hue(), clamp(result.saturation()), clamp(result.value() * 1.16));
|
||||
result.setHsv(result.hue(), clamp(static_cast<float>(result.saturation())), clamp(static_cast<float>(result.value()) * 1.16F));
|
||||
}
|
||||
|
||||
return result;
|
||||
@@ -146,7 +146,7 @@ QColor StyleHelper::highlightColor(bool lightColored) {
|
||||
QColor StyleHelper::shadowColor(bool lightColored) {
|
||||
|
||||
QColor result = baseColor(lightColored);
|
||||
result.setHsv(result.hue(), clamp(result.saturation() * 1.1), clamp(result.value() * 0.70));
|
||||
result.setHsv(result.hue(), clamp(static_cast<float>(result.saturation()) * 1.1F), clamp(static_cast<float>(result.value()) * 0.70F));
|
||||
return result;
|
||||
|
||||
}
|
||||
@@ -162,7 +162,7 @@ QColor StyleHelper::borderColor(bool lightColored) {
|
||||
QColor StyleHelper::toolBarBorderColor() {
|
||||
|
||||
const QColor base = baseColor();
|
||||
return QColor::fromHsv(base.hue(), base.saturation(), clamp(base.value() * 0.80F));
|
||||
return QColor::fromHsv(base.hue(), base.saturation(), clamp(static_cast<float>(base.value()) * 0.80F));
|
||||
|
||||
}
|
||||
|
||||
@@ -172,7 +172,7 @@ void StyleHelper::setBaseColor(const QColor &newcolor) {
|
||||
m_requestedBaseColor = newcolor;
|
||||
|
||||
QColor color;
|
||||
color.setHsv(newcolor.hue(), newcolor.saturation() * 0.7, 64 + newcolor.value() / 3);
|
||||
color.setHsv(newcolor.hue(), static_cast<int>(static_cast<float>(newcolor.saturation()) * 0.7F), 64 + newcolor.value() / 3);
|
||||
|
||||
if (color.isValid() && color != m_baseColor) {
|
||||
m_baseColor = color;
|
||||
@@ -303,7 +303,7 @@ void StyleHelper::drawArrow(QStyle::PrimitiveElement element, QPainter *painter,
|
||||
QPixmap pixmap;
|
||||
QString pixmapName = QString::asprintf("StyleHelper::drawArrow-%d-%d-%d-%f", element, size, (enabled ? 1 : 0), devicePixelRatio);
|
||||
if (!QPixmapCache::find(pixmapName, &pixmap)) {
|
||||
QImage image(size * devicePixelRatio, size * devicePixelRatio, QImage::Format_ARGB32_Premultiplied);
|
||||
QImage image(size * static_cast<int>(devicePixelRatio), size * static_cast<int>(devicePixelRatio), QImage::Format_ARGB32_Premultiplied);
|
||||
image.fill(Qt::transparent);
|
||||
QPainter p(&image);
|
||||
|
||||
@@ -325,7 +325,7 @@ void StyleHelper::drawArrow(QStyle::PrimitiveElement element, QPainter *painter,
|
||||
drawCommonStyleArrow(image.rect(), m_IconsDisabledColor);
|
||||
}
|
||||
else {
|
||||
drawCommonStyleArrow(image.rect().translated(0, devicePixelRatio), toolBarDropShadowColor());
|
||||
drawCommonStyleArrow(image.rect().translated(0, static_cast<int>(devicePixelRatio)), toolBarDropShadowColor());
|
||||
drawCommonStyleArrow(image.rect(), m_IconsBaseColor);
|
||||
}
|
||||
p.end();
|
||||
@@ -434,7 +434,7 @@ void StyleHelper::tintImage(QImage &img, const QColor &tintColor) {
|
||||
|
||||
if (alpha > 0) {
|
||||
c.toHsl();
|
||||
qreal l = c.lightnessF();
|
||||
float l = c.lightnessF();
|
||||
QColor newColor = QColor::fromHslF(tintColor.hslHueF(), tintColor.hslSaturationF(), l);
|
||||
newColor.setAlpha(alpha);
|
||||
img.setPixel(x, y, newColor.rgba());
|
||||
|
||||
@@ -29,6 +29,7 @@
|
||||
#include <QByteArray>
|
||||
#include <QString>
|
||||
#include <QImage>
|
||||
#include <QSettings>
|
||||
#include <QtDebug>
|
||||
|
||||
#include "core/logging.h"
|
||||
@@ -36,6 +37,7 @@
|
||||
|
||||
#include "song.h"
|
||||
#include "tagreaderclient.h"
|
||||
#include "settings/collectionsettingspage.h"
|
||||
|
||||
const char *TagReaderClient::kWorkerExecutableName = "strawberry-tagreader";
|
||||
TagReaderClient *TagReaderClient::sInstance = nullptr;
|
||||
@@ -45,8 +47,15 @@ TagReaderClient::TagReaderClient(QObject *parent) : QObject(parent), worker_pool
|
||||
sInstance = this;
|
||||
original_thread_ = thread();
|
||||
|
||||
QSettings s;
|
||||
s.beginGroup(CollectionSettingsPage::kSettingsGroup);
|
||||
int workers = s.value("tagreader_workers", qBound(1, QThread::idealThreadCount() / 2, 4)).toInt();
|
||||
s.endGroup();
|
||||
|
||||
qLog(Debug) << "Using" << workers << "tagreader workers.";
|
||||
|
||||
worker_pool_->SetExecutableName(kWorkerExecutableName);
|
||||
worker_pool_->SetWorkerCount(qBound(1, QThread::idealThreadCount() / 2, 4));
|
||||
worker_pool_->SetWorkerCount(workers);
|
||||
QObject::connect(worker_pool_, &WorkerPool<HandlerType>::WorkerFailedToStart, this, &TagReaderClient::WorkerFailedToStart);
|
||||
|
||||
}
|
||||
@@ -69,6 +78,17 @@ void TagReaderClient::WorkerFailedToStart() {
|
||||
qLog(Error) << "The" << kWorkerExecutableName << "executable was not found in the current directory or on the PATH. Strawberry will not be able to read music file tags without it.";
|
||||
}
|
||||
|
||||
TagReaderReply *TagReaderClient::IsMediaFile(const QString &filename) {
|
||||
|
||||
spb::tagreader::Message message;
|
||||
spb::tagreader::IsMediaFileRequest *req = message.mutable_is_media_file_request();
|
||||
|
||||
req->set_filename(DataCommaSizeFromQString(filename));
|
||||
|
||||
return worker_pool_->SendMessageWithReply(&message);
|
||||
|
||||
}
|
||||
|
||||
TagReaderReply *TagReaderClient::ReadFile(const QString &filename) {
|
||||
|
||||
spb::tagreader::Message message;
|
||||
@@ -94,17 +114,6 @@ TagReaderReply *TagReaderClient::SaveFile(const QString &filename, const Song &m
|
||||
|
||||
}
|
||||
|
||||
TagReaderReply *TagReaderClient::IsMediaFile(const QString &filename) {
|
||||
|
||||
spb::tagreader::Message message;
|
||||
spb::tagreader::IsMediaFileRequest *req = message.mutable_is_media_file_request();
|
||||
|
||||
req->set_filename(DataCommaSizeFromQString(filename));
|
||||
|
||||
return worker_pool_->SendMessageWithReply(&message);
|
||||
|
||||
}
|
||||
|
||||
TagReaderReply *TagReaderClient::LoadEmbeddedArt(const QString &filename) {
|
||||
|
||||
spb::tagreader::Message message;
|
||||
@@ -128,6 +137,64 @@ TagReaderReply *TagReaderClient::SaveEmbeddedArt(const QString &filename, const
|
||||
|
||||
}
|
||||
|
||||
TagReaderReply *TagReaderClient::UpdateSongPlaycount(const Song &metadata) {
|
||||
|
||||
spb::tagreader::Message message;
|
||||
spb::tagreader::SaveSongPlaycountToFileRequest *req = message.mutable_save_song_playcount_to_file_request();
|
||||
|
||||
req->set_filename(DataCommaSizeFromQString(metadata.url().toLocalFile()));
|
||||
metadata.ToProtobuf(req->mutable_metadata());
|
||||
|
||||
return worker_pool_->SendMessageWithReply(&message);
|
||||
|
||||
}
|
||||
|
||||
void TagReaderClient::UpdateSongsPlaycount(const SongList &songs) {
|
||||
|
||||
for (const Song &song : songs) {
|
||||
TagReaderReply *reply = UpdateSongPlaycount(song);
|
||||
QObject::connect(reply, &TagReaderReply::Finished, reply, &TagReaderReply::deleteLater);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
TagReaderReply *TagReaderClient::UpdateSongRating(const Song &metadata) {
|
||||
|
||||
spb::tagreader::Message message;
|
||||
spb::tagreader::SaveSongRatingToFileRequest *req = message.mutable_save_song_rating_to_file_request();
|
||||
|
||||
req->set_filename(DataCommaSizeFromQString(metadata.url().toLocalFile()));
|
||||
metadata.ToProtobuf(req->mutable_metadata());
|
||||
|
||||
return worker_pool_->SendMessageWithReply(&message);
|
||||
|
||||
}
|
||||
|
||||
void TagReaderClient::UpdateSongsRating(const SongList &songs) {
|
||||
|
||||
for (const Song &song : songs) {
|
||||
TagReaderReply *reply = UpdateSongRating(song);
|
||||
QObject::connect(reply, &TagReaderReply::Finished, reply, &TagReaderReply::deleteLater);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
bool TagReaderClient::IsMediaFileBlocking(const QString &filename) {
|
||||
|
||||
Q_ASSERT(QThread::currentThread() != thread());
|
||||
|
||||
bool ret = false;
|
||||
|
||||
TagReaderReply *reply = IsMediaFile(filename);
|
||||
if (reply->WaitForFinished()) {
|
||||
ret = reply->message().is_media_file_response().success();
|
||||
}
|
||||
QMetaObject::invokeMethod(reply, "deleteLater", Qt::QueuedConnection);
|
||||
|
||||
return ret;
|
||||
|
||||
}
|
||||
|
||||
void TagReaderClient::ReadFileBlocking(const QString &filename, Song *song) {
|
||||
|
||||
Q_ASSERT(QThread::currentThread() != thread());
|
||||
@@ -156,22 +223,6 @@ bool TagReaderClient::SaveFileBlocking(const QString &filename, const Song &meta
|
||||
|
||||
}
|
||||
|
||||
bool TagReaderClient::IsMediaFileBlocking(const QString &filename) {
|
||||
|
||||
Q_ASSERT(QThread::currentThread() != thread());
|
||||
|
||||
bool ret = false;
|
||||
|
||||
TagReaderReply *reply = IsMediaFile(filename);
|
||||
if (reply->WaitForFinished()) {
|
||||
ret = reply->message().is_media_file_response().success();
|
||||
}
|
||||
QMetaObject::invokeMethod(reply, "deleteLater", Qt::QueuedConnection);
|
||||
|
||||
return ret;
|
||||
|
||||
}
|
||||
|
||||
QByteArray TagReaderClient::LoadEmbeddedArtBlocking(const QString &filename) {
|
||||
|
||||
Q_ASSERT(QThread::currentThread() != thread());
|
||||
@@ -181,7 +232,7 @@ QByteArray TagReaderClient::LoadEmbeddedArtBlocking(const QString &filename) {
|
||||
TagReaderReply *reply = LoadEmbeddedArt(filename);
|
||||
if (reply->WaitForFinished()) {
|
||||
const std::string &data_str = reply->message().load_embedded_art_response().data();
|
||||
ret = QByteArray(data_str.data(), data_str.size());
|
||||
ret = QByteArray(data_str.data(), static_cast<qint64>(data_str.size()));
|
||||
}
|
||||
QMetaObject::invokeMethod(reply, "deleteLater", Qt::QueuedConnection);
|
||||
|
||||
@@ -198,7 +249,7 @@ QImage TagReaderClient::LoadEmbeddedArtAsImageBlocking(const QString &filename)
|
||||
TagReaderReply *reply = LoadEmbeddedArt(filename);
|
||||
if (reply->WaitForFinished()) {
|
||||
const std::string &data_str = reply->message().load_embedded_art_response().data();
|
||||
ret.loadFromData(QByteArray(data_str.data(), data_str.size()));
|
||||
ret.loadFromData(QByteArray(data_str.data(), static_cast<qint64>(data_str.size())));
|
||||
}
|
||||
QMetaObject::invokeMethod(reply, "deleteLater", Qt::QueuedConnection);
|
||||
|
||||
@@ -210,14 +261,46 @@ bool TagReaderClient::SaveEmbeddedArtBlocking(const QString &filename, const QBy
|
||||
|
||||
Q_ASSERT(QThread::currentThread() != thread());
|
||||
|
||||
bool ret = false;
|
||||
bool success = false;
|
||||
|
||||
TagReaderReply *reply = SaveEmbeddedArt(filename, data);
|
||||
if (reply->WaitForFinished()) {
|
||||
ret = reply->message().save_embedded_art_response().success();
|
||||
success = reply->message().save_embedded_art_response().success();
|
||||
}
|
||||
QMetaObject::invokeMethod(reply, "deleteLater", Qt::QueuedConnection);
|
||||
|
||||
return ret;
|
||||
return success;
|
||||
|
||||
}
|
||||
|
||||
bool TagReaderClient::UpdateSongPlaycountBlocking(const Song &metadata) {
|
||||
|
||||
Q_ASSERT(QThread::currentThread() != thread());
|
||||
|
||||
bool success = false;
|
||||
|
||||
TagReaderReply *reply = UpdateSongPlaycount(metadata);
|
||||
if (reply->WaitForFinished()) {
|
||||
success = reply->message().save_song_playcount_to_file_response().success();
|
||||
}
|
||||
reply->deleteLater();
|
||||
|
||||
return success;
|
||||
|
||||
}
|
||||
|
||||
bool TagReaderClient::UpdateSongRatingBlocking(const Song &metadata) {
|
||||
|
||||
Q_ASSERT(QThread::currentThread() != thread());
|
||||
|
||||
bool success = false;
|
||||
|
||||
TagReaderReply *reply = UpdateSongRating(metadata);
|
||||
if (reply->WaitForFinished()) {
|
||||
success = reply->message().save_song_rating_to_file_response().success();
|
||||
}
|
||||
reply->deleteLater();
|
||||
|
||||
return success;
|
||||
|
||||
}
|
||||
|
||||
@@ -58,6 +58,8 @@ class TagReaderClient : public QObject {
|
||||
ReplyType *IsMediaFile(const QString &filename);
|
||||
ReplyType *LoadEmbeddedArt(const QString &filename);
|
||||
ReplyType *SaveEmbeddedArt(const QString &filename, const QByteArray &data);
|
||||
ReplyType* UpdateSongPlaycount(const Song &metadata);
|
||||
ReplyType* UpdateSongRating(const Song &metadata);
|
||||
|
||||
// Convenience functions that call the above functions and wait for a response.
|
||||
// These block the calling thread with a semaphore, and must NOT be called from the TagReaderClient's thread.
|
||||
@@ -67,6 +69,8 @@ class TagReaderClient : public QObject {
|
||||
QByteArray LoadEmbeddedArtBlocking(const QString &filename);
|
||||
QImage LoadEmbeddedArtAsImageBlocking(const QString &filename);
|
||||
bool SaveEmbeddedArtBlocking(const QString &filename, const QByteArray &data);
|
||||
bool UpdateSongPlaycountBlocking(const Song &metadata);
|
||||
bool UpdateSongRatingBlocking(const Song &metadata);
|
||||
|
||||
// TODO: Make this not a singleton
|
||||
static TagReaderClient *Instance() { return sInstance; }
|
||||
@@ -78,6 +82,10 @@ class TagReaderClient : public QObject {
|
||||
void Exit();
|
||||
void WorkerFailedToStart();
|
||||
|
||||
public slots:
|
||||
void UpdateSongsPlaycount(const SongList &songs);
|
||||
void UpdateSongsRating(const SongList &songs);
|
||||
|
||||
private:
|
||||
static TagReaderClient *sInstance;
|
||||
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user