Compare commits
279 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e2f5486987 | ||
|
|
b0d390aaf1 | ||
|
|
bae1b42394 | ||
|
|
a2533edd57 | ||
|
|
7b88c198fe | ||
|
|
c49cb0c119 | ||
|
|
03aabeb848 | ||
|
|
1d3a837f7a | ||
|
|
92adc18b8f | ||
|
|
e4697c8ff1 | ||
|
|
b741f1a580 | ||
|
|
8ee6de4162 | ||
|
|
2b9e7db924 | ||
|
|
49384ce294 | ||
|
|
ac59fff346 | ||
|
|
a3e9f152d8 | ||
|
|
ffe6a81b9a | ||
|
|
9bb051b4eb | ||
|
|
c1465a890f | ||
|
|
c3ee3318d7 | ||
|
|
272976f0b7 | ||
|
|
4724b170b1 | ||
|
|
337bd4bcef | ||
|
|
0604c78453 | ||
|
|
c8169adf7c | ||
|
|
7b610d131c | ||
|
|
18da55453f | ||
|
|
2aeab8b672 | ||
|
|
9c21707a55 | ||
|
|
b02ac833ad | ||
|
|
4de912cf41 | ||
|
|
ed260c6e20 | ||
|
|
fab38f693d | ||
|
|
aedbd52e9d | ||
|
|
4cbcb9d99c | ||
|
|
e967d15b4e | ||
|
|
bb43cc63ec | ||
|
|
ca176c319d | ||
|
|
17c960ecd4 | ||
|
|
b766ae3498 | ||
|
|
faf4c817cd | ||
|
|
b4e1f283c9 | ||
|
|
52c83d592c | ||
|
|
c8caea0d30 | ||
|
|
7a6c54d8e7 | ||
|
|
7082e52a4f | ||
|
|
6cdca617e0 | ||
|
|
a1adc1a75a | ||
|
|
b16bec704a | ||
|
|
664a8c79a1 | ||
|
|
bbf3d8e1a4 | ||
|
|
a35146440c | ||
|
|
8e79fafca9 | ||
|
|
6f8780d3cc | ||
|
|
264065a355 | ||
|
|
b606e4cd1a | ||
|
|
01d0eeaed0 | ||
|
|
8c4e24e65d | ||
|
|
19c9f9698d | ||
|
|
ae87c1b578 | ||
|
|
c42b1f5548 | ||
|
|
87ffbb0a85 | ||
|
|
702851a958 | ||
|
|
650f200a0b | ||
|
|
14d215bdf3 | ||
|
|
12aebc2fe9 | ||
|
|
f41b051ec7 | ||
|
|
25144eee89 | ||
|
|
0c62147536 | ||
|
|
7b282e21de | ||
|
|
c1fbe6d84c | ||
|
|
e20cbe4170 | ||
|
|
394955a03f | ||
|
|
16b4f5d065 | ||
|
|
c95295d8b4 | ||
|
|
658dce2607 | ||
|
|
01f8d0a27e | ||
|
|
39e1bfc84f | ||
|
|
e394416fa7 | ||
|
|
f1108bc0e2 | ||
|
|
ff31815d49 | ||
|
|
604aa63b47 | ||
|
|
ec4d036f50 | ||
|
|
1bf1c4ac63 | ||
|
|
981d46fbd4 | ||
|
|
eb6a353c31 | ||
|
|
ff673b1941 | ||
|
|
8b55cf8a3a | ||
|
|
d8682b4403 | ||
|
|
40a4bf195a | ||
|
|
312c5cbc3f | ||
|
|
f314c56ef0 | ||
|
|
ea8e5180ff | ||
|
|
7f76c3f2ce | ||
|
|
e4c5e99d0f | ||
|
|
80cfca5de2 | ||
|
|
9556a14de9 | ||
|
|
2025fc8325 | ||
|
|
2dd0f6a9ba | ||
|
|
a42039d6e5 | ||
|
|
7fafa8adfb | ||
|
|
f789657552 | ||
|
|
3e183bc10e | ||
|
|
16e5d93be3 | ||
|
|
6515e06a13 | ||
|
|
9ae0b32318 | ||
|
|
4cb3bc4185 | ||
|
|
f9ca24598e | ||
|
|
3ccc892d6a | ||
|
|
d7cacea843 | ||
|
|
e30233ac74 | ||
|
|
7c57631fcf | ||
|
|
0adc084dad | ||
|
|
e22199817c | ||
|
|
78f691d006 | ||
|
|
749bae1f16 | ||
|
|
1043e24322 | ||
|
|
a6d10b1fa7 | ||
|
|
a3159423f8 | ||
|
|
ecb5ca321b | ||
|
|
fd827fdfd8 | ||
|
|
92d77b14d5 | ||
|
|
be67d89d8b | ||
|
|
ddd1ce732a | ||
|
|
012d82183c | ||
|
|
f9c98ebcb3 | ||
|
|
10fe861dde | ||
|
|
16c027ecab | ||
|
|
cc578e7cc5 | ||
|
|
6972d3c4f9 | ||
|
|
46b164f2fb | ||
|
|
5431307527 | ||
|
|
79bf194ed6 | ||
|
|
506e670aa7 | ||
|
|
fdfe164dd1 | ||
|
|
af37056179 | ||
|
|
b0fc7187cf | ||
|
|
33ad1a7a86 | ||
|
|
dd72fb4ca5 | ||
|
|
e6c5f76872 | ||
|
|
14aa22d590 | ||
|
|
5ed4293641 | ||
|
|
f9fefcda57 | ||
|
|
99b40293db | ||
|
|
9b06e85f94 | ||
|
|
93d1d40ea5 | ||
|
|
98597c047a | ||
|
|
a5c1f4b0ee | ||
|
|
3d4c98d981 | ||
|
|
384e7dedb5 | ||
|
|
7df4453560 | ||
|
|
d406a1c341 | ||
|
|
6671d97b4a | ||
|
|
d02de72830 | ||
|
|
08f5172028 | ||
|
|
04f062547d | ||
|
|
4717d783dc | ||
|
|
93af064b36 | ||
|
|
c78d73d727 | ||
|
|
b69b3228be | ||
|
|
377f54700d | ||
|
|
d276339c80 | ||
|
|
b982a6a762 | ||
|
|
536fe637aa | ||
|
|
69f36eaa25 | ||
|
|
d6927a70bb | ||
|
|
1b1892a187 | ||
|
|
5bea71cd5c | ||
|
|
bb8d4e70ae | ||
|
|
8d8c7e8b7b | ||
|
|
9ff1f4d7b4 | ||
|
|
3a60dfe025 | ||
|
|
d358854e16 | ||
|
|
129587e94a | ||
|
|
0f575f4639 | ||
|
|
7aac741571 | ||
|
|
b8a9da8a4e | ||
|
|
be6b974334 | ||
|
|
194e81205b | ||
|
|
d711dcc99d | ||
|
|
3b72a12540 | ||
|
|
38a1b7765a | ||
|
|
02f2b8b6f0 | ||
|
|
7bfa75102c | ||
|
|
b0f3e7351c | ||
|
|
b5fa401db9 | ||
|
|
41f2710dea | ||
|
|
f19dda57f0 | ||
|
|
cc4a99ad80 | ||
|
|
1c1a3fc417 | ||
|
|
e97be5850b | ||
|
|
2c302654b7 | ||
|
|
3ee4dc77b2 | ||
|
|
db55f314c9 | ||
|
|
0b536b287f | ||
|
|
6f298a9917 | ||
|
|
70f829a2e5 | ||
|
|
1dfe07003f | ||
|
|
286b908062 | ||
|
|
4ec028e736 | ||
|
|
35a6d1437a | ||
|
|
7e3cb3de89 | ||
|
|
bd945039f1 | ||
|
|
46743c8f15 | ||
|
|
9b75284447 | ||
|
|
dca3b0052a | ||
|
|
6d05bb2de5 | ||
|
|
8b2e8d3804 | ||
|
|
1a2ab19ab4 | ||
|
|
04f010aa7b | ||
|
|
25323b4a3a | ||
|
|
f353c022f6 | ||
|
|
9d8b3d3428 | ||
|
|
e4b06772c0 | ||
|
|
a2b5c3ea08 | ||
|
|
f632e95600 | ||
|
|
4b5da8c6d4 | ||
|
|
77d7ffd10a | ||
|
|
d2f720011a | ||
|
|
6329a7711d | ||
|
|
fd14d044e3 | ||
|
|
6368d507e1 | ||
|
|
205b7f2401 | ||
|
|
f7d10f9530 | ||
|
|
66154bb51e | ||
|
|
fac4ad5313 | ||
|
|
fee63891ac | ||
|
|
efa979ee03 | ||
|
|
c756346357 | ||
|
|
cc95db25cc | ||
|
|
44970c3321 | ||
|
|
43c7934af7 | ||
|
|
3e7b51163c | ||
|
|
4d5f8a53f0 | ||
|
|
0b73ca8318 | ||
|
|
754cfc3bfd | ||
|
|
2ed1a5c06a | ||
|
|
b2073df3c3 | ||
|
|
6267edaa81 | ||
|
|
292f2de3e6 | ||
|
|
7803dc8be3 | ||
|
|
2a1260b79e | ||
|
|
f9ec438b7f | ||
|
|
de544c4856 | ||
|
|
55e04dd520 | ||
|
|
b2d06f900b | ||
|
|
b92ec71810 | ||
|
|
3a4199240e | ||
|
|
157de12bdf | ||
|
|
7c0c9fccdb | ||
|
|
2cb29d06b3 | ||
|
|
1fdeb50d93 | ||
|
|
f59632ae59 | ||
|
|
67b503da44 | ||
|
|
a351de6904 | ||
|
|
f94742abd5 | ||
|
|
891182637e | ||
|
|
086681c915 | ||
|
|
3970d2d02b | ||
|
|
2b3fddd015 | ||
|
|
30fa0480a3 | ||
|
|
320a81ce69 | ||
|
|
950411ef56 | ||
|
|
cb9cf084c0 | ||
|
|
bb18af09ae | ||
|
|
049bf0c7f9 | ||
|
|
ebc73ef775 | ||
|
|
1bd6f59355 | ||
|
|
c16396a690 | ||
|
|
32a9fe3e9c | ||
|
|
f48d133bc6 | ||
|
|
0678526d7d | ||
|
|
26d3e8371f | ||
|
|
6768f614c7 | ||
|
|
171dc84df1 | ||
|
|
5ca6513c04 | ||
|
|
41aeb0ac80 | ||
|
|
f871c4adef | ||
|
|
ad4d0e89b1 |
6
.github/dependabot.yml
vendored
Normal file
6
.github/dependabot.yml
vendored
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
version: 2
|
||||||
|
updates:
|
||||||
|
- package-ecosystem: "github-actions"
|
||||||
|
directory: "/"
|
||||||
|
schedule:
|
||||||
|
interval: daily
|
||||||
133
.github/workflows/build.yml
vendored
133
.github/workflows/build.yml
vendored
@@ -1,4 +1,4 @@
|
|||||||
name: build
|
name: Build
|
||||||
on: [push, pull_request]
|
on: [push, pull_request]
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
@@ -101,13 +101,10 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
- name: Add safe git directory
|
- name: Add safe git directory
|
||||||
shell: bash
|
|
||||||
run: git config --global --add safe.directory ${GITHUB_WORKSPACE}
|
run: git config --global --add safe.directory ${GITHUB_WORKSPACE}
|
||||||
- name: Create Build Environment
|
- name: Create Build Environment
|
||||||
shell: bash
|
|
||||||
run: cmake -E make_directory build
|
run: cmake -E make_directory build
|
||||||
- name: Configure CMake
|
- name: Configure CMake
|
||||||
shell: bash
|
|
||||||
working-directory: build
|
working-directory: build
|
||||||
run: cmake .. -DCMAKE_BUILD_TYPE=Debug -DBUILD_WERROR=ON -DUSE_TAGLIB=ON
|
run: cmake .. -DCMAKE_BUILD_TYPE=Debug -DBUILD_WERROR=ON -DUSE_TAGLIB=ON
|
||||||
- name: Create source tarball
|
- name: Create source tarball
|
||||||
@@ -137,17 +134,17 @@ jobs:
|
|||||||
strategy:
|
strategy:
|
||||||
fail-fast: false
|
fail-fast: false
|
||||||
matrix:
|
matrix:
|
||||||
fedora_version: [ '35', '36', '37' ]
|
fedora_version: [ '36', '37', '38' ]
|
||||||
container:
|
container:
|
||||||
image: fedora:${{matrix.fedora_version}}
|
image: fedora:${{matrix.fedora_version}}
|
||||||
steps:
|
steps:
|
||||||
- name: Update repositories
|
- name: Update repositories
|
||||||
run: yum update --assumeyes
|
run: dnf update -y
|
||||||
- name: Upgrade packages
|
- name: Upgrade packages
|
||||||
run: yum upgrade --assumeyes
|
run: dnf upgrade -y
|
||||||
- name: Install dependencies
|
- name: Install dependencies
|
||||||
run: >
|
run: >
|
||||||
dnf install --assumeyes
|
dnf install -y
|
||||||
@development-tools
|
@development-tools
|
||||||
redhat-lsb-core
|
redhat-lsb-core
|
||||||
which
|
which
|
||||||
@@ -187,17 +184,16 @@ jobs:
|
|||||||
desktop-file-utils
|
desktop-file-utils
|
||||||
libappstream-glib
|
libappstream-glib
|
||||||
hicolor-icon-theme
|
hicolor-icon-theme
|
||||||
|
- name: Remove dangling Qt 6 SQL CMake files
|
||||||
|
run: rm -rf /usr/lib64/cmake/Qt6Sql/{Qt6QMYSQL*,Qt6QODBCD*,Qt6QPSQL*}
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v3
|
||||||
with:
|
with:
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
- name: Add safe git directory
|
- name: Add safe git directory
|
||||||
shell: bash
|
|
||||||
run: git config --global --add safe.directory ${GITHUB_WORKSPACE}
|
run: git config --global --add safe.directory ${GITHUB_WORKSPACE}
|
||||||
- name: Create Build Environment
|
- name: Create Build Environment
|
||||||
shell: bash
|
|
||||||
run: cmake -E make_directory build
|
run: cmake -E make_directory build
|
||||||
- name: Configure CMake
|
- name: Configure CMake
|
||||||
shell: bash
|
|
||||||
working-directory: build
|
working-directory: build
|
||||||
run: cmake .. -DCMAKE_BUILD_TYPE=Debug -DBUILD_WERROR=ON
|
run: cmake .. -DCMAKE_BUILD_TYPE=Debug -DBUILD_WERROR=ON
|
||||||
- name: Create source tarball
|
- name: Create source tarball
|
||||||
@@ -227,12 +223,12 @@ jobs:
|
|||||||
image: openmandriva/${{matrix.openmandriva_version}}
|
image: openmandriva/${{matrix.openmandriva_version}}
|
||||||
steps:
|
steps:
|
||||||
- name: Update repositories
|
- name: Update repositories
|
||||||
run: dnf update --assumeyes
|
run: dnf update -y
|
||||||
- name: Upgrade packages
|
- name: Upgrade packages
|
||||||
run: dnf upgrade --assumeyes
|
run: dnf upgrade -y
|
||||||
- name: Install dependencies
|
- name: Install dependencies
|
||||||
run: >
|
run: >
|
||||||
dnf install --assumeyes
|
dnf install -y
|
||||||
glibc
|
glibc
|
||||||
gcc-c++
|
gcc-c++
|
||||||
git
|
git
|
||||||
@@ -281,13 +277,10 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
- name: Add safe git directory
|
- name: Add safe git directory
|
||||||
shell: bash
|
|
||||||
run: git config --global --add safe.directory ${GITHUB_WORKSPACE}
|
run: git config --global --add safe.directory ${GITHUB_WORKSPACE}
|
||||||
- name: Create Build Environment
|
- name: Create Build Environment
|
||||||
shell: bash
|
|
||||||
run: cmake -E make_directory build
|
run: cmake -E make_directory build
|
||||||
- name: Configure CMake
|
- name: Configure CMake
|
||||||
shell: bash
|
|
||||||
working-directory: build
|
working-directory: build
|
||||||
run: cmake .. -DCMAKE_BUILD_TYPE=Debug -DBUILD_WERROR=ON
|
run: cmake .. -DCMAKE_BUILD_TYPE=Debug -DBUILD_WERROR=ON
|
||||||
- name: Create source tarball
|
- name: Create source tarball
|
||||||
@@ -317,7 +310,7 @@ jobs:
|
|||||||
image: debian:${{matrix.debian_version}}
|
image: debian:${{matrix.debian_version}}
|
||||||
steps:
|
steps:
|
||||||
- name: Update repositories
|
- name: Update repositories
|
||||||
run: apt update
|
run: apt update -y
|
||||||
- name: Install packages
|
- name: Install packages
|
||||||
env:
|
env:
|
||||||
DEBIAN_FRONTEND: noninteractive
|
DEBIAN_FRONTEND: noninteractive
|
||||||
@@ -360,39 +353,23 @@ jobs:
|
|||||||
if: matrix.debian_version == 'buster' || matrix.debian_version == 'bullseye'
|
if: matrix.debian_version == 'buster' || matrix.debian_version == 'bullseye'
|
||||||
env:
|
env:
|
||||||
DEBIAN_FRONTEND: noninteractive
|
DEBIAN_FRONTEND: noninteractive
|
||||||
run: >
|
run: apt install -y qtbase5-dev qtbase5-dev-tools qttools5-dev qttools5-dev-tools libqt5x11extras5-dev
|
||||||
apt install -y
|
|
||||||
qtbase5-dev
|
|
||||||
qtbase5-dev-tools
|
|
||||||
qttools5-dev
|
|
||||||
qttools5-dev-tools
|
|
||||||
libqt5x11extras5-dev
|
|
||||||
- name: Install Qt 6
|
- name: Install Qt 6
|
||||||
if: matrix.debian_version != 'buster' && matrix.debian_version != 'bullseye'
|
if: matrix.debian_version != 'buster' && matrix.debian_version != 'bullseye'
|
||||||
env:
|
env:
|
||||||
DEBIAN_FRONTEND: noninteractive
|
DEBIAN_FRONTEND: noninteractive
|
||||||
run: >
|
run: apt install -y qt6-base-dev qt6-base-dev-tools qt6-tools-dev qt6-tools-dev-tools qt6-l10n-tools
|
||||||
apt install -y
|
|
||||||
qt6-base-dev
|
|
||||||
qt6-base-dev-tools
|
|
||||||
qt6-tools-dev
|
|
||||||
qt6-tools-dev-tools
|
|
||||||
qt6-l10n-tools
|
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v3
|
||||||
with:
|
with:
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
- name: Add safe git directory
|
- name: Add safe git directory
|
||||||
shell: bash
|
|
||||||
run: git config --global --add safe.directory ${GITHUB_WORKSPACE}
|
run: git config --global --add safe.directory ${GITHUB_WORKSPACE}
|
||||||
- name: Create Build Environment
|
- name: Create Build Environment
|
||||||
shell: bash
|
|
||||||
run: cmake -E make_directory build
|
run: cmake -E make_directory build
|
||||||
- name: Configure CMake
|
- name: Configure CMake
|
||||||
shell: bash
|
|
||||||
working-directory: build
|
working-directory: build
|
||||||
run: cmake .. -DCMAKE_BUILD_TYPE=Debug -DBUILD_WERROR=ON
|
run: cmake .. -DCMAKE_BUILD_TYPE=Debug -DBUILD_WERROR=ON
|
||||||
- name: make deb
|
- name: make deb
|
||||||
shell: bash
|
|
||||||
run: dpkg-buildpackage -b -d -uc -us -nc -j2
|
run: dpkg-buildpackage -b -d -uc -us -nc -j2
|
||||||
|
|
||||||
|
|
||||||
@@ -402,15 +379,17 @@ jobs:
|
|||||||
strategy:
|
strategy:
|
||||||
fail-fast: false
|
fail-fast: false
|
||||||
matrix:
|
matrix:
|
||||||
ubuntu_version: [ 'bionic', 'focal', 'jammy', 'kinetic' ]
|
ubuntu_version: [ 'bionic', 'focal', 'jammy', 'kinetic', 'lunar' ]
|
||||||
container:
|
container:
|
||||||
image: ubuntu:${{matrix.ubuntu_version}}
|
image: ubuntu:${{matrix.ubuntu_version}}
|
||||||
steps:
|
steps:
|
||||||
|
- name: Update repositories
|
||||||
|
run: apt update -y
|
||||||
- name: Install packages
|
- name: Install packages
|
||||||
env:
|
env:
|
||||||
DEBIAN_FRONTEND: noninteractive
|
DEBIAN_FRONTEND: noninteractive
|
||||||
run: >
|
run: >
|
||||||
apt-get update && apt-get install -y
|
apt install -y
|
||||||
build-essential
|
build-essential
|
||||||
dh-make
|
dh-make
|
||||||
ssh
|
ssh
|
||||||
@@ -451,27 +430,23 @@ jobs:
|
|||||||
if: matrix.ubuntu_version == 'bionic' || matrix.ubuntu_version == 'focal'
|
if: matrix.ubuntu_version == 'bionic' || matrix.ubuntu_version == 'focal'
|
||||||
env:
|
env:
|
||||||
DEBIAN_FRONTEND: noninteractive
|
DEBIAN_FRONTEND: noninteractive
|
||||||
run: apt-get update && apt-get install -y qtbase5-dev qtbase5-dev-tools qttools5-dev qttools5-dev-tools libqt5x11extras5-dev
|
run: apt install -y qtbase5-dev qtbase5-dev-tools qttools5-dev qttools5-dev-tools libqt5x11extras5-dev
|
||||||
- name: Install Qt 6
|
- name: Install Qt 6
|
||||||
if: matrix.ubuntu_version != 'bionic' && matrix.ubuntu_version != 'focal'
|
if: matrix.ubuntu_version != 'bionic' && matrix.ubuntu_version != 'focal'
|
||||||
env:
|
env:
|
||||||
DEBIAN_FRONTEND: noninteractive
|
DEBIAN_FRONTEND: noninteractive
|
||||||
run: apt-get update && apt-get install -y qt6-base-dev qt6-base-dev-tools qt6-tools-dev qt6-tools-dev-tools qt6-l10n-tools
|
run: apt install -y qt6-base-dev qt6-base-dev-tools qt6-tools-dev qt6-tools-dev-tools qt6-l10n-tools
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v3
|
||||||
with:
|
with:
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
- name: Add safe git directory
|
- name: Add safe git directory
|
||||||
shell: bash
|
|
||||||
run: git config --global --add safe.directory ${GITHUB_WORKSPACE}
|
run: git config --global --add safe.directory ${GITHUB_WORKSPACE}
|
||||||
- name: Create Build Environment
|
- name: Create Build Environment
|
||||||
shell: bash
|
|
||||||
run: cmake -E make_directory build
|
run: cmake -E make_directory build
|
||||||
- name: Configure CMake
|
- name: Configure CMake
|
||||||
shell: bash
|
|
||||||
working-directory: build
|
working-directory: build
|
||||||
run: cmake .. -DCMAKE_BUILD_TYPE=Debug -DBUILD_WERROR=ON
|
run: cmake .. -DCMAKE_BUILD_TYPE=Debug -DBUILD_WERROR=ON
|
||||||
- name: make deb
|
- name: make deb
|
||||||
shell: bash
|
|
||||||
run: dpkg-buildpackage -b -d -uc -us -nc -j2
|
run: dpkg-buildpackage -b -d -uc -us -nc -j2
|
||||||
|
|
||||||
|
|
||||||
@@ -484,11 +459,11 @@ jobs:
|
|||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
- name: Build FreeBSD
|
- name: Build FreeBSD
|
||||||
id: build-freebsd
|
id: build-freebsd
|
||||||
uses: vmactions/freebsd-vm@v0.2.5
|
uses: vmactions/freebsd-vm@v0.3.0
|
||||||
with:
|
with:
|
||||||
usesh: true
|
usesh: true
|
||||||
mem: 4096
|
mem: 4096
|
||||||
prepare: pkg install -y git cmake pkgconf gettext-tools boost-libs glib gnutls qt5-core qt5-concurrent qt5-network qt5-sql qt5-dbus qt5-gui qt5-widgets qt5-buildtools qt5-linguisttools qt5-qmake qt5-sqldrivers-sqlite3 qt5-testlib sqlite gstreamer1 gstreamer1-plugins chromaprint protobuf protobuf-c taglib libcdio libmtp gdk-pixbuf2 libgpod fftw3 googletest iconv icu
|
prepare: pkg install -y git cmake pkgconf gettext-tools boost-libs glib gnutls qt6-base qt6-tools sqlite gstreamer1 gstreamer1-plugins chromaprint protobuf protobuf-c taglib libcdio libmtp gdk-pixbuf2 libgpod fftw3 googletest iconv icu
|
||||||
run: |
|
run: |
|
||||||
git config --global --add safe.directory ${GITHUB_WORKSPACE}
|
git config --global --add safe.directory ${GITHUB_WORKSPACE}
|
||||||
cmake -E make_directory build
|
cmake -E make_directory build
|
||||||
@@ -501,17 +476,17 @@ jobs:
|
|||||||
|
|
||||||
build-macos:
|
build-macos:
|
||||||
name: Build macOS
|
name: Build macOS
|
||||||
runs-on: macos-10.15
|
runs-on: macos-11
|
||||||
steps:
|
steps:
|
||||||
|
|
||||||
- name: Remove packages
|
- name: Remove packages
|
||||||
run: brew remove aliyun-cli ant aws-sam-cli azure-cli bazelisk bicep composer geckodriver ghostscript go@1.17 helm httpd imagemagick kotlin maven mongodb-community@5.0 mongodb-database-tools mongosh nginx node@16 php postgresql@14 ruby@2.7 rustup-init sbt selenium-server swiftformat switchaudio-osx chromedriver firefox google-chrome graalvm-ce-java11 julia microsoft-edge r session-manager-plugin vagrant
|
run: brew remove aliyun-cli ant aws-sam-cli azure-cli bazelisk bicep composer geckodriver ghostscript go@1.17 helm httpd imagemagick kotlin maven mongodb-community@5.0 mongodb-database-tools mongosh nginx php postgresql@14 ruby@2.7 rustup-init sbt selenium-server swiftformat switchaudio-osx chromedriver firefox google-chrome graalvm-ce-java11 julia microsoft-edge r session-manager-plugin
|
||||||
|
|
||||||
- name: Update packages
|
- name: Update packages
|
||||||
run: brew update
|
run: brew update
|
||||||
|
|
||||||
- name: Upgrade packages
|
- name: Upgrade packages
|
||||||
run: brew upgrade
|
run: brew upgrade || true
|
||||||
|
|
||||||
- name: Install packages
|
- name: Install packages
|
||||||
run: |
|
run: |
|
||||||
@@ -532,25 +507,36 @@ jobs:
|
|||||||
mv gstreamer.rb gst-plugins-{base,good,bad,ugly}.rb gst-libav.rb /usr/local/Homebrew/Library/Taps/homebrew/homebrew-core/Formula/
|
mv gstreamer.rb gst-plugins-{base,good,bad,ugly}.rb gst-libav.rb /usr/local/Homebrew/Library/Taps/homebrew/homebrew-core/Formula/
|
||||||
|
|
||||||
- name: Build and install gstreamer
|
- name: Build and install gstreamer
|
||||||
|
env:
|
||||||
|
HOMEBREW_NO_INSTALL_FROM_API: 1
|
||||||
run: brew reinstall --build-from-source gstreamer
|
run: brew reinstall --build-from-source gstreamer
|
||||||
|
|
||||||
- name: Build and install gst-plugins-base
|
- name: Build and install gst-plugins-base
|
||||||
|
env:
|
||||||
|
HOMEBREW_NO_INSTALL_FROM_API: 1
|
||||||
run: brew reinstall --build-from-source gst-plugins-base
|
run: brew reinstall --build-from-source gst-plugins-base
|
||||||
|
|
||||||
- name: Build and install gst-plugins-good
|
- name: Build and install gst-plugins-good
|
||||||
|
env:
|
||||||
|
HOMEBREW_NO_INSTALL_FROM_API: 1
|
||||||
run: brew reinstall --build-from-source gst-plugins-good
|
run: brew reinstall --build-from-source gst-plugins-good
|
||||||
|
|
||||||
- name: Build and install gst-plugins-bad
|
- name: Build and install gst-plugins-bad
|
||||||
|
env:
|
||||||
|
HOMEBREW_NO_INSTALL_FROM_API: 1
|
||||||
run: brew reinstall --build-from-source gst-plugins-bad
|
run: brew reinstall --build-from-source gst-plugins-bad
|
||||||
|
|
||||||
- name: Build and install gst-plugins-ugly
|
- name: Build and install gst-plugins-ugly
|
||||||
|
env:
|
||||||
|
HOMEBREW_NO_INSTALL_FROM_API: 1
|
||||||
run: brew reinstall --build-from-source gst-plugins-ugly
|
run: brew reinstall --build-from-source gst-plugins-ugly
|
||||||
|
|
||||||
- name: Build and install gst-libav
|
- name: Build and install gst-libav
|
||||||
|
env:
|
||||||
|
HOMEBREW_NO_INSTALL_FROM_API: 1
|
||||||
run: brew reinstall --build-from-source gst-libav
|
run: brew reinstall --build-from-source gst-libav
|
||||||
|
|
||||||
- name: Build libgpod
|
- name: Build libgpod
|
||||||
shell: bash
|
|
||||||
env:
|
env:
|
||||||
PERL_MM_USE_DEFAULT: 1
|
PERL_MM_USE_DEFAULT: 1
|
||||||
run: |
|
run: |
|
||||||
@@ -562,21 +548,14 @@ jobs:
|
|||||||
make -j 4
|
make -j 4
|
||||||
sudo make install
|
sudo make install
|
||||||
|
|
||||||
- name: Remove problematic files
|
|
||||||
shell: bash
|
|
||||||
run: |
|
|
||||||
sudo rm -rf /usr/local/opt/qt6/share/qt/plugins/virtualkeyboard /usr/local/opt/qt6/share/qt/plugins/platforminputcontexts
|
|
||||||
sudo rm -f /usr/local/Cellar/qt/*/share/qt/plugins/imageformats/libqpdf.dylib
|
|
||||||
|
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v3
|
||||||
with:
|
with:
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
|
|
||||||
- name: Create Build Environment
|
- name: Create Build Environment
|
||||||
shell: bash
|
|
||||||
run: cmake -E make_directory build
|
run: cmake -E make_directory build
|
||||||
|
|
||||||
- name: Configure CMake
|
- name: Configure CMake
|
||||||
shell: bash
|
|
||||||
env:
|
env:
|
||||||
MACOSX_DEPLOYMENT_TARGET: 10.15
|
MACOSX_DEPLOYMENT_TARGET: 10.15
|
||||||
PKG_CONFIG_PATH: /usr/local/lib/pkgconfig
|
PKG_CONFIG_PATH: /usr/local/lib/pkgconfig
|
||||||
@@ -588,34 +567,33 @@ jobs:
|
|||||||
|
|
||||||
- name: Build
|
- name: Build
|
||||||
working-directory: build
|
working-directory: build
|
||||||
shell: bash
|
|
||||||
run: cmake --build . --config Release --parallel 4
|
run: cmake --build . --config Release --parallel 4
|
||||||
|
|
||||||
- name: Install
|
- name: Install
|
||||||
working-directory: build
|
working-directory: build
|
||||||
shell: bash
|
|
||||||
run: make install
|
run: make install
|
||||||
|
|
||||||
|
- name: Remove problematic files
|
||||||
|
run: |
|
||||||
|
sudo rm -rf /usr/local/opt/qt6/share/qt/plugins/virtualkeyboard /usr/local/opt/qt6/share/qt/plugins/platforminputcontexts
|
||||||
|
sudo rm -f /usr/local/Cellar/qt/*/share/qt/plugins/imageformats/libqpdf.dylib
|
||||||
|
|
||||||
- name: Manually copy files not handled by macdeployqt
|
- name: Manually copy files not handled by macdeployqt
|
||||||
working-directory: build
|
working-directory: build
|
||||||
shell: bash
|
|
||||||
run: |
|
run: |
|
||||||
mkdir -p strawberry.app/Contents/Frameworks/
|
mkdir -p strawberry.app/Contents/Frameworks/
|
||||||
cp /usr/local/lib/libsoup-3.0.0.dylib strawberry.app/Contents/Frameworks/
|
cp /usr/local/lib/libsoup-3.0.0.dylib strawberry.app/Contents/Frameworks/
|
||||||
|
|
||||||
- name: Deploy
|
- name: Deploy
|
||||||
working-directory: build
|
working-directory: build
|
||||||
shell: bash
|
|
||||||
run: make deploy
|
run: make deploy
|
||||||
|
|
||||||
- name: Deploy check
|
- name: Deploy check
|
||||||
working-directory: build
|
working-directory: build
|
||||||
shell: bash
|
|
||||||
run: make deploycheck
|
run: make deploycheck
|
||||||
|
|
||||||
- name: Create DMG
|
- name: Create DMG
|
||||||
working-directory: build
|
working-directory: build
|
||||||
shell: bash
|
|
||||||
run: make dmg
|
run: make dmg
|
||||||
|
|
||||||
- uses: actions/upload-artifact@v3
|
- uses: actions/upload-artifact@v3
|
||||||
@@ -657,48 +635,42 @@ jobs:
|
|||||||
arch: [ 'i686', 'x86_64' ]
|
arch: [ 'i686', 'x86_64' ]
|
||||||
build_type: [ 'debug', 'release' ]
|
build_type: [ 'debug', 'release' ]
|
||||||
container:
|
container:
|
||||||
image: jonaski/strawberry-mxe-${{matrix.arch}}
|
image: jonaski/strawberry-mxe-${{matrix.arch}}-${{matrix.build_type}}
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v3
|
||||||
with:
|
with:
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
|
|
||||||
- name: Add safe git directory
|
- name: Add safe git directory
|
||||||
shell: bash
|
|
||||||
run: git config --global --add safe.directory ${GITHUB_WORKSPACE}
|
run: git config --global --add safe.directory ${GITHUB_WORKSPACE}
|
||||||
|
|
||||||
- name: Create Build Environment
|
- name: Create Build Environment
|
||||||
shell: bash
|
|
||||||
run: cmake -E make_directory build
|
run: cmake -E make_directory build
|
||||||
|
|
||||||
- name: Link MXE directory
|
- name: Link MXE directory
|
||||||
shell: bash
|
|
||||||
run: ln -s /strawberry-mxe ~/mxe-shared
|
run: ln -s /strawberry-mxe ~/mxe-shared
|
||||||
|
|
||||||
- name: Set ENABLE_WIN32_CONSOLE (debug)
|
- name: Set ENABLE_WIN32_CONSOLE (debug)
|
||||||
if: matrix.build_type == 'debug'
|
if: matrix.build_type == 'debug'
|
||||||
shell: bash
|
|
||||||
run: echo "win32_console=ON" >> $GITHUB_ENV
|
run: echo "win32_console=ON" >> $GITHUB_ENV
|
||||||
|
|
||||||
- name: Set ENABLE_WIN32_CONSOLE (release)
|
- name: Set ENABLE_WIN32_CONSOLE (release)
|
||||||
if: matrix.build_type == 'release'
|
if: matrix.build_type == 'release'
|
||||||
shell: bash
|
|
||||||
run: echo "win32_console=OFF" >> $GITHUB_ENV
|
run: echo "win32_console=OFF" >> $GITHUB_ENV
|
||||||
|
|
||||||
- name: Run CMake
|
- name: Run CMake
|
||||||
shell: bash
|
|
||||||
env:
|
env:
|
||||||
PKG_CONFIG_PATH: /strawberry-mxe/usr/${{matrix.arch}}-w64-mingw32.shared/lib/pkgconfig
|
PKG_CONFIG_PATH: /strawberry-mxe/usr/${{matrix.arch}}-w64-mingw32.shared/lib/pkgconfig
|
||||||
working-directory: build
|
working-directory: build
|
||||||
run: >
|
run: >
|
||||||
cmake ..
|
cmake ..
|
||||||
-DCMAKE_TOOLCHAIN_FILE="../cmake/Toolchain-${{matrix.arch}}-w64-mingw32-shared.cmake"
|
-DCMAKE_TOOLCHAIN_FILE="../cmake/Toolchain-${{matrix.arch}}-w64-mingw32-shared.cmake"
|
||||||
-DCMAKE_BUILD_TYPE="${{env.build_type}}"
|
-DCMAKE_BUILD_TYPE="${{matrix.build_type}}"
|
||||||
-DCMAKE_PREFIX_PATH="/strawberry-mxe/usr/${{matrix.arch}}-w64-mingw32.shared/qt6"
|
-DCMAKE_PREFIX_PATH="/strawberry-mxe/usr/${{matrix.arch}}-w64-mingw32.shared/qt6"
|
||||||
-DBUILD_WITH_QT6=ON
|
-DBUILD_WITH_QT6=ON
|
||||||
-DBUILD_WERROR=OFF
|
-DBUILD_WERROR=OFF
|
||||||
-DARCH="${{matrix.arch}}"
|
-DARCH="${{matrix.arch}}"
|
||||||
-DENABLE_WIN32_CONSOLE=${{env.build_type}}
|
-DENABLE_WIN32_CONSOLE=${{env.win32_console}}
|
||||||
-DENABLE_DBUS=OFF
|
-DENABLE_DBUS=OFF
|
||||||
-DENABLE_LIBGPOD=OFF
|
-DENABLE_LIBGPOD=OFF
|
||||||
-DENABLE_LIBMTP=OFF
|
-DENABLE_LIBMTP=OFF
|
||||||
@@ -745,6 +717,11 @@ jobs:
|
|||||||
working-directory: build
|
working-directory: build
|
||||||
run: cp /strawberry-mxe/usr/${{matrix.arch}}-w64-mingw32.shared/bin/{sqlite3.exe,gst-launch-1.0.exe,gst-discoverer-1.0.exe,libsoup-3.0-0.dll,libnghttp2.dll} .
|
run: cp /strawberry-mxe/usr/${{matrix.arch}}-w64-mingw32.shared/bin/{sqlite3.exe,gst-launch-1.0.exe,gst-discoverer-1.0.exe,libsoup-3.0-0.dll,libnghttp2.dll} .
|
||||||
|
|
||||||
|
- name: Copy extra binaries (debug)
|
||||||
|
if: matrix.build_type == 'debug'
|
||||||
|
working-directory: build
|
||||||
|
run: cp /strawberry-mxe/usr/${{matrix.arch}}-w64-mingw32.shared/bin/{gdb.exe,libreadline8.dll} .
|
||||||
|
|
||||||
- name: Copy dependencies
|
- name: Copy dependencies
|
||||||
working-directory: build
|
working-directory: build
|
||||||
run: >
|
run: >
|
||||||
@@ -780,7 +757,7 @@ jobs:
|
|||||||
|
|
||||||
build-windows-msvc:
|
build-windows-msvc:
|
||||||
name: Build Windows MSVC
|
name: Build Windows MSVC
|
||||||
runs-on: windows-2019
|
runs-on: windows-2022
|
||||||
strategy:
|
strategy:
|
||||||
fail-fast: false
|
fail-fast: false
|
||||||
matrix:
|
matrix:
|
||||||
@@ -791,7 +768,9 @@ jobs:
|
|||||||
- uses: ilammy/msvc-dev-cmd@v1
|
- uses: ilammy/msvc-dev-cmd@v1
|
||||||
with:
|
with:
|
||||||
arch: ${{matrix.arch}}
|
arch: ${{matrix.arch}}
|
||||||
toolset: 14.29
|
sdk: 10.0.20348.0
|
||||||
|
vsversion: 17
|
||||||
|
toolset: 14.3
|
||||||
|
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v3
|
||||||
with:
|
with:
|
||||||
@@ -800,7 +779,7 @@ jobs:
|
|||||||
- name: Delete conflicting files
|
- name: Delete conflicting files
|
||||||
shell: bash
|
shell: bash
|
||||||
run: |
|
run: |
|
||||||
rm -f /c/programdata/chocolatey/bin/{addr2line.exe,ar.exe,as.exe,c++.exe,c++filt.exe,cc1.exe,cc1plus.exe,cpp.exe,g++.exe,gcc-ar.exe,gcc-nm.exe,gcc-ranlib.exe,gcc.exe,gdb.exe,gfortran.exe,ld.bfd.exe,ld.exe,ld.gold.exe,nm.exe,ranlib.exe,readelf.exe,windres.exe,x86_64-w64-mingw32-c++.exe,x86_64-w64-mingw32-g++.exe,x86_64-w64-mingw32-gcc-8.1.0.exe,x86_64-w64-mingw32-gcc-ar.exe,x86_64-w64-mingw32-gcc-nm.exe,x86_64-w64-mingw32-gcc-ranlib.exe,x86_64-w64-mingw32-gcc.exe,x86_64-w64-mingw32-gfortran.exe}
|
rm -f /c/programdata/chocolatey/bin/{addr2line.exe,ar.exe,as.exe,c++.exe,c++filt.exe,cc1.exe,cc1plus.exe,cpp.exe,g++.exe,gcc-ar.exe,gcc-nm.exe,gcc-ranlib.exe,gcc.exe,gdb.exe,gfortran.exe,ld.bfd.exe,ld.exe,ld.gold.exe,nm.exe,ranlib.exe,readelf.exe,windres.exe,x86_64-w64-mingw32-c++.exe,x86_64-w64-mingw32-g++.exe,x86_64-w64-mingw32-gcc-8.1.0.exe,x86_64-w64-mingw32-gcc-ar.exe,x86_64-w64-mingw32-gcc-nm.exe,x86_64-w64-mingw32-gcc-ranlib.exe,x86_64-w64-mingw32-gcc.exe,x86_64-w64-mingw32-gfortran.exe,ccache.exe}
|
||||||
rm -f /c/strawberry/c/bin/{addr2line.exe,ar.exe,as.exe,c++.exe,c++filt.exe,cpp.exe,g++.exe,gcc-ar.exe,gcc-nm.exe,gcc-ranlib.exe,gcc.exe,ld.exe,nm.exe,ranlib.exe,readelf.exe,widl.exe,windmc.exe,windres.exe,x86_64-w64-mingw32-c++.exe,x86_64-w64-mingw32-g++.exe,x86_64-w64-mingw32-gcc-8.3.0.exe,x86_64-w64-mingw32-gcc-ar.exe,x86_64-w64-mingw32-gcc-nm.exe,x86_64-w64-mingw32-gcc-ranlib.exe,x86_64-w64-mingw32-gcc.exe,x86_64-w64-mingw32-gfortran.exe}
|
rm -f /c/strawberry/c/bin/{addr2line.exe,ar.exe,as.exe,c++.exe,c++filt.exe,cpp.exe,g++.exe,gcc-ar.exe,gcc-nm.exe,gcc-ranlib.exe,gcc.exe,ld.exe,nm.exe,ranlib.exe,readelf.exe,widl.exe,windmc.exe,windres.exe,x86_64-w64-mingw32-c++.exe,x86_64-w64-mingw32-g++.exe,x86_64-w64-mingw32-gcc-8.3.0.exe,x86_64-w64-mingw32-gcc-ar.exe,x86_64-w64-mingw32-gcc-nm.exe,x86_64-w64-mingw32-gcc-ranlib.exe,x86_64-w64-mingw32-gcc.exe,x86_64-w64-mingw32-gfortran.exe}
|
||||||
|
|
||||||
- name: Get latest MSVC dependencies
|
- name: Get latest MSVC dependencies
|
||||||
@@ -1063,7 +1042,7 @@ jobs:
|
|||||||
upload-windows-msvc:
|
upload-windows-msvc:
|
||||||
name: Upload Windows MSVC Setup
|
name: Upload Windows MSVC Setup
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
if: github.ref == 'refs/heads/master'
|
if: github.ref == 'refs/heads/master' || github.ref == 'refs/heads/msvc'
|
||||||
needs:
|
needs:
|
||||||
- build-windows-msvc
|
- build-windows-msvc
|
||||||
steps:
|
steps:
|
||||||
|
|||||||
88
.github/workflows/codeql.yml
vendored
Normal file
88
.github/workflows/codeql.yml
vendored
Normal file
@@ -0,0 +1,88 @@
|
|||||||
|
name: CodeQL
|
||||||
|
on: [push, pull_request]
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
codeql:
|
||||||
|
name: CodeQL Analyze
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
|
container:
|
||||||
|
image: opensuse/tumbleweed
|
||||||
|
|
||||||
|
steps:
|
||||||
|
|
||||||
|
- name: Refresh repositories
|
||||||
|
run: zypper -n --gpg-auto-import-keys ref
|
||||||
|
|
||||||
|
- name: Upgrade packages
|
||||||
|
run: zypper -n --gpg-auto-import-keys dup
|
||||||
|
|
||||||
|
- name: Install packages
|
||||||
|
run: >
|
||||||
|
zypper -n --gpg-auto-import-keys in
|
||||||
|
lsb-release
|
||||||
|
rpm-build
|
||||||
|
git
|
||||||
|
tar
|
||||||
|
gcc
|
||||||
|
gcc-c++
|
||||||
|
make
|
||||||
|
cmake
|
||||||
|
gettext-tools
|
||||||
|
glibc-devel
|
||||||
|
libboost_headers-devel
|
||||||
|
boost-devel
|
||||||
|
glib2-devel
|
||||||
|
glib2-tools
|
||||||
|
dbus-1-devel
|
||||||
|
alsa-devel
|
||||||
|
libnotify-devel
|
||||||
|
libgnutls-devel
|
||||||
|
protobuf-devel
|
||||||
|
sqlite3-devel
|
||||||
|
libpulse-devel
|
||||||
|
gstreamer-devel
|
||||||
|
gstreamer-plugins-base-devel
|
||||||
|
vlc-devel
|
||||||
|
taglib-devel
|
||||||
|
libicu-devel
|
||||||
|
libcdio-devel
|
||||||
|
libgpod-devel
|
||||||
|
libmtp-devel
|
||||||
|
libchromaprint-devel
|
||||||
|
qt6-core-devel
|
||||||
|
qt6-gui-devel
|
||||||
|
qt6-widgets-devel
|
||||||
|
qt6-concurrent-devel
|
||||||
|
qt6-network-devel
|
||||||
|
qt6-sql-devel
|
||||||
|
qt6-dbus-devel
|
||||||
|
qt6-test-devel
|
||||||
|
qt6-base-common-devel
|
||||||
|
qt6-sql-sqlite
|
||||||
|
qt6-linguist-devel
|
||||||
|
desktop-file-utils
|
||||||
|
update-desktop-files
|
||||||
|
appstream-glib
|
||||||
|
hicolor-icon-theme
|
||||||
|
|
||||||
|
- name: Checkout repository
|
||||||
|
uses: actions/checkout@v3
|
||||||
|
with:
|
||||||
|
fetch-depth: 0
|
||||||
|
|
||||||
|
- name: Add safe git directory
|
||||||
|
run: git config --global --add safe.directory ${GITHUB_WORKSPACE}
|
||||||
|
|
||||||
|
- name: Initialize CodeQL
|
||||||
|
uses: github/codeql-action/init@v2
|
||||||
|
with:
|
||||||
|
languages: cpp
|
||||||
|
|
||||||
|
- name: Autobuild
|
||||||
|
uses: github/codeql-action/autobuild@v2
|
||||||
|
|
||||||
|
- name: Perform CodeQL Analysis
|
||||||
|
uses: github/codeql-action/analyze@v2
|
||||||
|
with:
|
||||||
|
category: "/language:cpp"
|
||||||
2
3rdparty/macdeployqt/shared.cpp
vendored
2
3rdparty/macdeployqt/shared.cpp
vendored
@@ -1315,7 +1315,7 @@ void deployPlugins(const ApplicationBundleInfo &appBundleInfo, const QString &pl
|
|||||||
<< "libgstfdkaac.dylib"
|
<< "libgstfdkaac.dylib"
|
||||||
<< "libgstflac.dylib"
|
<< "libgstflac.dylib"
|
||||||
<< "libgstgio.dylib"
|
<< "libgstgio.dylib"
|
||||||
<< "libgstgme.dylib"
|
//<< "libgstgme.dylib"
|
||||||
<< "libgsthls.dylib"
|
<< "libgsthls.dylib"
|
||||||
<< "libgsticydemux.dylib"
|
<< "libgsticydemux.dylib"
|
||||||
<< "libgstid3demux.dylib"
|
<< "libgstid3demux.dylib"
|
||||||
|
|||||||
30
3rdparty/singleapplication/CMakeLists.txt
vendored
30
3rdparty/singleapplication/CMakeLists.txt
vendored
@@ -6,31 +6,7 @@ include(CheckFunctionExists)
|
|||||||
check_function_exists(geteuid HAVE_GETEUID)
|
check_function_exists(geteuid HAVE_GETEUID)
|
||||||
check_function_exists(getpwuid HAVE_GETPWUID)
|
check_function_exists(getpwuid HAVE_GETPWUID)
|
||||||
|
|
||||||
set(SINGLEAPP-SOURCES singleapplication.cpp singleapplication_p.cpp)
|
|
||||||
set(SINGLEAPP-MOC-HEADERS singleapplication.h singleapplication_p.h)
|
|
||||||
qt_wrap_cpp(SINGLEAPP-SOURCES-MOC ${SINGLEAPP-MOC-HEADERS})
|
|
||||||
add_library(singleapplication STATIC ${SINGLEAPP-SOURCES} ${SINGLEAPP-SOURCES-MOC})
|
|
||||||
target_include_directories(singleapplication PRIVATE
|
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}
|
|
||||||
${CMAKE_CURRENT_BINARY_DIR}
|
|
||||||
)
|
|
||||||
target_link_libraries(singleapplication PRIVATE
|
|
||||||
${QtCore_LIBRARIES}
|
|
||||||
${QtWidgets_LIBRARIES}
|
|
||||||
${QtNetwork_LIBRARIES}
|
|
||||||
)
|
|
||||||
|
|
||||||
set(SINGLECOREAPP-SOURCES singlecoreapplication.cpp singlecoreapplication_p.cpp)
|
|
||||||
set(SINGLECOREAPP-MOC-HEADERS singlecoreapplication.h singlecoreapplication_p.h)
|
|
||||||
qt_wrap_cpp(SINGLECOREAPP-SOURCES-MOC ${SINGLECOREAPP-MOC-HEADERS})
|
|
||||||
add_library(singlecoreapplication STATIC ${SINGLECOREAPP-SOURCES} ${SINGLECOREAPP-SOURCES-MOC})
|
|
||||||
target_include_directories(singlecoreapplication PRIVATE
|
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}
|
|
||||||
${CMAKE_CURRENT_BINARY_DIR}
|
|
||||||
)
|
|
||||||
target_link_libraries(singlecoreapplication PRIVATE
|
|
||||||
${QtCore_LIBRARIES}
|
|
||||||
${QtNetwork_LIBRARIES}
|
|
||||||
)
|
|
||||||
|
|
||||||
configure_file(config.h.in "${CMAKE_CURRENT_BINARY_DIR}/config.h")
|
configure_file(config.h.in "${CMAKE_CURRENT_BINARY_DIR}/config.h")
|
||||||
|
|
||||||
|
add_subdirectory(singleapplication)
|
||||||
|
add_subdirectory(singlecoreapplication)
|
||||||
|
|||||||
266
3rdparty/singleapplication/singleapplication.cpp
vendored
266
3rdparty/singleapplication/singleapplication.cpp
vendored
@@ -1,266 +0,0 @@
|
|||||||
// The MIT License (MIT)
|
|
||||||
//
|
|
||||||
// Copyright (c) Itay Grudev 2015 - 2020
|
|
||||||
//
|
|
||||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
||||||
// of this software and associated documentation files (the "Software"), to deal
|
|
||||||
// in the Software without restriction, including without limitation the rights
|
|
||||||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
||||||
// copies of the Software, and to permit persons to whom the Software is
|
|
||||||
// furnished to do so, subject to the following conditions:
|
|
||||||
//
|
|
||||||
// The above copyright notice and this permission notice shall be included in
|
|
||||||
// all copies or substantial portions of the Software.
|
|
||||||
//
|
|
||||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
||||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
||||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
||||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
||||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
||||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
|
||||||
// THE SOFTWARE.
|
|
||||||
|
|
||||||
//
|
|
||||||
// W A R N I N G !!!
|
|
||||||
// -----------------
|
|
||||||
//
|
|
||||||
// This is a modified version of SingleApplication,
|
|
||||||
// The original version is at:
|
|
||||||
//
|
|
||||||
// https://github.com/itay-grudev/SingleApplication
|
|
||||||
//
|
|
||||||
//
|
|
||||||
|
|
||||||
#include <cstdlib>
|
|
||||||
#include <limits>
|
|
||||||
|
|
||||||
#include <QtGlobal>
|
|
||||||
#include <QApplication>
|
|
||||||
#include <QThread>
|
|
||||||
#include <QSharedMemory>
|
|
||||||
#include <QLocalSocket>
|
|
||||||
#include <QByteArray>
|
|
||||||
#include <QElapsedTimer>
|
|
||||||
#include <QtDebug>
|
|
||||||
|
|
||||||
#include "singleapplication.h"
|
|
||||||
#include "singleapplication_p.h"
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Constructor. Checks and fires up LocalServer or closes the program if another instance already exists
|
|
||||||
* @param argc
|
|
||||||
* @param argv
|
|
||||||
* @param allowSecondary Whether to enable secondary instance support
|
|
||||||
* @param options Optional flags to toggle specific behaviour
|
|
||||||
* @param timeout Maximum time blocking functions are allowed during app load
|
|
||||||
*/
|
|
||||||
SingleApplication::SingleApplication(int &argc, char *argv[], const bool allowSecondary, const Options options, const int timeout)
|
|
||||||
: app_t(argc, argv),
|
|
||||||
d_ptr(new SingleApplicationPrivate(this)) {
|
|
||||||
|
|
||||||
Q_D(SingleApplication);
|
|
||||||
|
|
||||||
// Store the current mode of the program
|
|
||||||
d->options_ = options;
|
|
||||||
|
|
||||||
// Generating an application ID used for identifying the shared memory block and QLocalServer
|
|
||||||
d->genBlockServerName();
|
|
||||||
|
|
||||||
// To mitigate QSharedMemory issues with large amount of processes attempting to attach at the same time
|
|
||||||
SingleApplicationPrivate::randomSleep();
|
|
||||||
|
|
||||||
#ifdef Q_OS_UNIX
|
|
||||||
// By explicitly attaching it and then deleting it we make sure that the memory is deleted even after the process has crashed on Unix.
|
|
||||||
d->memory_ = new QSharedMemory(d->blockServerName_);
|
|
||||||
d->memory_->attach();
|
|
||||||
delete d->memory_;
|
|
||||||
#endif
|
|
||||||
|
|
||||||
// Guarantee thread safe behaviour with a shared memory block.
|
|
||||||
d->memory_ = new QSharedMemory(d->blockServerName_);
|
|
||||||
|
|
||||||
// Create a shared memory block
|
|
||||||
if (d->memory_->create(sizeof(InstancesInfo))) {
|
|
||||||
// Initialize the shared memory block
|
|
||||||
if (!d->memory_->lock()) {
|
|
||||||
qCritical() << "SingleApplication: Unable to lock memory block after create.";
|
|
||||||
abortSafely();
|
|
||||||
}
|
|
||||||
d->initializeMemoryBlock();
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
if (d->memory_->error() == QSharedMemory::AlreadyExists) {
|
|
||||||
// Attempt to attach to the memory segment
|
|
||||||
if (!d->memory_->attach()) {
|
|
||||||
qCritical() << "SingleApplication: Unable to attach to shared memory block.";
|
|
||||||
abortSafely();
|
|
||||||
}
|
|
||||||
if (!d->memory_->lock()) {
|
|
||||||
qCritical() << "SingleApplication: Unable to lock memory block after attach.";
|
|
||||||
abortSafely();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
qCritical() << "SingleApplication: Unable to create block.";
|
|
||||||
abortSafely();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
InstancesInfo *instance = static_cast<InstancesInfo*>(d->memory_->data());
|
|
||||||
QElapsedTimer time;
|
|
||||||
time.start();
|
|
||||||
|
|
||||||
// Make sure the shared memory block is initialised and in consistent state
|
|
||||||
forever {
|
|
||||||
// If the shared memory block's checksum is valid continue
|
|
||||||
if (d->blockChecksum() == instance->checksum) break;
|
|
||||||
|
|
||||||
// If more than 5s have elapsed, assume the primary instance crashed and assume it's position
|
|
||||||
if (time.elapsed() > 5000) {
|
|
||||||
qWarning() << "SingleApplication: Shared memory block has been in an inconsistent state from more than 5s. Assuming primary instance failure.";
|
|
||||||
d->initializeMemoryBlock();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Otherwise wait for a random period and try again.
|
|
||||||
// The random sleep here limits the probability of a collision between two racing apps and allows the app to initialise faster
|
|
||||||
if (!d->memory_->unlock()) {
|
|
||||||
qDebug() << "SingleApplication: Unable to unlock memory for random wait.";
|
|
||||||
qDebug() << d->memory_->errorString();
|
|
||||||
}
|
|
||||||
SingleApplicationPrivate::randomSleep();
|
|
||||||
if (!d->memory_->lock()) {
|
|
||||||
qCritical() << "SingleApplication: Unable to lock memory after random wait.";
|
|
||||||
abortSafely();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!instance->primary) {
|
|
||||||
d->startPrimary();
|
|
||||||
if (!d->memory_->unlock()) {
|
|
||||||
qDebug() << "SingleApplication: Unable to unlock memory after primary start.";
|
|
||||||
qDebug() << d->memory_->errorString();
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check if another instance can be started
|
|
||||||
if (allowSecondary) {
|
|
||||||
d->startSecondary();
|
|
||||||
if (d->options_ & Mode::SecondaryNotification) {
|
|
||||||
d->connectToPrimary(timeout, SingleApplicationPrivate::SecondaryInstance);
|
|
||||||
}
|
|
||||||
if (!d->memory_->unlock()) {
|
|
||||||
qDebug() << "SingleApplication: Unable to unlock memory after secondary start.";
|
|
||||||
qDebug() << d->memory_->errorString();
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!d->memory_->unlock()) {
|
|
||||||
qDebug() << "SingleApplication: Unable to unlock memory at end of execution.";
|
|
||||||
qDebug() << d->memory_->errorString();
|
|
||||||
}
|
|
||||||
|
|
||||||
d->connectToPrimary(timeout, SingleApplicationPrivate::NewInstance);
|
|
||||||
|
|
||||||
delete d;
|
|
||||||
|
|
||||||
::exit(EXIT_SUCCESS);
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
SingleApplication::~SingleApplication() {
|
|
||||||
Q_D(SingleApplication);
|
|
||||||
delete d;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Checks if the current application instance is primary.
|
|
||||||
* @return Returns true if the instance is primary, false otherwise.
|
|
||||||
*/
|
|
||||||
bool SingleApplication::isPrimary() const {
|
|
||||||
Q_D(const SingleApplication);
|
|
||||||
return d->server_ != nullptr;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Checks if the current application instance is secondary.
|
|
||||||
* @return Returns true if the instance is secondary, false otherwise.
|
|
||||||
*/
|
|
||||||
bool SingleApplication::isSecondary() const {
|
|
||||||
Q_D(const SingleApplication);
|
|
||||||
return d->server_ == nullptr;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Allows you to identify an instance by returning unique consecutive instance ids.
|
|
||||||
* It is reset when the first (primary) instance of your app starts and only incremented afterwards.
|
|
||||||
* @return Returns a unique instance id.
|
|
||||||
*/
|
|
||||||
quint32 SingleApplication::instanceId() const {
|
|
||||||
Q_D(const SingleApplication);
|
|
||||||
return d->instanceNumber_;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the OS PID (Process Identifier) of the process running the primary instance.
|
|
||||||
* Especially useful when SingleApplication is coupled with OS. specific APIs.
|
|
||||||
* @return Returns the primary instance PID.
|
|
||||||
*/
|
|
||||||
qint64 SingleApplication::primaryPid() const {
|
|
||||||
Q_D(const SingleApplication);
|
|
||||||
return d->primaryPid();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the username the primary instance is running as.
|
|
||||||
* @return Returns the username the primary instance is running as.
|
|
||||||
*/
|
|
||||||
QString SingleApplication::primaryUser() const {
|
|
||||||
Q_D(const SingleApplication);
|
|
||||||
return d->primaryUser();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the username the current instance is running as.
|
|
||||||
* @return Returns the username the current instance is running as.
|
|
||||||
*/
|
|
||||||
QString SingleApplication::currentUser() const {
|
|
||||||
return SingleApplicationPrivate::getUsername();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Sends message to the Primary Instance.
|
|
||||||
* @param message The message to send.
|
|
||||||
* @param timeout the maximum timeout in milliseconds for blocking functions.
|
|
||||||
* @return true if the message was sent successfully, false otherwise.
|
|
||||||
*/
|
|
||||||
bool SingleApplication::sendMessage(const QByteArray &message, const int timeout) {
|
|
||||||
|
|
||||||
Q_D(SingleApplication);
|
|
||||||
|
|
||||||
// Nobody to connect to
|
|
||||||
if (isPrimary()) return false;
|
|
||||||
|
|
||||||
// Make sure the socket is connected
|
|
||||||
if (!d->connectToPrimary(timeout, SingleApplicationPrivate::Reconnect)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return d->writeConfirmedMessage(timeout, message);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Cleans up the shared memory block and exits with a failure.
|
|
||||||
* This function halts program execution.
|
|
||||||
*/
|
|
||||||
void SingleApplication::abortSafely() {
|
|
||||||
|
|
||||||
Q_D(SingleApplication);
|
|
||||||
|
|
||||||
qCritical() << "SingleApplication: " << d->memory_->error() << d->memory_->errorString();
|
|
||||||
delete d;
|
|
||||||
|
|
||||||
::exit(EXIT_FAILURE);
|
|
||||||
|
|
||||||
}
|
|
||||||
18
3rdparty/singleapplication/singleapplication/CMakeLists.txt
vendored
Normal file
18
3rdparty/singleapplication/singleapplication/CMakeLists.txt
vendored
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
cmake_minimum_required(VERSION 3.7)
|
||||||
|
|
||||||
|
add_definitions(-DSINGLEAPPLICATION)
|
||||||
|
|
||||||
|
set(SOURCES ../singleapplication_t.cpp ../singleapplication_p.cpp)
|
||||||
|
set(HEADERS ../singleapplication_t.h ../singleapplication_p.h)
|
||||||
|
qt_wrap_cpp(MOC ${HEADERS})
|
||||||
|
add_library(singleapplication STATIC ${SOURCES} ${MOC})
|
||||||
|
target_include_directories(singleapplication PUBLIC
|
||||||
|
${CMAKE_CURRENT_SOURCE_DIR}/..
|
||||||
|
${CMAKE_CURRENT_BINARY_DIR}/..
|
||||||
|
${Boost_INCLUDE_DIRS}
|
||||||
|
)
|
||||||
|
target_link_libraries(singleapplication PUBLIC
|
||||||
|
${QtCore_LIBRARIES}
|
||||||
|
${QtWidgets_LIBRARIES}
|
||||||
|
${QtNetwork_LIBRARIES}
|
||||||
|
)
|
||||||
13
3rdparty/singleapplication/singleapplication/singleapplication.h
vendored
Normal file
13
3rdparty/singleapplication/singleapplication/singleapplication.h
vendored
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
#ifndef SINGLEAPPLICATION_H
|
||||||
|
#define SINGLEAPPLICATION_H
|
||||||
|
|
||||||
|
#ifdef SINGLEAPPLICATION
|
||||||
|
# error "SINGLEAPPLICATION already defined."
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#define SINGLEAPPLICATION
|
||||||
|
#include "../singleapplication_t.h"
|
||||||
|
#undef SINGLEAPPLICATION_T_H
|
||||||
|
#undef SINGLEAPPLICATION
|
||||||
|
|
||||||
|
#endif // SINGLEAPPLICATION_H
|
||||||
114
3rdparty/singleapplication/singleapplication_p.cpp
vendored
114
3rdparty/singleapplication/singleapplication_p.cpp
vendored
@@ -68,44 +68,41 @@
|
|||||||
# include <QDateTime>
|
# include <QDateTime>
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#include "singleapplication.h"
|
#include "singleapplication_t.h"
|
||||||
#include "singleapplication_p.h"
|
#include "singleapplication_p.h"
|
||||||
|
|
||||||
SingleApplicationPrivate::SingleApplicationPrivate(SingleApplication *ptr)
|
SingleApplicationPrivateClass::SingleApplicationPrivateClass(SingleApplicationClass *ptr)
|
||||||
: q_ptr(ptr),
|
: q_ptr(ptr),
|
||||||
memory_(nullptr),
|
memory_(nullptr),
|
||||||
socket_(nullptr),
|
socket_(nullptr),
|
||||||
server_(nullptr),
|
server_(nullptr),
|
||||||
instanceNumber_(-1) {}
|
instanceNumber_(-1) {}
|
||||||
|
|
||||||
SingleApplicationPrivate::~SingleApplicationPrivate() {
|
SingleApplicationPrivateClass::~SingleApplicationPrivateClass() {
|
||||||
|
|
||||||
if (socket_ != nullptr) {
|
if (socket_ != nullptr && socket_->isOpen()) {
|
||||||
socket_->close();
|
socket_->close();
|
||||||
delete socket_;
|
|
||||||
socket_ = nullptr;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (memory_ != nullptr) {
|
if (memory_ != nullptr) {
|
||||||
memory_->lock();
|
memory_->lock();
|
||||||
InstancesInfo *instance = static_cast<InstancesInfo*>(memory_->data());
|
|
||||||
if (server_ != nullptr) {
|
if (server_ != nullptr) {
|
||||||
server_->close();
|
server_->close();
|
||||||
delete server_;
|
InstancesInfo *instance = static_cast<InstancesInfo*>(memory_->data());
|
||||||
instance->primary = false;
|
instance->primary = false;
|
||||||
instance->primaryPid = -1;
|
instance->primaryPid = -1;
|
||||||
instance->primaryUser[0] = '\0';
|
instance->primaryUser[0] = '\0';
|
||||||
instance->checksum = blockChecksum();
|
instance->checksum = blockChecksum();
|
||||||
}
|
}
|
||||||
memory_->unlock();
|
memory_->unlock();
|
||||||
|
if (memory_->isAttached()) {
|
||||||
delete memory_;
|
memory_->detach();
|
||||||
memory_ = nullptr;
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
QString SingleApplicationPrivate::getUsername() {
|
QString SingleApplicationPrivateClass::getUsername() {
|
||||||
|
|
||||||
#ifdef Q_OS_UNIX
|
#ifdef Q_OS_UNIX
|
||||||
QString username;
|
QString username;
|
||||||
@@ -141,36 +138,36 @@ QString SingleApplicationPrivate::getUsername() {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void SingleApplicationPrivate::genBlockServerName() {
|
void SingleApplicationPrivateClass::genBlockServerName() {
|
||||||
|
|
||||||
QCryptographicHash appData(QCryptographicHash::Sha256);
|
QCryptographicHash appData(QCryptographicHash::Sha256);
|
||||||
appData.addData("SingleApplication");
|
appData.addData("SingleApplication");
|
||||||
appData.addData(SingleApplication::app_t::applicationName().toUtf8());
|
appData.addData(SingleApplicationClass::applicationName().toUtf8());
|
||||||
appData.addData(SingleApplication::app_t::organizationName().toUtf8());
|
appData.addData(SingleApplicationClass::organizationName().toUtf8());
|
||||||
appData.addData(SingleApplication::app_t::organizationDomain().toUtf8());
|
appData.addData(SingleApplicationClass::organizationDomain().toUtf8());
|
||||||
|
|
||||||
if (!(options_ & SingleApplication::Mode::ExcludeAppVersion)) {
|
if (!(options_ & SingleApplicationClass::Mode::ExcludeAppVersion)) {
|
||||||
appData.addData(SingleApplication::app_t::applicationVersion().toUtf8());
|
appData.addData(SingleApplicationClass::applicationVersion().toUtf8());
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!(options_ & SingleApplication::Mode::ExcludeAppPath)) {
|
if (!(options_ & SingleApplicationClass::Mode::ExcludeAppPath)) {
|
||||||
#if defined(Q_OS_UNIX)
|
#if defined(Q_OS_UNIX)
|
||||||
const QByteArray appImagePath = qgetenv("APPIMAGE");
|
const QByteArray appImagePath = qgetenv("APPIMAGE");
|
||||||
if (appImagePath.isEmpty()) {
|
if (appImagePath.isEmpty()) {
|
||||||
appData.addData(SingleApplication::app_t::applicationFilePath().toUtf8());
|
appData.addData(SingleApplicationClass::applicationFilePath().toUtf8());
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
appData.addData(appImagePath);
|
appData.addData(appImagePath);
|
||||||
};
|
}
|
||||||
#elif defined(Q_OS_WIN)
|
#elif defined(Q_OS_WIN)
|
||||||
appData.addData(SingleApplication::app_t::applicationFilePath().toLower().toUtf8());
|
appData.addData(SingleApplicationClass::applicationFilePath().toLower().toUtf8());
|
||||||
#else
|
#else
|
||||||
appData.addData(SingleApplication::app_t::applicationFilePath().toUtf8());
|
appData.addData(SingleApplicationClass::applicationFilePath().toUtf8());
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
// User level block requires a user specific data in the hash
|
// User level block requires a user specific data in the hash
|
||||||
if (options_ & SingleApplication::Mode::User) {
|
if (options_ & SingleApplicationClass::Mode::User) {
|
||||||
appData.addData(getUsername().toUtf8());
|
appData.addData(getUsername().toUtf8());
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -179,7 +176,7 @@ void SingleApplicationPrivate::genBlockServerName() {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void SingleApplicationPrivate::initializeMemoryBlock() const {
|
void SingleApplicationPrivateClass::initializeMemoryBlock() const {
|
||||||
|
|
||||||
InstancesInfo *instance = static_cast<InstancesInfo*>(memory_->data());
|
InstancesInfo *instance = static_cast<InstancesInfo*>(memory_->data());
|
||||||
instance->primary = false;
|
instance->primary = false;
|
||||||
@@ -190,7 +187,7 @@ void SingleApplicationPrivate::initializeMemoryBlock() const {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void SingleApplicationPrivate::startPrimary() {
|
void SingleApplicationPrivateClass::startPrimary() {
|
||||||
|
|
||||||
// Reset the number of connections
|
// Reset the number of connections
|
||||||
InstancesInfo *instance = static_cast<InstancesInfo*>(memory_->data());
|
InstancesInfo *instance = static_cast<InstancesInfo*>(memory_->data());
|
||||||
@@ -203,10 +200,10 @@ void SingleApplicationPrivate::startPrimary() {
|
|||||||
// Successful creation means that no main process exists
|
// Successful creation means that no main process exists
|
||||||
// So we start a QLocalServer to listen for connections
|
// So we start a QLocalServer to listen for connections
|
||||||
QLocalServer::removeServer(blockServerName_);
|
QLocalServer::removeServer(blockServerName_);
|
||||||
server_ = new QLocalServer();
|
server_ = new QLocalServer(this);
|
||||||
|
|
||||||
// Restrict access to the socket according to the SingleApplication::Mode::User flag on User level or no restrictions
|
// Restrict access to the socket according to the SingleApplication::Mode::User flag on User level or no restrictions
|
||||||
if (options_ & SingleApplication::Mode::User) {
|
if (options_ & SingleApplicationClass::Mode::User) {
|
||||||
server_->setSocketOptions(QLocalServer::UserAccessOption);
|
server_->setSocketOptions(QLocalServer::UserAccessOption);
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
@@ -214,11 +211,11 @@ void SingleApplicationPrivate::startPrimary() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
server_->listen(blockServerName_);
|
server_->listen(blockServerName_);
|
||||||
QObject::connect(server_, &QLocalServer::newConnection, this, &SingleApplicationPrivate::slotConnectionEstablished);
|
QObject::connect(server_, &QLocalServer::newConnection, this, &SingleApplicationPrivateClass::slotConnectionEstablished);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void SingleApplicationPrivate::startSecondary() {
|
void SingleApplicationPrivateClass::startSecondary() {
|
||||||
|
|
||||||
InstancesInfo *instance = static_cast<InstancesInfo*>(memory_->data());
|
InstancesInfo *instance = static_cast<InstancesInfo*>(memory_->data());
|
||||||
|
|
||||||
@@ -228,18 +225,18 @@ void SingleApplicationPrivate::startSecondary() {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bool SingleApplicationPrivate::connectToPrimary(const int timeout, const ConnectionType connectionType) {
|
bool SingleApplicationPrivateClass::connectToPrimary(const int timeout, const ConnectionType connectionType) {
|
||||||
|
|
||||||
QElapsedTimer time;
|
|
||||||
time.start();
|
|
||||||
|
|
||||||
// Connect to the Local Server of the Primary Instance if not already connected.
|
// Connect to the Local Server of the Primary Instance if not already connected.
|
||||||
if (socket_ == nullptr) {
|
if (socket_ == nullptr) {
|
||||||
socket_ = new QLocalSocket();
|
socket_ = new QLocalSocket(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (socket_->state() == QLocalSocket::ConnectedState) return true;
|
if (socket_->state() == QLocalSocket::ConnectedState) return true;
|
||||||
|
|
||||||
|
QElapsedTimer time;
|
||||||
|
time.start();
|
||||||
|
|
||||||
if (socket_->state() != QLocalSocket::ConnectedState) {
|
if (socket_->state() != QLocalSocket::ConnectedState) {
|
||||||
|
|
||||||
forever {
|
forever {
|
||||||
@@ -261,7 +258,7 @@ bool SingleApplicationPrivate::connectToPrimary(const int timeout, const Connect
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Initialisation message according to the SingleApplication protocol
|
// Initialization message according to the SingleApplication protocol
|
||||||
QByteArray initMsg;
|
QByteArray initMsg;
|
||||||
QDataStream writeStream(&initMsg, QIODevice::WriteOnly);
|
QDataStream writeStream(&initMsg, QIODevice::WriteOnly);
|
||||||
writeStream.setVersion(QDataStream::Qt_5_8);
|
writeStream.setVersion(QDataStream::Qt_5_8);
|
||||||
@@ -282,11 +279,11 @@ bool SingleApplicationPrivate::connectToPrimary(const int timeout, const Connect
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void SingleApplicationPrivate::writeAck(QLocalSocket *sock) {
|
void SingleApplicationPrivateClass::writeAck(QLocalSocket *sock) {
|
||||||
sock->putChar('\n');
|
sock->putChar('\n');
|
||||||
}
|
}
|
||||||
|
|
||||||
bool SingleApplicationPrivate::writeConfirmedMessage(const int timeout, const QByteArray &msg) const {
|
bool SingleApplicationPrivateClass::writeConfirmedMessage(const int timeout, const QByteArray &msg) const {
|
||||||
|
|
||||||
QElapsedTimer time;
|
QElapsedTimer time;
|
||||||
time.start();
|
time.start();
|
||||||
@@ -306,7 +303,7 @@ bool SingleApplicationPrivate::writeConfirmedMessage(const int timeout, const QB
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bool SingleApplicationPrivate::writeConfirmedFrame(const int timeout, const QByteArray &msg) const {
|
bool SingleApplicationPrivateClass::writeConfirmedFrame(const int timeout, const QByteArray &msg) const {
|
||||||
|
|
||||||
socket_->write(msg);
|
socket_->write(msg);
|
||||||
socket_->flush();
|
socket_->flush();
|
||||||
@@ -321,7 +318,7 @@ bool SingleApplicationPrivate::writeConfirmedFrame(const int timeout, const QByt
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
quint16 SingleApplicationPrivate::blockChecksum() const {
|
quint16 SingleApplicationPrivateClass::blockChecksum() const {
|
||||||
|
|
||||||
#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
|
#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
|
||||||
quint16 checksum = qChecksum(QByteArray(static_cast<const char*>(memory_->constData()), offsetof(InstancesInfo, checksum)));
|
quint16 checksum = qChecksum(QByteArray(static_cast<const char*>(memory_->constData()), offsetof(InstancesInfo, checksum)));
|
||||||
@@ -333,7 +330,7 @@ quint16 SingleApplicationPrivate::blockChecksum() const {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
qint64 SingleApplicationPrivate::primaryPid() const {
|
qint64 SingleApplicationPrivateClass::primaryPid() const {
|
||||||
|
|
||||||
memory_->lock();
|
memory_->lock();
|
||||||
InstancesInfo *instance = static_cast<InstancesInfo*>(memory_->data());
|
InstancesInfo *instance = static_cast<InstancesInfo*>(memory_->data());
|
||||||
@@ -344,7 +341,7 @@ qint64 SingleApplicationPrivate::primaryPid() const {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
QString SingleApplicationPrivate::primaryUser() const {
|
QString SingleApplicationPrivateClass::primaryUser() const {
|
||||||
|
|
||||||
memory_->lock();
|
memory_->lock();
|
||||||
InstancesInfo *instance = static_cast<InstancesInfo*>(memory_->data());
|
InstancesInfo *instance = static_cast<InstancesInfo*>(memory_->data());
|
||||||
@@ -358,23 +355,23 @@ QString SingleApplicationPrivate::primaryUser() const {
|
|||||||
/**
|
/**
|
||||||
* @brief Executed when a connection has been made to the LocalServer
|
* @brief Executed when a connection has been made to the LocalServer
|
||||||
*/
|
*/
|
||||||
void SingleApplicationPrivate::slotConnectionEstablished() {
|
void SingleApplicationPrivateClass::slotConnectionEstablished() {
|
||||||
|
|
||||||
QLocalSocket *nextConnSocket = server_->nextPendingConnection();
|
QLocalSocket *nextConnSocket = server_->nextPendingConnection();
|
||||||
connectionMap_.insert(nextConnSocket, ConnectionInfo());
|
connectionMap_.insert(nextConnSocket, ConnectionInfo());
|
||||||
|
|
||||||
QObject::connect(nextConnSocket, &QLocalSocket::aboutToClose, this, [nextConnSocket, this]() {
|
QObject::connect(nextConnSocket, &QLocalSocket::aboutToClose, this, [this, nextConnSocket]() {
|
||||||
const ConnectionInfo &info = connectionMap_[nextConnSocket];
|
const ConnectionInfo &info = connectionMap_[nextConnSocket];
|
||||||
slotClientConnectionClosed(nextConnSocket, info.instanceId);
|
slotClientConnectionClosed(nextConnSocket, info.instanceId);
|
||||||
});
|
});
|
||||||
|
|
||||||
QObject::connect(nextConnSocket, &QLocalSocket::disconnected, nextConnSocket, &QLocalSocket::deleteLater);
|
QObject::connect(nextConnSocket, &QLocalSocket::disconnected, nextConnSocket, &QLocalSocket::deleteLater);
|
||||||
|
|
||||||
QObject::connect(nextConnSocket, &QLocalSocket::destroyed, this, [nextConnSocket, this]() {
|
QObject::connect(nextConnSocket, &QLocalSocket::destroyed, this, [this, nextConnSocket]() {
|
||||||
connectionMap_.remove(nextConnSocket);
|
connectionMap_.remove(nextConnSocket);
|
||||||
});
|
});
|
||||||
|
|
||||||
QObject::connect(nextConnSocket, &QLocalSocket::readyRead, this, [nextConnSocket, this]() {
|
QObject::connect(nextConnSocket, &QLocalSocket::readyRead, this, [this, nextConnSocket]() {
|
||||||
const ConnectionInfo &info = connectionMap_[nextConnSocket];
|
const ConnectionInfo &info = connectionMap_[nextConnSocket];
|
||||||
switch (info.stage) {
|
switch (info.stage) {
|
||||||
case StageInitHeader:
|
case StageInitHeader:
|
||||||
@@ -387,16 +384,16 @@ void SingleApplicationPrivate::slotConnectionEstablished() {
|
|||||||
readMessageHeader(nextConnSocket, StageConnectedBody);
|
readMessageHeader(nextConnSocket, StageConnectedBody);
|
||||||
break;
|
break;
|
||||||
case StageConnectedBody:
|
case StageConnectedBody:
|
||||||
this->slotDataAvailable(nextConnSocket, info.instanceId);
|
slotDataAvailable(nextConnSocket, info.instanceId);
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
break;
|
break;
|
||||||
};
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void SingleApplicationPrivate::readMessageHeader(QLocalSocket *sock, const SingleApplicationPrivate::ConnectionStage nextStage) {
|
void SingleApplicationPrivateClass::readMessageHeader(QLocalSocket *sock, const SingleApplicationPrivateClass::ConnectionStage nextStage) {
|
||||||
|
|
||||||
if (!connectionMap_.contains(sock)) {
|
if (!connectionMap_.contains(sock)) {
|
||||||
return;
|
return;
|
||||||
@@ -420,7 +417,7 @@ void SingleApplicationPrivate::readMessageHeader(QLocalSocket *sock, const Singl
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bool SingleApplicationPrivate::isFrameComplete(QLocalSocket *sock) {
|
bool SingleApplicationPrivateClass::isFrameComplete(QLocalSocket *sock) {
|
||||||
|
|
||||||
if (!connectionMap_.contains(sock)) {
|
if (!connectionMap_.contains(sock)) {
|
||||||
return false;
|
return false;
|
||||||
@@ -431,9 +428,9 @@ bool SingleApplicationPrivate::isFrameComplete(QLocalSocket *sock) {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void SingleApplicationPrivate::readInitMessageBody(QLocalSocket *sock) {
|
void SingleApplicationPrivateClass::readInitMessageBody(QLocalSocket *sock) {
|
||||||
|
|
||||||
Q_Q(SingleApplication);
|
Q_Q(SingleApplicationClass);
|
||||||
|
|
||||||
if (!isFrameComplete(sock)) {
|
if (!isFrameComplete(sock)) {
|
||||||
return;
|
return;
|
||||||
@@ -449,10 +446,9 @@ void SingleApplicationPrivate::readInitMessageBody(QLocalSocket *sock) {
|
|||||||
readStream >> latin1Name;
|
readStream >> latin1Name;
|
||||||
|
|
||||||
// connection type
|
// connection type
|
||||||
ConnectionType connectionType = InvalidConnection;
|
|
||||||
quint8 connTypeVal = InvalidConnection;
|
quint8 connTypeVal = InvalidConnection;
|
||||||
readStream >> connTypeVal;
|
readStream >> connTypeVal;
|
||||||
connectionType = static_cast<ConnectionType>(connTypeVal);
|
const ConnectionType connectionType = static_cast<ConnectionType>(connTypeVal);
|
||||||
|
|
||||||
// instance id
|
// instance id
|
||||||
quint32 instanceId = 0;
|
quint32 instanceId = 0;
|
||||||
@@ -479,7 +475,7 @@ void SingleApplicationPrivate::readInitMessageBody(QLocalSocket *sock) {
|
|||||||
info.instanceId = instanceId;
|
info.instanceId = instanceId;
|
||||||
info.stage = StageConnectedHeader;
|
info.stage = StageConnectedHeader;
|
||||||
|
|
||||||
if (connectionType == NewInstance || (connectionType == SecondaryInstance && options_ & SingleApplication::Mode::SecondaryNotification)) {
|
if (connectionType == NewInstance || (connectionType == SecondaryInstance && options_ & SingleApplicationClass::Mode::SecondaryNotification)) {
|
||||||
emit q->instanceStarted();
|
emit q->instanceStarted();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -487,9 +483,9 @@ void SingleApplicationPrivate::readInitMessageBody(QLocalSocket *sock) {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void SingleApplicationPrivate::slotDataAvailable(QLocalSocket *dataSocket, const quint32 instanceId) {
|
void SingleApplicationPrivateClass::slotDataAvailable(QLocalSocket *dataSocket, const quint32 instanceId) {
|
||||||
|
|
||||||
Q_Q(SingleApplication);
|
Q_Q(SingleApplicationClass);
|
||||||
|
|
||||||
if (!isFrameComplete(dataSocket)) {
|
if (!isFrameComplete(dataSocket)) {
|
||||||
return;
|
return;
|
||||||
@@ -506,7 +502,7 @@ void SingleApplicationPrivate::slotDataAvailable(QLocalSocket *dataSocket, const
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void SingleApplicationPrivate::slotClientConnectionClosed(QLocalSocket *closedSocket, const quint32 instanceId) {
|
void SingleApplicationPrivateClass::slotClientConnectionClosed(QLocalSocket *closedSocket, const quint32 instanceId) {
|
||||||
|
|
||||||
if (closedSocket->bytesAvailable() > 0) {
|
if (closedSocket->bytesAvailable() > 0) {
|
||||||
slotDataAvailable(closedSocket, instanceId);
|
slotDataAvailable(closedSocket, instanceId);
|
||||||
@@ -514,7 +510,7 @@ void SingleApplicationPrivate::slotClientConnectionClosed(QLocalSocket *closedSo
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void SingleApplicationPrivate::randomSleep() {
|
void SingleApplicationPrivateClass::randomSleep() {
|
||||||
|
|
||||||
#if QT_VERSION >= QT_VERSION_CHECK(5, 10, 0)
|
#if QT_VERSION >= QT_VERSION_CHECK(5, 10, 0)
|
||||||
QThread::msleep(QRandomGenerator::global()->bounded(8U, 18U));
|
QThread::msleep(QRandomGenerator::global()->bounded(8U, 18U));
|
||||||
|
|||||||
47
3rdparty/singleapplication/singleapplication_p.h
vendored
47
3rdparty/singleapplication/singleapplication_p.h
vendored
@@ -39,31 +39,19 @@
|
|||||||
#include <QString>
|
#include <QString>
|
||||||
#include <QHash>
|
#include <QHash>
|
||||||
|
|
||||||
#include "singleapplication.h"
|
#include "singleapplication_t.h"
|
||||||
|
|
||||||
class QLocalServer;
|
class QLocalServer;
|
||||||
class QLocalSocket;
|
class QLocalSocket;
|
||||||
class QSharedMemory;
|
class QSharedMemory;
|
||||||
|
|
||||||
struct InstancesInfo {
|
class SingleApplicationPrivateClass : public QObject {
|
||||||
bool primary;
|
|
||||||
quint32 secondary;
|
|
||||||
qint64 primaryPid;
|
|
||||||
char primaryUser[128];
|
|
||||||
quint16 checksum;
|
|
||||||
};
|
|
||||||
|
|
||||||
struct ConnectionInfo {
|
|
||||||
explicit ConnectionInfo() : msgLen(0), instanceId(0), stage(0) {}
|
|
||||||
quint64 msgLen;
|
|
||||||
quint32 instanceId;
|
|
||||||
quint8 stage;
|
|
||||||
};
|
|
||||||
|
|
||||||
class SingleApplicationPrivate : public QObject {
|
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
|
|
||||||
public:
|
public:
|
||||||
|
explicit SingleApplicationPrivateClass(SingleApplicationClass *ptr);
|
||||||
|
~SingleApplicationPrivateClass() override;
|
||||||
|
|
||||||
enum ConnectionType : quint8 {
|
enum ConnectionType : quint8 {
|
||||||
InvalidConnection = 0,
|
InvalidConnection = 0,
|
||||||
NewInstance = 1,
|
NewInstance = 1,
|
||||||
@@ -74,12 +62,25 @@ class SingleApplicationPrivate : public QObject {
|
|||||||
StageInitHeader = 0,
|
StageInitHeader = 0,
|
||||||
StageInitBody = 1,
|
StageInitBody = 1,
|
||||||
StageConnectedHeader = 2,
|
StageConnectedHeader = 2,
|
||||||
StageConnectedBody = 3,
|
StageConnectedBody = 3
|
||||||
};
|
};
|
||||||
Q_DECLARE_PUBLIC(SingleApplication)
|
Q_DECLARE_PUBLIC(SingleApplicationClass)
|
||||||
|
|
||||||
explicit SingleApplicationPrivate(SingleApplication *ptr);
|
struct InstancesInfo {
|
||||||
~SingleApplicationPrivate() override;
|
explicit InstancesInfo() : primary(false), secondary(0), primaryPid(0), checksum(0) {}
|
||||||
|
bool primary;
|
||||||
|
quint32 secondary;
|
||||||
|
qint64 primaryPid;
|
||||||
|
char primaryUser[128];
|
||||||
|
quint16 checksum;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct ConnectionInfo {
|
||||||
|
explicit ConnectionInfo() : msgLen(0), instanceId(0), stage(0) {}
|
||||||
|
quint64 msgLen;
|
||||||
|
quint32 instanceId;
|
||||||
|
quint8 stage;
|
||||||
|
};
|
||||||
|
|
||||||
static QString getUsername();
|
static QString getUsername();
|
||||||
void genBlockServerName();
|
void genBlockServerName();
|
||||||
@@ -98,13 +99,13 @@ class SingleApplicationPrivate : public QObject {
|
|||||||
bool writeConfirmedMessage(const int timeout, const QByteArray &msg) const;
|
bool writeConfirmedMessage(const int timeout, const QByteArray &msg) const;
|
||||||
static void randomSleep();
|
static void randomSleep();
|
||||||
|
|
||||||
SingleApplication *q_ptr;
|
SingleApplicationClass *q_ptr;
|
||||||
QSharedMemory *memory_;
|
QSharedMemory *memory_;
|
||||||
QLocalSocket *socket_;
|
QLocalSocket *socket_;
|
||||||
QLocalServer *server_;
|
QLocalServer *server_;
|
||||||
quint32 instanceNumber_;
|
quint32 instanceNumber_;
|
||||||
QString blockServerName_;
|
QString blockServerName_;
|
||||||
SingleApplication::Options options_;
|
SingleApplicationClass::Options options_;
|
||||||
QHash<QLocalSocket*, ConnectionInfo> connectionMap_;
|
QHash<QLocalSocket*, ConnectionInfo> connectionMap_;
|
||||||
|
|
||||||
public slots:
|
public slots:
|
||||||
|
|||||||
330
3rdparty/singleapplication/singleapplication_t.cpp
vendored
Normal file
330
3rdparty/singleapplication/singleapplication_t.cpp
vendored
Normal file
@@ -0,0 +1,330 @@
|
|||||||
|
// The MIT License (MIT)
|
||||||
|
//
|
||||||
|
// Copyright (c) Itay Grudev 2015 - 2020
|
||||||
|
//
|
||||||
|
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
// of this software and associated documentation files (the "Software"), to deal
|
||||||
|
// in the Software without restriction, including without limitation the rights
|
||||||
|
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
// copies of the Software, and to permit persons to whom the Software is
|
||||||
|
// furnished to do so, subject to the following conditions:
|
||||||
|
//
|
||||||
|
// The above copyright notice and this permission notice shall be included in
|
||||||
|
// all copies or substantial portions of the Software.
|
||||||
|
//
|
||||||
|
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
|
// THE SOFTWARE.
|
||||||
|
|
||||||
|
//
|
||||||
|
// W A R N I N G !!!
|
||||||
|
// -----------------
|
||||||
|
//
|
||||||
|
// This is a modified version of SingleApplication,
|
||||||
|
// The original version is at:
|
||||||
|
//
|
||||||
|
// https://github.com/itay-grudev/SingleApplication
|
||||||
|
//
|
||||||
|
//
|
||||||
|
|
||||||
|
#include <cstdlib>
|
||||||
|
#include <limits>
|
||||||
|
#include <memory>
|
||||||
|
|
||||||
|
#include <boost/scope_exit.hpp>
|
||||||
|
|
||||||
|
#include <QtGlobal>
|
||||||
|
#include <QThread>
|
||||||
|
#include <QSharedMemory>
|
||||||
|
#include <QLocalSocket>
|
||||||
|
#include <QByteArray>
|
||||||
|
#include <QElapsedTimer>
|
||||||
|
#include <QtDebug>
|
||||||
|
#if QT_VERSION >= QT_VERSION_CHECK(6, 6, 0)
|
||||||
|
# include <QNativeIpcKey>
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#include "singleapplication_t.h"
|
||||||
|
#include "singleapplication_p.h"
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Constructor. Checks and fires up LocalServer or closes the program if another instance already exists
|
||||||
|
* @param argc
|
||||||
|
* @param argv
|
||||||
|
* @param allowSecondary Whether to enable secondary instance support
|
||||||
|
* @param options Optional flags to toggle specific behaviour
|
||||||
|
* @param timeout Maximum time blocking functions are allowed during app load
|
||||||
|
*/
|
||||||
|
SingleApplicationClass::SingleApplicationClass(int &argc, char *argv[], const bool allowSecondary, const Options options, const int timeout)
|
||||||
|
: ApplicationClass(argc, argv),
|
||||||
|
d_ptr(new SingleApplicationPrivateClass(this)) {
|
||||||
|
|
||||||
|
#if defined(SINGLEAPPLICATION)
|
||||||
|
Q_D(SingleApplication);
|
||||||
|
#elif defined(SINGLECOREAPPLICATION)
|
||||||
|
Q_D(SingleCoreApplication);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// Store the current mode of the program
|
||||||
|
d->options_ = options;
|
||||||
|
|
||||||
|
// Generating an application ID used for identifying the shared memory block and QLocalServer
|
||||||
|
d->genBlockServerName();
|
||||||
|
|
||||||
|
// To mitigate QSharedMemory issues with large amount of processes attempting to attach at the same time
|
||||||
|
SingleApplicationPrivateClass::randomSleep();
|
||||||
|
|
||||||
|
#ifdef Q_OS_UNIX
|
||||||
|
// By explicitly attaching it and then deleting it we make sure that the memory is deleted even after the process has crashed on Unix.
|
||||||
|
{
|
||||||
|
# if QT_VERSION >= QT_VERSION_CHECK(6, 6, 0)
|
||||||
|
std::unique_ptr<QSharedMemory> memory = std::make_unique<QSharedMemory>(QNativeIpcKey(d->blockServerName_));
|
||||||
|
# else
|
||||||
|
std::unique_ptr<QSharedMemory> memory = std::make_unique<QSharedMemory>(d->blockServerName_);
|
||||||
|
# endif
|
||||||
|
if (memory->attach()) {
|
||||||
|
memory->detach();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// Guarantee thread safe behaviour with a shared memory block.
|
||||||
|
#if QT_VERSION >= QT_VERSION_CHECK(6, 6, 0)
|
||||||
|
QSharedMemory *memory = new QSharedMemory(QNativeIpcKey(d->blockServerName_), this);
|
||||||
|
#else
|
||||||
|
QSharedMemory *memory = new QSharedMemory(d->blockServerName_, this);
|
||||||
|
#endif
|
||||||
|
d->memory_ = memory;
|
||||||
|
|
||||||
|
bool primary = false;
|
||||||
|
|
||||||
|
// Create a shared memory block
|
||||||
|
if (d->memory_->create(sizeof(SingleApplicationPrivateClass::InstancesInfo))) {
|
||||||
|
primary = true;
|
||||||
|
}
|
||||||
|
else if (d->memory_->error() == QSharedMemory::AlreadyExists) {
|
||||||
|
if (!d->memory_->attach()) {
|
||||||
|
qCritical() << "SingleApplication: Unable to attach to shared memory block:" << d->memory_->error() << d->memory_->errorString();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
qCritical() << "SingleApplication: Unable to create shared memory block:" << d->memory_->error() << d->memory_->errorString();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool locked = false;
|
||||||
|
|
||||||
|
BOOST_SCOPE_EXIT((memory)(&locked)) {
|
||||||
|
if (locked && !memory->unlock()) {
|
||||||
|
qWarning() << "SingleApplication: Unable to unlock shared memory block:" << memory->error() << memory->errorString();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}BOOST_SCOPE_EXIT_END
|
||||||
|
|
||||||
|
if (!d->memory_->lock()) {
|
||||||
|
qCritical() << "SingleApplication: Unable to lock shared memory block:" << d->memory_->error() << d->memory_->errorString();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
locked = true;
|
||||||
|
|
||||||
|
if (primary) {
|
||||||
|
// Initialize the shared memory block
|
||||||
|
d->initializeMemoryBlock();
|
||||||
|
}
|
||||||
|
|
||||||
|
SingleApplicationPrivateClass::InstancesInfo *instance = static_cast<SingleApplicationPrivateClass::InstancesInfo*>(d->memory_->data());
|
||||||
|
QElapsedTimer time;
|
||||||
|
time.start();
|
||||||
|
|
||||||
|
// Make sure the shared memory block is initialized and in a consistent state
|
||||||
|
while (d->blockChecksum() != instance->checksum) {
|
||||||
|
|
||||||
|
// If more than 5 seconds have elapsed, assume the primary instance crashed and assume its position
|
||||||
|
if (time.elapsed() > 5000) {
|
||||||
|
qWarning() << "SingleApplication: Shared memory block has been in an inconsistent state from more than 5 seconds. Assuming primary instance failure.";
|
||||||
|
d->initializeMemoryBlock();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Otherwise wait for a random period and try again.
|
||||||
|
// The random sleep here limits the probability of a collision between two racing apps and allows the app to initialize faster
|
||||||
|
if (locked) {
|
||||||
|
if (d->memory_->unlock()) {
|
||||||
|
locked = false;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
qCritical() << "SingleApplication: Unable to unlock shared memory block for random wait:" << memory->error() << memory->errorString();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
SingleApplicationPrivateClass::randomSleep();
|
||||||
|
|
||||||
|
if (!d->memory_->lock()) {
|
||||||
|
qCritical() << "SingleApplication: Unable to lock shared memory block after random wait:" << memory->error() << memory->errorString();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
locked = true;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
if (instance->primary) {
|
||||||
|
// Check if another instance can be started
|
||||||
|
if (allowSecondary) {
|
||||||
|
d->startSecondary();
|
||||||
|
if (d->options_ & Mode::SecondaryNotification) {
|
||||||
|
d->connectToPrimary(timeout, SingleApplicationPrivateClass::SecondaryInstance);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
d->startPrimary();
|
||||||
|
primary = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (locked) {
|
||||||
|
if (d->memory_->unlock()) {
|
||||||
|
locked = false;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
qWarning() << "SingleApplication: Unable to unlock shared memory block:" << memory->error() << memory->errorString();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!primary && !allowSecondary) {
|
||||||
|
d->connectToPrimary(timeout, SingleApplicationPrivateClass::NewInstance);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
SingleApplicationClass::~SingleApplicationClass() {
|
||||||
|
|
||||||
|
#if defined(SINGLEAPPLICATION)
|
||||||
|
Q_D(SingleApplication);
|
||||||
|
#elif defined(SINGLECOREAPPLICATION)
|
||||||
|
Q_D(SingleCoreApplication);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
delete d;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks if the current application instance is primary.
|
||||||
|
* @return Returns true if the instance is primary, false otherwise.
|
||||||
|
*/
|
||||||
|
bool SingleApplicationClass::isPrimary() const {
|
||||||
|
|
||||||
|
#if defined(SINGLEAPPLICATION)
|
||||||
|
Q_D(const SingleApplication);
|
||||||
|
#elif defined(SINGLECOREAPPLICATION)
|
||||||
|
Q_D(const SingleCoreApplication);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
return d->server_ != nullptr;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks if the current application instance is secondary.
|
||||||
|
* @return Returns true if the instance is secondary, false otherwise.
|
||||||
|
*/
|
||||||
|
bool SingleApplicationClass::isSecondary() const {
|
||||||
|
|
||||||
|
#if defined(SINGLEAPPLICATION)
|
||||||
|
Q_D(const SingleApplication);
|
||||||
|
#elif defined(SINGLECOREAPPLICATION)
|
||||||
|
Q_D(const SingleCoreApplication);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
return d->server_ == nullptr;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Allows you to identify an instance by returning unique consecutive instance ids.
|
||||||
|
* It is reset when the first (primary) instance of your app starts and only incremented afterwards.
|
||||||
|
* @return Returns a unique instance id.
|
||||||
|
*/
|
||||||
|
quint32 SingleApplicationClass::instanceId() const {
|
||||||
|
|
||||||
|
#if defined(SINGLEAPPLICATION)
|
||||||
|
Q_D(const SingleApplication);
|
||||||
|
#elif defined(SINGLECOREAPPLICATION)
|
||||||
|
Q_D(const SingleCoreApplication);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
return d->instanceNumber_;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the OS PID (Process Identifier) of the process running the primary instance.
|
||||||
|
* Especially useful when SingleApplication is coupled with OS. specific APIs.
|
||||||
|
* @return Returns the primary instance PID.
|
||||||
|
*/
|
||||||
|
qint64 SingleApplicationClass::primaryPid() const {
|
||||||
|
|
||||||
|
#if defined(SINGLEAPPLICATION)
|
||||||
|
Q_D(const SingleApplication);
|
||||||
|
#elif defined(SINGLECOREAPPLICATION)
|
||||||
|
Q_D(const SingleCoreApplication);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
return d->primaryPid();
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the username the primary instance is running as.
|
||||||
|
* @return Returns the username the primary instance is running as.
|
||||||
|
*/
|
||||||
|
QString SingleApplicationClass::primaryUser() const {
|
||||||
|
|
||||||
|
#if defined(SINGLEAPPLICATION)
|
||||||
|
Q_D(const SingleApplication);
|
||||||
|
#elif defined(SINGLECOREAPPLICATION)
|
||||||
|
Q_D(const SingleCoreApplication);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
return d->primaryUser();
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the username the current instance is running as.
|
||||||
|
* @return Returns the username the current instance is running as.
|
||||||
|
*/
|
||||||
|
QString SingleApplicationClass::currentUser() const {
|
||||||
|
return SingleApplicationPrivateClass::getUsername();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sends message to the Primary Instance.
|
||||||
|
* @param message The message to send.
|
||||||
|
* @param timeout the maximum timeout in milliseconds for blocking functions.
|
||||||
|
* @return true if the message was sent successfully, false otherwise.
|
||||||
|
*/
|
||||||
|
bool SingleApplicationClass::sendMessage(const QByteArray &message, const int timeout) {
|
||||||
|
|
||||||
|
#if defined(SINGLEAPPLICATION)
|
||||||
|
Q_D(SingleApplication);
|
||||||
|
#elif defined(SINGLECOREAPPLICATION)
|
||||||
|
Q_D(SingleCoreApplication);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// Nobody to connect to
|
||||||
|
if (isPrimary()) return false;
|
||||||
|
|
||||||
|
// Make sure the socket is connected
|
||||||
|
if (!d->connectToPrimary(timeout, SingleApplicationPrivateClass::Reconnect)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return d->writeConfirmedMessage(timeout, message);
|
||||||
|
|
||||||
|
}
|
||||||
@@ -31,25 +31,41 @@
|
|||||||
//
|
//
|
||||||
//
|
//
|
||||||
|
|
||||||
#ifndef SINGLEAPPLICATION_H
|
#ifndef SINGLEAPPLICATION_T_H
|
||||||
#define SINGLEAPPLICATION_H
|
#define SINGLEAPPLICATION_T_H
|
||||||
|
|
||||||
#include <QtGlobal>
|
#include <QtGlobal>
|
||||||
#include <QApplication>
|
|
||||||
|
#undef ApplicationClass
|
||||||
|
#undef SingleApplicationClass
|
||||||
|
#undef SingleApplicationPrivateClass
|
||||||
|
|
||||||
|
#if defined(SINGLEAPPLICATION)
|
||||||
|
# include <QApplication>
|
||||||
|
# define ApplicationClass QApplication
|
||||||
|
# define SingleApplicationClass SingleApplication
|
||||||
|
# define SingleApplicationPrivateClass SingleApplicationPrivate
|
||||||
|
#elif defined(SINGLECOREAPPLICATION)
|
||||||
|
# include <QCoreApplication>
|
||||||
|
# define ApplicationClass QCoreApplication
|
||||||
|
# define SingleApplicationClass SingleCoreApplication
|
||||||
|
# define SingleApplicationPrivateClass SingleCoreApplicationPrivate
|
||||||
|
#else
|
||||||
|
# error "Define SINGLEAPPLICATION or SINGLECOREAPPLICATION."
|
||||||
|
#endif
|
||||||
|
|
||||||
#include <QFlags>
|
#include <QFlags>
|
||||||
#include <QByteArray>
|
#include <QByteArray>
|
||||||
|
|
||||||
class SingleApplicationPrivate;
|
class SingleApplicationPrivateClass;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief The SingleApplication class handles multiple instances of the same Application
|
* @brief The SingleApplication class handles multiple instances of the same Application
|
||||||
* @see QApplication
|
* @see QApplication
|
||||||
*/
|
*/
|
||||||
class SingleApplication : public QApplication { // clazy:exclude=ctor-missing-parent-argument
|
class SingleApplicationClass : public ApplicationClass { // clazy:exclude=ctor-missing-parent-argument
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
|
|
||||||
using app_t = QApplication;
|
|
||||||
|
|
||||||
public:
|
public:
|
||||||
/**
|
/**
|
||||||
* @brief Mode of operation of SingleApplication.
|
* @brief Mode of operation of SingleApplication.
|
||||||
@@ -61,7 +77,7 @@ class SingleApplication : public QApplication { // clazy:exclude=ctor-missing-p
|
|||||||
* block will be user wide.
|
* block will be user wide.
|
||||||
* @enum
|
* @enum
|
||||||
*/
|
*/
|
||||||
enum Mode {
|
enum class Mode {
|
||||||
User = 1 << 0,
|
User = 1 << 0,
|
||||||
System = 1 << 1,
|
System = 1 << 1,
|
||||||
SecondaryNotification = 1 << 2,
|
SecondaryNotification = 1 << 2,
|
||||||
@@ -86,11 +102,11 @@ class SingleApplication : public QApplication { // clazy:exclude=ctor-missing-p
|
|||||||
* instance and the secondary instance.
|
* instance and the secondary instance.
|
||||||
* @note The timeout is just a hint for the maximum time of blocking
|
* @note The timeout is just a hint for the maximum time of blocking
|
||||||
* operations. It does not guarantee that the SingleApplication
|
* operations. It does not guarantee that the SingleApplication
|
||||||
* initialisation will be completed in given time, though is a good hint.
|
* initialization will be completed in given time, though is a good hint.
|
||||||
* Usually 4*timeout would be the worst case (fail) scenario.
|
* Usually 4*timeout would be the worst case (fail) scenario.
|
||||||
*/
|
*/
|
||||||
explicit SingleApplication(int &argc, char *argv[], const bool allowSecondary = false, const Options options = Mode::User, const int timeout = 1000);
|
explicit SingleApplicationClass(int &argc, char *argv[], const bool allowSecondary = false, const Options options = Mode::User, const int timeout = 1000);
|
||||||
~SingleApplication() override;
|
~SingleApplicationClass() override;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Returns if the instance is the primary instance
|
* @brief Returns if the instance is the primary instance
|
||||||
@@ -142,11 +158,15 @@ class SingleApplication : public QApplication { // clazy:exclude=ctor-missing-p
|
|||||||
void receivedMessage(quint32 instanceId, QByteArray message);
|
void receivedMessage(quint32 instanceId, QByteArray message);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
SingleApplicationPrivate *d_ptr;
|
SingleApplicationPrivateClass *d_ptr;
|
||||||
|
#if defined(SINGLEAPPLICATION)
|
||||||
Q_DECLARE_PRIVATE(SingleApplication)
|
Q_DECLARE_PRIVATE(SingleApplication)
|
||||||
|
#elif defined(SINGLECOREAPPLICATION)
|
||||||
|
Q_DECLARE_PRIVATE(SingleCoreApplication)
|
||||||
|
#endif
|
||||||
void abortSafely();
|
void abortSafely();
|
||||||
};
|
};
|
||||||
|
|
||||||
Q_DECLARE_OPERATORS_FOR_FLAGS(SingleApplication::Options)
|
Q_DECLARE_OPERATORS_FOR_FLAGS(SingleApplicationClass::Options)
|
||||||
|
|
||||||
#endif // SINGLEAPPLICATION_H
|
#endif // SINGLEAPPLICATION_T_H
|
||||||
266
3rdparty/singleapplication/singlecoreapplication.cpp
vendored
266
3rdparty/singleapplication/singlecoreapplication.cpp
vendored
@@ -1,266 +0,0 @@
|
|||||||
// The MIT License (MIT)
|
|
||||||
//
|
|
||||||
// Copyright (c) Itay Grudev 2015 - 2020
|
|
||||||
//
|
|
||||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
||||||
// of this software and associated documentation files (the "Software"), to deal
|
|
||||||
// in the Software without restriction, including without limitation the rights
|
|
||||||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
||||||
// copies of the Software, and to permit persons to whom the Software is
|
|
||||||
// furnished to do so, subject to the following conditions:
|
|
||||||
//
|
|
||||||
// The above copyright notice and this permission notice shall be included in
|
|
||||||
// all copies or substantial portions of the Software.
|
|
||||||
//
|
|
||||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
||||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
||||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
||||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
||||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
||||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
|
||||||
// THE SOFTWARE.
|
|
||||||
|
|
||||||
//
|
|
||||||
// W A R N I N G !!!
|
|
||||||
// -----------------
|
|
||||||
//
|
|
||||||
// This is a modified version of SingleApplication,
|
|
||||||
// The original version is at:
|
|
||||||
//
|
|
||||||
// https://github.com/itay-grudev/SingleApplication
|
|
||||||
//
|
|
||||||
//
|
|
||||||
|
|
||||||
#include <cstdlib>
|
|
||||||
#include <limits>
|
|
||||||
|
|
||||||
#include <QtGlobal>
|
|
||||||
#include <QCoreApplication>
|
|
||||||
#include <QThread>
|
|
||||||
#include <QSharedMemory>
|
|
||||||
#include <QLocalSocket>
|
|
||||||
#include <QByteArray>
|
|
||||||
#include <QElapsedTimer>
|
|
||||||
#include <QtDebug>
|
|
||||||
|
|
||||||
#include "singlecoreapplication.h"
|
|
||||||
#include "singlecoreapplication_p.h"
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Constructor. Checks and fires up LocalServer or closes the program if another instance already exists
|
|
||||||
* @param argc
|
|
||||||
* @param argv
|
|
||||||
* @param allowSecondary Whether to enable secondary instance support
|
|
||||||
* @param options Optional flags to toggle specific behaviour
|
|
||||||
* @param timeout Maximum time blocking functions are allowed during app load
|
|
||||||
*/
|
|
||||||
SingleCoreApplication::SingleCoreApplication(int &argc, char *argv[], const bool allowSecondary, const Options options, const int timeout)
|
|
||||||
: app_t(argc, argv),
|
|
||||||
d_ptr(new SingleCoreApplicationPrivate(this)) {
|
|
||||||
|
|
||||||
Q_D(SingleCoreApplication);
|
|
||||||
|
|
||||||
// Store the current mode of the program
|
|
||||||
d->options_ = options;
|
|
||||||
|
|
||||||
// Generating an application ID used for identifying the shared memory block and QLocalServer
|
|
||||||
d->genBlockServerName();
|
|
||||||
|
|
||||||
// To mitigate QSharedMemory issues with large amount of processes attempting to attach at the same time
|
|
||||||
SingleCoreApplicationPrivate::randomSleep();
|
|
||||||
|
|
||||||
#ifdef Q_OS_UNIX
|
|
||||||
// By explicitly attaching it and then deleting it we make sure that the memory is deleted even after the process has crashed on Unix.
|
|
||||||
d->memory_ = new QSharedMemory(d->blockServerName_);
|
|
||||||
d->memory_->attach();
|
|
||||||
delete d->memory_;
|
|
||||||
#endif
|
|
||||||
|
|
||||||
// Guarantee thread safe behaviour with a shared memory block.
|
|
||||||
d->memory_ = new QSharedMemory(d->blockServerName_);
|
|
||||||
|
|
||||||
// Create a shared memory block
|
|
||||||
if (d->memory_->create(sizeof(InstancesInfo))) {
|
|
||||||
// Initialize the shared memory block
|
|
||||||
if (!d->memory_->lock()) {
|
|
||||||
qCritical() << "SingleCoreApplication: Unable to lock memory block after create.";
|
|
||||||
abortSafely();
|
|
||||||
}
|
|
||||||
d->initializeMemoryBlock();
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
if (d->memory_->error() == QSharedMemory::AlreadyExists) {
|
|
||||||
// Attempt to attach to the memory segment
|
|
||||||
if (!d->memory_->attach()) {
|
|
||||||
qCritical() << "SingleCoreApplication: Unable to attach to shared memory block.";
|
|
||||||
abortSafely();
|
|
||||||
}
|
|
||||||
if (!d->memory_->lock()) {
|
|
||||||
qCritical() << "SingleCoreApplication: Unable to lock memory block after attach.";
|
|
||||||
abortSafely();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
qCritical() << "SingleCoreApplication: Unable to create block.";
|
|
||||||
abortSafely();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
InstancesInfo *instance = static_cast<InstancesInfo*>(d->memory_->data());
|
|
||||||
QElapsedTimer time;
|
|
||||||
time.start();
|
|
||||||
|
|
||||||
// Make sure the shared memory block is initialised and in consistent state
|
|
||||||
forever {
|
|
||||||
// If the shared memory block's checksum is valid continue
|
|
||||||
if (d->blockChecksum() == instance->checksum) break;
|
|
||||||
|
|
||||||
// If more than 5s have elapsed, assume the primary instance crashed and assume it's position
|
|
||||||
if (time.elapsed() > 5000) {
|
|
||||||
qWarning() << "SingleCoreApplication: Shared memory block has been in an inconsistent state from more than 5s. Assuming primary instance failure.";
|
|
||||||
d->initializeMemoryBlock();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Otherwise wait for a random period and try again.
|
|
||||||
// The random sleep here limits the probability of a collision between two racing apps and allows the app to initialise faster
|
|
||||||
if (!d->memory_->unlock()) {
|
|
||||||
qDebug() << "SingleCoreApplication: Unable to unlock memory for random wait.";
|
|
||||||
qDebug() << d->memory_->errorString();
|
|
||||||
}
|
|
||||||
SingleCoreApplicationPrivate::randomSleep();
|
|
||||||
if (!d->memory_->lock()) {
|
|
||||||
qCritical() << "SingleCoreApplication: Unable to lock memory after random wait.";
|
|
||||||
abortSafely();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!instance->primary) {
|
|
||||||
d->startPrimary();
|
|
||||||
if (!d->memory_->unlock()) {
|
|
||||||
qDebug() << "SingleCoreApplication: Unable to unlock memory after primary start.";
|
|
||||||
qDebug() << d->memory_->errorString();
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check if another instance can be started
|
|
||||||
if (allowSecondary) {
|
|
||||||
d->startSecondary();
|
|
||||||
if (d->options_ & Mode::SecondaryNotification) {
|
|
||||||
d->connectToPrimary(timeout, SingleCoreApplicationPrivate::SecondaryInstance);
|
|
||||||
}
|
|
||||||
if (!d->memory_->unlock()) {
|
|
||||||
qDebug() << "SingleCoreApplication: Unable to unlock memory after secondary start.";
|
|
||||||
qDebug() << d->memory_->errorString();
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!d->memory_->unlock()) {
|
|
||||||
qDebug() << "SingleCoreApplication: Unable to unlock memory at end of execution.";
|
|
||||||
qDebug() << d->memory_->errorString();
|
|
||||||
}
|
|
||||||
|
|
||||||
d->connectToPrimary(timeout, SingleCoreApplicationPrivate::NewInstance);
|
|
||||||
|
|
||||||
delete d;
|
|
||||||
|
|
||||||
::exit(EXIT_SUCCESS);
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
SingleCoreApplication::~SingleCoreApplication() {
|
|
||||||
Q_D(SingleCoreApplication);
|
|
||||||
delete d;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Checks if the current application instance is primary.
|
|
||||||
* @return Returns true if the instance is primary, false otherwise.
|
|
||||||
*/
|
|
||||||
bool SingleCoreApplication::isPrimary() const {
|
|
||||||
Q_D(const SingleCoreApplication);
|
|
||||||
return d->server_ != nullptr;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Checks if the current application instance is secondary.
|
|
||||||
* @return Returns true if the instance is secondary, false otherwise.
|
|
||||||
*/
|
|
||||||
bool SingleCoreApplication::isSecondary() const {
|
|
||||||
Q_D(const SingleCoreApplication);
|
|
||||||
return d->server_ == nullptr;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Allows you to identify an instance by returning unique consecutive instance ids.
|
|
||||||
* It is reset when the first (primary) instance of your app starts and only incremented afterwards.
|
|
||||||
* @return Returns a unique instance id.
|
|
||||||
*/
|
|
||||||
quint32 SingleCoreApplication::instanceId() const {
|
|
||||||
Q_D(const SingleCoreApplication);
|
|
||||||
return d->instanceNumber_;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the OS PID (Process Identifier) of the process running the primary instance.
|
|
||||||
* Especially useful when SingleCoreApplication is coupled with OS. specific APIs.
|
|
||||||
* @return Returns the primary instance PID.
|
|
||||||
*/
|
|
||||||
qint64 SingleCoreApplication::primaryPid() const {
|
|
||||||
Q_D(const SingleCoreApplication);
|
|
||||||
return d->primaryPid();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the username the primary instance is running as.
|
|
||||||
* @return Returns the username the primary instance is running as.
|
|
||||||
*/
|
|
||||||
QString SingleCoreApplication::primaryUser() const {
|
|
||||||
Q_D(const SingleCoreApplication);
|
|
||||||
return d->primaryUser();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the username the current instance is running as.
|
|
||||||
* @return Returns the username the current instance is running as.
|
|
||||||
*/
|
|
||||||
QString SingleCoreApplication::currentUser() const {
|
|
||||||
return SingleCoreApplicationPrivate::getUsername();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Sends message to the Primary Instance.
|
|
||||||
* @param message The message to send.
|
|
||||||
* @param timeout the maximum timeout in milliseconds for blocking functions.
|
|
||||||
* @return true if the message was sent successfully, false otherwise.
|
|
||||||
*/
|
|
||||||
bool SingleCoreApplication::sendMessage(const QByteArray &message, const int timeout) {
|
|
||||||
|
|
||||||
Q_D(SingleCoreApplication);
|
|
||||||
|
|
||||||
// Nobody to connect to
|
|
||||||
if (isPrimary()) return false;
|
|
||||||
|
|
||||||
// Make sure the socket is connected
|
|
||||||
if (!d->connectToPrimary(timeout, SingleCoreApplicationPrivate::Reconnect)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return d->writeConfirmedMessage(timeout, message);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Cleans up the shared memory block and exits with a failure.
|
|
||||||
* This function halts program execution.
|
|
||||||
*/
|
|
||||||
void SingleCoreApplication::abortSafely() {
|
|
||||||
|
|
||||||
Q_D(SingleCoreApplication);
|
|
||||||
|
|
||||||
qCritical() << "SingleCoreApplication: " << d->memory_->error() << d->memory_->errorString();
|
|
||||||
delete d;
|
|
||||||
|
|
||||||
::exit(EXIT_FAILURE);
|
|
||||||
|
|
||||||
}
|
|
||||||
152
3rdparty/singleapplication/singlecoreapplication.h
vendored
152
3rdparty/singleapplication/singlecoreapplication.h
vendored
@@ -1,152 +0,0 @@
|
|||||||
// The MIT License (MIT)
|
|
||||||
//
|
|
||||||
// Copyright (c) Itay Grudev 2015 - 2020
|
|
||||||
//
|
|
||||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
||||||
// of this software and associated documentation files (the "Software"), to deal
|
|
||||||
// in the Software without restriction, including without limitation the rights
|
|
||||||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
||||||
// copies of the Software, and to permit persons to whom the Software is
|
|
||||||
// furnished to do so, subject to the following conditions:
|
|
||||||
//
|
|
||||||
// The above copyright notice and this permission notice shall be included in
|
|
||||||
// all copies or substantial portions of the Software.
|
|
||||||
//
|
|
||||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
||||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
||||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
||||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
||||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
||||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
|
||||||
// THE SOFTWARE.
|
|
||||||
|
|
||||||
//
|
|
||||||
// W A R N I N G !!!
|
|
||||||
// -----------------
|
|
||||||
//
|
|
||||||
// This is a modified version of SingleApplication,
|
|
||||||
// The original version is at:
|
|
||||||
//
|
|
||||||
// https://github.com/itay-grudev/SingleApplication
|
|
||||||
//
|
|
||||||
//
|
|
||||||
|
|
||||||
#ifndef SINGLECOREAPPLICATION_H
|
|
||||||
#define SINGLECOREAPPLICATION_H
|
|
||||||
|
|
||||||
#include <QtGlobal>
|
|
||||||
#include <QCoreApplication>
|
|
||||||
#include <QFlags>
|
|
||||||
#include <QByteArray>
|
|
||||||
|
|
||||||
class SingleCoreApplicationPrivate;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief The SingleCoreApplication class handles multiple instances of the same Application
|
|
||||||
* @see QCoreApplication
|
|
||||||
*/
|
|
||||||
class SingleCoreApplication : public QCoreApplication { // clazy:exclude=ctor-missing-parent-argument
|
|
||||||
Q_OBJECT
|
|
||||||
|
|
||||||
using app_t = QCoreApplication;
|
|
||||||
|
|
||||||
public:
|
|
||||||
/**
|
|
||||||
* @brief Mode of operation of SingleCoreApplication.
|
|
||||||
* Whether the block should be user-wide or system-wide and whether the
|
|
||||||
* primary instance should be notified when a secondary instance had been
|
|
||||||
* started.
|
|
||||||
* @note Operating system can restrict the shared memory blocks to the same
|
|
||||||
* user, in which case the User/System modes will have no effect and the
|
|
||||||
* block will be user wide.
|
|
||||||
* @enum
|
|
||||||
*/
|
|
||||||
enum Mode {
|
|
||||||
User = 1 << 0,
|
|
||||||
System = 1 << 1,
|
|
||||||
SecondaryNotification = 1 << 2,
|
|
||||||
ExcludeAppVersion = 1 << 3,
|
|
||||||
ExcludeAppPath = 1 << 4
|
|
||||||
};
|
|
||||||
Q_DECLARE_FLAGS(Options, Mode)
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Intitializes a SingleCoreApplication instance with argc command line
|
|
||||||
* arguments in argv
|
|
||||||
* @arg {int &} argc - Number of arguments in argv
|
|
||||||
* @arg {const char *[]} argv - Supplied command line arguments
|
|
||||||
* @arg {bool} allowSecondary - Whether to start the instance as secondary
|
|
||||||
* if there is already a primary instance.
|
|
||||||
* @arg {Mode} mode - Whether for the SingleCoreApplication block to be applied
|
|
||||||
* User wide or System wide.
|
|
||||||
* @arg {int} timeout - Timeout to wait in milliseconds.
|
|
||||||
* @note argc and argv may be changed as Qt removes arguments that it
|
|
||||||
* recognizes
|
|
||||||
* @note Mode::SecondaryNotification only works if set on both the primary
|
|
||||||
* instance and the secondary instance.
|
|
||||||
* @note The timeout is just a hint for the maximum time of blocking
|
|
||||||
* operations. It does not guarantee that the SingleCoreApplication
|
|
||||||
* initialisation will be completed in given time, though is a good hint.
|
|
||||||
* Usually 4*timeout would be the worst case (fail) scenario.
|
|
||||||
*/
|
|
||||||
explicit SingleCoreApplication(int &argc, char *argv[], const bool allowSecondary = false, const Options options = Mode::User, const int timeout = 1000);
|
|
||||||
~SingleCoreApplication() override;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Returns if the instance is the primary instance
|
|
||||||
* @returns {bool}
|
|
||||||
*/
|
|
||||||
bool isPrimary() const;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Returns if the instance is a secondary instance
|
|
||||||
* @returns {bool}
|
|
||||||
*/
|
|
||||||
bool isSecondary() const;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Returns a unique identifier for the current instance
|
|
||||||
* @returns {qint32}
|
|
||||||
*/
|
|
||||||
quint32 instanceId() const;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Returns the process ID (PID) of the primary instance
|
|
||||||
* @returns {qint64}
|
|
||||||
*/
|
|
||||||
qint64 primaryPid() const;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Returns the username of the user running the primary instance
|
|
||||||
* @returns {QString}
|
|
||||||
*/
|
|
||||||
QString primaryUser() const;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Returns the username of the current user
|
|
||||||
* @returns {QString}
|
|
||||||
*/
|
|
||||||
QString currentUser() const;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Sends a message to the primary instance. Returns true on success.
|
|
||||||
* @param {int} timeout - Timeout for connecting
|
|
||||||
* @returns {bool}
|
|
||||||
* @note sendMessage() will return false if invoked from the primary
|
|
||||||
* instance.
|
|
||||||
*/
|
|
||||||
bool sendMessage(const QByteArray &message, const int timeout = 1000);
|
|
||||||
|
|
||||||
signals:
|
|
||||||
void instanceStarted();
|
|
||||||
void receivedMessage(quint32 instanceId, QByteArray message);
|
|
||||||
|
|
||||||
private:
|
|
||||||
SingleCoreApplicationPrivate *d_ptr;
|
|
||||||
Q_DECLARE_PRIVATE(SingleCoreApplication)
|
|
||||||
void abortSafely();
|
|
||||||
};
|
|
||||||
|
|
||||||
Q_DECLARE_OPERATORS_FOR_FLAGS(SingleCoreApplication::Options)
|
|
||||||
|
|
||||||
#endif // SINGLECOREAPPLICATION_H
|
|
||||||
17
3rdparty/singleapplication/singlecoreapplication/CMakeLists.txt
vendored
Normal file
17
3rdparty/singleapplication/singlecoreapplication/CMakeLists.txt
vendored
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
cmake_minimum_required(VERSION 3.7)
|
||||||
|
|
||||||
|
add_definitions(-DSINGLECOREAPPLICATION)
|
||||||
|
|
||||||
|
set(SOURCES ../singleapplication_t.cpp ../singleapplication_p.cpp)
|
||||||
|
set(HEADERS ../singleapplication_t.h ../singleapplication_p.h)
|
||||||
|
qt_wrap_cpp(MOC ${HEADERS})
|
||||||
|
add_library(singlecoreapplication STATIC ${SOURCES} ${MOC})
|
||||||
|
target_include_directories(singlecoreapplication PUBLIC
|
||||||
|
${CMAKE_CURRENT_SOURCE_DIR}/..
|
||||||
|
${CMAKE_CURRENT_BINARY_DIR}/..
|
||||||
|
${Boost_INCLUDE_DIRS}
|
||||||
|
)
|
||||||
|
target_link_libraries(singlecoreapplication PUBLIC
|
||||||
|
${QtCore_LIBRARIES}
|
||||||
|
${QtNetwork_LIBRARIES}
|
||||||
|
)
|
||||||
13
3rdparty/singleapplication/singlecoreapplication/singlecoreapplication.h
vendored
Normal file
13
3rdparty/singleapplication/singlecoreapplication/singlecoreapplication.h
vendored
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
#ifndef SINGLECOREAPPLICATION_H
|
||||||
|
#define SINGLECOREAPPLICATION_H
|
||||||
|
|
||||||
|
#ifdef SINGLECOREAPPLICATION
|
||||||
|
# error "SINGLECOREAPPLICATION already defined."
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#define SINGLECOREAPPLICATION
|
||||||
|
#include "../singleapplication_t.h"
|
||||||
|
#undef SINGLEAPPLICATION_T_H
|
||||||
|
#undef SINGLECOREAPPLICATION
|
||||||
|
|
||||||
|
#endif // SINGLECOREAPPLICATION_H
|
||||||
@@ -1,526 +0,0 @@
|
|||||||
// The MIT License (MIT)
|
|
||||||
//
|
|
||||||
// Copyright (c) Itay Grudev 2015 - 2020
|
|
||||||
//
|
|
||||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
||||||
// of this software and associated documentation files (the "Software"), to deal
|
|
||||||
// in the Software without restriction, including without limitation the rights
|
|
||||||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
||||||
// copies of the Software, and to permit persons to whom the Software is
|
|
||||||
// furnished to do so, subject to the following conditions:
|
|
||||||
//
|
|
||||||
// The above copyright notice and this permission notice shall be included in
|
|
||||||
// all copies or substantial portions of the Software.
|
|
||||||
//
|
|
||||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
||||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
||||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
||||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
||||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
||||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
|
||||||
// THE SOFTWARE.
|
|
||||||
|
|
||||||
//
|
|
||||||
// W A R N I N G !!!
|
|
||||||
// -----------------
|
|
||||||
//
|
|
||||||
// This is a modified version of SingleApplication,
|
|
||||||
// The original version is at:
|
|
||||||
//
|
|
||||||
// https://github.com/itay-grudev/SingleApplication
|
|
||||||
//
|
|
||||||
//
|
|
||||||
|
|
||||||
#include "config.h"
|
|
||||||
|
|
||||||
#include <QtGlobal>
|
|
||||||
|
|
||||||
#include <cstdlib>
|
|
||||||
#include <cstddef>
|
|
||||||
|
|
||||||
#ifdef Q_OS_UNIX
|
|
||||||
# include <unistd.h>
|
|
||||||
# include <sys/types.h>
|
|
||||||
# include <pwd.h>
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#ifdef Q_OS_WIN
|
|
||||||
# ifndef NOMINMAX
|
|
||||||
# define NOMINMAX 1
|
|
||||||
# endif
|
|
||||||
# include <windows.h>
|
|
||||||
# include <lmcons.h>
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#include <QObject>
|
|
||||||
#include <QThread>
|
|
||||||
#include <QIODevice>
|
|
||||||
#include <QSharedMemory>
|
|
||||||
#include <QByteArray>
|
|
||||||
#include <QDataStream>
|
|
||||||
#include <QCryptographicHash>
|
|
||||||
#include <QLocalServer>
|
|
||||||
#include <QLocalSocket>
|
|
||||||
#include <QElapsedTimer>
|
|
||||||
#if QT_VERSION >= QT_VERSION_CHECK(5, 10, 0)
|
|
||||||
# include <QRandomGenerator>
|
|
||||||
#else
|
|
||||||
# include <QDateTime>
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#include "singlecoreapplication.h"
|
|
||||||
#include "singlecoreapplication_p.h"
|
|
||||||
|
|
||||||
SingleCoreApplicationPrivate::SingleCoreApplicationPrivate(SingleCoreApplication *ptr)
|
|
||||||
: q_ptr(ptr),
|
|
||||||
memory_(nullptr),
|
|
||||||
socket_(nullptr),
|
|
||||||
server_(nullptr),
|
|
||||||
instanceNumber_(-1) {}
|
|
||||||
|
|
||||||
SingleCoreApplicationPrivate::~SingleCoreApplicationPrivate() {
|
|
||||||
|
|
||||||
if (socket_ != nullptr) {
|
|
||||||
socket_->close();
|
|
||||||
delete socket_;
|
|
||||||
socket_ = nullptr;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (memory_ != nullptr) {
|
|
||||||
memory_->lock();
|
|
||||||
InstancesInfo *instance = static_cast<InstancesInfo*>(memory_->data());
|
|
||||||
if (server_ != nullptr) {
|
|
||||||
server_->close();
|
|
||||||
delete server_;
|
|
||||||
instance->primary = false;
|
|
||||||
instance->primaryPid = -1;
|
|
||||||
instance->primaryUser[0] = '\0';
|
|
||||||
instance->checksum = blockChecksum();
|
|
||||||
}
|
|
||||||
memory_->unlock();
|
|
||||||
|
|
||||||
delete memory_;
|
|
||||||
memory_ = nullptr;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
QString SingleCoreApplicationPrivate::getUsername() {
|
|
||||||
|
|
||||||
#ifdef Q_OS_UNIX
|
|
||||||
QString username;
|
|
||||||
#if defined(HAVE_GETEUID) && defined(HAVE_GETPWUID)
|
|
||||||
struct passwd *pw = getpwuid(geteuid());
|
|
||||||
if (pw) {
|
|
||||||
username = QString::fromLocal8Bit(pw->pw_name);
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
if (username.isEmpty()) {
|
|
||||||
#if QT_VERSION >= QT_VERSION_CHECK(5, 10, 0)
|
|
||||||
username = qEnvironmentVariable("USER");
|
|
||||||
#else
|
|
||||||
username = QString::fromLocal8Bit(qgetenv("USER"));
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
return username;
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#ifdef Q_OS_WIN
|
|
||||||
wchar_t username[UNLEN + 1];
|
|
||||||
// Specifies size of the buffer on input
|
|
||||||
DWORD usernameLength = UNLEN + 1;
|
|
||||||
if (GetUserNameW(username, &usernameLength)) {
|
|
||||||
return QString::fromWCharArray(username);
|
|
||||||
}
|
|
||||||
#if QT_VERSION >= QT_VERSION_CHECK(5, 10, 0)
|
|
||||||
return qEnvironmentVariable("USERNAME");
|
|
||||||
#else
|
|
||||||
return QString::fromLocal8Bit(qgetenv("USERNAME"));
|
|
||||||
#endif
|
|
||||||
#endif
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
void SingleCoreApplicationPrivate::genBlockServerName() {
|
|
||||||
|
|
||||||
QCryptographicHash appData(QCryptographicHash::Sha256);
|
|
||||||
appData.addData("SingleApplication");
|
|
||||||
appData.addData(SingleCoreApplication::app_t::applicationName().toUtf8());
|
|
||||||
appData.addData(SingleCoreApplication::app_t::organizationName().toUtf8());
|
|
||||||
appData.addData(SingleCoreApplication::app_t::organizationDomain().toUtf8());
|
|
||||||
|
|
||||||
if (!(options_ & SingleCoreApplication::Mode::ExcludeAppVersion)) {
|
|
||||||
appData.addData(SingleCoreApplication::app_t::applicationVersion().toUtf8());
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!(options_ & SingleCoreApplication::Mode::ExcludeAppPath)) {
|
|
||||||
#if defined(Q_OS_UNIX)
|
|
||||||
const QByteArray appImagePath = qgetenv("APPIMAGE");
|
|
||||||
if (appImagePath.isEmpty()) {
|
|
||||||
appData.addData(SingleCoreApplication::app_t::applicationFilePath().toUtf8());
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
appData.addData(appImagePath);
|
|
||||||
};
|
|
||||||
#elif defined(Q_OS_WIN)
|
|
||||||
appData.addData(SingleCoreApplication::app_t::applicationFilePath().toLower().toUtf8());
|
|
||||||
#else
|
|
||||||
appData.addData(SingleCoreApplication::app_t::applicationFilePath().toUtf8());
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
|
|
||||||
// User level block requires a user specific data in the hash
|
|
||||||
if (options_ & SingleCoreApplication::Mode::User) {
|
|
||||||
appData.addData(getUsername().toUtf8());
|
|
||||||
}
|
|
||||||
|
|
||||||
// Replace the backslash in RFC 2045 Base64 [a-zA-Z0-9+/=] to comply with server naming requirements.
|
|
||||||
blockServerName_ = appData.result().toBase64().replace("/", "_");
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
void SingleCoreApplicationPrivate::initializeMemoryBlock() const {
|
|
||||||
|
|
||||||
InstancesInfo *instance = static_cast<InstancesInfo*>(memory_->data());
|
|
||||||
instance->primary = false;
|
|
||||||
instance->secondary = 0;
|
|
||||||
instance->primaryPid = -1;
|
|
||||||
instance->primaryUser[0] = '\0';
|
|
||||||
instance->checksum = blockChecksum();
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
void SingleCoreApplicationPrivate::startPrimary() {
|
|
||||||
|
|
||||||
// Reset the number of connections
|
|
||||||
InstancesInfo *instance = static_cast<InstancesInfo*>(memory_->data());
|
|
||||||
|
|
||||||
instance->primary = true;
|
|
||||||
instance->primaryPid = QCoreApplication::applicationPid();
|
|
||||||
qstrncpy(instance->primaryUser, getUsername().toUtf8().data(), sizeof(instance->primaryUser));
|
|
||||||
instance->checksum = blockChecksum();
|
|
||||||
instanceNumber_ = 0;
|
|
||||||
// Successful creation means that no main process exists
|
|
||||||
// So we start a QLocalServer to listen for connections
|
|
||||||
QLocalServer::removeServer(blockServerName_);
|
|
||||||
server_ = new QLocalServer();
|
|
||||||
|
|
||||||
// Restrict access to the socket according to the SingleCoreApplication::Mode::User flag on User level or no restrictions
|
|
||||||
if (options_ & SingleCoreApplication::Mode::User) {
|
|
||||||
server_->setSocketOptions(QLocalServer::UserAccessOption);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
server_->setSocketOptions(QLocalServer::WorldAccessOption);
|
|
||||||
}
|
|
||||||
|
|
||||||
server_->listen(blockServerName_);
|
|
||||||
QObject::connect(server_, &QLocalServer::newConnection, this, &SingleCoreApplicationPrivate::slotConnectionEstablished);
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
void SingleCoreApplicationPrivate::startSecondary() {
|
|
||||||
|
|
||||||
InstancesInfo *instance = static_cast<InstancesInfo*>(memory_->data());
|
|
||||||
|
|
||||||
instance->secondary += 1;
|
|
||||||
instance->checksum = blockChecksum();
|
|
||||||
instanceNumber_ = instance->secondary;
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
bool SingleCoreApplicationPrivate::connectToPrimary(const int timeout, const ConnectionType connectionType) {
|
|
||||||
|
|
||||||
QElapsedTimer time;
|
|
||||||
time.start();
|
|
||||||
|
|
||||||
// Connect to the Local Server of the Primary Instance if not already connected.
|
|
||||||
if (socket_ == nullptr) {
|
|
||||||
socket_ = new QLocalSocket();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (socket_->state() == QLocalSocket::ConnectedState) return true;
|
|
||||||
|
|
||||||
if (socket_->state() != QLocalSocket::ConnectedState) {
|
|
||||||
|
|
||||||
forever {
|
|
||||||
randomSleep();
|
|
||||||
|
|
||||||
if (socket_->state() != QLocalSocket::ConnectingState) {
|
|
||||||
socket_->connectToServer(blockServerName_);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (socket_->state() == QLocalSocket::ConnectingState) {
|
|
||||||
socket_->waitForConnected(static_cast<int>(timeout - time.elapsed()));
|
|
||||||
}
|
|
||||||
|
|
||||||
// If connected break out of the loop
|
|
||||||
if (socket_->state() == QLocalSocket::ConnectedState) break;
|
|
||||||
|
|
||||||
// If elapsed time since start is longer than the method timeout return
|
|
||||||
if (time.elapsed() >= timeout) return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Initialisation message according to the SingleCoreApplication protocol
|
|
||||||
QByteArray initMsg;
|
|
||||||
QDataStream writeStream(&initMsg, QIODevice::WriteOnly);
|
|
||||||
writeStream.setVersion(QDataStream::Qt_5_8);
|
|
||||||
|
|
||||||
writeStream << blockServerName_.toLatin1();
|
|
||||||
writeStream << static_cast<quint8>(connectionType);
|
|
||||||
writeStream << instanceNumber_;
|
|
||||||
|
|
||||||
#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
|
|
||||||
quint16 checksum = qChecksum(QByteArray(initMsg, static_cast<quint32>(initMsg.length())));
|
|
||||||
#else
|
|
||||||
quint16 checksum = qChecksum(initMsg.constData(), static_cast<quint32>(initMsg.length()));
|
|
||||||
#endif
|
|
||||||
|
|
||||||
writeStream << checksum;
|
|
||||||
|
|
||||||
return writeConfirmedMessage(static_cast<int>(timeout - time.elapsed()), initMsg);
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
void SingleCoreApplicationPrivate::writeAck(QLocalSocket *sock) {
|
|
||||||
sock->putChar('\n');
|
|
||||||
}
|
|
||||||
|
|
||||||
bool SingleCoreApplicationPrivate::writeConfirmedMessage(const int timeout, const QByteArray &msg) const {
|
|
||||||
|
|
||||||
QElapsedTimer time;
|
|
||||||
time.start();
|
|
||||||
|
|
||||||
// Frame 1: The header indicates the message length that follows
|
|
||||||
QByteArray header;
|
|
||||||
QDataStream headerStream(&header, QIODevice::WriteOnly);
|
|
||||||
headerStream.setVersion(QDataStream::Qt_5_8);
|
|
||||||
headerStream << static_cast<quint64>(msg.length());
|
|
||||||
|
|
||||||
if (!writeConfirmedFrame(static_cast<int>(timeout - time.elapsed()), header)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Frame 2: The message
|
|
||||||
return writeConfirmedFrame(static_cast<int>(timeout - time.elapsed()), msg);
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
bool SingleCoreApplicationPrivate::writeConfirmedFrame(const int timeout, const QByteArray &msg) const {
|
|
||||||
|
|
||||||
socket_->write(msg);
|
|
||||||
socket_->flush();
|
|
||||||
|
|
||||||
bool result = socket_->waitForReadyRead(timeout);
|
|
||||||
if (result) {
|
|
||||||
socket_->read(1);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
quint16 SingleCoreApplicationPrivate::blockChecksum() const {
|
|
||||||
|
|
||||||
#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
|
|
||||||
quint16 checksum = qChecksum(QByteArray(static_cast<const char*>(memory_->constData()), offsetof(InstancesInfo, checksum)));
|
|
||||||
#else
|
|
||||||
quint16 checksum = qChecksum(static_cast<const char*>(memory_->constData()), offsetof(InstancesInfo, checksum));
|
|
||||||
#endif
|
|
||||||
|
|
||||||
return checksum;
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
qint64 SingleCoreApplicationPrivate::primaryPid() const {
|
|
||||||
|
|
||||||
memory_->lock();
|
|
||||||
InstancesInfo *instance = static_cast<InstancesInfo*>(memory_->data());
|
|
||||||
qint64 pid = instance->primaryPid;
|
|
||||||
memory_->unlock();
|
|
||||||
|
|
||||||
return pid;
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
QString SingleCoreApplicationPrivate::primaryUser() const {
|
|
||||||
|
|
||||||
memory_->lock();
|
|
||||||
InstancesInfo *instance = static_cast<InstancesInfo*>(memory_->data());
|
|
||||||
QByteArray username = instance->primaryUser;
|
|
||||||
memory_->unlock();
|
|
||||||
|
|
||||||
return QString::fromUtf8(username);
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Executed when a connection has been made to the LocalServer
|
|
||||||
*/
|
|
||||||
void SingleCoreApplicationPrivate::slotConnectionEstablished() {
|
|
||||||
|
|
||||||
QLocalSocket *nextConnSocket = server_->nextPendingConnection();
|
|
||||||
connectionMap_.insert(nextConnSocket, ConnectionInfo());
|
|
||||||
|
|
||||||
QObject::connect(nextConnSocket, &QLocalSocket::aboutToClose, this, [nextConnSocket, this]() {
|
|
||||||
const ConnectionInfo &info = connectionMap_[nextConnSocket];
|
|
||||||
slotClientConnectionClosed(nextConnSocket, info.instanceId);
|
|
||||||
});
|
|
||||||
|
|
||||||
QObject::connect(nextConnSocket, &QLocalSocket::disconnected, nextConnSocket, &QLocalSocket::deleteLater);
|
|
||||||
|
|
||||||
QObject::connect(nextConnSocket, &QLocalSocket::destroyed, this, [nextConnSocket, this]() {
|
|
||||||
connectionMap_.remove(nextConnSocket);
|
|
||||||
});
|
|
||||||
|
|
||||||
QObject::connect(nextConnSocket, &QLocalSocket::readyRead, this, [nextConnSocket, this]() {
|
|
||||||
const ConnectionInfo &info = connectionMap_[nextConnSocket];
|
|
||||||
switch (info.stage) {
|
|
||||||
case StageInitHeader:
|
|
||||||
readMessageHeader(nextConnSocket, StageInitBody);
|
|
||||||
break;
|
|
||||||
case StageInitBody:
|
|
||||||
readInitMessageBody(nextConnSocket);
|
|
||||||
break;
|
|
||||||
case StageConnectedHeader:
|
|
||||||
readMessageHeader(nextConnSocket, StageConnectedBody);
|
|
||||||
break;
|
|
||||||
case StageConnectedBody:
|
|
||||||
this->slotDataAvailable(nextConnSocket, info.instanceId);
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
break;
|
|
||||||
};
|
|
||||||
});
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
void SingleCoreApplicationPrivate::readMessageHeader(QLocalSocket *sock, SingleCoreApplicationPrivate::ConnectionStage nextStage) {
|
|
||||||
|
|
||||||
if (!connectionMap_.contains(sock)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (sock->bytesAvailable() < static_cast<qint64>(sizeof(quint64))) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
QDataStream headerStream(sock);
|
|
||||||
headerStream.setVersion(QDataStream::Qt_5_8);
|
|
||||||
|
|
||||||
// Read the header to know the message length
|
|
||||||
quint64 msgLen = 0;
|
|
||||||
headerStream >> msgLen;
|
|
||||||
ConnectionInfo &info = connectionMap_[sock];
|
|
||||||
info.stage = nextStage;
|
|
||||||
info.msgLen = msgLen;
|
|
||||||
|
|
||||||
writeAck(sock);
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
bool SingleCoreApplicationPrivate::isFrameComplete(QLocalSocket *sock) {
|
|
||||||
|
|
||||||
if (!connectionMap_.contains(sock)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
ConnectionInfo &info = connectionMap_[sock];
|
|
||||||
return (sock->bytesAvailable() >= static_cast<qint64>(info.msgLen));
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
void SingleCoreApplicationPrivate::readInitMessageBody(QLocalSocket *sock) {
|
|
||||||
|
|
||||||
Q_Q(SingleCoreApplication);
|
|
||||||
|
|
||||||
if (!isFrameComplete(sock)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Read the message body
|
|
||||||
QByteArray msgBytes = sock->readAll();
|
|
||||||
QDataStream readStream(msgBytes);
|
|
||||||
readStream.setVersion(QDataStream::Qt_5_8);
|
|
||||||
|
|
||||||
// server name
|
|
||||||
QByteArray latin1Name;
|
|
||||||
readStream >> latin1Name;
|
|
||||||
|
|
||||||
// connection type
|
|
||||||
ConnectionType connectionType = InvalidConnection;
|
|
||||||
quint8 connTypeVal = InvalidConnection;
|
|
||||||
readStream >> connTypeVal;
|
|
||||||
connectionType = static_cast<ConnectionType>(connTypeVal);
|
|
||||||
|
|
||||||
// instance id
|
|
||||||
quint32 instanceId = 0;
|
|
||||||
readStream >> instanceId;
|
|
||||||
|
|
||||||
// checksum
|
|
||||||
quint16 msgChecksum = 0;
|
|
||||||
readStream >> msgChecksum;
|
|
||||||
|
|
||||||
#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
|
|
||||||
const quint16 actualChecksum = qChecksum(QByteArray(msgBytes, static_cast<quint32>(msgBytes.length() - sizeof(quint16))));
|
|
||||||
#else
|
|
||||||
const quint16 actualChecksum = qChecksum(msgBytes.constData(), static_cast<quint32>(msgBytes.length() - sizeof(quint16)));
|
|
||||||
#endif
|
|
||||||
|
|
||||||
bool isValid = readStream.status() == QDataStream::Ok && QLatin1String(latin1Name) == blockServerName_ && msgChecksum == actualChecksum;
|
|
||||||
|
|
||||||
if (!isValid) {
|
|
||||||
sock->close();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
ConnectionInfo &info = connectionMap_[sock];
|
|
||||||
info.instanceId = instanceId;
|
|
||||||
info.stage = StageConnectedHeader;
|
|
||||||
|
|
||||||
if (connectionType == NewInstance || (connectionType == SecondaryInstance && options_ & SingleCoreApplication::Mode::SecondaryNotification)) {
|
|
||||||
emit q->instanceStarted();
|
|
||||||
}
|
|
||||||
|
|
||||||
writeAck(sock);
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
void SingleCoreApplicationPrivate::slotDataAvailable(QLocalSocket *dataSocket, const quint32 instanceId) {
|
|
||||||
|
|
||||||
Q_Q(SingleCoreApplication);
|
|
||||||
|
|
||||||
if (!isFrameComplete(dataSocket)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const QByteArray message = dataSocket->readAll();
|
|
||||||
|
|
||||||
writeAck(dataSocket);
|
|
||||||
|
|
||||||
ConnectionInfo &info = connectionMap_[dataSocket];
|
|
||||||
info.stage = StageConnectedHeader;
|
|
||||||
|
|
||||||
emit q->receivedMessage(instanceId, message);
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
void SingleCoreApplicationPrivate::slotClientConnectionClosed(QLocalSocket *closedSocket, const quint32 instanceId) {
|
|
||||||
|
|
||||||
if (closedSocket->bytesAvailable() > 0) {
|
|
||||||
slotDataAvailable(closedSocket, instanceId);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
void SingleCoreApplicationPrivate::randomSleep() {
|
|
||||||
|
|
||||||
#if QT_VERSION >= QT_VERSION_CHECK(5, 10, 0)
|
|
||||||
QThread::msleep(QRandomGenerator::global()->bounded(8U, 18U));
|
|
||||||
#else
|
|
||||||
qsrand(QDateTime::currentMSecsSinceEpoch() % std::numeric_limits<uint>::max());
|
|
||||||
QThread::msleep(qrand() % 11 + 8);
|
|
||||||
#endif
|
|
||||||
|
|
||||||
}
|
|
||||||
116
3rdparty/singleapplication/singlecoreapplication_p.h
vendored
116
3rdparty/singleapplication/singlecoreapplication_p.h
vendored
@@ -1,116 +0,0 @@
|
|||||||
// The MIT License (MIT)
|
|
||||||
//
|
|
||||||
// Copyright (c) Itay Grudev 2015 - 2020
|
|
||||||
//
|
|
||||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
||||||
// of this software and associated documentation files (the "Software"), to deal
|
|
||||||
// in the Software without restriction, including without limitation the rights
|
|
||||||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
||||||
// copies of the Software, and to permit persons to whom the Software is
|
|
||||||
// furnished to do so, subject to the following conditions:
|
|
||||||
//
|
|
||||||
// The above copyright notice and this permission notice shall be included in
|
|
||||||
// all copies or substantial portions of the Software.
|
|
||||||
//
|
|
||||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
||||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
||||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
||||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
||||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
||||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
|
||||||
// THE SOFTWARE.
|
|
||||||
|
|
||||||
//
|
|
||||||
// W A R N I N G !!!
|
|
||||||
// -----------------
|
|
||||||
//
|
|
||||||
// This is a modified version of SingleApplication,
|
|
||||||
// The original version is at:
|
|
||||||
//
|
|
||||||
// https://github.com/itay-grudev/SingleApplication
|
|
||||||
//
|
|
||||||
//
|
|
||||||
|
|
||||||
#ifndef SINGLECOREAPPLICATION_P_H
|
|
||||||
#define SINGLECOREAPPLICATION_P_H
|
|
||||||
|
|
||||||
#include <QtGlobal>
|
|
||||||
#include <QObject>
|
|
||||||
#include <QString>
|
|
||||||
#include <QHash>
|
|
||||||
|
|
||||||
#include "singlecoreapplication.h"
|
|
||||||
|
|
||||||
class QLocalServer;
|
|
||||||
class QLocalSocket;
|
|
||||||
class QSharedMemory;
|
|
||||||
|
|
||||||
struct InstancesInfo {
|
|
||||||
bool primary;
|
|
||||||
quint32 secondary;
|
|
||||||
qint64 primaryPid;
|
|
||||||
char primaryUser[128];
|
|
||||||
quint16 checksum;
|
|
||||||
};
|
|
||||||
|
|
||||||
struct ConnectionInfo {
|
|
||||||
explicit ConnectionInfo() : msgLen(0), instanceId(0), stage(0) {}
|
|
||||||
quint64 msgLen;
|
|
||||||
quint32 instanceId;
|
|
||||||
quint8 stage;
|
|
||||||
};
|
|
||||||
|
|
||||||
class SingleCoreApplicationPrivate : public QObject {
|
|
||||||
Q_OBJECT
|
|
||||||
|
|
||||||
public:
|
|
||||||
enum ConnectionType : quint8 {
|
|
||||||
InvalidConnection = 0,
|
|
||||||
NewInstance = 1,
|
|
||||||
SecondaryInstance = 2,
|
|
||||||
Reconnect = 3
|
|
||||||
};
|
|
||||||
enum ConnectionStage : quint8 {
|
|
||||||
StageInitHeader = 0,
|
|
||||||
StageInitBody = 1,
|
|
||||||
StageConnectedHeader = 2,
|
|
||||||
StageConnectedBody = 3,
|
|
||||||
};
|
|
||||||
Q_DECLARE_PUBLIC(SingleCoreApplication)
|
|
||||||
|
|
||||||
explicit SingleCoreApplicationPrivate(SingleCoreApplication *ptr);
|
|
||||||
~SingleCoreApplicationPrivate() override;
|
|
||||||
|
|
||||||
static QString getUsername();
|
|
||||||
void genBlockServerName();
|
|
||||||
void initializeMemoryBlock() const;
|
|
||||||
void startPrimary();
|
|
||||||
void startSecondary();
|
|
||||||
bool connectToPrimary(const int timeout, const ConnectionType connectionType);
|
|
||||||
quint16 blockChecksum() const;
|
|
||||||
qint64 primaryPid() const;
|
|
||||||
QString primaryUser() const;
|
|
||||||
bool isFrameComplete(QLocalSocket *sock);
|
|
||||||
void readMessageHeader(QLocalSocket *socket, const ConnectionStage nextStage);
|
|
||||||
void readInitMessageBody(QLocalSocket *socket);
|
|
||||||
void writeAck(QLocalSocket *sock);
|
|
||||||
bool writeConfirmedFrame(const int timeout, const QByteArray &msg) const;
|
|
||||||
bool writeConfirmedMessage(const int timeout, const QByteArray &msg) const;
|
|
||||||
static void randomSleep();
|
|
||||||
|
|
||||||
SingleCoreApplication *q_ptr;
|
|
||||||
QSharedMemory *memory_;
|
|
||||||
QLocalSocket *socket_;
|
|
||||||
QLocalServer *server_;
|
|
||||||
quint32 instanceNumber_;
|
|
||||||
QString blockServerName_;
|
|
||||||
SingleCoreApplication::Options options_;
|
|
||||||
QHash<QLocalSocket*, ConnectionInfo> connectionMap_;
|
|
||||||
|
|
||||||
public slots:
|
|
||||||
void slotConnectionEstablished();
|
|
||||||
void slotDataAvailable(QLocalSocket*, const quint32);
|
|
||||||
void slotClientConnectionClosed(QLocalSocket*, const quint32);
|
|
||||||
};
|
|
||||||
|
|
||||||
#endif // SINGLECOREAPPLICATION_P_H
|
|
||||||
@@ -2,8 +2,10 @@ cmake_minimum_required(VERSION 3.7)
|
|||||||
|
|
||||||
project(strawberry)
|
project(strawberry)
|
||||||
|
|
||||||
cmake_policy(SET CMP0054 NEW)
|
if(POLICY CMP0054)
|
||||||
if (CMAKE_VERSION VERSION_GREATER_EQUAL 3.12)
|
cmake_policy(SET CMP0054 NEW)
|
||||||
|
endif()
|
||||||
|
if(POLICY CMP0074)
|
||||||
cmake_policy(SET CMP0074 NEW)
|
cmake_policy(SET CMP0074 NEW)
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
@@ -309,7 +311,10 @@ endif()
|
|||||||
|
|
||||||
# SingleApplication
|
# SingleApplication
|
||||||
add_subdirectory(3rdparty/singleapplication)
|
add_subdirectory(3rdparty/singleapplication)
|
||||||
set(SINGLEAPPLICATION_INCLUDE_DIRS ${CMAKE_CURRENT_SOURCE_DIR}/3rdparty/singleapplication)
|
set(SINGLEAPPLICATION_INCLUDE_DIRS
|
||||||
|
${CMAKE_CURRENT_SOURCE_DIR}/3rdparty/singleapplication/singleapplication
|
||||||
|
${CMAKE_CURRENT_SOURCE_DIR}/3rdparty/singleapplication/singlecoreapplication
|
||||||
|
)
|
||||||
set(SINGLEAPPLICATION_LIBRARIES singleapplication)
|
set(SINGLEAPPLICATION_LIBRARIES singleapplication)
|
||||||
set(SINGLECOREAPPLICATION_LIBRARIES singlecoreapplication)
|
set(SINGLECOREAPPLICATION_LIBRARIES singlecoreapplication)
|
||||||
|
|
||||||
@@ -376,12 +381,12 @@ optional_component(VLC ON "Engine: VLC backend"
|
|||||||
|
|
||||||
optional_component(SONGFINGERPRINTING ON "Song fingerprinting and tracking"
|
optional_component(SONGFINGERPRINTING ON "Song fingerprinting and tracking"
|
||||||
DEPENDS "chromaprint" CHROMAPRINT_FOUND
|
DEPENDS "chromaprint" CHROMAPRINT_FOUND
|
||||||
DEPENDS "gstreamer" GSTREAMER_FOUND
|
DEPENDS "gstreamer" HAVE_GSTREAMER
|
||||||
)
|
)
|
||||||
|
|
||||||
optional_component(MUSICBRAINZ ON "MusicBrainz integration"
|
optional_component(MUSICBRAINZ ON "MusicBrainz integration"
|
||||||
DEPENDS "chromaprint" CHROMAPRINT_FOUND
|
DEPENDS "chromaprint" CHROMAPRINT_FOUND
|
||||||
DEPENDS "gstreamer" GSTREAMER_FOUND
|
DEPENDS "gstreamer" HAVE_GSTREAMER
|
||||||
)
|
)
|
||||||
|
|
||||||
if(X11_FOUND OR HAVE_DBUS OR APPLE OR WIN32)
|
if(X11_FOUND OR HAVE_DBUS OR APPLE OR WIN32)
|
||||||
@@ -406,7 +411,7 @@ endif()
|
|||||||
|
|
||||||
optional_component(AUDIOCD ON "Devices: Audio CD support"
|
optional_component(AUDIOCD ON "Devices: Audio CD support"
|
||||||
DEPENDS "libcdio" LIBCDIO_FOUND
|
DEPENDS "libcdio" LIBCDIO_FOUND
|
||||||
DEPENDS "gstreamer" GSTREAMER_FOUND
|
DEPENDS "gstreamer" HAVE_GSTREAMER
|
||||||
)
|
)
|
||||||
|
|
||||||
optional_component(UDISKS2 ON "Devices: UDisks2 backend"
|
optional_component(UDISKS2 ON "Devices: UDisks2 backend"
|
||||||
|
|||||||
112
CONTRIBUTING.md
Normal file
112
CONTRIBUTING.md
Normal file
@@ -0,0 +1,112 @@
|
|||||||
|
# Contribution guidelines
|
||||||
|
|
||||||
|
Strawberry is an free and open-source project, it is possible and encouraged to participate in the development.
|
||||||
|
You can also participate by answering questions, reporting bugs or helping with documentation.
|
||||||
|
|
||||||
|
|
||||||
|
## Submitting a pull request
|
||||||
|
|
||||||
|
You should start by creating a fork of the Strawberry repository using the GitHub
|
||||||
|
fork button, after that you can clone the repository from your fork.
|
||||||
|
Replace "username" with your own.
|
||||||
|
|
||||||
|
|
||||||
|
### Clone the repository
|
||||||
|
|
||||||
|
git clone git@github.com:username/strawberry.git
|
||||||
|
cd strawberry
|
||||||
|
|
||||||
|
|
||||||
|
### Setup the remote
|
||||||
|
|
||||||
|
git remote add upstream git@github.com:strawberrymusicplayer/strawberry.git
|
||||||
|
|
||||||
|
|
||||||
|
### Create a new branch
|
||||||
|
|
||||||
|
This creates a new branch from the master branch that you use for specific
|
||||||
|
changes.
|
||||||
|
|
||||||
|
git checkout -b your-branch
|
||||||
|
|
||||||
|
|
||||||
|
### Stage changes
|
||||||
|
|
||||||
|
Once you've finished working on a specific change, stage the changes for
|
||||||
|
a specific commit.
|
||||||
|
|
||||||
|
Always keep your commits relevant to the pull request, and each commit as
|
||||||
|
small as possible.
|
||||||
|
|
||||||
|
git add -p
|
||||||
|
|
||||||
|
|
||||||
|
### Commit changes
|
||||||
|
|
||||||
|
git commit
|
||||||
|
|
||||||
|
|
||||||
|
### Commit messages
|
||||||
|
|
||||||
|
The first line should start with "Class:", which referer to the class
|
||||||
|
that is changed. Don't use a trailing period after the first line.
|
||||||
|
If this change affects more than one class, omit the class and write a
|
||||||
|
more general message.
|
||||||
|
You only need to include a main description (body) for larger changes
|
||||||
|
where the one line is not enough to describe everything.
|
||||||
|
The main description starts after two newlines, it is normal prose and
|
||||||
|
should use normal punctuation and capital letters where appropriate.
|
||||||
|
|
||||||
|
An example of the expected format for git commit messages is as follows:
|
||||||
|
|
||||||
|
```
|
||||||
|
class: Short explanation of the commit
|
||||||
|
|
||||||
|
Longer explanation explaining exactly what's changed, why it's changed,
|
||||||
|
and what bugs were fixed.
|
||||||
|
|
||||||
|
Fixes #1234
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
### Push the changes to GitHub
|
||||||
|
|
||||||
|
Once you've finished working on the changes, push the branch
|
||||||
|
to the Git repository and open a new pull request.
|
||||||
|
|
||||||
|
|
||||||
|
git push origin your-branch
|
||||||
|
|
||||||
|
|
||||||
|
### Update your fork's master branch
|
||||||
|
|
||||||
|
git checkout master
|
||||||
|
git pull --rebase origin master
|
||||||
|
git fetch upstream
|
||||||
|
git merge upstream/master
|
||||||
|
git push origin master
|
||||||
|
|
||||||
|
|
||||||
|
### Update your branch
|
||||||
|
|
||||||
|
git checkout your-branch
|
||||||
|
git fetch upstream
|
||||||
|
git rebase upstream/master
|
||||||
|
git push origin your-branch --force-with-lease
|
||||||
|
|
||||||
|
|
||||||
|
### Rebase your branch
|
||||||
|
|
||||||
|
If you need fix any issues with your commits, you need to rebase your
|
||||||
|
branch to squash any commits, or to change the commit message.
|
||||||
|
|
||||||
|
git checkout your-branch
|
||||||
|
git log
|
||||||
|
git rebase -i commit_sha~
|
||||||
|
git push origin your-branch --force-with-lease
|
||||||
|
|
||||||
|
|
||||||
|
### Delete your fork
|
||||||
|
|
||||||
|
If you do not plan to work more on Strawberry, please delete your fork from GitHub
|
||||||
|
once the pull requests are merged.
|
||||||
105
Changelog
105
Changelog
@@ -2,6 +2,103 @@ Strawberry Music Player
|
|||||||
=======================
|
=======================
|
||||||
ChangeLog
|
ChangeLog
|
||||||
|
|
||||||
|
Version 1.0.17 (2023.03.29):
|
||||||
|
|
||||||
|
Bugfixes:
|
||||||
|
* Fixed over-sized context album cover with device pixel ratio higher than 1.0 (#1166).
|
||||||
|
* Fixed playing widget fading from a blurry previous cover with device pixel ratio higher than 1.0.
|
||||||
|
* Made playlist source icon, album cover manager and OSD pretty cover respect device pixel ratio.
|
||||||
|
|
||||||
|
Version 1.0.16 (2023.03.27):
|
||||||
|
|
||||||
|
Bugfixes:
|
||||||
|
* Fixed lyrics from Musixmatch.
|
||||||
|
* Fixed possible file corruption when saving both tags and embedded cover using the tag editor (#1158).
|
||||||
|
* Fixed compile without GStreamer.
|
||||||
|
* Fixed context and playing now album art rendering on High DPI displays (#1161).
|
||||||
|
* Fixed setting source properties (device, user-agent, ssl-strict) with GStreamer 1.22 (playbin3) and higher (#1148).
|
||||||
|
* Fixed rescan songs feature not ignoring mtime.
|
||||||
|
* Search lyrics by artist instead of album artist by default.
|
||||||
|
|
||||||
|
Code improvements:
|
||||||
|
* Replace use of deprecated QSqlDatabase::exec().
|
||||||
|
|
||||||
|
Added features:
|
||||||
|
* Added backend setting for strict SSL mode.
|
||||||
|
* Read AcoustID and MusicBrainz tags.
|
||||||
|
* Submit MusicBrainz tags with ListenBrainz.
|
||||||
|
|
||||||
|
Version 1.0.15 (2023.03.04):
|
||||||
|
|
||||||
|
Bugfixes:
|
||||||
|
* Fixed playlist column showing invalid last played date for streams.
|
||||||
|
* Fixed crash when the audio bin failed to initialize (#1123, #1133).
|
||||||
|
* Fixed duplicated filename when organizing files using dot in the filename (#1136).
|
||||||
|
* Fixed tag inline editing for streams (#1130).
|
||||||
|
* Fixed resetting play statistics using tag edit dialog (#1124).
|
||||||
|
* Fixed compilation songs not showing if group by was set to other than (Album) Artist / Album (#1140).
|
||||||
|
|
||||||
|
Enhancements:
|
||||||
|
* Added lyrics from stands4 (lyrics.com).
|
||||||
|
* Added Sonogram analyzer.
|
||||||
|
* Use GStreamer playbin3 with GStreamer 1.22.0 and higher.
|
||||||
|
|
||||||
|
Code improvements:
|
||||||
|
* Made use of C++11 enum class where possible.
|
||||||
|
* Use new QNativeIpcKey based QSharedMemory constructor with Qt 6.6 and higher.
|
||||||
|
|
||||||
|
Version 1.0.14 (2023.01.13):
|
||||||
|
|
||||||
|
Bugfixes:
|
||||||
|
* Fix initial volume not set when using Auto as output (#1104).
|
||||||
|
* Fix saving moodbar if the URL contains host, ie.: UNC paths for SMB (#1101).
|
||||||
|
* Fix CollectionBackendTest compile error (#1100).
|
||||||
|
* Remove explicitly enabling debug messages (#1106).
|
||||||
|
|
||||||
|
Version 1.0.13 (2023.01.09):
|
||||||
|
|
||||||
|
Bugfixes:
|
||||||
|
* Fixed volume synchronization leading to infinite loop resulting in crash when adjusting volume while playing (#1089).
|
||||||
|
* Fixed incorrect volume.
|
||||||
|
* Fixed collection organizing incorrectly handling slashes inside {} brackets for variables (#1091).
|
||||||
|
* Fixed saving relative playlists to non-existing playlist files (#1092).
|
||||||
|
* Fixed intermittent crash on collection model query (#1095).
|
||||||
|
* Require system icons for fancy tabbar and settings sidebar to be larger than 22x22 (#1084).
|
||||||
|
|
||||||
|
Version 1.0.12 (2023.01.02):
|
||||||
|
|
||||||
|
Bugfixes:
|
||||||
|
* Fixed crash when adjusting volume with mouse wheel (#1089).
|
||||||
|
* Fixed playback stopping in certain cases where the next track was unavailable (#958).
|
||||||
|
* (Windows) Apply patch for fonts too large on High DPI screen (QTBUG-108593).
|
||||||
|
|
||||||
|
Removed features:
|
||||||
|
* Removed appearance settings for changing palette colors, it was never properly implemented.
|
||||||
|
|
||||||
|
Version 1.0.11 (2022.12.30):
|
||||||
|
|
||||||
|
Bugfixes:
|
||||||
|
* Capitalize GLib application name so it appears nicely in GNOME and PulseAudio Volume Control (#1066).
|
||||||
|
* Fixed missing application icon for PulseAudio Volume Control (#1066).
|
||||||
|
* Ignore errors for missing albums when updating Tidal collection if there are results (#1061).
|
||||||
|
* Only run periodic collection scan when moitoring collection setting is on.
|
||||||
|
* Fixed an edge case where the context headline text was being cut short (#1067).
|
||||||
|
* Made "Show in file browser" support SpaceFM filemanager (#1073).
|
||||||
|
* Fixed incorrect tab order in edit tag dialog (#1075).
|
||||||
|
* Changed "FMPS_PlayCount" to "FMPS_Playcount" when saving tag (#1074).
|
||||||
|
* Fixed compilation tag read and write for MP4 (#1076).
|
||||||
|
* Removed incorrect use of "TPE1" for performer when reading ID3 tags (#1076).
|
||||||
|
* Disable tag fields for unsupported tags in tag editor.
|
||||||
|
* Don't allow organizing files without unique tags (track or title) for filename (#1077).
|
||||||
|
* Don't remove disc from album title when creating cover hash to allow different covers for each disc on an album (#1069).
|
||||||
|
* Fixed incorrect relative paths for song filenames when saving playlists if the saved playlist location is a symablic link to the song filename (#1071).
|
||||||
|
* Scrobble "Various Artists" as album artist (#1082).
|
||||||
|
|
||||||
|
Enhancements:
|
||||||
|
* Use system volume instead of own software volume when available (#1037).
|
||||||
|
* Improved Tidal and Qobuz support with timed requests.
|
||||||
|
* Support MPRIS2 xesam:userRating.
|
||||||
|
|
||||||
Version 1.0.10 (2022.10.21):
|
Version 1.0.10 (2022.10.21):
|
||||||
|
|
||||||
Bugfixes:
|
Bugfixes:
|
||||||
@@ -250,7 +347,7 @@ Version 0.9.3 (2021.04.18)
|
|||||||
Bugfixes:
|
Bugfixes:
|
||||||
* Fix "Show in file browser" to work with thunar.
|
* Fix "Show in file browser" to work with thunar.
|
||||||
* Check that the clicked rating position is to the right or left of the rectangle.
|
* Check that the clicked rating position is to the right or left of the rectangle.
|
||||||
* Fix rescan when collection directory is removed and readded.
|
* Fix rescan when collection directory is removed and re-added.
|
||||||
* Create GLib main event loop on non-glib systems to fix stream discoverer.
|
* Create GLib main event loop on non-glib systems to fix stream discoverer.
|
||||||
* (macOS) Fix intermittent abort on startup.
|
* (macOS) Fix intermittent abort on startup.
|
||||||
* (macOS) Fix Tidal and Qobuz search field not showing.
|
* (macOS) Fix Tidal and Qobuz search field not showing.
|
||||||
@@ -587,7 +684,7 @@ Version 0.6.10 (2020.05.01)
|
|||||||
* Made font and font sizes in context configurable.
|
* Made font and font sizes in context configurable.
|
||||||
* Splitting artist and song title to the relevant metadata when artist and song title is sent as title separated by a dash in streams.
|
* Splitting artist and song title to the relevant metadata when artist and song title is sent as title separated by a dash in streams.
|
||||||
* Added label to show collection pixmap disk cache used in settings.
|
* Added label to show collection pixmap disk cache used in settings.
|
||||||
* Icreased default collection pixmap disk cache to 360.
|
* Increased default collection pixmap disk cache to 360.
|
||||||
|
|
||||||
New features:
|
New features:
|
||||||
* Added back Tidal streaming support.
|
* Added back Tidal streaming support.
|
||||||
@@ -655,7 +752,7 @@ Version 0.6.7 (2019.11.27)
|
|||||||
* Fixed "Pressing Previous in player" behaviour setting
|
* Fixed "Pressing Previous in player" behaviour setting
|
||||||
* Fixed updating compilations where there are spaces or special characters in filenames
|
* Fixed updating compilations where there are spaces or special characters in filenames
|
||||||
* Fixed cases where songs were stuck in "Various Artists" because not all songs in
|
* Fixed cases where songs were stuck in "Various Artists" because not all songs in
|
||||||
the same compilation was removed from the model before readded with actual artist.
|
the same compilation was removed from the model before re-added with actual artist.
|
||||||
* Fixed a bug when importing playlists where metadata was reset
|
* Fixed a bug when importing playlists where metadata was reset
|
||||||
* Fixed scrobbler to also scrobble songs without album title
|
* Fixed scrobbler to also scrobble songs without album title
|
||||||
* Fixed text for replay gain setting not loading in backend setting
|
* Fixed text for replay gain setting not loading in backend setting
|
||||||
@@ -1008,7 +1105,7 @@ Version 0.1.5 (2018.05.16)
|
|||||||
|
|
||||||
|
|
||||||
Version 0.1.4 (2018.05.14)
|
Version 0.1.4 (2018.05.14)
|
||||||
* Fixed compliation with clang compiler
|
* Fixed compilation with clang compiler
|
||||||
* This release is mainly to get it working on openbsd and freebsd.
|
* This release is mainly to get it working on openbsd and freebsd.
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
14
README.md
14
README.md
@@ -51,7 +51,7 @@ Funding developers is a way to contribute to open source projects you appreciate
|
|||||||
* Edit tags on audio files
|
* Edit tags on audio files
|
||||||
* Fetch tags from MusicBrainz
|
* Fetch tags from MusicBrainz
|
||||||
* Album cover art from [Last.fm](https://www.last.fm/), [Musicbrainz](https://musicbrainz.org/), [Discogs](https://www.discogs.com/), [Musixmatch](https://www.musixmatch.com/), [Deezer](https://www.deezer.com/), [Tidal](https://www.tidal.com/), [Qobuz](https://www.qobuz.com/) and [Spotify](https://www.spotify.com/)
|
* Album cover art from [Last.fm](https://www.last.fm/), [Musicbrainz](https://musicbrainz.org/), [Discogs](https://www.discogs.com/), [Musixmatch](https://www.musixmatch.com/), [Deezer](https://www.deezer.com/), [Tidal](https://www.tidal.com/), [Qobuz](https://www.qobuz.com/) and [Spotify](https://www.spotify.com/)
|
||||||
* Song lyrics from [AudD](https://audd.io/), [Genius](https://genius.com/), [Musixmatch](https://www.musixmatch.com/), [ChartLyrics](http://www.chartlyrics.com/), [lyrics.ovh](https://lyrics.ovh/) and [lololyrics.com](https://www.lololyrics.com/)
|
* Song lyrics from [Lyrics.com](https://www.lyrics.com/), [AudD](https://audd.io/), [Genius](https://genius.com/), [Musixmatch](https://www.musixmatch.com/), [ChartLyrics](http://www.chartlyrics.com/), [lyrics.ovh](https://lyrics.ovh/) and [lololyrics.com](https://www.lololyrics.com/)
|
||||||
* Support for multiple backends
|
* Support for multiple backends
|
||||||
* Audio analyzer
|
* Audio analyzer
|
||||||
* Audio equalizer
|
* Audio equalizer
|
||||||
@@ -69,10 +69,11 @@ It has so far been tested to work on Linux, OpenBSD, FreeBSD, macOS and Windows.
|
|||||||
To build Strawberry from source you need the following installed on your system with the additional development packages/headers:
|
To build Strawberry from source you need the following installed on your system with the additional development packages/headers:
|
||||||
|
|
||||||
* [CMake](https://cmake.org/)
|
* [CMake](https://cmake.org/)
|
||||||
* [GCC](https://gcc.gnu.org/), [Clang](https://clang.llvm.org/) or [MSVC](https://visualstudio.microsoft.com/vs/features/cplusplus/) compiler
|
* C/C++ compiler ([GCC](https://gcc.gnu.org/), [Clang](https://clang.llvm.org/) or [MSVC](https://visualstudio.microsoft.com/vs/features/cplusplus/))
|
||||||
|
* [pkg-config](https://www.freedesktop.org/wiki/Software/pkg-config/) or [pkgconf](https://github.com/pkgconf/pkgconf)
|
||||||
* [Boost](https://www.boost.org/)
|
* [Boost](https://www.boost.org/)
|
||||||
* [GLib](https://developer.gnome.org/glib/)
|
* [GLib](https://developer.gnome.org/glib/)
|
||||||
* [Qt 5.9 or higher (or Qt 6) with components Core, Gui, Widgets, Concurrent, Network and Sql](https://www.qt.io/)
|
* [Qt 6 or Qt 5.9 or higher with components Core, Gui, Widgets, Concurrent, Network and Sql](https://www.qt.io/)
|
||||||
* [SQLite 3.9 or newer with FTS5](https://www.sqlite.org)
|
* [SQLite 3.9 or newer with FTS5](https://www.sqlite.org)
|
||||||
* [Protobuf](https://developers.google.com/protocol-buffers/)
|
* [Protobuf](https://developers.google.com/protocol-buffers/)
|
||||||
* [ALSA (Required on Linux)](https://www.alsa-project.org/)
|
* [ALSA (Required on Linux)](https://www.alsa-project.org/)
|
||||||
@@ -102,15 +103,18 @@ You should also install the gstreamer plugins base and good, and optionally bad,
|
|||||||
### Compile and install:
|
### Compile and install:
|
||||||
|
|
||||||
cd strawberry
|
cd strawberry
|
||||||
mkdir build && cd build
|
mkdir build
|
||||||
|
cd build
|
||||||
cmake .. -DBUILD_WITH_QT6=ON
|
cmake .. -DBUILD_WITH_QT6=ON
|
||||||
make -j$(nproc)
|
make -j $(nproc)
|
||||||
sudo make install
|
sudo make install
|
||||||
|
|
||||||
Strawberry is backwards compatible with Qt 5, to compile with Qt 5 use:
|
Strawberry is backwards compatible with Qt 5, to compile with Qt 5 use:
|
||||||
|
|
||||||
cmake .. -DBUILD_WITH_QT5=ON
|
cmake .. -DBUILD_WITH_QT5=ON
|
||||||
|
|
||||||
|
To compile on Windows with Visual Studio 2019 or 2022, see https://github.com/strawberrymusicplayer/strawberry-msvc
|
||||||
|
|
||||||
### :penguin: Packaging status
|
### :penguin: Packaging status
|
||||||
|
|
||||||
[](https://repology.org/metapackage/strawberry/versions)
|
[](https://repology.org/metapackage/strawberry/versions)
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
set(STRAWBERRY_VERSION_MAJOR 1)
|
set(STRAWBERRY_VERSION_MAJOR 1)
|
||||||
set(STRAWBERRY_VERSION_MINOR 0)
|
set(STRAWBERRY_VERSION_MINOR 0)
|
||||||
set(STRAWBERRY_VERSION_PATCH 10)
|
set(STRAWBERRY_VERSION_PATCH 17)
|
||||||
#set(STRAWBERRY_VERSION_PRERELEASE rc1)
|
#set(STRAWBERRY_VERSION_PRERELEASE rc1)
|
||||||
|
|
||||||
set(INCLUDE_GIT_REVISION OFF)
|
set(INCLUDE_GIT_REVISION OFF)
|
||||||
|
|||||||
@@ -7,6 +7,7 @@
|
|||||||
<file>schema/schema-13.sql</file>
|
<file>schema/schema-13.sql</file>
|
||||||
<file>schema/schema-14.sql</file>
|
<file>schema/schema-14.sql</file>
|
||||||
<file>schema/schema-15.sql</file>
|
<file>schema/schema-15.sql</file>
|
||||||
|
<file>schema/schema-16.sql</file>
|
||||||
<file>schema/device-schema.sql</file>
|
<file>schema/device-schema.sql</file>
|
||||||
<file>style/strawberry.css</file>
|
<file>style/strawberry.css</file>
|
||||||
<file>style/smartplaylistsearchterm.css</file>
|
<file>style/smartplaylistsearchterm.css</file>
|
||||||
|
|||||||
@@ -67,7 +67,21 @@ CREATE TABLE device_%deviceid_songs (
|
|||||||
|
|
||||||
cue_path TEXT,
|
cue_path TEXT,
|
||||||
|
|
||||||
rating INTEGER DEFAULT -1
|
rating INTEGER DEFAULT -1,
|
||||||
|
|
||||||
|
acoustid_id TEXT,
|
||||||
|
acoustid_fingerprint TEXT,
|
||||||
|
|
||||||
|
musicbrainz_album_artist_id TEXT,
|
||||||
|
musicbrainz_artist_id TEXT,
|
||||||
|
musicbrainz_original_artist_id TEXT,
|
||||||
|
musicbrainz_album_id TEXT,
|
||||||
|
musicbrainz_original_album_id TEXT,
|
||||||
|
musicbrainz_recording_id TEXT,
|
||||||
|
musicbrainz_track_id TEXT,
|
||||||
|
musicbrainz_disc_id TEXT,
|
||||||
|
musicbrainz_release_group_id TEXT,
|
||||||
|
musicbrainz_work_id TEXT
|
||||||
|
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -80,4 +94,4 @@ CREATE VIRTUAL TABLE device_%deviceid_fts USING fts5(
|
|||||||
tokenize = "unicode61 remove_diacritics 1"
|
tokenize = "unicode61 remove_diacritics 1"
|
||||||
);
|
);
|
||||||
|
|
||||||
UPDATE devices SET schema_version=3 WHERE ROWID=%deviceid;
|
UPDATE devices SET schema_version=4 WHERE ROWID=%deviceid;
|
||||||
|
|||||||
217
data/schema/schema-16.sql
Normal file
217
data/schema/schema-16.sql
Normal file
@@ -0,0 +1,217 @@
|
|||||||
|
ALTER TABLE songs ADD COLUMN acoustid_id TEXT;
|
||||||
|
|
||||||
|
ALTER TABLE songs ADD COLUMN acoustid_fingerprint TEXT;
|
||||||
|
|
||||||
|
ALTER TABLE songs ADD COLUMN musicbrainz_album_artist_id TEXT;
|
||||||
|
|
||||||
|
ALTER TABLE songs ADD COLUMN musicbrainz_artist_id TEXT;
|
||||||
|
|
||||||
|
ALTER TABLE songs ADD COLUMN musicbrainz_original_artist_id TEXT;
|
||||||
|
|
||||||
|
ALTER TABLE songs ADD COLUMN musicbrainz_album_id TEXT;
|
||||||
|
|
||||||
|
ALTER TABLE songs ADD COLUMN musicbrainz_original_album_id TEXT;
|
||||||
|
|
||||||
|
ALTER TABLE songs ADD COLUMN musicbrainz_recording_id TEXT;
|
||||||
|
|
||||||
|
ALTER TABLE songs ADD COLUMN musicbrainz_track_id TEXT;
|
||||||
|
|
||||||
|
ALTER TABLE songs ADD COLUMN musicbrainz_disc_id TEXT;
|
||||||
|
|
||||||
|
ALTER TABLE songs ADD COLUMN musicbrainz_release_group_id TEXT;
|
||||||
|
|
||||||
|
ALTER TABLE songs ADD COLUMN musicbrainz_work_id TEXT;
|
||||||
|
|
||||||
|
ALTER TABLE subsonic_songs ADD COLUMN acoustid_id TEXT;
|
||||||
|
|
||||||
|
ALTER TABLE subsonic_songs ADD COLUMN acoustid_fingerprint TEXT;
|
||||||
|
|
||||||
|
ALTER TABLE subsonic_songs ADD COLUMN musicbrainz_album_artist_id TEXT;
|
||||||
|
|
||||||
|
ALTER TABLE subsonic_songs ADD COLUMN musicbrainz_artist_id TEXT;
|
||||||
|
|
||||||
|
ALTER TABLE subsonic_songs ADD COLUMN musicbrainz_original_artist_id TEXT;
|
||||||
|
|
||||||
|
ALTER TABLE subsonic_songs ADD COLUMN musicbrainz_album_id TEXT;
|
||||||
|
|
||||||
|
ALTER TABLE subsonic_songs ADD COLUMN musicbrainz_original_album_id TEXT;
|
||||||
|
|
||||||
|
ALTER TABLE subsonic_songs ADD COLUMN musicbrainz_recording_id TEXT;
|
||||||
|
|
||||||
|
ALTER TABLE subsonic_songs ADD COLUMN musicbrainz_track_id TEXT;
|
||||||
|
|
||||||
|
ALTER TABLE subsonic_songs ADD COLUMN musicbrainz_disc_id TEXT;
|
||||||
|
|
||||||
|
ALTER TABLE subsonic_songs ADD COLUMN musicbrainz_release_group_id TEXT;
|
||||||
|
|
||||||
|
ALTER TABLE subsonic_songs ADD COLUMN musicbrainz_work_id TEXT;
|
||||||
|
|
||||||
|
ALTER TABLE tidal_artists_songs ADD COLUMN acoustid_id TEXT;
|
||||||
|
|
||||||
|
ALTER TABLE tidal_artists_songs ADD COLUMN acoustid_fingerprint TEXT;
|
||||||
|
|
||||||
|
ALTER TABLE tidal_artists_songs ADD COLUMN musicbrainz_album_artist_id TEXT;
|
||||||
|
|
||||||
|
ALTER TABLE tidal_artists_songs ADD COLUMN musicbrainz_artist_id TEXT;
|
||||||
|
|
||||||
|
ALTER TABLE tidal_artists_songs ADD COLUMN musicbrainz_original_artist_id TEXT;
|
||||||
|
|
||||||
|
ALTER TABLE tidal_artists_songs ADD COLUMN musicbrainz_album_id TEXT;
|
||||||
|
|
||||||
|
ALTER TABLE tidal_artists_songs ADD COLUMN musicbrainz_original_album_id TEXT;
|
||||||
|
|
||||||
|
ALTER TABLE tidal_artists_songs ADD COLUMN musicbrainz_recording_id TEXT;
|
||||||
|
|
||||||
|
ALTER TABLE tidal_artists_songs ADD COLUMN musicbrainz_track_id TEXT;
|
||||||
|
|
||||||
|
ALTER TABLE tidal_artists_songs ADD COLUMN musicbrainz_disc_id TEXT;
|
||||||
|
|
||||||
|
ALTER TABLE tidal_artists_songs ADD COLUMN musicbrainz_release_group_id TEXT;
|
||||||
|
|
||||||
|
ALTER TABLE tidal_artists_songs ADD COLUMN musicbrainz_work_id TEXT;
|
||||||
|
|
||||||
|
ALTER TABLE tidal_albums_songs ADD COLUMN acoustid_id TEXT;
|
||||||
|
|
||||||
|
ALTER TABLE tidal_albums_songs ADD COLUMN acoustid_fingerprint TEXT;
|
||||||
|
|
||||||
|
ALTER TABLE tidal_albums_songs ADD COLUMN musicbrainz_album_artist_id TEXT;
|
||||||
|
|
||||||
|
ALTER TABLE tidal_albums_songs ADD COLUMN musicbrainz_artist_id TEXT;
|
||||||
|
|
||||||
|
ALTER TABLE tidal_albums_songs ADD COLUMN musicbrainz_original_artist_id TEXT;
|
||||||
|
|
||||||
|
ALTER TABLE tidal_albums_songs ADD COLUMN musicbrainz_album_id TEXT;
|
||||||
|
|
||||||
|
ALTER TABLE tidal_albums_songs ADD COLUMN musicbrainz_original_album_id TEXT;
|
||||||
|
|
||||||
|
ALTER TABLE tidal_albums_songs ADD COLUMN musicbrainz_recording_id TEXT;
|
||||||
|
|
||||||
|
ALTER TABLE tidal_albums_songs ADD COLUMN musicbrainz_track_id TEXT;
|
||||||
|
|
||||||
|
ALTER TABLE tidal_albums_songs ADD COLUMN musicbrainz_disc_id TEXT;
|
||||||
|
|
||||||
|
ALTER TABLE tidal_albums_songs ADD COLUMN musicbrainz_release_group_id TEXT;
|
||||||
|
|
||||||
|
ALTER TABLE tidal_albums_songs ADD COLUMN musicbrainz_work_id TEXT;
|
||||||
|
|
||||||
|
ALTER TABLE tidal_songs ADD COLUMN acoustid_id TEXT;
|
||||||
|
|
||||||
|
ALTER TABLE tidal_songs ADD COLUMN acoustid_fingerprint TEXT;
|
||||||
|
|
||||||
|
ALTER TABLE tidal_songs ADD COLUMN musicbrainz_album_artist_id TEXT;
|
||||||
|
|
||||||
|
ALTER TABLE tidal_songs ADD COLUMN musicbrainz_artist_id TEXT;
|
||||||
|
|
||||||
|
ALTER TABLE tidal_songs ADD COLUMN musicbrainz_original_artist_id TEXT;
|
||||||
|
|
||||||
|
ALTER TABLE tidal_songs ADD COLUMN musicbrainz_album_id TEXT;
|
||||||
|
|
||||||
|
ALTER TABLE tidal_songs ADD COLUMN musicbrainz_original_album_id TEXT;
|
||||||
|
|
||||||
|
ALTER TABLE tidal_songs ADD COLUMN musicbrainz_recording_id TEXT;
|
||||||
|
|
||||||
|
ALTER TABLE tidal_songs ADD COLUMN musicbrainz_track_id TEXT;
|
||||||
|
|
||||||
|
ALTER TABLE tidal_songs ADD COLUMN musicbrainz_disc_id TEXT;
|
||||||
|
|
||||||
|
ALTER TABLE tidal_songs ADD COLUMN musicbrainz_release_group_id TEXT;
|
||||||
|
|
||||||
|
ALTER TABLE tidal_songs ADD COLUMN musicbrainz_work_id TEXT;
|
||||||
|
|
||||||
|
ALTER TABLE qobuz_artists_songs ADD COLUMN acoustid_id TEXT;
|
||||||
|
|
||||||
|
ALTER TABLE qobuz_artists_songs ADD COLUMN acoustid_fingerprint TEXT;
|
||||||
|
|
||||||
|
ALTER TABLE qobuz_artists_songs ADD COLUMN musicbrainz_album_artist_id TEXT;
|
||||||
|
|
||||||
|
ALTER TABLE qobuz_artists_songs ADD COLUMN musicbrainz_artist_id TEXT;
|
||||||
|
|
||||||
|
ALTER TABLE qobuz_artists_songs ADD COLUMN musicbrainz_original_artist_id TEXT;
|
||||||
|
|
||||||
|
ALTER TABLE qobuz_artists_songs ADD COLUMN musicbrainz_album_id TEXT;
|
||||||
|
|
||||||
|
ALTER TABLE qobuz_artists_songs ADD COLUMN musicbrainz_original_album_id TEXT;
|
||||||
|
|
||||||
|
ALTER TABLE qobuz_artists_songs ADD COLUMN musicbrainz_recording_id TEXT;
|
||||||
|
|
||||||
|
ALTER TABLE qobuz_artists_songs ADD COLUMN musicbrainz_track_id TEXT;
|
||||||
|
|
||||||
|
ALTER TABLE qobuz_artists_songs ADD COLUMN musicbrainz_disc_id TEXT;
|
||||||
|
|
||||||
|
ALTER TABLE qobuz_artists_songs ADD COLUMN musicbrainz_release_group_id TEXT;
|
||||||
|
|
||||||
|
ALTER TABLE qobuz_artists_songs ADD COLUMN musicbrainz_work_id TEXT;
|
||||||
|
|
||||||
|
ALTER TABLE qobuz_albums_songs ADD COLUMN acoustid_id TEXT;
|
||||||
|
|
||||||
|
ALTER TABLE qobuz_albums_songs ADD COLUMN acoustid_fingerprint TEXT;
|
||||||
|
|
||||||
|
ALTER TABLE qobuz_albums_songs ADD COLUMN musicbrainz_album_artist_id TEXT;
|
||||||
|
|
||||||
|
ALTER TABLE qobuz_albums_songs ADD COLUMN musicbrainz_artist_id TEXT;
|
||||||
|
|
||||||
|
ALTER TABLE qobuz_albums_songs ADD COLUMN musicbrainz_original_artist_id TEXT;
|
||||||
|
|
||||||
|
ALTER TABLE qobuz_albums_songs ADD COLUMN musicbrainz_album_id TEXT;
|
||||||
|
|
||||||
|
ALTER TABLE qobuz_albums_songs ADD COLUMN musicbrainz_original_album_id TEXT;
|
||||||
|
|
||||||
|
ALTER TABLE qobuz_albums_songs ADD COLUMN musicbrainz_recording_id TEXT;
|
||||||
|
|
||||||
|
ALTER TABLE qobuz_albums_songs ADD COLUMN musicbrainz_track_id TEXT;
|
||||||
|
|
||||||
|
ALTER TABLE qobuz_albums_songs ADD COLUMN musicbrainz_disc_id TEXT;
|
||||||
|
|
||||||
|
ALTER TABLE qobuz_albums_songs ADD COLUMN musicbrainz_release_group_id TEXT;
|
||||||
|
|
||||||
|
ALTER TABLE qobuz_albums_songs ADD COLUMN musicbrainz_work_id TEXT;
|
||||||
|
|
||||||
|
ALTER TABLE qobuz_songs ADD COLUMN acoustid_id TEXT;
|
||||||
|
|
||||||
|
ALTER TABLE qobuz_songs ADD COLUMN acoustid_fingerprint TEXT;
|
||||||
|
|
||||||
|
ALTER TABLE qobuz_songs ADD COLUMN musicbrainz_album_artist_id TEXT;
|
||||||
|
|
||||||
|
ALTER TABLE qobuz_songs ADD COLUMN musicbrainz_artist_id TEXT;
|
||||||
|
|
||||||
|
ALTER TABLE qobuz_songs ADD COLUMN musicbrainz_original_artist_id TEXT;
|
||||||
|
|
||||||
|
ALTER TABLE qobuz_songs ADD COLUMN musicbrainz_album_id TEXT;
|
||||||
|
|
||||||
|
ALTER TABLE qobuz_songs ADD COLUMN musicbrainz_original_album_id TEXT;
|
||||||
|
|
||||||
|
ALTER TABLE qobuz_songs ADD COLUMN musicbrainz_recording_id TEXT;
|
||||||
|
|
||||||
|
ALTER TABLE qobuz_songs ADD COLUMN musicbrainz_track_id TEXT;
|
||||||
|
|
||||||
|
ALTER TABLE qobuz_songs ADD COLUMN musicbrainz_disc_id TEXT;
|
||||||
|
|
||||||
|
ALTER TABLE qobuz_songs ADD COLUMN musicbrainz_release_group_id TEXT;
|
||||||
|
|
||||||
|
ALTER TABLE qobuz_songs ADD COLUMN musicbrainz_work_id TEXT;
|
||||||
|
|
||||||
|
ALTER TABLE playlist_items ADD COLUMN acoustid_id TEXT;
|
||||||
|
|
||||||
|
ALTER TABLE playlist_items ADD COLUMN acoustid_fingerprint TEXT;
|
||||||
|
|
||||||
|
ALTER TABLE playlist_items ADD COLUMN musicbrainz_album_artist_id TEXT;
|
||||||
|
|
||||||
|
ALTER TABLE playlist_items ADD COLUMN musicbrainz_artist_id TEXT;
|
||||||
|
|
||||||
|
ALTER TABLE playlist_items ADD COLUMN musicbrainz_original_artist_id TEXT;
|
||||||
|
|
||||||
|
ALTER TABLE playlist_items ADD COLUMN musicbrainz_album_id TEXT;
|
||||||
|
|
||||||
|
ALTER TABLE playlist_items ADD COLUMN musicbrainz_original_album_id TEXT;
|
||||||
|
|
||||||
|
ALTER TABLE playlist_items ADD COLUMN musicbrainz_recording_id TEXT;
|
||||||
|
|
||||||
|
ALTER TABLE playlist_items ADD COLUMN musicbrainz_track_id TEXT;
|
||||||
|
|
||||||
|
ALTER TABLE playlist_items ADD COLUMN musicbrainz_disc_id TEXT;
|
||||||
|
|
||||||
|
ALTER TABLE playlist_items ADD COLUMN musicbrainz_release_group_id TEXT;
|
||||||
|
|
||||||
|
ALTER TABLE playlist_items ADD COLUMN musicbrainz_work_id TEXT;
|
||||||
|
|
||||||
|
UPDATE schema_version SET version=16;
|
||||||
@@ -4,7 +4,7 @@ CREATE TABLE IF NOT EXISTS schema_version (
|
|||||||
|
|
||||||
DELETE FROM schema_version;
|
DELETE FROM schema_version;
|
||||||
|
|
||||||
INSERT INTO schema_version (version) VALUES (15);
|
INSERT INTO schema_version (version) VALUES (16);
|
||||||
|
|
||||||
CREATE TABLE IF NOT EXISTS directories (
|
CREATE TABLE IF NOT EXISTS directories (
|
||||||
path TEXT NOT NULL,
|
path TEXT NOT NULL,
|
||||||
@@ -75,7 +75,21 @@ CREATE TABLE IF NOT EXISTS songs (
|
|||||||
|
|
||||||
cue_path TEXT,
|
cue_path TEXT,
|
||||||
|
|
||||||
rating INTEGER DEFAULT -1
|
rating INTEGER DEFAULT -1,
|
||||||
|
|
||||||
|
acoustid_id TEXT,
|
||||||
|
acoustid_fingerprint TEXT,
|
||||||
|
|
||||||
|
musicbrainz_album_artist_id TEXT,
|
||||||
|
musicbrainz_artist_id TEXT,
|
||||||
|
musicbrainz_original_artist_id TEXT,
|
||||||
|
musicbrainz_album_id TEXT,
|
||||||
|
musicbrainz_original_album_id TEXT,
|
||||||
|
musicbrainz_recording_id TEXT,
|
||||||
|
musicbrainz_track_id TEXT,
|
||||||
|
musicbrainz_disc_id TEXT,
|
||||||
|
musicbrainz_release_group_id TEXT,
|
||||||
|
musicbrainz_work_id TEXT
|
||||||
|
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -137,7 +151,21 @@ CREATE TABLE IF NOT EXISTS subsonic_songs (
|
|||||||
|
|
||||||
cue_path TEXT,
|
cue_path TEXT,
|
||||||
|
|
||||||
rating INTEGER DEFAULT -1
|
rating INTEGER DEFAULT -1,
|
||||||
|
|
||||||
|
acoustid_id TEXT,
|
||||||
|
acoustid_fingerprint TEXT,
|
||||||
|
|
||||||
|
musicbrainz_album_artist_id TEXT,
|
||||||
|
musicbrainz_artist_id TEXT,
|
||||||
|
musicbrainz_original_artist_id TEXT,
|
||||||
|
musicbrainz_album_id TEXT,
|
||||||
|
musicbrainz_original_album_id TEXT,
|
||||||
|
musicbrainz_recording_id TEXT,
|
||||||
|
musicbrainz_track_id TEXT,
|
||||||
|
musicbrainz_disc_id TEXT,
|
||||||
|
musicbrainz_release_group_id TEXT,
|
||||||
|
musicbrainz_work_id TEXT
|
||||||
|
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -199,7 +227,21 @@ CREATE TABLE IF NOT EXISTS tidal_artists_songs (
|
|||||||
|
|
||||||
cue_path TEXT,
|
cue_path TEXT,
|
||||||
|
|
||||||
rating INTEGER DEFAULT -1
|
rating INTEGER DEFAULT -1,
|
||||||
|
|
||||||
|
acoustid_id TEXT,
|
||||||
|
acoustid_fingerprint TEXT,
|
||||||
|
|
||||||
|
musicbrainz_album_artist_id TEXT,
|
||||||
|
musicbrainz_artist_id TEXT,
|
||||||
|
musicbrainz_original_artist_id TEXT,
|
||||||
|
musicbrainz_album_id TEXT,
|
||||||
|
musicbrainz_original_album_id TEXT,
|
||||||
|
musicbrainz_recording_id TEXT,
|
||||||
|
musicbrainz_track_id TEXT,
|
||||||
|
musicbrainz_disc_id TEXT,
|
||||||
|
musicbrainz_release_group_id TEXT,
|
||||||
|
musicbrainz_work_id TEXT
|
||||||
|
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -261,7 +303,21 @@ CREATE TABLE IF NOT EXISTS tidal_albums_songs (
|
|||||||
|
|
||||||
cue_path TEXT,
|
cue_path TEXT,
|
||||||
|
|
||||||
rating INTEGER DEFAULT -1
|
rating INTEGER DEFAULT -1,
|
||||||
|
|
||||||
|
acoustid_id TEXT,
|
||||||
|
acoustid_fingerprint TEXT,
|
||||||
|
|
||||||
|
musicbrainz_album_artist_id TEXT,
|
||||||
|
musicbrainz_artist_id TEXT,
|
||||||
|
musicbrainz_original_artist_id TEXT,
|
||||||
|
musicbrainz_album_id TEXT,
|
||||||
|
musicbrainz_original_album_id TEXT,
|
||||||
|
musicbrainz_recording_id TEXT,
|
||||||
|
musicbrainz_track_id TEXT,
|
||||||
|
musicbrainz_disc_id TEXT,
|
||||||
|
musicbrainz_release_group_id TEXT,
|
||||||
|
musicbrainz_work_id TEXT
|
||||||
|
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -323,7 +379,21 @@ CREATE TABLE IF NOT EXISTS tidal_songs (
|
|||||||
|
|
||||||
cue_path TEXT,
|
cue_path TEXT,
|
||||||
|
|
||||||
rating INTEGER DEFAULT -1
|
rating INTEGER DEFAULT -1,
|
||||||
|
|
||||||
|
acoustid_id TEXT,
|
||||||
|
acoustid_fingerprint TEXT,
|
||||||
|
|
||||||
|
musicbrainz_album_artist_id TEXT,
|
||||||
|
musicbrainz_artist_id TEXT,
|
||||||
|
musicbrainz_original_artist_id TEXT,
|
||||||
|
musicbrainz_album_id TEXT,
|
||||||
|
musicbrainz_original_album_id TEXT,
|
||||||
|
musicbrainz_recording_id TEXT,
|
||||||
|
musicbrainz_track_id TEXT,
|
||||||
|
musicbrainz_disc_id TEXT,
|
||||||
|
musicbrainz_release_group_id TEXT,
|
||||||
|
musicbrainz_work_id TEXT
|
||||||
|
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -385,7 +455,21 @@ CREATE TABLE IF NOT EXISTS qobuz_artists_songs (
|
|||||||
|
|
||||||
cue_path TEXT,
|
cue_path TEXT,
|
||||||
|
|
||||||
rating INTEGER DEFAULT -1
|
rating INTEGER DEFAULT -1,
|
||||||
|
|
||||||
|
acoustid_id TEXT,
|
||||||
|
acoustid_fingerprint TEXT,
|
||||||
|
|
||||||
|
musicbrainz_album_artist_id TEXT,
|
||||||
|
musicbrainz_artist_id TEXT,
|
||||||
|
musicbrainz_original_artist_id TEXT,
|
||||||
|
musicbrainz_album_id TEXT,
|
||||||
|
musicbrainz_original_album_id TEXT,
|
||||||
|
musicbrainz_recording_id TEXT,
|
||||||
|
musicbrainz_track_id TEXT,
|
||||||
|
musicbrainz_disc_id TEXT,
|
||||||
|
musicbrainz_release_group_id TEXT,
|
||||||
|
musicbrainz_work_id TEXT
|
||||||
|
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -447,7 +531,21 @@ CREATE TABLE IF NOT EXISTS qobuz_albums_songs (
|
|||||||
|
|
||||||
cue_path TEXT,
|
cue_path TEXT,
|
||||||
|
|
||||||
rating INTEGER DEFAULT -1
|
rating INTEGER DEFAULT -1,
|
||||||
|
|
||||||
|
acoustid_id TEXT,
|
||||||
|
acoustid_fingerprint TEXT,
|
||||||
|
|
||||||
|
musicbrainz_album_artist_id TEXT,
|
||||||
|
musicbrainz_artist_id TEXT,
|
||||||
|
musicbrainz_original_artist_id TEXT,
|
||||||
|
musicbrainz_album_id TEXT,
|
||||||
|
musicbrainz_original_album_id TEXT,
|
||||||
|
musicbrainz_recording_id TEXT,
|
||||||
|
musicbrainz_track_id TEXT,
|
||||||
|
musicbrainz_disc_id TEXT,
|
||||||
|
musicbrainz_release_group_id TEXT,
|
||||||
|
musicbrainz_work_id TEXT
|
||||||
|
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -509,7 +607,21 @@ CREATE TABLE IF NOT EXISTS qobuz_songs (
|
|||||||
|
|
||||||
cue_path TEXT,
|
cue_path TEXT,
|
||||||
|
|
||||||
rating INTEGER DEFAULT -1
|
rating INTEGER DEFAULT -1,
|
||||||
|
|
||||||
|
acoustid_id TEXT,
|
||||||
|
acoustid_fingerprint TEXT,
|
||||||
|
|
||||||
|
musicbrainz_album_artist_id TEXT,
|
||||||
|
musicbrainz_artist_id TEXT,
|
||||||
|
musicbrainz_original_artist_id TEXT,
|
||||||
|
musicbrainz_album_id TEXT,
|
||||||
|
musicbrainz_original_album_id TEXT,
|
||||||
|
musicbrainz_recording_id TEXT,
|
||||||
|
musicbrainz_track_id TEXT,
|
||||||
|
musicbrainz_disc_id TEXT,
|
||||||
|
musicbrainz_release_group_id TEXT,
|
||||||
|
musicbrainz_work_id TEXT
|
||||||
|
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -591,7 +703,21 @@ CREATE TABLE IF NOT EXISTS playlist_items (
|
|||||||
|
|
||||||
cue_path TEXT,
|
cue_path TEXT,
|
||||||
|
|
||||||
rating INTEGER DEFAULT -1
|
rating INTEGER DEFAULT -1,
|
||||||
|
|
||||||
|
acoustid_id TEXT,
|
||||||
|
acoustid_fingerprint TEXT,
|
||||||
|
|
||||||
|
musicbrainz_album_artist_id TEXT,
|
||||||
|
musicbrainz_artist_id TEXT,
|
||||||
|
musicbrainz_original_artist_id TEXT,
|
||||||
|
musicbrainz_album_id TEXT,
|
||||||
|
musicbrainz_original_album_id TEXT,
|
||||||
|
musicbrainz_recording_id TEXT,
|
||||||
|
musicbrainz_track_id TEXT,
|
||||||
|
musicbrainz_disc_id TEXT,
|
||||||
|
musicbrainz_release_group_id TEXT,
|
||||||
|
musicbrainz_work_id TEXT
|
||||||
|
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|||||||
2
debian/control.in
vendored
2
debian/control.in
vendored
@@ -52,7 +52,7 @@ Description: music player and music collection organizer
|
|||||||
- Edit tags on audio files
|
- Edit tags on audio files
|
||||||
- Automatically retrieve tags from MusicBrainz
|
- Automatically retrieve tags from MusicBrainz
|
||||||
- Album cover art from Last.fm, Musicbrainz, Discogs, Musixmatch, Deezer, Tidal, Qobuz and Spotify
|
- Album cover art from Last.fm, Musicbrainz, Discogs, Musixmatch, Deezer, Tidal, Qobuz and Spotify
|
||||||
- Song lyrics from AudD, Genius, Musixmatch, ChartLyrics, lyrics.ovh and lololyrics.com
|
- Song lyrics from Lyrics.com, AudD, Genius, Musixmatch, ChartLyrics, lyrics.ovh and lololyrics.com
|
||||||
- Audio analyzer
|
- Audio analyzer
|
||||||
- Audio equalizer
|
- Audio equalizer
|
||||||
- Transfer music to mass-storage USB players, MTP compatible devices and iPod Nano/Classic
|
- Transfer music to mass-storage USB players, MTP compatible devices and iPod Nano/Classic
|
||||||
|
|||||||
20
debian/copyright
vendored
20
debian/copyright
vendored
@@ -5,10 +5,10 @@ Source: https://github.com/strawberrymusicplayer/strawberry
|
|||||||
|
|
||||||
Files: *
|
Files: *
|
||||||
Copyright: 2010-2015, David Sansome <me@davidsansome.com>
|
Copyright: 2010-2015, David Sansome <me@davidsansome.com>
|
||||||
2012-2014, 2017-2022 Jonas Kvinge <jonas@jkvinge.net>
|
2012-2014, 2017-2023 Jonas Kvinge <jonas@jkvinge.net>
|
||||||
License: GPL-3+
|
License: GPL-3+
|
||||||
|
|
||||||
Files: src/core/timeconstants.h
|
Files: src/utilities/timeconstants.h
|
||||||
ext/libstrawberry-common/core/logging.cpp
|
ext/libstrawberry-common/core/logging.cpp
|
||||||
ext/libstrawberry-common/core/logging.h
|
ext/libstrawberry-common/core/logging.h
|
||||||
ext/libstrawberry-common/core/messagehandler.cpp
|
ext/libstrawberry-common/core/messagehandler.cpp
|
||||||
@@ -98,7 +98,7 @@ Files: src/core/main.h
|
|||||||
ext/macdeploycheck/*
|
ext/macdeploycheck/*
|
||||||
src/widgets/resizabletextedit.cpp
|
src/widgets/resizabletextedit.cpp
|
||||||
src/widgets/resizabletextedit.h
|
src/widgets/resizabletextedit.h
|
||||||
Copyright: 2012-2014, 2017-2022, Jonas Kvinge <jonas@jkvinge.net>
|
Copyright: 2012-2014, 2017-2023, Jonas Kvinge <jonas@jkvinge.net>
|
||||||
License: GPL-3+
|
License: GPL-3+
|
||||||
|
|
||||||
Files: src/engine/enginebase.cpp
|
Files: src/engine/enginebase.cpp
|
||||||
@@ -130,11 +130,6 @@ Copyright: 2012, David Sansome <me@davidsansome.com>
|
|||||||
2018-2021, Jonas Kvinge <jonas@jkvinge.net>
|
2018-2021, Jonas Kvinge <jonas@jkvinge.net>
|
||||||
License: GPL-3+
|
License: GPL-3+
|
||||||
|
|
||||||
Files: src/core/appearance.cpp
|
|
||||||
src/core/appearance.h
|
|
||||||
Copyright: 2012, Arnaud Bienner <arnaud.bienner@gmail.com>
|
|
||||||
License: GPL-3+
|
|
||||||
|
|
||||||
Files: src/covermanager/discogscoverprovider.cpp
|
Files: src/covermanager/discogscoverprovider.cpp
|
||||||
src/covermanager/discogscoverprovider.h
|
src/covermanager/discogscoverprovider.h
|
||||||
Copyright: 2012, Martin Björklund <mbj4668@gmail.com>
|
Copyright: 2012, Martin Björklund <mbj4668@gmail.com>
|
||||||
@@ -232,9 +227,14 @@ Files: src/widgets/clickablelabel.cpp
|
|||||||
Copyright: 2010, 2011, Andrea Decorte <adecorte@gmail.com>
|
Copyright: 2010, 2011, Andrea Decorte <adecorte@gmail.com>
|
||||||
License: GPL-3+
|
License: GPL-3+
|
||||||
|
|
||||||
Files: src/widgets/volumeslider.cpp
|
Files: src/widgets/sliderslider.cpp
|
||||||
|
src/widgets/sliderslider.h
|
||||||
|
src/widgets/prettyslider.cpp
|
||||||
|
src/widgets/prettyslider.h
|
||||||
|
src/widgets/volumeslider.cpp
|
||||||
src/widgets/volumeslider.h
|
src/widgets/volumeslider.h
|
||||||
Copyright: 2005, Gábor Lehel
|
Copyright: 2018-2023, Jonas Kvinge <jonas@jkvinge.net>
|
||||||
|
2005, Gábor Lehel
|
||||||
2003, Mark Kretschmann <markey@web.de>
|
2003, Mark Kretschmann <markey@web.de>
|
||||||
License: GPL-2+
|
License: GPL-2+
|
||||||
|
|
||||||
|
|||||||
@@ -29,7 +29,7 @@
|
|||||||
<li>Edit tags on audio files</li>
|
<li>Edit tags on audio files</li>
|
||||||
<li>Automatically retrieve tags from MusicBrainz</li>
|
<li>Automatically retrieve tags from MusicBrainz</li>
|
||||||
<li>Album cover art from Last.fm, Musicbrainz, Discogs, Musixmatch, Deezer, Tidal, Qobuz and Spotify</li>
|
<li>Album cover art from Last.fm, Musicbrainz, Discogs, Musixmatch, Deezer, Tidal, Qobuz and Spotify</li>
|
||||||
<li>Song lyrics from AudD, Genius, Musixmatch, ChartLyrics, lyrics.ovh and lololyrics.com</li>
|
<li>Song lyrics from Lyrics.com, AudD, Genius, Musixmatch, ChartLyrics, lyrics.ovh and lololyrics.com</li>
|
||||||
<li>Support for multiple backends</li>
|
<li>Support for multiple backends</li>
|
||||||
<li>Audio analyzer and equalizer</li>
|
<li>Audio analyzer and equalizer</li>
|
||||||
<li>Transfer music to mass-storage USB players, MTP compatible devices and iPod Nano/Classic</li>
|
<li>Transfer music to mass-storage USB players, MTP compatible devices and iPod Nano/Classic</li>
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ TryExec=strawberry
|
|||||||
Icon=strawberry
|
Icon=strawberry
|
||||||
Terminal=false
|
Terminal=false
|
||||||
Categories=AudioVideo;Player;Qt;Audio;
|
Categories=AudioVideo;Player;Qt;Audio;
|
||||||
|
Keywords=Audio;Player;
|
||||||
StartupNotify=false
|
StartupNotify=false
|
||||||
MimeType=x-content/audio-player;application/ogg;application/x-ogg;application/x-ogm-audio;audio/flac;audio/ogg;audio/vorbis;audio/aac;audio/mp4;audio/mpeg;audio/mpegurl;audio/vnd.rn-realaudio;audio/x-flac;audio/x-oggflac;audio/x-vorbis;audio/x-vorbis+ogg;audio/x-speex;audio/x-wav;audio/x-wavpack;audio/x-ape;audio/x-mp3;audio/x-mpeg;audio/x-mpegurl;audio/x-ms-wma;audio/x-musepack;audio/x-pn-realaudio;audio/x-scpls;video/x-ms-asf;x-scheme-handler/tidal;
|
MimeType=x-content/audio-player;application/ogg;application/x-ogg;application/x-ogm-audio;audio/flac;audio/ogg;audio/vorbis;audio/aac;audio/mp4;audio/mpeg;audio/mpegurl;audio/vnd.rn-realaudio;audio/x-flac;audio/x-oggflac;audio/x-vorbis;audio/x-vorbis+ogg;audio/x-speex;audio/x-wav;audio/x-wavpack;audio/x-ape;audio/x-mp3;audio/x-mpeg;audio/x-mpegurl;audio/x-ms-wma;audio/x-musepack;audio/x-pn-realaudio;audio/x-scpls;video/x-ms-asf;x-scheme-handler/tidal;
|
||||||
StartupWMClass=strawberry
|
StartupWMClass=strawberry
|
||||||
|
|||||||
2
dist/unix/strawberry.1
vendored
2
dist/unix/strawberry.1
vendored
@@ -29,7 +29,7 @@ Features:
|
|||||||
.br
|
.br
|
||||||
- Album cover art from Last.fm, Musicbrainz, Discogs, Musixmatch, Deezer, Tidal, Qobuz and Spotify
|
- Album cover art from Last.fm, Musicbrainz, Discogs, Musixmatch, Deezer, Tidal, Qobuz and Spotify
|
||||||
.br
|
.br
|
||||||
- Song lyrics from AudD, Genius, Musixmatch, ChartLyrics, lyrics.ovh and lololyrics.com
|
- Song lyrics from Lyrics.com, AudD, Genius, Musixmatch, ChartLyrics, lyrics.ovh and lololyrics.com
|
||||||
.br
|
.br
|
||||||
- Support for multiple backends
|
- Support for multiple backends
|
||||||
.br
|
.br
|
||||||
|
|||||||
2
dist/unix/strawberry.spec.in
vendored
2
dist/unix/strawberry.spec.in
vendored
@@ -119,7 +119,7 @@ Features:
|
|||||||
- Edit tags on audio files
|
- Edit tags on audio files
|
||||||
- Automatically retrieve tags from MusicBrainz
|
- Automatically retrieve tags from MusicBrainz
|
||||||
- Album cover art from Last.fm, Musicbrainz, Discogs, Musixmatch, Deezer, Tidal, Qobuz and Spotify
|
- Album cover art from Last.fm, Musicbrainz, Discogs, Musixmatch, Deezer, Tidal, Qobuz and Spotify
|
||||||
- Song lyrics from AudD, Genius, Musixmatch, ChartLyrics, lyrics.ovh and lololyrics.com
|
- Song lyrics from Lyrics.com, AudD, Genius, Musixmatch, ChartLyrics, lyrics.ovh and lololyrics.com
|
||||||
- Support for multiple backends
|
- Support for multiple backends
|
||||||
- Audio analyzer
|
- Audio analyzer
|
||||||
- Audio equalizer
|
- Audio equalizer
|
||||||
|
|||||||
213
dist/windows/strawberry.nsi.in
vendored
213
dist/windows/strawberry.nsi.in
vendored
@@ -1,57 +1,68 @@
|
|||||||
!define build_type ""
|
|
||||||
!define compiler "unknown"
|
|
||||||
!define arch "unknown"
|
|
||||||
|
|
||||||
!if "@ARCH@" == "x86"
|
|
||||||
!define arch_x86
|
|
||||||
!undef arch
|
|
||||||
!define arch "x86"
|
|
||||||
!endif
|
|
||||||
|
|
||||||
!if "@ARCH@" == "i686-w64-mingw32.shared"
|
|
||||||
!define arch_x86
|
|
||||||
!undef arch
|
|
||||||
!define arch "x86"
|
|
||||||
!endif
|
|
||||||
|
|
||||||
!if "@ARCH@" == "x86_64"
|
|
||||||
!define arch_x64
|
|
||||||
!undef arch
|
|
||||||
!define arch "x64"
|
|
||||||
!endif
|
|
||||||
|
|
||||||
!if "@ARCH@" == "x86_64-w64-mingw32.shared"
|
|
||||||
!define arch_x64
|
|
||||||
!undef arch
|
|
||||||
!define arch "x64"
|
|
||||||
!endif
|
|
||||||
|
|
||||||
!if "@MINGW@" == "1"
|
!if "@MINGW@" == "1"
|
||||||
!define mingw
|
!define mingw
|
||||||
!undef compiler
|
|
||||||
!define compiler "mingw"
|
!define compiler "mingw"
|
||||||
!endif
|
!endif
|
||||||
|
|
||||||
!if "@MSVC@" == "1"
|
!if "@MSVC@" == "1"
|
||||||
!define msvc
|
!define msvc
|
||||||
!undef compiler
|
|
||||||
!define compiler "msvc"
|
!define compiler "msvc"
|
||||||
!endif
|
!endif
|
||||||
|
|
||||||
|
|
||||||
|
!if "@ARCH@" == "x86"
|
||||||
|
!define arch_x86
|
||||||
|
!else if "@ARCH@" == "i686"
|
||||||
|
!define arch_x86
|
||||||
|
!else if "@ARCH@" == "i686-w64-mingw32.shared"
|
||||||
|
!define arch_x86
|
||||||
|
!else if "@ARCH@" == "x64"
|
||||||
|
!define arch_x64
|
||||||
|
!else if "@ARCH@" == "x86_64"
|
||||||
|
!define arch_x64
|
||||||
|
!else if "@ARCH@" == "x86_64-w64-mingw32.shared"
|
||||||
|
!define arch_x64
|
||||||
|
!endif
|
||||||
|
|
||||||
|
!ifdef arch_x86
|
||||||
|
!define arch "x86"
|
||||||
|
!endif
|
||||||
|
|
||||||
|
!ifdef arch_x64
|
||||||
|
!define arch "x64"
|
||||||
|
!endif
|
||||||
|
|
||||||
|
|
||||||
!if "@CMAKE_BUILD_TYPE@" == "Release"
|
!if "@CMAKE_BUILD_TYPE@" == "Release"
|
||||||
!define release
|
!define release
|
||||||
!endif
|
!else if "@CMAKE_BUILD_TYPE@" == "RelWithDebInfo"
|
||||||
|
|
||||||
!if "@CMAKE_BUILD_TYPE@" == "RelWithDebInfo"
|
|
||||||
!define release
|
!define release
|
||||||
|
!else if "@CMAKE_BUILD_TYPE@" == "Debug"
|
||||||
|
!define debug
|
||||||
!endif
|
!endif
|
||||||
|
|
||||||
!if "@CMAKE_BUILD_TYPE@" == "Debug"
|
!ifdef release
|
||||||
!define debug
|
!define build_type ""
|
||||||
!undef build_type
|
!endif
|
||||||
|
|
||||||
|
!ifdef debug
|
||||||
!define build_type "-Debug"
|
!define build_type "-Debug"
|
||||||
!endif
|
!endif
|
||||||
|
|
||||||
|
|
||||||
|
!ifndef compiler
|
||||||
|
!error "Missing compiler."
|
||||||
|
!endif
|
||||||
|
|
||||||
|
!ifndef build_type
|
||||||
|
!error "Missing build type."
|
||||||
|
!endif
|
||||||
|
|
||||||
|
!ifndef arch
|
||||||
|
!error "Missing arch."
|
||||||
|
!endif
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
!ifdef debug
|
!ifdef debug
|
||||||
!define PRODUCT_NAME "Strawberry Music Player Debug"
|
!define PRODUCT_NAME "Strawberry Music Player Debug"
|
||||||
!define PRODUCT_NAME_SHORT "Strawberry"
|
!define PRODUCT_NAME_SHORT "Strawberry"
|
||||||
@@ -241,10 +252,10 @@ Section "Strawberry" Strawberry
|
|||||||
File "libssl-3-x64.dll"
|
File "libssl-3-x64.dll"
|
||||||
!endif
|
!endif
|
||||||
|
|
||||||
File "avcodec-59.dll"
|
File "avcodec-60.dll"
|
||||||
File "avfilter-8.dll"
|
File "avfilter-9.dll"
|
||||||
File "avformat-59.dll"
|
File "avformat-60.dll"
|
||||||
File "avutil-57.dll"
|
File "avutil-58.dll"
|
||||||
File "libFLAC-12.dll"
|
File "libFLAC-12.dll"
|
||||||
File "libbrotlicommon.dll"
|
File "libbrotlicommon.dll"
|
||||||
File "libbrotlidec.dll"
|
File "libbrotlidec.dll"
|
||||||
@@ -301,7 +312,6 @@ Section "Strawberry" Strawberry
|
|||||||
File "libopus-0.dll"
|
File "libopus-0.dll"
|
||||||
File "liborc-0.4-0.dll"
|
File "liborc-0.4-0.dll"
|
||||||
File "libpng16-16.dll"
|
File "libpng16-16.dll"
|
||||||
File "libprotobuf-32.dll"
|
|
||||||
File "libpsl-5.dll"
|
File "libpsl-5.dll"
|
||||||
File "libqtsparkle-qt6.dll"
|
File "libqtsparkle-qt6.dll"
|
||||||
File "libsoup-3.0-0.dll"
|
File "libsoup-3.0-0.dll"
|
||||||
@@ -320,11 +330,51 @@ Section "Strawberry" Strawberry
|
|||||||
File "libwinpthread-1.dll"
|
File "libwinpthread-1.dll"
|
||||||
File "libxml2-2.dll"
|
File "libxml2-2.dll"
|
||||||
File "libzstd.dll"
|
File "libzstd.dll"
|
||||||
File "postproc-56.dll"
|
File "postproc-57.dll"
|
||||||
File "swresample-4.dll"
|
File "swresample-4.dll"
|
||||||
File "swscale-6.dll"
|
File "swscale-7.dll"
|
||||||
File "zlib1.dll"
|
File "zlib1.dll"
|
||||||
|
|
||||||
|
File "libabsl_base.dll"
|
||||||
|
File "libabsl_city.dll"
|
||||||
|
File "libabsl_cord.dll"
|
||||||
|
File "libabsl_cord_internal.dll"
|
||||||
|
File "libabsl_cordz_handle.dll"
|
||||||
|
File "libabsl_cordz_info.dll"
|
||||||
|
File "libabsl_crc32c.dll"
|
||||||
|
File "libabsl_crc_cord_state.dll"
|
||||||
|
File "libabsl_crc_internal.dll"
|
||||||
|
File "libabsl_die_if_null.dll"
|
||||||
|
File "libabsl_examine_stack.dll"
|
||||||
|
File "libabsl_hash.dll"
|
||||||
|
File "libabsl_int128.dll"
|
||||||
|
File "libabsl_log_globals.dll"
|
||||||
|
File "libabsl_log_internal_check_op.dll"
|
||||||
|
File "libabsl_log_internal_format.dll"
|
||||||
|
File "libabsl_log_internal_globals.dll"
|
||||||
|
File "libabsl_log_internal_log_sink_set.dll"
|
||||||
|
File "libabsl_log_internal_message.dll"
|
||||||
|
File "libabsl_log_internal_nullguard.dll"
|
||||||
|
File "libabsl_log_internal_proto.dll"
|
||||||
|
File "libabsl_log_sink.dll"
|
||||||
|
File "libabsl_low_level_hash.dll"
|
||||||
|
File "libabsl_malloc_internal.dll"
|
||||||
|
File "libabsl_raw_hash_set.dll"
|
||||||
|
File "libabsl_raw_logging_internal.dll"
|
||||||
|
File "libabsl_spinlock_wait.dll"
|
||||||
|
File "libabsl_stacktrace.dll"
|
||||||
|
File "libabsl_status.dll"
|
||||||
|
File "libabsl_statusor.dll"
|
||||||
|
File "libabsl_strerror.dll"
|
||||||
|
File "libabsl_str_format_internal.dll"
|
||||||
|
File "libabsl_strings.dll"
|
||||||
|
File "libabsl_strings_internal.dll"
|
||||||
|
File "libabsl_symbolize.dll"
|
||||||
|
File "libabsl_synchronization.dll"
|
||||||
|
File "libabsl_throw_delegate.dll"
|
||||||
|
File "libabsl_time.dll"
|
||||||
|
File "libabsl_time_zone.dll"
|
||||||
|
|
||||||
!ifdef debug
|
!ifdef debug
|
||||||
File "gdb.exe"
|
File "gdb.exe"
|
||||||
File "libexpat-1.dll"
|
File "libexpat-1.dll"
|
||||||
@@ -334,6 +384,7 @@ Section "Strawberry" Strawberry
|
|||||||
File "libpcre2-16d.dll"
|
File "libpcre2-16d.dll"
|
||||||
File "libreadline8.dll"
|
File "libreadline8.dll"
|
||||||
File "libtermcap.dll"
|
File "libtermcap.dll"
|
||||||
|
File "libabsl_graphcycles_internal.dll"
|
||||||
!else
|
!else
|
||||||
File "libpcre2-8.dll"
|
File "libpcre2-8.dll"
|
||||||
File "libpcre2-16.dll"
|
File "libpcre2-16.dll"
|
||||||
@@ -393,7 +444,6 @@ Section "Strawberry" Strawberry
|
|||||||
File "jpeg62.dll"
|
File "jpeg62.dll"
|
||||||
File "libbs2b.dll"
|
File "libbs2b.dll"
|
||||||
File "libfaac_dll.dll"
|
File "libfaac_dll.dll"
|
||||||
File "libiconv.dll"
|
|
||||||
File "liblzma.dll"
|
File "liblzma.dll"
|
||||||
File "libmp3lame.dll"
|
File "libmp3lame.dll"
|
||||||
File "libopenmpt.dll"
|
File "libopenmpt.dll"
|
||||||
@@ -415,11 +465,12 @@ Section "Strawberry" Strawberry
|
|||||||
File "vorbis.dll"
|
File "vorbis.dll"
|
||||||
File "vorbisfile.dll"
|
File "vorbisfile.dll"
|
||||||
File "wavpackdll.dll"
|
File "wavpackdll.dll"
|
||||||
|
File "abseil_dll.dll"
|
||||||
|
|
||||||
!ifdef release
|
!ifdef release
|
||||||
File "freetype.dll"
|
File "freetype.dll"
|
||||||
|
File "libiconv.dll"
|
||||||
File "libpng16.dll"
|
File "libpng16.dll"
|
||||||
File "libprotobuf.dll"
|
|
||||||
File "libxml2.dll"
|
File "libxml2.dll"
|
||||||
File "pcre2-8.dll"
|
File "pcre2-8.dll"
|
||||||
File "pcre2-16.dll"
|
File "pcre2-16.dll"
|
||||||
@@ -428,8 +479,8 @@ Section "Strawberry" Strawberry
|
|||||||
!endif
|
!endif
|
||||||
!ifdef debug
|
!ifdef debug
|
||||||
File "freetyped.dll"
|
File "freetyped.dll"
|
||||||
|
File "libiconvd.dll"
|
||||||
File "libpng16d.dll"
|
File "libpng16d.dll"
|
||||||
File "libprotobufd.dll"
|
|
||||||
File "libxml2d.dll"
|
File "libxml2d.dll"
|
||||||
File "pcre2-8d.dll"
|
File "pcre2-8d.dll"
|
||||||
File "pcre2-16d.dll"
|
File "pcre2-16d.dll"
|
||||||
@@ -443,6 +494,11 @@ Section "Strawberry" Strawberry
|
|||||||
|
|
||||||
File "icudt72.dll"
|
File "icudt72.dll"
|
||||||
File "libfftw3-3.dll"
|
File "libfftw3-3.dll"
|
||||||
|
!ifdef debug
|
||||||
|
File "libprotobufd.dll"
|
||||||
|
!else
|
||||||
|
File "libprotobuf.dll"
|
||||||
|
!endif
|
||||||
!ifdef msvc && debug
|
!ifdef msvc && debug
|
||||||
File "icuin72d.dll"
|
File "icuin72d.dll"
|
||||||
File "icuuc72d.dll"
|
File "icuuc72d.dll"
|
||||||
@@ -743,10 +799,10 @@ Section "Uninstall"
|
|||||||
Delete "$INSTDIR\libssl-3-x64.dll"
|
Delete "$INSTDIR\libssl-3-x64.dll"
|
||||||
!endif
|
!endif
|
||||||
|
|
||||||
Delete "$INSTDIR\avcodec-59.dll"
|
Delete "$INSTDIR\avcodec-60.dll"
|
||||||
Delete "$INSTDIR\avfilter-8.dll"
|
Delete "$INSTDIR\avfilter-9.dll"
|
||||||
Delete "$INSTDIR\avformat-59.dll"
|
Delete "$INSTDIR\avformat-60.dll"
|
||||||
Delete "$INSTDIR\avutil-57.dll"
|
Delete "$INSTDIR\avutil-58.dll"
|
||||||
Delete "$INSTDIR\libFLAC-12.dll"
|
Delete "$INSTDIR\libFLAC-12.dll"
|
||||||
Delete "$INSTDIR\libbrotlicommon.dll"
|
Delete "$INSTDIR\libbrotlicommon.dll"
|
||||||
Delete "$INSTDIR\libbrotlidec.dll"
|
Delete "$INSTDIR\libbrotlidec.dll"
|
||||||
@@ -803,7 +859,6 @@ Section "Uninstall"
|
|||||||
Delete "$INSTDIR\libopus-0.dll"
|
Delete "$INSTDIR\libopus-0.dll"
|
||||||
Delete "$INSTDIR\liborc-0.4-0.dll"
|
Delete "$INSTDIR\liborc-0.4-0.dll"
|
||||||
Delete "$INSTDIR\libpng16-16.dll"
|
Delete "$INSTDIR\libpng16-16.dll"
|
||||||
Delete "$INSTDIR\libprotobuf-32.dll"
|
|
||||||
Delete "$INSTDIR\libpsl-5.dll"
|
Delete "$INSTDIR\libpsl-5.dll"
|
||||||
Delete "$INSTDIR\libqtsparkle-qt6.dll"
|
Delete "$INSTDIR\libqtsparkle-qt6.dll"
|
||||||
Delete "$INSTDIR\libsoup-3.0-0.dll"
|
Delete "$INSTDIR\libsoup-3.0-0.dll"
|
||||||
@@ -822,11 +877,51 @@ Section "Uninstall"
|
|||||||
Delete "$INSTDIR\libwinpthread-1.dll"
|
Delete "$INSTDIR\libwinpthread-1.dll"
|
||||||
Delete "$INSTDIR\libxml2-2.dll"
|
Delete "$INSTDIR\libxml2-2.dll"
|
||||||
Delete "$INSTDIR\libzstd.dll"
|
Delete "$INSTDIR\libzstd.dll"
|
||||||
Delete "$INSTDIR\postproc-56.dll"
|
Delete "$INSTDIR\postproc-57.dll"
|
||||||
Delete "$INSTDIR\swresample-4.dll"
|
Delete "$INSTDIR\swresample-4.dll"
|
||||||
Delete "$INSTDIR\swscale-6.dll"
|
Delete "$INSTDIR\swscale-7.dll"
|
||||||
Delete "$INSTDIR\zlib1.dll"
|
Delete "$INSTDIR\zlib1.dll"
|
||||||
|
|
||||||
|
Delete "$INSTDIR\libabsl_base.dll"
|
||||||
|
Delete "$INSTDIR\libabsl_city.dll"
|
||||||
|
Delete "$INSTDIR\libabsl_cord.dll"
|
||||||
|
Delete "$INSTDIR\libabsl_cord_internal.dll"
|
||||||
|
Delete "$INSTDIR\libabsl_cordz_handle.dll"
|
||||||
|
Delete "$INSTDIR\libabsl_cordz_info.dll"
|
||||||
|
Delete "$INSTDIR\libabsl_crc32c.dll"
|
||||||
|
Delete "$INSTDIR\libabsl_crc_cord_state.dll"
|
||||||
|
Delete "$INSTDIR\libabsl_crc_internal.dll"
|
||||||
|
Delete "$INSTDIR\libabsl_die_if_null.dll"
|
||||||
|
Delete "$INSTDIR\libabsl_examine_stack.dll"
|
||||||
|
Delete "$INSTDIR\libabsl_hash.dll"
|
||||||
|
Delete "$INSTDIR\libabsl_int128.dll"
|
||||||
|
Delete "$INSTDIR\libabsl_log_globals.dll"
|
||||||
|
Delete "$INSTDIR\libabsl_log_internal_check_op.dll"
|
||||||
|
Delete "$INSTDIR\libabsl_log_internal_format.dll"
|
||||||
|
Delete "$INSTDIR\libabsl_log_internal_globals.dll"
|
||||||
|
Delete "$INSTDIR\libabsl_log_internal_log_sink_set.dll"
|
||||||
|
Delete "$INSTDIR\libabsl_log_internal_message.dll"
|
||||||
|
Delete "$INSTDIR\libabsl_log_internal_nullguard.dll"
|
||||||
|
Delete "$INSTDIR\libabsl_log_internal_proto.dll"
|
||||||
|
Delete "$INSTDIR\libabsl_log_sink.dll"
|
||||||
|
Delete "$INSTDIR\libabsl_low_level_hash.dll"
|
||||||
|
Delete "$INSTDIR\libabsl_malloc_internal.dll"
|
||||||
|
Delete "$INSTDIR\libabsl_raw_hash_set.dll"
|
||||||
|
Delete "$INSTDIR\libabsl_raw_logging_internal.dll"
|
||||||
|
Delete "$INSTDIR\libabsl_spinlock_wait.dll"
|
||||||
|
Delete "$INSTDIR\libabsl_stacktrace.dll"
|
||||||
|
Delete "$INSTDIR\libabsl_status.dll"
|
||||||
|
Delete "$INSTDIR\libabsl_statusor.dll"
|
||||||
|
Delete "$INSTDIR\libabsl_strerror.dll"
|
||||||
|
Delete "$INSTDIR\libabsl_str_format_internal.dll"
|
||||||
|
Delete "$INSTDIR\libabsl_strings.dll"
|
||||||
|
Delete "$INSTDIR\libabsl_strings_internal.dll"
|
||||||
|
Delete "$INSTDIR\libabsl_symbolize.dll"
|
||||||
|
Delete "$INSTDIR\libabsl_synchronization.dll"
|
||||||
|
Delete "$INSTDIR\libabsl_throw_delegate.dll"
|
||||||
|
Delete "$INSTDIR\libabsl_time.dll"
|
||||||
|
Delete "$INSTDIR\libabsl_time_zone.dll"
|
||||||
|
|
||||||
!ifdef debug
|
!ifdef debug
|
||||||
Delete "$INSTDIR\gdb.exe"
|
Delete "$INSTDIR\gdb.exe"
|
||||||
Delete "$INSTDIR\libexpat-1.dll"
|
Delete "$INSTDIR\libexpat-1.dll"
|
||||||
@@ -836,6 +931,7 @@ Section "Uninstall"
|
|||||||
Delete "$INSTDIR\libpcre2-16d.dll"
|
Delete "$INSTDIR\libpcre2-16d.dll"
|
||||||
Delete "$INSTDIR\libreadline8.dll"
|
Delete "$INSTDIR\libreadline8.dll"
|
||||||
Delete "$INSTDIR\libtermcap.dll"
|
Delete "$INSTDIR\libtermcap.dll"
|
||||||
|
Delete "$INSTDIR\libabsl_graphcycles_internal.dll"
|
||||||
!else
|
!else
|
||||||
Delete "$INSTDIR\libpcre2-8.dll"
|
Delete "$INSTDIR\libpcre2-8.dll"
|
||||||
Delete "$INSTDIR\libpcre2-16.dll"
|
Delete "$INSTDIR\libpcre2-16.dll"
|
||||||
@@ -895,7 +991,6 @@ Section "Uninstall"
|
|||||||
Delete "$INSTDIR\jpeg62.dll"
|
Delete "$INSTDIR\jpeg62.dll"
|
||||||
Delete "$INSTDIR\libbs2b.dll"
|
Delete "$INSTDIR\libbs2b.dll"
|
||||||
Delete "$INSTDIR\libfaac_dll.dll"
|
Delete "$INSTDIR\libfaac_dll.dll"
|
||||||
Delete "$INSTDIR\libiconv.dll"
|
|
||||||
Delete "$INSTDIR\liblzma.dll"
|
Delete "$INSTDIR\liblzma.dll"
|
||||||
Delete "$INSTDIR\libmp3lame.dll"
|
Delete "$INSTDIR\libmp3lame.dll"
|
||||||
Delete "$INSTDIR\libopenmpt.dll"
|
Delete "$INSTDIR\libopenmpt.dll"
|
||||||
@@ -917,11 +1012,12 @@ Section "Uninstall"
|
|||||||
Delete "$INSTDIR\vorbis.dll"
|
Delete "$INSTDIR\vorbis.dll"
|
||||||
Delete "$INSTDIR\vorbisfile.dll"
|
Delete "$INSTDIR\vorbisfile.dll"
|
||||||
Delete "$INSTDIR\wavpackdll.dll"
|
Delete "$INSTDIR\wavpackdll.dll"
|
||||||
|
Delete "$INSTDIR\abseil_dll.dll"
|
||||||
|
|
||||||
!ifdef release
|
!ifdef release
|
||||||
Delete "$INSTDIR\freetype.dll"
|
Delete "$INSTDIR\freetype.dll"
|
||||||
|
Delete "$INSTDIR\libiconv.dll"
|
||||||
Delete "$INSTDIR\libpng16.dll"
|
Delete "$INSTDIR\libpng16.dll"
|
||||||
Delete "$INSTDIR\libprotobuf.dll"
|
|
||||||
Delete "$INSTDIR\libxml2.dll"
|
Delete "$INSTDIR\libxml2.dll"
|
||||||
Delete "$INSTDIR\pcre2-8.dll"
|
Delete "$INSTDIR\pcre2-8.dll"
|
||||||
Delete "$INSTDIR\pcre2-16.dll"
|
Delete "$INSTDIR\pcre2-16.dll"
|
||||||
@@ -930,8 +1026,8 @@ Section "Uninstall"
|
|||||||
!endif
|
!endif
|
||||||
!ifdef debug
|
!ifdef debug
|
||||||
Delete "$INSTDIR\freetyped.dll"
|
Delete "$INSTDIR\freetyped.dll"
|
||||||
|
Delete "$INSTDIR\libiconvd.dll"
|
||||||
Delete "$INSTDIR\libpng16d.dll"
|
Delete "$INSTDIR\libpng16d.dll"
|
||||||
Delete "$INSTDIR\libprotobufd.dll"
|
|
||||||
Delete "$INSTDIR\libxml2d.dll"
|
Delete "$INSTDIR\libxml2d.dll"
|
||||||
Delete "$INSTDIR\pcre2-8d.dll"
|
Delete "$INSTDIR\pcre2-8d.dll"
|
||||||
Delete "$INSTDIR\pcre2-16d.dll"
|
Delete "$INSTDIR\pcre2-16d.dll"
|
||||||
@@ -945,6 +1041,11 @@ Section "Uninstall"
|
|||||||
|
|
||||||
Delete "$INSTDIR\icudt72.dll"
|
Delete "$INSTDIR\icudt72.dll"
|
||||||
Delete "$INSTDIR\libfftw3-3.dll"
|
Delete "$INSTDIR\libfftw3-3.dll"
|
||||||
|
!ifdef debug
|
||||||
|
Delete "$INSTDIR\libprotobufd.dll"
|
||||||
|
!else
|
||||||
|
Delete "$INSTDIR\libprotobuf.dll"
|
||||||
|
!endif
|
||||||
!ifdef msvc && debug
|
!ifdef msvc && debug
|
||||||
Delete "$INSTDIR\icuin72d.dll"
|
Delete "$INSTDIR\icuin72d.dll"
|
||||||
Delete "$INSTDIR\icuuc72d.dll"
|
Delete "$INSTDIR\icuuc72d.dll"
|
||||||
|
|||||||
@@ -82,9 +82,9 @@ static void gst_fastspectrum_class_init(GstFastSpectrumClass *klass) {
|
|||||||
|
|
||||||
filter_class->setup = GST_DEBUG_FUNCPTR(gst_fastspectrum_setup);
|
filter_class->setup = GST_DEBUG_FUNCPTR(gst_fastspectrum_setup);
|
||||||
|
|
||||||
g_object_class_install_property(gobject_class, PROP_INTERVAL, g_param_spec_uint64("interval", "Interval", "Interval of time between message posts (in nanoseconds)", 1, G_MAXUINT64, DEFAULT_INTERVAL, GParamFlags(G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)));
|
g_object_class_install_property(gobject_class, PROP_INTERVAL, g_param_spec_uint64("interval", "Interval", "Interval of time between message posts (in nanoseconds)", 1, G_MAXUINT64, DEFAULT_INTERVAL, static_cast<GParamFlags>(G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)));
|
||||||
|
|
||||||
g_object_class_install_property(gobject_class, PROP_BANDS, g_param_spec_uint("bands", "Bands", "Number of frequency bands", 0, G_MAXUINT, DEFAULT_BANDS, GParamFlags(G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)));
|
g_object_class_install_property(gobject_class, PROP_BANDS, g_param_spec_uint("bands", "Bands", "Number of frequency bands", 0, G_MAXUINT, DEFAULT_BANDS, static_cast<GParamFlags>(G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)));
|
||||||
|
|
||||||
GST_DEBUG_CATEGORY_INIT(gst_fastspectrum_debug, "spectrum", 0, "audio spectrum analyser element");
|
GST_DEBUG_CATEGORY_INIT(gst_fastspectrum_debug, "spectrum", 0, "audio spectrum analyser element");
|
||||||
|
|
||||||
|
|||||||
@@ -165,10 +165,10 @@ class WorkerPool : public _WorkerPoolBase {
|
|||||||
template<typename HandlerType>
|
template<typename HandlerType>
|
||||||
WorkerPool<HandlerType>::WorkerPool(QObject *parent)
|
WorkerPool<HandlerType>::WorkerPool(QObject *parent)
|
||||||
: _WorkerPoolBase(parent),
|
: _WorkerPoolBase(parent),
|
||||||
|
worker_count_(1),
|
||||||
next_worker_(0),
|
next_worker_(0),
|
||||||
next_id_(0) {
|
next_id_(0) {
|
||||||
|
|
||||||
worker_count_ = qBound(1, QThread::idealThreadCount() / 2, 4);
|
|
||||||
local_server_name_ = qApp->applicationName().toLower();
|
local_server_name_ = qApp->applicationName().toLower();
|
||||||
|
|
||||||
if (local_server_name_.isEmpty()) {
|
if (local_server_name_.isEmpty()) {
|
||||||
|
|||||||
@@ -43,12 +43,21 @@ target_include_directories(libstrawberry-tagreader PRIVATE
|
|||||||
|
|
||||||
target_link_libraries(libstrawberry-tagreader PRIVATE
|
target_link_libraries(libstrawberry-tagreader PRIVATE
|
||||||
${GLIB_LIBRARIES}
|
${GLIB_LIBRARIES}
|
||||||
${PROTOBUF_LIBRARY}
|
${Protobuf_LIBRARIES}
|
||||||
${QtCore_LIBRARIES}
|
${QtCore_LIBRARIES}
|
||||||
${QtNetwork_LIBRARIES}
|
${QtNetwork_LIBRARIES}
|
||||||
|
${QtGui_LIBRARIES}
|
||||||
libstrawberry-common
|
libstrawberry-common
|
||||||
)
|
)
|
||||||
|
|
||||||
|
if(WIN32 AND Protobuf_VERSION VERSION_GREATER_EQUAL 4.22.0)
|
||||||
|
if (MSVC)
|
||||||
|
target_link_libraries(libstrawberry-tagreader PRIVATE abseil_dll)
|
||||||
|
else()
|
||||||
|
target_link_libraries(libstrawberry-tagreader PRIVATE absl_log_internal_message absl_log_internal_check_op)
|
||||||
|
endif()
|
||||||
|
endif()
|
||||||
|
|
||||||
if(USE_TAGLIB AND TAGLIB_FOUND)
|
if(USE_TAGLIB AND TAGLIB_FOUND)
|
||||||
target_include_directories(libstrawberry-tagreader SYSTEM PRIVATE ${TAGLIB_INCLUDE_DIRS})
|
target_include_directories(libstrawberry-tagreader SYSTEM PRIVATE ${TAGLIB_INCLUDE_DIRS})
|
||||||
target_link_libraries(libstrawberry-tagreader PRIVATE ${TAGLIB_LIBRARIES})
|
target_link_libraries(libstrawberry-tagreader PRIVATE ${TAGLIB_LIBRARIES})
|
||||||
|
|||||||
@@ -19,6 +19,15 @@
|
|||||||
|
|
||||||
#include <string>
|
#include <string>
|
||||||
|
|
||||||
|
#include <QByteArray>
|
||||||
|
#include <QString>
|
||||||
|
#include <QIODevice>
|
||||||
|
#include <QFile>
|
||||||
|
#include <QBuffer>
|
||||||
|
#include <QImage>
|
||||||
|
#include <QMimeDatabase>
|
||||||
|
|
||||||
|
#include "core/logging.h"
|
||||||
#include "tagreaderbase.h"
|
#include "tagreaderbase.h"
|
||||||
|
|
||||||
const std::string TagReaderBase::kEmbeddedCover = "(embedded)";
|
const std::string TagReaderBase::kEmbeddedCover = "(embedded)";
|
||||||
@@ -26,12 +35,6 @@ const std::string TagReaderBase::kEmbeddedCover = "(embedded)";
|
|||||||
TagReaderBase::TagReaderBase() = default;
|
TagReaderBase::TagReaderBase() = default;
|
||||||
TagReaderBase::~TagReaderBase() = default;
|
TagReaderBase::~TagReaderBase() = default;
|
||||||
|
|
||||||
void TagReaderBase::Decode(const QString &tag, std::string *output) {
|
|
||||||
|
|
||||||
output->assign(DataCommaSizeFromQString(tag));
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
float TagReaderBase::ConvertPOPMRating(const int POPM_rating) {
|
float TagReaderBase::ConvertPOPMRating(const int POPM_rating) {
|
||||||
|
|
||||||
if (POPM_rating < 0x01) return 0.0F;
|
if (POPM_rating < 0x01) return 0.0F;
|
||||||
@@ -55,3 +58,86 @@ int TagReaderBase::ConvertToPOPMRating(const float rating) {
|
|||||||
return 0xFF;
|
return 0xFF;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
QByteArray TagReaderBase::LoadCoverDataFromRequest(const spb::tagreader::SaveFileRequest &request) {
|
||||||
|
|
||||||
|
if (!request.has_save_cover() || !request.save_cover()) {
|
||||||
|
return QByteArray();
|
||||||
|
}
|
||||||
|
|
||||||
|
const QString song_filename = QString::fromUtf8(request.filename().data(), request.filename().size());
|
||||||
|
QString cover_filename;
|
||||||
|
if (request.has_cover_filename()) {
|
||||||
|
cover_filename = QString::fromUtf8(request.cover_filename().data(), request.cover_filename().size());
|
||||||
|
}
|
||||||
|
QByteArray cover_data;
|
||||||
|
if (request.has_cover_data()) {
|
||||||
|
cover_data = QByteArray(request.cover_data().data(), request.cover_data().size());
|
||||||
|
}
|
||||||
|
bool cover_is_jpeg = false;
|
||||||
|
if (request.has_cover_is_jpeg()) {
|
||||||
|
cover_is_jpeg = request.cover_is_jpeg();
|
||||||
|
}
|
||||||
|
|
||||||
|
return LoadCoverDataFromRequest(song_filename, cover_filename, cover_data, cover_is_jpeg);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
QByteArray TagReaderBase::LoadCoverDataFromRequest(const spb::tagreader::SaveEmbeddedArtRequest &request) {
|
||||||
|
|
||||||
|
const QString song_filename = QString::fromUtf8(request.filename().data(), request.filename().size());
|
||||||
|
QString cover_filename;
|
||||||
|
if (request.has_cover_filename()) {
|
||||||
|
cover_filename = QString::fromUtf8(request.cover_filename().data(), request.cover_filename().size());
|
||||||
|
}
|
||||||
|
QByteArray cover_data;
|
||||||
|
if (request.has_cover_data()) {
|
||||||
|
cover_data = QByteArray(request.cover_data().data(), request.cover_data().size());
|
||||||
|
}
|
||||||
|
bool cover_is_jpeg = false;
|
||||||
|
if (request.has_cover_is_jpeg()) {
|
||||||
|
cover_is_jpeg = request.cover_is_jpeg();
|
||||||
|
}
|
||||||
|
|
||||||
|
return LoadCoverDataFromRequest(song_filename, cover_filename, cover_data, cover_is_jpeg);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
QByteArray TagReaderBase::LoadCoverDataFromRequest(const QString &song_filename, const QString &cover_filename, QByteArray cover_data, const bool cover_is_jpeg) {
|
||||||
|
|
||||||
|
if (!cover_data.isEmpty() && cover_is_jpeg) {
|
||||||
|
qLog(Debug) << "Using cover from JPEG data for" << song_filename;
|
||||||
|
return cover_data;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (cover_data.isEmpty() && !cover_filename.isEmpty()) {
|
||||||
|
qLog(Debug) << "Loading cover from" << cover_filename << "for" << song_filename;
|
||||||
|
QFile file(cover_filename);
|
||||||
|
if (!file.open(QIODevice::ReadOnly)) {
|
||||||
|
qLog(Error) << "Failed to open file" << cover_filename << "for reading:" << file.errorString();
|
||||||
|
return QByteArray();
|
||||||
|
}
|
||||||
|
cover_data = file.readAll();
|
||||||
|
file.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!cover_data.isEmpty()) {
|
||||||
|
if (QMimeDatabase().mimeTypeForData(cover_data).name() == "image/jpeg") {
|
||||||
|
qLog(Debug) << "Using cover from JPEG data for" << song_filename;
|
||||||
|
return cover_data;
|
||||||
|
}
|
||||||
|
// Convert image to JPEG.
|
||||||
|
qLog(Debug) << "Converting cover to JPEG data for" << song_filename;
|
||||||
|
QImage cover_image(cover_data);
|
||||||
|
cover_data.clear();
|
||||||
|
QBuffer buffer(&cover_data);
|
||||||
|
if (buffer.open(QIODevice::WriteOnly)) {
|
||||||
|
cover_image.save(&buffer, "JPEG");
|
||||||
|
buffer.close();
|
||||||
|
}
|
||||||
|
return cover_data;
|
||||||
|
}
|
||||||
|
|
||||||
|
return QByteArray();
|
||||||
|
|
||||||
|
}
|
||||||
|
|||||||
@@ -27,9 +27,6 @@
|
|||||||
|
|
||||||
#include "tagreadermessages.pb.h"
|
#include "tagreadermessages.pb.h"
|
||||||
|
|
||||||
#define QStringFromStdString(x) QString::fromUtf8((x).data(), (x).size())
|
|
||||||
#define DataCommaSizeFromQString(x) (x).toUtf8().constData(), (x).toUtf8().length()
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* This class holds all useful methods to read and write tags from/to files.
|
* This class holds all useful methods to read and write tags from/to files.
|
||||||
* You should not use it directly in the main process but rather use a TagReaderWorker process (using TagReaderClient)
|
* You should not use it directly in the main process but rather use a TagReaderWorker process (using TagReaderClient)
|
||||||
@@ -42,19 +39,23 @@ class TagReaderBase {
|
|||||||
virtual bool IsMediaFile(const QString &filename) const = 0;
|
virtual bool IsMediaFile(const QString &filename) const = 0;
|
||||||
|
|
||||||
virtual bool ReadFile(const QString &filename, spb::tagreader::SongMetadata *song) const = 0;
|
virtual bool ReadFile(const QString &filename, spb::tagreader::SongMetadata *song) const = 0;
|
||||||
virtual bool SaveFile(const QString &filename, const spb::tagreader::SongMetadata &song) const = 0;
|
virtual bool SaveFile(const spb::tagreader::SaveFileRequest &request) const = 0;
|
||||||
|
|
||||||
virtual QByteArray LoadEmbeddedArt(const QString &filename) const = 0;
|
virtual QByteArray LoadEmbeddedArt(const QString &filename) const = 0;
|
||||||
virtual bool SaveEmbeddedArt(const QString &filename, const QByteArray &data) = 0;
|
virtual bool SaveEmbeddedArt(const spb::tagreader::SaveEmbeddedArtRequest &request) const = 0;
|
||||||
|
|
||||||
virtual bool SaveSongPlaycountToFile(const QString &filename, const spb::tagreader::SongMetadata &song) const = 0;
|
virtual bool SaveSongPlaycountToFile(const QString &filename, const spb::tagreader::SongMetadata &song) const = 0;
|
||||||
virtual bool SaveSongRatingToFile(const QString &filename, const spb::tagreader::SongMetadata &song) const = 0;
|
virtual bool SaveSongRatingToFile(const QString &filename, const spb::tagreader::SongMetadata &song) const = 0;
|
||||||
|
|
||||||
static void Decode(const QString &tag, std::string *output);
|
|
||||||
|
|
||||||
static float ConvertPOPMRating(const int POPM_rating);
|
static float ConvertPOPMRating(const int POPM_rating);
|
||||||
static int ConvertToPOPMRating(const float rating);
|
static int ConvertToPOPMRating(const float rating);
|
||||||
|
|
||||||
|
static QByteArray LoadCoverDataFromRequest(const spb::tagreader::SaveFileRequest &request);
|
||||||
|
static QByteArray LoadCoverDataFromRequest(const spb::tagreader::SaveEmbeddedArtRequest &request);
|
||||||
|
|
||||||
|
private:
|
||||||
|
static QByteArray LoadCoverDataFromRequest(const QString &song_filename, const QString &cover_filename, QByteArray cover_data, const bool cover_is_jpeg);
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
static const std::string kEmbeddedCover;
|
static const std::string kEmbeddedCover;
|
||||||
|
|
||||||
|
|||||||
@@ -29,8 +29,8 @@
|
|||||||
#include <QFile>
|
#include <QFile>
|
||||||
#include <QTextStream>
|
#include <QTextStream>
|
||||||
|
|
||||||
|
#include "utilities/timeconstants.h"
|
||||||
#include "core/logging.h"
|
#include "core/logging.h"
|
||||||
#include "core/timeconstants.h"
|
|
||||||
#include "core/messagehandler.h"
|
#include "core/messagehandler.h"
|
||||||
#include "tagreaderbase.h"
|
#include "tagreaderbase.h"
|
||||||
#include "tagreadertaglib.h"
|
#include "tagreadertaglib.h"
|
||||||
@@ -84,7 +84,8 @@ void GME::SPC::Read(const QFileInfo &file_info, spb::tagreader::SongMetadata *so
|
|||||||
// Make sure to check id6 documentation before changing the read values!
|
// Make sure to check id6 documentation before changing the read values!
|
||||||
|
|
||||||
file.seek(HAS_ID6_OFFSET);
|
file.seek(HAS_ID6_OFFSET);
|
||||||
bool has_id6 = (file.read(1)[0] == static_cast<char>(xID6_STATUS::ON));
|
const QByteArray id6_status = file.read(1);
|
||||||
|
const bool has_id6 = id6_status.length() >= 1 && id6_status[0] == static_cast<char>(xID6_STATUS::ON);
|
||||||
|
|
||||||
file.seek(SONG_TITLE_OFFSET);
|
file.seek(SONG_TITLE_OFFSET);
|
||||||
song_info->set_title(QString::fromLatin1(file.read(32)).toStdString());
|
song_info->set_title(QString::fromLatin1(file.read(32)).toStdString());
|
||||||
@@ -156,10 +157,10 @@ void GME::SPC::Read(const QFileInfo &file_info, spb::tagreader::SongMetadata *so
|
|||||||
TagLib::Tag *tag = ape.tag();
|
TagLib::Tag *tag = ape.tag();
|
||||||
if (!tag) return;
|
if (!tag) return;
|
||||||
|
|
||||||
TagReaderTagLib::Decode(tag->artist(), song_info->mutable_artist());
|
TagReaderTagLib::TStringToStdString(tag->artist(), song_info->mutable_artist());
|
||||||
TagReaderTagLib::Decode(tag->album(), song_info->mutable_album());
|
TagReaderTagLib::TStringToStdString(tag->album(), song_info->mutable_album());
|
||||||
TagReaderTagLib::Decode(tag->title(), song_info->mutable_title());
|
TagReaderTagLib::TStringToStdString(tag->title(), song_info->mutable_title());
|
||||||
TagReaderTagLib::Decode(tag->genre(), song_info->mutable_genre());
|
TagReaderTagLib::TStringToStdString(tag->genre(), song_info->mutable_genre());
|
||||||
song_info->set_track(tag->track());
|
song_info->set_track(tag->track());
|
||||||
song_info->set_year(tag->year());
|
song_info->set_year(tag->year());
|
||||||
}
|
}
|
||||||
@@ -278,7 +279,7 @@ bool TagReaderGME::ReadFile(const QString &filename, spb::tagreader::SongMetadat
|
|||||||
return GME::ReadFile(fileinfo, song);
|
return GME::ReadFile(fileinfo, song);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool TagReaderGME::SaveFile(const QString&, const spb::tagreader::SongMetadata&) const {
|
bool TagReaderGME::SaveFile(const spb::tagreader::SaveFileRequest&) const {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -286,7 +287,7 @@ QByteArray TagReaderGME::LoadEmbeddedArt(const QString&) const {
|
|||||||
return QByteArray();
|
return QByteArray();
|
||||||
}
|
}
|
||||||
|
|
||||||
bool TagReaderGME::SaveEmbeddedArt(const QString&, const QByteArray&) {
|
bool TagReaderGME::SaveEmbeddedArt(const spb::tagreader::SaveEmbeddedArtRequest&) const {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -55,14 +55,22 @@ constexpr int XID6_OFFSET = (0x101C0 + 64);
|
|||||||
|
|
||||||
constexpr int NANO_PER_MS = 1000000;
|
constexpr int NANO_PER_MS = 1000000;
|
||||||
|
|
||||||
enum xID6_STATUS {
|
enum class xID6_STATUS {
|
||||||
ON = 0x26,
|
ON = 0x26,
|
||||||
OFF = 0x27,
|
OFF = 0x27
|
||||||
};
|
};
|
||||||
|
|
||||||
enum xID6_ID { SongName = 0x01, GameName = 0x02, ArtistName = 0x03 };
|
enum class xID6_ID {
|
||||||
|
SongName = 0x01,
|
||||||
|
GameName = 0x02,
|
||||||
|
ArtistName = 0x03
|
||||||
|
};
|
||||||
|
|
||||||
enum xID6_TYPE { Length = 0x0, String = 0x1, Integer = 0x4 };
|
enum class xID6_TYPE {
|
||||||
|
Length = 0x0,
|
||||||
|
String = 0x1,
|
||||||
|
Integer = 0x4
|
||||||
|
};
|
||||||
|
|
||||||
void Read(const QFileInfo &file_info, spb::tagreader::SongMetadata *song_info);
|
void Read(const QFileInfo &file_info, spb::tagreader::SongMetadata *song_info);
|
||||||
qint16 GetNextMemAddressAlign32bit(qint16 input);
|
qint16 GetNextMemAddressAlign32bit(qint16 input);
|
||||||
@@ -99,13 +107,13 @@ class TagReaderGME : public TagReaderBase {
|
|||||||
bool IsMediaFile(const QString &filename) const override;
|
bool IsMediaFile(const QString &filename) const override;
|
||||||
|
|
||||||
bool ReadFile(const QString &filename, spb::tagreader::SongMetadata *song) const override;
|
bool ReadFile(const QString &filename, spb::tagreader::SongMetadata *song) const override;
|
||||||
bool SaveFile(const QString &filename, const spb::tagreader::SongMetadata &song) const override;
|
bool SaveFile(const spb::tagreader::SaveFileRequest &request) const override;
|
||||||
|
|
||||||
QByteArray LoadEmbeddedArt(const QString &filename) const override;
|
QByteArray LoadEmbeddedArt(const QString &filename) const override;
|
||||||
bool SaveEmbeddedArt(const QString &filename, const QByteArray &data) override;
|
bool SaveEmbeddedArt(const spb::tagreader::SaveEmbeddedArtRequest &request) const override;
|
||||||
|
|
||||||
bool SaveSongPlaycountToFile(const QString &filename, const spb::tagreader::SongMetadata &song) const override;
|
bool SaveSongPlaycountToFile(const QString &filename, const spb::tagreader::SongMetadata &song) const override;
|
||||||
bool SaveSongRatingToFile(const QString &filename, const spb::tagreader::SongMetadata &song) const override;
|
bool SaveSongRatingToFile(const QString &filename, const spb::tagreader::SongMetadata &song) const override;
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif
|
#endif // TAGREADERGME_H
|
||||||
|
|||||||
@@ -73,8 +73,30 @@ message SongMetadata {
|
|||||||
|
|
||||||
optional float rating = 32;
|
optional float rating = 32;
|
||||||
|
|
||||||
optional bool suspicious_tags = 40;
|
optional string acoustid_id = 33;
|
||||||
|
optional string acoustid_fingerprint = 34;
|
||||||
|
|
||||||
|
optional string musicbrainz_album_artist_id = 35; // MusicBrainz Release Artist ID (MUSICBRAINZ_ALBUMARTISTID)
|
||||||
|
optional string musicbrainz_artist_id = 36; // MusicBrainz Artist ID (MUSICBRAINZ_ARTISTID)
|
||||||
|
optional string musicbrainz_original_artist_id = 37; // MusicBrainz Original Artist ID (MUSICBRAINZ_ORIGINALARTISTID)
|
||||||
|
optional string musicbrainz_album_id = 38; // MusicBrainz Release ID (MUSICBRAINZ_ALBUMID)
|
||||||
|
optional string musicbrainz_original_album_id = 39; // MusicBrainz Original Release ID (MUSICBRAINZ_ORIGINALALBUMID)
|
||||||
|
optional string musicbrainz_recording_id = 40; // MusicBrainz Recording ID (MUSICBRAINZ_TRACKID)
|
||||||
|
optional string musicbrainz_track_id = 41; // MusicBrainz Track ID (MUSICBRAINZ_RELEASETRACKID)
|
||||||
|
optional string musicbrainz_disc_id = 42; // MusicBrainz Disc ID (MUSICBRAINZ_DISCID)
|
||||||
|
optional string musicbrainz_release_group_id = 43; // MusicBrainz Release Group ID (MUSICBRAINZ_RELEASEGROUPID)
|
||||||
|
optional string musicbrainz_work_id = 44; // MusicBrainz Work ID (MUSICBRAINZ_WORKID)
|
||||||
|
|
||||||
|
optional bool suspicious_tags = 50;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
message IsMediaFileRequest {
|
||||||
|
optional string filename = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
message IsMediaFileResponse {
|
||||||
|
optional bool success = 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
message ReadFileRequest {
|
message ReadFileRequest {
|
||||||
@@ -87,21 +109,20 @@ message ReadFileResponse {
|
|||||||
|
|
||||||
message SaveFileRequest {
|
message SaveFileRequest {
|
||||||
optional string filename = 1;
|
optional string filename = 1;
|
||||||
optional SongMetadata metadata = 2;
|
optional bool save_tags = 2;
|
||||||
|
optional bool save_playcount = 3;
|
||||||
|
optional bool save_rating = 4;
|
||||||
|
optional bool save_cover = 5;
|
||||||
|
optional SongMetadata metadata = 6;
|
||||||
|
optional string cover_filename = 7;
|
||||||
|
optional bytes cover_data = 8;
|
||||||
|
optional bool cover_is_jpeg = 9;
|
||||||
}
|
}
|
||||||
|
|
||||||
message SaveFileResponse {
|
message SaveFileResponse {
|
||||||
optional bool success = 1;
|
optional bool success = 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
message IsMediaFileRequest {
|
|
||||||
optional string filename = 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
message IsMediaFileResponse {
|
|
||||||
optional bool success = 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
message LoadEmbeddedArtRequest {
|
message LoadEmbeddedArtRequest {
|
||||||
optional string filename = 1;
|
optional string filename = 1;
|
||||||
}
|
}
|
||||||
@@ -112,7 +133,9 @@ message LoadEmbeddedArtResponse {
|
|||||||
|
|
||||||
message SaveEmbeddedArtRequest {
|
message SaveEmbeddedArtRequest {
|
||||||
optional string filename = 1;
|
optional string filename = 1;
|
||||||
optional bytes data = 2;
|
optional string cover_filename = 2;
|
||||||
|
optional bytes cover_data = 3;
|
||||||
|
optional bool cover_is_jpeg = 4;
|
||||||
}
|
}
|
||||||
|
|
||||||
message SaveEmbeddedArtResponse {
|
message SaveEmbeddedArtResponse {
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -29,8 +29,12 @@
|
|||||||
#include <taglib/tstring.h>
|
#include <taglib/tstring.h>
|
||||||
#include <taglib/fileref.h>
|
#include <taglib/fileref.h>
|
||||||
#include <taglib/xiphcomment.h>
|
#include <taglib/xiphcomment.h>
|
||||||
|
#include <taglib/flacfile.h>
|
||||||
|
#include <taglib/mpegfile.h>
|
||||||
|
#include <taglib/mp4file.h>
|
||||||
#include <taglib/apetag.h>
|
#include <taglib/apetag.h>
|
||||||
#include <taglib/apefile.h>
|
#include <taglib/apefile.h>
|
||||||
|
#include <taglib/asffile.h>
|
||||||
#include <taglib/id3v2tag.h>
|
#include <taglib/id3v2tag.h>
|
||||||
#include <taglib/popularimeterframe.h>
|
#include <taglib/popularimeterframe.h>
|
||||||
|
|
||||||
@@ -51,15 +55,15 @@ class TagReaderTagLib : public TagReaderBase {
|
|||||||
bool IsMediaFile(const QString &filename) const override;
|
bool IsMediaFile(const QString &filename) const override;
|
||||||
|
|
||||||
bool ReadFile(const QString &filename, spb::tagreader::SongMetadata *song) const override;
|
bool ReadFile(const QString &filename, spb::tagreader::SongMetadata *song) const override;
|
||||||
bool SaveFile(const QString &filename, const spb::tagreader::SongMetadata &song) const override;
|
bool SaveFile(const spb::tagreader::SaveFileRequest &request) const override;
|
||||||
|
|
||||||
QByteArray LoadEmbeddedArt(const QString &filename) const override;
|
QByteArray LoadEmbeddedArt(const QString &filename) const override;
|
||||||
bool SaveEmbeddedArt(const QString &filename, const QByteArray &data) override;
|
bool SaveEmbeddedArt(const spb::tagreader::SaveEmbeddedArtRequest &request) const override;
|
||||||
|
|
||||||
bool SaveSongPlaycountToFile(const QString &filename, const spb::tagreader::SongMetadata &song) const override;
|
bool SaveSongPlaycountToFile(const QString &filename, const spb::tagreader::SongMetadata &song) const override;
|
||||||
bool SaveSongRatingToFile(const QString &filename, const spb::tagreader::SongMetadata &song) const override;
|
bool SaveSongRatingToFile(const QString &filename, const spb::tagreader::SongMetadata &song) const override;
|
||||||
|
|
||||||
static void Decode(const TagLib::String &tag, std::string *output);
|
static void TStringToStdString(const TagLib::String &tag, std::string *output);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
spb::tagreader::SongMetadata_FileType GuessFileType(TagLib::FileRef *fileref) const;
|
spb::tagreader::SongMetadata_FileType GuessFileType(TagLib::FileRef *fileref) const;
|
||||||
@@ -67,7 +71,7 @@ class TagReaderTagLib : public TagReaderBase {
|
|||||||
void ParseOggTag(const TagLib::Ogg::FieldListMap &map, QString *disc, QString *compilation, spb::tagreader::SongMetadata *song) const;
|
void ParseOggTag(const TagLib::Ogg::FieldListMap &map, QString *disc, QString *compilation, spb::tagreader::SongMetadata *song) const;
|
||||||
void ParseAPETag(const TagLib::APE::ItemListMap &map, QString *disc, QString *compilation, spb::tagreader::SongMetadata *song) const;
|
void ParseAPETag(const TagLib::APE::ItemListMap &map, QString *disc, QString *compilation, spb::tagreader::SongMetadata *song) const;
|
||||||
|
|
||||||
void SetVorbisComments(TagLib::Ogg::XiphComment *vorbis_comments, const spb::tagreader::SongMetadata &song) const;
|
void SetVorbisComments(TagLib::Ogg::XiphComment *vorbis_comment, const spb::tagreader::SongMetadata &song) const;
|
||||||
void SaveAPETag(TagLib::APE::Tag *tag, const spb::tagreader::SongMetadata &song) const;
|
void SaveAPETag(TagLib::APE::Tag *tag, const spb::tagreader::SongMetadata &song) const;
|
||||||
|
|
||||||
void SetTextFrame(const char *id, const QString &value, TagLib::ID3v2::Tag *tag) const;
|
void SetTextFrame(const char *id, const QString &value, TagLib::ID3v2::Tag *tag) const;
|
||||||
@@ -80,6 +84,23 @@ class TagReaderTagLib : public TagReaderBase {
|
|||||||
|
|
||||||
static TagLib::ID3v2::PopularimeterFrame *GetPOPMFrameFromTag(TagLib::ID3v2::Tag *tag);
|
static TagLib::ID3v2::PopularimeterFrame *GetPOPMFrameFromTag(TagLib::ID3v2::Tag *tag);
|
||||||
|
|
||||||
|
void SetPlaycount(TagLib::Ogg::XiphComment *xiph_comment, const spb::tagreader::SongMetadata &song) const;
|
||||||
|
void SetPlaycount(TagLib::APE::Tag *tag, const spb::tagreader::SongMetadata &song) const;
|
||||||
|
void SetPlaycount(TagLib::ID3v2::Tag *tag, const spb::tagreader::SongMetadata &song) const;
|
||||||
|
void SetPlaycount(TagLib::MP4::Tag *tag, const spb::tagreader::SongMetadata &song) const;
|
||||||
|
void SetPlaycount(TagLib::ASF::Tag *tag, const spb::tagreader::SongMetadata &song) const;
|
||||||
|
|
||||||
|
void SetRating(TagLib::Ogg::XiphComment *xiph_comment, const spb::tagreader::SongMetadata &song) const;
|
||||||
|
void SetRating(TagLib::APE::Tag *tag, const spb::tagreader::SongMetadata &song) const;
|
||||||
|
void SetRating(TagLib::ID3v2::Tag *tag, const spb::tagreader::SongMetadata &song) const;
|
||||||
|
void SetRating(TagLib::MP4::Tag *tag, const spb::tagreader::SongMetadata &song) const;
|
||||||
|
void SetRating(TagLib::ASF::Tag *tag, const spb::tagreader::SongMetadata &song) const;
|
||||||
|
|
||||||
|
void SetEmbeddedArt(TagLib::FLAC::File *flac_file, TagLib::Ogg::XiphComment *xiph_comment, const QByteArray &data) const;
|
||||||
|
void SetEmbeddedArt(TagLib::Ogg::XiphComment *xiph_comment, const QByteArray &data) const;
|
||||||
|
void SetEmbeddedArt(TagLib::MPEG::File *file_mp3, TagLib::ID3v2::Tag *tag, const QByteArray &data) const;
|
||||||
|
void SetEmbeddedArt(TagLib::MP4::File *aac_file, TagLib::MP4::Tag *tag, const QByteArray &data) const;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
FileRefFactory *factory_;
|
FileRefFactory *factory_;
|
||||||
|
|
||||||
|
|||||||
@@ -22,6 +22,7 @@
|
|||||||
#include <string>
|
#include <string>
|
||||||
#include <memory>
|
#include <memory>
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
|
#include <vector>
|
||||||
#include <sys/stat.h>
|
#include <sys/stat.h>
|
||||||
|
|
||||||
#include <tagparser/mediafileinfo.h>
|
#include <tagparser/mediafileinfo.h>
|
||||||
@@ -41,7 +42,7 @@
|
|||||||
|
|
||||||
#include "core/logging.h"
|
#include "core/logging.h"
|
||||||
#include "core/messagehandler.h"
|
#include "core/messagehandler.h"
|
||||||
#include "core/timeconstants.h"
|
#include "utilities/timeconstants.h"
|
||||||
|
|
||||||
TagReaderTagParser::TagReaderTagParser() = default;
|
TagReaderTagParser::TagReaderTagParser() = default;
|
||||||
|
|
||||||
@@ -79,7 +80,7 @@ bool TagReaderTagParser::IsMediaFile(const QString &filename) const {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const auto tracks = taginfo.tracks();
|
const auto tracks = taginfo.tracks();
|
||||||
for (const auto track : tracks) {
|
for (TagParser::AbstractTrack *track : tracks) {
|
||||||
if (track->mediaType() == TagParser::MediaType::Audio) {
|
if (track->mediaType() == TagParser::MediaType::Audio) {
|
||||||
taginfo.close();
|
taginfo.close();
|
||||||
return true;
|
return true;
|
||||||
@@ -102,8 +103,9 @@ bool TagReaderTagParser::ReadFile(const QString &filename, spb::tagreader::SongM
|
|||||||
if (!fileinfo.exists() || fileinfo.suffix().compare("bak", Qt::CaseInsensitive) == 0) return false;
|
if (!fileinfo.exists() || fileinfo.suffix().compare("bak", Qt::CaseInsensitive) == 0) return false;
|
||||||
|
|
||||||
const QByteArray url(QUrl::fromLocalFile(filename).toEncoded());
|
const QByteArray url(QUrl::fromLocalFile(filename).toEncoded());
|
||||||
|
const QByteArray basefilename = fileinfo.fileName().toUtf8();
|
||||||
|
|
||||||
song->set_basefilename(DataCommaSizeFromQString(fileinfo.fileName()));
|
song->set_basefilename(basefilename.constData(), basefilename.size());
|
||||||
song->set_url(url.constData(), url.size());
|
song->set_url(url.constData(), url.size());
|
||||||
song->set_filesize(fileinfo.size());
|
song->set_filesize(fileinfo.size());
|
||||||
song->set_mtime(fileinfo.lastModified().isValid() ? std::max(fileinfo.lastModified().toSecsSinceEpoch(), 0LL) : 0LL);
|
song->set_mtime(fileinfo.lastModified().isValid() ? std::max(fileinfo.lastModified().toSecsSinceEpoch(), 0LL) : 0LL);
|
||||||
@@ -154,8 +156,8 @@ bool TagReaderTagParser::ReadFile(const QString &filename, spb::tagreader::SongM
|
|||||||
qLog(Debug) << QString::fromStdString(msg.message());
|
qLog(Debug) << QString::fromStdString(msg.message());
|
||||||
}
|
}
|
||||||
|
|
||||||
const auto tracks = taginfo.tracks();
|
std::vector<TagParser::AbstractTrack*> tracks = taginfo.tracks();
|
||||||
for (const auto track : tracks) {
|
for (TagParser::AbstractTrack *track : tracks) {
|
||||||
switch (track->format().general) {
|
switch (track->format().general) {
|
||||||
case TagParser::GeneralMediaFormat::Flac:
|
case TagParser::GeneralMediaFormat::Flac:
|
||||||
song->set_filetype(spb::tagreader::SongMetadata_FileType::SongMetadata_FileType_FLAC);
|
song->set_filetype(spb::tagreader::SongMetadata_FileType::SongMetadata_FileType_FLAC);
|
||||||
@@ -209,7 +211,7 @@ bool TagReaderTagParser::ReadFile(const QString &filename, spb::tagreader::SongM
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
for (const auto tag : taginfo.tags()) {
|
for (TagParser::Tag *tag : taginfo.tags()) {
|
||||||
song->set_albumartist(tag->value(TagParser::KnownField::AlbumArtist).toString(TagParser::TagTextEncoding::Utf8));
|
song->set_albumartist(tag->value(TagParser::KnownField::AlbumArtist).toString(TagParser::TagTextEncoding::Utf8));
|
||||||
song->set_artist(tag->value(TagParser::KnownField::Artist).toString(TagParser::TagTextEncoding::Utf8));
|
song->set_artist(tag->value(TagParser::KnownField::Artist).toString(TagParser::TagTextEncoding::Utf8));
|
||||||
song->set_album(tag->value(TagParser::KnownField::Album).toString(TagParser::TagTextEncoding::Utf8));
|
song->set_album(tag->value(TagParser::KnownField::Album).toString(TagParser::TagTextEncoding::Utf8));
|
||||||
@@ -256,11 +258,34 @@ bool TagReaderTagParser::ReadFile(const QString &filename, spb::tagreader::SongM
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bool TagReaderTagParser::SaveFile(const QString &filename, const spb::tagreader::SongMetadata &song) const {
|
bool TagReaderTagParser::SaveFile(const spb::tagreader::SaveFileRequest &request) const {
|
||||||
|
|
||||||
if (filename.isEmpty()) return false;
|
if (request.filename().empty()) return false;
|
||||||
|
|
||||||
qLog(Debug) << "Saving tags to" << filename;
|
const QString filename = QString::fromUtf8(request.filename().data(), request.filename().size());
|
||||||
|
const spb::tagreader::SongMetadata song = request.metadata();
|
||||||
|
const bool save_tags = request.has_save_tags() && request.save_tags();
|
||||||
|
const bool save_playcount = request.has_save_playcount() && request.save_playcount();
|
||||||
|
const bool save_rating = request.has_save_rating() && request.save_rating();
|
||||||
|
const bool save_cover = request.has_save_cover() && request.save_cover();
|
||||||
|
|
||||||
|
QStringList save_tags_options;
|
||||||
|
if (save_tags) {
|
||||||
|
save_tags_options << "tags";
|
||||||
|
}
|
||||||
|
if (save_playcount) {
|
||||||
|
save_tags_options << "playcount";
|
||||||
|
}
|
||||||
|
if (save_rating) {
|
||||||
|
save_tags_options << "rating";
|
||||||
|
}
|
||||||
|
if (save_cover) {
|
||||||
|
save_tags_options << "embedded cover";
|
||||||
|
}
|
||||||
|
|
||||||
|
qLog(Debug) << "Saving" << save_tags_options.join(", ") << "to" << filename;
|
||||||
|
|
||||||
|
const QByteArray cover_data = LoadCoverDataFromRequest(request);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
TagParser::MediaFileInfo taginfo;
|
TagParser::MediaFileInfo taginfo;
|
||||||
@@ -295,22 +320,34 @@ bool TagReaderTagParser::SaveFile(const QString &filename, const spb::tagreader:
|
|||||||
taginfo.createAppropriateTags();
|
taginfo.createAppropriateTags();
|
||||||
}
|
}
|
||||||
|
|
||||||
for (const auto tag : taginfo.tags()) {
|
for (TagParser::Tag *tag : taginfo.tags()) {
|
||||||
tag->setValue(TagParser::KnownField::AlbumArtist, TagParser::TagValue(song.albumartist(), TagParser::TagTextEncoding::Utf8, tag->proposedTextEncoding()));
|
if (save_tags) {
|
||||||
tag->setValue(TagParser::KnownField::Artist, TagParser::TagValue(song.artist(), TagParser::TagTextEncoding::Utf8, tag->proposedTextEncoding()));
|
tag->setValue(TagParser::KnownField::AlbumArtist, TagParser::TagValue(song.albumartist(), TagParser::TagTextEncoding::Utf8, tag->proposedTextEncoding()));
|
||||||
tag->setValue(TagParser::KnownField::Album, TagParser::TagValue(song.album(), TagParser::TagTextEncoding::Utf8, tag->proposedTextEncoding()));
|
tag->setValue(TagParser::KnownField::Artist, TagParser::TagValue(song.artist(), TagParser::TagTextEncoding::Utf8, tag->proposedTextEncoding()));
|
||||||
tag->setValue(TagParser::KnownField::Title, TagParser::TagValue(song.title(), TagParser::TagTextEncoding::Utf8, tag->proposedTextEncoding()));
|
tag->setValue(TagParser::KnownField::Album, TagParser::TagValue(song.album(), TagParser::TagTextEncoding::Utf8, tag->proposedTextEncoding()));
|
||||||
tag->setValue(TagParser::KnownField::Genre, TagParser::TagValue(song.genre(), TagParser::TagTextEncoding::Utf8, tag->proposedTextEncoding()));
|
tag->setValue(TagParser::KnownField::Title, TagParser::TagValue(song.title(), TagParser::TagTextEncoding::Utf8, tag->proposedTextEncoding()));
|
||||||
tag->setValue(TagParser::KnownField::Composer, TagParser::TagValue(song.composer(), TagParser::TagTextEncoding::Utf8, tag->proposedTextEncoding()));
|
tag->setValue(TagParser::KnownField::Genre, TagParser::TagValue(song.genre(), TagParser::TagTextEncoding::Utf8, tag->proposedTextEncoding()));
|
||||||
tag->setValue(TagParser::KnownField::Performers, TagParser::TagValue(song.performer(), TagParser::TagTextEncoding::Utf8, tag->proposedTextEncoding()));
|
tag->setValue(TagParser::KnownField::Composer, TagParser::TagValue(song.composer(), TagParser::TagTextEncoding::Utf8, tag->proposedTextEncoding()));
|
||||||
tag->setValue(TagParser::KnownField::Grouping, TagParser::TagValue(song.grouping(), TagParser::TagTextEncoding::Utf8, tag->proposedTextEncoding()));
|
tag->setValue(TagParser::KnownField::Performers, TagParser::TagValue(song.performer(), TagParser::TagTextEncoding::Utf8, tag->proposedTextEncoding()));
|
||||||
tag->setValue(TagParser::KnownField::Comment, TagParser::TagValue(song.comment(), TagParser::TagTextEncoding::Utf8, tag->proposedTextEncoding()));
|
tag->setValue(TagParser::KnownField::Grouping, TagParser::TagValue(song.grouping(), TagParser::TagTextEncoding::Utf8, tag->proposedTextEncoding()));
|
||||||
tag->setValue(TagParser::KnownField::Lyrics, TagParser::TagValue(song.lyrics(), TagParser::TagTextEncoding::Utf8, tag->proposedTextEncoding()));
|
tag->setValue(TagParser::KnownField::Comment, TagParser::TagValue(song.comment(), TagParser::TagTextEncoding::Utf8, tag->proposedTextEncoding()));
|
||||||
tag->setValue(TagParser::KnownField::TrackPosition, TagParser::TagValue(song.track()));
|
tag->setValue(TagParser::KnownField::Lyrics, TagParser::TagValue(song.lyrics(), TagParser::TagTextEncoding::Utf8, tag->proposedTextEncoding()));
|
||||||
tag->setValue(TagParser::KnownField::DiskPosition, TagParser::TagValue(song.disc()));
|
tag->setValue(TagParser::KnownField::TrackPosition, TagParser::TagValue(song.track()));
|
||||||
tag->setValue(TagParser::KnownField::RecordDate, TagParser::TagValue(song.year()));
|
tag->setValue(TagParser::KnownField::DiskPosition, TagParser::TagValue(song.disc()));
|
||||||
tag->setValue(TagParser::KnownField::ReleaseDate, TagParser::TagValue(song.originalyear()));
|
tag->setValue(TagParser::KnownField::RecordDate, TagParser::TagValue(song.year()));
|
||||||
|
tag->setValue(TagParser::KnownField::ReleaseDate, TagParser::TagValue(song.originalyear()));
|
||||||
|
}
|
||||||
|
if (save_playcount) {
|
||||||
|
SaveSongPlaycountToFile(tag, song);
|
||||||
|
}
|
||||||
|
if (save_rating) {
|
||||||
|
SaveSongRatingToFile(tag, song);
|
||||||
|
}
|
||||||
|
if (save_cover) {
|
||||||
|
SaveEmbeddedArt(tag, cover_data);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
taginfo.applyChanges(diag, progress);
|
taginfo.applyChanges(diag, progress);
|
||||||
taginfo.close();
|
taginfo.close();
|
||||||
|
|
||||||
@@ -358,7 +395,7 @@ QByteArray TagReaderTagParser::LoadEmbeddedArt(const QString &filename) const {
|
|||||||
return QByteArray();
|
return QByteArray();
|
||||||
}
|
}
|
||||||
|
|
||||||
for (const auto tag : taginfo.tags()) {
|
for (TagParser::Tag *tag : taginfo.tags()) {
|
||||||
if (!tag->value(TagParser::KnownField::Cover).empty() && tag->value(TagParser::KnownField::Cover).dataSize() > 0) {
|
if (!tag->value(TagParser::KnownField::Cover).empty() && tag->value(TagParser::KnownField::Cover).dataSize() > 0) {
|
||||||
QByteArray data(tag->value(TagParser::KnownField::Cover).dataPointer(), tag->value(TagParser::KnownField::Cover).dataSize());
|
QByteArray data(tag->value(TagParser::KnownField::Cover).dataPointer(), tag->value(TagParser::KnownField::Cover).dataSize());
|
||||||
taginfo.close();
|
taginfo.close();
|
||||||
@@ -379,12 +416,22 @@ QByteArray TagReaderTagParser::LoadEmbeddedArt(const QString &filename) const {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bool TagReaderTagParser::SaveEmbeddedArt(const QString &filename, const QByteArray &data) {
|
void TagReaderTagParser::SaveEmbeddedArt(TagParser::Tag *tag, const QByteArray &data) const {
|
||||||
|
|
||||||
if (filename.isEmpty()) return false;
|
tag->setValue(TagParser::KnownField::Cover, TagParser::TagValue(data.toStdString()));
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
bool TagReaderTagParser::SaveEmbeddedArt(const spb::tagreader::SaveEmbeddedArtRequest &request) const {
|
||||||
|
|
||||||
|
if (request.filename().empty()) return false;
|
||||||
|
|
||||||
|
const QString filename = QString::fromUtf8(request.filename().data(), request.filename().size());
|
||||||
|
|
||||||
qLog(Debug) << "Saving art to" << filename;
|
qLog(Debug) << "Saving art to" << filename;
|
||||||
|
|
||||||
|
const QByteArray cover_data = LoadCoverDataFromRequest(request);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
|
||||||
TagParser::MediaFileInfo taginfo;
|
TagParser::MediaFileInfo taginfo;
|
||||||
@@ -415,8 +462,8 @@ bool TagReaderTagParser::SaveEmbeddedArt(const QString &filename, const QByteArr
|
|||||||
taginfo.createAppropriateTags();
|
taginfo.createAppropriateTags();
|
||||||
}
|
}
|
||||||
|
|
||||||
for (const auto tag : taginfo.tags()) {
|
for (TagParser::Tag *tag : taginfo.tags()) {
|
||||||
tag->setValue(TagParser::KnownField::Cover, TagParser::TagValue(data.toStdString()));
|
SaveEmbeddedArt(tag, cover_data);
|
||||||
}
|
}
|
||||||
|
|
||||||
taginfo.applyChanges(diag, progress);
|
taginfo.applyChanges(diag, progress);
|
||||||
@@ -435,8 +482,16 @@ bool TagReaderTagParser::SaveEmbeddedArt(const QString &filename, const QByteArr
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void TagReaderTagParser::SaveSongPlaycountToFile(TagParser::Tag*, const spb::tagreader::SongMetadata&) const {}
|
||||||
|
|
||||||
bool TagReaderTagParser::SaveSongPlaycountToFile(const QString&, const spb::tagreader::SongMetadata&) const { return false; }
|
bool TagReaderTagParser::SaveSongPlaycountToFile(const QString&, const spb::tagreader::SongMetadata&) const { return false; }
|
||||||
|
|
||||||
|
void TagReaderTagParser::SaveSongRatingToFile(TagParser::Tag *tag, const spb::tagreader::SongMetadata &song) const {
|
||||||
|
|
||||||
|
tag->setValue(TagParser::KnownField::Rating, TagParser::TagValue(ConvertToPOPMRating(song.rating())));
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
bool TagReaderTagParser::SaveSongRatingToFile(const QString &filename, const spb::tagreader::SongMetadata &song) const {
|
bool TagReaderTagParser::SaveSongRatingToFile(const QString &filename, const spb::tagreader::SongMetadata &song) const {
|
||||||
|
|
||||||
if (filename.isEmpty()) return false;
|
if (filename.isEmpty()) return false;
|
||||||
@@ -476,9 +531,10 @@ bool TagReaderTagParser::SaveSongRatingToFile(const QString &filename, const spb
|
|||||||
taginfo.createAppropriateTags();
|
taginfo.createAppropriateTags();
|
||||||
}
|
}
|
||||||
|
|
||||||
for (const auto tag : taginfo.tags()) {
|
for (TagParser::Tag *tag : taginfo.tags()) {
|
||||||
tag->setValue(TagParser::KnownField::Rating, TagParser::TagValue(ConvertToPOPMRating(song.rating())));
|
SaveSongRatingToFile(tag, song);
|
||||||
}
|
}
|
||||||
|
|
||||||
taginfo.applyChanges(diag, progress);
|
taginfo.applyChanges(diag, progress);
|
||||||
taginfo.close();
|
taginfo.close();
|
||||||
|
|
||||||
|
|||||||
@@ -22,6 +22,8 @@
|
|||||||
|
|
||||||
#include <string>
|
#include <string>
|
||||||
|
|
||||||
|
#include <tagparser/tag.h>
|
||||||
|
|
||||||
#include <QByteArray>
|
#include <QByteArray>
|
||||||
#include <QString>
|
#include <QString>
|
||||||
|
|
||||||
@@ -40,14 +42,20 @@ class TagReaderTagParser : public TagReaderBase {
|
|||||||
bool IsMediaFile(const QString &filename) const override;
|
bool IsMediaFile(const QString &filename) const override;
|
||||||
|
|
||||||
bool ReadFile(const QString &filename, spb::tagreader::SongMetadata *song) const override;
|
bool ReadFile(const QString &filename, spb::tagreader::SongMetadata *song) const override;
|
||||||
bool SaveFile(const QString &filename, const spb::tagreader::SongMetadata &song) const override;
|
bool SaveFile(const spb::tagreader::SaveFileRequest &request) const override;
|
||||||
|
|
||||||
QByteArray LoadEmbeddedArt(const QString &filename) const override;
|
QByteArray LoadEmbeddedArt(const QString &filename) const override;
|
||||||
bool SaveEmbeddedArt(const QString &filename, const QByteArray &data) override;
|
bool SaveEmbeddedArt(const spb::tagreader::SaveEmbeddedArtRequest &request) const override;
|
||||||
|
|
||||||
bool SaveSongPlaycountToFile(const QString &filename, const spb::tagreader::SongMetadata &song) const override;
|
bool SaveSongPlaycountToFile(const QString &filename, const spb::tagreader::SongMetadata &song) const override;
|
||||||
bool SaveSongRatingToFile(const QString &filename, const spb::tagreader::SongMetadata &song) const override;
|
bool SaveSongRatingToFile(const QString &filename, const spb::tagreader::SongMetadata &song) const override;
|
||||||
|
|
||||||
|
private:
|
||||||
|
void SaveSongPlaycountToFile(TagParser::Tag *tag, const spb::tagreader::SongMetadata &song) const;
|
||||||
|
void SaveSongRatingToFile(TagParser::Tag *tag, const spb::tagreader::SongMetadata &song) const;
|
||||||
|
void SaveEmbeddedArt(TagParser::Tag *tag, const QByteArray &data) const;
|
||||||
|
|
||||||
|
public:
|
||||||
Q_DISABLE_COPY(TagReaderTagParser)
|
Q_DISABLE_COPY(TagReaderTagParser)
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -56,36 +56,41 @@ void TagReaderWorker::DeviceClosed() {
|
|||||||
bool TagReaderWorker::HandleMessage(const spb::tagreader::Message &message, spb::tagreader::Message &reply, TagReaderBase *reader) {
|
bool TagReaderWorker::HandleMessage(const spb::tagreader::Message &message, spb::tagreader::Message &reply, TagReaderBase *reader) {
|
||||||
|
|
||||||
if (message.has_is_media_file_request()) {
|
if (message.has_is_media_file_request()) {
|
||||||
bool success = reader->IsMediaFile(QStringFromStdString(message.is_media_file_request().filename()));
|
const QString filename = QString::fromUtf8(message.is_media_file_request().filename().data(), message.is_media_file_request().filename().size());
|
||||||
|
bool success = reader->IsMediaFile(filename);
|
||||||
reply.mutable_is_media_file_response()->set_success(success);
|
reply.mutable_is_media_file_response()->set_success(success);
|
||||||
return success;
|
return success;
|
||||||
}
|
}
|
||||||
else if (message.has_read_file_request()) {
|
else if (message.has_read_file_request()) {
|
||||||
bool success = reader->ReadFile(QStringFromStdString(message.read_file_request().filename()), reply.mutable_read_file_response()->mutable_metadata());
|
const QString filename = QString::fromUtf8(message.read_file_request().filename().data(), message.read_file_request().filename().size());
|
||||||
|
bool success = reader->ReadFile(filename, reply.mutable_read_file_response()->mutable_metadata());
|
||||||
return success;
|
return success;
|
||||||
}
|
}
|
||||||
else if (message.has_save_file_request()) {
|
else if (message.has_save_file_request()) {
|
||||||
bool success = reader->SaveFile(QStringFromStdString(message.save_file_request().filename()), message.save_file_request().metadata());
|
bool success = reader->SaveFile(message.save_file_request());
|
||||||
reply.mutable_save_file_response()->set_success(success);
|
reply.mutable_save_file_response()->set_success(success);
|
||||||
return success;
|
return success;
|
||||||
}
|
}
|
||||||
else if (message.has_load_embedded_art_request()) {
|
else if (message.has_load_embedded_art_request()) {
|
||||||
QByteArray data = reader->LoadEmbeddedArt(QStringFromStdString(message.load_embedded_art_request().filename()));
|
const QString filename = QString::fromUtf8(message.load_embedded_art_request().filename().data(), message.load_embedded_art_request().filename().size());
|
||||||
|
QByteArray data = reader->LoadEmbeddedArt(filename);
|
||||||
reply.mutable_load_embedded_art_response()->set_data(data.constData(), data.size());
|
reply.mutable_load_embedded_art_response()->set_data(data.constData(), data.size());
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
else if (message.has_save_embedded_art_request()) {
|
else if (message.has_save_embedded_art_request()) {
|
||||||
bool success = reader->SaveEmbeddedArt(QStringFromStdString(message.save_embedded_art_request().filename()), QByteArray(message.save_embedded_art_request().data().data(), static_cast<qint64>(message.save_embedded_art_request().data().size())));
|
bool success = reader->SaveEmbeddedArt(message.save_embedded_art_request());
|
||||||
reply.mutable_save_embedded_art_response()->set_success(success);
|
reply.mutable_save_embedded_art_response()->set_success(success);
|
||||||
return success;
|
return success;
|
||||||
}
|
}
|
||||||
else if (message.has_save_song_playcount_to_file_request()) {
|
else if (message.has_save_song_playcount_to_file_request()) {
|
||||||
bool success = reader->SaveSongPlaycountToFile(QStringFromStdString(message.save_song_playcount_to_file_request().filename()), message.save_song_playcount_to_file_request().metadata());
|
const QString filename = QString::fromUtf8(message.save_song_playcount_to_file_request().filename().data(), message.save_song_playcount_to_file_request().filename().size());
|
||||||
|
bool success = reader->SaveSongPlaycountToFile(filename, message.save_song_playcount_to_file_request().metadata());
|
||||||
reply.mutable_save_song_playcount_to_file_response()->set_success(success);
|
reply.mutable_save_song_playcount_to_file_response()->set_success(success);
|
||||||
return success;
|
return success;
|
||||||
}
|
}
|
||||||
else if (message.has_save_song_rating_to_file_request()) {
|
else if (message.has_save_song_rating_to_file_request()) {
|
||||||
bool success = reader->SaveSongRatingToFile(QStringFromStdString(message.save_song_rating_to_file_request().filename()), message.save_song_rating_to_file_request().metadata());
|
const QString filename = QString::fromUtf8(message.save_song_rating_to_file_request().filename().data(), message.save_song_rating_to_file_request().filename().size());
|
||||||
|
bool success = reader->SaveSongRatingToFile(filename, message.save_song_rating_to_file_request().metadata());
|
||||||
reply.mutable_save_song_rating_to_file_response()->set_success(success);
|
reply.mutable_save_song_rating_to_file_response()->set_success(success);
|
||||||
return success;
|
return success;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,7 +7,6 @@ endif()
|
|||||||
set(SOURCES
|
set(SOURCES
|
||||||
core/mainwindow.cpp
|
core/mainwindow.cpp
|
||||||
core/application.cpp
|
core/application.cpp
|
||||||
core/appearance.cpp
|
|
||||||
core/player.cpp
|
core/player.cpp
|
||||||
core/commandlineoptions.cpp
|
core/commandlineoptions.cpp
|
||||||
core/database.cpp
|
core/database.cpp
|
||||||
@@ -35,14 +34,29 @@ set(SOURCES
|
|||||||
core/taskmanager.cpp
|
core/taskmanager.cpp
|
||||||
core/thread.cpp
|
core/thread.cpp
|
||||||
core/urlhandler.cpp
|
core/urlhandler.cpp
|
||||||
core/utilities.cpp
|
|
||||||
core/imageutils.cpp
|
|
||||||
core/iconloader.cpp
|
core/iconloader.cpp
|
||||||
core/standarditemiconloader.cpp
|
core/standarditemiconloader.cpp
|
||||||
core/scopedtransaction.cpp
|
core/scopedtransaction.cpp
|
||||||
core/translations.cpp
|
core/translations.cpp
|
||||||
core/systemtrayicon.cpp
|
core/systemtrayicon.cpp
|
||||||
|
|
||||||
|
utilities/strutils.cpp
|
||||||
|
utilities/envutils.cpp
|
||||||
|
utilities/colorutils.cpp
|
||||||
|
utilities/cryptutils.cpp
|
||||||
|
utilities/fileutils.cpp
|
||||||
|
utilities/diskutils.cpp
|
||||||
|
utilities/imageutils.cpp
|
||||||
|
utilities/macaddrutils.cpp
|
||||||
|
utilities/mimeutils.cpp
|
||||||
|
utilities/randutils.cpp
|
||||||
|
utilities/threadutils.cpp
|
||||||
|
utilities/timeutils.cpp
|
||||||
|
utilities/transliterate.cpp
|
||||||
|
utilities/xmlutils.cpp
|
||||||
|
utilities/filemanagerutils.cpp
|
||||||
|
utilities/coverutils.cpp
|
||||||
|
|
||||||
engine/enginetype.cpp
|
engine/enginetype.cpp
|
||||||
engine/enginebase.cpp
|
engine/enginebase.cpp
|
||||||
engine/devicefinders.cpp
|
engine/devicefinders.cpp
|
||||||
@@ -54,6 +68,7 @@ set(SOURCES
|
|||||||
analyzer/blockanalyzer.cpp
|
analyzer/blockanalyzer.cpp
|
||||||
analyzer/boomanalyzer.cpp
|
analyzer/boomanalyzer.cpp
|
||||||
analyzer/rainbowanalyzer.cpp
|
analyzer/rainbowanalyzer.cpp
|
||||||
|
analyzer/sonogram.cpp
|
||||||
|
|
||||||
equalizer/equalizer.cpp
|
equalizer/equalizer.cpp
|
||||||
equalizer/equalizerslider.cpp
|
equalizer/equalizerslider.cpp
|
||||||
@@ -69,9 +84,11 @@ set(SOURCES
|
|||||||
collection/collectionitemdelegate.cpp
|
collection/collectionitemdelegate.cpp
|
||||||
collection/collectionviewcontainer.cpp
|
collection/collectionviewcontainer.cpp
|
||||||
collection/collectiondirectorymodel.cpp
|
collection/collectiondirectorymodel.cpp
|
||||||
|
collection/collectionfilteroptions.cpp
|
||||||
collection/collectionfilterwidget.cpp
|
collection/collectionfilterwidget.cpp
|
||||||
collection/collectionplaylistitem.cpp
|
collection/collectionplaylistitem.cpp
|
||||||
collection/collectionquery.cpp
|
collection/collectionquery.cpp
|
||||||
|
collection/collectionqueryoptions.cpp
|
||||||
collection/savedgroupingmanager.cpp
|
collection/savedgroupingmanager.cpp
|
||||||
collection/groupbydialog.cpp
|
collection/groupbydialog.cpp
|
||||||
collection/collectiontask.cpp
|
collection/collectiontask.cpp
|
||||||
@@ -152,6 +169,8 @@ set(SOURCES
|
|||||||
|
|
||||||
lyrics/lyricsproviders.cpp
|
lyrics/lyricsproviders.cpp
|
||||||
lyrics/lyricsprovider.cpp
|
lyrics/lyricsprovider.cpp
|
||||||
|
lyrics/lyricssearchrequest.h
|
||||||
|
lyrics/lyricssearchresult.h
|
||||||
lyrics/lyricsfetcher.cpp
|
lyrics/lyricsfetcher.cpp
|
||||||
lyrics/lyricsfetchersearch.cpp
|
lyrics/lyricsfetchersearch.cpp
|
||||||
lyrics/jsonlyricsprovider.cpp
|
lyrics/jsonlyricsprovider.cpp
|
||||||
@@ -161,6 +180,7 @@ set(SOURCES
|
|||||||
lyrics/geniuslyricsprovider.cpp
|
lyrics/geniuslyricsprovider.cpp
|
||||||
lyrics/musixmatchlyricsprovider.cpp
|
lyrics/musixmatchlyricsprovider.cpp
|
||||||
lyrics/chartlyricsprovider.cpp
|
lyrics/chartlyricsprovider.cpp
|
||||||
|
lyrics/lyricscomlyricsprovider.cpp
|
||||||
|
|
||||||
providers/musixmatchprovider.cpp
|
providers/musixmatchprovider.cpp
|
||||||
|
|
||||||
@@ -205,6 +225,8 @@ set(SOURCES
|
|||||||
widgets/multiloadingindicator.cpp
|
widgets/multiloadingindicator.cpp
|
||||||
widgets/playingwidget.cpp
|
widgets/playingwidget.cpp
|
||||||
widgets/renametablineedit.cpp
|
widgets/renametablineedit.cpp
|
||||||
|
widgets/sliderslider.cpp
|
||||||
|
widgets/prettyslider.cpp
|
||||||
widgets/volumeslider.cpp
|
widgets/volumeslider.cpp
|
||||||
widgets/stickyslider.cpp
|
widgets/stickyslider.cpp
|
||||||
widgets/stretchheaderview.cpp
|
widgets/stretchheaderview.cpp
|
||||||
@@ -248,6 +270,7 @@ set(SOURCES
|
|||||||
scrobbler/scrobblerservice.cpp
|
scrobbler/scrobblerservice.cpp
|
||||||
scrobbler/scrobblercache.cpp
|
scrobbler/scrobblercache.cpp
|
||||||
scrobbler/scrobblercacheitem.cpp
|
scrobbler/scrobblercacheitem.cpp
|
||||||
|
scrobbler/scrobblemetadata.cpp
|
||||||
scrobbler/scrobblingapi20.cpp
|
scrobbler/scrobblingapi20.cpp
|
||||||
scrobbler/lastfmscrobbler.cpp
|
scrobbler/lastfmscrobbler.cpp
|
||||||
scrobbler/librefmscrobbler.cpp
|
scrobbler/librefmscrobbler.cpp
|
||||||
@@ -264,7 +287,6 @@ set(SOURCES
|
|||||||
set(HEADERS
|
set(HEADERS
|
||||||
core/mainwindow.h
|
core/mainwindow.h
|
||||||
core/application.h
|
core/application.h
|
||||||
core/appearance.h
|
|
||||||
core/player.h
|
core/player.h
|
||||||
core/database.h
|
core/database.h
|
||||||
core/deletefiles.h
|
core/deletefiles.h
|
||||||
@@ -294,6 +316,7 @@ set(HEADERS
|
|||||||
analyzer/blockanalyzer.h
|
analyzer/blockanalyzer.h
|
||||||
analyzer/boomanalyzer.h
|
analyzer/boomanalyzer.h
|
||||||
analyzer/rainbowanalyzer.h
|
analyzer/rainbowanalyzer.h
|
||||||
|
analyzer/sonogram.h
|
||||||
|
|
||||||
equalizer/equalizer.h
|
equalizer/equalizer.h
|
||||||
equalizer/equalizerslider.h
|
equalizer/equalizerslider.h
|
||||||
@@ -395,6 +418,7 @@ set(HEADERS
|
|||||||
lyrics/geniuslyricsprovider.h
|
lyrics/geniuslyricsprovider.h
|
||||||
lyrics/musixmatchlyricsprovider.h
|
lyrics/musixmatchlyricsprovider.h
|
||||||
lyrics/chartlyricsprovider.h
|
lyrics/chartlyricsprovider.h
|
||||||
|
lyrics/lyricscomlyricsprovider.h
|
||||||
|
|
||||||
settings/settingsdialog.h
|
settings/settingsdialog.h
|
||||||
settings/settingspage.h
|
settings/settingspage.h
|
||||||
@@ -436,6 +460,8 @@ set(HEADERS
|
|||||||
widgets/multiloadingindicator.h
|
widgets/multiloadingindicator.h
|
||||||
widgets/playingwidget.h
|
widgets/playingwidget.h
|
||||||
widgets/renametablineedit.h
|
widgets/renametablineedit.h
|
||||||
|
widgets/sliderslider.h
|
||||||
|
widgets/prettyslider.h
|
||||||
widgets/volumeslider.h
|
widgets/volumeslider.h
|
||||||
widgets/stickyslider.h
|
widgets/stickyslider.h
|
||||||
widgets/stretchheaderview.h
|
widgets/stretchheaderview.h
|
||||||
@@ -446,7 +472,6 @@ set(HEADERS
|
|||||||
widgets/qsearchfield.h
|
widgets/qsearchfield.h
|
||||||
widgets/ratingwidget.h
|
widgets/ratingwidget.h
|
||||||
widgets/forcescrollperpixel.h
|
widgets/forcescrollperpixel.h
|
||||||
widgets/resizabletextedit.h
|
|
||||||
|
|
||||||
osd/osdbase.h
|
osd/osdbase.h
|
||||||
osd/osdpretty.h
|
osd/osdpretty.h
|
||||||
@@ -478,7 +503,6 @@ set(HEADERS
|
|||||||
scrobbler/scrobblerservices.h
|
scrobbler/scrobblerservices.h
|
||||||
scrobbler/scrobblerservice.h
|
scrobbler/scrobblerservice.h
|
||||||
scrobbler/scrobblercache.h
|
scrobbler/scrobblercache.h
|
||||||
scrobbler/scrobblercacheitem.h
|
|
||||||
scrobbler/scrobblingapi20.h
|
scrobbler/scrobblingapi20.h
|
||||||
scrobbler/lastfmscrobbler.h
|
scrobbler/lastfmscrobbler.h
|
||||||
scrobbler/librefmscrobbler.h
|
scrobbler/librefmscrobbler.h
|
||||||
@@ -783,8 +807,8 @@ optional_source(HAVE_AUDIOCD
|
|||||||
# Platform specific - macOS
|
# Platform specific - macOS
|
||||||
optional_source(APPLE
|
optional_source(APPLE
|
||||||
SOURCES
|
SOURCES
|
||||||
|
utilities/macosutils.mm
|
||||||
core/scoped_nsautorelease_pool.mm
|
core/scoped_nsautorelease_pool.mm
|
||||||
core/mac_utilities.mm
|
|
||||||
core/mac_startup.mm
|
core/mac_startup.mm
|
||||||
core/macsystemtrayicon.mm
|
core/macsystemtrayicon.mm
|
||||||
core/macfslistener.mm
|
core/macfslistener.mm
|
||||||
@@ -805,8 +829,10 @@ optional_source(APPLE
|
|||||||
# Platform specific - Windows
|
# Platform specific - Windows
|
||||||
optional_source(WIN32
|
optional_source(WIN32
|
||||||
SOURCES
|
SOURCES
|
||||||
|
utilities/winutils.cpp
|
||||||
engine/directsounddevicefinder.cpp
|
engine/directsounddevicefinder.cpp
|
||||||
engine/mmdevicefinder.cpp
|
engine/mmdevicefinder.cpp
|
||||||
|
core/scopedwchararray.cpp
|
||||||
core/windows7thumbbar.cpp
|
core/windows7thumbbar.cpp
|
||||||
HEADERS
|
HEADERS
|
||||||
core/windows7thumbbar.h
|
core/windows7thumbbar.h
|
||||||
@@ -943,6 +969,7 @@ link_directories(
|
|||||||
${GOBJECT_LIBRARY_DIRS}
|
${GOBJECT_LIBRARY_DIRS}
|
||||||
${GNUTLS_LIBRARY_DIRS}
|
${GNUTLS_LIBRARY_DIRS}
|
||||||
${SQLITE_LIBRARY_DIRS}
|
${SQLITE_LIBRARY_DIRS}
|
||||||
|
${PROTOBUF_LIBRARY_DIRS}
|
||||||
${SINGLEAPPLICATION_LIBRARY_DIRS}
|
${SINGLEAPPLICATION_LIBRARY_DIRS}
|
||||||
${SINGLECOREAPPLICATION_LIBRARY_DIRS}
|
${SINGLECOREAPPLICATION_LIBRARY_DIRS}
|
||||||
)
|
)
|
||||||
@@ -1062,6 +1089,7 @@ target_link_libraries(strawberry_lib PUBLIC
|
|||||||
${GNUTLS_LIBRARIES}
|
${GNUTLS_LIBRARIES}
|
||||||
${SQLITE_LIBRARIES}
|
${SQLITE_LIBRARIES}
|
||||||
${QT_LIBRARIES}
|
${QT_LIBRARIES}
|
||||||
|
${Protobuf_LIBRARIES}
|
||||||
${SINGLEAPPLICATION_LIBRARIES}
|
${SINGLEAPPLICATION_LIBRARIES}
|
||||||
${SINGLECOREAPPLICATION_LIBRARIES}
|
${SINGLECOREAPPLICATION_LIBRARIES}
|
||||||
libstrawberry-common
|
libstrawberry-common
|
||||||
|
|||||||
@@ -108,7 +108,7 @@ void Analyzer::Base::paintEvent(QPaintEvent *e) {
|
|||||||
p.fillRect(e->rect(), palette().color(QPalette::Window));
|
p.fillRect(e->rect(), palette().color(QPalette::Window));
|
||||||
|
|
||||||
switch (engine_->state()) {
|
switch (engine_->state()) {
|
||||||
case Engine::Playing: {
|
case Engine::State::Playing: {
|
||||||
const Engine::Scope &thescope = engine_->scope(timeout_);
|
const Engine::Scope &thescope = engine_->scope(timeout_);
|
||||||
int i = 0;
|
int i = 0;
|
||||||
|
|
||||||
@@ -126,7 +126,7 @@ void Analyzer::Base::paintEvent(QPaintEvent *e) {
|
|||||||
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case Engine::Paused:
|
case Engine::State::Paused:
|
||||||
is_playing_ = false;
|
is_playing_ = false;
|
||||||
analyze(p, lastscope_, new_frame_);
|
analyze(p, lastscope_, new_frame_);
|
||||||
break;
|
break;
|
||||||
|
|||||||
@@ -40,6 +40,7 @@
|
|||||||
#include "blockanalyzer.h"
|
#include "blockanalyzer.h"
|
||||||
#include "boomanalyzer.h"
|
#include "boomanalyzer.h"
|
||||||
#include "rainbowanalyzer.h"
|
#include "rainbowanalyzer.h"
|
||||||
|
#include "sonogram.h"
|
||||||
|
|
||||||
#include "core/logging.h"
|
#include "core/logging.h"
|
||||||
#include "engine/enginebase.h"
|
#include "engine/enginebase.h"
|
||||||
@@ -85,6 +86,7 @@ AnalyzerContainer::AnalyzerContainer(QWidget *parent)
|
|||||||
AddAnalyzerType<BoomAnalyzer>();
|
AddAnalyzerType<BoomAnalyzer>();
|
||||||
AddAnalyzerType<Rainbow::NyanCatAnalyzer>();
|
AddAnalyzerType<Rainbow::NyanCatAnalyzer>();
|
||||||
AddAnalyzerType<Rainbow::RainbowDashAnalyzer>();
|
AddAnalyzerType<Rainbow::RainbowDashAnalyzer>();
|
||||||
|
AddAnalyzerType<Sonogram>();
|
||||||
|
|
||||||
disable_action_ = context_menu_->addAction(tr("No analyzer"), this, &AnalyzerContainer::DisableAnalyzer);
|
disable_action_ = context_menu_->addAction(tr("No analyzer"), this, &AnalyzerContainer::DisableAnalyzer);
|
||||||
disable_action_->setCheckable(true);
|
disable_action_->setCheckable(true);
|
||||||
|
|||||||
@@ -110,7 +110,7 @@ void BoomAnalyzer::transform(Scope &s) {
|
|||||||
|
|
||||||
void BoomAnalyzer::analyze(QPainter &p, const Scope &scope, const bool new_frame) {
|
void BoomAnalyzer::analyze(QPainter &p, const Scope &scope, const bool new_frame) {
|
||||||
|
|
||||||
if (!new_frame || engine_->state() == Engine::Paused) {
|
if (!new_frame || engine_->state() == Engine::State::Paused) {
|
||||||
p.drawPixmap(0, 0, canvas_);
|
p.drawPixmap(0, 0, canvas_);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|||||||
92
src/analyzer/sonogram.cpp
Normal file
92
src/analyzer/sonogram.cpp
Normal file
@@ -0,0 +1,92 @@
|
|||||||
|
/*
|
||||||
|
Strawberry Music Player
|
||||||
|
This file was part of Clementine.
|
||||||
|
Copyright 2004, Melchior FRANZ <mfranz@kde.org>
|
||||||
|
Copyright 2009-2010, David Sansome <davidsansome@gmail.com>
|
||||||
|
Copyright 2010, 2014, John Maguire <john.maguire@gmail.com>
|
||||||
|
Copyright 2014-2015, Mark Furneaux <mark@furneaux.ca>
|
||||||
|
Copyright 2014, Krzysztof Sobiecki <sobkas@gmail.com>
|
||||||
|
|
||||||
|
Strawberry is free software: you can redistribute it and/or modify
|
||||||
|
it under the terms of the GNU General Public License as published by
|
||||||
|
the Free Software Foundation, either version 3 of the License, or
|
||||||
|
(at your option) any later version.
|
||||||
|
|
||||||
|
Strawberry is distributed in the hope that it will be useful,
|
||||||
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
GNU General Public License for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU General Public License
|
||||||
|
along with Strawberry. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <QPainter>
|
||||||
|
#include <QResizeEvent>
|
||||||
|
|
||||||
|
#include "sonogram.h"
|
||||||
|
|
||||||
|
const char *Sonogram::kName = QT_TRANSLATE_NOOP("AnalyzerContainer", "Sonogram");
|
||||||
|
|
||||||
|
Sonogram::Sonogram(QWidget *parent)
|
||||||
|
: Analyzer::Base(parent, 9) {}
|
||||||
|
|
||||||
|
void Sonogram::resizeEvent(QResizeEvent *e) {
|
||||||
|
|
||||||
|
Q_UNUSED(e)
|
||||||
|
|
||||||
|
canvas_ = QPixmap(size());
|
||||||
|
canvas_.fill(palette().color(QPalette::Window));
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
void Sonogram::analyze(QPainter &p, const Analyzer::Scope &s, bool new_frame) {
|
||||||
|
|
||||||
|
if (!new_frame || engine_->state() == Engine::State::Paused) {
|
||||||
|
p.drawPixmap(0, 0, canvas_);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
QPainter canvas_painter(&canvas_);
|
||||||
|
canvas_painter.drawPixmap(0, 0, canvas_, 1, 0, width() - 1, -1);
|
||||||
|
|
||||||
|
Analyzer::Scope::const_iterator it = s.begin(), end = s.end();
|
||||||
|
|
||||||
|
for (int y = height() - 1; y;) {
|
||||||
|
QColor c;
|
||||||
|
if (it >= end || *it < .005) {
|
||||||
|
c = palette().color(QPalette::Window);
|
||||||
|
}
|
||||||
|
else if (*it < .05) {
|
||||||
|
c.setHsv(95, 255, 255 - static_cast<int>(*it * 4000.0));
|
||||||
|
}
|
||||||
|
else if (*it < 1.0) {
|
||||||
|
c.setHsv(95 - static_cast<int>(*it * 90.0), 255, 255);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
c = Qt::red;
|
||||||
|
}
|
||||||
|
|
||||||
|
canvas_painter.setPen(c);
|
||||||
|
canvas_painter.drawPoint(width() - 1, y--);
|
||||||
|
|
||||||
|
if (it < end) ++it;
|
||||||
|
}
|
||||||
|
|
||||||
|
canvas_painter.end();
|
||||||
|
|
||||||
|
p.drawPixmap(0, 0, canvas_);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
void Sonogram::transform(Analyzer::Scope &scope) {
|
||||||
|
|
||||||
|
fht_->power2(scope.data());
|
||||||
|
fht_->scale(scope.data(), 1.0 / 256);
|
||||||
|
scope.resize(fht_->size() / 2);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
void Sonogram::demo(QPainter &p) {
|
||||||
|
analyze(p, Analyzer::Scope(fht_->size(), 0), new_frame_);
|
||||||
|
}
|
||||||
49
src/analyzer/sonogram.h
Normal file
49
src/analyzer/sonogram.h
Normal file
@@ -0,0 +1,49 @@
|
|||||||
|
/*
|
||||||
|
Strawberry Music Player
|
||||||
|
This file was part of Clementine.
|
||||||
|
Copyright 2004, Melchior FRANZ <mfranz@kde.org>
|
||||||
|
Copyright 2009-2010, David Sansome <davidsansome@gmail.com>
|
||||||
|
Copyright 2014, Krzysztof Sobiecki <sobkas@gmail.com>
|
||||||
|
Copyright 2014, John Maguire <john.maguire@gmail.com>
|
||||||
|
Copyright 2015, Mark Furneaux <mark@furneaux.ca>
|
||||||
|
|
||||||
|
Strawberry is free software: you can redistribute it and/or modify
|
||||||
|
it under the terms of the GNU General Public License as published by
|
||||||
|
the Free Software Foundation, either version 3 of the License, or
|
||||||
|
(at your option) any later version.
|
||||||
|
|
||||||
|
Strawberry is distributed in the hope that it will be useful,
|
||||||
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
GNU General Public License for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU General Public License
|
||||||
|
along with Strawberry. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef SONOGRAM_H
|
||||||
|
#define SONOGRAM_H
|
||||||
|
|
||||||
|
#include <QPixmap>
|
||||||
|
#include <QPainter>
|
||||||
|
|
||||||
|
#include "analyzerbase.h"
|
||||||
|
|
||||||
|
class Sonogram : public Analyzer::Base {
|
||||||
|
Q_OBJECT
|
||||||
|
public:
|
||||||
|
Q_INVOKABLE explicit Sonogram(QWidget *parent);
|
||||||
|
|
||||||
|
static const char *kName;
|
||||||
|
|
||||||
|
protected:
|
||||||
|
void resizeEvent(QResizeEvent *e) override;
|
||||||
|
void analyze(QPainter &p, const Analyzer::Scope &s, bool new_frame) override;
|
||||||
|
void transform(Analyzer::Scope &scope) override;
|
||||||
|
void demo(QPainter &p) override;
|
||||||
|
|
||||||
|
private:
|
||||||
|
QPixmap canvas_;
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif // SONOGRAM_H
|
||||||
@@ -34,9 +34,9 @@
|
|||||||
#include "core/player.h"
|
#include "core/player.h"
|
||||||
#include "core/tagreaderclient.h"
|
#include "core/tagreaderclient.h"
|
||||||
#include "core/thread.h"
|
#include "core/thread.h"
|
||||||
#include "core/utilities.h"
|
|
||||||
#include "core/song.h"
|
#include "core/song.h"
|
||||||
#include "core/logging.h"
|
#include "core/logging.h"
|
||||||
|
#include "utilities/threadutils.h"
|
||||||
#include "collection.h"
|
#include "collection.h"
|
||||||
#include "collectionwatcher.h"
|
#include "collectionwatcher.h"
|
||||||
#include "collectionbackend.h"
|
#include "collectionbackend.h"
|
||||||
@@ -58,8 +58,6 @@ SCollection::SCollection(Application *app, QObject *parent)
|
|||||||
watcher_(nullptr),
|
watcher_(nullptr),
|
||||||
watcher_thread_(nullptr),
|
watcher_thread_(nullptr),
|
||||||
original_thread_(nullptr),
|
original_thread_(nullptr),
|
||||||
io_priority_(Utilities::IoPriority::IOPRIO_CLASS_IDLE),
|
|
||||||
thread_priority_(QThread::Priority::IdlePriority),
|
|
||||||
save_playcounts_to_files_(false),
|
save_playcounts_to_files_(false),
|
||||||
save_ratings_to_files_(false) {
|
save_ratings_to_files_(false) {
|
||||||
|
|
||||||
@@ -69,7 +67,7 @@ SCollection::SCollection(Application *app, QObject *parent)
|
|||||||
backend()->moveToThread(app->database()->thread());
|
backend()->moveToThread(app->database()->thread());
|
||||||
qLog(Debug) << backend_ << "moved to thread" << app->database()->thread();
|
qLog(Debug) << backend_ << "moved to thread" << app->database()->thread();
|
||||||
|
|
||||||
backend_->Init(app->database(), app->task_manager(), Song::Source_Collection, kSongsTable, kFtsTable, kDirsTable, kSubdirsTable);
|
backend_->Init(app->database(), app->task_manager(), Song::Source::Collection, kSongsTable, kFtsTable, kDirsTable, kSubdirsTable);
|
||||||
|
|
||||||
model_ = new CollectionModel(backend_, app_, this);
|
model_ = new CollectionModel(backend_, app_, this);
|
||||||
|
|
||||||
@@ -93,20 +91,16 @@ SCollection::~SCollection() {
|
|||||||
|
|
||||||
void SCollection::Init() {
|
void SCollection::Init() {
|
||||||
|
|
||||||
watcher_ = new CollectionWatcher(Song::Source_Collection);
|
watcher_ = new CollectionWatcher(Song::Source::Collection);
|
||||||
watcher_thread_ = new Thread(this);
|
watcher_thread_ = new Thread(this);
|
||||||
|
|
||||||
#ifndef Q_OS_WIN32
|
watcher_thread_->SetIoPriority(Utilities::IoPriority::IOPRIO_CLASS_IDLE);
|
||||||
if (io_priority_ != Utilities::IoPriority::IOPRIO_CLASS_NONE) {
|
|
||||||
watcher_thread_->SetIoPriority(io_priority_);
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
|
|
||||||
watcher_->moveToThread(watcher_thread_);
|
watcher_->moveToThread(watcher_thread_);
|
||||||
|
|
||||||
qLog(Debug) << watcher_ << "moved to thread" << watcher_thread_ << "with I/O priority" << io_priority_ << "and thread priority" << thread_priority_;
|
qLog(Debug) << watcher_ << "moved to thread" << watcher_thread_;
|
||||||
|
|
||||||
watcher_thread_->start(thread_priority_);
|
watcher_thread_->start(QThread::IdlePriority);
|
||||||
|
|
||||||
watcher_->set_backend(backend_);
|
watcher_->set_backend(backend_);
|
||||||
watcher_->set_task_manager(app_->task_manager());
|
watcher_->set_task_manager(app_->task_manager());
|
||||||
@@ -169,7 +163,9 @@ void SCollection::AbortScan() { watcher_->Stop(); }
|
|||||||
void SCollection::Rescan(const SongList &songs) {
|
void SCollection::Rescan(const SongList &songs) {
|
||||||
|
|
||||||
qLog(Debug) << "Rescan" << songs.size() << "songs";
|
qLog(Debug) << "Rescan" << songs.size() << "songs";
|
||||||
if (!songs.isEmpty()) watcher_->RescanTracksAsync(songs);
|
if (!songs.isEmpty()) {
|
||||||
|
watcher_->RescanSongsAsync(songs);
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -184,8 +180,6 @@ void SCollection::ReloadSettings() {
|
|||||||
|
|
||||||
QSettings s;
|
QSettings s;
|
||||||
s.beginGroup(CollectionSettingsPage::kSettingsGroup);
|
s.beginGroup(CollectionSettingsPage::kSettingsGroup);
|
||||||
io_priority_ = static_cast<Utilities::IoPriority>(s.value("io_priority", Utilities::IOPRIO_CLASS_IDLE).toInt());
|
|
||||||
thread_priority_ = static_cast<QThread::Priority>(s.value("thread_priority", QThread::Priority::IdlePriority).toInt());
|
|
||||||
save_playcounts_to_files_ = s.value("save_playcounts", false).toBool();
|
save_playcounts_to_files_ = s.value("save_playcounts", false).toBool();
|
||||||
save_ratings_to_files_ = s.value("save_ratings", false).toBool();
|
save_ratings_to_files_ = s.value("save_ratings", false).toBool();
|
||||||
s.endGroup();
|
s.endGroup();
|
||||||
@@ -219,9 +213,9 @@ void SCollection::SyncPlaycountAndRatingToFiles() {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void SCollection::SongsPlaycountChanged(const SongList &songs) {
|
void SCollection::SongsPlaycountChanged(const SongList &songs, const bool save_tags) {
|
||||||
|
|
||||||
if (save_playcounts_to_files_) {
|
if (save_tags || save_playcounts_to_files_) {
|
||||||
app_->tag_reader_client()->UpdateSongsPlaycount(songs);
|
app_->tag_reader_client()->UpdateSongsPlaycount(songs);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -28,11 +28,10 @@
|
|||||||
#include <QList>
|
#include <QList>
|
||||||
#include <QHash>
|
#include <QHash>
|
||||||
#include <QString>
|
#include <QString>
|
||||||
#include <QThread>
|
|
||||||
|
|
||||||
#include "core/song.h"
|
#include "core/song.h"
|
||||||
#include "core/utilities.h"
|
|
||||||
|
|
||||||
|
class QThread;
|
||||||
class Application;
|
class Application;
|
||||||
class Thread;
|
class Thread;
|
||||||
class CollectionBackend;
|
class CollectionBackend;
|
||||||
@@ -78,7 +77,7 @@ class SCollection : public QObject {
|
|||||||
|
|
||||||
private slots:
|
private slots:
|
||||||
void ExitReceived();
|
void ExitReceived();
|
||||||
void SongsPlaycountChanged(const SongList &songs);
|
void SongsPlaycountChanged(const SongList &songs, const bool save_tags = false);
|
||||||
void SongsRatingChanged(const SongList &songs, const bool save_tags = false);
|
void SongsRatingChanged(const SongList &songs, const bool save_tags = false);
|
||||||
|
|
||||||
signals:
|
signals:
|
||||||
@@ -99,8 +98,6 @@ class SCollection : public QObject {
|
|||||||
|
|
||||||
QList<QObject*> wait_for_exit_;
|
QList<QObject*> wait_for_exit_;
|
||||||
|
|
||||||
Utilities::IoPriority io_priority_;
|
|
||||||
QThread::Priority thread_priority_;
|
|
||||||
bool save_playcounts_to_files_;
|
bool save_playcounts_to_files_;
|
||||||
bool save_ratings_to_files_;
|
bool save_ratings_to_files_;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -50,8 +50,9 @@
|
|||||||
#include "core/sqlrow.h"
|
#include "core/sqlrow.h"
|
||||||
#include "smartplaylists/smartplaylistsearch.h"
|
#include "smartplaylists/smartplaylistsearch.h"
|
||||||
|
|
||||||
#include "directory.h"
|
#include "collectiondirectory.h"
|
||||||
#include "collectionbackend.h"
|
#include "collectionbackend.h"
|
||||||
|
#include "collectionfilteroptions.h"
|
||||||
#include "collectionquery.h"
|
#include "collectionquery.h"
|
||||||
#include "collectiontask.h"
|
#include "collectiontask.h"
|
||||||
|
|
||||||
@@ -59,7 +60,7 @@ CollectionBackend::CollectionBackend(QObject *parent)
|
|||||||
: CollectionBackendInterface(parent),
|
: CollectionBackendInterface(parent),
|
||||||
db_(nullptr),
|
db_(nullptr),
|
||||||
task_manager_(nullptr),
|
task_manager_(nullptr),
|
||||||
source_(Song::Source_Unknown),
|
source_(Song::Source::Unknown),
|
||||||
original_thread_(nullptr) {
|
original_thread_(nullptr) {
|
||||||
|
|
||||||
original_thread_ = thread();
|
original_thread_ = thread();
|
||||||
@@ -139,18 +140,22 @@ void CollectionBackend::IncrementSkipCountAsync(const int id, const float progre
|
|||||||
QMetaObject::invokeMethod(this, "IncrementSkipCount", Qt::QueuedConnection, Q_ARG(int, id), Q_ARG(float, progress));
|
QMetaObject::invokeMethod(this, "IncrementSkipCount", Qt::QueuedConnection, Q_ARG(int, id), Q_ARG(float, progress));
|
||||||
}
|
}
|
||||||
|
|
||||||
void CollectionBackend::ResetStatisticsAsync(const int id) {
|
void CollectionBackend::ResetPlayStatisticsAsync(const int id, const bool save_tags) {
|
||||||
QMetaObject::invokeMethod(this, "ResetStatistics", Qt::QueuedConnection, Q_ARG(int, id));
|
QMetaObject::invokeMethod(this, "ResetPlayStatistics", Qt::QueuedConnection, Q_ARG(int, id), Q_ARG(bool, save_tags));
|
||||||
|
}
|
||||||
|
|
||||||
|
void CollectionBackend::ResetPlayStatisticsAsync(const QList<int> &id_list, const bool save_tags) {
|
||||||
|
QMetaObject::invokeMethod(this, "ResetPlayStatistics", Qt::QueuedConnection, Q_ARG(QList<int>, id_list), Q_ARG(bool, save_tags));
|
||||||
}
|
}
|
||||||
|
|
||||||
void CollectionBackend::LoadDirectories() {
|
void CollectionBackend::LoadDirectories() {
|
||||||
|
|
||||||
DirectoryList dirs = GetAllDirectories();
|
CollectionDirectoryList dirs = GetAllDirectories();
|
||||||
|
|
||||||
QMutexLocker l(db_->Mutex());
|
QMutexLocker l(db_->Mutex());
|
||||||
QSqlDatabase db(db_->Connect());
|
QSqlDatabase db(db_->Connect());
|
||||||
|
|
||||||
for (const Directory &dir : dirs) {
|
for (const CollectionDirectory &dir : dirs) {
|
||||||
emit DirectoryDiscovered(dir, SubdirsInDirectory(dir.id, db));
|
emit DirectoryDiscovered(dir, SubdirsInDirectory(dir.id, db));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -207,12 +212,12 @@ void CollectionBackend::ChangeDirPath(const int id, const QString &old_path, con
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
DirectoryList CollectionBackend::GetAllDirectories() {
|
CollectionDirectoryList CollectionBackend::GetAllDirectories() {
|
||||||
|
|
||||||
QMutexLocker l(db_->Mutex());
|
QMutexLocker l(db_->Mutex());
|
||||||
QSqlDatabase db(db_->Connect());
|
QSqlDatabase db(db_->Connect());
|
||||||
|
|
||||||
DirectoryList ret;
|
CollectionDirectoryList ret;
|
||||||
|
|
||||||
SqlQuery q(db);
|
SqlQuery q(db);
|
||||||
q.prepare(QString("SELECT ROWID, path FROM %1").arg(dirs_table_));
|
q.prepare(QString("SELECT ROWID, path FROM %1").arg(dirs_table_));
|
||||||
@@ -222,7 +227,7 @@ DirectoryList CollectionBackend::GetAllDirectories() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
while (q.next()) {
|
while (q.next()) {
|
||||||
Directory dir;
|
CollectionDirectory dir;
|
||||||
dir.id = q.value(0).toInt();
|
dir.id = q.value(0).toInt();
|
||||||
dir.path = q.value(1).toString();
|
dir.path = q.value(1).toString();
|
||||||
|
|
||||||
@@ -232,7 +237,7 @@ DirectoryList CollectionBackend::GetAllDirectories() {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
SubdirectoryList CollectionBackend::SubdirsInDirectory(const int id) {
|
CollectionSubdirectoryList CollectionBackend::SubdirsInDirectory(const int id) {
|
||||||
|
|
||||||
QMutexLocker l(db_->Mutex());
|
QMutexLocker l(db_->Mutex());
|
||||||
QSqlDatabase db = db_->Connect();
|
QSqlDatabase db = db_->Connect();
|
||||||
@@ -240,19 +245,19 @@ SubdirectoryList CollectionBackend::SubdirsInDirectory(const int id) {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
SubdirectoryList CollectionBackend::SubdirsInDirectory(const int id, QSqlDatabase &db) {
|
CollectionSubdirectoryList CollectionBackend::SubdirsInDirectory(const int id, QSqlDatabase &db) {
|
||||||
|
|
||||||
SqlQuery q(db);
|
SqlQuery q(db);
|
||||||
q.prepare(QString("SELECT path, mtime FROM %1 WHERE directory_id = :dir").arg(subdirs_table_));
|
q.prepare(QString("SELECT path, mtime FROM %1 WHERE directory_id = :dir").arg(subdirs_table_));
|
||||||
q.BindValue(":dir", id);
|
q.BindValue(":dir", id);
|
||||||
if (!q.Exec()) {
|
if (!q.Exec()) {
|
||||||
db_->ReportErrors(q);
|
db_->ReportErrors(q);
|
||||||
return SubdirectoryList();
|
return CollectionSubdirectoryList();
|
||||||
}
|
}
|
||||||
|
|
||||||
SubdirectoryList subdirs;
|
CollectionSubdirectoryList subdirs;
|
||||||
while (q.next()) {
|
while (q.next()) {
|
||||||
Subdirectory subdir;
|
CollectionSubdirectory subdir;
|
||||||
subdir.directory_id = id;
|
subdir.directory_id = id;
|
||||||
subdir.path = q.value(0).toString();
|
subdir.path = q.value(0).toString();
|
||||||
subdir.mtime = q.value(1).toLongLong();
|
subdir.mtime = q.value(1).toLongLong();
|
||||||
@@ -339,15 +344,15 @@ void CollectionBackend::AddDirectory(const QString &path) {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
Directory dir;
|
CollectionDirectory dir;
|
||||||
dir.path = canonical_path;
|
dir.path = canonical_path;
|
||||||
dir.id = q.lastInsertId().toInt();
|
dir.id = q.lastInsertId().toInt();
|
||||||
|
|
||||||
emit DirectoryDiscovered(dir, SubdirectoryList());
|
emit DirectoryDiscovered(dir, CollectionSubdirectoryList());
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void CollectionBackend::RemoveDirectory(const Directory &dir) {
|
void CollectionBackend::RemoveDirectory(const CollectionDirectory &dir) {
|
||||||
|
|
||||||
QMutexLocker l(db_->Mutex());
|
QMutexLocker l(db_->Mutex());
|
||||||
QSqlDatabase db(db_->Connect());
|
QSqlDatabase db(db_->Connect());
|
||||||
@@ -447,13 +452,13 @@ void CollectionBackend::SongPathChanged(const Song &song, const QFileInfo &new_f
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void CollectionBackend::AddOrUpdateSubdirs(const SubdirectoryList &subdirs) {
|
void CollectionBackend::AddOrUpdateSubdirs(const CollectionSubdirectoryList &subdirs) {
|
||||||
|
|
||||||
QMutexLocker l(db_->Mutex());
|
QMutexLocker l(db_->Mutex());
|
||||||
QSqlDatabase db(db_->Connect());
|
QSqlDatabase db(db_->Connect());
|
||||||
|
|
||||||
ScopedTransaction transaction(&db);
|
ScopedTransaction transaction(&db);
|
||||||
for (const Subdirectory &subdir : subdirs) {
|
for (const CollectionSubdirectory &subdir : subdirs) {
|
||||||
if (subdir.mtime == 0) {
|
if (subdir.mtime == 0) {
|
||||||
// Delete the subdirectory
|
// Delete the subdirectory
|
||||||
SqlQuery q(db);
|
SqlQuery q(db);
|
||||||
@@ -713,7 +718,7 @@ void CollectionBackend::UpdateSongsBySongID(const SongMap &new_songs) {
|
|||||||
|
|
||||||
Song old_song = old_songs[new_song.song_id()];
|
Song old_song = old_songs[new_song.song_id()];
|
||||||
|
|
||||||
if (!new_song.IsMetadataEqual(old_song)) { // Update existing song.
|
if (!new_song.IsAllMetadataEqual(old_song) || !new_song.IsFingerprintEqual(old_song)) { // Update existing song.
|
||||||
|
|
||||||
{
|
{
|
||||||
SqlQuery q(db);
|
SqlQuery q(db);
|
||||||
@@ -900,12 +905,12 @@ void CollectionBackend::MarkSongsUnavailable(const SongList &songs, const bool u
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
QStringList CollectionBackend::GetAll(const QString &column, const QueryOptions &opt) {
|
QStringList CollectionBackend::GetAll(const QString &column, const CollectionFilterOptions &filter_options) {
|
||||||
|
|
||||||
QMutexLocker l(db_->Mutex());
|
QMutexLocker l(db_->Mutex());
|
||||||
QSqlDatabase db(db_->Connect());
|
QSqlDatabase db(db_->Connect());
|
||||||
|
|
||||||
CollectionQuery query(db, songs_table_, fts_table_, opt);
|
CollectionQuery query(db, songs_table_, fts_table_, filter_options);
|
||||||
query.SetColumnSpec("DISTINCT " + column);
|
query.SetColumnSpec("DISTINCT " + column);
|
||||||
query.AddCompilationRequirement(false);
|
query.AddCompilationRequirement(false);
|
||||||
|
|
||||||
@@ -922,12 +927,12 @@ QStringList CollectionBackend::GetAll(const QString &column, const QueryOptions
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
QStringList CollectionBackend::GetAllArtists(const QueryOptions &opt) {
|
QStringList CollectionBackend::GetAllArtists(const CollectionFilterOptions &opt) {
|
||||||
|
|
||||||
return GetAll("artist", opt);
|
return GetAll("artist", opt);
|
||||||
}
|
}
|
||||||
|
|
||||||
QStringList CollectionBackend::GetAllArtistsWithAlbums(const QueryOptions &opt) {
|
QStringList CollectionBackend::GetAllArtistsWithAlbums(const CollectionFilterOptions &opt) {
|
||||||
|
|
||||||
QMutexLocker l(db_->Mutex());
|
QMutexLocker l(db_->Mutex());
|
||||||
QSqlDatabase db(db_->Connect());
|
QSqlDatabase db(db_->Connect());
|
||||||
@@ -967,15 +972,15 @@ QStringList CollectionBackend::GetAllArtistsWithAlbums(const QueryOptions &opt)
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
CollectionBackend::AlbumList CollectionBackend::GetAllAlbums(const QueryOptions &opt) {
|
CollectionBackend::AlbumList CollectionBackend::GetAllAlbums(const CollectionFilterOptions &opt) {
|
||||||
return GetAlbums(QString(), false, opt);
|
return GetAlbums(QString(), false, opt);
|
||||||
}
|
}
|
||||||
|
|
||||||
CollectionBackend::AlbumList CollectionBackend::GetAlbumsByArtist(const QString &artist, const QueryOptions &opt) {
|
CollectionBackend::AlbumList CollectionBackend::GetAlbumsByArtist(const QString &artist, const CollectionFilterOptions &opt) {
|
||||||
return GetAlbums(artist, false, opt);
|
return GetAlbums(artist, false, opt);
|
||||||
}
|
}
|
||||||
|
|
||||||
SongList CollectionBackend::GetArtistSongs(const QString &effective_albumartist, const QueryOptions &opt) {
|
SongList CollectionBackend::GetArtistSongs(const QString &effective_albumartist, const CollectionFilterOptions &opt) {
|
||||||
|
|
||||||
QSqlDatabase db(db_->Connect());
|
QSqlDatabase db(db_->Connect());
|
||||||
QMutexLocker l(db_->Mutex());
|
QMutexLocker l(db_->Mutex());
|
||||||
@@ -993,7 +998,7 @@ SongList CollectionBackend::GetArtistSongs(const QString &effective_albumartist,
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
SongList CollectionBackend::GetAlbumSongs(const QString &effective_albumartist, const QString &album, const QueryOptions &opt) {
|
SongList CollectionBackend::GetAlbumSongs(const QString &effective_albumartist, const QString &album, const CollectionFilterOptions &opt) {
|
||||||
|
|
||||||
QSqlDatabase db(db_->Connect());
|
QSqlDatabase db(db_->Connect());
|
||||||
QMutexLocker l(db_->Mutex());
|
QMutexLocker l(db_->Mutex());
|
||||||
@@ -1012,7 +1017,7 @@ SongList CollectionBackend::GetAlbumSongs(const QString &effective_albumartist,
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
SongList CollectionBackend::GetSongsByAlbum(const QString &album, const QueryOptions &opt) {
|
SongList CollectionBackend::GetSongsByAlbum(const QString &album, const CollectionFilterOptions &opt) {
|
||||||
|
|
||||||
QSqlDatabase db(db_->Connect());
|
QSqlDatabase db(db_->Connect());
|
||||||
QMutexLocker l(db_->Mutex());
|
QMutexLocker l(db_->Mutex());
|
||||||
@@ -1285,11 +1290,11 @@ SongList CollectionBackend::GetSongsByFingerprint(const QString &fingerprint) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
CollectionBackend::AlbumList CollectionBackend::GetCompilationAlbums(const QueryOptions &opt) {
|
CollectionBackend::AlbumList CollectionBackend::GetCompilationAlbums(const CollectionFilterOptions &opt) {
|
||||||
return GetAlbums(QString(), true, opt);
|
return GetAlbums(QString(), true, opt);
|
||||||
}
|
}
|
||||||
|
|
||||||
SongList CollectionBackend::GetCompilationSongs(const QString &album, const QueryOptions &opt) {
|
SongList CollectionBackend::GetCompilationSongs(const QString &album, const CollectionFilterOptions &opt) {
|
||||||
|
|
||||||
QMutexLocker l(db_->Mutex());
|
QMutexLocker l(db_->Mutex());
|
||||||
QSqlDatabase db(db_->Connect());
|
QSqlDatabase db(db_->Connect());
|
||||||
@@ -1426,7 +1431,7 @@ bool CollectionBackend::UpdateCompilations(const QSqlDatabase &db, SongList &del
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
CollectionBackend::AlbumList CollectionBackend::GetAlbums(const QString &artist, const bool compilation_required, const QueryOptions &opt) {
|
CollectionBackend::AlbumList CollectionBackend::GetAlbums(const QString &artist, const bool compilation_required, const CollectionFilterOptions &opt) {
|
||||||
|
|
||||||
QMutexLocker l(db_->Mutex());
|
QMutexLocker l(db_->Mutex());
|
||||||
QSqlDatabase db(db_->Connect());
|
QSqlDatabase db(db_->Connect());
|
||||||
@@ -1516,7 +1521,7 @@ CollectionBackend::Album CollectionBackend::GetAlbumArt(const QString &effective
|
|||||||
ret.album = album;
|
ret.album = album;
|
||||||
ret.album_artist = effective_albumartist;
|
ret.album_artist = effective_albumartist;
|
||||||
|
|
||||||
CollectionQuery query(db, songs_table_, fts_table_, QueryOptions());
|
CollectionQuery query(db, songs_table_, fts_table_);
|
||||||
query.SetColumnSpec("art_automatic, art_manual, url");
|
query.SetColumnSpec("art_automatic, art_manual, url");
|
||||||
if (!effective_albumartist.isEmpty()) {
|
if (!effective_albumartist.isEmpty()) {
|
||||||
query.AddWhere("effective_albumartist", effective_albumartist);
|
query.AddWhere("effective_albumartist", effective_albumartist);
|
||||||
@@ -1775,23 +1780,48 @@ void CollectionBackend::IncrementSkipCount(const int id, const float progress) {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void CollectionBackend::ResetStatistics(const int id) {
|
void CollectionBackend::ResetPlayStatistics(const int id, const bool save_tags) {
|
||||||
|
|
||||||
if (id == -1) return;
|
if (id == -1) return;
|
||||||
|
|
||||||
|
ResetPlayStatistics(QList<int>() << id, save_tags);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
void CollectionBackend::ResetPlayStatistics(const QList<int> &id_list, const bool save_tags) {
|
||||||
|
|
||||||
|
if (id_list.isEmpty()) return;
|
||||||
|
|
||||||
|
QStringList id_str_list;
|
||||||
|
id_str_list.reserve(id_list.count());
|
||||||
|
for (const int id : id_list) {
|
||||||
|
id_str_list << QString::number(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
const bool success = ResetPlayStatistics(id_str_list);
|
||||||
|
if (success) {
|
||||||
|
const SongList songs = GetSongsById(id_list);
|
||||||
|
emit SongsStatisticsChanged(songs, save_tags);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
bool CollectionBackend::ResetPlayStatistics(const QStringList &id_str_list) {
|
||||||
|
|
||||||
|
if (id_str_list.isEmpty()) return false;
|
||||||
|
|
||||||
QMutexLocker l(db_->Mutex());
|
QMutexLocker l(db_->Mutex());
|
||||||
QSqlDatabase db(db_->Connect());
|
QSqlDatabase db(db_->Connect());
|
||||||
|
|
||||||
SqlQuery q(db);
|
SqlQuery q(db);
|
||||||
q.prepare(QString("UPDATE %1 SET playcount = 0, skipcount = 0, lastplayed = -1 WHERE ROWID = :id").arg(songs_table_));
|
q.prepare(QString("UPDATE %1 SET playcount = 0, skipcount = 0, lastplayed = -1 WHERE ROWID IN (:ids)").arg(songs_table_));
|
||||||
q.BindValue(":id", id);
|
q.BindValue(":ids", id_str_list.join(","));
|
||||||
if (!q.Exec()) {
|
if (!q.Exec()) {
|
||||||
db_->ReportErrors(q);
|
db_->ReportErrors(q);
|
||||||
return;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
Song new_song = GetSongById(id, db);
|
return true;
|
||||||
emit SongsStatisticsChanged(SongList() << new_song);
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1863,7 +1893,7 @@ SongList CollectionBackend::SmartPlaylistsFindSongs(const SmartPlaylistSearch &s
|
|||||||
SongList CollectionBackend::SmartPlaylistsGetAllSongs() {
|
SongList CollectionBackend::SmartPlaylistsGetAllSongs() {
|
||||||
|
|
||||||
// Get all the songs!
|
// Get all the songs!
|
||||||
return SmartPlaylistsFindSongs(SmartPlaylistSearch(SmartPlaylistSearch::Type_All, SmartPlaylistSearch::TermList(), SmartPlaylistSearch::Sort_FieldAsc, SmartPlaylistSearchTerm::Field_Artist, -1));
|
return SmartPlaylistsFindSongs(SmartPlaylistSearch(SmartPlaylistSearch::SearchType::All, SmartPlaylistSearch::TermList(), SmartPlaylistSearch::SortType::FieldAsc, SmartPlaylistSearchTerm::Field::Artist, -1));
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1926,7 +1956,7 @@ void CollectionBackend::UpdateLastPlayed(const QString &artist, const QString &a
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void CollectionBackend::UpdatePlayCount(const QString &artist, const QString &title, const int playcount) {
|
void CollectionBackend::UpdatePlayCount(const QString &artist, const QString &title, const int playcount, const bool save_tags) {
|
||||||
|
|
||||||
SongList songs = GetSongsBy(artist, QString(), title);
|
SongList songs = GetSongsBy(artist, QString(), title);
|
||||||
if (songs.isEmpty()) {
|
if (songs.isEmpty()) {
|
||||||
@@ -1948,7 +1978,7 @@ void CollectionBackend::UpdatePlayCount(const QString &artist, const QString &ti
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
emit SongsStatisticsChanged(SongList() << songs);
|
emit SongsStatisticsChanged(SongList() << songs, save_tags);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -38,8 +38,9 @@
|
|||||||
|
|
||||||
#include "core/song.h"
|
#include "core/song.h"
|
||||||
#include "core/sqlquery.h"
|
#include "core/sqlquery.h"
|
||||||
|
#include "collectionfilteroptions.h"
|
||||||
#include "collectionquery.h"
|
#include "collectionquery.h"
|
||||||
#include "directory.h"
|
#include "collectiondirectory.h"
|
||||||
|
|
||||||
class QThread;
|
class QThread;
|
||||||
class TaskManager;
|
class TaskManager;
|
||||||
@@ -53,7 +54,7 @@ class CollectionBackendInterface : public QObject {
|
|||||||
explicit CollectionBackendInterface(QObject *parent = nullptr) : QObject(parent) {}
|
explicit CollectionBackendInterface(QObject *parent = nullptr) : QObject(parent) {}
|
||||||
|
|
||||||
struct Album {
|
struct Album {
|
||||||
Album() {}
|
Album() : filetype(Song::FileType::Unknown) {}
|
||||||
Album(const QString &_album_artist, const QString &_album, const QUrl &_art_automatic, const QUrl &_art_manual, const QList<QUrl> &_urls, const Song::FileType _filetype, const QString &_cue_path)
|
Album(const QString &_album_artist, const QString &_album, const QUrl &_art_automatic, const QUrl &_art_manual, const QList<QUrl> &_urls, const Song::FileType _filetype, const QString &_cue_path)
|
||||||
: album_artist(_album_artist),
|
: album_artist(_album_artist),
|
||||||
album(_album),
|
album(_album),
|
||||||
@@ -90,23 +91,23 @@ class CollectionBackendInterface : public QObject {
|
|||||||
|
|
||||||
virtual SongList FindSongsInDirectory(const int id) = 0;
|
virtual SongList FindSongsInDirectory(const int id) = 0;
|
||||||
virtual SongList SongsWithMissingFingerprint(const int id) = 0;
|
virtual SongList SongsWithMissingFingerprint(const int id) = 0;
|
||||||
virtual SubdirectoryList SubdirsInDirectory(const int id) = 0;
|
virtual CollectionSubdirectoryList SubdirsInDirectory(const int id) = 0;
|
||||||
virtual DirectoryList GetAllDirectories() = 0;
|
virtual CollectionDirectoryList GetAllDirectories() = 0;
|
||||||
virtual void ChangeDirPath(const int id, const QString &old_path, const QString &new_path) = 0;
|
virtual void ChangeDirPath(const int id, const QString &old_path, const QString &new_path) = 0;
|
||||||
|
|
||||||
virtual SongList GetAllSongs() = 0;
|
virtual SongList GetAllSongs() = 0;
|
||||||
|
|
||||||
virtual QStringList GetAllArtists(const QueryOptions &opt = QueryOptions()) = 0;
|
virtual QStringList GetAllArtists(const CollectionFilterOptions &opt = CollectionFilterOptions()) = 0;
|
||||||
virtual QStringList GetAllArtistsWithAlbums(const QueryOptions &opt = QueryOptions()) = 0;
|
virtual QStringList GetAllArtistsWithAlbums(const CollectionFilterOptions &opt = CollectionFilterOptions()) = 0;
|
||||||
virtual SongList GetArtistSongs(const QString &effective_albumartist, const QueryOptions &opt = QueryOptions()) = 0;
|
virtual SongList GetArtistSongs(const QString &effective_albumartist, const CollectionFilterOptions &opt = CollectionFilterOptions()) = 0;
|
||||||
virtual SongList GetAlbumSongs(const QString &effective_albumartist, const QString &album, const QueryOptions &opt = QueryOptions()) = 0;
|
virtual SongList GetAlbumSongs(const QString &effective_albumartist, const QString &album, const CollectionFilterOptions &opt = CollectionFilterOptions()) = 0;
|
||||||
virtual SongList GetSongsByAlbum(const QString &album, const QueryOptions &opt = QueryOptions()) = 0;
|
virtual SongList GetSongsByAlbum(const QString &album, const CollectionFilterOptions &opt = CollectionFilterOptions()) = 0;
|
||||||
|
|
||||||
virtual SongList GetCompilationSongs(const QString &album, const QueryOptions &opt = QueryOptions()) = 0;
|
virtual SongList GetCompilationSongs(const QString &album, const CollectionFilterOptions &opt = CollectionFilterOptions()) = 0;
|
||||||
|
|
||||||
virtual AlbumList GetAllAlbums(const QueryOptions &opt = QueryOptions()) = 0;
|
virtual AlbumList GetAllAlbums(const CollectionFilterOptions &opt = CollectionFilterOptions()) = 0;
|
||||||
virtual AlbumList GetAlbumsByArtist(const QString &artist, const QueryOptions &opt = QueryOptions()) = 0;
|
virtual AlbumList GetAlbumsByArtist(const QString &artist, const CollectionFilterOptions &opt = CollectionFilterOptions()) = 0;
|
||||||
virtual AlbumList GetCompilationAlbums(const QueryOptions &opt = QueryOptions()) = 0;
|
virtual AlbumList GetCompilationAlbums(const CollectionFilterOptions &opt = CollectionFilterOptions()) = 0;
|
||||||
|
|
||||||
virtual void UpdateManualAlbumArtAsync(const QString &effective_albumartist, const QString &album, const QUrl &cover_url, const bool clear_art_automatic = false) = 0;
|
virtual void UpdateManualAlbumArtAsync(const QString &effective_albumartist, const QString &album, const QUrl &cover_url, const bool clear_art_automatic = false) = 0;
|
||||||
virtual void UpdateAutomaticAlbumArtAsync(const QString &effective_albumartist, const QString &album, const QUrl &cover_url, const bool clear_art_manual = false) = 0;
|
virtual void UpdateAutomaticAlbumArtAsync(const QString &effective_albumartist, const QString &album, const QUrl &cover_url, const bool clear_art_manual = false) = 0;
|
||||||
@@ -124,7 +125,7 @@ class CollectionBackendInterface : public QObject {
|
|||||||
virtual Song GetSongByUrl(const QUrl &url, const qint64 beginning = 0) = 0;
|
virtual Song GetSongByUrl(const QUrl &url, const qint64 beginning = 0) = 0;
|
||||||
|
|
||||||
virtual void AddDirectory(const QString &path) = 0;
|
virtual void AddDirectory(const QString &path) = 0;
|
||||||
virtual void RemoveDirectory(const Directory &dir) = 0;
|
virtual void RemoveDirectory(const CollectionDirectory &dir) = 0;
|
||||||
};
|
};
|
||||||
|
|
||||||
class CollectionBackend : public CollectionBackendInterface {
|
class CollectionBackend : public CollectionBackendInterface {
|
||||||
@@ -159,24 +160,24 @@ class CollectionBackend : public CollectionBackendInterface {
|
|||||||
|
|
||||||
SongList FindSongsInDirectory(const int id) override;
|
SongList FindSongsInDirectory(const int id) override;
|
||||||
SongList SongsWithMissingFingerprint(const int id) override;
|
SongList SongsWithMissingFingerprint(const int id) override;
|
||||||
SubdirectoryList SubdirsInDirectory(const int id) override;
|
CollectionSubdirectoryList SubdirsInDirectory(const int id) override;
|
||||||
DirectoryList GetAllDirectories() override;
|
CollectionDirectoryList GetAllDirectories() override;
|
||||||
void ChangeDirPath(const int id, const QString &old_path, const QString &new_path) override;
|
void ChangeDirPath(const int id, const QString &old_path, const QString &new_path) override;
|
||||||
|
|
||||||
SongList GetAllSongs() override;
|
SongList GetAllSongs() override;
|
||||||
|
|
||||||
QStringList GetAll(const QString &column, const QueryOptions &opt = QueryOptions());
|
QStringList GetAll(const QString &column, const CollectionFilterOptions &filter_options = CollectionFilterOptions());
|
||||||
QStringList GetAllArtists(const QueryOptions &opt = QueryOptions()) override;
|
QStringList GetAllArtists(const CollectionFilterOptions &opt = CollectionFilterOptions()) override;
|
||||||
QStringList GetAllArtistsWithAlbums(const QueryOptions &opt = QueryOptions()) override;
|
QStringList GetAllArtistsWithAlbums(const CollectionFilterOptions &opt = CollectionFilterOptions()) override;
|
||||||
SongList GetArtistSongs(const QString &effective_albumartist, const QueryOptions &opt = QueryOptions()) override;
|
SongList GetArtistSongs(const QString &effective_albumartist, const CollectionFilterOptions &opt = CollectionFilterOptions()) override;
|
||||||
SongList GetAlbumSongs(const QString &effective_albumartist, const QString &album, const QueryOptions &opt = QueryOptions()) override;
|
SongList GetAlbumSongs(const QString &effective_albumartist, const QString &album, const CollectionFilterOptions &opt = CollectionFilterOptions()) override;
|
||||||
SongList GetSongsByAlbum(const QString &album, const QueryOptions &opt = QueryOptions()) override;
|
SongList GetSongsByAlbum(const QString &album, const CollectionFilterOptions &opt = CollectionFilterOptions()) override;
|
||||||
|
|
||||||
SongList GetCompilationSongs(const QString &album, const QueryOptions &opt = QueryOptions()) override;
|
SongList GetCompilationSongs(const QString &album, const CollectionFilterOptions &opt = CollectionFilterOptions()) override;
|
||||||
|
|
||||||
AlbumList GetAllAlbums(const QueryOptions &opt = QueryOptions()) override;
|
AlbumList GetAllAlbums(const CollectionFilterOptions &opt = CollectionFilterOptions()) override;
|
||||||
AlbumList GetCompilationAlbums(const QueryOptions &opt = QueryOptions()) override;
|
AlbumList GetCompilationAlbums(const CollectionFilterOptions &opt = CollectionFilterOptions()) override;
|
||||||
AlbumList GetAlbumsByArtist(const QString &artist, const QueryOptions &opt = QueryOptions()) override;
|
AlbumList GetAlbumsByArtist(const QString &artist, const CollectionFilterOptions &opt = CollectionFilterOptions()) override;
|
||||||
|
|
||||||
void UpdateManualAlbumArtAsync(const QString &effective_albumartist, const QString &album, const QUrl &cover_url, const bool clear_art_automatic = false) override;
|
void UpdateManualAlbumArtAsync(const QString &effective_albumartist, const QString &album, const QUrl &cover_url, const bool clear_art_automatic = false) override;
|
||||||
void UpdateAutomaticAlbumArtAsync(const QString &effective_albumartist, const QString &album, const QUrl &cover_url, const bool clear_art_manual = false) override;
|
void UpdateAutomaticAlbumArtAsync(const QString &effective_albumartist, const QString &album, const QUrl &cover_url, const bool clear_art_manual = false) override;
|
||||||
@@ -192,14 +193,15 @@ class CollectionBackend : public CollectionBackendInterface {
|
|||||||
Song GetSongByUrl(const QUrl &url, qint64 beginning = 0) override;
|
Song GetSongByUrl(const QUrl &url, qint64 beginning = 0) override;
|
||||||
|
|
||||||
void AddDirectory(const QString &path) override;
|
void AddDirectory(const QString &path) override;
|
||||||
void RemoveDirectory(const Directory &dir) override;
|
void RemoveDirectory(const CollectionDirectory &dir) override;
|
||||||
|
|
||||||
bool ExecCollectionQuery(CollectionQuery *query, SongList &songs);
|
bool ExecCollectionQuery(CollectionQuery *query, SongList &songs);
|
||||||
bool ExecCollectionQuery(CollectionQuery *query, SongMap &songs);
|
bool ExecCollectionQuery(CollectionQuery *query, SongMap &songs);
|
||||||
|
|
||||||
void IncrementPlayCountAsync(const int id);
|
void IncrementPlayCountAsync(const int id);
|
||||||
void IncrementSkipCountAsync(const int id, const float progress);
|
void IncrementSkipCountAsync(const int id, const float progress);
|
||||||
void ResetStatisticsAsync(const int id);
|
void ResetPlayStatisticsAsync(const int id, const bool save_tags = false);
|
||||||
|
void ResetPlayStatisticsAsync(const QList<int> &id_list, const bool save_tags = false);
|
||||||
|
|
||||||
void DeleteAllAsync();
|
void DeleteAllAsync();
|
||||||
|
|
||||||
@@ -228,20 +230,22 @@ class CollectionBackend : public CollectionBackendInterface {
|
|||||||
void UpdateMTimesOnly(const SongList &songs);
|
void UpdateMTimesOnly(const SongList &songs);
|
||||||
void DeleteSongs(const SongList &songs);
|
void DeleteSongs(const SongList &songs);
|
||||||
void MarkSongsUnavailable(const SongList &songs, const bool unavailable = true);
|
void MarkSongsUnavailable(const SongList &songs, const bool unavailable = true);
|
||||||
void AddOrUpdateSubdirs(const SubdirectoryList &subdirs);
|
void AddOrUpdateSubdirs(const CollectionSubdirectoryList &subdirs);
|
||||||
void CompilationsNeedUpdating();
|
void CompilationsNeedUpdating();
|
||||||
void UpdateManualAlbumArt(const QString &effective_albumartist, const QString &album, const QUrl &cover_url, const bool clear_art_automatic = false);
|
void UpdateManualAlbumArt(const QString &effective_albumartist, const QString &album, const QUrl &cover_url, const bool clear_art_automatic = false);
|
||||||
void UpdateAutomaticAlbumArt(const QString &effective_albumartist, const QString &album, const QUrl &cover_url, const bool clear_art_manual = false);
|
void UpdateAutomaticAlbumArt(const QString &effective_albumartist, const QString &album, const QUrl &cover_url, const bool clear_art_manual = false);
|
||||||
void ForceCompilation(const QString &album, const QList<QString> &artists, const bool on);
|
void ForceCompilation(const QString &album, const QList<QString> &artists, const bool on);
|
||||||
void IncrementPlayCount(const int id);
|
void IncrementPlayCount(const int id);
|
||||||
void IncrementSkipCount(const int id, const float progress);
|
void IncrementSkipCount(const int id, const float progress);
|
||||||
void ResetStatistics(const int id);
|
void ResetPlayStatistics(const int id, const bool save_tags = false);
|
||||||
|
void ResetPlayStatistics(const QList<int> &id_list, const bool save_tags = false);
|
||||||
|
bool ResetPlayStatistics(const QStringList &id_str_list);
|
||||||
void DeleteAll();
|
void DeleteAll();
|
||||||
void SongPathChanged(const Song &song, const QFileInfo &new_file, const std::optional<int> new_collection_directory_id);
|
void SongPathChanged(const Song &song, const QFileInfo &new_file, const std::optional<int> new_collection_directory_id);
|
||||||
|
|
||||||
SongList GetSongsBy(const QString &artist, const QString &album, const QString &title);
|
SongList GetSongsBy(const QString &artist, const QString &album, const QString &title);
|
||||||
void UpdateLastPlayed(const QString &artist, const QString &album, const QString &title, const qint64 lastplayed);
|
void UpdateLastPlayed(const QString &artist, const QString &album, const QString &title, const qint64 lastplayed);
|
||||||
void UpdatePlayCount(const QString &artist, const QString &title, const int playcount);
|
void UpdatePlayCount(const QString &artist, const QString &title, const int playcount, const bool save_tags = false);
|
||||||
|
|
||||||
void UpdateSongRating(const int id, const float rating, const bool save_tags = false);
|
void UpdateSongRating(const int id, const float rating, const bool save_tags = false);
|
||||||
void UpdateSongsRating(const QList<int> &id_list, const float rating, const bool save_tags = false);
|
void UpdateSongsRating(const QList<int> &id_list, const float rating, const bool save_tags = false);
|
||||||
@@ -250,12 +254,12 @@ class CollectionBackend : public CollectionBackendInterface {
|
|||||||
void ExpireSongs(const int directory_id, const int expire_unavailable_songs_days);
|
void ExpireSongs(const int directory_id, const int expire_unavailable_songs_days);
|
||||||
|
|
||||||
signals:
|
signals:
|
||||||
void DirectoryDiscovered(Directory, SubdirectoryList);
|
void DirectoryDiscovered(CollectionDirectory, CollectionSubdirectoryList);
|
||||||
void DirectoryDeleted(Directory);
|
void DirectoryDeleted(CollectionDirectory);
|
||||||
|
|
||||||
void SongsDiscovered(SongList);
|
void SongsDiscovered(SongList);
|
||||||
void SongsDeleted(SongList);
|
void SongsDeleted(SongList);
|
||||||
void SongsStatisticsChanged(SongList);
|
void SongsStatisticsChanged(SongList, bool = false);
|
||||||
|
|
||||||
void DatabaseReset();
|
void DatabaseReset();
|
||||||
|
|
||||||
@@ -280,9 +284,9 @@ class CollectionBackend : public CollectionBackendInterface {
|
|||||||
};
|
};
|
||||||
|
|
||||||
bool UpdateCompilations(const QSqlDatabase &db, SongList &deleted_songs, SongList &added_songs, const QUrl &url, const bool compilation_detected);
|
bool UpdateCompilations(const QSqlDatabase &db, SongList &deleted_songs, SongList &added_songs, const QUrl &url, const bool compilation_detected);
|
||||||
AlbumList GetAlbums(const QString &artist, const QString &album_artist, const bool compilation_required = false, const QueryOptions &opt = QueryOptions());
|
AlbumList GetAlbums(const QString &artist, const QString &album_artist, const bool compilation_required = false, const CollectionFilterOptions &opt = CollectionFilterOptions());
|
||||||
AlbumList GetAlbums(const QString &artist, const bool compilation_required, const QueryOptions &opt = QueryOptions());
|
AlbumList GetAlbums(const QString &artist, const bool compilation_required, const CollectionFilterOptions &opt = CollectionFilterOptions());
|
||||||
SubdirectoryList SubdirsInDirectory(const int id, QSqlDatabase &db);
|
CollectionSubdirectoryList SubdirsInDirectory(const int id, QSqlDatabase &db);
|
||||||
|
|
||||||
Song GetSongById(const int id, QSqlDatabase &db);
|
Song GetSongById(const int id, QSqlDatabase &db);
|
||||||
SongList GetSongsById(const QStringList &ids, QSqlDatabase &db);
|
SongList GetSongsById(const QStringList &ids, QSqlDatabase &db);
|
||||||
|
|||||||
57
src/collection/collectiondirectory.h
Normal file
57
src/collection/collectiondirectory.h
Normal file
@@ -0,0 +1,57 @@
|
|||||||
|
/*
|
||||||
|
* Strawberry Music Player
|
||||||
|
* This file was part of Clementine.
|
||||||
|
* Copyright 2010, David Sansome <me@davidsansome.com>
|
||||||
|
*
|
||||||
|
* Strawberry is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* Strawberry is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with Strawberry. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef COLLECTIONDIRECTORY_H
|
||||||
|
#define COLLECTIONDIRECTORY_H
|
||||||
|
|
||||||
|
#include "config.h"
|
||||||
|
|
||||||
|
#include <QMetaType>
|
||||||
|
#include <QList>
|
||||||
|
#include <QString>
|
||||||
|
|
||||||
|
struct CollectionDirectory {
|
||||||
|
CollectionDirectory() : id(-1) {}
|
||||||
|
|
||||||
|
bool operator==(const CollectionDirectory &other) const {
|
||||||
|
return path == other.path && id == other.id;
|
||||||
|
}
|
||||||
|
|
||||||
|
QString path;
|
||||||
|
int id;
|
||||||
|
};
|
||||||
|
Q_DECLARE_METATYPE(CollectionDirectory)
|
||||||
|
|
||||||
|
using CollectionDirectoryList = QList<CollectionDirectory>;
|
||||||
|
Q_DECLARE_METATYPE(CollectionDirectoryList)
|
||||||
|
|
||||||
|
struct CollectionSubdirectory {
|
||||||
|
CollectionSubdirectory() : directory_id(-1), mtime(0) {}
|
||||||
|
|
||||||
|
int directory_id;
|
||||||
|
QString path;
|
||||||
|
qint64 mtime;
|
||||||
|
};
|
||||||
|
Q_DECLARE_METATYPE(CollectionSubdirectory)
|
||||||
|
|
||||||
|
using CollectionSubdirectoryList = QList<CollectionSubdirectory>;
|
||||||
|
Q_DECLARE_METATYPE(CollectionSubdirectoryList)
|
||||||
|
|
||||||
|
#endif // COLLECTIONDIRECTORY_H
|
||||||
@@ -29,8 +29,8 @@
|
|||||||
#include "core/filesystemmusicstorage.h"
|
#include "core/filesystemmusicstorage.h"
|
||||||
#include "core/iconloader.h"
|
#include "core/iconloader.h"
|
||||||
#include "core/musicstorage.h"
|
#include "core/musicstorage.h"
|
||||||
#include "core/utilities.h"
|
#include "utilities/diskutils.h"
|
||||||
#include "directory.h"
|
#include "collectiondirectory.h"
|
||||||
#include "collectionbackend.h"
|
#include "collectionbackend.h"
|
||||||
#include "collectiondirectorymodel.h"
|
#include "collectiondirectorymodel.h"
|
||||||
|
|
||||||
@@ -46,7 +46,7 @@ CollectionDirectoryModel::CollectionDirectoryModel(CollectionBackend *backend, Q
|
|||||||
|
|
||||||
CollectionDirectoryModel::~CollectionDirectoryModel() = default;
|
CollectionDirectoryModel::~CollectionDirectoryModel() = default;
|
||||||
|
|
||||||
void CollectionDirectoryModel::DirectoryDiscovered(const Directory &dir) {
|
void CollectionDirectoryModel::DirectoryDiscovered(const CollectionDirectory &dir) {
|
||||||
|
|
||||||
QStandardItem *item = new QStandardItem(dir.path);
|
QStandardItem *item = new QStandardItem(dir.path);
|
||||||
item->setData(dir.id, kIdRole);
|
item->setData(dir.id, kIdRole);
|
||||||
@@ -56,7 +56,7 @@ void CollectionDirectoryModel::DirectoryDiscovered(const Directory &dir) {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void CollectionDirectoryModel::DirectoryDeleted(const Directory &dir) {
|
void CollectionDirectoryModel::DirectoryDeleted(const CollectionDirectory &dir) {
|
||||||
|
|
||||||
for (int i = 0; i < rowCount(); ++i) {
|
for (int i = 0; i < rowCount(); ++i) {
|
||||||
if (item(i, 0)->data(kIdRole).toInt() == dir.id) {
|
if (item(i, 0)->data(kIdRole).toInt() == dir.id) {
|
||||||
@@ -80,7 +80,7 @@ void CollectionDirectoryModel::RemoveDirectory(const QModelIndex &idx) {
|
|||||||
|
|
||||||
if (!backend_ || !idx.isValid()) return;
|
if (!backend_ || !idx.isValid()) return;
|
||||||
|
|
||||||
Directory dir;
|
CollectionDirectory dir;
|
||||||
dir.path = idx.data().toString();
|
dir.path = idx.data().toString();
|
||||||
dir.id = idx.data(kIdRole).toInt();
|
dir.id = idx.data(kIdRole).toInt();
|
||||||
|
|
||||||
|
|||||||
@@ -34,7 +34,7 @@
|
|||||||
|
|
||||||
class QModelIndex;
|
class QModelIndex;
|
||||||
|
|
||||||
struct Directory;
|
struct CollectionDirectory;
|
||||||
class CollectionBackend;
|
class CollectionBackend;
|
||||||
class MusicStorage;
|
class MusicStorage;
|
||||||
|
|
||||||
@@ -53,8 +53,8 @@ class CollectionDirectoryModel : public QStandardItemModel {
|
|||||||
|
|
||||||
private slots:
|
private slots:
|
||||||
// To be called by the backend
|
// To be called by the backend
|
||||||
void DirectoryDiscovered(const Directory &directories);
|
void DirectoryDiscovered(const CollectionDirectory &directories);
|
||||||
void DirectoryDeleted(const Directory &directories);
|
void DirectoryDeleted(const CollectionDirectory &directories);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
static const int kIdRole = Qt::UserRole + 1;
|
static const int kIdRole = Qt::UserRole + 1;
|
||||||
|
|||||||
42
src/collection/collectionfilteroptions.cpp
Normal file
42
src/collection/collectionfilteroptions.cpp
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
/*
|
||||||
|
* Strawberry Music Player
|
||||||
|
* Copyright 2023, Jonas Kvinge <jonas@jkvinge.net>
|
||||||
|
*
|
||||||
|
* Strawberry is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* Strawberry is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with Strawberry. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <QtGlobal>
|
||||||
|
#include <QDateTime>
|
||||||
|
|
||||||
|
#include "core/song.h"
|
||||||
|
|
||||||
|
#include "collectionfilteroptions.h"
|
||||||
|
|
||||||
|
CollectionFilterOptions::CollectionFilterOptions() : filter_mode_(FilterMode::All), max_age_(-1) {}
|
||||||
|
|
||||||
|
bool CollectionFilterOptions::Matches(const Song &song) const {
|
||||||
|
|
||||||
|
if (max_age_ != -1) {
|
||||||
|
const qint64 cutoff = QDateTime::currentDateTime().toSecsSinceEpoch() - max_age_;
|
||||||
|
if (song.ctime() <= cutoff) return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!filter_text_.isNull()) {
|
||||||
|
return song.albumartist().contains(filter_text_, Qt::CaseInsensitive) || song.artist().contains(filter_text_, Qt::CaseInsensitive) || song.album().contains(filter_text_, Qt::CaseInsensitive) || song.title().contains(filter_text_, Qt::CaseInsensitive);
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
|
||||||
|
}
|
||||||
65
src/collection/collectionfilteroptions.h
Normal file
65
src/collection/collectionfilteroptions.h
Normal file
@@ -0,0 +1,65 @@
|
|||||||
|
/*
|
||||||
|
* Strawberry Music Player
|
||||||
|
* Copyright 2023, Jonas Kvinge <jonas@jkvinge.net>
|
||||||
|
*
|
||||||
|
* Strawberry is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* Strawberry is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with Strawberry. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef COLLECTIONFILTEROPTIONS_H
|
||||||
|
#define COLLECTIONFILTEROPTIONS_H
|
||||||
|
|
||||||
|
#include <QString>
|
||||||
|
|
||||||
|
#include "core/song.h"
|
||||||
|
|
||||||
|
class CollectionFilterOptions {
|
||||||
|
public:
|
||||||
|
|
||||||
|
explicit CollectionFilterOptions();
|
||||||
|
|
||||||
|
// Filter mode:
|
||||||
|
// - use the all songs table
|
||||||
|
// - use the duplicated songs view; by duplicated we mean those songs for which the (artist, album, title) tuple is found more than once in the songs table
|
||||||
|
// - use the untagged songs view; by untagged we mean those for which at least one of the (artist, album, title) tags is empty
|
||||||
|
// Please note that additional filtering based on FTS table (the filter attribute) won't work in Duplicates and Untagged modes.
|
||||||
|
enum class FilterMode {
|
||||||
|
All,
|
||||||
|
Duplicates,
|
||||||
|
Untagged
|
||||||
|
};
|
||||||
|
|
||||||
|
FilterMode filter_mode() const { return filter_mode_; }
|
||||||
|
int max_age() const { return max_age_; }
|
||||||
|
QString filter_text() const { return filter_text_; }
|
||||||
|
|
||||||
|
void set_filter_mode(const FilterMode filter_mode) {
|
||||||
|
filter_mode_ = filter_mode;
|
||||||
|
filter_text_.clear();
|
||||||
|
}
|
||||||
|
void set_max_age(const int max_age) { max_age_ = max_age; }
|
||||||
|
void set_filter_text(const QString &filter_text) {
|
||||||
|
filter_mode_ = FilterMode::All;
|
||||||
|
filter_text_ = filter_text;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Matches(const Song &song) const;
|
||||||
|
|
||||||
|
private:
|
||||||
|
FilterMode filter_mode_;
|
||||||
|
int max_age_;
|
||||||
|
QString filter_text_;
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif // COLLECTIONFILTEROPTIONS_H
|
||||||
@@ -46,6 +46,7 @@
|
|||||||
#include "core/iconloader.h"
|
#include "core/iconloader.h"
|
||||||
#include "core/song.h"
|
#include "core/song.h"
|
||||||
#include "core/logging.h"
|
#include "core/logging.h"
|
||||||
|
#include "collectionfilteroptions.h"
|
||||||
#include "collectionmodel.h"
|
#include "collectionmodel.h"
|
||||||
#include "collectionquery.h"
|
#include "collectionquery.h"
|
||||||
#include "savedgroupingmanager.h"
|
#include "savedgroupingmanager.h"
|
||||||
@@ -53,6 +54,7 @@
|
|||||||
#include "groupbydialog.h"
|
#include "groupbydialog.h"
|
||||||
#include "ui_collectionfilterwidget.h"
|
#include "ui_collectionfilterwidget.h"
|
||||||
#include "widgets/qsearchfield.h"
|
#include "widgets/qsearchfield.h"
|
||||||
|
#include "settings/collectionsettingspage.h"
|
||||||
#include "settings/appearancesettingspage.h"
|
#include "settings/appearancesettingspage.h"
|
||||||
|
|
||||||
CollectionFilterWidget::CollectionFilterWidget(QWidget *parent)
|
CollectionFilterWidget::CollectionFilterWidget(QWidget *parent)
|
||||||
@@ -67,7 +69,7 @@ CollectionFilterWidget::CollectionFilterWidget(QWidget *parent)
|
|||||||
group_by_group_(nullptr),
|
group_by_group_(nullptr),
|
||||||
filter_delay_(new QTimer(this)),
|
filter_delay_(new QTimer(this)),
|
||||||
filter_applies_to_model_(true),
|
filter_applies_to_model_(true),
|
||||||
delay_behaviour_(DelayedOnLargeLibraries) {
|
delay_behaviour_(DelayBehaviour::DelayedOnLargeLibraries) {
|
||||||
|
|
||||||
ui_->setupUi(this);
|
ui_->setupUi(this);
|
||||||
|
|
||||||
@@ -177,12 +179,13 @@ void CollectionFilterWidget::Init(CollectionModel *model) {
|
|||||||
if (s.contains(group_by_version())) version = s.value(group_by_version(), 0).toInt();
|
if (s.contains(group_by_version())) version = s.value(group_by_version(), 0).toInt();
|
||||||
if (version == 1) {
|
if (version == 1) {
|
||||||
model_->SetGroupBy(CollectionModel::Grouping(
|
model_->SetGroupBy(CollectionModel::Grouping(
|
||||||
CollectionModel::GroupBy(s.value(group_by_key(1), static_cast<int>(CollectionModel::GroupBy_AlbumArtist)).toInt()),
|
static_cast<CollectionModel::GroupBy>(s.value(group_by_key(1), static_cast<int>(CollectionModel::GroupBy::AlbumArtist)).toInt()),
|
||||||
CollectionModel::GroupBy(s.value(group_by_key(2), static_cast<int>(CollectionModel::GroupBy_AlbumDisc)).toInt()),
|
static_cast<CollectionModel::GroupBy>(s.value(group_by_key(2), static_cast<int>(CollectionModel::GroupBy::AlbumDisc)).toInt()),
|
||||||
CollectionModel::GroupBy(s.value(group_by_key(3), static_cast<int>(CollectionModel::GroupBy_None)).toInt())), s.value(separate_albums_by_grouping_key(), false).toBool());
|
static_cast<CollectionModel::GroupBy>(s.value(group_by_key(3), static_cast<int>(CollectionModel::GroupBy::None)).toInt())),
|
||||||
|
s.value(separate_albums_by_grouping_key(), false).toBool());
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
model_->SetGroupBy(CollectionModel::Grouping(CollectionModel::GroupBy_AlbumArtist, CollectionModel::GroupBy_AlbumDisc, CollectionModel::GroupBy_None), false);
|
model_->SetGroupBy(CollectionModel::Grouping(CollectionModel::GroupBy::AlbumArtist, CollectionModel::GroupBy::AlbumDisc, CollectionModel::GroupBy::None), false);
|
||||||
}
|
}
|
||||||
s.endGroup();
|
s.endGroup();
|
||||||
}
|
}
|
||||||
@@ -271,24 +274,24 @@ QActionGroup *CollectionFilterWidget::CreateGroupByActions(const QString &saved_
|
|||||||
|
|
||||||
QActionGroup *ret = new QActionGroup(parent);
|
QActionGroup *ret = new QActionGroup(parent);
|
||||||
|
|
||||||
ret->addAction(CreateGroupByAction(tr("Group by Album artist/Album"), parent, CollectionModel::Grouping(CollectionModel::GroupBy_AlbumArtist, CollectionModel::GroupBy_Album)));
|
ret->addAction(CreateGroupByAction(tr("Group by Album artist/Album"), parent, CollectionModel::Grouping(CollectionModel::GroupBy::AlbumArtist, CollectionModel::GroupBy::Album)));
|
||||||
ret->addAction(CreateGroupByAction(tr("Group by Album artist/Album - Disc"), parent, CollectionModel::Grouping(CollectionModel::GroupBy_AlbumArtist, CollectionModel::GroupBy_AlbumDisc)));
|
ret->addAction(CreateGroupByAction(tr("Group by Album artist/Album - Disc"), parent, CollectionModel::Grouping(CollectionModel::GroupBy::AlbumArtist, CollectionModel::GroupBy::AlbumDisc)));
|
||||||
ret->addAction(CreateGroupByAction(tr("Group by Album artist/Year - Album"), parent, CollectionModel::Grouping(CollectionModel::GroupBy_AlbumArtist, CollectionModel::GroupBy_YearAlbum)));
|
ret->addAction(CreateGroupByAction(tr("Group by Album artist/Year - Album"), parent, CollectionModel::Grouping(CollectionModel::GroupBy::AlbumArtist, CollectionModel::GroupBy::YearAlbum)));
|
||||||
ret->addAction(CreateGroupByAction(tr("Group by Album artist/Year - Album - Disc"), parent, CollectionModel::Grouping(CollectionModel::GroupBy_AlbumArtist, CollectionModel::GroupBy_YearAlbumDisc)));
|
ret->addAction(CreateGroupByAction(tr("Group by Album artist/Year - Album - Disc"), parent, CollectionModel::Grouping(CollectionModel::GroupBy::AlbumArtist, CollectionModel::GroupBy::YearAlbumDisc)));
|
||||||
|
|
||||||
ret->addAction(CreateGroupByAction(tr("Group by Artist/Album"), parent, CollectionModel::Grouping(CollectionModel::GroupBy_Artist, CollectionModel::GroupBy_Album)));
|
ret->addAction(CreateGroupByAction(tr("Group by Artist/Album"), parent, CollectionModel::Grouping(CollectionModel::GroupBy::Artist, CollectionModel::GroupBy::Album)));
|
||||||
ret->addAction(CreateGroupByAction(tr("Group by Artist/Album - Disc"), parent, CollectionModel::Grouping(CollectionModel::GroupBy_Artist, CollectionModel::GroupBy_AlbumDisc)));
|
ret->addAction(CreateGroupByAction(tr("Group by Artist/Album - Disc"), parent, CollectionModel::Grouping(CollectionModel::GroupBy::Artist, CollectionModel::GroupBy::AlbumDisc)));
|
||||||
ret->addAction(CreateGroupByAction(tr("Group by Artist/Year - Album"), parent, CollectionModel::Grouping(CollectionModel::GroupBy_Artist, CollectionModel::GroupBy_YearAlbum)));
|
ret->addAction(CreateGroupByAction(tr("Group by Artist/Year - Album"), parent, CollectionModel::Grouping(CollectionModel::GroupBy::Artist, CollectionModel::GroupBy::YearAlbum)));
|
||||||
ret->addAction(CreateGroupByAction(tr("Group by Artist/Year - Album - Disc"), parent, CollectionModel::Grouping(CollectionModel::GroupBy_Artist, CollectionModel::GroupBy_YearAlbumDisc)));
|
ret->addAction(CreateGroupByAction(tr("Group by Artist/Year - Album - Disc"), parent, CollectionModel::Grouping(CollectionModel::GroupBy::Artist, CollectionModel::GroupBy::YearAlbumDisc)));
|
||||||
|
|
||||||
ret->addAction(CreateGroupByAction(tr("Group by Genre/Album artist/Album"), parent, CollectionModel::Grouping(CollectionModel::GroupBy_Genre, CollectionModel::GroupBy_AlbumArtist, CollectionModel::GroupBy_Album)));
|
ret->addAction(CreateGroupByAction(tr("Group by Genre/Album artist/Album"), parent, CollectionModel::Grouping(CollectionModel::GroupBy::Genre, CollectionModel::GroupBy::AlbumArtist, CollectionModel::GroupBy::Album)));
|
||||||
ret->addAction(CreateGroupByAction(tr("Group by Genre/Artist/Album"), parent, CollectionModel::Grouping(CollectionModel::GroupBy_Genre, CollectionModel::GroupBy_Artist, CollectionModel::GroupBy_Album)));
|
ret->addAction(CreateGroupByAction(tr("Group by Genre/Artist/Album"), parent, CollectionModel::Grouping(CollectionModel::GroupBy::Genre, CollectionModel::GroupBy::Artist, CollectionModel::GroupBy::Album)));
|
||||||
|
|
||||||
ret->addAction(CreateGroupByAction(tr("Group by Album Artist"), parent, CollectionModel::Grouping(CollectionModel::GroupBy_AlbumArtist)));
|
ret->addAction(CreateGroupByAction(tr("Group by Album Artist"), parent, CollectionModel::Grouping(CollectionModel::GroupBy::AlbumArtist)));
|
||||||
ret->addAction(CreateGroupByAction(tr("Group by Artist"), parent, CollectionModel::Grouping(CollectionModel::GroupBy_Artist)));
|
ret->addAction(CreateGroupByAction(tr("Group by Artist"), parent, CollectionModel::Grouping(CollectionModel::GroupBy::Artist)));
|
||||||
|
|
||||||
ret->addAction(CreateGroupByAction(tr("Group by Album"), parent, CollectionModel::Grouping(CollectionModel::GroupBy_Album)));
|
ret->addAction(CreateGroupByAction(tr("Group by Album"), parent, CollectionModel::Grouping(CollectionModel::GroupBy::Album)));
|
||||||
ret->addAction(CreateGroupByAction(tr("Group by Genre/Album"), parent, CollectionModel::Grouping(CollectionModel::GroupBy_Genre, CollectionModel::GroupBy_Album)));
|
ret->addAction(CreateGroupByAction(tr("Group by Genre/Album"), parent, CollectionModel::Grouping(CollectionModel::GroupBy::Genre, CollectionModel::GroupBy::Album)));
|
||||||
|
|
||||||
QAction *sep1 = new QAction(parent);
|
QAction *sep1 = new QAction(parent);
|
||||||
sep1->setSeparator(true);
|
sep1->setSeparator(true);
|
||||||
@@ -333,7 +336,7 @@ QAction *CollectionFilterWidget::CreateGroupByAction(const QString &text, QObjec
|
|||||||
QAction *ret = new QAction(text, parent);
|
QAction *ret = new QAction(text, parent);
|
||||||
ret->setCheckable(true);
|
ret->setCheckable(true);
|
||||||
|
|
||||||
if (grouping.first != CollectionModel::GroupBy_None) {
|
if (grouping.first != CollectionModel::GroupBy::None) {
|
||||||
ret->setProperty("group_by", QVariant::fromValue(grouping));
|
ret->setProperty("group_by", QVariant::fromValue(grouping));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -455,12 +458,12 @@ void CollectionFilterWidget::SetFilterHint(const QString &hint) {
|
|||||||
ui_->search_field->setPlaceholderText(hint);
|
ui_->search_field->setPlaceholderText(hint);
|
||||||
}
|
}
|
||||||
|
|
||||||
void CollectionFilterWidget::SetQueryMode(QueryOptions::QueryMode query_mode) {
|
void CollectionFilterWidget::SetFilterMode(CollectionFilterOptions::FilterMode filter_mode) {
|
||||||
|
|
||||||
ui_->search_field->clear();
|
ui_->search_field->clear();
|
||||||
ui_->search_field->setEnabled(query_mode == QueryOptions::QueryMode_All);
|
ui_->search_field->setEnabled(filter_mode == CollectionFilterOptions::FilterMode::All);
|
||||||
|
|
||||||
model_->SetFilterQueryMode(query_mode);
|
model_->SetFilterMode(filter_mode);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -508,7 +511,7 @@ void CollectionFilterWidget::FilterTextChanged(const QString &text) {
|
|||||||
// Searching with one or two characters can be very expensive on the database even with FTS,
|
// Searching with one or two characters can be very expensive on the database even with FTS,
|
||||||
// so if there are a large number of songs in the database introduce a small delay before actually filtering the model,
|
// so if there are a large number of songs in the database introduce a small delay before actually filtering the model,
|
||||||
// so if the user is typing the first few characters of something it will be quicker.
|
// so if the user is typing the first few characters of something it will be quicker.
|
||||||
const bool delay = (delay_behaviour_ == AlwaysDelayed) || (delay_behaviour_ == DelayedOnLargeLibraries && !text.isEmpty() && text.length() < 3 && model_->total_song_count() >= 100000);
|
const bool delay = (delay_behaviour_ == DelayBehaviour::AlwaysDelayed) || (delay_behaviour_ == DelayBehaviour::DelayedOnLargeLibraries && !text.isEmpty() && text.length() < 3 && model_->total_song_count() >= 100000);
|
||||||
|
|
||||||
if (delay) {
|
if (delay) {
|
||||||
filter_delay_->start();
|
filter_delay_->start();
|
||||||
|
|||||||
@@ -32,6 +32,7 @@
|
|||||||
#include <QString>
|
#include <QString>
|
||||||
|
|
||||||
#include "collectionquery.h"
|
#include "collectionquery.h"
|
||||||
|
#include "collectionqueryoptions.h"
|
||||||
#include "collectionmodel.h"
|
#include "collectionmodel.h"
|
||||||
|
|
||||||
class QTimer;
|
class QTimer;
|
||||||
@@ -53,7 +54,7 @@ class CollectionFilterWidget : public QWidget {
|
|||||||
|
|
||||||
static const int kFilterDelay = 500; // msec
|
static const int kFilterDelay = 500; // msec
|
||||||
|
|
||||||
enum DelayBehaviour {
|
enum class DelayBehaviour {
|
||||||
AlwaysInstant,
|
AlwaysInstant,
|
||||||
DelayedOnLargeLibraries,
|
DelayedOnLargeLibraries,
|
||||||
AlwaysDelayed,
|
AlwaysDelayed,
|
||||||
@@ -88,7 +89,7 @@ class CollectionFilterWidget : public QWidget {
|
|||||||
|
|
||||||
public slots:
|
public slots:
|
||||||
void UpdateGroupByActions();
|
void UpdateGroupByActions();
|
||||||
void SetQueryMode(QueryOptions::QueryMode query_mode);
|
void SetFilterMode(CollectionFilterOptions::FilterMode filter_mode);
|
||||||
void FocusOnFilter(QKeyEvent *e);
|
void FocusOnFilter(QKeyEvent *e);
|
||||||
|
|
||||||
signals:
|
signals:
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -49,7 +49,9 @@
|
|||||||
#include "core/song.h"
|
#include "core/song.h"
|
||||||
#include "core/sqlrow.h"
|
#include "core/sqlrow.h"
|
||||||
#include "covermanager/albumcoverloader.h"
|
#include "covermanager/albumcoverloader.h"
|
||||||
|
#include "collectionfilteroptions.h"
|
||||||
#include "collectionquery.h"
|
#include "collectionquery.h"
|
||||||
|
#include "collectionqueryoptions.h"
|
||||||
#include "collectionitem.h"
|
#include "collectionitem.h"
|
||||||
#include "covermanager/albumcoverloaderoptions.h"
|
#include "covermanager/albumcoverloaderoptions.h"
|
||||||
|
|
||||||
@@ -81,34 +83,34 @@ class CollectionModel : public SimpleTreeModel<CollectionItem> {
|
|||||||
};
|
};
|
||||||
|
|
||||||
// These values get saved in QSettings - don't change them
|
// These values get saved in QSettings - don't change them
|
||||||
enum GroupBy {
|
enum class GroupBy {
|
||||||
GroupBy_None = 0,
|
None = 0,
|
||||||
GroupBy_AlbumArtist = 1,
|
AlbumArtist = 1,
|
||||||
GroupBy_Artist = 2,
|
Artist = 2,
|
||||||
GroupBy_Album = 3,
|
Album = 3,
|
||||||
GroupBy_AlbumDisc = 4,
|
AlbumDisc = 4,
|
||||||
GroupBy_YearAlbum = 5,
|
YearAlbum = 5,
|
||||||
GroupBy_YearAlbumDisc = 6,
|
YearAlbumDisc = 6,
|
||||||
GroupBy_OriginalYearAlbum = 7,
|
OriginalYearAlbum = 7,
|
||||||
GroupBy_OriginalYearAlbumDisc = 8,
|
OriginalYearAlbumDisc = 8,
|
||||||
GroupBy_Disc = 9,
|
Disc = 9,
|
||||||
GroupBy_Year = 10,
|
Year = 10,
|
||||||
GroupBy_OriginalYear = 11,
|
OriginalYear = 11,
|
||||||
GroupBy_Genre = 12,
|
Genre = 12,
|
||||||
GroupBy_Composer = 13,
|
Composer = 13,
|
||||||
GroupBy_Performer = 14,
|
Performer = 14,
|
||||||
GroupBy_Grouping = 15,
|
Grouping = 15,
|
||||||
GroupBy_FileType = 16,
|
FileType = 16,
|
||||||
GroupBy_Format = 17,
|
Format = 17,
|
||||||
GroupBy_Samplerate = 18,
|
Samplerate = 18,
|
||||||
GroupBy_Bitdepth = 19,
|
Bitdepth = 19,
|
||||||
GroupBy_Bitrate = 20,
|
Bitrate = 20,
|
||||||
GroupByCount = 21,
|
GroupByCount = 21,
|
||||||
};
|
};
|
||||||
Q_ENUM(GroupBy)
|
Q_ENUM(GroupBy)
|
||||||
|
|
||||||
struct Grouping {
|
struct Grouping {
|
||||||
explicit Grouping(GroupBy f = GroupBy_None, GroupBy s = GroupBy_None, GroupBy t = GroupBy_None)
|
explicit Grouping(GroupBy f = GroupBy::None, GroupBy s = GroupBy::None, GroupBy t = GroupBy::None)
|
||||||
: first(f), second(s), third(t) {}
|
: first(f), second(s), third(t) {}
|
||||||
|
|
||||||
GroupBy first;
|
GroupBy first;
|
||||||
@@ -179,9 +181,9 @@ class CollectionModel : public SimpleTreeModel<CollectionItem> {
|
|||||||
quint64 icon_cache_disk_size() { return sIconCache->cacheSize(); }
|
quint64 icon_cache_disk_size() { return sIconCache->cacheSize(); }
|
||||||
|
|
||||||
static bool IsArtistGroupBy(const GroupBy group_by) {
|
static bool IsArtistGroupBy(const GroupBy group_by) {
|
||||||
return group_by == CollectionModel::GroupBy_Artist || group_by == CollectionModel::GroupBy_AlbumArtist;
|
return group_by == CollectionModel::GroupBy::Artist || group_by == CollectionModel::GroupBy::AlbumArtist;
|
||||||
}
|
}
|
||||||
static bool IsAlbumGroupBy(const GroupBy group_by) { return group_by == GroupBy_Album || group_by == GroupBy_YearAlbum || group_by == GroupBy_AlbumDisc || group_by == GroupBy_YearAlbumDisc || group_by == GroupBy_OriginalYearAlbum || group_by == GroupBy_OriginalYearAlbumDisc; }
|
static bool IsAlbumGroupBy(const GroupBy group_by) { return group_by == GroupBy::Album || group_by == GroupBy::YearAlbum || group_by == GroupBy::AlbumDisc || group_by == GroupBy::YearAlbumDisc || group_by == GroupBy::OriginalYearAlbum || group_by == GroupBy::OriginalYearAlbumDisc; }
|
||||||
|
|
||||||
void set_use_lazy_loading(const bool value) { use_lazy_loading_ = value; }
|
void set_use_lazy_loading(const bool value) { use_lazy_loading_ = value; }
|
||||||
|
|
||||||
@@ -203,9 +205,9 @@ class CollectionModel : public SimpleTreeModel<CollectionItem> {
|
|||||||
void GroupingChanged(CollectionModel::Grouping g, bool separate_albums_by_grouping);
|
void GroupingChanged(CollectionModel::Grouping g, bool separate_albums_by_grouping);
|
||||||
|
|
||||||
public slots:
|
public slots:
|
||||||
void SetFilterAge(const int age);
|
void SetFilterMode(CollectionFilterOptions::FilterMode filter_mode);
|
||||||
void SetFilterText(const QString &text);
|
void SetFilterAge(const int filter_age);
|
||||||
void SetFilterQueryMode(QueryOptions::QueryMode query_mode);
|
void SetFilterText(const QString &filter_text);
|
||||||
|
|
||||||
void Init(const bool async = true);
|
void Init(const bool async = true);
|
||||||
void Reset();
|
void Reset();
|
||||||
@@ -232,20 +234,21 @@ class CollectionModel : public SimpleTreeModel<CollectionItem> {
|
|||||||
void AlbumCoverLoaded(const quint64 id, const AlbumCoverLoaderResult &result);
|
void AlbumCoverLoaded(const quint64 id, const AlbumCoverLoaderResult &result);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
// Provides some optimisations for loading the list of items in the root.
|
// Provides some optimizations for loading the list of items in the root.
|
||||||
// This gets called a lot when filtering the playlist, so it's nice to be able to do it in a background thread.
|
// This gets called a lot when filtering the playlist, so it's nice to be able to do it in a background thread.
|
||||||
QueryResult RunQuery(CollectionItem *parent);
|
CollectionQueryOptions PrepareQuery(CollectionItem *parent);
|
||||||
|
QueryResult RunQuery(const CollectionFilterOptions &filter_options = CollectionFilterOptions(), const CollectionQueryOptions &query_options = CollectionQueryOptions());
|
||||||
void PostQuery(CollectionItem *parent, const QueryResult &result, const bool signal);
|
void PostQuery(CollectionItem *parent, const QueryResult &result, const bool signal);
|
||||||
|
|
||||||
bool HasCompilations(const QSqlDatabase &db, const CollectionQuery &query);
|
bool HasCompilations(const QSqlDatabase &db, const CollectionFilterOptions &filter_options, const CollectionQueryOptions &query_options);
|
||||||
|
|
||||||
void BeginReset();
|
void BeginReset();
|
||||||
|
|
||||||
// Functions for working with queries and creating items.
|
// Functions for working with queries and creating items.
|
||||||
// When the model is reset or when a node is lazy-loaded the Collection constructs a database query to populate the items.
|
// When the model is reset or when a node is lazy-loaded the Collection constructs a database query to populate the items.
|
||||||
// Filters are added for each parent item, restricting the songs returned to a particular album or artist for example.
|
// Filters are added for each parent item, restricting the songs returned to a particular album or artist for example.
|
||||||
static void InitQuery(const GroupBy group_by, const bool separate_albums_by_grouping, CollectionQuery *q);
|
static void SetQueryColumnSpec(const GroupBy group_by, const bool separate_albums_by_grouping, CollectionQueryOptions *query_options);
|
||||||
static void FilterQuery(const GroupBy group_by, const bool separate_albums_by_grouping, CollectionItem *item, CollectionQuery *q);
|
static void AddQueryWhere(const GroupBy group_by, const bool separate_albums_by_grouping, CollectionItem *item, CollectionQueryOptions *query_options);
|
||||||
|
|
||||||
// Items can be created either from a query that's been run to populate a node, or by a spontaneous SongsDiscovered emission from the backend.
|
// Items can be created either from a query that's been run to populate a node, or by a spontaneous SongsDiscovered emission from the backend.
|
||||||
CollectionItem *ItemFromQuery(const GroupBy group_by, const bool separate_albums_by_grouping, const bool signal, const bool create_divider, CollectionItem *parent, const SqlRow &row, const int container_level);
|
CollectionItem *ItemFromQuery(const GroupBy group_by, const bool separate_albums_by_grouping, const bool signal, const bool create_divider, CollectionItem *parent, const SqlRow &row, const int container_level);
|
||||||
@@ -279,7 +282,7 @@ class CollectionModel : public SimpleTreeModel<CollectionItem> {
|
|||||||
int total_artist_count_;
|
int total_artist_count_;
|
||||||
int total_album_count_;
|
int total_album_count_;
|
||||||
|
|
||||||
QueryOptions query_options_;
|
CollectionFilterOptions filter_options_;
|
||||||
Grouping group_by_;
|
Grouping group_by_;
|
||||||
bool separate_albums_by_grouping_;
|
bool separate_albums_by_grouping_;
|
||||||
|
|
||||||
|
|||||||
@@ -29,12 +29,12 @@
|
|||||||
|
|
||||||
class SqlRow;
|
class SqlRow;
|
||||||
|
|
||||||
CollectionPlaylistItem::CollectionPlaylistItem() : PlaylistItem(Song::Source_Collection) {
|
CollectionPlaylistItem::CollectionPlaylistItem() : PlaylistItem(Song::Source::Collection) {
|
||||||
song_.set_source(Song::Source_Collection);
|
song_.set_source(Song::Source::Collection);
|
||||||
}
|
}
|
||||||
|
|
||||||
CollectionPlaylistItem::CollectionPlaylistItem(const Song &song) : PlaylistItem(Song::Source_Collection), song_(song) {
|
CollectionPlaylistItem::CollectionPlaylistItem(const Song &song) : PlaylistItem(Song::Source::Collection), song_(song) {
|
||||||
song_.set_source(Song::Source_Collection);
|
song_.set_source(Song::Source::Collection);
|
||||||
}
|
}
|
||||||
|
|
||||||
QUrl CollectionPlaylistItem::Url() const { return song_.url(); }
|
QUrl CollectionPlaylistItem::Url() const { return song_.url(); }
|
||||||
@@ -50,7 +50,7 @@ bool CollectionPlaylistItem::InitFromQuery(const SqlRow &query) {
|
|||||||
|
|
||||||
// Rows from the songs tables come first
|
// Rows from the songs tables come first
|
||||||
song_.InitFromQuery(query, true);
|
song_.InitFromQuery(query, true);
|
||||||
song_.set_source(Song::Source_Collection);
|
song_.set_source(Song::Source::Collection);
|
||||||
return song_.is_valid();
|
return song_.is_valid();
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -52,7 +52,7 @@ class CollectionPlaylistItem : public PlaylistItem {
|
|||||||
|
|
||||||
protected:
|
protected:
|
||||||
QVariant DatabaseValue(DatabaseColumn column) const override;
|
QVariant DatabaseValue(DatabaseColumn column) const override;
|
||||||
Song DatabaseSongMetadata() const override { return Song(Song::Source_Collection); }
|
Song DatabaseSongMetadata() const override { return Song(Song::Source::Collection); }
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
Song song_;
|
Song song_;
|
||||||
|
|||||||
@@ -31,16 +31,16 @@
|
|||||||
#include <QRegularExpression>
|
#include <QRegularExpression>
|
||||||
#include <QSqlDatabase>
|
#include <QSqlDatabase>
|
||||||
#include <QSqlQuery>
|
#include <QSqlQuery>
|
||||||
#include <QSqlError>
|
|
||||||
|
|
||||||
#include "core/logging.h"
|
#include "core/logging.h"
|
||||||
|
#include "core/sqlquery.h"
|
||||||
#include "core/song.h"
|
#include "core/song.h"
|
||||||
|
|
||||||
#include "collectionquery.h"
|
#include "collectionquery.h"
|
||||||
|
#include "collectionfilteroptions.h"
|
||||||
|
#include "collectionqueryoptions.h"
|
||||||
|
|
||||||
QueryOptions::QueryOptions() : max_age_(-1), query_mode_(QueryMode_All) {}
|
CollectionQuery::CollectionQuery(const QSqlDatabase &db, const QString &songs_table, const QString &fts_table, const CollectionFilterOptions &filter_options)
|
||||||
|
|
||||||
CollectionQuery::CollectionQuery(const QSqlDatabase &db, const QString &songs_table, const QString &fts_table, const QueryOptions &options)
|
|
||||||
: QSqlQuery(db),
|
: QSqlQuery(db),
|
||||||
songs_table_(songs_table),
|
songs_table_(songs_table),
|
||||||
fts_table_(fts_table),
|
fts_table_(fts_table),
|
||||||
@@ -49,7 +49,7 @@ CollectionQuery::CollectionQuery(const QSqlDatabase &db, const QString &songs_ta
|
|||||||
duplicates_only_(false),
|
duplicates_only_(false),
|
||||||
limit_(-1) {
|
limit_(-1) {
|
||||||
|
|
||||||
if (!options.filter().isEmpty()) {
|
if (!filter_options.filter_text().isEmpty()) {
|
||||||
// We need to munge the filter text a little bit to get it to work as expected with sqlite's FTS5:
|
// We need to munge the filter text a little bit to get it to work as expected with sqlite's FTS5:
|
||||||
// 1) Append * to all tokens.
|
// 1) Append * to all tokens.
|
||||||
// 2) Prefix "fts" to column names.
|
// 2) Prefix "fts" to column names.
|
||||||
@@ -57,9 +57,9 @@ CollectionQuery::CollectionQuery(const QSqlDatabase &db, const QString &songs_ta
|
|||||||
|
|
||||||
// Split on whitespace
|
// Split on whitespace
|
||||||
#if QT_VERSION >= QT_VERSION_CHECK(5, 14, 0)
|
#if QT_VERSION >= QT_VERSION_CHECK(5, 14, 0)
|
||||||
QStringList tokens(options.filter().split(QRegularExpression("\\s+"), Qt::SkipEmptyParts));
|
QStringList tokens(filter_options.filter_text().split(QRegularExpression("\\s+"), Qt::SkipEmptyParts));
|
||||||
#else
|
#else
|
||||||
QStringList tokens(options.filter().split(QRegularExpression("\\s+"), QString::SkipEmptyParts));
|
QStringList tokens(filter_options.filter_text().split(QRegularExpression("\\s+"), QString::SkipEmptyParts));
|
||||||
#endif
|
#endif
|
||||||
QString query;
|
QString query;
|
||||||
for (QString token : tokens) {
|
for (QString token : tokens) {
|
||||||
@@ -100,49 +100,40 @@ CollectionQuery::CollectionQuery(const QSqlDatabase &db, const QString &songs_ta
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (options.max_age() != -1) {
|
if (filter_options.max_age() != -1) {
|
||||||
qint64 cutoff = QDateTime::currentDateTime().toSecsSinceEpoch() - options.max_age();
|
qint64 cutoff = QDateTime::currentDateTime().toSecsSinceEpoch() - filter_options.max_age();
|
||||||
|
|
||||||
where_clauses_ << "ctime > ?";
|
where_clauses_ << "ctime > ?";
|
||||||
bound_values_ << cutoff;
|
bound_values_ << cutoff;
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: Currently you cannot use any QueryMode other than All and FTS at the same time.
|
// TODO: Currently you cannot use any FilterMode other than All and FTS at the same time.
|
||||||
// Joining songs, duplicated_songs and songs_fts all together takes a huge amount of time.
|
// Joining songs, duplicated_songs and songs_fts all together takes a huge amount of time.
|
||||||
// The query takes about 20 seconds on my machine then. Why?
|
// The query takes about 20 seconds on my machine then. Why?
|
||||||
// Untagged mode could work with additional filtering but I'm disabling it just to be consistent
|
// Untagged mode could work with additional filtering but I'm disabling it just to be consistent
|
||||||
// this way filtering is available only in the All mode.
|
// this way filtering is available only in the All mode.
|
||||||
// Remember though that when you fix the Duplicates + FTS cooperation, enable the filtering in both Duplicates and Untagged modes.
|
// Remember though that when you fix the Duplicates + FTS cooperation, enable the filtering in both Duplicates and Untagged modes.
|
||||||
duplicates_only_ = options.query_mode() == QueryOptions::QueryMode_Duplicates;
|
duplicates_only_ = filter_options.filter_mode() == CollectionFilterOptions::FilterMode::Duplicates;
|
||||||
|
|
||||||
if (options.query_mode() == QueryOptions::QueryMode_Untagged) {
|
if (filter_options.filter_mode() == CollectionFilterOptions::FilterMode::Untagged) {
|
||||||
where_clauses_ << "(artist = '' OR album = '' OR title ='')";
|
where_clauses_ << "(artist = '' OR album = '' OR title ='')";
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
QString CollectionQuery::GetInnerQuery() const {
|
|
||||||
return duplicates_only_
|
|
||||||
? QString(" INNER JOIN (select * from duplicated_songs) dsongs "
|
|
||||||
"ON (%songs_table.artist = dsongs.dup_artist "
|
|
||||||
"AND %songs_table.album = dsongs.dup_album "
|
|
||||||
"AND %songs_table.title = dsongs.dup_title) ")
|
|
||||||
: QString();
|
|
||||||
}
|
|
||||||
|
|
||||||
void CollectionQuery::AddWhere(const QString &column, const QVariant &value, const QString &op) {
|
void CollectionQuery::AddWhere(const QString &column, const QVariant &value, const QString &op) {
|
||||||
|
|
||||||
// Ignore 'literal' for IN
|
// Ignore 'literal' for IN
|
||||||
if (op.compare("IN", Qt::CaseInsensitive) == 0) {
|
if (op.compare("IN", Qt::CaseInsensitive) == 0) {
|
||||||
QStringList values = value.toStringList();
|
QStringList values = value.toStringList();
|
||||||
QStringList final;
|
QStringList final_values;
|
||||||
final.reserve(values.count());
|
final_values.reserve(values.count());
|
||||||
for (const QString &single_value : values) {
|
for (const QString &single_value : values) {
|
||||||
final.append("?");
|
final_values.append("?");
|
||||||
bound_values_ << single_value;
|
bound_values_ << single_value;
|
||||||
}
|
}
|
||||||
|
|
||||||
where_clauses_ << QString("%1 IN (" + final.join(",") + ")").arg(column);
|
where_clauses_ << QString("%1 IN (" + final_values.join(",") + ")").arg(column);
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
// Do integers inline - sqlite seems to get confused when you pass integers to bound parameters
|
// Do integers inline - sqlite seems to get confused when you pass integers to bound parameters
|
||||||
@@ -187,6 +178,15 @@ void CollectionQuery::AddCompilationRequirement(const bool compilation) {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
QString CollectionQuery::GetInnerQuery() const {
|
||||||
|
return duplicates_only_
|
||||||
|
? QString(" INNER JOIN (select * from duplicated_songs) dsongs "
|
||||||
|
"ON (%songs_table.artist = dsongs.dup_artist "
|
||||||
|
"AND %songs_table.album = dsongs.dup_album "
|
||||||
|
"AND %songs_table.title = dsongs.dup_title) ")
|
||||||
|
: QString();
|
||||||
|
}
|
||||||
|
|
||||||
bool CollectionQuery::Exec() {
|
bool CollectionQuery::Exec() {
|
||||||
|
|
||||||
QString sql;
|
QString sql;
|
||||||
@@ -213,32 +213,17 @@ bool CollectionQuery::Exec() {
|
|||||||
sql.replace("%fts_table_noprefix", fts_table_.section('.', -1, -1));
|
sql.replace("%fts_table_noprefix", fts_table_.section('.', -1, -1));
|
||||||
sql.replace("%fts_table", fts_table_);
|
sql.replace("%fts_table", fts_table_);
|
||||||
|
|
||||||
prepare(sql);
|
QSqlQuery::prepare(sql);
|
||||||
|
|
||||||
// Bind values
|
// Bind values
|
||||||
for (const QVariant &value : bound_values_) {
|
for (const QVariant &value : bound_values_) {
|
||||||
addBindValue(value);
|
QSqlQuery::addBindValue(value);
|
||||||
}
|
}
|
||||||
|
|
||||||
return exec();
|
return QSqlQuery::exec();
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bool CollectionQuery::Next() { return next(); }
|
bool CollectionQuery::Next() { return QSqlQuery::next(); }
|
||||||
|
|
||||||
QVariant CollectionQuery::Value(const int column) const { return value(column); }
|
QVariant CollectionQuery::Value(const int column) const { return QSqlQuery::value(column); }
|
||||||
|
|
||||||
bool QueryOptions::Matches(const Song &song) const {
|
|
||||||
|
|
||||||
if (max_age_ != -1) {
|
|
||||||
const qint64 cutoff = QDateTime::currentDateTime().toSecsSinceEpoch() - max_age_;
|
|
||||||
if (song.ctime() <= cutoff) return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!filter_.isNull()) {
|
|
||||||
return song.artist().contains(filter_, Qt::CaseInsensitive) || song.album().contains(filter_, Qt::CaseInsensitive) || song.title().contains(filter_, Qt::CaseInsensitive);
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -28,75 +28,23 @@
|
|||||||
#include <QVariant>
|
#include <QVariant>
|
||||||
#include <QString>
|
#include <QString>
|
||||||
#include <QStringList>
|
#include <QStringList>
|
||||||
#include <QMap>
|
|
||||||
#include <QSqlDatabase>
|
#include <QSqlDatabase>
|
||||||
#include <QSqlQuery>
|
#include <QSqlQuery>
|
||||||
|
|
||||||
class Song;
|
#include "collectionfilteroptions.h"
|
||||||
|
#include "collectionqueryoptions.h"
|
||||||
// This structure let's you customize behaviour of any CollectionQuery.
|
|
||||||
struct QueryOptions {
|
|
||||||
// Modes of CollectionQuery:
|
|
||||||
// - use the all songs table
|
|
||||||
// - use the duplicated songs view; by duplicated we mean those songs for which the (artist, album, title) tuple is found more than once in the songs table
|
|
||||||
// - use the untagged songs view; by untagged we mean those for which at least one of the (artist, album, title) tags is empty
|
|
||||||
// Please note that additional filtering based on FTS table (the filter attribute) won't work in Duplicates and Untagged modes.
|
|
||||||
enum QueryMode {
|
|
||||||
QueryMode_All,
|
|
||||||
QueryMode_Duplicates,
|
|
||||||
QueryMode_Untagged
|
|
||||||
};
|
|
||||||
|
|
||||||
QueryOptions();
|
|
||||||
|
|
||||||
bool Matches(const Song &song) const;
|
|
||||||
|
|
||||||
QString filter() const { return filter_; }
|
|
||||||
void set_filter(const QString &filter) {
|
|
||||||
filter_ = filter;
|
|
||||||
query_mode_ = QueryMode_All;
|
|
||||||
}
|
|
||||||
|
|
||||||
int max_age() const { return max_age_; }
|
|
||||||
void set_max_age(int max_age) { max_age_ = max_age; }
|
|
||||||
|
|
||||||
QueryMode query_mode() const { return query_mode_; }
|
|
||||||
void set_query_mode(QueryMode query_mode) {
|
|
||||||
query_mode_ = query_mode;
|
|
||||||
filter_ = QString();
|
|
||||||
}
|
|
||||||
|
|
||||||
private:
|
|
||||||
QString filter_;
|
|
||||||
int max_age_;
|
|
||||||
QueryMode query_mode_;
|
|
||||||
};
|
|
||||||
|
|
||||||
class CollectionQuery : public QSqlQuery {
|
class CollectionQuery : public QSqlQuery {
|
||||||
public:
|
public:
|
||||||
explicit CollectionQuery(const QSqlDatabase &db, const QString &songs_table, const QString &fts_table, const QueryOptions &options = QueryOptions());
|
explicit CollectionQuery(const QSqlDatabase &db, const QString &songs_table, const QString &fts_table, const CollectionFilterOptions &filter_options = CollectionFilterOptions());
|
||||||
|
|
||||||
// Sets contents of SELECT clause on the query (list of columns to get).
|
QVariant Value(const int column) const;
|
||||||
void SetColumnSpec(const QString &spec) { column_spec_ = spec; }
|
QVariant value(const int column) const { return Value(column); }
|
||||||
|
|
||||||
// Sets an ORDER BY clause on the query.
|
|
||||||
void SetOrderBy(const QString &order_by) { order_by_ = order_by; }
|
|
||||||
|
|
||||||
// Adds a fragment of WHERE clause. When executed, this Query will connect all the fragments with AND operator.
|
|
||||||
// Please note that IN operator expects a QStringList as value.
|
|
||||||
void AddWhere(const QString &column, const QVariant &value, const QString &op = "=");
|
|
||||||
void AddWhereArtist(const QVariant &value);
|
|
||||||
|
|
||||||
void SetWhereClauses(const QStringList &where_clauses) { where_clauses_ = where_clauses; }
|
|
||||||
void SetBoundValues(const QVariantList &bound_values) { bound_values_ = bound_values; }
|
|
||||||
void SetDuplicatesOnly(const bool duplicates_only) { duplicates_only_ = duplicates_only; }
|
|
||||||
void SetIncludeUnavailable(const bool include_unavailable) { include_unavailable_ = include_unavailable; }
|
|
||||||
void SetLimit(const int limit) { limit_ = limit; }
|
|
||||||
void AddCompilationRequirement(const bool compilation);
|
|
||||||
|
|
||||||
bool Exec();
|
bool Exec();
|
||||||
|
bool exec() { return QSqlQuery::exec(); }
|
||||||
|
|
||||||
bool Next();
|
bool Next();
|
||||||
QVariant Value(const int column) const;
|
|
||||||
|
|
||||||
QString column_spec() const { return column_spec_; }
|
QString column_spec() const { return column_spec_; }
|
||||||
QString order_by() const { return order_by_; }
|
QString order_by() const { return order_by_; }
|
||||||
@@ -107,6 +55,24 @@ class CollectionQuery : public QSqlQuery {
|
|||||||
bool duplicates_only() const { return duplicates_only_; }
|
bool duplicates_only() const { return duplicates_only_; }
|
||||||
int limit() const { return limit_; }
|
int limit() const { return limit_; }
|
||||||
|
|
||||||
|
// Sets contents of SELECT clause on the query (list of columns to get).
|
||||||
|
void SetColumnSpec(const QString &column_spec) { column_spec_ = column_spec; }
|
||||||
|
|
||||||
|
// Sets an ORDER BY clause on the query.
|
||||||
|
void SetOrderBy(const QString &order_by) { order_by_ = order_by; }
|
||||||
|
|
||||||
|
void SetWhereClauses(const QStringList &where_clauses) { where_clauses_ = where_clauses; }
|
||||||
|
// Adds a fragment of WHERE clause. When executed, this Query will connect all the fragments with AND operator.
|
||||||
|
// Please note that IN operator expects a QStringList as value.
|
||||||
|
void AddWhere(const QString &column, const QVariant &value, const QString &op = "=");
|
||||||
|
void AddWhereArtist(const QVariant &value);
|
||||||
|
|
||||||
|
void SetBoundValues(const QVariantList &bound_values) { bound_values_ = bound_values; }
|
||||||
|
void SetDuplicatesOnly(const bool duplicates_only) { duplicates_only_ = duplicates_only; }
|
||||||
|
void SetIncludeUnavailable(const bool include_unavailable) { include_unavailable_ = include_unavailable; }
|
||||||
|
void SetLimit(const int limit) { limit_ = limit; }
|
||||||
|
void AddCompilationRequirement(const bool compilation);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
QString GetInnerQuery() const;
|
QString GetInnerQuery() const;
|
||||||
|
|
||||||
|
|||||||
34
src/collection/collectionqueryoptions.cpp
Normal file
34
src/collection/collectionqueryoptions.cpp
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
/*
|
||||||
|
* Strawberry Music Player
|
||||||
|
* Copyright 2023, Jonas Kvinge <jonas@jkvinge.net>
|
||||||
|
*
|
||||||
|
* Strawberry is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* Strawberry is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with Strawberry. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <QVariant>
|
||||||
|
#include <QString>
|
||||||
|
|
||||||
|
#include "collectionqueryoptions.h"
|
||||||
|
#include "collectionfilteroptions.h"
|
||||||
|
|
||||||
|
CollectionQueryOptions::CollectionQueryOptions()
|
||||||
|
: compilation_requirement_(CollectionQueryOptions::CompilationRequirement::None),
|
||||||
|
query_have_compilations_(false) {}
|
||||||
|
|
||||||
|
void CollectionQueryOptions::AddWhere(const QString &column, const QVariant &value, const QString &op) {
|
||||||
|
|
||||||
|
where_clauses_ << Where(column, value, op);
|
||||||
|
|
||||||
|
}
|
||||||
63
src/collection/collectionqueryoptions.h
Normal file
63
src/collection/collectionqueryoptions.h
Normal file
@@ -0,0 +1,63 @@
|
|||||||
|
/*
|
||||||
|
* Strawberry Music Player
|
||||||
|
* Copyright 2023, Jonas Kvinge <jonas@jkvinge.net>
|
||||||
|
*
|
||||||
|
* Strawberry is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* Strawberry is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with Strawberry. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef COLLECTIONQUERYOPTIONS_H
|
||||||
|
#define COLLECTIONQUERYOPTIONS_H
|
||||||
|
|
||||||
|
#include <QList>
|
||||||
|
#include <QVariant>
|
||||||
|
#include <QString>
|
||||||
|
|
||||||
|
class CollectionQueryOptions {
|
||||||
|
public:
|
||||||
|
|
||||||
|
explicit CollectionQueryOptions();
|
||||||
|
|
||||||
|
struct Where {
|
||||||
|
explicit Where(const QString _column = QString(), const QVariant _value = QString(), const QString _op = QString()) : column(_column), value(_value), op(_op) {}
|
||||||
|
QString column;
|
||||||
|
QVariant value;
|
||||||
|
QString op;
|
||||||
|
};
|
||||||
|
|
||||||
|
enum class CompilationRequirement {
|
||||||
|
None,
|
||||||
|
On,
|
||||||
|
Off
|
||||||
|
};
|
||||||
|
|
||||||
|
QString column_spec() const { return column_spec_; }
|
||||||
|
CompilationRequirement compilation_requirement() const { return compilation_requirement_; }
|
||||||
|
bool query_have_compilations() const { return query_have_compilations_; }
|
||||||
|
|
||||||
|
void set_column_spec(const QString &column_spec) { column_spec_ = column_spec; }
|
||||||
|
void set_compilation_requirement(const CompilationRequirement compilation_requirement) { compilation_requirement_ = compilation_requirement; }
|
||||||
|
void set_query_have_compilations(const bool query_have_compilations) { query_have_compilations_ = query_have_compilations; }
|
||||||
|
|
||||||
|
QList<Where> where_clauses() const { return where_clauses_; }
|
||||||
|
void AddWhere(const QString &column, const QVariant &value, const QString &op = "=");
|
||||||
|
|
||||||
|
private:
|
||||||
|
QString column_spec_;
|
||||||
|
CompilationRequirement compilation_requirement_;
|
||||||
|
bool query_have_compilations_;
|
||||||
|
QList<Where> where_clauses_;
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif // COLLECTIONQUERYOPTIONS_H
|
||||||
@@ -50,9 +50,9 @@
|
|||||||
#include "core/application.h"
|
#include "core/application.h"
|
||||||
#include "core/iconloader.h"
|
#include "core/iconloader.h"
|
||||||
#include "core/mimedata.h"
|
#include "core/mimedata.h"
|
||||||
#include "core/utilities.h"
|
|
||||||
#include "core/musicstorage.h"
|
#include "core/musicstorage.h"
|
||||||
#include "core/deletefiles.h"
|
#include "core/deletefiles.h"
|
||||||
|
#include "utilities/filemanagerutils.h"
|
||||||
#include "collection.h"
|
#include "collection.h"
|
||||||
#include "collectionbackend.h"
|
#include "collectionbackend.h"
|
||||||
#include "collectiondirectorymodel.h"
|
#include "collectiondirectorymodel.h"
|
||||||
@@ -699,7 +699,7 @@ void CollectionView::DeleteFilesFinished(const SongList &songs_with_errors) {
|
|||||||
if (songs_with_errors.isEmpty()) return;
|
if (songs_with_errors.isEmpty()) return;
|
||||||
|
|
||||||
OrganizeErrorDialog *dialog = new OrganizeErrorDialog(this);
|
OrganizeErrorDialog *dialog = new OrganizeErrorDialog(this);
|
||||||
dialog->Show(OrganizeErrorDialog::Type_Delete, songs_with_errors);
|
dialog->Show(OrganizeErrorDialog::OperationType::Delete, songs_with_errors);
|
||||||
// It deletes itself when the user closes it
|
// It deletes itself when the user closes it
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -46,11 +46,11 @@
|
|||||||
|
|
||||||
#include "core/filesystemwatcherinterface.h"
|
#include "core/filesystemwatcherinterface.h"
|
||||||
#include "core/logging.h"
|
#include "core/logging.h"
|
||||||
#include "core/timeconstants.h"
|
|
||||||
#include "core/tagreaderclient.h"
|
#include "core/tagreaderclient.h"
|
||||||
#include "core/taskmanager.h"
|
#include "core/taskmanager.h"
|
||||||
#include "core/imageutils.h"
|
#include "utilities/imageutils.h"
|
||||||
#include "directory.h"
|
#include "utilities/timeconstants.h"
|
||||||
|
#include "collectiondirectory.h"
|
||||||
#include "collectionbackend.h"
|
#include "collectionbackend.h"
|
||||||
#include "collectionwatcher.h"
|
#include "collectionwatcher.h"
|
||||||
#include "playlistparsers/cueparser.h"
|
#include "playlistparsers/cueparser.h"
|
||||||
@@ -78,13 +78,12 @@ CollectionWatcher::CollectionWatcher(Song::Source source, QObject *parent)
|
|||||||
scan_on_startup_(true),
|
scan_on_startup_(true),
|
||||||
monitor_(true),
|
monitor_(true),
|
||||||
song_tracking_(false),
|
song_tracking_(false),
|
||||||
mark_songs_unavailable_(source_ == Song::Source_Collection),
|
mark_songs_unavailable_(source_ == Song::Source::Collection),
|
||||||
expire_unavailable_songs_days_(60),
|
expire_unavailable_songs_days_(60),
|
||||||
overwrite_playcount_(false),
|
overwrite_playcount_(false),
|
||||||
overwrite_rating_(false),
|
overwrite_rating_(false),
|
||||||
stop_requested_(false),
|
stop_requested_(false),
|
||||||
abort_requested_(false),
|
abort_requested_(false),
|
||||||
rescan_in_progress_(false),
|
|
||||||
rescan_timer_(new QTimer(this)),
|
rescan_timer_(new QTimer(this)),
|
||||||
periodic_scan_timer_(new QTimer(this)),
|
periodic_scan_timer_(new QTimer(this)),
|
||||||
rescan_paused_(false),
|
rescan_paused_(false),
|
||||||
@@ -143,7 +142,7 @@ void CollectionWatcher::ReloadSettings() {
|
|||||||
scan_on_startup_ = s.value("startup_scan", true).toBool();
|
scan_on_startup_ = s.value("startup_scan", true).toBool();
|
||||||
monitor_ = s.value("monitor", true).toBool();
|
monitor_ = s.value("monitor", true).toBool();
|
||||||
QStringList filters = s.value("cover_art_patterns", QStringList() << "front" << "cover").toStringList();
|
QStringList filters = s.value("cover_art_patterns", QStringList() << "front" << "cover").toStringList();
|
||||||
if (source_ == Song::Source_Collection) {
|
if (source_ == Song::Source::Collection) {
|
||||||
song_tracking_ = s.value("song_tracking", false).toBool();
|
song_tracking_ = s.value("song_tracking", false).toBool();
|
||||||
mark_songs_unavailable_ = song_tracking_ ? true : s.value("mark_songs_unavailable", true).toBool();
|
mark_songs_unavailable_ = song_tracking_ ? true : s.value("mark_songs_unavailable", true).toBool();
|
||||||
}
|
}
|
||||||
@@ -167,15 +166,15 @@ void CollectionWatcher::ReloadSettings() {
|
|||||||
}
|
}
|
||||||
else if (monitor_ && !was_monitoring_before) {
|
else if (monitor_ && !was_monitoring_before) {
|
||||||
// Add all directories to all QFileSystemWatchers again
|
// Add all directories to all QFileSystemWatchers again
|
||||||
for (const Directory &dir : std::as_const(watched_dirs_)) {
|
for (const CollectionDirectory &dir : std::as_const(watched_dirs_)) {
|
||||||
SubdirectoryList subdirs = backend_->SubdirsInDirectory(dir.id);
|
CollectionSubdirectoryList subdirs = backend_->SubdirsInDirectory(dir.id);
|
||||||
for (const Subdirectory &subdir : subdirs) {
|
for (const CollectionSubdirectory &subdir : subdirs) {
|
||||||
AddWatch(dir, subdir.path);
|
AddWatch(dir, subdir.path);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (mark_songs_unavailable_ && !periodic_scan_timer_->isActive()) {
|
if (monitor_ && scan_on_startup_ && mark_songs_unavailable_ && !periodic_scan_timer_->isActive()) {
|
||||||
periodic_scan_timer_->start();
|
periodic_scan_timer_->start();
|
||||||
}
|
}
|
||||||
else if (!mark_songs_unavailable_ && periodic_scan_timer_->isActive()) {
|
else if (!mark_songs_unavailable_ && periodic_scan_timer_->isActive()) {
|
||||||
@@ -239,7 +238,7 @@ void CollectionWatcher::ScanTransaction::AddToProgressMax(const quint64 n) {
|
|||||||
void CollectionWatcher::ScanTransaction::CommitNewOrUpdatedSongs() {
|
void CollectionWatcher::ScanTransaction::CommitNewOrUpdatedSongs() {
|
||||||
|
|
||||||
if (!deleted_songs.isEmpty()) {
|
if (!deleted_songs.isEmpty()) {
|
||||||
if (mark_songs_unavailable_ && watcher_->source() == Song::Source_Collection) {
|
if (mark_songs_unavailable_ && watcher_->source() == Song::Source::Collection) {
|
||||||
emit watcher_->SongsUnavailable(deleted_songs);
|
emit watcher_->SongsUnavailable(deleted_songs);
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
@@ -272,7 +271,7 @@ void CollectionWatcher::ScanTransaction::CommitNewOrUpdatedSongs() {
|
|||||||
touched_subdirs.clear();
|
touched_subdirs.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
for (const Subdirectory &subdir : deleted_subdirs) {
|
for (const CollectionSubdirectory &subdir : deleted_subdirs) {
|
||||||
if (watcher_->watched_dirs_.contains(dir_)) {
|
if (watcher_->watched_dirs_.contains(dir_)) {
|
||||||
watcher_->RemoveWatch(watcher_->watched_dirs_[dir_], subdir);
|
watcher_->RemoveWatch(watcher_->watched_dirs_[dir_], subdir);
|
||||||
}
|
}
|
||||||
@@ -281,7 +280,7 @@ void CollectionWatcher::ScanTransaction::CommitNewOrUpdatedSongs() {
|
|||||||
|
|
||||||
if (watcher_->monitor_) {
|
if (watcher_->monitor_) {
|
||||||
// Watch the new subdirectories
|
// Watch the new subdirectories
|
||||||
for (const Subdirectory &subdir : new_subdirs) {
|
for (const CollectionSubdirectory &subdir : new_subdirs) {
|
||||||
if (watcher_->watched_dirs_.contains(dir_)) {
|
if (watcher_->watched_dirs_.contains(dir_)) {
|
||||||
watcher_->AddWatch(watcher_->watched_dirs_[dir_], subdir.path);
|
watcher_->AddWatch(watcher_->watched_dirs_[dir_], subdir.path);
|
||||||
}
|
}
|
||||||
@@ -329,7 +328,7 @@ bool CollectionWatcher::ScanTransaction::HasSongsWithMissingFingerprint(const QS
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void CollectionWatcher::ScanTransaction::SetKnownSubdirs(const SubdirectoryList &subdirs) {
|
void CollectionWatcher::ScanTransaction::SetKnownSubdirs(const CollectionSubdirectoryList &subdirs) {
|
||||||
|
|
||||||
known_subdirs_ = subdirs;
|
known_subdirs_ = subdirs;
|
||||||
known_subdirs_dirty_ = false;
|
known_subdirs_dirty_ = false;
|
||||||
@@ -342,18 +341,18 @@ bool CollectionWatcher::ScanTransaction::HasSeenSubdir(const QString &path) {
|
|||||||
SetKnownSubdirs(watcher_->backend_->SubdirsInDirectory(dir_));
|
SetKnownSubdirs(watcher_->backend_->SubdirsInDirectory(dir_));
|
||||||
}
|
}
|
||||||
|
|
||||||
return std::any_of(known_subdirs_.begin(), known_subdirs_.end(), [path](const Subdirectory &subdir) { return subdir.path == path && subdir.mtime != 0; });
|
return std::any_of(known_subdirs_.begin(), known_subdirs_.end(), [path](const CollectionSubdirectory &subdir) { return subdir.path == path && subdir.mtime != 0; });
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
SubdirectoryList CollectionWatcher::ScanTransaction::GetImmediateSubdirs(const QString &path) {
|
CollectionSubdirectoryList CollectionWatcher::ScanTransaction::GetImmediateSubdirs(const QString &path) {
|
||||||
|
|
||||||
if (known_subdirs_dirty_) {
|
if (known_subdirs_dirty_) {
|
||||||
SetKnownSubdirs(watcher_->backend_->SubdirsInDirectory(dir_));
|
SetKnownSubdirs(watcher_->backend_->SubdirsInDirectory(dir_));
|
||||||
}
|
}
|
||||||
|
|
||||||
SubdirectoryList ret;
|
CollectionSubdirectoryList ret;
|
||||||
for (const Subdirectory &subdir : known_subdirs_) {
|
for (const CollectionSubdirectory &subdir : known_subdirs_) {
|
||||||
if (subdir.path.left(subdir.path.lastIndexOf(QDir::separator())) == path && subdir.mtime != 0) {
|
if (subdir.path.left(subdir.path.lastIndexOf(QDir::separator())) == path && subdir.mtime != 0) {
|
||||||
ret << subdir;
|
ret << subdir;
|
||||||
}
|
}
|
||||||
@@ -363,7 +362,7 @@ SubdirectoryList CollectionWatcher::ScanTransaction::GetImmediateSubdirs(const Q
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
SubdirectoryList CollectionWatcher::ScanTransaction::GetAllSubdirs() {
|
CollectionSubdirectoryList CollectionWatcher::ScanTransaction::GetAllSubdirs() {
|
||||||
|
|
||||||
if (known_subdirs_dirty_) {
|
if (known_subdirs_dirty_) {
|
||||||
SetKnownSubdirs(watcher_->backend_->SubdirsInDirectory(dir_));
|
SetKnownSubdirs(watcher_->backend_->SubdirsInDirectory(dir_));
|
||||||
@@ -373,7 +372,7 @@ SubdirectoryList CollectionWatcher::ScanTransaction::GetAllSubdirs() {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void CollectionWatcher::AddDirectory(const Directory &dir, const SubdirectoryList &subdirs) {
|
void CollectionWatcher::AddDirectory(const CollectionDirectory &dir, const CollectionSubdirectoryList &subdirs) {
|
||||||
|
|
||||||
stop_requested_ = false;
|
stop_requested_ = false;
|
||||||
|
|
||||||
@@ -385,7 +384,7 @@ void CollectionWatcher::AddDirectory(const Directory &dir, const SubdirectoryLis
|
|||||||
const quint64 files_count = FilesCountForPath(&transaction, dir.path);
|
const quint64 files_count = FilesCountForPath(&transaction, dir.path);
|
||||||
transaction.SetKnownSubdirs(subdirs);
|
transaction.SetKnownSubdirs(subdirs);
|
||||||
transaction.AddToProgressMax(files_count);
|
transaction.AddToProgressMax(files_count);
|
||||||
ScanSubdirectory(dir.path, Subdirectory(), files_count, &transaction);
|
ScanSubdirectory(dir.path, CollectionSubdirectory(), files_count, &transaction);
|
||||||
last_scan_time_ = QDateTime::currentDateTime().toSecsSinceEpoch();
|
last_scan_time_ = QDateTime::currentDateTime().toSecsSinceEpoch();
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
@@ -395,7 +394,7 @@ void CollectionWatcher::AddDirectory(const Directory &dir, const SubdirectoryLis
|
|||||||
const quint64 files_count = FilesCountForSubdirs(&transaction, subdirs, subdir_files_count);
|
const quint64 files_count = FilesCountForSubdirs(&transaction, subdirs, subdir_files_count);
|
||||||
transaction.SetKnownSubdirs(subdirs);
|
transaction.SetKnownSubdirs(subdirs);
|
||||||
transaction.AddToProgressMax(files_count);
|
transaction.AddToProgressMax(files_count);
|
||||||
for (const Subdirectory &subdir : subdirs) {
|
for (const CollectionSubdirectory &subdir : subdirs) {
|
||||||
if (stop_requested_ || abort_requested_) break;
|
if (stop_requested_ || abort_requested_) break;
|
||||||
|
|
||||||
if (scan_on_startup_) ScanSubdirectory(subdir.path, subdir, subdir_files_count[subdir.path], &transaction);
|
if (scan_on_startup_) ScanSubdirectory(subdir.path, subdir, subdir_files_count[subdir.path], &transaction);
|
||||||
@@ -411,14 +410,14 @@ void CollectionWatcher::AddDirectory(const Directory &dir, const SubdirectoryLis
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void CollectionWatcher::ScanSubdirectory(const QString &path, const Subdirectory &subdir, const quint64 files_count, ScanTransaction *t, const bool force_noincremental) {
|
void CollectionWatcher::ScanSubdirectory(const QString &path, const CollectionSubdirectory &subdir, const quint64 files_count, ScanTransaction *t, const bool force_noincremental) {
|
||||||
|
|
||||||
QFileInfo path_info(path);
|
QFileInfo path_info(path);
|
||||||
|
|
||||||
// Do not scan symlinked dirs that are already in collection
|
// Do not scan symlinked dirs that are already in collection
|
||||||
if (path_info.isSymLink()) {
|
if (path_info.isSymLink()) {
|
||||||
QString real_path = path_info.symLinkTarget();
|
QString real_path = path_info.symLinkTarget();
|
||||||
for (const Directory &dir : std::as_const(watched_dirs_)) {
|
for (const CollectionDirectory &dir : std::as_const(watched_dirs_)) {
|
||||||
if (real_path.startsWith(dir.path)) {
|
if (real_path.startsWith(dir.path)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -440,12 +439,12 @@ void CollectionWatcher::ScanSubdirectory(const QString &path, const Subdirectory
|
|||||||
|
|
||||||
QMap<QString, QStringList> album_art;
|
QMap<QString, QStringList> album_art;
|
||||||
QStringList files_on_disk;
|
QStringList files_on_disk;
|
||||||
SubdirectoryList my_new_subdirs;
|
CollectionSubdirectoryList my_new_subdirs;
|
||||||
|
|
||||||
// If a directory is moved then only its parent gets a changed notification, so we need to look and see if any of our children don't exist anymore.
|
// If a directory is moved then only its parent gets a changed notification, so we need to look and see if any of our children don't exist anymore.
|
||||||
// If one has been removed, "rescan" it to get the deleted songs
|
// If one has been removed, "rescan" it to get the deleted songs
|
||||||
SubdirectoryList previous_subdirs = t->GetImmediateSubdirs(path);
|
CollectionSubdirectoryList previous_subdirs = t->GetImmediateSubdirs(path);
|
||||||
for (const Subdirectory &prev_subdir : previous_subdirs) {
|
for (const CollectionSubdirectory &prev_subdir : previous_subdirs) {
|
||||||
if (!QFile::exists(prev_subdir.path) && prev_subdir.path != path) {
|
if (!QFile::exists(prev_subdir.path) && prev_subdir.path != path) {
|
||||||
ScanSubdirectory(prev_subdir.path, prev_subdir, 0, t, true);
|
ScanSubdirectory(prev_subdir.path, prev_subdir, 0, t, true);
|
||||||
}
|
}
|
||||||
@@ -463,7 +462,7 @@ void CollectionWatcher::ScanSubdirectory(const QString &path, const Subdirectory
|
|||||||
if (child_info.isDir()) {
|
if (child_info.isDir()) {
|
||||||
if (!t->HasSeenSubdir(child)) {
|
if (!t->HasSeenSubdir(child)) {
|
||||||
// We haven't seen this subdirectory before - add it to a list, and later we'll tell the backend about it and scan it.
|
// We haven't seen this subdirectory before - add it to a list, and later we'll tell the backend about it and scan it.
|
||||||
Subdirectory new_subdir;
|
CollectionSubdirectory new_subdir;
|
||||||
new_subdir.directory_id = -1;
|
new_subdir.directory_id = -1;
|
||||||
new_subdir.path = child;
|
new_subdir.path = child;
|
||||||
new_subdir.mtime = child_info.lastModified().toSecsSinceEpoch();
|
new_subdir.mtime = child_info.lastModified().toSecsSinceEpoch();
|
||||||
@@ -676,7 +675,7 @@ void CollectionWatcher::ScanSubdirectory(const QString &path, const Subdirectory
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Add this subdir to the new or touched list
|
// Add this subdir to the new or touched list
|
||||||
Subdirectory updated_subdir;
|
CollectionSubdirectory updated_subdir;
|
||||||
updated_subdir.directory_id = t->dir();
|
updated_subdir.directory_id = t->dir();
|
||||||
updated_subdir.mtime = path_info.exists() ? path_info.lastModified().toSecsSinceEpoch() : 0;
|
updated_subdir.mtime = path_info.exists() ? path_info.lastModified().toSecsSinceEpoch() : 0;
|
||||||
updated_subdir.path = path;
|
updated_subdir.path = path;
|
||||||
@@ -688,12 +687,12 @@ void CollectionWatcher::ScanSubdirectory(const QString &path, const Subdirectory
|
|||||||
t->touched_subdirs << updated_subdir;
|
t->touched_subdirs << updated_subdir;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (updated_subdir.mtime == 0) { // Subdirectory deleted, mark it for removal from the watcher.
|
if (updated_subdir.mtime == 0) { // CollectionSubdirectory deleted, mark it for removal from the watcher.
|
||||||
t->deleted_subdirs << updated_subdir;
|
t->deleted_subdirs << updated_subdir;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Recurse into the new subdirs that we found
|
// Recurse into the new subdirs that we found
|
||||||
for (const Subdirectory &my_new_subdir : my_new_subdirs) {
|
for (const CollectionSubdirectory &my_new_subdir : my_new_subdirs) {
|
||||||
if (stop_requested_ || abort_requested_) return;
|
if (stop_requested_ || abort_requested_) return;
|
||||||
ScanSubdirectory(my_new_subdir.path, my_new_subdir, 0, t, true);
|
ScanSubdirectory(my_new_subdir.path, my_new_subdir, 0, t, true);
|
||||||
}
|
}
|
||||||
@@ -763,7 +762,7 @@ void CollectionWatcher::UpdateNonCueAssociatedSong(const QString &file,
|
|||||||
const Song &matching_song = matching_songs.first();
|
const Song &matching_song = matching_songs.first();
|
||||||
if (cue_deleted) {
|
if (cue_deleted) {
|
||||||
for (const Song &song : matching_songs) {
|
for (const Song &song : matching_songs) {
|
||||||
if (!song.IsMetadataAndMoreEqual(matching_song)) {
|
if (!song.IsAllMetadataEqual(matching_song)) {
|
||||||
t->deleted_songs << song;
|
t->deleted_songs << song;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -855,10 +854,26 @@ void CollectionWatcher::AddChangedSong(const QString &file, const Song &matching
|
|||||||
changes << "metadata";
|
changes << "metadata";
|
||||||
notify_new = true;
|
notify_new = true;
|
||||||
}
|
}
|
||||||
if (matching_song.art_automatic() != new_song.art_automatic() || matching_song.art_manual() != new_song.art_manual()) {
|
if (!matching_song.IsPlayStatisticsEqual(new_song)) {
|
||||||
|
changes << "play statistics";
|
||||||
|
notify_new = true;
|
||||||
|
}
|
||||||
|
if (!matching_song.IsRatingEqual(new_song)) {
|
||||||
|
changes << "rating";
|
||||||
|
notify_new = true;
|
||||||
|
}
|
||||||
|
if (!matching_song.IsArtEqual(new_song)) {
|
||||||
changes << "album art";
|
changes << "album art";
|
||||||
notify_new = true;
|
notify_new = true;
|
||||||
}
|
}
|
||||||
|
if (!matching_song.IsAcoustIdEqual(new_song)) {
|
||||||
|
changes << "acoustid";
|
||||||
|
notify_new = true;
|
||||||
|
}
|
||||||
|
if (!matching_song.IsMusicBrainzEqual(new_song)) {
|
||||||
|
changes << "musicbrainz";
|
||||||
|
notify_new = true;
|
||||||
|
}
|
||||||
if (matching_song.mtime() != new_song.mtime()) {
|
if (matching_song.mtime() != new_song.mtime()) {
|
||||||
changes << "mtime";
|
changes << "mtime";
|
||||||
}
|
}
|
||||||
@@ -897,7 +912,7 @@ quint64 CollectionWatcher::GetMtimeForCue(const QString &cue_path) {
|
|||||||
return cue_last_modified.isValid() ? cue_last_modified.toSecsSinceEpoch() : 0;
|
return cue_last_modified.isValid() ? cue_last_modified.toSecsSinceEpoch() : 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
void CollectionWatcher::AddWatch(const Directory &dir, const QString &path) {
|
void CollectionWatcher::AddWatch(const CollectionDirectory &dir, const QString &path) {
|
||||||
|
|
||||||
if (!QFile::exists(path)) return;
|
if (!QFile::exists(path)) return;
|
||||||
|
|
||||||
@@ -907,7 +922,7 @@ void CollectionWatcher::AddWatch(const Directory &dir, const QString &path) {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void CollectionWatcher::RemoveWatch(const Directory &dir, const Subdirectory &subdir) {
|
void CollectionWatcher::RemoveWatch(const CollectionDirectory &dir, const CollectionSubdirectory &subdir) {
|
||||||
|
|
||||||
QStringList subdir_paths = subdir_mapping_.keys(dir);
|
QStringList subdir_paths = subdir_mapping_.keys(dir);
|
||||||
for (const QString &subdir_path : subdir_paths) {
|
for (const QString &subdir_path : subdir_paths) {
|
||||||
@@ -919,7 +934,7 @@ void CollectionWatcher::RemoveWatch(const Directory &dir, const Subdirectory &su
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void CollectionWatcher::RemoveDirectory(const Directory &dir) {
|
void CollectionWatcher::RemoveDirectory(const CollectionDirectory &dir) {
|
||||||
|
|
||||||
rescan_queue_.remove(dir.id);
|
rescan_queue_.remove(dir.id);
|
||||||
watched_dirs_.remove(dir.id);
|
watched_dirs_.remove(dir.id);
|
||||||
@@ -979,11 +994,11 @@ bool CollectionWatcher::FindSongsByFingerprint(const QString &file, const SongLi
|
|||||||
void CollectionWatcher::DirectoryChanged(const QString &subdir) {
|
void CollectionWatcher::DirectoryChanged(const QString &subdir) {
|
||||||
|
|
||||||
// Find what dir it was in
|
// Find what dir it was in
|
||||||
QHash<QString, Directory>::const_iterator it = subdir_mapping_.constFind(subdir);
|
QHash<QString, CollectionDirectory>::const_iterator it = subdir_mapping_.constFind(subdir);
|
||||||
if (it == subdir_mapping_.constEnd()) {
|
if (it == subdir_mapping_.constEnd()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
Directory dir = *it;
|
CollectionDirectory dir = *it;
|
||||||
|
|
||||||
qLog(Debug) << "Subdir" << subdir << "changed under directory" << dir.path << "id" << dir.id;
|
qLog(Debug) << "Subdir" << subdir << "changed under directory" << dir.path << "id" << dir.id;
|
||||||
|
|
||||||
@@ -1010,7 +1025,7 @@ void CollectionWatcher::RescanPathsNow() {
|
|||||||
|
|
||||||
for (const QString &path : rescan_queue_[dir]) {
|
for (const QString &path : rescan_queue_[dir]) {
|
||||||
if (stop_requested_ || abort_requested_) break;
|
if (stop_requested_ || abort_requested_) break;
|
||||||
Subdirectory subdir;
|
CollectionSubdirectory subdir;
|
||||||
subdir.directory_id = dir;
|
subdir.directory_id = dir;
|
||||||
subdir.mtime = 0;
|
subdir.mtime = 0;
|
||||||
subdir.path = path;
|
subdir.path = path;
|
||||||
@@ -1113,18 +1128,6 @@ void CollectionWatcher::FullScanAsync() {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void CollectionWatcher::RescanTracksAsync(const SongList &songs) {
|
|
||||||
|
|
||||||
// Is List thread safe? if not, this may crash.
|
|
||||||
song_rescan_queue_.append(songs);
|
|
||||||
|
|
||||||
// Call only if it's not already running
|
|
||||||
if (!rescan_in_progress_) {
|
|
||||||
QMetaObject::invokeMethod(this, "RescanTracksNow", Qt::QueuedConnection);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
void CollectionWatcher::IncrementalScanCheck() {
|
void CollectionWatcher::IncrementalScanCheck() {
|
||||||
|
|
||||||
qint64 duration = QDateTime::currentDateTime().toSecsSinceEpoch() - last_scan_time_;
|
qint64 duration = QDateTime::currentDateTime().toSecsSinceEpoch() - last_scan_time_;
|
||||||
@@ -1139,48 +1142,20 @@ void CollectionWatcher::IncrementalScanNow() { PerformScan(true, false); }
|
|||||||
|
|
||||||
void CollectionWatcher::FullScanNow() { PerformScan(false, true); }
|
void CollectionWatcher::FullScanNow() { PerformScan(false, true); }
|
||||||
|
|
||||||
void CollectionWatcher::RescanTracksNow() {
|
|
||||||
|
|
||||||
Q_ASSERT(!rescan_in_progress_);
|
|
||||||
stop_requested_ = false;
|
|
||||||
|
|
||||||
// Currently we are too stupid to rescan one file at a time, so we'll just scan the full directories
|
|
||||||
QStringList scanned_dirs; // To avoid double scans
|
|
||||||
while (!song_rescan_queue_.isEmpty()) {
|
|
||||||
if (stop_requested_ || abort_requested_) break;
|
|
||||||
Song song = song_rescan_queue_.takeFirst();
|
|
||||||
QString songdir = song.url().toLocalFile().section('/', 0, -2);
|
|
||||||
if (!scanned_dirs.contains(songdir)) {
|
|
||||||
qLog(Debug) << "Song" << song.title() << "dir id" << song.directory_id() << "dir" << songdir;
|
|
||||||
ScanTransaction transaction(this, song.directory_id(), false, false, mark_songs_unavailable_);
|
|
||||||
quint64 files_count = FilesCountForPath(&transaction, songdir);
|
|
||||||
ScanSubdirectory(songdir, Subdirectory(), files_count, &transaction);
|
|
||||||
scanned_dirs << songdir;
|
|
||||||
emit CompilationsNeedUpdating();
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
qLog(Debug) << "Directory" << songdir << "already scanned - skipping.";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Q_ASSERT(song_rescan_queue_.isEmpty());
|
|
||||||
rescan_in_progress_ = false;
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
void CollectionWatcher::PerformScan(const bool incremental, const bool ignore_mtimes) {
|
void CollectionWatcher::PerformScan(const bool incremental, const bool ignore_mtimes) {
|
||||||
|
|
||||||
stop_requested_ = false;
|
stop_requested_ = false;
|
||||||
|
|
||||||
for (const Directory &dir : std::as_const(watched_dirs_)) {
|
for (const CollectionDirectory &dir : std::as_const(watched_dirs_)) {
|
||||||
|
|
||||||
if (stop_requested_ || abort_requested_) break;
|
if (stop_requested_ || abort_requested_) break;
|
||||||
|
|
||||||
ScanTransaction transaction(this, dir.id, incremental, ignore_mtimes, mark_songs_unavailable_);
|
ScanTransaction transaction(this, dir.id, incremental, ignore_mtimes, mark_songs_unavailable_);
|
||||||
SubdirectoryList subdirs(transaction.GetAllSubdirs());
|
CollectionSubdirectoryList subdirs(transaction.GetAllSubdirs());
|
||||||
|
|
||||||
if (subdirs.isEmpty()) {
|
if (subdirs.isEmpty()) {
|
||||||
qLog(Debug) << "Collection directory wasn't in subdir list.";
|
qLog(Debug) << "Collection directory wasn't in subdir list.";
|
||||||
Subdirectory subdir;
|
CollectionSubdirectory subdir;
|
||||||
subdir.path = dir.path;
|
subdir.path = dir.path;
|
||||||
subdir.directory_id = dir.id;
|
subdir.directory_id = dir.id;
|
||||||
subdirs << subdir;
|
subdirs << subdir;
|
||||||
@@ -1190,7 +1165,7 @@ void CollectionWatcher::PerformScan(const bool incremental, const bool ignore_mt
|
|||||||
quint64 files_count = FilesCountForSubdirs(&transaction, subdirs, subdir_files_count);
|
quint64 files_count = FilesCountForSubdirs(&transaction, subdirs, subdir_files_count);
|
||||||
transaction.AddToProgressMax(files_count);
|
transaction.AddToProgressMax(files_count);
|
||||||
|
|
||||||
for (const Subdirectory &subdir : subdirs) {
|
for (const CollectionSubdirectory &subdir : subdirs) {
|
||||||
if (stop_requested_ || abort_requested_) break;
|
if (stop_requested_ || abort_requested_) break;
|
||||||
ScanSubdirectory(subdir.path, subdir, subdir_files_count[subdir.path], &transaction);
|
ScanSubdirectory(subdir.path, subdir, subdir_files_count[subdir.path], &transaction);
|
||||||
}
|
}
|
||||||
@@ -1217,7 +1192,7 @@ quint64 CollectionWatcher::FilesCountForPath(ScanTransaction *t, const QString &
|
|||||||
if (path_info.isDir()) {
|
if (path_info.isDir()) {
|
||||||
if (path_info.isSymLink()) {
|
if (path_info.isSymLink()) {
|
||||||
QString real_path = path_info.symLinkTarget();
|
QString real_path = path_info.symLinkTarget();
|
||||||
for (const Directory &dir : std::as_const(watched_dirs_)) {
|
for (const CollectionDirectory &dir : std::as_const(watched_dirs_)) {
|
||||||
if (real_path.startsWith(dir.path)) {
|
if (real_path.startsWith(dir.path)) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
@@ -1239,10 +1214,10 @@ quint64 CollectionWatcher::FilesCountForPath(ScanTransaction *t, const QString &
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
quint64 CollectionWatcher::FilesCountForSubdirs(ScanTransaction *t, const SubdirectoryList &subdirs, QMap<QString, quint64> &subdir_files_count) {
|
quint64 CollectionWatcher::FilesCountForSubdirs(ScanTransaction *t, const CollectionSubdirectoryList &subdirs, QMap<QString, quint64> &subdir_files_count) {
|
||||||
|
|
||||||
quint64 i = 0;
|
quint64 i = 0;
|
||||||
for (const Subdirectory &subdir : subdirs) {
|
for (const CollectionSubdirectory &subdir : subdirs) {
|
||||||
if (stop_requested_ || abort_requested_) break;
|
if (stop_requested_ || abort_requested_) break;
|
||||||
const quint64 files_count = FilesCountForPath(t, subdir.path);
|
const quint64 files_count = FilesCountForPath(t, subdir.path);
|
||||||
subdir_files_count[subdir.path] = files_count;
|
subdir_files_count[subdir.path] = files_count;
|
||||||
@@ -1252,3 +1227,34 @@ quint64 CollectionWatcher::FilesCountForSubdirs(ScanTransaction *t, const Subdir
|
|||||||
return i;
|
return i;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void CollectionWatcher::RescanSongsAsync(const SongList &songs) {
|
||||||
|
|
||||||
|
QMetaObject::invokeMethod(this, "RescanSongs", Qt::QueuedConnection, Q_ARG(SongList, songs));
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
void CollectionWatcher::RescanSongs(const SongList &songs) {
|
||||||
|
|
||||||
|
stop_requested_ = false;
|
||||||
|
|
||||||
|
QStringList scanned_paths;
|
||||||
|
for (const Song &song : songs) {
|
||||||
|
if (stop_requested_ || abort_requested_) break;
|
||||||
|
const QString song_path = song.url().toLocalFile().section('/', 0, -2);
|
||||||
|
if (scanned_paths.contains(song_path)) continue;
|
||||||
|
ScanTransaction transaction(this, song.directory_id(), false, true, mark_songs_unavailable_);
|
||||||
|
CollectionSubdirectoryList subdirs(transaction.GetAllSubdirs());
|
||||||
|
for (const CollectionSubdirectory &subdir : subdirs) {
|
||||||
|
if (stop_requested_ || abort_requested_) break;
|
||||||
|
if (subdir.path != song_path) continue;
|
||||||
|
qLog(Debug) << "Rescan for directory ID" << song.directory_id() << "directory" << subdir.path;
|
||||||
|
quint64 files_count = FilesCountForPath(&transaction, subdir.path);
|
||||||
|
ScanSubdirectory(song_path, subdir, files_count, &transaction);
|
||||||
|
scanned_paths << subdir.path;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
emit CompilationsNeedUpdating();
|
||||||
|
|
||||||
|
}
|
||||||
|
|||||||
@@ -34,7 +34,7 @@
|
|||||||
#include <QStringList>
|
#include <QStringList>
|
||||||
#include <QUrl>
|
#include <QUrl>
|
||||||
|
|
||||||
#include "directory.h"
|
#include "collectiondirectory.h"
|
||||||
#include "core/song.h"
|
#include "core/song.h"
|
||||||
|
|
||||||
class QThread;
|
class QThread;
|
||||||
@@ -59,7 +59,6 @@ class CollectionWatcher : public QObject {
|
|||||||
|
|
||||||
void IncrementalScanAsync();
|
void IncrementalScanAsync();
|
||||||
void FullScanAsync();
|
void FullScanAsync();
|
||||||
void RescanTracksAsync(const SongList &songs);
|
|
||||||
void SetRescanPausedAsync(const bool pause);
|
void SetRescanPausedAsync(const bool pause);
|
||||||
void ReloadSettingsAsync();
|
void ReloadSettingsAsync();
|
||||||
|
|
||||||
@@ -68,14 +67,16 @@ class CollectionWatcher : public QObject {
|
|||||||
|
|
||||||
void ExitAsync();
|
void ExitAsync();
|
||||||
|
|
||||||
|
void RescanSongsAsync(const SongList &songs);
|
||||||
|
|
||||||
signals:
|
signals:
|
||||||
void NewOrUpdatedSongs(SongList);
|
void NewOrUpdatedSongs(SongList);
|
||||||
void SongsMTimeUpdated(SongList);
|
void SongsMTimeUpdated(SongList);
|
||||||
void SongsDeleted(SongList);
|
void SongsDeleted(SongList);
|
||||||
void SongsUnavailable(SongList songs, bool unavailable = true);
|
void SongsUnavailable(SongList songs, bool unavailable = true);
|
||||||
void SongsReadded(SongList songs, bool unavailable = false);
|
void SongsReadded(SongList songs, bool unavailable = false);
|
||||||
void SubdirsDiscovered(SubdirectoryList subdirs);
|
void SubdirsDiscovered(CollectionSubdirectoryList subdirs);
|
||||||
void SubdirsMTimeUpdated(SubdirectoryList subdirs);
|
void SubdirsMTimeUpdated(CollectionSubdirectoryList subdirs);
|
||||||
void CompilationsNeedUpdating();
|
void CompilationsNeedUpdating();
|
||||||
void UpdateLastSeen(int directory_id, int expire_unavailable_songs_days);
|
void UpdateLastSeen(int directory_id, int expire_unavailable_songs_days);
|
||||||
void ExitFinished();
|
void ExitFinished();
|
||||||
@@ -83,8 +84,8 @@ class CollectionWatcher : public QObject {
|
|||||||
void ScanStarted(int task_id);
|
void ScanStarted(int task_id);
|
||||||
|
|
||||||
public slots:
|
public slots:
|
||||||
void AddDirectory(const Directory &dir, const SubdirectoryList &subdirs);
|
void AddDirectory(const CollectionDirectory &dir, const CollectionSubdirectoryList &subdirs);
|
||||||
void RemoveDirectory(const Directory &dir);
|
void RemoveDirectory(const CollectionDirectory &dir);
|
||||||
void SetRescanPaused(bool pause);
|
void SetRescanPaused(bool pause);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
@@ -102,9 +103,9 @@ class CollectionWatcher : public QObject {
|
|||||||
SongList FindSongsInSubdirectory(const QString &path);
|
SongList FindSongsInSubdirectory(const QString &path);
|
||||||
bool HasSongsWithMissingFingerprint(const QString &path);
|
bool HasSongsWithMissingFingerprint(const QString &path);
|
||||||
bool HasSeenSubdir(const QString &path);
|
bool HasSeenSubdir(const QString &path);
|
||||||
void SetKnownSubdirs(const SubdirectoryList &subdirs);
|
void SetKnownSubdirs(const CollectionSubdirectoryList &subdirs);
|
||||||
SubdirectoryList GetImmediateSubdirs(const QString &path);
|
CollectionSubdirectoryList GetImmediateSubdirs(const QString &path);
|
||||||
SubdirectoryList GetAllSubdirs();
|
CollectionSubdirectoryList GetAllSubdirs();
|
||||||
|
|
||||||
void AddToProgress(const quint64 n = 1);
|
void AddToProgress(const quint64 n = 1);
|
||||||
void AddToProgressMax(const quint64 n);
|
void AddToProgressMax(const quint64 n);
|
||||||
@@ -120,9 +121,9 @@ class CollectionWatcher : public QObject {
|
|||||||
SongList readded_songs;
|
SongList readded_songs;
|
||||||
SongList new_songs;
|
SongList new_songs;
|
||||||
SongList touched_songs;
|
SongList touched_songs;
|
||||||
SubdirectoryList new_subdirs;
|
CollectionSubdirectoryList new_subdirs;
|
||||||
SubdirectoryList touched_subdirs;
|
CollectionSubdirectoryList touched_subdirs;
|
||||||
SubdirectoryList deleted_subdirs;
|
CollectionSubdirectoryList deleted_subdirs;
|
||||||
|
|
||||||
QStringList files_changed_path_;
|
QStringList files_changed_path_;
|
||||||
|
|
||||||
@@ -155,7 +156,7 @@ class CollectionWatcher : public QObject {
|
|||||||
QMultiMap<QString, Song> cached_songs_missing_fingerprint_;
|
QMultiMap<QString, Song> cached_songs_missing_fingerprint_;
|
||||||
bool cached_songs_missing_fingerprint_dirty_;
|
bool cached_songs_missing_fingerprint_dirty_;
|
||||||
|
|
||||||
SubdirectoryList known_subdirs_;
|
CollectionSubdirectoryList known_subdirs_;
|
||||||
bool known_subdirs_dirty_;
|
bool known_subdirs_dirty_;
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -166,9 +167,9 @@ class CollectionWatcher : public QObject {
|
|||||||
void IncrementalScanCheck();
|
void IncrementalScanCheck();
|
||||||
void IncrementalScanNow();
|
void IncrementalScanNow();
|
||||||
void FullScanNow();
|
void FullScanNow();
|
||||||
void RescanTracksNow();
|
|
||||||
void RescanPathsNow();
|
void RescanPathsNow();
|
||||||
void ScanSubdirectory(const QString &path, const Subdirectory &subdir, const quint64 files_count, CollectionWatcher::ScanTransaction *t, const bool force_noincremental = false);
|
void ScanSubdirectory(const QString &path, const CollectionSubdirectory &subdir, const quint64 files_count, CollectionWatcher::ScanTransaction *t, const bool force_noincremental = false);
|
||||||
|
void RescanSongs(const SongList &songs);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
static bool FindSongsByPath(const SongList &songs, const QString &path, SongList *out);
|
static bool FindSongsByPath(const SongList &songs, const QString &path, SongList *out);
|
||||||
@@ -179,8 +180,8 @@ class CollectionWatcher : public QObject {
|
|||||||
inline static QString DirectoryPart(const QString &fileName);
|
inline static QString DirectoryPart(const QString &fileName);
|
||||||
QString PickBestImage(const QStringList &images);
|
QString PickBestImage(const QStringList &images);
|
||||||
QUrl ImageForSong(const QString &path, QMap<QString, QStringList> &album_art);
|
QUrl ImageForSong(const QString &path, QMap<QString, QStringList> &album_art);
|
||||||
void AddWatch(const Directory &dir, const QString &path);
|
void AddWatch(const CollectionDirectory &dir, const QString &path);
|
||||||
void RemoveWatch(const Directory &dir, const Subdirectory &subdir);
|
void RemoveWatch(const CollectionDirectory &dir, const CollectionSubdirectory &subdir);
|
||||||
static quint64 GetMtimeForCue(const QString &cue_path);
|
static quint64 GetMtimeForCue(const QString &cue_path);
|
||||||
void PerformScan(const bool incremental, const bool ignore_mtimes);
|
void PerformScan(const bool incremental, const bool ignore_mtimes);
|
||||||
|
|
||||||
@@ -195,7 +196,7 @@ class CollectionWatcher : public QObject {
|
|||||||
static void AddChangedSong(const QString &file, const Song &matching_song, const Song &new_song, ScanTransaction *t);
|
static void AddChangedSong(const QString &file, const Song &matching_song, const Song &new_song, ScanTransaction *t);
|
||||||
|
|
||||||
quint64 FilesCountForPath(ScanTransaction *t, const QString &path);
|
quint64 FilesCountForPath(ScanTransaction *t, const QString &path);
|
||||||
quint64 FilesCountForSubdirs(ScanTransaction *t, const SubdirectoryList &subdirs, QMap<QString, quint64> &subdir_files_count);
|
quint64 FilesCountForSubdirs(ScanTransaction *t, const CollectionSubdirectoryList &subdirs, QMap<QString, quint64> &subdir_files_count);
|
||||||
|
|
||||||
QString FindCueFilename(const QString &filename);
|
QString FindCueFilename(const QString &filename);
|
||||||
|
|
||||||
@@ -207,7 +208,7 @@ class CollectionWatcher : public QObject {
|
|||||||
|
|
||||||
FileSystemWatcherInterface *fs_watcher_;
|
FileSystemWatcherInterface *fs_watcher_;
|
||||||
QThread *original_thread_;
|
QThread *original_thread_;
|
||||||
QHash<QString, Directory> subdir_mapping_;
|
QHash<QString, CollectionDirectory> subdir_mapping_;
|
||||||
|
|
||||||
// A list of words use to try to identify the (likely) best image found in an directory to use as cover artwork.
|
// A list of words use to try to identify the (likely) best image found in an directory to use as cover artwork.
|
||||||
// e.g. using ["front", "cover"] would identify front.jpg and exclude back.jpg.
|
// e.g. using ["front", "cover"] would identify front.jpg and exclude back.jpg.
|
||||||
@@ -223,9 +224,8 @@ class CollectionWatcher : public QObject {
|
|||||||
|
|
||||||
bool stop_requested_;
|
bool stop_requested_;
|
||||||
bool abort_requested_;
|
bool abort_requested_;
|
||||||
bool rescan_in_progress_; // True if RescanTracksNow() has been called and is working.
|
|
||||||
|
|
||||||
QMap<int, Directory> watched_dirs_;
|
QMap<int, CollectionDirectory> watched_dirs_;
|
||||||
QTimer *rescan_timer_;
|
QTimer *rescan_timer_;
|
||||||
QTimer *periodic_scan_timer_;
|
QTimer *periodic_scan_timer_;
|
||||||
QMap<int, QStringList> rescan_queue_; // dir id -> list of subdirs to be scanned
|
QMap<int, QStringList> rescan_queue_; // dir id -> list of subdirs to be scanned
|
||||||
@@ -237,8 +237,6 @@ class CollectionWatcher : public QObject {
|
|||||||
|
|
||||||
static QStringList sValidImages;
|
static QStringList sValidImages;
|
||||||
|
|
||||||
SongList song_rescan_queue_; // Set by UI thread
|
|
||||||
|
|
||||||
qint64 last_scan_time_;
|
qint64 last_scan_time_;
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,60 +0,0 @@
|
|||||||
/*
|
|
||||||
* Strawberry Music Player
|
|
||||||
* This file was part of Clementine.
|
|
||||||
* Copyright 2010, David Sansome <me@davidsansome.com>
|
|
||||||
*
|
|
||||||
* Strawberry is free software: you can redistribute it and/or modify
|
|
||||||
* it under the terms of the GNU General Public License as published by
|
|
||||||
* the Free Software Foundation, either version 3 of the License, or
|
|
||||||
* (at your option) any later version.
|
|
||||||
*
|
|
||||||
* Strawberry is distributed in the hope that it will be useful,
|
|
||||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
* GNU General Public License for more details.
|
|
||||||
*
|
|
||||||
* You should have received a copy of the GNU General Public License
|
|
||||||
* along with Strawberry. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
|
|
||||||
#ifndef DIRECTORY_H
|
|
||||||
#define DIRECTORY_H
|
|
||||||
|
|
||||||
#include "config.h"
|
|
||||||
|
|
||||||
#include <QMetaType>
|
|
||||||
#include <QList>
|
|
||||||
#include <QString>
|
|
||||||
#include <QSqlQuery>
|
|
||||||
|
|
||||||
struct Directory {
|
|
||||||
Directory() : id(-1) {}
|
|
||||||
|
|
||||||
bool operator==(const Directory &other) const {
|
|
||||||
return path == other.path && id == other.id;
|
|
||||||
}
|
|
||||||
|
|
||||||
QString path;
|
|
||||||
int id;
|
|
||||||
};
|
|
||||||
Q_DECLARE_METATYPE(Directory)
|
|
||||||
|
|
||||||
using DirectoryList = QList<Directory>;
|
|
||||||
Q_DECLARE_METATYPE(DirectoryList)
|
|
||||||
|
|
||||||
|
|
||||||
struct Subdirectory {
|
|
||||||
Subdirectory() : directory_id(-1), mtime(0) {}
|
|
||||||
|
|
||||||
int directory_id;
|
|
||||||
QString path;
|
|
||||||
qint64 mtime;
|
|
||||||
};
|
|
||||||
Q_DECLARE_METATYPE(Subdirectory)
|
|
||||||
|
|
||||||
using SubdirectoryList = QList<Subdirectory>;
|
|
||||||
Q_DECLARE_METATYPE(SubdirectoryList)
|
|
||||||
|
|
||||||
#endif // DIRECTORY_H
|
|
||||||
|
|
||||||
|
|||||||
@@ -74,26 +74,26 @@ GroupByDialog::GroupByDialog(QWidget *parent) : QDialog(parent), ui_(new Ui_Grou
|
|||||||
ui_->setupUi(this);
|
ui_->setupUi(this);
|
||||||
Reset();
|
Reset();
|
||||||
|
|
||||||
p_->mapping_.insert(Mapping(CollectionModel::GroupBy_None, 0));
|
p_->mapping_.insert(Mapping(CollectionModel::GroupBy::None, 0));
|
||||||
p_->mapping_.insert(Mapping(CollectionModel::GroupBy_Artist, 1));
|
p_->mapping_.insert(Mapping(CollectionModel::GroupBy::Artist, 1));
|
||||||
p_->mapping_.insert(Mapping(CollectionModel::GroupBy_AlbumArtist, 2));
|
p_->mapping_.insert(Mapping(CollectionModel::GroupBy::AlbumArtist, 2));
|
||||||
p_->mapping_.insert(Mapping(CollectionModel::GroupBy_Album, 3));
|
p_->mapping_.insert(Mapping(CollectionModel::GroupBy::Album, 3));
|
||||||
p_->mapping_.insert(Mapping(CollectionModel::GroupBy_AlbumDisc, 4));
|
p_->mapping_.insert(Mapping(CollectionModel::GroupBy::AlbumDisc, 4));
|
||||||
p_->mapping_.insert(Mapping(CollectionModel::GroupBy_Disc, 5));
|
p_->mapping_.insert(Mapping(CollectionModel::GroupBy::Disc, 5));
|
||||||
p_->mapping_.insert(Mapping(CollectionModel::GroupBy_Format, 6));
|
p_->mapping_.insert(Mapping(CollectionModel::GroupBy::Format, 6));
|
||||||
p_->mapping_.insert(Mapping(CollectionModel::GroupBy_Genre, 7));
|
p_->mapping_.insert(Mapping(CollectionModel::GroupBy::Genre, 7));
|
||||||
p_->mapping_.insert(Mapping(CollectionModel::GroupBy_Year, 8));
|
p_->mapping_.insert(Mapping(CollectionModel::GroupBy::Year, 8));
|
||||||
p_->mapping_.insert(Mapping(CollectionModel::GroupBy_YearAlbum, 9));
|
p_->mapping_.insert(Mapping(CollectionModel::GroupBy::YearAlbum, 9));
|
||||||
p_->mapping_.insert(Mapping(CollectionModel::GroupBy_YearAlbumDisc, 10));
|
p_->mapping_.insert(Mapping(CollectionModel::GroupBy::YearAlbumDisc, 10));
|
||||||
p_->mapping_.insert(Mapping(CollectionModel::GroupBy_OriginalYear, 11));
|
p_->mapping_.insert(Mapping(CollectionModel::GroupBy::OriginalYear, 11));
|
||||||
p_->mapping_.insert(Mapping(CollectionModel::GroupBy_OriginalYearAlbum, 12));
|
p_->mapping_.insert(Mapping(CollectionModel::GroupBy::OriginalYearAlbum, 12));
|
||||||
p_->mapping_.insert(Mapping(CollectionModel::GroupBy_Composer, 13));
|
p_->mapping_.insert(Mapping(CollectionModel::GroupBy::Composer, 13));
|
||||||
p_->mapping_.insert(Mapping(CollectionModel::GroupBy_Performer, 14));
|
p_->mapping_.insert(Mapping(CollectionModel::GroupBy::Performer, 14));
|
||||||
p_->mapping_.insert(Mapping(CollectionModel::GroupBy_Grouping, 15));
|
p_->mapping_.insert(Mapping(CollectionModel::GroupBy::Grouping, 15));
|
||||||
p_->mapping_.insert(Mapping(CollectionModel::GroupBy_FileType, 16));
|
p_->mapping_.insert(Mapping(CollectionModel::GroupBy::FileType, 16));
|
||||||
p_->mapping_.insert(Mapping(CollectionModel::GroupBy_Samplerate, 17));
|
p_->mapping_.insert(Mapping(CollectionModel::GroupBy::Samplerate, 17));
|
||||||
p_->mapping_.insert(Mapping(CollectionModel::GroupBy_Bitdepth, 18));
|
p_->mapping_.insert(Mapping(CollectionModel::GroupBy::Bitdepth, 18));
|
||||||
p_->mapping_.insert(Mapping(CollectionModel::GroupBy_Bitrate, 19));
|
p_->mapping_.insert(Mapping(CollectionModel::GroupBy::Bitrate, 19));
|
||||||
|
|
||||||
QObject::connect(ui_->buttonbox->button(QDialogButtonBox::Reset), &QPushButton::clicked, this, &GroupByDialog::Reset);
|
QObject::connect(ui_->buttonbox->button(QDialogButtonBox::Reset), &QPushButton::clicked, this, &GroupByDialog::Reset);
|
||||||
|
|
||||||
|
|||||||
@@ -36,6 +36,7 @@
|
|||||||
|
|
||||||
#include "core/logging.h"
|
#include "core/logging.h"
|
||||||
#include "core/iconloader.h"
|
#include "core/iconloader.h"
|
||||||
|
#include "settings/collectionsettingspage.h"
|
||||||
#include "collectionmodel.h"
|
#include "collectionmodel.h"
|
||||||
#include "savedgroupingmanager.h"
|
#include "savedgroupingmanager.h"
|
||||||
#include "ui_savedgroupingmanager.h"
|
#include "ui_savedgroupingmanager.h"
|
||||||
@@ -83,68 +84,68 @@ QString SavedGroupingManager::GetSavedGroupingsSettingsGroup(const QString &sett
|
|||||||
QString SavedGroupingManager::GroupByToString(const CollectionModel::GroupBy g) {
|
QString SavedGroupingManager::GroupByToString(const CollectionModel::GroupBy g) {
|
||||||
|
|
||||||
switch (g) {
|
switch (g) {
|
||||||
case CollectionModel::GroupBy_None:
|
case CollectionModel::GroupBy::None:
|
||||||
case CollectionModel::GroupByCount: {
|
case CollectionModel::GroupBy::GroupByCount: {
|
||||||
return tr("None");
|
return tr("None");
|
||||||
}
|
}
|
||||||
case CollectionModel::GroupBy_AlbumArtist: {
|
case CollectionModel::GroupBy::AlbumArtist: {
|
||||||
return tr("Album artist");
|
return tr("Album artist");
|
||||||
}
|
}
|
||||||
case CollectionModel::GroupBy_Artist: {
|
case CollectionModel::GroupBy::Artist: {
|
||||||
return tr("Artist");
|
return tr("Artist");
|
||||||
}
|
}
|
||||||
case CollectionModel::GroupBy_Album: {
|
case CollectionModel::GroupBy::Album: {
|
||||||
return tr("Album");
|
return tr("Album");
|
||||||
}
|
}
|
||||||
case CollectionModel::GroupBy_AlbumDisc: {
|
case CollectionModel::GroupBy::AlbumDisc: {
|
||||||
return tr("Album - Disc");
|
return tr("Album - Disc");
|
||||||
}
|
}
|
||||||
case CollectionModel::GroupBy_YearAlbum: {
|
case CollectionModel::GroupBy::YearAlbum: {
|
||||||
return tr("Year - Album");
|
return tr("Year - Album");
|
||||||
}
|
}
|
||||||
case CollectionModel::GroupBy_YearAlbumDisc: {
|
case CollectionModel::GroupBy::YearAlbumDisc: {
|
||||||
return tr("Year - Album - Disc");
|
return tr("Year - Album - Disc");
|
||||||
}
|
}
|
||||||
case CollectionModel::GroupBy_OriginalYearAlbum: {
|
case CollectionModel::GroupBy::OriginalYearAlbum: {
|
||||||
return tr("Original year - Album");
|
return tr("Original year - Album");
|
||||||
}
|
}
|
||||||
case CollectionModel::GroupBy_OriginalYearAlbumDisc: {
|
case CollectionModel::GroupBy::OriginalYearAlbumDisc: {
|
||||||
return tr("Original year - Album - Disc");
|
return tr("Original year - Album - Disc");
|
||||||
}
|
}
|
||||||
case CollectionModel::GroupBy_Disc: {
|
case CollectionModel::GroupBy::Disc: {
|
||||||
return tr("Disc");
|
return tr("Disc");
|
||||||
}
|
}
|
||||||
case CollectionModel::GroupBy_Year: {
|
case CollectionModel::GroupBy::Year: {
|
||||||
return tr("Year");
|
return tr("Year");
|
||||||
}
|
}
|
||||||
case CollectionModel::GroupBy_OriginalYear: {
|
case CollectionModel::GroupBy::OriginalYear: {
|
||||||
return tr("Original year");
|
return tr("Original year");
|
||||||
}
|
}
|
||||||
case CollectionModel::GroupBy_Genre: {
|
case CollectionModel::GroupBy::Genre: {
|
||||||
return tr("Genre");
|
return tr("Genre");
|
||||||
}
|
}
|
||||||
case CollectionModel::GroupBy_Composer: {
|
case CollectionModel::GroupBy::Composer: {
|
||||||
return tr("Composer");
|
return tr("Composer");
|
||||||
}
|
}
|
||||||
case CollectionModel::GroupBy_Performer: {
|
case CollectionModel::GroupBy::Performer: {
|
||||||
return tr("Performer");
|
return tr("Performer");
|
||||||
}
|
}
|
||||||
case CollectionModel::GroupBy_Grouping: {
|
case CollectionModel::GroupBy::Grouping: {
|
||||||
return tr("Grouping");
|
return tr("Grouping");
|
||||||
}
|
}
|
||||||
case CollectionModel::GroupBy_FileType: {
|
case CollectionModel::GroupBy::FileType: {
|
||||||
return tr("File type");
|
return tr("File type");
|
||||||
}
|
}
|
||||||
case CollectionModel::GroupBy_Format: {
|
case CollectionModel::GroupBy::Format: {
|
||||||
return tr("Format");
|
return tr("Format");
|
||||||
}
|
}
|
||||||
case CollectionModel::GroupBy_Samplerate: {
|
case CollectionModel::GroupBy::Samplerate: {
|
||||||
return tr("Sample rate");
|
return tr("Sample rate");
|
||||||
}
|
}
|
||||||
case CollectionModel::GroupBy_Bitdepth: {
|
case CollectionModel::GroupBy::Bitdepth: {
|
||||||
return tr("Bit depth");
|
return tr("Bit depth");
|
||||||
}
|
}
|
||||||
case CollectionModel::GroupBy_Bitrate: {
|
case CollectionModel::GroupBy::Bitrate: {
|
||||||
return tr("Bitrate");
|
return tr("Bitrate");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -37,7 +37,7 @@
|
|||||||
#include <QContextMenuEvent>
|
#include <QContextMenuEvent>
|
||||||
#include <QPaintEvent>
|
#include <QPaintEvent>
|
||||||
|
|
||||||
#include "core/imageutils.h"
|
#include "utilities/imageutils.h"
|
||||||
#include "covermanager/albumcoverchoicecontroller.h"
|
#include "covermanager/albumcoverchoicecontroller.h"
|
||||||
|
|
||||||
#include "contextview.h"
|
#include "contextview.h"
|
||||||
@@ -63,7 +63,7 @@ ContextAlbum::ContextAlbum(QWidget *parent)
|
|||||||
cover_loader_options_.desired_height_ = width();
|
cover_loader_options_.desired_height_ = width();
|
||||||
cover_loader_options_.pad_output_image_ = true;
|
cover_loader_options_.pad_output_image_ = true;
|
||||||
cover_loader_options_.scale_output_image_ = true;
|
cover_loader_options_.scale_output_image_ = true;
|
||||||
QImage image = ImageUtils::ScaleAndPad(image_strawberry_, cover_loader_options_.scale_output_image_, cover_loader_options_.pad_output_image_, cover_loader_options_.desired_height_);
|
QImage image = ImageUtils::ScaleAndPad(image_strawberry_, cover_loader_options_.scale_output_image_, cover_loader_options_.pad_output_image_, cover_loader_options_.desired_height_, devicePixelRatioF());
|
||||||
if (!image.isNull()) {
|
if (!image.isNull()) {
|
||||||
pixmap_current_ = QPixmap::fromImage(image);
|
pixmap_current_ = QPixmap::fromImage(image);
|
||||||
}
|
}
|
||||||
@@ -91,7 +91,7 @@ void ContextAlbum::Init(ContextView *context_view, AlbumCoverChoiceController *a
|
|||||||
|
|
||||||
QSize ContextAlbum::sizeHint() const {
|
QSize ContextAlbum::sizeHint() const {
|
||||||
|
|
||||||
return QSize(pixmap_current_.width(), pixmap_current_.height());
|
return QSize(pixmap_current_.width() / devicePixelRatioF(), pixmap_current_.height() / devicePixelRatioF());
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -182,7 +182,7 @@ void ContextAlbum::DrawImage(QPainter *p, const QPixmap &pixmap, const qreal opa
|
|||||||
if (qFuzzyCompare(opacity, static_cast<qreal>(0.0))) return;
|
if (qFuzzyCompare(opacity, static_cast<qreal>(0.0))) return;
|
||||||
|
|
||||||
p->setOpacity(opacity);
|
p->setOpacity(opacity);
|
||||||
p->drawPixmap(0, 0, pixmap.width(), pixmap.height(), pixmap);
|
p->drawPixmap(0, 0, pixmap.width() / pixmap.devicePixelRatioF(), pixmap.height() / pixmap.devicePixelRatioF(), pixmap);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -235,7 +235,7 @@ void ContextAlbum::FadePreviousCoverFinished(std::shared_ptr<PreviousCover> prev
|
|||||||
|
|
||||||
void ContextAlbum::ScaleCover() {
|
void ContextAlbum::ScaleCover() {
|
||||||
|
|
||||||
QImage image = ImageUtils::ScaleAndPad(image_original_, cover_loader_options_.scale_output_image_, cover_loader_options_.pad_output_image_, cover_loader_options_.desired_height_);
|
QImage image = ImageUtils::ScaleAndPad(image_original_, cover_loader_options_.scale_output_image_, cover_loader_options_.pad_output_image_, cover_loader_options_.desired_height_, devicePixelRatioF());
|
||||||
if (image.isNull()) {
|
if (image.isNull()) {
|
||||||
pixmap_current_ = QPixmap();
|
pixmap_current_ = QPixmap();
|
||||||
}
|
}
|
||||||
@@ -248,7 +248,7 @@ void ContextAlbum::ScaleCover() {
|
|||||||
void ContextAlbum::ScalePreviousCovers() {
|
void ContextAlbum::ScalePreviousCovers() {
|
||||||
|
|
||||||
for (std::shared_ptr<PreviousCover> previous_cover : previous_covers_) {
|
for (std::shared_ptr<PreviousCover> previous_cover : previous_covers_) {
|
||||||
QImage image = ImageUtils::ScaleAndPad(previous_cover->image, cover_loader_options_.scale_output_image_, cover_loader_options_.pad_output_image_, cover_loader_options_.desired_height_);
|
QImage image = ImageUtils::ScaleAndPad(previous_cover->image, cover_loader_options_.scale_output_image_, cover_loader_options_.pad_output_image_, cover_loader_options_.desired_height_, devicePixelRatioF());
|
||||||
if (image.isNull()) {
|
if (image.isNull()) {
|
||||||
previous_cover->pixmap = QPixmap();
|
previous_cover->pixmap = QPixmap();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -51,8 +51,9 @@
|
|||||||
#include "core/application.h"
|
#include "core/application.h"
|
||||||
#include "core/player.h"
|
#include "core/player.h"
|
||||||
#include "core/song.h"
|
#include "core/song.h"
|
||||||
#include "core/utilities.h"
|
|
||||||
#include "core/iconloader.h"
|
#include "core/iconloader.h"
|
||||||
|
#include "utilities/strutils.h"
|
||||||
|
#include "utilities/timeutils.h"
|
||||||
#include "widgets/resizabletextedit.h"
|
#include "widgets/resizabletextedit.h"
|
||||||
#include "engine/engine_fwd.h"
|
#include "engine/engine_fwd.h"
|
||||||
#include "engine/enginebase.h"
|
#include "engine/enginebase.h"
|
||||||
@@ -337,11 +338,11 @@ void ContextView::ReloadSettings() {
|
|||||||
s.beginGroup(ContextSettingsPage::kSettingsGroup);
|
s.beginGroup(ContextSettingsPage::kSettingsGroup);
|
||||||
title_fmt_ = s.value(ContextSettingsPage::kSettingsTitleFmt, "%title% - %artist%").toString();
|
title_fmt_ = s.value(ContextSettingsPage::kSettingsTitleFmt, "%title% - %artist%").toString();
|
||||||
summary_fmt_ = s.value(ContextSettingsPage::kSettingsSummaryFmt, "%album%").toString();
|
summary_fmt_ = s.value(ContextSettingsPage::kSettingsSummaryFmt, "%album%").toString();
|
||||||
action_show_album_->setChecked(s.value(ContextSettingsPage::kSettingsGroupEnable[ContextSettingsPage::ContextSettingsOrder::ALBUM], true).toBool());
|
action_show_album_->setChecked(s.value(ContextSettingsPage::kSettingsGroupEnable[static_cast<int>(ContextSettingsPage::ContextSettingsOrder::ALBUM)], true).toBool());
|
||||||
action_show_data_->setChecked(s.value(ContextSettingsPage::kSettingsGroupEnable[ContextSettingsPage::ContextSettingsOrder::TECHNICAL_DATA], false).toBool());
|
action_show_data_->setChecked(s.value(ContextSettingsPage::kSettingsGroupEnable[static_cast<int>(ContextSettingsPage::ContextSettingsOrder::TECHNICAL_DATA)], false).toBool());
|
||||||
action_show_output_->setChecked(s.value(ContextSettingsPage::kSettingsGroupEnable[ContextSettingsPage::ContextSettingsOrder::ENGINE_AND_DEVICE], false).toBool());
|
action_show_output_->setChecked(s.value(ContextSettingsPage::kSettingsGroupEnable[static_cast<int>(ContextSettingsPage::ContextSettingsOrder::ENGINE_AND_DEVICE)], false).toBool());
|
||||||
action_show_lyrics_->setChecked(s.value(ContextSettingsPage::kSettingsGroupEnable[ContextSettingsPage::ContextSettingsOrder::SONG_LYRICS], true).toBool());
|
action_show_lyrics_->setChecked(s.value(ContextSettingsPage::kSettingsGroupEnable[static_cast<int>(ContextSettingsPage::ContextSettingsOrder::SONG_LYRICS)], true).toBool());
|
||||||
action_search_lyrics_->setChecked(s.value(ContextSettingsPage::kSettingsGroupEnable[ContextSettingsPage::ContextSettingsOrder::SEARCH_LYRICS], true).toBool());
|
action_search_lyrics_->setChecked(s.value(ContextSettingsPage::kSettingsGroupEnable[static_cast<int>(ContextSettingsPage::ContextSettingsOrder::SEARCH_LYRICS)], true).toBool());
|
||||||
font_headline_ = s.value("font_headline", font().family()).toString();
|
font_headline_ = s.value("font_headline", font().family()).toString();
|
||||||
font_normal_ = s.value("font_normal", font().family()).toString();
|
font_normal_ = s.value("font_normal", font().family()).toString();
|
||||||
font_size_headline_ = s.value("font_size_headline", ContextSettingsPage::kDefaultFontSizeHeadline).toReal();
|
font_size_headline_ = s.value("font_size_headline", ContextSettingsPage::kDefaultFontSizeHeadline).toReal();
|
||||||
@@ -406,7 +407,7 @@ void ContextView::SearchLyrics() {
|
|||||||
if (lyrics_.isEmpty() && action_show_lyrics_->isChecked() && action_search_lyrics_->isChecked() && !song_playing_.artist().isEmpty() && !song_playing_.title().isEmpty() && !lyrics_tried_ && lyrics_id_ == -1) {
|
if (lyrics_.isEmpty() && action_show_lyrics_->isChecked() && action_search_lyrics_->isChecked() && !song_playing_.artist().isEmpty() && !song_playing_.title().isEmpty() && !lyrics_tried_ && lyrics_id_ == -1) {
|
||||||
lyrics_fetcher_->Clear();
|
lyrics_fetcher_->Clear();
|
||||||
lyrics_tried_ = true;
|
lyrics_tried_ = true;
|
||||||
lyrics_id_ = static_cast<qint64>(lyrics_fetcher_->Search(song_playing_.effective_albumartist(), song_playing_.album(), song_playing_.title()));
|
lyrics_id_ = static_cast<qint64>(lyrics_fetcher_->Search(song_playing_.effective_albumartist(), song_playing_.artist(), song_playing_.album(), song_playing_.title()));
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -434,8 +435,8 @@ void ContextView::NoSong() {
|
|||||||
widget_album_->show();
|
widget_album_->show();
|
||||||
}
|
}
|
||||||
|
|
||||||
textedit_top_->setFont(QFont(font_headline_, font_size_headline_ * 1.6));
|
textedit_top_->setFont(QFont(font_headline_, static_cast<int>(font_size_headline_ * 1.6)));
|
||||||
textedit_top_->setText(tr("No song playing"));
|
textedit_top_->SetText(tr("No song playing"));
|
||||||
|
|
||||||
QString html;
|
QString html;
|
||||||
if (collectionview_->TotalSongs() == 1) html += tr("%1 song").arg(collectionview_->TotalSongs());
|
if (collectionview_->TotalSongs() == 1) html += tr("%1 song").arg(collectionview_->TotalSongs());
|
||||||
@@ -450,14 +451,14 @@ void ContextView::NoSong() {
|
|||||||
else html += tr("%1 albums").arg(collectionview_->TotalAlbums());
|
else html += tr("%1 albums").arg(collectionview_->TotalAlbums());
|
||||||
html += "<br />";
|
html += "<br />";
|
||||||
|
|
||||||
label_stop_summary_->setFont(QFont(font_normal_, font_size_normal_));
|
label_stop_summary_->setFont(QFont(font_normal_, static_cast<int>(font_size_normal_)));
|
||||||
label_stop_summary_->setText(html);
|
label_stop_summary_->setText(html);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void ContextView::UpdateFonts() {
|
void ContextView::UpdateFonts() {
|
||||||
|
|
||||||
QFont font(font_normal_, font_size_normal_);
|
QFont font(font_normal_, static_cast<int>(font_size_normal_));
|
||||||
font.setBold(false);
|
font.setBold(false);
|
||||||
for (QLabel *l : labels_play_all_) {
|
for (QLabel *l : labels_play_all_) {
|
||||||
l->setFont(font);
|
l->setFont(font);
|
||||||
@@ -470,9 +471,8 @@ void ContextView::UpdateFonts() {
|
|||||||
|
|
||||||
void ContextView::SetSong() {
|
void ContextView::SetSong() {
|
||||||
|
|
||||||
|
textedit_top_->setFont(QFont(font_headline_, static_cast<int>(font_size_headline_)));
|
||||||
textedit_top_->setFont(QFont(font_headline_, font_size_headline_));
|
textedit_top_->SetText(QString("<b>%1</b><br />%2").arg(Utilities::ReplaceMessage(title_fmt_, song_playing_, "<br />", true), Utilities::ReplaceMessage(summary_fmt_, song_playing_, "<br />", true)));
|
||||||
textedit_top_->setText(QString("<b>%1</b><br />%2").arg(Utilities::ReplaceMessage(title_fmt_, song_playing_, "<br />", true), Utilities::ReplaceMessage(summary_fmt_, song_playing_, "<br />", true)));
|
|
||||||
|
|
||||||
label_stop_summary_->clear();
|
label_stop_summary_->clear();
|
||||||
|
|
||||||
@@ -544,9 +544,9 @@ void ContextView::SetSong() {
|
|||||||
|
|
||||||
if (action_show_output_->isChecked()) {
|
if (action_show_output_->isChecked()) {
|
||||||
widget_play_output_->show();
|
widget_play_output_->show();
|
||||||
Engine::EngineType enginetype(Engine::None);
|
Engine::EngineType enginetype(Engine::EngineType::None);
|
||||||
if (app_->player()->engine()) enginetype = app_->player()->engine()->type();
|
if (app_->player()->engine()) enginetype = app_->player()->engine()->type();
|
||||||
QIcon icon_engine = IconLoader::Load(EngineName(enginetype), 32);
|
QIcon icon_engine = IconLoader::Load(EngineName(enginetype), true, 32);
|
||||||
|
|
||||||
label_engine_icon_->setPixmap(icon_engine.pixmap(QSize(32, 32)));
|
label_engine_icon_->setPixmap(icon_engine.pixmap(QSize(32, 32)));
|
||||||
label_engine_->setText(EngineDescription(enginetype));
|
label_engine_->setText(EngineDescription(enginetype));
|
||||||
@@ -564,7 +564,7 @@ void ContextView::SetSong() {
|
|||||||
label_device_title_->show();
|
label_device_title_->show();
|
||||||
label_device_icon_->show();
|
label_device_icon_->show();
|
||||||
label_device_->show();
|
label_device_->show();
|
||||||
QIcon icon_device = IconLoader::Load(device.iconname, 32);
|
QIcon icon_device = IconLoader::Load(device.iconname, true, 32);
|
||||||
label_device_icon_->setPixmap(icon_device.pixmap(QSize(32, 32)));
|
label_device_icon_->setPixmap(icon_device.pixmap(QSize(32, 32)));
|
||||||
label_device_->setText(device.description);
|
label_device_->setText(device.description);
|
||||||
}
|
}
|
||||||
@@ -586,7 +586,7 @@ void ContextView::SetSong() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (action_show_lyrics_->isChecked() && !lyrics_.isEmpty()) {
|
if (action_show_lyrics_->isChecked() && !lyrics_.isEmpty()) {
|
||||||
textedit_play_lyrics_->setText(lyrics_);
|
textedit_play_lyrics_->SetText(lyrics_);
|
||||||
textedit_play_lyrics_->show();
|
textedit_play_lyrics_->show();
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
@@ -601,7 +601,7 @@ void ContextView::SetSong() {
|
|||||||
|
|
||||||
void ContextView::UpdateSong(const Song &song) {
|
void ContextView::UpdateSong(const Song &song) {
|
||||||
|
|
||||||
textedit_top_->setText(QString("<b>%1</b><br />%2").arg(Utilities::ReplaceMessage(title_fmt_, song, "<br />", true), Utilities::ReplaceMessage(summary_fmt_, song, "<br />", true)));
|
textedit_top_->SetText(QString("<b>%1</b><br />%2").arg(Utilities::ReplaceMessage(title_fmt_, song, "<br />", true), Utilities::ReplaceMessage(summary_fmt_, song, "<br />", true)));
|
||||||
|
|
||||||
if (action_show_data_->isChecked()) {
|
if (action_show_data_->isChecked()) {
|
||||||
if (song.filetype() != song_playing_.filetype()) label_filetype_->setText(song.TextForFiletype());
|
if (song.filetype() != song_playing_.filetype()) label_filetype_->setText(song.TextForFiletype());
|
||||||
@@ -657,6 +657,8 @@ void ContextView::UpdateSong(const Song &song) {
|
|||||||
|
|
||||||
song_playing_ = song;
|
song_playing_ = song;
|
||||||
|
|
||||||
|
widget_stacked_->updateGeometry();
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void ContextView::ResetSong() {
|
void ContextView::ResetSong() {
|
||||||
@@ -683,7 +685,7 @@ void ContextView::UpdateLyrics(const quint64 id, const QString &provider, const
|
|||||||
lyrics_id_ = -1;
|
lyrics_id_ = -1;
|
||||||
|
|
||||||
if (action_show_lyrics_->isChecked() && !lyrics_.isEmpty()) {
|
if (action_show_lyrics_->isChecked() && !lyrics_.isEmpty()) {
|
||||||
textedit_play_lyrics_->setText(lyrics_);
|
textedit_play_lyrics_->SetText(lyrics_);
|
||||||
textedit_play_lyrics_->show();
|
textedit_play_lyrics_->show();
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
@@ -737,7 +739,7 @@ void ContextView::ActionShowAlbum() {
|
|||||||
|
|
||||||
QSettings s;
|
QSettings s;
|
||||||
s.beginGroup(ContextSettingsPage::kSettingsGroup);
|
s.beginGroup(ContextSettingsPage::kSettingsGroup);
|
||||||
s.setValue(ContextSettingsPage::kSettingsGroupEnable[ContextSettingsPage::ContextSettingsOrder::ALBUM], action_show_album_->isChecked());
|
s.setValue(ContextSettingsPage::kSettingsGroupEnable[static_cast<int>(ContextSettingsPage::ContextSettingsOrder::ALBUM)], action_show_album_->isChecked());
|
||||||
s.endGroup();
|
s.endGroup();
|
||||||
if (song_playing_.is_valid()) SetSong();
|
if (song_playing_.is_valid()) SetSong();
|
||||||
|
|
||||||
@@ -747,7 +749,7 @@ void ContextView::ActionShowData() {
|
|||||||
|
|
||||||
QSettings s;
|
QSettings s;
|
||||||
s.beginGroup(ContextSettingsPage::kSettingsGroup);
|
s.beginGroup(ContextSettingsPage::kSettingsGroup);
|
||||||
s.setValue(ContextSettingsPage::kSettingsGroupEnable[ContextSettingsPage::ContextSettingsOrder::TECHNICAL_DATA], action_show_data_->isChecked());
|
s.setValue(ContextSettingsPage::kSettingsGroupEnable[static_cast<int>(ContextSettingsPage::ContextSettingsOrder::TECHNICAL_DATA)], action_show_data_->isChecked());
|
||||||
s.endGroup();
|
s.endGroup();
|
||||||
if (song_playing_.is_valid()) SetSong();
|
if (song_playing_.is_valid()) SetSong();
|
||||||
|
|
||||||
@@ -757,7 +759,7 @@ void ContextView::ActionShowOutput() {
|
|||||||
|
|
||||||
QSettings s;
|
QSettings s;
|
||||||
s.beginGroup(ContextSettingsPage::kSettingsGroup);
|
s.beginGroup(ContextSettingsPage::kSettingsGroup);
|
||||||
s.setValue(ContextSettingsPage::kSettingsGroupEnable[ContextSettingsPage::ContextSettingsOrder::ENGINE_AND_DEVICE], action_show_output_->isChecked());
|
s.setValue(ContextSettingsPage::kSettingsGroupEnable[static_cast<int>(ContextSettingsPage::ContextSettingsOrder::ENGINE_AND_DEVICE)], action_show_output_->isChecked());
|
||||||
s.endGroup();
|
s.endGroup();
|
||||||
if (song_playing_.is_valid()) SetSong();
|
if (song_playing_.is_valid()) SetSong();
|
||||||
|
|
||||||
@@ -767,7 +769,7 @@ void ContextView::ActionShowLyrics() {
|
|||||||
|
|
||||||
QSettings s;
|
QSettings s;
|
||||||
s.beginGroup(ContextSettingsPage::kSettingsGroup);
|
s.beginGroup(ContextSettingsPage::kSettingsGroup);
|
||||||
s.setValue(ContextSettingsPage::kSettingsGroupEnable[ContextSettingsPage::ContextSettingsOrder::SONG_LYRICS], action_show_lyrics_->isChecked());
|
s.setValue(ContextSettingsPage::kSettingsGroupEnable[static_cast<int>(ContextSettingsPage::ContextSettingsOrder::SONG_LYRICS)], action_show_lyrics_->isChecked());
|
||||||
s.endGroup();
|
s.endGroup();
|
||||||
|
|
||||||
if (song_playing_.is_valid()) SetSong();
|
if (song_playing_.is_valid()) SetSong();
|
||||||
@@ -780,7 +782,7 @@ void ContextView::ActionSearchLyrics() {
|
|||||||
|
|
||||||
QSettings s;
|
QSettings s;
|
||||||
s.beginGroup(ContextSettingsPage::kSettingsGroup);
|
s.beginGroup(ContextSettingsPage::kSettingsGroup);
|
||||||
s.setValue(ContextSettingsPage::kSettingsGroupEnable[ContextSettingsPage::ContextSettingsOrder::SEARCH_LYRICS], action_search_lyrics_->isChecked());
|
s.setValue(ContextSettingsPage::kSettingsGroupEnable[static_cast<int>(ContextSettingsPage::ContextSettingsOrder::SEARCH_LYRICS)], action_search_lyrics_->isChecked());
|
||||||
s.endGroup();
|
s.endGroup();
|
||||||
|
|
||||||
if (song_playing_.is_valid()) SetSong();
|
if (song_playing_.is_valid()) SetSong();
|
||||||
|
|||||||
@@ -1,92 +0,0 @@
|
|||||||
/*
|
|
||||||
* Strawberry Music Player
|
|
||||||
* This file was part of Clementine.
|
|
||||||
* Copyright 2012, Arnaud Bienner <arnaud.bienner@gmail.com>
|
|
||||||
*
|
|
||||||
* Strawberry is free software: you can redistribute it and/or modify
|
|
||||||
* it under the terms of the GNU General Public License as published by
|
|
||||||
* the Free Software Foundation, either version 3 of the License, or
|
|
||||||
* (at your option) any later version.
|
|
||||||
*
|
|
||||||
* Strawberry is distributed in the hope that it will be useful,
|
|
||||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
* GNU General Public License for more details.
|
|
||||||
*
|
|
||||||
* You should have received a copy of the GNU General Public License
|
|
||||||
* along with Strawberry. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
|
|
||||||
#include "config.h"
|
|
||||||
|
|
||||||
#include <QApplication>
|
|
||||||
#include <QObject>
|
|
||||||
#include <QPalette>
|
|
||||||
#include <QColor>
|
|
||||||
#include <QSettings>
|
|
||||||
|
|
||||||
#include "appearance.h"
|
|
||||||
#include "settings/appearancesettingspage.h"
|
|
||||||
|
|
||||||
const QPalette Appearance::kDefaultPalette = QPalette();
|
|
||||||
|
|
||||||
Appearance::Appearance(QObject *parent) : QObject(parent) {
|
|
||||||
|
|
||||||
QPalette p = QApplication::palette();
|
|
||||||
|
|
||||||
QSettings s;
|
|
||||||
s.beginGroup(AppearanceSettingsPage::kSettingsGroup);
|
|
||||||
background_color_ = s.value(AppearanceSettingsPage::kBackgroundColor, p.color(QPalette::WindowText)).value<QColor>();
|
|
||||||
foreground_color_ = s.value(AppearanceSettingsPage::kForegroundColor, p.color(QPalette::Window)).value<QColor>();
|
|
||||||
s.endGroup();
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
void Appearance::LoadUserTheme() {
|
|
||||||
|
|
||||||
QSettings s;
|
|
||||||
s.beginGroup(AppearanceSettingsPage::kSettingsGroup);
|
|
||||||
bool use_a_custom_color_set = s.value(AppearanceSettingsPage::kUseCustomColorSet).toBool();
|
|
||||||
s.endGroup();
|
|
||||||
|
|
||||||
if (use_a_custom_color_set) {
|
|
||||||
ChangeForegroundColor(foreground_color_);
|
|
||||||
ChangeBackgroundColor(background_color_);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
void Appearance::ResetToSystemDefaultTheme() {
|
|
||||||
QApplication::setPalette(kDefaultPalette);
|
|
||||||
}
|
|
||||||
|
|
||||||
void Appearance::ChangeForegroundColor(const QColor &color) {
|
|
||||||
|
|
||||||
// Get the application palette
|
|
||||||
QPalette p = QApplication::palette();
|
|
||||||
|
|
||||||
// Modify the palette
|
|
||||||
p.setColor(QPalette::WindowText, color);
|
|
||||||
p.setColor(QPalette::Text, color);
|
|
||||||
|
|
||||||
// Make the modified palette the new application's palette
|
|
||||||
QApplication::setPalette(p);
|
|
||||||
foreground_color_ = color;
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
void Appearance::ChangeBackgroundColor(const QColor &color) {
|
|
||||||
|
|
||||||
// Get the application palette
|
|
||||||
QPalette p = QApplication::palette();
|
|
||||||
|
|
||||||
// Modify the palette
|
|
||||||
p.setColor(QPalette::Window, color);
|
|
||||||
p.setColor(QPalette::Base, color);
|
|
||||||
|
|
||||||
// Make the modified palette the new application's palette
|
|
||||||
QApplication::setPalette(p);
|
|
||||||
background_color_ = color;
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -38,7 +38,6 @@
|
|||||||
#include "database.h"
|
#include "database.h"
|
||||||
#include "taskmanager.h"
|
#include "taskmanager.h"
|
||||||
#include "player.h"
|
#include "player.h"
|
||||||
#include "appearance.h"
|
|
||||||
|
|
||||||
#include "engine/devicefinders.h"
|
#include "engine/devicefinders.h"
|
||||||
#ifndef Q_OS_WIN
|
#ifndef Q_OS_WIN
|
||||||
@@ -64,6 +63,7 @@
|
|||||||
#include "lyrics/lololyricsprovider.h"
|
#include "lyrics/lololyricsprovider.h"
|
||||||
#include "lyrics/musixmatchlyricsprovider.h"
|
#include "lyrics/musixmatchlyricsprovider.h"
|
||||||
#include "lyrics/chartlyricsprovider.h"
|
#include "lyrics/chartlyricsprovider.h"
|
||||||
|
#include "lyrics/lyricscomlyricsprovider.h"
|
||||||
|
|
||||||
#include "scrobbler/audioscrobbler.h"
|
#include "scrobbler/audioscrobbler.h"
|
||||||
#include "scrobbler/lastfmimport.h"
|
#include "scrobbler/lastfmimport.h"
|
||||||
@@ -109,7 +109,6 @@ class ApplicationImpl {
|
|||||||
QTimer::singleShot(30s, db, &Database::DoBackup);
|
QTimer::singleShot(30s, db, &Database::DoBackup);
|
||||||
return db;
|
return db;
|
||||||
}),
|
}),
|
||||||
appearance_([app]() { return new Appearance(app); }),
|
|
||||||
task_manager_([app]() { return new TaskManager(app); }),
|
task_manager_([app]() { return new TaskManager(app); }),
|
||||||
player_([app]() { return new Player(app, app); }),
|
player_([app]() { return new Player(app, app); }),
|
||||||
device_finders_([app]() { return new DeviceFinders(app); }),
|
device_finders_([app]() { return new DeviceFinders(app); }),
|
||||||
@@ -156,6 +155,7 @@ class ApplicationImpl {
|
|||||||
lyrics_providers->AddProvider(new LoloLyricsProvider(lyrics_providers->network(), app));
|
lyrics_providers->AddProvider(new LoloLyricsProvider(lyrics_providers->network(), app));
|
||||||
lyrics_providers->AddProvider(new MusixmatchLyricsProvider(lyrics_providers->network(), app));
|
lyrics_providers->AddProvider(new MusixmatchLyricsProvider(lyrics_providers->network(), app));
|
||||||
lyrics_providers->AddProvider(new ChartLyricsProvider(lyrics_providers->network(), app));
|
lyrics_providers->AddProvider(new ChartLyricsProvider(lyrics_providers->network(), app));
|
||||||
|
lyrics_providers->AddProvider(new LyricsComLyricsProvider(lyrics_providers->network(), app));
|
||||||
lyrics_providers->ReloadSettings();
|
lyrics_providers->ReloadSettings();
|
||||||
return lyrics_providers;
|
return lyrics_providers;
|
||||||
}),
|
}),
|
||||||
@@ -183,7 +183,6 @@ class ApplicationImpl {
|
|||||||
|
|
||||||
Lazy<TagReaderClient> tag_reader_client_;
|
Lazy<TagReaderClient> tag_reader_client_;
|
||||||
Lazy<Database> database_;
|
Lazy<Database> database_;
|
||||||
Lazy<Appearance> appearance_;
|
|
||||||
Lazy<TaskManager> task_manager_;
|
Lazy<TaskManager> task_manager_;
|
||||||
Lazy<Player> player_;
|
Lazy<Player> player_;
|
||||||
Lazy<DeviceFinders> device_finders_;
|
Lazy<DeviceFinders> device_finders_;
|
||||||
@@ -315,7 +314,6 @@ void Application::ReloadSettings() { emit SettingsChanged(); }
|
|||||||
void Application::OpenSettingsDialogAtPage(SettingsDialog::Page page) { emit SettingsDialogRequested(page); }
|
void Application::OpenSettingsDialogAtPage(SettingsDialog::Page page) { emit SettingsDialogRequested(page); }
|
||||||
|
|
||||||
TagReaderClient *Application::tag_reader_client() const { return p_->tag_reader_client_.get(); }
|
TagReaderClient *Application::tag_reader_client() const { return p_->tag_reader_client_.get(); }
|
||||||
Appearance *Application::appearance() const { return p_->appearance_.get(); }
|
|
||||||
Database *Application::database() const { return p_->database_.get(); }
|
Database *Application::database() const { return p_->database_.get(); }
|
||||||
TaskManager *Application::task_manager() const { return p_->task_manager_.get(); }
|
TaskManager *Application::task_manager() const { return p_->task_manager_.get(); }
|
||||||
Player *Application::player() const { return p_->player_.get(); }
|
Player *Application::player() const { return p_->player_.get(); }
|
||||||
|
|||||||
@@ -41,7 +41,6 @@ class TagReaderClient;
|
|||||||
class Database;
|
class Database;
|
||||||
class DeviceFinders;
|
class DeviceFinders;
|
||||||
class Player;
|
class Player;
|
||||||
class Appearance;
|
|
||||||
class SCollection;
|
class SCollection;
|
||||||
class CollectionBackend;
|
class CollectionBackend;
|
||||||
class CollectionModel;
|
class CollectionModel;
|
||||||
@@ -73,7 +72,6 @@ class Application : public QObject {
|
|||||||
|
|
||||||
TagReaderClient *tag_reader_client() const;
|
TagReaderClient *tag_reader_client() const;
|
||||||
Database *database() const;
|
Database *database() const;
|
||||||
Appearance *appearance() const;
|
|
||||||
TaskManager *task_manager() const;
|
TaskManager *task_manager() const;
|
||||||
Player *player() const;
|
Player *player() const;
|
||||||
DeviceFinders *device_finders() const;
|
DeviceFinders *device_finders() const;
|
||||||
|
|||||||
@@ -81,8 +81,8 @@ const char *CommandlineOptions::kVersionText = "Strawberry %1";
|
|||||||
CommandlineOptions::CommandlineOptions(int argc, char **argv)
|
CommandlineOptions::CommandlineOptions(int argc, char **argv)
|
||||||
: argc_(argc),
|
: argc_(argc),
|
||||||
argv_(argv),
|
argv_(argv),
|
||||||
url_list_action_(UrlList_None),
|
url_list_action_(UrlListAction::None),
|
||||||
player_action_(Player_None),
|
player_action_(PlayerAction::None),
|
||||||
set_volume_(-1),
|
set_volume_(-1),
|
||||||
volume_modifier_(0),
|
volume_modifier_(0),
|
||||||
seek_to_(-1),
|
seek_to_(-1),
|
||||||
@@ -128,13 +128,13 @@ bool CommandlineOptions::Parse() {
|
|||||||
{"previous", no_argument, nullptr, 'r'},
|
{"previous", no_argument, nullptr, 'r'},
|
||||||
{"next", no_argument, nullptr, 'f'},
|
{"next", no_argument, nullptr, 'f'},
|
||||||
{"volume", required_argument, nullptr, 'v'},
|
{"volume", required_argument, nullptr, 'v'},
|
||||||
{"volume-up", no_argument, nullptr, VolumeUp},
|
{"volume-up", no_argument, nullptr, LongOptions::VolumeUp},
|
||||||
{"volume-down", no_argument, nullptr, VolumeDown},
|
{"volume-down", no_argument, nullptr, LongOptions::VolumeDown},
|
||||||
{"volume-increase-by", required_argument, nullptr, VolumeIncreaseBy},
|
{"volume-increase-by", required_argument, nullptr, LongOptions::VolumeIncreaseBy},
|
||||||
{"volume-decrease-by", required_argument, nullptr, VolumeDecreaseBy},
|
{"volume-decrease-by", required_argument, nullptr, LongOptions::VolumeDecreaseBy},
|
||||||
{"seek-to", required_argument, nullptr, SeekTo},
|
{"seek-to", required_argument, nullptr, LongOptions::SeekTo},
|
||||||
{"seek-by", required_argument, nullptr, SeekBy},
|
{"seek-by", required_argument, nullptr, LongOptions::SeekBy},
|
||||||
{"restart-or-previous", no_argument, nullptr, RestartOrPrevious},
|
{"restart-or-previous", no_argument, nullptr, LongOptions::RestartOrPrevious},
|
||||||
{"create", required_argument, nullptr, 'c'},
|
{"create", required_argument, nullptr, 'c'},
|
||||||
{"append", no_argument, nullptr, 'a'},
|
{"append", no_argument, nullptr, 'a'},
|
||||||
{"load", no_argument, nullptr, 'l'},
|
{"load", no_argument, nullptr, 'l'},
|
||||||
@@ -144,10 +144,10 @@ bool CommandlineOptions::Parse() {
|
|||||||
{"toggle-pretty-osd", no_argument, nullptr, 'y'},
|
{"toggle-pretty-osd", no_argument, nullptr, 'y'},
|
||||||
{"language", required_argument, nullptr, 'g'},
|
{"language", required_argument, nullptr, 'g'},
|
||||||
{"resize-window", required_argument, nullptr, 'w'},
|
{"resize-window", required_argument, nullptr, 'w'},
|
||||||
{"quiet", no_argument, nullptr, Quiet},
|
{"quiet", no_argument, nullptr, LongOptions::Quiet},
|
||||||
{"verbose", no_argument, nullptr, Verbose},
|
{"verbose", no_argument, nullptr, LongOptions::Verbose},
|
||||||
{"log-levels", required_argument, nullptr, LogLevels},
|
{"log-levels", required_argument, nullptr, LongOptions::LogLevels},
|
||||||
{"version", no_argument, nullptr, Version},
|
{"version", no_argument, nullptr, LongOptions::Version},
|
||||||
{nullptr, 0, nullptr, 0}};
|
{nullptr, 0, nullptr, 0}};
|
||||||
|
|
||||||
// Parse the arguments
|
// Parse the arguments
|
||||||
@@ -198,39 +198,39 @@ bool CommandlineOptions::Parse() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
case 'p':
|
case 'p':
|
||||||
player_action_ = Player_Play;
|
player_action_ = PlayerAction::Play;
|
||||||
break;
|
break;
|
||||||
case 't':
|
case 't':
|
||||||
player_action_ = Player_PlayPause;
|
player_action_ = PlayerAction::PlayPause;
|
||||||
break;
|
break;
|
||||||
case 'u':
|
case 'u':
|
||||||
player_action_ = Player_Pause;
|
player_action_ = PlayerAction::Pause;
|
||||||
break;
|
break;
|
||||||
case 's':
|
case 's':
|
||||||
player_action_ = Player_Stop;
|
player_action_ = PlayerAction::Stop;
|
||||||
break;
|
break;
|
||||||
case 'q':
|
case 'q':
|
||||||
player_action_ = Player_StopAfterCurrent;
|
player_action_ = PlayerAction::StopAfterCurrent;
|
||||||
break;
|
break;
|
||||||
case 'r':
|
case 'r':
|
||||||
player_action_ = Player_Previous;
|
player_action_ = PlayerAction::Previous;
|
||||||
break;
|
break;
|
||||||
case 'f':
|
case 'f':
|
||||||
player_action_ = Player_Next;
|
player_action_ = PlayerAction::Next;
|
||||||
break;
|
break;
|
||||||
case 'i':
|
case 'i':
|
||||||
player_action_ = Player_PlayPlaylist;
|
player_action_ = PlayerAction::PlayPlaylist;
|
||||||
playlist_name_ = QString(optarg);
|
playlist_name_ = QString(optarg);
|
||||||
break;
|
break;
|
||||||
case 'c':
|
case 'c':
|
||||||
url_list_action_ = UrlList_CreateNew;
|
url_list_action_ = UrlListAction::CreateNew;
|
||||||
playlist_name_ = QString(optarg);
|
playlist_name_ = QString(optarg);
|
||||||
break;
|
break;
|
||||||
case 'a':
|
case 'a':
|
||||||
url_list_action_ = UrlList_Append;
|
url_list_action_ = UrlListAction::Append;
|
||||||
break;
|
break;
|
||||||
case 'l':
|
case 'l':
|
||||||
url_list_action_ = UrlList_Load;
|
url_list_action_ = UrlListAction::Load;
|
||||||
break;
|
break;
|
||||||
case 'o':
|
case 'o':
|
||||||
show_osd_ = true;
|
show_osd_ = true;
|
||||||
@@ -241,22 +241,22 @@ bool CommandlineOptions::Parse() {
|
|||||||
case 'g':
|
case 'g':
|
||||||
language_ = QString(optarg);
|
language_ = QString(optarg);
|
||||||
break;
|
break;
|
||||||
case VolumeUp:
|
case LongOptions::VolumeUp:
|
||||||
volume_modifier_ = +4;
|
volume_modifier_ = +4;
|
||||||
break;
|
break;
|
||||||
case VolumeDown:
|
case LongOptions::VolumeDown:
|
||||||
volume_modifier_ = -4;
|
volume_modifier_ = -4;
|
||||||
break;
|
break;
|
||||||
case Quiet:
|
case LongOptions::Quiet:
|
||||||
log_levels_ = "1";
|
log_levels_ = "1";
|
||||||
break;
|
break;
|
||||||
case Verbose:
|
case LongOptions::Verbose:
|
||||||
log_levels_ = "3";
|
log_levels_ = "3";
|
||||||
break;
|
break;
|
||||||
case LogLevels:
|
case LongOptions::LogLevels:
|
||||||
log_levels_ = QString(optarg);
|
log_levels_ = QString(optarg);
|
||||||
break;
|
break;
|
||||||
case Version: {
|
case LongOptions::Version: {
|
||||||
QString version_text = QString(kVersionText).arg(STRAWBERRY_VERSION_DISPLAY);
|
QString version_text = QString(kVersionText).arg(STRAWBERRY_VERSION_DISPLAY);
|
||||||
std::cout << version_text.toLocal8Bit().constData() << std::endl;
|
std::cout << version_text.toLocal8Bit().constData() << std::endl;
|
||||||
std::exit(0);
|
std::exit(0);
|
||||||
@@ -266,28 +266,28 @@ bool CommandlineOptions::Parse() {
|
|||||||
if (!ok) set_volume_ = -1;
|
if (!ok) set_volume_ = -1;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case VolumeIncreaseBy:
|
case LongOptions::VolumeIncreaseBy:
|
||||||
volume_modifier_ = QString(optarg).toInt(&ok);
|
volume_modifier_ = QString(optarg).toInt(&ok);
|
||||||
if (!ok) volume_modifier_ = 0;
|
if (!ok) volume_modifier_ = 0;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case VolumeDecreaseBy:
|
case LongOptions::VolumeDecreaseBy:
|
||||||
volume_modifier_ = -QString(optarg).toInt(&ok);
|
volume_modifier_ = -QString(optarg).toInt(&ok);
|
||||||
if (!ok) volume_modifier_ = 0;
|
if (!ok) volume_modifier_ = 0;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case SeekTo:
|
case LongOptions::SeekTo:
|
||||||
seek_to_ = QString(optarg).toInt(&ok);
|
seek_to_ = QString(optarg).toInt(&ok);
|
||||||
if (!ok) seek_to_ = -1;
|
if (!ok) seek_to_ = -1;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case SeekBy:
|
case LongOptions::SeekBy:
|
||||||
seek_by_ = QString(optarg).toInt(&ok);
|
seek_by_ = QString(optarg).toInt(&ok);
|
||||||
if (!ok) seek_by_ = 0;
|
if (!ok) seek_by_ = 0;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case RestartOrPrevious:
|
case LongOptions::RestartOrPrevious:
|
||||||
player_action_ = Player_RestartOrPrevious;
|
player_action_ = PlayerAction::RestartOrPrevious;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 'k':
|
case 'k':
|
||||||
@@ -297,7 +297,7 @@ bool CommandlineOptions::Parse() {
|
|||||||
|
|
||||||
case 'w':
|
case 'w':
|
||||||
window_size_ = QString(optarg);
|
window_size_ = QString(optarg);
|
||||||
player_action_ = Player_ResizeWindow;
|
player_action_ = PlayerAction::ResizeWindow;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case '?':
|
case '?':
|
||||||
@@ -323,7 +323,7 @@ bool CommandlineOptions::Parse() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
bool CommandlineOptions::is_empty() const {
|
bool CommandlineOptions::is_empty() const {
|
||||||
return player_action_ == Player_None &&
|
return player_action_ == PlayerAction::None &&
|
||||||
set_volume_ == -1 &&
|
set_volume_ == -1 &&
|
||||||
volume_modifier_ == 0 &&
|
volume_modifier_ == 0 &&
|
||||||
seek_to_ == -1 &&
|
seek_to_ == -1 &&
|
||||||
@@ -335,7 +335,7 @@ bool CommandlineOptions::is_empty() const {
|
|||||||
}
|
}
|
||||||
|
|
||||||
bool CommandlineOptions::contains_play_options() const {
|
bool CommandlineOptions::contains_play_options() const {
|
||||||
return player_action_ != Player_None || play_track_at_ != -1 || !urls_.isEmpty();
|
return player_action_ != PlayerAction::None || play_track_at_ != -1 || !urls_.isEmpty();
|
||||||
}
|
}
|
||||||
|
|
||||||
QByteArray CommandlineOptions::Serialize() const {
|
QByteArray CommandlineOptions::Serialize() const {
|
||||||
|
|||||||
@@ -41,24 +41,24 @@ class CommandlineOptions {
|
|||||||
|
|
||||||
// Don't change the values or order, these get serialised and sent to
|
// Don't change the values or order, these get serialised and sent to
|
||||||
// possibly a different version of Strawberry
|
// possibly a different version of Strawberry
|
||||||
enum UrlListAction {
|
enum class UrlListAction {
|
||||||
UrlList_Append = 0,
|
Append = 0,
|
||||||
UrlList_Load = 1,
|
Load = 1,
|
||||||
UrlList_None = 2,
|
None = 2,
|
||||||
UrlList_CreateNew = 3,
|
CreateNew = 3
|
||||||
};
|
};
|
||||||
enum PlayerAction {
|
enum class PlayerAction {
|
||||||
Player_None = 0,
|
None = 0,
|
||||||
Player_Play = 1,
|
Play = 1,
|
||||||
Player_PlayPause = 2,
|
PlayPause = 2,
|
||||||
Player_Pause = 3,
|
Pause = 3,
|
||||||
Player_Stop = 4,
|
Stop = 4,
|
||||||
Player_Previous = 5,
|
Previous = 5,
|
||||||
Player_Next = 6,
|
Next = 6,
|
||||||
Player_RestartOrPrevious = 7,
|
RestartOrPrevious = 7,
|
||||||
Player_StopAfterCurrent = 8,
|
StopAfterCurrent = 8,
|
||||||
Player_PlayPlaylist = 9,
|
PlayPlaylist = 9,
|
||||||
Player_ResizeWindow = 10
|
ResizeWindow = 10
|
||||||
};
|
};
|
||||||
|
|
||||||
bool Parse();
|
bool Parse();
|
||||||
|
|||||||
@@ -48,7 +48,7 @@
|
|||||||
#include "scopedtransaction.h"
|
#include "scopedtransaction.h"
|
||||||
|
|
||||||
const char *Database::kDatabaseFilename = "strawberry.db";
|
const char *Database::kDatabaseFilename = "strawberry.db";
|
||||||
const int Database::kSchemaVersion = 15;
|
const int Database::kSchemaVersion = 16;
|
||||||
const int Database::kMinSupportedSchemaVersion = 10;
|
const int Database::kMinSupportedSchemaVersion = 10;
|
||||||
const char *Database::kMagicAllSongsTables = "%allsongstables";
|
const char *Database::kMagicAllSongsTables = "%allsongstables";
|
||||||
|
|
||||||
|
|||||||
@@ -29,7 +29,7 @@
|
|||||||
#include <QString>
|
#include <QString>
|
||||||
|
|
||||||
#include "core/logging.h"
|
#include "core/logging.h"
|
||||||
#include "utilities.h"
|
#include "utilities/fileutils.h"
|
||||||
#include "musicstorage.h"
|
#include "musicstorage.h"
|
||||||
|
|
||||||
#include "filesystemmusicstorage.h"
|
#include "filesystemmusicstorage.h"
|
||||||
@@ -106,12 +106,7 @@ bool FilesystemMusicStorage::DeleteFromStorage(const DeleteJob &job) {
|
|||||||
|
|
||||||
if (job.use_trash_) {
|
if (job.use_trash_) {
|
||||||
#if QT_VERSION >= QT_VERSION_CHECK(5, 15, 0)
|
#if QT_VERSION >= QT_VERSION_CHECK(5, 15, 0)
|
||||||
if (fileInfo.isDir()) {
|
return QFile::moveToTrash(path);
|
||||||
return Utilities::MoveToTrashRecursive(path);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
return QFile::moveToTrash(path);
|
|
||||||
}
|
|
||||||
#else
|
#else
|
||||||
return false;
|
return false;
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
@@ -52,7 +52,7 @@ void IconLoader::Init() {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
QIcon IconLoader::Load(const QString &name, const int fixed_size, const int min_size, const int max_size) {
|
QIcon IconLoader::Load(const QString &name, const bool system_icon, const int fixed_size, const int min_size, const int max_size) {
|
||||||
|
|
||||||
QIcon ret;
|
QIcon ret;
|
||||||
|
|
||||||
@@ -69,7 +69,7 @@ QIcon IconLoader::Load(const QString &name, const int fixed_size, const int min_
|
|||||||
sizes << fixed_size;
|
sizes << fixed_size;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (system_icons_) {
|
if (system_icon && system_icons_) {
|
||||||
IconMapper::IconProperties icon_prop;
|
IconMapper::IconProperties icon_prop;
|
||||||
if (IconMapper::iconmapper_.contains(name)) {
|
if (IconMapper::iconmapper_.contains(name)) {
|
||||||
icon_prop = IconMapper::iconmapper_[name];
|
icon_prop = IconMapper::iconmapper_[name];
|
||||||
|
|||||||
@@ -26,7 +26,7 @@
|
|||||||
class IconLoader {
|
class IconLoader {
|
||||||
public:
|
public:
|
||||||
static void Init();
|
static void Init();
|
||||||
static QIcon Load(const QString &name, const int fixed_size = 0, const int min_size = 0, const int max_size = 0);
|
static QIcon Load(const QString &name, const bool system_icon = true, const int fixed_size = 0, const int min_size = 0, const int max_size = 0);
|
||||||
private:
|
private:
|
||||||
explicit IconLoader() {}
|
explicit IconLoader() {}
|
||||||
static bool system_icons_;
|
static bool system_icons_;
|
||||||
|
|||||||
@@ -1,29 +1,44 @@
|
|||||||
|
/*
|
||||||
|
* Strawberry Music Player
|
||||||
|
* This file was part of Clementine.
|
||||||
|
* Copyright 2010, David Sansome <me@davidsansome.com>
|
||||||
|
* Copyright 2018-2021, Jonas Kvinge <jonas@jkvinge.net>
|
||||||
|
*
|
||||||
|
* Strawberry is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* Strawberry is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with Strawberry. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
#ifndef MAC_STARTUP_H
|
#ifndef MAC_STARTUP_H
|
||||||
#define MAC_STARTUP_H
|
#define MAC_STARTUP_H
|
||||||
|
|
||||||
#include "config.h"
|
#include "config.h"
|
||||||
|
|
||||||
|
#include <CoreFoundation/CFDictionary.h>
|
||||||
|
|
||||||
#include <QString>
|
#include <QString>
|
||||||
|
#include <QWidget>
|
||||||
#include <QKeySequence>
|
#include <QKeySequence>
|
||||||
|
|
||||||
class QObject;
|
#ifdef __OBJC__
|
||||||
class QWidget;
|
@class NSEvent;
|
||||||
|
#else
|
||||||
|
class NSEvent;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
class PlatformInterface;
|
||||||
class GlobalShortcutsBackendMacOS;
|
class GlobalShortcutsBackendMacOS;
|
||||||
|
|
||||||
class PlatformInterface {
|
|
||||||
public:
|
|
||||||
PlatformInterface() = default;
|
|
||||||
virtual ~PlatformInterface() {}
|
|
||||||
|
|
||||||
// Called when the application should show itself.
|
|
||||||
virtual void Activate() = 0;
|
|
||||||
virtual bool LoadUrl(const QString &url) = 0;
|
|
||||||
|
|
||||||
private:
|
|
||||||
Q_DISABLE_COPY(PlatformInterface)
|
|
||||||
};
|
|
||||||
|
|
||||||
namespace mac {
|
namespace mac {
|
||||||
|
|
||||||
void MacMain();
|
void MacMain();
|
||||||
@@ -32,6 +47,9 @@ void SetApplicationHandler(PlatformInterface *handler);
|
|||||||
|
|
||||||
void EnableFullScreen(const QWidget &main_window);
|
void EnableFullScreen(const QWidget &main_window);
|
||||||
|
|
||||||
|
QKeySequence KeySequenceFromNSEvent(NSEvent *event);
|
||||||
|
void DumpDictionary(CFDictionaryRef dict);
|
||||||
|
|
||||||
} // namespace mac
|
} // namespace mac
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
@@ -45,13 +45,12 @@
|
|||||||
|
|
||||||
#include "config.h"
|
#include "config.h"
|
||||||
|
|
||||||
|
#include "platforminterface.h"
|
||||||
#include "mac_delegate.h"
|
#include "mac_delegate.h"
|
||||||
#include "mac_startup.h"
|
#include "mac_startup.h"
|
||||||
#include "mac_utilities.h"
|
|
||||||
#include "utilities.h"
|
|
||||||
#include "scoped_cftyperef.h"
|
#include "scoped_cftyperef.h"
|
||||||
#include "core/logging.h"
|
#include "core/logging.h"
|
||||||
#include "core/scoped_nsautorelease_pool.h"
|
#include "scoped_nsautorelease_pool.h"
|
||||||
#include "globalshortcuts/globalshortcutsmanager.h"
|
#include "globalshortcuts/globalshortcutsmanager.h"
|
||||||
#include "globalshortcuts/globalshortcutsbackend-macos.h"
|
#include "globalshortcuts/globalshortcutsbackend-macos.h"
|
||||||
|
|
||||||
|
|||||||
@@ -59,6 +59,7 @@
|
|||||||
#include <QActionGroup>
|
#include <QActionGroup>
|
||||||
#include <QShortcut>
|
#include <QShortcut>
|
||||||
#include <QMessageBox>
|
#include <QMessageBox>
|
||||||
|
#include <QErrorMessage>
|
||||||
#include <QtEvents>
|
#include <QtEvents>
|
||||||
#include <QSettings>
|
#include <QSettings>
|
||||||
#include <QColor>
|
#include <QColor>
|
||||||
@@ -74,13 +75,10 @@
|
|||||||
#include <QClipboard>
|
#include <QClipboard>
|
||||||
|
|
||||||
#include "core/logging.h"
|
#include "core/logging.h"
|
||||||
#include "core/networkaccessmanager.h"
|
|
||||||
|
|
||||||
#include "mainwindow.h"
|
#include "mainwindow.h"
|
||||||
#include "ui_mainwindow.h"
|
#include "ui_mainwindow.h"
|
||||||
|
|
||||||
#include "utilities.h"
|
|
||||||
#include "timeconstants.h"
|
|
||||||
#include "commandlineoptions.h"
|
#include "commandlineoptions.h"
|
||||||
#include "mimedata.h"
|
#include "mimedata.h"
|
||||||
#include "iconloader.h"
|
#include "iconloader.h"
|
||||||
@@ -91,14 +89,19 @@
|
|||||||
#include "application.h"
|
#include "application.h"
|
||||||
#include "database.h"
|
#include "database.h"
|
||||||
#include "player.h"
|
#include "player.h"
|
||||||
#include "appearance.h"
|
|
||||||
#include "filesystemmusicstorage.h"
|
#include "filesystemmusicstorage.h"
|
||||||
#include "deletefiles.h"
|
#include "deletefiles.h"
|
||||||
#ifdef Q_OS_MACOS
|
#ifdef Q_OS_MACOS
|
||||||
|
# include "mac_startup.h"
|
||||||
# include "macsystemtrayicon.h"
|
# include "macsystemtrayicon.h"
|
||||||
|
# include "utilities/macosutils.h"
|
||||||
#else
|
#else
|
||||||
# include "qtsystemtrayicon.h"
|
# include "qtsystemtrayicon.h"
|
||||||
#endif
|
#endif
|
||||||
|
#include "networkaccessmanager.h"
|
||||||
|
#include "utilities/envutils.h"
|
||||||
|
#include "utilities/filemanagerutils.h"
|
||||||
|
#include "utilities/timeconstants.h"
|
||||||
#include "engine/enginetype.h"
|
#include "engine/enginetype.h"
|
||||||
#include "engine/enginebase.h"
|
#include "engine/enginebase.h"
|
||||||
#include "engine/engine_fwd.h"
|
#include "engine/engine_fwd.h"
|
||||||
@@ -292,13 +295,13 @@ MainWindow::MainWindow(Application *app, std::shared_ptr<SystemTrayIcon> tray_ic
|
|||||||
}),
|
}),
|
||||||
smartplaylists_view_(new SmartPlaylistsViewContainer(app, this)),
|
smartplaylists_view_(new SmartPlaylistsViewContainer(app, this)),
|
||||||
#ifdef HAVE_SUBSONIC
|
#ifdef HAVE_SUBSONIC
|
||||||
subsonic_view_(new InternetSongsView(app_, app->internet_services()->ServiceBySource(Song::Source_Subsonic), SubsonicSettingsPage::kSettingsGroup, SettingsDialog::Page_Subsonic, this)),
|
subsonic_view_(new InternetSongsView(app_, app->internet_services()->ServiceBySource(Song::Source::Subsonic), SubsonicSettingsPage::kSettingsGroup, SettingsDialog::Page::Subsonic, this)),
|
||||||
#endif
|
#endif
|
||||||
#ifdef HAVE_TIDAL
|
#ifdef HAVE_TIDAL
|
||||||
tidal_view_(new InternetTabsView(app_, app->internet_services()->ServiceBySource(Song::Source_Tidal), TidalSettingsPage::kSettingsGroup, SettingsDialog::Page_Tidal, this)),
|
tidal_view_(new InternetTabsView(app_, app->internet_services()->ServiceBySource(Song::Source::Tidal), TidalSettingsPage::kSettingsGroup, SettingsDialog::Page::Tidal, this)),
|
||||||
#endif
|
#endif
|
||||||
#ifdef HAVE_QOBUZ
|
#ifdef HAVE_QOBUZ
|
||||||
qobuz_view_(new InternetTabsView(app_, app->internet_services()->ServiceBySource(Song::Source_Qobuz), QobuzSettingsPage::kSettingsGroup, SettingsDialog::Page_Qobuz, this)),
|
qobuz_view_(new InternetTabsView(app_, app->internet_services()->ServiceBySource(Song::Source::Qobuz), QobuzSettingsPage::kSettingsGroup, SettingsDialog::Page::Qobuz, this)),
|
||||||
#endif
|
#endif
|
||||||
radio_view_(new RadioViewContainer(this)),
|
radio_view_(new RadioViewContainer(this)),
|
||||||
lastfm_import_dialog_(new LastFMImportDialog(app_->lastfm_import(), this)),
|
lastfm_import_dialog_(new LastFMImportDialog(app_->lastfm_import(), this)),
|
||||||
@@ -330,10 +333,10 @@ MainWindow::MainWindow(Application *app, std::shared_ptr<SystemTrayIcon> tray_ic
|
|||||||
track_slider_timer_(new QTimer(this)),
|
track_slider_timer_(new QTimer(this)),
|
||||||
keep_running_(false),
|
keep_running_(false),
|
||||||
playing_widget_(true),
|
playing_widget_(true),
|
||||||
doubleclick_addmode_(BehaviourSettingsPage::AddBehaviour_Append),
|
doubleclick_addmode_(BehaviourSettingsPage::AddBehaviour::Append),
|
||||||
doubleclick_playmode_(BehaviourSettingsPage::PlayBehaviour_Never),
|
doubleclick_playmode_(BehaviourSettingsPage::PlayBehaviour::Never),
|
||||||
doubleclick_playlist_addmode_(BehaviourSettingsPage::PlaylistAddBehaviour_Play),
|
doubleclick_playlist_addmode_(BehaviourSettingsPage::PlaylistAddBehaviour::Play),
|
||||||
menu_playmode_(BehaviourSettingsPage::PlayBehaviour_Never),
|
menu_playmode_(BehaviourSettingsPage::PlayBehaviour::Never),
|
||||||
initialized_(false),
|
initialized_(false),
|
||||||
was_maximized_(true),
|
was_maximized_(true),
|
||||||
was_minimized_(false),
|
was_minimized_(false),
|
||||||
@@ -363,24 +366,24 @@ MainWindow::MainWindow(Application *app, std::shared_ptr<SystemTrayIcon> tray_ic
|
|||||||
StyleHelper::setBaseColor(palette().color(QPalette::Highlight).darker());
|
StyleHelper::setBaseColor(palette().color(QPalette::Highlight).darker());
|
||||||
|
|
||||||
// Add tabs to the fancy tab widget
|
// Add tabs to the fancy tab widget
|
||||||
ui_->tabs->AddTab(context_view_, "context", IconLoader::Load("strawberry"), tr("Context"));
|
ui_->tabs->AddTab(context_view_, "context", IconLoader::Load("strawberry", true, 0, 32), tr("Context"));
|
||||||
ui_->tabs->AddTab(collection_view_, "collection", IconLoader::Load("library-music"), tr("Collection"));
|
ui_->tabs->AddTab(collection_view_, "collection", IconLoader::Load("library-music", true, 0, 32), tr("Collection"));
|
||||||
ui_->tabs->AddTab(queue_view_, "queue", IconLoader::Load("footsteps"), tr("Queue"));
|
ui_->tabs->AddTab(queue_view_, "queue", IconLoader::Load("footsteps", true, 0, 32), tr("Queue"));
|
||||||
ui_->tabs->AddTab(playlist_list_, "playlists", IconLoader::Load("view-media-playlist"), tr("Playlists"));
|
ui_->tabs->AddTab(playlist_list_, "playlists", IconLoader::Load("view-media-playlist", true, 0, 32), tr("Playlists"));
|
||||||
ui_->tabs->AddTab(smartplaylists_view_, "smartplaylists", IconLoader::Load("view-media-playlist"), tr("Smart playlists"));
|
ui_->tabs->AddTab(smartplaylists_view_, "smartplaylists", IconLoader::Load("view-media-playlist", true, 0, 32), tr("Smart playlists"));
|
||||||
ui_->tabs->AddTab(file_view_, "files", IconLoader::Load("document-open"), tr("Files"));
|
ui_->tabs->AddTab(file_view_, "files", IconLoader::Load("document-open", true, 0, 32), tr("Files"));
|
||||||
ui_->tabs->AddTab(radio_view_, "radios", IconLoader::Load("radio"), tr("Radios"));
|
ui_->tabs->AddTab(radio_view_, "radios", IconLoader::Load("radio", true, 0, 32), tr("Radios"));
|
||||||
#ifndef Q_OS_WIN
|
#ifndef Q_OS_WIN
|
||||||
ui_->tabs->AddTab(device_view_, "devices", IconLoader::Load("device"), tr("Devices"));
|
ui_->tabs->AddTab(device_view_, "devices", IconLoader::Load("device", true, 0, 32), tr("Devices"));
|
||||||
#endif
|
#endif
|
||||||
#ifdef HAVE_SUBSONIC
|
#ifdef HAVE_SUBSONIC
|
||||||
ui_->tabs->AddTab(subsonic_view_, "subsonic", IconLoader::Load("subsonic"), tr("Subsonic"));
|
ui_->tabs->AddTab(subsonic_view_, "subsonic", IconLoader::Load("subsonic", true, 0, 32), tr("Subsonic"));
|
||||||
#endif
|
#endif
|
||||||
#ifdef HAVE_TIDAL
|
#ifdef HAVE_TIDAL
|
||||||
ui_->tabs->AddTab(tidal_view_, "tidal", IconLoader::Load("tidal"), tr("Tidal"));
|
ui_->tabs->AddTab(tidal_view_, "tidal", IconLoader::Load("tidal", true, 0, 32), tr("Tidal"));
|
||||||
#endif
|
#endif
|
||||||
#ifdef HAVE_QOBUZ
|
#ifdef HAVE_QOBUZ
|
||||||
ui_->tabs->AddTab(qobuz_view_, "qobuz", IconLoader::Load("qobuz"), tr("Qobuz"));
|
ui_->tabs->AddTab(qobuz_view_, "qobuz", IconLoader::Load("qobuz", true, 0, 32), tr("Qobuz"));
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
// Add the playing widget to the fancy tab widget
|
// Add the playing widget to the fancy tab widget
|
||||||
@@ -400,7 +403,7 @@ MainWindow::MainWindow(Application *app, std::shared_ptr<SystemTrayIcon> tray_ic
|
|||||||
app_->player()->Init();
|
app_->player()->Init();
|
||||||
EngineChanged(app_->player()->engine()->type());
|
EngineChanged(app_->player()->engine()->type());
|
||||||
const uint volume = app_->player()->GetVolume();
|
const uint volume = app_->player()->GetVolume();
|
||||||
ui_->volume->SetValueFromVolume(volume);
|
ui_->volume->SetValue(volume);
|
||||||
VolumeChanged(volume);
|
VolumeChanged(volume);
|
||||||
|
|
||||||
// Models
|
// Models
|
||||||
@@ -583,7 +586,7 @@ MainWindow::MainWindow(Application *app, std::shared_ptr<SystemTrayIcon> tray_ic
|
|||||||
ui_->stop_button->setMenu(stop_menu);
|
ui_->stop_button->setMenu(stop_menu);
|
||||||
|
|
||||||
// Player connections
|
// Player connections
|
||||||
QObject::connect(ui_->volume, &VolumeSlider::valueChanged, app_->player(), &Player::SetVolumeFromValue);
|
QObject::connect(ui_->volume, &VolumeSlider::valueChanged, app_->player(), &Player::SetVolumeFromSlider);
|
||||||
|
|
||||||
QObject::connect(app_->player(), &Player::EngineChanged, this, &MainWindow::EngineChanged);
|
QObject::connect(app_->player(), &Player::EngineChanged, this, &MainWindow::EngineChanged);
|
||||||
QObject::connect(app_->player(), &Player::Error, this, &MainWindow::ShowErrorDialog);
|
QObject::connect(app_->player(), &Player::Error, this, &MainWindow::ShowErrorDialog);
|
||||||
@@ -606,7 +609,7 @@ MainWindow::MainWindow(Application *app, std::shared_ptr<SystemTrayIcon> tray_ic
|
|||||||
QObject::connect(app_->player(), &Player::Stopped, osd_, &OSDBase::Stopped);
|
QObject::connect(app_->player(), &Player::Stopped, osd_, &OSDBase::Stopped);
|
||||||
QObject::connect(app_->player(), &Player::PlaylistFinished, osd_, &OSDBase::PlaylistFinished);
|
QObject::connect(app_->player(), &Player::PlaylistFinished, osd_, &OSDBase::PlaylistFinished);
|
||||||
QObject::connect(app_->player(), &Player::VolumeChanged, osd_, &OSDBase::VolumeChanged);
|
QObject::connect(app_->player(), &Player::VolumeChanged, osd_, &OSDBase::VolumeChanged);
|
||||||
QObject::connect(app_->player(), &Player::VolumeChanged, ui_->volume, &VolumeSlider::SetValueFromVolume);
|
QObject::connect(app_->player(), &Player::VolumeChanged, ui_->volume, &VolumeSlider::SetValue);
|
||||||
QObject::connect(app_->player(), &Player::ForceShowOSD, this, &MainWindow::ForceShowOSD);
|
QObject::connect(app_->player(), &Player::ForceShowOSD, this, &MainWindow::ForceShowOSD);
|
||||||
|
|
||||||
QObject::connect(app_->playlist_manager(), &PlaylistManager::CurrentSongChanged, this, &MainWindow::SongChanged);
|
QObject::connect(app_->playlist_manager(), &PlaylistManager::CurrentSongChanged, this, &MainWindow::SongChanged);
|
||||||
@@ -674,7 +677,7 @@ MainWindow::MainWindow(Application *app, std::shared_ptr<SystemTrayIcon> tray_ic
|
|||||||
collection_show_untagged_->setCheckable(true);
|
collection_show_untagged_->setCheckable(true);
|
||||||
collection_show_all_->setChecked(true);
|
collection_show_all_->setChecked(true);
|
||||||
|
|
||||||
QObject::connect(collection_view_group, &QActionGroup::triggered, this, &MainWindow::ChangeCollectionQueryMode);
|
QObject::connect(collection_view_group, &QActionGroup::triggered, this, &MainWindow::ChangeCollectionFilterMode);
|
||||||
|
|
||||||
QAction *collection_config_action = new QAction(IconLoader::Load("configure"), tr("Configure collection..."), this);
|
QAction *collection_config_action = new QAction(IconLoader::Load("configure"), tr("Configure collection..."), this);
|
||||||
QObject::connect(collection_config_action, &QAction::triggered, this, &MainWindow::ShowCollectionConfig);
|
QObject::connect(collection_config_action, &QAction::triggered, this, &MainWindow::ShowCollectionConfig);
|
||||||
@@ -699,7 +702,7 @@ MainWindow::MainWindow(Application *app, std::shared_ptr<SystemTrayIcon> tray_ic
|
|||||||
QObject::connect(tidal_view_->albums_collection_view(), &InternetCollectionView::AddToPlaylistSignal, this, &MainWindow::AddToPlaylist);
|
QObject::connect(tidal_view_->albums_collection_view(), &InternetCollectionView::AddToPlaylistSignal, this, &MainWindow::AddToPlaylist);
|
||||||
QObject::connect(tidal_view_->songs_collection_view(), &InternetCollectionView::AddToPlaylistSignal, this, &MainWindow::AddToPlaylist);
|
QObject::connect(tidal_view_->songs_collection_view(), &InternetCollectionView::AddToPlaylistSignal, this, &MainWindow::AddToPlaylist);
|
||||||
QObject::connect(tidal_view_->search_view(), &InternetSearchView::AddToPlaylist, this, &MainWindow::AddToPlaylist);
|
QObject::connect(tidal_view_->search_view(), &InternetSearchView::AddToPlaylist, this, &MainWindow::AddToPlaylist);
|
||||||
if (TidalService *tidalservice = qobject_cast<TidalService*>(app_->internet_services()->ServiceBySource(Song::Source_Tidal))) {
|
if (TidalService *tidalservice = qobject_cast<TidalService*>(app_->internet_services()->ServiceBySource(Song::Source::Tidal))) {
|
||||||
QObject::connect(this, &MainWindow::AuthorizationUrlReceived, tidalservice, &TidalService::AuthorizationUrlReceived);
|
QObject::connect(this, &MainWindow::AuthorizationUrlReceived, tidalservice, &TidalService::AuthorizationUrlReceived);
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
@@ -872,11 +875,6 @@ MainWindow::MainWindow(Application *app, std::shared_ptr<SystemTrayIcon> tray_ic
|
|||||||
QObject::connect(ui_->action_console, &QAction::triggered, this, &MainWindow::ShowConsole);
|
QObject::connect(ui_->action_console, &QAction::triggered, this, &MainWindow::ShowConsole);
|
||||||
PlayingWidgetPositionChanged(ui_->widget_playing->show_above_status_bar());
|
PlayingWidgetPositionChanged(ui_->widget_playing->show_above_status_bar());
|
||||||
|
|
||||||
// Load theme
|
|
||||||
// This is tricky: we need to save the default/system palette now,
|
|
||||||
// before loading user preferred theme (which will override it), to be able to restore it later
|
|
||||||
const_cast<QPalette&>(Appearance::kDefaultPalette) = QApplication::palette();
|
|
||||||
app_->appearance()->LoadUserTheme();
|
|
||||||
StyleSheetLoader *css_loader = new StyleSheetLoader(this);
|
StyleSheetLoader *css_loader = new StyleSheetLoader(this);
|
||||||
css_loader->SetStyleSheet(this, ":/style/strawberry.css");
|
css_loader->SetStyleSheet(this, ":/style/strawberry.css");
|
||||||
|
|
||||||
@@ -924,10 +922,9 @@ MainWindow::MainWindow(Application *app, std::shared_ptr<SystemTrayIcon> tray_ic
|
|||||||
}
|
}
|
||||||
|
|
||||||
ui_->tabs->setCurrentIndex(settings_.value("current_tab", 1).toInt());
|
ui_->tabs->setCurrentIndex(settings_.value("current_tab", 1).toInt());
|
||||||
FancyTabWidget::Mode default_mode = FancyTabWidget::Mode_LargeSidebar;
|
FancyTabWidget::Mode default_mode = FancyTabWidget::Mode::LargeSidebar;
|
||||||
int tab_mode_int = settings_.value("tab_mode", default_mode).toInt();
|
FancyTabWidget::Mode tab_mode = static_cast<FancyTabWidget::Mode>(settings_.value("tab_mode", static_cast<int>(default_mode)).toInt());
|
||||||
FancyTabWidget::Mode tab_mode = static_cast<FancyTabWidget::Mode>(tab_mode_int);
|
if (tab_mode == FancyTabWidget::Mode::None) tab_mode = default_mode;
|
||||||
if (tab_mode == FancyTabWidget::Mode_None) tab_mode = default_mode;
|
|
||||||
ui_->tabs->SetMode(tab_mode);
|
ui_->tabs->SetMode(tab_mode);
|
||||||
|
|
||||||
TabSwitched();
|
TabSwitched();
|
||||||
@@ -950,26 +947,26 @@ MainWindow::MainWindow(Application *app, std::shared_ptr<SystemTrayIcon> tray_ic
|
|||||||
#else
|
#else
|
||||||
QSettings s;
|
QSettings s;
|
||||||
s.beginGroup(BehaviourSettingsPage::kSettingsGroup);
|
s.beginGroup(BehaviourSettingsPage::kSettingsGroup);
|
||||||
BehaviourSettingsPage::StartupBehaviour behaviour = BehaviourSettingsPage::StartupBehaviour(s.value("startupbehaviour", BehaviourSettingsPage::Startup_Remember).toInt());
|
const BehaviourSettingsPage::StartupBehaviour startupbehaviour = static_cast<BehaviourSettingsPage::StartupBehaviour>(s.value("startupbehaviour", static_cast<int>(BehaviourSettingsPage::StartupBehaviour::Remember)).toInt());
|
||||||
s.endGroup();
|
s.endGroup();
|
||||||
switch (behaviour) {
|
switch (startupbehaviour) {
|
||||||
case BehaviourSettingsPage::Startup_Show:
|
case BehaviourSettingsPage::StartupBehaviour::Show:
|
||||||
show();
|
show();
|
||||||
break;
|
break;
|
||||||
case BehaviourSettingsPage::Startup_ShowMaximized:
|
case BehaviourSettingsPage::StartupBehaviour::ShowMaximized:
|
||||||
setWindowState(windowState() | Qt::WindowMaximized);
|
setWindowState(windowState() | Qt::WindowMaximized);
|
||||||
show();
|
show();
|
||||||
break;
|
break;
|
||||||
case BehaviourSettingsPage::Startup_ShowMinimized:
|
case BehaviourSettingsPage::StartupBehaviour::ShowMinimized:
|
||||||
setWindowState(windowState() | Qt::WindowMinimized);
|
setWindowState(windowState() | Qt::WindowMinimized);
|
||||||
show();
|
show();
|
||||||
break;
|
break;
|
||||||
case BehaviourSettingsPage::Startup_Hide:
|
case BehaviourSettingsPage::StartupBehaviour::Hide:
|
||||||
if (tray_icon_->IsSystemTrayAvailable() && tray_icon_->isVisible()) {
|
if (tray_icon_->IsSystemTrayAvailable() && tray_icon_->isVisible()) {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
[[fallthrough]];
|
[[fallthrough]];
|
||||||
case BehaviourSettingsPage::Startup_Remember:
|
case BehaviourSettingsPage::StartupBehaviour::Remember:
|
||||||
default: {
|
default: {
|
||||||
|
|
||||||
was_maximized_ = settings_.value("maximized", true).toBool();
|
was_maximized_ = settings_.value("maximized", true).toBool();
|
||||||
@@ -1041,6 +1038,14 @@ MainWindow::MainWindow(Application *app, std::shared_ptr<SystemTrayIcon> tray_ic
|
|||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
#if defined(Q_OS_MACOS)
|
||||||
|
if (Utilities::ProcessTranslated()) {
|
||||||
|
QErrorMessage *error_message = new QErrorMessage;
|
||||||
|
error_message->setAttribute(Qt::WA_DeleteOnClose);
|
||||||
|
error_message->showMessage(tr("It is detected that Strawberry is running under Rosetta. Strawberry currently have limited macOS support, and running Strawberry under Rosetta is unsupported and known to have issues. If you want to use Strawberry on the current CPU, you should build Strawberry from source. For instructions see.: https://wiki.strawberrymusicplayer.org/wiki/Compile"));
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
qLog(Debug) << "Started" << QThread::currentThread();
|
qLog(Debug) << "Started" << QThread::currentThread();
|
||||||
initialized_ = true;
|
initialized_ = true;
|
||||||
|
|
||||||
@@ -1071,10 +1076,10 @@ void MainWindow::ReloadSettings() {
|
|||||||
playing_widget_ = s.value("playing_widget", true).toBool();
|
playing_widget_ = s.value("playing_widget", true).toBool();
|
||||||
bool trayicon_progress = s.value("trayicon_progress", false).toBool();
|
bool trayicon_progress = s.value("trayicon_progress", false).toBool();
|
||||||
if (playing_widget_ != ui_->widget_playing->IsEnabled()) TabSwitched();
|
if (playing_widget_ != ui_->widget_playing->IsEnabled()) TabSwitched();
|
||||||
doubleclick_addmode_ = BehaviourSettingsPage::AddBehaviour(s.value("doubleclick_addmode", BehaviourSettingsPage::AddBehaviour_Append).toInt());
|
doubleclick_addmode_ = static_cast<BehaviourSettingsPage::AddBehaviour>(s.value("doubleclick_addmode", static_cast<int>(BehaviourSettingsPage::AddBehaviour::Append)).toInt());
|
||||||
doubleclick_playmode_ = BehaviourSettingsPage::PlayBehaviour(s.value("doubleclick_playmode", BehaviourSettingsPage::PlayBehaviour_Never).toInt());
|
doubleclick_playmode_ = static_cast<BehaviourSettingsPage::PlayBehaviour>(s.value("doubleclick_playmode", static_cast<int>(BehaviourSettingsPage::PlayBehaviour::Never)).toInt());
|
||||||
doubleclick_playlist_addmode_ = BehaviourSettingsPage::PlaylistAddBehaviour(s.value("doubleclick_playlist_addmode", BehaviourSettingsPage::PlayBehaviour_Never).toInt());
|
doubleclick_playlist_addmode_ = static_cast<BehaviourSettingsPage::PlaylistAddBehaviour>(s.value("doubleclick_playlist_addmode", static_cast<int>(BehaviourSettingsPage::PlayBehaviour::Never)).toInt());
|
||||||
menu_playmode_ = BehaviourSettingsPage::PlayBehaviour(s.value("menu_playmode", BehaviourSettingsPage::PlayBehaviour_Never).toInt());
|
menu_playmode_ = static_cast<BehaviourSettingsPage::PlayBehaviour>(s.value("menu_playmode", static_cast<int>(BehaviourSettingsPage::PlayBehaviour::Never)).toInt());
|
||||||
s.endGroup();
|
s.endGroup();
|
||||||
|
|
||||||
s.beginGroup(AppearanceSettingsPage::kSettingsGroup);
|
s.beginGroup(AppearanceSettingsPage::kSettingsGroup);
|
||||||
@@ -1164,7 +1169,6 @@ void MainWindow::ReloadAllSettings() {
|
|||||||
collection_view_->ReloadSettings();
|
collection_view_->ReloadSettings();
|
||||||
ui_->playlist->view()->ReloadSettings();
|
ui_->playlist->view()->ReloadSettings();
|
||||||
app_->playlist_manager()->playlist_container()->ReloadSettings();
|
app_->playlist_manager()->playlist_container()->ReloadSettings();
|
||||||
app_->album_cover_loader()->ReloadSettings();
|
|
||||||
album_cover_choice_controller_->ReloadSettings();
|
album_cover_choice_controller_->ReloadSettings();
|
||||||
context_view_->ReloadSettings();
|
context_view_->ReloadSettings();
|
||||||
file_view_->ReloadSettings();
|
file_view_->ReloadSettings();
|
||||||
@@ -1201,6 +1205,7 @@ void MainWindow::SaveSettings() {
|
|||||||
|
|
||||||
SaveGeometry();
|
SaveGeometry();
|
||||||
SavePlaybackStatus();
|
SavePlaybackStatus();
|
||||||
|
app_->player()->SaveVolume();
|
||||||
ui_->tabs->SaveSettings(kSettingsGroup);
|
ui_->tabs->SaveSettings(kSettingsGroup);
|
||||||
ui_->playlist->view()->SaveSettings();
|
ui_->playlist->view()->SaveSettings();
|
||||||
app_->scrobbler()->WriteCache();
|
app_->scrobbler()->WriteCache();
|
||||||
@@ -1227,7 +1232,7 @@ void MainWindow::Exit() {
|
|||||||
if (app_->player()->engine()->is_fadeout_enabled()) {
|
if (app_->player()->engine()->is_fadeout_enabled()) {
|
||||||
// To shut down the application when fadeout will be finished
|
// To shut down the application when fadeout will be finished
|
||||||
QObject::connect(app_->player()->engine(), &EngineBase::FadeoutFinishedSignal, this, &MainWindow::DoExit);
|
QObject::connect(app_->player()->engine(), &EngineBase::FadeoutFinishedSignal, this, &MainWindow::DoExit);
|
||||||
if (app_->player()->GetState() == Engine::Playing) {
|
if (app_->player()->GetState() == Engine::State::Playing) {
|
||||||
app_->player()->Stop();
|
app_->player()->Stop();
|
||||||
ignore_close_ = true;
|
ignore_close_ = true;
|
||||||
close();
|
close();
|
||||||
@@ -1325,8 +1330,8 @@ void MainWindow::MediaPlaying() {
|
|||||||
|
|
||||||
PlaylistItemPtr item(app_->player()->GetCurrentItem());
|
PlaylistItemPtr item(app_->player()->GetCurrentItem());
|
||||||
if (item) {
|
if (item) {
|
||||||
enable_play_pause = !(item->options() & PlaylistItem::PauseDisabled);
|
enable_play_pause = !(item->options() & PlaylistItem::Option::PauseDisabled);
|
||||||
can_seek = !(item->options() & PlaylistItem::SeekDisabled);
|
can_seek = !(item->options() & PlaylistItem::Option::SeekDisabled);
|
||||||
}
|
}
|
||||||
ui_->action_play_pause->setEnabled(enable_play_pause);
|
ui_->action_play_pause->setEnabled(enable_play_pause);
|
||||||
ui_->track_slider->SetCanSeek(can_seek);
|
ui_->track_slider->SetCanSeek(can_seek);
|
||||||
@@ -1440,8 +1445,8 @@ void MainWindow::SavePlaybackStatus() {
|
|||||||
QSettings s;
|
QSettings s;
|
||||||
|
|
||||||
s.beginGroup(Player::kSettingsGroup);
|
s.beginGroup(Player::kSettingsGroup);
|
||||||
s.setValue("playback_state", app_->player()->GetState());
|
s.setValue("playback_state", static_cast<int>(app_->player()->GetState()));
|
||||||
if (app_->player()->GetState() == Engine::Playing || app_->player()->GetState() == Engine::Paused) {
|
if (app_->player()->GetState() == Engine::State::Playing || app_->player()->GetState() == Engine::State::Paused) {
|
||||||
s.setValue("playback_playlist", app_->playlist_manager()->active()->id());
|
s.setValue("playback_playlist", app_->playlist_manager()->active()->id());
|
||||||
s.setValue("playback_position", app_->player()->engine()->position_nanosec() / kNsecPerSec);
|
s.setValue("playback_position", app_->player()->engine()->position_nanosec() / kNsecPerSec);
|
||||||
}
|
}
|
||||||
@@ -1459,14 +1464,14 @@ void MainWindow::LoadPlaybackStatus() {
|
|||||||
QSettings s;
|
QSettings s;
|
||||||
|
|
||||||
s.beginGroup(BehaviourSettingsPage::kSettingsGroup);
|
s.beginGroup(BehaviourSettingsPage::kSettingsGroup);
|
||||||
bool resume_playback = s.value("resumeplayback", false).toBool();
|
const bool resume_playback = s.value("resumeplayback", false).toBool();
|
||||||
s.endGroup();
|
s.endGroup();
|
||||||
|
|
||||||
s.beginGroup(Player::kSettingsGroup);
|
s.beginGroup(Player::kSettingsGroup);
|
||||||
Engine::State playback_state = static_cast<Engine::State>(s.value("playback_state", Engine::Empty).toInt());
|
const Engine::State playback_state = static_cast<Engine::State>(s.value("playback_state", static_cast<int>(Engine::State::Empty)).toInt());
|
||||||
s.endGroup();
|
s.endGroup();
|
||||||
|
|
||||||
if (resume_playback && playback_state != Engine::Empty && playback_state != Engine::Idle) {
|
if (resume_playback && playback_state != Engine::State::Empty && playback_state != Engine::State::Idle) {
|
||||||
std::shared_ptr<QMetaObject::Connection> connection = std::make_shared<QMetaObject::Connection>();
|
std::shared_ptr<QMetaObject::Connection> connection = std::make_shared<QMetaObject::Connection>();
|
||||||
*connection = QObject::connect(app_->playlist_manager(), &PlaylistManager::AllPlaylistsLoaded, this, [this, connection]() {
|
*connection = QObject::connect(app_->playlist_manager(), &PlaylistManager::AllPlaylistsLoaded, this, [this, connection]() {
|
||||||
QObject::disconnect(*connection);
|
QObject::disconnect(*connection);
|
||||||
@@ -1482,7 +1487,7 @@ void MainWindow::ResumePlayback() {
|
|||||||
|
|
||||||
QSettings s;
|
QSettings s;
|
||||||
s.beginGroup(Player::kSettingsGroup);
|
s.beginGroup(Player::kSettingsGroup);
|
||||||
Engine::State playback_state = static_cast<Engine::State>(s.value("playback_state", Engine::Empty).toInt());
|
const Engine::State playback_state = static_cast<Engine::State>(s.value("playback_state", static_cast<int>(Engine::State::Empty)).toInt());
|
||||||
int playback_playlist = s.value("playback_playlist", -1).toInt();
|
int playback_playlist = s.value("playback_playlist", -1).toInt();
|
||||||
int playback_position = s.value("playback_position", 0).toInt();
|
int playback_position = s.value("playback_position", 0).toInt();
|
||||||
s.endGroup();
|
s.endGroup();
|
||||||
@@ -1490,7 +1495,7 @@ void MainWindow::ResumePlayback() {
|
|||||||
if (playback_playlist == app_->playlist_manager()->current()->id()) {
|
if (playback_playlist == app_->playlist_manager()->current()->id()) {
|
||||||
// Set active to current to resume playback on correct playlist.
|
// Set active to current to resume playback on correct playlist.
|
||||||
app_->playlist_manager()->SetActiveToCurrent();
|
app_->playlist_manager()->SetActiveToCurrent();
|
||||||
if (playback_state == Engine::Paused) {
|
if (playback_state == Engine::State::Paused) {
|
||||||
std::shared_ptr<QMetaObject::Connection> connection = std::make_shared<QMetaObject::Connection>();
|
std::shared_ptr<QMetaObject::Connection> connection = std::make_shared<QMetaObject::Connection>();
|
||||||
*connection = QObject::connect(app_->player(), &Player::Playing, app_->player(), [this, connection]() {
|
*connection = QObject::connect(app_->player(), &Player::Playing, app_->player(), [this, connection]() {
|
||||||
QObject::disconnect(*connection);
|
QObject::disconnect(*connection);
|
||||||
@@ -1502,7 +1507,7 @@ void MainWindow::ResumePlayback() {
|
|||||||
|
|
||||||
// Reset saved playback status so we don't resume again from the same position.
|
// Reset saved playback status so we don't resume again from the same position.
|
||||||
s.beginGroup(Player::kSettingsGroup);
|
s.beginGroup(Player::kSettingsGroup);
|
||||||
s.setValue("playback_state", Engine::Empty);
|
s.setValue("playback_state", static_cast<int>(Engine::State::Empty));
|
||||||
s.setValue("playback_playlist", -1);
|
s.setValue("playback_playlist", -1);
|
||||||
s.setValue("playback_position", 0);
|
s.setValue("playback_position", 0);
|
||||||
s.endGroup();
|
s.endGroup();
|
||||||
@@ -1520,7 +1525,7 @@ void MainWindow::PlayIndex(const QModelIndex &idx, Playlist::AutoScroll autoscro
|
|||||||
}
|
}
|
||||||
|
|
||||||
app_->playlist_manager()->SetActiveToCurrent();
|
app_->playlist_manager()->SetActiveToCurrent();
|
||||||
app_->player()->PlayAt(row, 0, Engine::Manual, autoscroll, true);
|
app_->player()->PlayAt(row, 0, Engine::TrackChangeType::Manual, autoscroll, true);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1535,16 +1540,16 @@ void MainWindow::PlaylistDoubleClick(const QModelIndex &idx) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
switch (doubleclick_playlist_addmode_) {
|
switch (doubleclick_playlist_addmode_) {
|
||||||
case BehaviourSettingsPage::PlaylistAddBehaviour_Play:
|
case BehaviourSettingsPage::PlaylistAddBehaviour::Play:
|
||||||
app_->playlist_manager()->SetActiveToCurrent();
|
app_->playlist_manager()->SetActiveToCurrent();
|
||||||
app_->player()->PlayAt(source_idx.row(), 0, Engine::Manual, Playlist::AutoScroll_Never, true, true);
|
app_->player()->PlayAt(source_idx.row(), 0, Engine::TrackChangeType::Manual, Playlist::AutoScroll::Never, true, true);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case BehaviourSettingsPage::PlaylistAddBehaviour_Enqueue:
|
case BehaviourSettingsPage::PlaylistAddBehaviour::Enqueue:
|
||||||
app_->playlist_manager()->current()->queue()->ToggleTracks(QModelIndexList() << source_idx);
|
app_->playlist_manager()->current()->queue()->ToggleTracks(QModelIndexList() << source_idx);
|
||||||
if (app_->player()->GetState() != Engine::Playing) {
|
if (app_->player()->GetState() != Engine::State::Playing) {
|
||||||
app_->playlist_manager()->SetActiveToCurrent();
|
app_->playlist_manager()->SetActiveToCurrent();
|
||||||
app_->player()->PlayAt(app_->playlist_manager()->current()->queue()->TakeNext(), 0, Engine::Manual, Playlist::AutoScroll_Never, true);
|
app_->player()->PlayAt(app_->playlist_manager()->current()->queue()->TakeNext(), 0, Engine::TrackChangeType::Manual, Playlist::AutoScroll::Never, true);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@@ -1697,22 +1702,22 @@ void MainWindow::UpdateTrackSliderPosition() {
|
|||||||
void MainWindow::ApplyAddBehaviour(const BehaviourSettingsPage::AddBehaviour b, MimeData *mimedata) {
|
void MainWindow::ApplyAddBehaviour(const BehaviourSettingsPage::AddBehaviour b, MimeData *mimedata) {
|
||||||
|
|
||||||
switch (b) {
|
switch (b) {
|
||||||
case BehaviourSettingsPage::AddBehaviour_Append:
|
case BehaviourSettingsPage::AddBehaviour::Append:
|
||||||
mimedata->clear_first_ = false;
|
mimedata->clear_first_ = false;
|
||||||
mimedata->enqueue_now_ = false;
|
mimedata->enqueue_now_ = false;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case BehaviourSettingsPage::AddBehaviour_Enqueue:
|
case BehaviourSettingsPage::AddBehaviour::Enqueue:
|
||||||
mimedata->clear_first_ = false;
|
mimedata->clear_first_ = false;
|
||||||
mimedata->enqueue_now_ = true;
|
mimedata->enqueue_now_ = true;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case BehaviourSettingsPage::AddBehaviour_Load:
|
case BehaviourSettingsPage::AddBehaviour::Load:
|
||||||
mimedata->clear_first_ = true;
|
mimedata->clear_first_ = true;
|
||||||
mimedata->enqueue_now_ = false;
|
mimedata->enqueue_now_ = false;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case BehaviourSettingsPage::AddBehaviour_OpenInNew:
|
case BehaviourSettingsPage::AddBehaviour::OpenInNew:
|
||||||
mimedata->open_in_new_playlist_ = true;
|
mimedata->open_in_new_playlist_ = true;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@@ -1721,16 +1726,16 @@ void MainWindow::ApplyAddBehaviour(const BehaviourSettingsPage::AddBehaviour b,
|
|||||||
void MainWindow::ApplyPlayBehaviour(const BehaviourSettingsPage::PlayBehaviour b, MimeData *mimedata) const {
|
void MainWindow::ApplyPlayBehaviour(const BehaviourSettingsPage::PlayBehaviour b, MimeData *mimedata) const {
|
||||||
|
|
||||||
switch (b) {
|
switch (b) {
|
||||||
case BehaviourSettingsPage::PlayBehaviour_Always:
|
case BehaviourSettingsPage::PlayBehaviour::Always:
|
||||||
mimedata->play_now_ = true;
|
mimedata->play_now_ = true;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case BehaviourSettingsPage::PlayBehaviour_Never:
|
case BehaviourSettingsPage::PlayBehaviour::Never:
|
||||||
mimedata->play_now_ = false;
|
mimedata->play_now_ = false;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case BehaviourSettingsPage::PlayBehaviour_IfStopped:
|
case BehaviourSettingsPage::PlayBehaviour::IfStopped:
|
||||||
mimedata->play_now_ = !(app_->player()->GetState() == Engine::Playing);
|
mimedata->play_now_ = !(app_->player()->GetState() == Engine::State::Playing);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1765,7 +1770,7 @@ void MainWindow::AddToPlaylist(QMimeData *q_mimedata) {
|
|||||||
void MainWindow::AddToPlaylistFromAction(QAction *action) {
|
void MainWindow::AddToPlaylistFromAction(QAction *action) {
|
||||||
|
|
||||||
const int destination = action->data().toInt();
|
const int destination = action->data().toInt();
|
||||||
PlaylistItemList items;
|
PlaylistItemPtrList items;
|
||||||
SongList songs;
|
SongList songs;
|
||||||
|
|
||||||
// Get the selected playlist items
|
// Get the selected playlist items
|
||||||
@@ -1816,7 +1821,7 @@ void MainWindow::PlaylistRightClick(const QPoint global_pos, const QModelIndex &
|
|||||||
playlist_menu_index_ = source_index;
|
playlist_menu_index_ = source_index;
|
||||||
|
|
||||||
// Is this song currently playing?
|
// Is this song currently playing?
|
||||||
if (app_->playlist_manager()->current()->current_row() == source_index.row() && app_->player()->GetState() == Engine::Playing) {
|
if (app_->playlist_manager()->current()->current_row() == source_index.row() && app_->player()->GetState() == Engine::State::Playing) {
|
||||||
playlist_play_pause_->setText(tr("Pause"));
|
playlist_play_pause_->setText(tr("Pause"));
|
||||||
playlist_play_pause_->setIcon(IconLoader::Load("media-playback-pause"));
|
playlist_play_pause_->setIcon(IconLoader::Load("media-playback-pause"));
|
||||||
}
|
}
|
||||||
@@ -1827,7 +1832,7 @@ void MainWindow::PlaylistRightClick(const QPoint global_pos, const QModelIndex &
|
|||||||
|
|
||||||
// Are we allowed to pause?
|
// Are we allowed to pause?
|
||||||
if (source_index.isValid()) {
|
if (source_index.isValid()) {
|
||||||
playlist_play_pause_->setEnabled(app_->playlist_manager()->current()->current_row() != source_index.row() || !(app_->playlist_manager()->current()->item_at(source_index.row())->options() & PlaylistItem::PauseDisabled));
|
playlist_play_pause_->setEnabled(app_->playlist_manager()->current()->current_row() != source_index.row() || !(app_->playlist_manager()->current()->item_at(source_index.row())->options() & PlaylistItem::Option::PauseDisabled));
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
playlist_play_pause_->setEnabled(false);
|
playlist_play_pause_->setEnabled(false);
|
||||||
@@ -1856,7 +1861,7 @@ void MainWindow::PlaylistRightClick(const QPoint global_pos, const QModelIndex &
|
|||||||
if (!item) continue;
|
if (!item) continue;
|
||||||
|
|
||||||
if (item->Metadata().url().isLocalFile()) ++local_songs;
|
if (item->Metadata().url().isLocalFile()) ++local_songs;
|
||||||
if (item->Metadata().source() == Song::Source_Collection) ++collection_songs;
|
if (item->Metadata().source() == Song::Source::Collection) ++collection_songs;
|
||||||
|
|
||||||
if (item->Metadata().has_cue()) {
|
if (item->Metadata().has_cue()) {
|
||||||
cue_selected = true;
|
cue_selected = true;
|
||||||
@@ -2036,10 +2041,10 @@ void MainWindow::PlaylistRightClick(const QPoint global_pos, const QModelIndex &
|
|||||||
void MainWindow::PlaylistPlay() {
|
void MainWindow::PlaylistPlay() {
|
||||||
|
|
||||||
if (app_->playlist_manager()->current()->current_row() == playlist_menu_index_.row()) {
|
if (app_->playlist_manager()->current()->current_row() == playlist_menu_index_.row()) {
|
||||||
app_->player()->PlayPause(Playlist::AutoScroll_Never);
|
app_->player()->PlayPause(0, Playlist::AutoScroll::Never);
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
PlayIndex(playlist_menu_index_, Playlist::AutoScroll_Never);
|
PlayIndex(playlist_menu_index_, Playlist::AutoScroll::Never);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -2060,7 +2065,7 @@ void MainWindow::RescanSongs() {
|
|||||||
if (item->IsLocalCollectionItem()) {
|
if (item->IsLocalCollectionItem()) {
|
||||||
songs << item->Metadata();
|
songs << item->Metadata();
|
||||||
}
|
}
|
||||||
else if (item->Metadata().source() == Song::Source_LocalFile) {
|
else if (item->Metadata().source() == Song::Source::LocalFile) {
|
||||||
QPersistentModelIndex persistent_index = QPersistentModelIndex(source_index);
|
QPersistentModelIndex persistent_index = QPersistentModelIndex(source_index);
|
||||||
app_->playlist_manager()->current()->ItemReload(persistent_index, item->OriginalMetadata(), false);
|
app_->playlist_manager()->current()->ItemReload(persistent_index, item->OriginalMetadata(), false);
|
||||||
}
|
}
|
||||||
@@ -2075,7 +2080,7 @@ void MainWindow::RescanSongs() {
|
|||||||
void MainWindow::EditTracks() {
|
void MainWindow::EditTracks() {
|
||||||
|
|
||||||
SongList songs;
|
SongList songs;
|
||||||
PlaylistItemList items;
|
PlaylistItemPtrList items;
|
||||||
|
|
||||||
for (const QModelIndex &proxy_index : ui_->playlist->view()->selectionModel()->selectedRows()) {
|
for (const QModelIndex &proxy_index : ui_->playlist->view()->selectionModel()->selectedRows()) {
|
||||||
const QModelIndex source_index = app_->playlist_manager()->current()->filter()->mapToSource(proxy_index);
|
const QModelIndex source_index = app_->playlist_manager()->current()->filter()->mapToSource(proxy_index);
|
||||||
@@ -2146,7 +2151,7 @@ void MainWindow::SongSaveComplete(TagReaderReply *reply, const QPersistentModelI
|
|||||||
if (reply->is_successful() && idx.isValid()) {
|
if (reply->is_successful() && idx.isValid()) {
|
||||||
app_->playlist_manager()->current()->ReloadItems(QList<int>() << idx.row());
|
app_->playlist_manager()->current()->ReloadItems(QList<int>() << idx.row());
|
||||||
}
|
}
|
||||||
QMetaObject::invokeMethod(reply, "deleteLater", Qt::QueuedConnection);
|
reply->deleteLater();
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2167,7 +2172,7 @@ void MainWindow::SelectionSetValue() {
|
|||||||
QPersistentModelIndex persistent_index = QPersistentModelIndex(source_index);
|
QPersistentModelIndex persistent_index = QPersistentModelIndex(source_index);
|
||||||
QObject::connect(reply, &TagReaderReply::Finished, this, [this, reply, persistent_index]() { SongSaveComplete(reply, persistent_index); }, Qt::QueuedConnection);
|
QObject::connect(reply, &TagReaderReply::Finished, this, [this, reply, persistent_index]() { SongSaveComplete(reply, persistent_index); }, Qt::QueuedConnection);
|
||||||
}
|
}
|
||||||
else if (song.source() == Song::Source_Stream) {
|
else if (song.source() == Song::Source::Stream) {
|
||||||
app_->playlist_manager()->current()->setData(source_index, column_value, 0);
|
app_->playlist_manager()->current()->setData(source_index, column_value, 0);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -2202,7 +2207,7 @@ void MainWindow::AddFile() {
|
|||||||
PlaylistParser parser(app_->collection_backend());
|
PlaylistParser parser(app_->collection_backend());
|
||||||
|
|
||||||
// Show dialog
|
// Show dialog
|
||||||
QStringList file_names = QFileDialog::getOpenFileNames(this, tr("Add file"), directory, QString("%1 (%2);;%3;;%4").arg(tr("Music"), FileView::kFileFilter, parser.filters(PlaylistParser::Type_Load), tr(kAllFilesFilterSpec)));
|
QStringList file_names = QFileDialog::getOpenFileNames(this, tr("Add file"), directory, QString("%1 (%2);;%3;;%4").arg(tr("Music"), FileView::kFileFilter, parser.filters(PlaylistParser::Type::Load), tr(kAllFilesFilterSpec)));
|
||||||
|
|
||||||
if (file_names.isEmpty()) return;
|
if (file_names.isEmpty()) return;
|
||||||
|
|
||||||
@@ -2338,30 +2343,30 @@ void MainWindow::CommandlineOptionsReceived(const quint32 instanceId, const QByt
|
|||||||
void MainWindow::CommandlineOptionsReceived(const CommandlineOptions &options) {
|
void MainWindow::CommandlineOptionsReceived(const CommandlineOptions &options) {
|
||||||
|
|
||||||
switch (options.player_action()) {
|
switch (options.player_action()) {
|
||||||
case CommandlineOptions::Player_Play:
|
case CommandlineOptions::PlayerAction::Play:
|
||||||
if (options.urls().empty()) {
|
if (options.urls().empty()) {
|
||||||
app_->player()->Play();
|
app_->player()->Play();
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case CommandlineOptions::Player_PlayPause:
|
case CommandlineOptions::PlayerAction::PlayPause:
|
||||||
app_->player()->PlayPause(Playlist::AutoScroll_Maybe);
|
app_->player()->PlayPause(0, Playlist::AutoScroll::Maybe);
|
||||||
break;
|
break;
|
||||||
case CommandlineOptions::Player_Pause:
|
case CommandlineOptions::PlayerAction::Pause:
|
||||||
app_->player()->Pause();
|
app_->player()->Pause();
|
||||||
break;
|
break;
|
||||||
case CommandlineOptions::Player_Stop:
|
case CommandlineOptions::PlayerAction::Stop:
|
||||||
app_->player()->Stop();
|
app_->player()->Stop();
|
||||||
break;
|
break;
|
||||||
case CommandlineOptions::Player_StopAfterCurrent:
|
case CommandlineOptions::PlayerAction::StopAfterCurrent:
|
||||||
app_->player()->StopAfterCurrent();
|
app_->player()->StopAfterCurrent();
|
||||||
break;
|
break;
|
||||||
case CommandlineOptions::Player_Previous:
|
case CommandlineOptions::PlayerAction::Previous:
|
||||||
app_->player()->Previous();
|
app_->player()->Previous();
|
||||||
break;
|
break;
|
||||||
case CommandlineOptions::Player_Next:
|
case CommandlineOptions::PlayerAction::Next:
|
||||||
app_->player()->Next();
|
app_->player()->Next();
|
||||||
break;
|
break;
|
||||||
case CommandlineOptions::Player_PlayPlaylist:
|
case CommandlineOptions::PlayerAction::PlayPlaylist:
|
||||||
if (options.playlist_name().isEmpty()) {
|
if (options.playlist_name().isEmpty()) {
|
||||||
qLog(Error) << "ERROR: playlist name missing";
|
qLog(Error) << "ERROR: playlist name missing";
|
||||||
}
|
}
|
||||||
@@ -2369,11 +2374,11 @@ void MainWindow::CommandlineOptionsReceived(const CommandlineOptions &options) {
|
|||||||
app_->player()->PlayPlaylist(options.playlist_name());
|
app_->player()->PlayPlaylist(options.playlist_name());
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case CommandlineOptions::Player_RestartOrPrevious:
|
case CommandlineOptions::PlayerAction::RestartOrPrevious:
|
||||||
app_->player()->RestartOrPrevious();
|
app_->player()->RestartOrPrevious();
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case CommandlineOptions::Player_ResizeWindow:{
|
case CommandlineOptions::PlayerAction::ResizeWindow:{
|
||||||
if (options.window_size().contains('x') && options.window_size().length() >= 4) {
|
if (options.window_size().contains('x') && options.window_size().length() >= 4) {
|
||||||
QString str_w = options.window_size().left(options.window_size().indexOf('x'));
|
QString str_w = options.window_size().left(options.window_size().indexOf('x'));
|
||||||
QString str_h = options.window_size().right(options.window_size().length() - options.window_size().indexOf('x') - 1);
|
QString str_h = options.window_size().right(options.window_size().length() - options.window_size().indexOf('x') - 1);
|
||||||
@@ -2410,7 +2415,7 @@ void MainWindow::CommandlineOptionsReceived(const CommandlineOptions &options) {
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
case CommandlineOptions::Player_None:
|
case CommandlineOptions::PlayerAction::None:
|
||||||
break;
|
break;
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -2430,22 +2435,22 @@ void MainWindow::CommandlineOptionsReceived(const CommandlineOptions &options) {
|
|||||||
// Behaviour depends on command line options, so set it here
|
// Behaviour depends on command line options, so set it here
|
||||||
mimedata->override_user_settings_ = true;
|
mimedata->override_user_settings_ = true;
|
||||||
|
|
||||||
if (options.player_action() == CommandlineOptions::Player_Play) mimedata->play_now_ = true;
|
if (options.player_action() == CommandlineOptions::PlayerAction::Play) mimedata->play_now_ = true;
|
||||||
else ApplyPlayBehaviour(doubleclick_playmode_, mimedata);
|
else ApplyPlayBehaviour(doubleclick_playmode_, mimedata);
|
||||||
|
|
||||||
switch (options.url_list_action()) {
|
switch (options.url_list_action()) {
|
||||||
case CommandlineOptions::UrlList_Load:
|
case CommandlineOptions::UrlListAction::Load:
|
||||||
mimedata->clear_first_ = true;
|
mimedata->clear_first_ = true;
|
||||||
break;
|
break;
|
||||||
case CommandlineOptions::UrlList_Append:
|
case CommandlineOptions::UrlListAction::Append:
|
||||||
// Nothing to do
|
// Nothing to do
|
||||||
break;
|
break;
|
||||||
case CommandlineOptions::UrlList_None:
|
case CommandlineOptions::UrlListAction::None:
|
||||||
ApplyAddBehaviour(doubleclick_addmode_, mimedata);
|
ApplyAddBehaviour(doubleclick_addmode_, mimedata);
|
||||||
break;
|
break;
|
||||||
case CommandlineOptions::UrlList_CreateNew:
|
case CommandlineOptions::UrlListAction::CreateNew:
|
||||||
mimedata->name_for_new_playlist_ = options.playlist_name();
|
mimedata->name_for_new_playlist_ = options.playlist_name();
|
||||||
ApplyAddBehaviour(BehaviourSettingsPage::AddBehaviour_OpenInNew, mimedata);
|
ApplyAddBehaviour(BehaviourSettingsPage::AddBehaviour::OpenInNew, mimedata);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2465,7 +2470,7 @@ void MainWindow::CommandlineOptionsReceived(const CommandlineOptions &options) {
|
|||||||
app_->player()->SeekTo(app_->player()->engine()->position_nanosec() / kNsecPerSec + options.seek_by());
|
app_->player()->SeekTo(app_->player()->engine()->position_nanosec() / kNsecPerSec + options.seek_by());
|
||||||
}
|
}
|
||||||
|
|
||||||
if (options.play_track_at() != -1) app_->player()->PlayAt(options.play_track_at(), 0, Engine::Manual, Playlist::AutoScroll_Maybe, true);
|
if (options.play_track_at() != -1) app_->player()->PlayAt(options.play_track_at(), 0, Engine::TrackChangeType::Manual, Playlist::AutoScroll::Maybe, true);
|
||||||
|
|
||||||
if (options.show_osd()) app_->player()->ShowOSD();
|
if (options.show_osd()) app_->player()->ShowOSD();
|
||||||
|
|
||||||
@@ -2543,7 +2548,7 @@ void MainWindow::AddFilesToTranscoder() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void MainWindow::ShowCollectionConfig() {
|
void MainWindow::ShowCollectionConfig() {
|
||||||
settings_dialog_->OpenAtPage(SettingsDialog::Page_Collection);
|
settings_dialog_->OpenAtPage(SettingsDialog::Page::Collection);
|
||||||
}
|
}
|
||||||
|
|
||||||
void MainWindow::TaskCountChanged(const int count) {
|
void MainWindow::TaskCountChanged(const int count) {
|
||||||
@@ -2613,7 +2618,7 @@ void MainWindow::EditFileTags(const QList<QUrl> &urls) {
|
|||||||
Song song;
|
Song song;
|
||||||
song.set_url(url);
|
song.set_url(url);
|
||||||
song.set_valid(true);
|
song.set_valid(true);
|
||||||
song.set_filetype(Song::FileType_MPEG);
|
song.set_filetype(Song::FileType::MPEG);
|
||||||
songs << song;
|
songs << song;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2755,16 +2760,16 @@ void MainWindow::PlaylistCopyToDevice() {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void MainWindow::ChangeCollectionQueryMode(QAction *action) {
|
void MainWindow::ChangeCollectionFilterMode(QAction *action) {
|
||||||
|
|
||||||
if (action == collection_show_duplicates_) {
|
if (action == collection_show_duplicates_) {
|
||||||
collection_view_->filter_widget()->SetQueryMode(QueryOptions::QueryMode_Duplicates);
|
collection_view_->filter_widget()->SetFilterMode(CollectionFilterOptions::FilterMode::Duplicates);
|
||||||
}
|
}
|
||||||
else if (action == collection_show_untagged_) {
|
else if (action == collection_show_untagged_) {
|
||||||
collection_view_->filter_widget()->SetQueryMode(QueryOptions::QueryMode_Untagged);
|
collection_view_->filter_widget()->SetFilterMode(CollectionFilterOptions::FilterMode::Untagged);
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
collection_view_->filter_widget()->SetQueryMode(QueryOptions::QueryMode_All);
|
collection_view_->filter_widget()->SetFilterMode(CollectionFilterOptions::FilterMode::All);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -2975,7 +2980,7 @@ void MainWindow::HandleNotificationPreview(const OSDBase::Behaviour type, const
|
|||||||
else {
|
else {
|
||||||
qLog(Debug) << "The current playlist is empty, showing a fake song";
|
qLog(Debug) << "The current playlist is empty, showing a fake song";
|
||||||
// Create a fake song
|
// Create a fake song
|
||||||
Song fake(Song::Source_LocalFile);
|
Song fake(Song::Source::LocalFile);
|
||||||
fake.Init("Title", "Artist", "Album", 123);
|
fake.Init("Title", "Artist", "Album", 123);
|
||||||
fake.set_genre("Classical");
|
fake.set_genre("Classical");
|
||||||
fake.set_composer("Anonymous");
|
fake.set_composer("Anonymous");
|
||||||
@@ -2999,7 +3004,7 @@ void MainWindow::ShowConsole() {
|
|||||||
void MainWindow::keyPressEvent(QKeyEvent *e) {
|
void MainWindow::keyPressEvent(QKeyEvent *e) {
|
||||||
|
|
||||||
if (e->key() == Qt::Key_Space) {
|
if (e->key() == Qt::Key_Space) {
|
||||||
app_->player()->PlayPause(Playlist::AutoScroll_Never);
|
app_->player()->PlayPause(0, Playlist::AutoScroll::Never);
|
||||||
e->accept();
|
e->accept();
|
||||||
}
|
}
|
||||||
else if (e->key() == Qt::Key_Left) {
|
else if (e->key() == Qt::Key_Left) {
|
||||||
@@ -3122,12 +3127,12 @@ void MainWindow::SetToggleScrobblingIcon(const bool value) {
|
|||||||
|
|
||||||
if (value) {
|
if (value) {
|
||||||
if (app_->playlist_manager()->active() && app_->playlist_manager()->active()->scrobbled())
|
if (app_->playlist_manager()->active() && app_->playlist_manager()->active()->scrobbled())
|
||||||
ui_->action_toggle_scrobbling->setIcon(IconLoader::Load("scrobble", 22));
|
ui_->action_toggle_scrobbling->setIcon(IconLoader::Load("scrobble", true, 22));
|
||||||
else
|
else
|
||||||
ui_->action_toggle_scrobbling->setIcon(IconLoader::Load("scrobble", 22)); // TODO: Create a faint version of the icon
|
ui_->action_toggle_scrobbling->setIcon(IconLoader::Load("scrobble", true, 22)); // TODO: Create a faint version of the icon
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
ui_->action_toggle_scrobbling->setIcon(IconLoader::Load("scrobble-disabled", 22));
|
ui_->action_toggle_scrobbling->setIcon(IconLoader::Load("scrobble-disabled", true, 22));
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -3162,17 +3167,17 @@ void MainWindow::PlaylistDelete() {
|
|||||||
|
|
||||||
if (DeleteConfirmationDialog::warning(files) != QDialogButtonBox::Yes) return;
|
if (DeleteConfirmationDialog::warning(files) != QDialogButtonBox::Yes) return;
|
||||||
|
|
||||||
if (app_->player()->GetState() == Engine::Playing && app_->playlist_manager()->current()->rowCount() == selected_songs.count()) {
|
if (app_->player()->GetState() == Engine::State::Playing && app_->playlist_manager()->current()->rowCount() == selected_songs.count()) {
|
||||||
app_->player()->Stop();
|
app_->player()->Stop();
|
||||||
}
|
}
|
||||||
|
|
||||||
ui_->playlist->view()->RemoveSelected();
|
ui_->playlist->view()->RemoveSelected();
|
||||||
|
|
||||||
if (app_->player()->GetState() == Engine::Playing && is_current_item) {
|
if (app_->player()->GetState() == Engine::State::Playing && is_current_item) {
|
||||||
app_->player()->Next();
|
app_->player()->Next();
|
||||||
}
|
}
|
||||||
|
|
||||||
std::shared_ptr<MusicStorage> storage = std::make_shared<FilesystemMusicStorage>(Song::Source_LocalFile, "/");
|
std::shared_ptr<MusicStorage> storage = std::make_shared<FilesystemMusicStorage>(Song::Source::LocalFile, "/");
|
||||||
DeleteFiles *delete_files = new DeleteFiles(app_->task_manager(), storage, true);
|
DeleteFiles *delete_files = new DeleteFiles(app_->task_manager(), storage, true);
|
||||||
//QObject::connect(delete_files, &DeleteFiles::Finished, this, &MainWindow::DeleteFinished);
|
//QObject::connect(delete_files, &DeleteFiles::Finished, this, &MainWindow::DeleteFinished);
|
||||||
delete_files->Start(selected_songs);
|
delete_files->Start(selected_songs);
|
||||||
|
|||||||
@@ -48,12 +48,12 @@
|
|||||||
#include <QSettings>
|
#include <QSettings>
|
||||||
#include <QtEvents>
|
#include <QtEvents>
|
||||||
|
|
||||||
#include "core/lazy.h"
|
#include "lazy.h"
|
||||||
#include "core/tagreaderclient.h"
|
#include "platforminterface.h"
|
||||||
#include "core/song.h"
|
#include "song.h"
|
||||||
|
#include "tagreaderclient.h"
|
||||||
#include "engine/enginetype.h"
|
#include "engine/enginetype.h"
|
||||||
#include "engine/engine_fwd.h"
|
#include "engine/engine_fwd.h"
|
||||||
#include "mac_startup.h"
|
|
||||||
#include "osd/osdbase.h"
|
#include "osd/osdbase.h"
|
||||||
#include "collection/collectionmodel.h"
|
#include "collection/collectionmodel.h"
|
||||||
#include "playlist/playlist.h"
|
#include "playlist/playlist.h"
|
||||||
@@ -180,7 +180,7 @@ class MainWindow : public QMainWindow, public PlatformInterface {
|
|||||||
void PlaylistCopyUrl();
|
void PlaylistCopyUrl();
|
||||||
void ShowInCollection();
|
void ShowInCollection();
|
||||||
|
|
||||||
void ChangeCollectionQueryMode(QAction *action);
|
void ChangeCollectionFilterMode(QAction *action);
|
||||||
|
|
||||||
void PlayIndex(const QModelIndex &idx, Playlist::AutoScroll autoscroll);
|
void PlayIndex(const QModelIndex &idx, Playlist::AutoScroll autoscroll);
|
||||||
void PlaylistDoubleClick(const QModelIndex &idx);
|
void PlaylistDoubleClick(const QModelIndex &idx);
|
||||||
@@ -331,7 +331,7 @@ class MainWindow : public QMainWindow, public PlatformInterface {
|
|||||||
std::unique_ptr<TagFetcher> tag_fetcher_;
|
std::unique_ptr<TagFetcher> tag_fetcher_;
|
||||||
#endif
|
#endif
|
||||||
std::unique_ptr<TrackSelectionDialog> track_selection_dialog_;
|
std::unique_ptr<TrackSelectionDialog> track_selection_dialog_;
|
||||||
PlaylistItemList autocomplete_tag_items_;
|
PlaylistItemPtrList autocomplete_tag_items_;
|
||||||
|
|
||||||
SmartPlaylistsViewContainer *smartplaylists_view_;
|
SmartPlaylistsViewContainer *smartplaylists_view_;
|
||||||
|
|
||||||
|
|||||||
@@ -38,7 +38,6 @@
|
|||||||
#include <QByteArray>
|
#include <QByteArray>
|
||||||
#include <QUrl>
|
#include <QUrl>
|
||||||
#include <QImage>
|
#include <QImage>
|
||||||
#include <QNetworkCookie>
|
|
||||||
#include <QNetworkReply>
|
#include <QNetworkReply>
|
||||||
#include <QItemSelection>
|
#include <QItemSelection>
|
||||||
#ifdef HAVE_DBUS
|
#ifdef HAVE_DBUS
|
||||||
@@ -54,7 +53,7 @@
|
|||||||
#ifdef HAVE_GSTREAMER
|
#ifdef HAVE_GSTREAMER
|
||||||
# include "engine/gstenginepipeline.h"
|
# include "engine/gstenginepipeline.h"
|
||||||
#endif
|
#endif
|
||||||
#include "collection/directory.h"
|
#include "collection/collectiondirectory.h"
|
||||||
#include "playlist/playlistitem.h"
|
#include "playlist/playlistitem.h"
|
||||||
#include "playlist/playlistsequence.h"
|
#include "playlist/playlistsequence.h"
|
||||||
#include "covermanager/albumcoverloaderresult.h"
|
#include "covermanager/albumcoverloaderresult.h"
|
||||||
@@ -78,17 +77,18 @@
|
|||||||
# include "device/mtpconnection.h"
|
# include "device/mtpconnection.h"
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
#include "settings/playlistsettingspage.h"
|
||||||
|
|
||||||
|
#include "smartplaylists/smartplaylistsearchterm.h"
|
||||||
|
#include "smartplaylists/smartplaylistsitem.h"
|
||||||
|
|
||||||
void RegisterMetaTypes() {
|
void RegisterMetaTypes() {
|
||||||
|
|
||||||
qRegisterMetaType<const char*>("const char*");
|
qRegisterMetaType<const char*>("const char*");
|
||||||
qRegisterMetaType<QList<int>>("QList<int>");
|
qRegisterMetaType<QList<int>>("QList<int>");
|
||||||
qRegisterMetaType<QList<QUrl>>("QList<QUrl>");
|
|
||||||
qRegisterMetaType<QVector<int>>("QVector<int>");
|
qRegisterMetaType<QVector<int>>("QVector<int>");
|
||||||
|
qRegisterMetaType<QList<QUrl>>("QList<QUrl>");
|
||||||
qRegisterMetaType<QFileInfo>("QFileInfo");
|
qRegisterMetaType<QFileInfo>("QFileInfo");
|
||||||
qRegisterMetaType<QAbstractSocket::SocketState>();
|
|
||||||
qRegisterMetaType<QAbstractSocket::SocketState>("QAbstractSocket::SocketState");
|
|
||||||
qRegisterMetaType<QNetworkCookie>("QNetworkCookie");
|
|
||||||
qRegisterMetaType<QList<QNetworkCookie>>("QList<QNetworkCookie>");
|
|
||||||
qRegisterMetaType<QNetworkReply*>("QNetworkReply*");
|
qRegisterMetaType<QNetworkReply*>("QNetworkReply*");
|
||||||
qRegisterMetaType<QNetworkReply**>("QNetworkReply**");
|
qRegisterMetaType<QNetworkReply**>("QNetworkReply**");
|
||||||
qRegisterMetaType<QItemSelection>("QItemSelection");
|
qRegisterMetaType<QItemSelection>("QItemSelection");
|
||||||
@@ -98,16 +98,12 @@ void RegisterMetaTypes() {
|
|||||||
qRegisterMetaTypeStreamOperators<QMap<int, Qt::Alignment>>("ColumnAlignmentMap");
|
qRegisterMetaTypeStreamOperators<QMap<int, Qt::Alignment>>("ColumnAlignmentMap");
|
||||||
qRegisterMetaTypeStreamOperators<QMap<int, int>>("ColumnAlignmentIntMap");
|
qRegisterMetaTypeStreamOperators<QMap<int, int>>("ColumnAlignmentIntMap");
|
||||||
#endif
|
#endif
|
||||||
qRegisterMetaType<Directory>("Directory");
|
|
||||||
qRegisterMetaType<DirectoryList>("DirectoryList");
|
|
||||||
qRegisterMetaType<Subdirectory>("Subdirectory");
|
|
||||||
qRegisterMetaType<SubdirectoryList>("SubdirectoryList");
|
|
||||||
qRegisterMetaType<Song>("Song");
|
qRegisterMetaType<Song>("Song");
|
||||||
qRegisterMetaType<SongList>("SongList");
|
qRegisterMetaType<SongList>("SongList");
|
||||||
qRegisterMetaType<SongMap>("SongMap");
|
qRegisterMetaType<SongMap>("SongMap");
|
||||||
qRegisterMetaType<QList<Song>>("QList<Song>");
|
qRegisterMetaType<Song::Source>("Song::Source");
|
||||||
qRegisterMetaType<QMap<QString, Song>>("QMap<QString, Song>");
|
qRegisterMetaType<Song::FileType>("Song::FileType");
|
||||||
qRegisterMetaType<Engine::EngineType>("EngineType");
|
qRegisterMetaType<Engine::EngineType>("Engine::EngineType");
|
||||||
qRegisterMetaType<Engine::SimpleMetaBundle>("Engine::SimpleMetaBundle");
|
qRegisterMetaType<Engine::SimpleMetaBundle>("Engine::SimpleMetaBundle");
|
||||||
qRegisterMetaType<Engine::State>("Engine::State");
|
qRegisterMetaType<Engine::State>("Engine::State");
|
||||||
qRegisterMetaType<Engine::TrackChangeFlags>("Engine::TrackChangeFlags");
|
qRegisterMetaType<Engine::TrackChangeFlags>("Engine::TrackChangeFlags");
|
||||||
@@ -117,23 +113,29 @@ void RegisterMetaTypes() {
|
|||||||
qRegisterMetaType<GstElement*>("GstElement*");
|
qRegisterMetaType<GstElement*>("GstElement*");
|
||||||
qRegisterMetaType<GstEnginePipeline*>("GstEnginePipeline*");
|
qRegisterMetaType<GstEnginePipeline*>("GstEnginePipeline*");
|
||||||
#endif
|
#endif
|
||||||
|
qRegisterMetaType<CollectionDirectory>("CollectionDirectory");
|
||||||
|
qRegisterMetaType<CollectionDirectoryList>("CollectionDirectoryList");
|
||||||
|
qRegisterMetaType<CollectionSubdirectory>("CollectionSubdirectory");
|
||||||
|
qRegisterMetaType<CollectionSubdirectoryList>("CollectionSubdirectoryList");
|
||||||
|
qRegisterMetaType<CollectionModel::Grouping>("CollectionModel::Grouping");
|
||||||
qRegisterMetaType<PlaylistItemPtr>("PlaylistItemPtr");
|
qRegisterMetaType<PlaylistItemPtr>("PlaylistItemPtr");
|
||||||
qRegisterMetaType<PlaylistItemList>("PlaylistItemList");
|
qRegisterMetaType<PlaylistItemPtrList>("PlaylistItemPtrList");
|
||||||
qRegisterMetaType<QList<PlaylistItemPtr>>("QList<PlaylistItemPtr>");
|
|
||||||
qRegisterMetaType<PlaylistSequence::RepeatMode>("PlaylistSequence::RepeatMode");
|
qRegisterMetaType<PlaylistSequence::RepeatMode>("PlaylistSequence::RepeatMode");
|
||||||
qRegisterMetaType<PlaylistSequence::ShuffleMode>("PlaylistSequence::ShuffleMode");
|
qRegisterMetaType<PlaylistSequence::ShuffleMode>("PlaylistSequence::ShuffleMode");
|
||||||
qRegisterMetaType<AlbumCoverLoaderResult>("AlbumCoverLoaderResult");
|
qRegisterMetaType<AlbumCoverLoaderResult>("AlbumCoverLoaderResult");
|
||||||
qRegisterMetaType<AlbumCoverLoaderResult::Type>("AlbumCoverLoaderResult::Type");
|
qRegisterMetaType<AlbumCoverLoaderResult::Type>("AlbumCoverLoaderResult::Type");
|
||||||
qRegisterMetaType<CoverProviderSearchResult>("CoverProviderSearchResult");
|
qRegisterMetaType<CoverProviderSearchResult>("CoverProviderSearchResult");
|
||||||
qRegisterMetaType<CoverSearchStatistics>("CoverSearchStatistics");
|
|
||||||
qRegisterMetaType<QList<CoverProviderSearchResult>>("QList<CoverProviderSearchResult>");
|
|
||||||
qRegisterMetaType<CoverProviderSearchResults>("CoverProviderSearchResults");
|
qRegisterMetaType<CoverProviderSearchResults>("CoverProviderSearchResults");
|
||||||
|
qRegisterMetaType<CoverSearchStatistics>("CoverSearchStatistics");
|
||||||
|
|
||||||
qRegisterMetaType<Equalizer::Params>("Equalizer::Params");
|
qRegisterMetaType<Equalizer::Params>("Equalizer::Params");
|
||||||
|
|
||||||
#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
|
#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
|
||||||
qRegisterMetaTypeStreamOperators<Equalizer::Params>("Equalizer::Params");
|
qRegisterMetaTypeStreamOperators<Equalizer::Params>("Equalizer::Params");
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#ifdef HAVE_DBUS
|
#ifdef HAVE_DBUS
|
||||||
qDBusRegisterMetaType<QList<QByteArray>>();
|
qDBusRegisterMetaType<QByteArrayList>();
|
||||||
qDBusRegisterMetaType<QImage>();
|
qDBusRegisterMetaType<QImage>();
|
||||||
qDBusRegisterMetaType<TrackMetadata>();
|
qDBusRegisterMetaType<TrackMetadata>();
|
||||||
qDBusRegisterMetaType<Track_Ids>();
|
qDBusRegisterMetaType<Track_Ids>();
|
||||||
@@ -144,15 +146,21 @@ void RegisterMetaTypes() {
|
|||||||
qDBusRegisterMetaType<ManagedObjectList>();
|
qDBusRegisterMetaType<ManagedObjectList>();
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
qRegisterMetaType<InternetSearchView::ResultList>("InternetSearchView::ResultList");
|
|
||||||
qRegisterMetaType<InternetSearchView::Result>("InternetSearchView::Result");
|
qRegisterMetaType<InternetSearchView::Result>("InternetSearchView::Result");
|
||||||
|
qRegisterMetaType<InternetSearchView::ResultList>("InternetSearchView::ResultList");
|
||||||
|
|
||||||
qRegisterMetaType<PlaylistGeneratorPtr>("PlaylistGeneratorPtr");
|
qRegisterMetaType<RadioChannel>("RadioChannel");
|
||||||
|
|
||||||
qRegisterMetaType<RadioChannelList>("RadioChannelList");
|
qRegisterMetaType<RadioChannelList>("RadioChannelList");
|
||||||
|
|
||||||
#ifdef HAVE_LIBMTP
|
#ifdef HAVE_LIBMTP
|
||||||
qRegisterMetaType<MtpConnection*>("MtpConnection*");
|
qRegisterMetaType<MtpConnection*>("MtpConnection*");
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
qRegisterMetaType<PlaylistSettingsPage::PathType>("PlaylistSettingsPage::PathType");
|
||||||
|
|
||||||
|
qRegisterMetaType<PlaylistGeneratorPtr>("PlaylistGeneratorPtr");
|
||||||
|
qRegisterMetaType<SmartPlaylistSearchTerm::Operator>("SmartPlaylistSearchTerm::Operator");
|
||||||
|
qRegisterMetaType<SmartPlaylistSearchTerm::OperatorList>("SmartPlaylistSearchTerm::OperatorList");
|
||||||
|
qRegisterMetaType<SmartPlaylistsItem::Type>("SmartPlaylistsItem::Type");
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -22,6 +22,7 @@
|
|||||||
#include "config.h"
|
#include "config.h"
|
||||||
|
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
|
#include <cmath>
|
||||||
|
|
||||||
#include <QApplication>
|
#include <QApplication>
|
||||||
#include <QCoreApplication>
|
#include <QCoreApplication>
|
||||||
@@ -45,10 +46,10 @@
|
|||||||
#include "mpris_common.h"
|
#include "mpris_common.h"
|
||||||
#include "mpris2.h"
|
#include "mpris2.h"
|
||||||
|
|
||||||
#include "timeconstants.h"
|
|
||||||
#include "song.h"
|
#include "song.h"
|
||||||
#include "application.h"
|
#include "application.h"
|
||||||
#include "player.h"
|
#include "player.h"
|
||||||
|
#include "utilities/timeconstants.h"
|
||||||
#include "engine/enginebase.h"
|
#include "engine/enginebase.h"
|
||||||
#include "playlist/playlist.h"
|
#include "playlist/playlist.h"
|
||||||
#include "playlist/playlistitem.h"
|
#include "playlist/playlistitem.h"
|
||||||
@@ -163,7 +164,7 @@ void Mpris2::PlaylistManagerInitialized() {
|
|||||||
|
|
||||||
void Mpris2::EngineStateChanged(Engine::State newState) {
|
void Mpris2::EngineStateChanged(Engine::State newState) {
|
||||||
|
|
||||||
if (newState != Engine::Playing && newState != Engine::Paused) {
|
if (newState != Engine::State::Playing && newState != Engine::State::Paused) {
|
||||||
last_metadata_ = QVariantMap();
|
last_metadata_ = QVariantMap();
|
||||||
EmitNotification("Metadata");
|
EmitNotification("Metadata");
|
||||||
}
|
}
|
||||||
@@ -171,11 +172,13 @@ void Mpris2::EngineStateChanged(Engine::State newState) {
|
|||||||
EmitNotification("CanPlay");
|
EmitNotification("CanPlay");
|
||||||
EmitNotification("CanPause");
|
EmitNotification("CanPause");
|
||||||
EmitNotification("PlaybackStatus", PlaybackStatus(newState));
|
EmitNotification("PlaybackStatus", PlaybackStatus(newState));
|
||||||
if (newState == Engine::Playing) EmitNotification("CanSeek", CanSeek(newState));
|
if (newState == Engine::State::Playing) EmitNotification("CanSeek", CanSeek(newState));
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void Mpris2::VolumeChanged() { EmitNotification("Volume"); }
|
void Mpris2::VolumeChanged() {
|
||||||
|
EmitNotification("Volume");
|
||||||
|
}
|
||||||
|
|
||||||
void Mpris2::ShuffleModeChanged() { EmitNotification("Shuffle"); }
|
void Mpris2::ShuffleModeChanged() { EmitNotification("Shuffle"); }
|
||||||
|
|
||||||
@@ -187,15 +190,15 @@ void Mpris2::RepeatModeChanged() {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void Mpris2::EmitNotification(const QString &name, const QVariant &val) {
|
void Mpris2::EmitNotification(const QString &name, const QVariant &value) {
|
||||||
EmitNotification(name, val, "org.mpris.MediaPlayer2.Player");
|
EmitNotification(name, value, "org.mpris.MediaPlayer2.Player");
|
||||||
}
|
}
|
||||||
|
|
||||||
void Mpris2::EmitNotification(const QString &name, const QVariant &val, const QString &mprisEntity) {
|
void Mpris2::EmitNotification(const QString &name, const QVariant &value, const QString &mprisEntity) {
|
||||||
|
|
||||||
QDBusMessage msg = QDBusMessage::createSignal(kMprisObjectPath, kFreedesktopPath, "PropertiesChanged");
|
QDBusMessage msg = QDBusMessage::createSignal(kMprisObjectPath, kFreedesktopPath, "PropertiesChanged");
|
||||||
QVariantMap map;
|
QVariantMap map;
|
||||||
map.insert(name, val);
|
map.insert(name, value);
|
||||||
QVariantList args = QVariantList() << mprisEntity << map << QStringList();
|
QVariantList args = QVariantList() << mprisEntity << map << QStringList();
|
||||||
msg.setArguments(args);
|
msg.setArguments(args);
|
||||||
QDBusConnection::sessionBus().send(msg);
|
QDBusConnection::sessionBus().send(msg);
|
||||||
@@ -304,8 +307,8 @@ QString Mpris2::PlaybackStatus() const {
|
|||||||
QString Mpris2::PlaybackStatus(Engine::State state) const {
|
QString Mpris2::PlaybackStatus(Engine::State state) const {
|
||||||
|
|
||||||
switch (state) {
|
switch (state) {
|
||||||
case Engine::Playing: return "Playing";
|
case Engine::State::Playing: return "Playing";
|
||||||
case Engine::Paused: return "Paused";
|
case Engine::State::Paused: return "Paused";
|
||||||
default: return "Stopped";
|
default: return "Stopped";
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -318,9 +321,9 @@ QString Mpris2::LoopStatus() const {
|
|||||||
}
|
}
|
||||||
|
|
||||||
switch (app_->playlist_manager()->sequence()->repeat_mode()) {
|
switch (app_->playlist_manager()->sequence()->repeat_mode()) {
|
||||||
case PlaylistSequence::Repeat_Album:
|
case PlaylistSequence::RepeatMode::Album:
|
||||||
case PlaylistSequence::Repeat_Playlist: return "Playlist";
|
case PlaylistSequence::RepeatMode::Playlist: return "Playlist";
|
||||||
case PlaylistSequence::Repeat_Track: return "Track";
|
case PlaylistSequence::RepeatMode::Track: return "Track";
|
||||||
default: return "None";
|
default: return "None";
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -328,16 +331,16 @@ QString Mpris2::LoopStatus() const {
|
|||||||
|
|
||||||
void Mpris2::SetLoopStatus(const QString &value) {
|
void Mpris2::SetLoopStatus(const QString &value) {
|
||||||
|
|
||||||
PlaylistSequence::RepeatMode mode = PlaylistSequence::Repeat_Off;
|
PlaylistSequence::RepeatMode mode = PlaylistSequence::RepeatMode::Off;
|
||||||
|
|
||||||
if (value == "None") {
|
if (value == "None") {
|
||||||
mode = PlaylistSequence::Repeat_Off;
|
mode = PlaylistSequence::RepeatMode::Off;
|
||||||
}
|
}
|
||||||
else if (value == "Track") {
|
else if (value == "Track") {
|
||||||
mode = PlaylistSequence::Repeat_Track;
|
mode = PlaylistSequence::RepeatMode::Track;
|
||||||
}
|
}
|
||||||
else if (value == "Playlist") {
|
else if (value == "Playlist") {
|
||||||
mode = PlaylistSequence::Repeat_Playlist;
|
mode = PlaylistSequence::RepeatMode::Playlist;
|
||||||
}
|
}
|
||||||
|
|
||||||
app_->playlist_manager()->active()->sequence()->SetRepeatMode(mode);
|
app_->playlist_manager()->active()->sequence()->SetRepeatMode(mode);
|
||||||
@@ -356,12 +359,12 @@ void Mpris2::SetRate(double rate) {
|
|||||||
|
|
||||||
bool Mpris2::Shuffle() const {
|
bool Mpris2::Shuffle() const {
|
||||||
|
|
||||||
return app_->playlist_manager()->sequence()->shuffle_mode() != PlaylistSequence::Shuffle_Off;
|
return app_->playlist_manager()->sequence()->shuffle_mode() != PlaylistSequence::ShuffleMode::Off;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void Mpris2::SetShuffle(bool enable) {
|
void Mpris2::SetShuffle(bool enable) {
|
||||||
app_->playlist_manager()->active()->sequence()->SetShuffleMode(enable ? PlaylistSequence::Shuffle_All : PlaylistSequence::Shuffle_Off);
|
app_->playlist_manager()->active()->sequence()->SetShuffleMode(enable ? PlaylistSequence::ShuffleMode::All : PlaylistSequence::ShuffleMode::Off);
|
||||||
}
|
}
|
||||||
|
|
||||||
QVariantMap Mpris2::Metadata() const { return last_metadata_; }
|
QVariantMap Mpris2::Metadata() const { return last_metadata_; }
|
||||||
@@ -398,6 +401,13 @@ void Mpris2::AlbumCoverLoaded(const Song &song, const AlbumCoverLoaderResult &re
|
|||||||
else if (result.temp_cover_url.isValid() && result.temp_cover_url.isLocalFile()) {
|
else if (result.temp_cover_url.isValid() && result.temp_cover_url.isLocalFile()) {
|
||||||
cover_url = result.temp_cover_url;
|
cover_url = result.temp_cover_url;
|
||||||
}
|
}
|
||||||
|
else if (song.art_manual().isValid() && song.art_manual().isLocalFile() && song.art_manual().path() != Song::kManuallyUnsetCover && song.art_manual().path() != Song::kEmbeddedCover) {
|
||||||
|
cover_url = song.art_manual();
|
||||||
|
}
|
||||||
|
else if (song.art_automatic().isValid() && song.art_automatic().isLocalFile() && song.art_automatic().path() != Song::kManuallyUnsetCover && song.art_automatic().path() != Song::kEmbeddedCover) {
|
||||||
|
cover_url = song.art_automatic();
|
||||||
|
}
|
||||||
|
|
||||||
if (cover_url.isValid()) AddMetadata("mpris:artUrl", cover_url.toString(), &last_metadata_);
|
if (cover_url.isValid()) AddMetadata("mpris:artUrl", cover_url.toString(), &last_metadata_);
|
||||||
|
|
||||||
AddMetadata("year", song.year(), &last_metadata_);
|
AddMetadata("year", song.year(), &last_metadata_);
|
||||||
@@ -411,8 +421,8 @@ double Mpris2::Volume() const {
|
|||||||
return app_->player()->GetVolume() / 100.0;
|
return app_->player()->GetVolume() / 100.0;
|
||||||
}
|
}
|
||||||
|
|
||||||
void Mpris2::SetVolume(const double value) {
|
void Mpris2::SetVolume(const double volume) {
|
||||||
app_->player()->SetVolume(static_cast<uint>(std::max(std::min(lround(value * 100.0), 100L), 0L)));
|
app_->player()->SetVolume(static_cast<uint>(qBound(0L, lround(volume * 100.0), 100L)));
|
||||||
}
|
}
|
||||||
|
|
||||||
qint64 Mpris2::Position() const {
|
qint64 Mpris2::Position() const {
|
||||||
@@ -437,13 +447,13 @@ bool Mpris2::CanPlay() const {
|
|||||||
|
|
||||||
// This one's a bit different than MPRIS 1 - we want this to be true even when the song is already paused or stopped.
|
// This one's a bit different than MPRIS 1 - we want this to be true even when the song is already paused or stopped.
|
||||||
bool Mpris2::CanPause() const {
|
bool Mpris2::CanPause() const {
|
||||||
return (app_->player()->GetCurrentItem() && app_->player()->GetState() == Engine::Playing && !(app_->player()->GetCurrentItem()->options() & PlaylistItem::PauseDisabled)) || PlaybackStatus() == "Paused" || PlaybackStatus() == "Stopped";
|
return (app_->player()->GetCurrentItem() && app_->player()->GetState() == Engine::State::Playing && !(app_->player()->GetCurrentItem()->options() & PlaylistItem::Option::PauseDisabled)) || PlaybackStatus() == "Paused" || PlaybackStatus() == "Stopped";
|
||||||
}
|
}
|
||||||
|
|
||||||
bool Mpris2::CanSeek() const { return CanSeek(app_->player()->GetState()); }
|
bool Mpris2::CanSeek() const { return CanSeek(app_->player()->GetState()); }
|
||||||
|
|
||||||
bool Mpris2::CanSeek(Engine::State state) const {
|
bool Mpris2::CanSeek(Engine::State state) const {
|
||||||
return app_->player()->GetCurrentItem() && state != Engine::Empty && !app_->player()->GetCurrentItem()->Metadata().is_stream();
|
return app_->player()->GetCurrentItem() && state != Engine::State::Empty && !app_->player()->GetCurrentItem()->Metadata().is_stream();
|
||||||
}
|
}
|
||||||
|
|
||||||
bool Mpris2::CanControl() const { return true; }
|
bool Mpris2::CanControl() const { return true; }
|
||||||
@@ -461,7 +471,7 @@ void Mpris2::Previous() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void Mpris2::Pause() {
|
void Mpris2::Pause() {
|
||||||
if (CanPause() && app_->player()->GetState() != Engine::Paused) {
|
if (CanPause() && app_->player()->GetState() != Engine::State::Paused) {
|
||||||
app_->player()->Pause();
|
app_->player()->Pause();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -145,7 +145,7 @@ class Mpris2 : public QObject {
|
|||||||
void SetShuffle(bool enable);
|
void SetShuffle(bool enable);
|
||||||
QVariantMap Metadata() const;
|
QVariantMap Metadata() const;
|
||||||
double Volume() const;
|
double Volume() const;
|
||||||
void SetVolume(const double value);
|
void SetVolume(const double volume);
|
||||||
qint64 Position() const;
|
qint64 Position() const;
|
||||||
double MaximumRate() const;
|
double MaximumRate() const;
|
||||||
double MinimumRate() const;
|
double MinimumRate() const;
|
||||||
@@ -215,8 +215,8 @@ class Mpris2 : public QObject {
|
|||||||
|
|
||||||
private:
|
private:
|
||||||
void EmitNotification(const QString &name);
|
void EmitNotification(const QString &name);
|
||||||
void EmitNotification(const QString &name, const QVariant &val);
|
void EmitNotification(const QString &name, const QVariant &value);
|
||||||
void EmitNotification(const QString &name, const QVariant &val, const QString &mprisEntity);
|
void EmitNotification(const QString &name, const QVariant &value, const QString &mprisEntity);
|
||||||
|
|
||||||
QString PlaybackStatus(Engine::State state) const;
|
QString PlaybackStatus(Engine::State state) const;
|
||||||
|
|
||||||
|
|||||||
@@ -50,7 +50,7 @@ class MusicStorage {
|
|||||||
};
|
};
|
||||||
|
|
||||||
// Values are saved in the database - don't change
|
// Values are saved in the database - don't change
|
||||||
enum TranscodeMode {
|
enum class TranscodeMode {
|
||||||
Transcode_Always = 1,
|
Transcode_Always = 1,
|
||||||
Transcode_Never = 2,
|
Transcode_Never = 2,
|
||||||
Transcode_Unsupported = 3,
|
Transcode_Unsupported = 3,
|
||||||
@@ -83,8 +83,8 @@ class MusicStorage {
|
|||||||
virtual QString LocalPath() const { return QString(); }
|
virtual QString LocalPath() const { return QString(); }
|
||||||
virtual std::optional<int> collection_directory_id() const { return std::optional<int>(); }
|
virtual std::optional<int> collection_directory_id() const { return std::optional<int>(); }
|
||||||
|
|
||||||
virtual TranscodeMode GetTranscodeMode() const { return Transcode_Never; }
|
virtual TranscodeMode GetTranscodeMode() const { return TranscodeMode::Transcode_Never; }
|
||||||
virtual Song::FileType GetTranscodeFormat() const { return Song::FileType_Unknown; }
|
virtual Song::FileType GetTranscodeFormat() const { return Song::FileType::Unknown; }
|
||||||
virtual bool GetSupportedFiletypes(QList<Song::FileType> *ret) { Q_UNUSED(ret); return true; }
|
virtual bool GetSupportedFiletypes(QList<Song::FileType> *ret) { Q_UNUSED(ret); return true; }
|
||||||
|
|
||||||
virtual bool StartCopy(QList<Song::FileType> *supported_types) { Q_UNUSED(supported_types); return true; }
|
virtual bool StartCopy(QList<Song::FileType> *supported_types) { Q_UNUSED(supported_types); return true; }
|
||||||
|
|||||||
@@ -35,7 +35,7 @@ NetworkProxyFactory *NetworkProxyFactory::sInstance = nullptr;
|
|||||||
const char *NetworkProxyFactory::kSettingsGroup = "NetworkProxy";
|
const char *NetworkProxyFactory::kSettingsGroup = "NetworkProxy";
|
||||||
|
|
||||||
NetworkProxyFactory::NetworkProxyFactory()
|
NetworkProxyFactory::NetworkProxyFactory()
|
||||||
: mode_(Mode_System),
|
: mode_(Mode::System),
|
||||||
type_(QNetworkProxy::HttpProxy),
|
type_(QNetworkProxy::HttpProxy),
|
||||||
port_(8080),
|
port_(8080),
|
||||||
use_authentication_(false) {
|
use_authentication_(false) {
|
||||||
@@ -81,7 +81,7 @@ void NetworkProxyFactory::ReloadSettings() {
|
|||||||
QSettings s;
|
QSettings s;
|
||||||
s.beginGroup(kSettingsGroup);
|
s.beginGroup(kSettingsGroup);
|
||||||
|
|
||||||
mode_ = Mode(s.value("mode", Mode_System).toInt());
|
mode_ = static_cast<Mode>(s.value("mode", static_cast<int>(Mode::System)).toInt());
|
||||||
type_ = QNetworkProxy::ProxyType(s.value("type", QNetworkProxy::HttpProxy).toInt());
|
type_ = QNetworkProxy::ProxyType(s.value("type", QNetworkProxy::HttpProxy).toInt());
|
||||||
hostname_ = s.value("hostname").toString();
|
hostname_ = s.value("hostname").toString();
|
||||||
port_ = s.value("port", 8080).toInt();
|
port_ = s.value("port", 8080).toInt();
|
||||||
@@ -100,7 +100,7 @@ QList<QNetworkProxy> NetworkProxyFactory::queryProxy(const QNetworkProxyQuery &q
|
|||||||
QNetworkProxy ret;
|
QNetworkProxy ret;
|
||||||
|
|
||||||
switch (mode_) {
|
switch (mode_) {
|
||||||
case Mode_System:
|
case Mode::System:
|
||||||
#ifdef Q_OS_LINUX
|
#ifdef Q_OS_LINUX
|
||||||
Q_UNUSED(query);
|
Q_UNUSED(query);
|
||||||
|
|
||||||
@@ -125,11 +125,11 @@ QList<QNetworkProxy> NetworkProxyFactory::queryProxy(const QNetworkProxyQuery &q
|
|||||||
return systemProxyForQuery(query);
|
return systemProxyForQuery(query);
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
case Mode_Direct:
|
case Mode::Direct:
|
||||||
ret.setType(QNetworkProxy::NoProxy);
|
ret.setType(QNetworkProxy::NoProxy);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case Mode_Manual:
|
case Mode::Manual:
|
||||||
ret.setType(type_);
|
ret.setType(type_);
|
||||||
ret.setHostName(hostname_);
|
ret.setHostName(hostname_);
|
||||||
ret.setPort(port_);
|
ret.setPort(port_);
|
||||||
|
|||||||
@@ -34,7 +34,11 @@
|
|||||||
class NetworkProxyFactory : public QNetworkProxyFactory {
|
class NetworkProxyFactory : public QNetworkProxyFactory {
|
||||||
public:
|
public:
|
||||||
// These values are persisted
|
// These values are persisted
|
||||||
enum Mode { Mode_System = 0, Mode_Direct = 1, Mode_Manual = 2, };
|
enum class Mode {
|
||||||
|
System = 0,
|
||||||
|
Direct = 1,
|
||||||
|
Manual = 2
|
||||||
|
};
|
||||||
|
|
||||||
static NetworkProxyFactory *Instance();
|
static NetworkProxyFactory *Instance();
|
||||||
static const char *kSettingsGroup;
|
static const char *kSettingsGroup;
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
/*
|
/*
|
||||||
* Strawberry Music Player
|
* Strawberry Music Player
|
||||||
* This file was part of Clementine.
|
* Copyright 2010, David Sansome <me@davidsansome.com>
|
||||||
* Copyright 2012, Arnaud Bienner <arnaud.bienner@gmail.com>
|
* Copyright 2018-2021, Jonas Kvinge <jonas@jkvinge.net>
|
||||||
*
|
*
|
||||||
* Strawberry is free software: you can redistribute it and/or modify
|
* Strawberry is free software: you can redistribute it and/or modify
|
||||||
* it under the terms of the GNU General Public License as published by
|
* it under the terms of the GNU General Public License as published by
|
||||||
@@ -18,31 +18,23 @@
|
|||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#ifndef APPEARANCE_H
|
#ifndef PLATFORMINTERFACE_H
|
||||||
#define APPEARANCE_H
|
#define PLATFORMINTERFACE_H
|
||||||
|
|
||||||
#include "config.h"
|
|
||||||
|
|
||||||
#include <QObject>
|
#include <QObject>
|
||||||
#include <QColor>
|
#include <QString>
|
||||||
#include <QPalette>
|
|
||||||
|
|
||||||
class Appearance : public QObject {
|
|
||||||
Q_OBJECT
|
|
||||||
|
|
||||||
|
class PlatformInterface {
|
||||||
public:
|
public:
|
||||||
explicit Appearance(QObject *parent = nullptr);
|
PlatformInterface() = default;
|
||||||
|
virtual ~PlatformInterface() {}
|
||||||
|
|
||||||
static const QPalette kDefaultPalette;
|
// Called when the application should show itself.
|
||||||
|
virtual void Activate() = 0;
|
||||||
void LoadUserTheme();
|
virtual bool LoadUrl(const QString &url) = 0;
|
||||||
static void ResetToSystemDefaultTheme();
|
|
||||||
void ChangeForegroundColor(const QColor &color);
|
|
||||||
void ChangeBackgroundColor(const QColor &color);
|
|
||||||
|
|
||||||
private:
|
private:
|
||||||
QColor foreground_color_;
|
Q_DISABLE_COPY(PlatformInterface)
|
||||||
QColor background_color_;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif // APPEARANCE_H
|
#endif // PLATFORMINTERFACE_H
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user