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]
|
||||
|
||||
jobs:
|
||||
@@ -101,13 +101,10 @@ jobs:
|
||||
with:
|
||||
fetch-depth: 0
|
||||
- name: Add safe git directory
|
||||
shell: bash
|
||||
run: git config --global --add safe.directory ${GITHUB_WORKSPACE}
|
||||
- name: Create Build Environment
|
||||
shell: bash
|
||||
run: cmake -E make_directory build
|
||||
- name: Configure CMake
|
||||
shell: bash
|
||||
working-directory: build
|
||||
run: cmake .. -DCMAKE_BUILD_TYPE=Debug -DBUILD_WERROR=ON -DUSE_TAGLIB=ON
|
||||
- name: Create source tarball
|
||||
@@ -137,17 +134,17 @@ jobs:
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
fedora_version: [ '35', '36', '37' ]
|
||||
fedora_version: [ '36', '37', '38' ]
|
||||
container:
|
||||
image: fedora:${{matrix.fedora_version}}
|
||||
steps:
|
||||
- name: Update repositories
|
||||
run: yum update --assumeyes
|
||||
run: dnf update -y
|
||||
- name: Upgrade packages
|
||||
run: yum upgrade --assumeyes
|
||||
run: dnf upgrade -y
|
||||
- name: Install dependencies
|
||||
run: >
|
||||
dnf install --assumeyes
|
||||
dnf install -y
|
||||
@development-tools
|
||||
redhat-lsb-core
|
||||
which
|
||||
@@ -187,17 +184,16 @@ jobs:
|
||||
desktop-file-utils
|
||||
libappstream-glib
|
||||
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
|
||||
with:
|
||||
fetch-depth: 0
|
||||
- name: Add safe git directory
|
||||
shell: bash
|
||||
run: git config --global --add safe.directory ${GITHUB_WORKSPACE}
|
||||
- name: Create Build Environment
|
||||
shell: bash
|
||||
run: cmake -E make_directory build
|
||||
- name: Configure CMake
|
||||
shell: bash
|
||||
working-directory: build
|
||||
run: cmake .. -DCMAKE_BUILD_TYPE=Debug -DBUILD_WERROR=ON
|
||||
- name: Create source tarball
|
||||
@@ -227,12 +223,12 @@ jobs:
|
||||
image: openmandriva/${{matrix.openmandriva_version}}
|
||||
steps:
|
||||
- name: Update repositories
|
||||
run: dnf update --assumeyes
|
||||
run: dnf update -y
|
||||
- name: Upgrade packages
|
||||
run: dnf upgrade --assumeyes
|
||||
run: dnf upgrade -y
|
||||
- name: Install dependencies
|
||||
run: >
|
||||
dnf install --assumeyes
|
||||
dnf install -y
|
||||
glibc
|
||||
gcc-c++
|
||||
git
|
||||
@@ -281,13 +277,10 @@ jobs:
|
||||
with:
|
||||
fetch-depth: 0
|
||||
- name: Add safe git directory
|
||||
shell: bash
|
||||
run: git config --global --add safe.directory ${GITHUB_WORKSPACE}
|
||||
- name: Create Build Environment
|
||||
shell: bash
|
||||
run: cmake -E make_directory build
|
||||
- name: Configure CMake
|
||||
shell: bash
|
||||
working-directory: build
|
||||
run: cmake .. -DCMAKE_BUILD_TYPE=Debug -DBUILD_WERROR=ON
|
||||
- name: Create source tarball
|
||||
@@ -317,7 +310,7 @@ jobs:
|
||||
image: debian:${{matrix.debian_version}}
|
||||
steps:
|
||||
- name: Update repositories
|
||||
run: apt update
|
||||
run: apt update -y
|
||||
- name: Install packages
|
||||
env:
|
||||
DEBIAN_FRONTEND: noninteractive
|
||||
@@ -360,39 +353,23 @@ jobs:
|
||||
if: matrix.debian_version == 'buster' || matrix.debian_version == 'bullseye'
|
||||
env:
|
||||
DEBIAN_FRONTEND: noninteractive
|
||||
run: >
|
||||
apt 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
|
||||
if: matrix.debian_version != 'buster' && matrix.debian_version != 'bullseye'
|
||||
env:
|
||||
DEBIAN_FRONTEND: noninteractive
|
||||
run: >
|
||||
apt 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
|
||||
with:
|
||||
fetch-depth: 0
|
||||
- name: Add safe git directory
|
||||
shell: bash
|
||||
run: git config --global --add safe.directory ${GITHUB_WORKSPACE}
|
||||
- name: Create Build Environment
|
||||
shell: bash
|
||||
run: cmake -E make_directory build
|
||||
- name: Configure CMake
|
||||
shell: bash
|
||||
working-directory: build
|
||||
run: cmake .. -DCMAKE_BUILD_TYPE=Debug -DBUILD_WERROR=ON
|
||||
- name: make deb
|
||||
shell: bash
|
||||
run: dpkg-buildpackage -b -d -uc -us -nc -j2
|
||||
|
||||
|
||||
@@ -402,15 +379,17 @@ jobs:
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
ubuntu_version: [ 'bionic', 'focal', 'jammy', 'kinetic' ]
|
||||
ubuntu_version: [ 'bionic', 'focal', 'jammy', 'kinetic', 'lunar' ]
|
||||
container:
|
||||
image: ubuntu:${{matrix.ubuntu_version}}
|
||||
steps:
|
||||
- name: Update repositories
|
||||
run: apt update -y
|
||||
- name: Install packages
|
||||
env:
|
||||
DEBIAN_FRONTEND: noninteractive
|
||||
run: >
|
||||
apt-get update && apt-get install -y
|
||||
apt install -y
|
||||
build-essential
|
||||
dh-make
|
||||
ssh
|
||||
@@ -451,27 +430,23 @@ jobs:
|
||||
if: matrix.ubuntu_version == 'bionic' || matrix.ubuntu_version == 'focal'
|
||||
env:
|
||||
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
|
||||
if: matrix.ubuntu_version != 'bionic' && matrix.ubuntu_version != 'focal'
|
||||
env:
|
||||
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
|
||||
with:
|
||||
fetch-depth: 0
|
||||
- name: Add safe git directory
|
||||
shell: bash
|
||||
run: git config --global --add safe.directory ${GITHUB_WORKSPACE}
|
||||
- name: Create Build Environment
|
||||
shell: bash
|
||||
run: cmake -E make_directory build
|
||||
- name: Configure CMake
|
||||
shell: bash
|
||||
working-directory: build
|
||||
run: cmake .. -DCMAKE_BUILD_TYPE=Debug -DBUILD_WERROR=ON
|
||||
- name: make deb
|
||||
shell: bash
|
||||
run: dpkg-buildpackage -b -d -uc -us -nc -j2
|
||||
|
||||
|
||||
@@ -484,11 +459,11 @@ jobs:
|
||||
fetch-depth: 0
|
||||
- name: Build FreeBSD
|
||||
id: build-freebsd
|
||||
uses: vmactions/freebsd-vm@v0.2.5
|
||||
uses: vmactions/freebsd-vm@v0.3.0
|
||||
with:
|
||||
usesh: true
|
||||
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: |
|
||||
git config --global --add safe.directory ${GITHUB_WORKSPACE}
|
||||
cmake -E make_directory build
|
||||
@@ -501,17 +476,17 @@ jobs:
|
||||
|
||||
build-macos:
|
||||
name: Build macOS
|
||||
runs-on: macos-10.15
|
||||
runs-on: macos-11
|
||||
steps:
|
||||
|
||||
- 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
|
||||
run: brew update
|
||||
|
||||
- name: Upgrade packages
|
||||
run: brew upgrade
|
||||
run: brew upgrade || true
|
||||
|
||||
- name: Install packages
|
||||
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/
|
||||
|
||||
- name: Build and install gstreamer
|
||||
env:
|
||||
HOMEBREW_NO_INSTALL_FROM_API: 1
|
||||
run: brew reinstall --build-from-source gstreamer
|
||||
|
||||
- name: Build and install gst-plugins-base
|
||||
env:
|
||||
HOMEBREW_NO_INSTALL_FROM_API: 1
|
||||
run: brew reinstall --build-from-source gst-plugins-base
|
||||
|
||||
- name: Build and install gst-plugins-good
|
||||
env:
|
||||
HOMEBREW_NO_INSTALL_FROM_API: 1
|
||||
run: brew reinstall --build-from-source gst-plugins-good
|
||||
|
||||
- name: Build and install gst-plugins-bad
|
||||
env:
|
||||
HOMEBREW_NO_INSTALL_FROM_API: 1
|
||||
run: brew reinstall --build-from-source gst-plugins-bad
|
||||
|
||||
- name: Build and install gst-plugins-ugly
|
||||
env:
|
||||
HOMEBREW_NO_INSTALL_FROM_API: 1
|
||||
run: brew reinstall --build-from-source gst-plugins-ugly
|
||||
|
||||
- name: Build and install gst-libav
|
||||
env:
|
||||
HOMEBREW_NO_INSTALL_FROM_API: 1
|
||||
run: brew reinstall --build-from-source gst-libav
|
||||
|
||||
- name: Build libgpod
|
||||
shell: bash
|
||||
env:
|
||||
PERL_MM_USE_DEFAULT: 1
|
||||
run: |
|
||||
@@ -562,21 +548,14 @@ jobs:
|
||||
make -j 4
|
||||
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
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Create Build Environment
|
||||
shell: bash
|
||||
run: cmake -E make_directory build
|
||||
|
||||
- name: Configure CMake
|
||||
shell: bash
|
||||
env:
|
||||
MACOSX_DEPLOYMENT_TARGET: 10.15
|
||||
PKG_CONFIG_PATH: /usr/local/lib/pkgconfig
|
||||
@@ -588,34 +567,33 @@ jobs:
|
||||
|
||||
- name: Build
|
||||
working-directory: build
|
||||
shell: bash
|
||||
run: cmake --build . --config Release --parallel 4
|
||||
|
||||
- name: Install
|
||||
working-directory: build
|
||||
shell: bash
|
||||
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
|
||||
working-directory: build
|
||||
shell: bash
|
||||
run: |
|
||||
mkdir -p strawberry.app/Contents/Frameworks/
|
||||
cp /usr/local/lib/libsoup-3.0.0.dylib strawberry.app/Contents/Frameworks/
|
||||
|
||||
- name: Deploy
|
||||
working-directory: build
|
||||
shell: bash
|
||||
run: make deploy
|
||||
|
||||
- name: Deploy check
|
||||
working-directory: build
|
||||
shell: bash
|
||||
run: make deploycheck
|
||||
|
||||
- name: Create DMG
|
||||
working-directory: build
|
||||
shell: bash
|
||||
run: make dmg
|
||||
|
||||
- uses: actions/upload-artifact@v3
|
||||
@@ -657,48 +635,42 @@ jobs:
|
||||
arch: [ 'i686', 'x86_64' ]
|
||||
build_type: [ 'debug', 'release' ]
|
||||
container:
|
||||
image: jonaski/strawberry-mxe-${{matrix.arch}}
|
||||
image: jonaski/strawberry-mxe-${{matrix.arch}}-${{matrix.build_type}}
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Add safe git directory
|
||||
shell: bash
|
||||
run: git config --global --add safe.directory ${GITHUB_WORKSPACE}
|
||||
|
||||
- name: Create Build Environment
|
||||
shell: bash
|
||||
run: cmake -E make_directory build
|
||||
|
||||
- name: Link MXE directory
|
||||
shell: bash
|
||||
run: ln -s /strawberry-mxe ~/mxe-shared
|
||||
|
||||
- name: Set ENABLE_WIN32_CONSOLE (debug)
|
||||
if: matrix.build_type == 'debug'
|
||||
shell: bash
|
||||
run: echo "win32_console=ON" >> $GITHUB_ENV
|
||||
|
||||
- name: Set ENABLE_WIN32_CONSOLE (release)
|
||||
if: matrix.build_type == 'release'
|
||||
shell: bash
|
||||
run: echo "win32_console=OFF" >> $GITHUB_ENV
|
||||
|
||||
- name: Run CMake
|
||||
shell: bash
|
||||
env:
|
||||
PKG_CONFIG_PATH: /strawberry-mxe/usr/${{matrix.arch}}-w64-mingw32.shared/lib/pkgconfig
|
||||
working-directory: build
|
||||
run: >
|
||||
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"
|
||||
-DBUILD_WITH_QT6=ON
|
||||
-DBUILD_WERROR=OFF
|
||||
-DARCH="${{matrix.arch}}"
|
||||
-DENABLE_WIN32_CONSOLE=${{env.build_type}}
|
||||
-DENABLE_WIN32_CONSOLE=${{env.win32_console}}
|
||||
-DENABLE_DBUS=OFF
|
||||
-DENABLE_LIBGPOD=OFF
|
||||
-DENABLE_LIBMTP=OFF
|
||||
@@ -745,6 +717,11 @@ jobs:
|
||||
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} .
|
||||
|
||||
- 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
|
||||
working-directory: build
|
||||
run: >
|
||||
@@ -780,7 +757,7 @@ jobs:
|
||||
|
||||
build-windows-msvc:
|
||||
name: Build Windows MSVC
|
||||
runs-on: windows-2019
|
||||
runs-on: windows-2022
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
@@ -791,7 +768,9 @@ jobs:
|
||||
- uses: ilammy/msvc-dev-cmd@v1
|
||||
with:
|
||||
arch: ${{matrix.arch}}
|
||||
toolset: 14.29
|
||||
sdk: 10.0.20348.0
|
||||
vsversion: 17
|
||||
toolset: 14.3
|
||||
|
||||
- uses: actions/checkout@v3
|
||||
with:
|
||||
@@ -800,7 +779,7 @@ jobs:
|
||||
- name: Delete conflicting files
|
||||
shell: bash
|
||||
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}
|
||||
|
||||
- name: Get latest MSVC dependencies
|
||||
@@ -1063,7 +1042,7 @@ jobs:
|
||||
upload-windows-msvc:
|
||||
name: Upload Windows MSVC Setup
|
||||
runs-on: ubuntu-latest
|
||||
if: github.ref == 'refs/heads/master'
|
||||
if: github.ref == 'refs/heads/master' || github.ref == 'refs/heads/msvc'
|
||||
needs:
|
||||
- build-windows-msvc
|
||||
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"
|
||||
<< "libgstflac.dylib"
|
||||
<< "libgstgio.dylib"
|
||||
<< "libgstgme.dylib"
|
||||
//<< "libgstgme.dylib"
|
||||
<< "libgsthls.dylib"
|
||||
<< "libgsticydemux.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(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")
|
||||
|
||||
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>
|
||||
#endif
|
||||
|
||||
#include "singleapplication.h"
|
||||
#include "singleapplication_t.h"
|
||||
#include "singleapplication_p.h"
|
||||
|
||||
SingleApplicationPrivate::SingleApplicationPrivate(SingleApplication *ptr)
|
||||
SingleApplicationPrivateClass::SingleApplicationPrivateClass(SingleApplicationClass *ptr)
|
||||
: q_ptr(ptr),
|
||||
memory_(nullptr),
|
||||
socket_(nullptr),
|
||||
server_(nullptr),
|
||||
instanceNumber_(-1) {}
|
||||
|
||||
SingleApplicationPrivate::~SingleApplicationPrivate() {
|
||||
SingleApplicationPrivateClass::~SingleApplicationPrivateClass() {
|
||||
|
||||
if (socket_ != nullptr) {
|
||||
if (socket_ != nullptr && socket_->isOpen()) {
|
||||
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_;
|
||||
InstancesInfo *instance = static_cast<InstancesInfo*>(memory_->data());
|
||||
instance->primary = false;
|
||||
instance->primaryPid = -1;
|
||||
instance->primaryUser[0] = '\0';
|
||||
instance->checksum = blockChecksum();
|
||||
}
|
||||
memory_->unlock();
|
||||
|
||||
delete memory_;
|
||||
memory_ = nullptr;
|
||||
if (memory_->isAttached()) {
|
||||
memory_->detach();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
QString SingleApplicationPrivate::getUsername() {
|
||||
QString SingleApplicationPrivateClass::getUsername() {
|
||||
|
||||
#ifdef Q_OS_UNIX
|
||||
QString username;
|
||||
@@ -141,36 +138,36 @@ QString SingleApplicationPrivate::getUsername() {
|
||||
|
||||
}
|
||||
|
||||
void SingleApplicationPrivate::genBlockServerName() {
|
||||
void SingleApplicationPrivateClass::genBlockServerName() {
|
||||
|
||||
QCryptographicHash appData(QCryptographicHash::Sha256);
|
||||
appData.addData("SingleApplication");
|
||||
appData.addData(SingleApplication::app_t::applicationName().toUtf8());
|
||||
appData.addData(SingleApplication::app_t::organizationName().toUtf8());
|
||||
appData.addData(SingleApplication::app_t::organizationDomain().toUtf8());
|
||||
appData.addData(SingleApplicationClass::applicationName().toUtf8());
|
||||
appData.addData(SingleApplicationClass::organizationName().toUtf8());
|
||||
appData.addData(SingleApplicationClass::organizationDomain().toUtf8());
|
||||
|
||||
if (!(options_ & SingleApplication::Mode::ExcludeAppVersion)) {
|
||||
appData.addData(SingleApplication::app_t::applicationVersion().toUtf8());
|
||||
if (!(options_ & SingleApplicationClass::Mode::ExcludeAppVersion)) {
|
||||
appData.addData(SingleApplicationClass::applicationVersion().toUtf8());
|
||||
}
|
||||
|
||||
if (!(options_ & SingleApplication::Mode::ExcludeAppPath)) {
|
||||
if (!(options_ & SingleApplicationClass::Mode::ExcludeAppPath)) {
|
||||
#if defined(Q_OS_UNIX)
|
||||
const QByteArray appImagePath = qgetenv("APPIMAGE");
|
||||
if (appImagePath.isEmpty()) {
|
||||
appData.addData(SingleApplication::app_t::applicationFilePath().toUtf8());
|
||||
appData.addData(SingleApplicationClass::applicationFilePath().toUtf8());
|
||||
}
|
||||
else {
|
||||
appData.addData(appImagePath);
|
||||
};
|
||||
}
|
||||
#elif defined(Q_OS_WIN)
|
||||
appData.addData(SingleApplication::app_t::applicationFilePath().toLower().toUtf8());
|
||||
appData.addData(SingleApplicationClass::applicationFilePath().toLower().toUtf8());
|
||||
#else
|
||||
appData.addData(SingleApplication::app_t::applicationFilePath().toUtf8());
|
||||
appData.addData(SingleApplicationClass::applicationFilePath().toUtf8());
|
||||
#endif
|
||||
}
|
||||
|
||||
// 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());
|
||||
}
|
||||
|
||||
@@ -179,7 +176,7 @@ void SingleApplicationPrivate::genBlockServerName() {
|
||||
|
||||
}
|
||||
|
||||
void SingleApplicationPrivate::initializeMemoryBlock() const {
|
||||
void SingleApplicationPrivateClass::initializeMemoryBlock() const {
|
||||
|
||||
InstancesInfo *instance = static_cast<InstancesInfo*>(memory_->data());
|
||||
instance->primary = false;
|
||||
@@ -190,7 +187,7 @@ void SingleApplicationPrivate::initializeMemoryBlock() const {
|
||||
|
||||
}
|
||||
|
||||
void SingleApplicationPrivate::startPrimary() {
|
||||
void SingleApplicationPrivateClass::startPrimary() {
|
||||
|
||||
// Reset the number of connections
|
||||
InstancesInfo *instance = static_cast<InstancesInfo*>(memory_->data());
|
||||
@@ -203,10 +200,10 @@ void SingleApplicationPrivate::startPrimary() {
|
||||
// Successful creation means that no main process exists
|
||||
// So we start a QLocalServer to listen for connections
|
||||
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
|
||||
if (options_ & SingleApplication::Mode::User) {
|
||||
if (options_ & SingleApplicationClass::Mode::User) {
|
||||
server_->setSocketOptions(QLocalServer::UserAccessOption);
|
||||
}
|
||||
else {
|
||||
@@ -214,11 +211,11 @@ void SingleApplicationPrivate::startPrimary() {
|
||||
}
|
||||
|
||||
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());
|
||||
|
||||
@@ -228,18 +225,18 @@ void SingleApplicationPrivate::startSecondary() {
|
||||
|
||||
}
|
||||
|
||||
bool SingleApplicationPrivate::connectToPrimary(const int timeout, const ConnectionType connectionType) {
|
||||
|
||||
QElapsedTimer time;
|
||||
time.start();
|
||||
bool SingleApplicationPrivateClass::connectToPrimary(const int timeout, const ConnectionType connectionType) {
|
||||
|
||||
// Connect to the Local Server of the Primary Instance if not already connected.
|
||||
if (socket_ == nullptr) {
|
||||
socket_ = new QLocalSocket();
|
||||
socket_ = new QLocalSocket(this);
|
||||
}
|
||||
|
||||
if (socket_->state() == QLocalSocket::ConnectedState) return true;
|
||||
|
||||
QElapsedTimer time;
|
||||
time.start();
|
||||
|
||||
if (socket_->state() != QLocalSocket::ConnectedState) {
|
||||
|
||||
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;
|
||||
QDataStream writeStream(&initMsg, QIODevice::WriteOnly);
|
||||
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');
|
||||
}
|
||||
|
||||
bool SingleApplicationPrivate::writeConfirmedMessage(const int timeout, const QByteArray &msg) const {
|
||||
bool SingleApplicationPrivateClass::writeConfirmedMessage(const int timeout, const QByteArray &msg) const {
|
||||
|
||||
QElapsedTimer time;
|
||||
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_->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)
|
||||
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();
|
||||
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();
|
||||
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
|
||||
*/
|
||||
void SingleApplicationPrivate::slotConnectionEstablished() {
|
||||
void SingleApplicationPrivateClass::slotConnectionEstablished() {
|
||||
|
||||
QLocalSocket *nextConnSocket = server_->nextPendingConnection();
|
||||
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];
|
||||
slotClientConnectionClosed(nextConnSocket, info.instanceId);
|
||||
});
|
||||
|
||||
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);
|
||||
});
|
||||
|
||||
QObject::connect(nextConnSocket, &QLocalSocket::readyRead, this, [nextConnSocket, this]() {
|
||||
QObject::connect(nextConnSocket, &QLocalSocket::readyRead, this, [this, nextConnSocket]() {
|
||||
const ConnectionInfo &info = connectionMap_[nextConnSocket];
|
||||
switch (info.stage) {
|
||||
case StageInitHeader:
|
||||
@@ -387,16 +384,16 @@ void SingleApplicationPrivate::slotConnectionEstablished() {
|
||||
readMessageHeader(nextConnSocket, StageConnectedBody);
|
||||
break;
|
||||
case StageConnectedBody:
|
||||
this->slotDataAvailable(nextConnSocket, info.instanceId);
|
||||
slotDataAvailable(nextConnSocket, info.instanceId);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
};
|
||||
}
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
void SingleApplicationPrivate::readMessageHeader(QLocalSocket *sock, const SingleApplicationPrivate::ConnectionStage nextStage) {
|
||||
void SingleApplicationPrivateClass::readMessageHeader(QLocalSocket *sock, const SingleApplicationPrivateClass::ConnectionStage nextStage) {
|
||||
|
||||
if (!connectionMap_.contains(sock)) {
|
||||
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)) {
|
||||
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)) {
|
||||
return;
|
||||
@@ -449,10 +446,9 @@ void SingleApplicationPrivate::readInitMessageBody(QLocalSocket *sock) {
|
||||
readStream >> latin1Name;
|
||||
|
||||
// connection type
|
||||
ConnectionType connectionType = InvalidConnection;
|
||||
quint8 connTypeVal = InvalidConnection;
|
||||
readStream >> connTypeVal;
|
||||
connectionType = static_cast<ConnectionType>(connTypeVal);
|
||||
const ConnectionType connectionType = static_cast<ConnectionType>(connTypeVal);
|
||||
|
||||
// instance id
|
||||
quint32 instanceId = 0;
|
||||
@@ -479,7 +475,7 @@ void SingleApplicationPrivate::readInitMessageBody(QLocalSocket *sock) {
|
||||
info.instanceId = instanceId;
|
||||
info.stage = StageConnectedHeader;
|
||||
|
||||
if (connectionType == NewInstance || (connectionType == SecondaryInstance && options_ & SingleApplication::Mode::SecondaryNotification)) {
|
||||
if (connectionType == NewInstance || (connectionType == SecondaryInstance && options_ & SingleApplicationClass::Mode::SecondaryNotification)) {
|
||||
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)) {
|
||||
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) {
|
||||
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)
|
||||
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 <QHash>
|
||||
|
||||
#include "singleapplication.h"
|
||||
#include "singleapplication_t.h"
|
||||
|
||||
class QLocalServer;
|
||||
class QLocalSocket;
|
||||
class QSharedMemory;
|
||||
|
||||
struct InstancesInfo {
|
||||
bool primary;
|
||||
quint32 secondary;
|
||||
qint64 primaryPid;
|
||||
char primaryUser[128];
|
||||
quint16 checksum;
|
||||
};
|
||||
|
||||
struct ConnectionInfo {
|
||||
explicit ConnectionInfo() : msgLen(0), instanceId(0), stage(0) {}
|
||||
quint64 msgLen;
|
||||
quint32 instanceId;
|
||||
quint8 stage;
|
||||
};
|
||||
|
||||
class SingleApplicationPrivate : public QObject {
|
||||
class SingleApplicationPrivateClass : public QObject {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit SingleApplicationPrivateClass(SingleApplicationClass *ptr);
|
||||
~SingleApplicationPrivateClass() override;
|
||||
|
||||
enum ConnectionType : quint8 {
|
||||
InvalidConnection = 0,
|
||||
NewInstance = 1,
|
||||
@@ -74,12 +62,25 @@ class SingleApplicationPrivate : public QObject {
|
||||
StageInitHeader = 0,
|
||||
StageInitBody = 1,
|
||||
StageConnectedHeader = 2,
|
||||
StageConnectedBody = 3,
|
||||
StageConnectedBody = 3
|
||||
};
|
||||
Q_DECLARE_PUBLIC(SingleApplication)
|
||||
Q_DECLARE_PUBLIC(SingleApplicationClass)
|
||||
|
||||
explicit SingleApplicationPrivate(SingleApplication *ptr);
|
||||
~SingleApplicationPrivate() override;
|
||||
struct InstancesInfo {
|
||||
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();
|
||||
void genBlockServerName();
|
||||
@@ -98,13 +99,13 @@ class SingleApplicationPrivate : public QObject {
|
||||
bool writeConfirmedMessage(const int timeout, const QByteArray &msg) const;
|
||||
static void randomSleep();
|
||||
|
||||
SingleApplication *q_ptr;
|
||||
SingleApplicationClass *q_ptr;
|
||||
QSharedMemory *memory_;
|
||||
QLocalSocket *socket_;
|
||||
QLocalServer *server_;
|
||||
quint32 instanceNumber_;
|
||||
QString blockServerName_;
|
||||
SingleApplication::Options options_;
|
||||
SingleApplicationClass::Options options_;
|
||||
QHash<QLocalSocket*, ConnectionInfo> connectionMap_;
|
||||
|
||||
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
|
||||
#define SINGLEAPPLICATION_H
|
||||
#ifndef SINGLEAPPLICATION_T_H
|
||||
#define SINGLEAPPLICATION_T_H
|
||||
|
||||
#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 <QByteArray>
|
||||
|
||||
class SingleApplicationPrivate;
|
||||
class SingleApplicationPrivateClass;
|
||||
|
||||
/**
|
||||
* @brief The SingleApplication class handles multiple instances of the same Application
|
||||
* @see QApplication
|
||||
*/
|
||||
class SingleApplication : public QApplication { // clazy:exclude=ctor-missing-parent-argument
|
||||
class SingleApplicationClass : public ApplicationClass { // clazy:exclude=ctor-missing-parent-argument
|
||||
Q_OBJECT
|
||||
|
||||
using app_t = QApplication;
|
||||
|
||||
public:
|
||||
/**
|
||||
* @brief Mode of operation of SingleApplication.
|
||||
@@ -61,7 +77,7 @@ class SingleApplication : public QApplication { // clazy:exclude=ctor-missing-p
|
||||
* block will be user wide.
|
||||
* @enum
|
||||
*/
|
||||
enum Mode {
|
||||
enum class Mode {
|
||||
User = 1 << 0,
|
||||
System = 1 << 1,
|
||||
SecondaryNotification = 1 << 2,
|
||||
@@ -86,11 +102,11 @@ class SingleApplication : public QApplication { // clazy:exclude=ctor-missing-p
|
||||
* instance and the secondary instance.
|
||||
* @note The timeout is just a hint for the maximum time of blocking
|
||||
* operations. It does not guarantee that the SingleApplication
|
||||
* initialisation will be completed in given time, though is a good hint.
|
||||
* initialization will be completed in given time, though is a good hint.
|
||||
* Usually 4*timeout would be the worst case (fail) scenario.
|
||||
*/
|
||||
explicit SingleApplication(int &argc, char *argv[], const bool allowSecondary = false, const Options options = Mode::User, const int timeout = 1000);
|
||||
~SingleApplication() override;
|
||||
explicit SingleApplicationClass(int &argc, char *argv[], const bool allowSecondary = false, const Options options = Mode::User, const int timeout = 1000);
|
||||
~SingleApplicationClass() override;
|
||||
|
||||
/**
|
||||
* @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);
|
||||
|
||||
private:
|
||||
SingleApplicationPrivate *d_ptr;
|
||||
SingleApplicationPrivateClass *d_ptr;
|
||||
#if defined(SINGLEAPPLICATION)
|
||||
Q_DECLARE_PRIVATE(SingleApplication)
|
||||
#elif defined(SINGLECOREAPPLICATION)
|
||||
Q_DECLARE_PRIVATE(SingleCoreApplication)
|
||||
#endif
|
||||
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)
|
||||
|
||||
cmake_policy(SET CMP0054 NEW)
|
||||
if (CMAKE_VERSION VERSION_GREATER_EQUAL 3.12)
|
||||
if(POLICY CMP0054)
|
||||
cmake_policy(SET CMP0054 NEW)
|
||||
endif()
|
||||
if(POLICY CMP0074)
|
||||
cmake_policy(SET CMP0074 NEW)
|
||||
endif()
|
||||
|
||||
@@ -309,7 +311,10 @@ endif()
|
||||
|
||||
# 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(SINGLECOREAPPLICATION_LIBRARIES singlecoreapplication)
|
||||
|
||||
@@ -376,12 +381,12 @@ optional_component(VLC ON "Engine: VLC backend"
|
||||
|
||||
optional_component(SONGFINGERPRINTING ON "Song fingerprinting and tracking"
|
||||
DEPENDS "chromaprint" CHROMAPRINT_FOUND
|
||||
DEPENDS "gstreamer" GSTREAMER_FOUND
|
||||
DEPENDS "gstreamer" HAVE_GSTREAMER
|
||||
)
|
||||
|
||||
optional_component(MUSICBRAINZ ON "MusicBrainz integration"
|
||||
DEPENDS "chromaprint" CHROMAPRINT_FOUND
|
||||
DEPENDS "gstreamer" GSTREAMER_FOUND
|
||||
DEPENDS "gstreamer" HAVE_GSTREAMER
|
||||
)
|
||||
|
||||
if(X11_FOUND OR HAVE_DBUS OR APPLE OR WIN32)
|
||||
@@ -406,7 +411,7 @@ endif()
|
||||
|
||||
optional_component(AUDIOCD ON "Devices: Audio CD support"
|
||||
DEPENDS "libcdio" LIBCDIO_FOUND
|
||||
DEPENDS "gstreamer" GSTREAMER_FOUND
|
||||
DEPENDS "gstreamer" HAVE_GSTREAMER
|
||||
)
|
||||
|
||||
optional_component(UDISKS2 ON "Devices: UDisks2 backend"
|
||||
|
||||
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
|
||||
|
||||
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):
|
||||
|
||||
Bugfixes:
|
||||
@@ -250,7 +347,7 @@ Version 0.9.3 (2021.04.18)
|
||||
Bugfixes:
|
||||
* Fix "Show in file browser" to work with thunar.
|
||||
* 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.
|
||||
* (macOS) Fix intermittent abort on startup.
|
||||
* (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.
|
||||
* 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.
|
||||
* Icreased default collection pixmap disk cache to 360.
|
||||
* Increased default collection pixmap disk cache to 360.
|
||||
|
||||
New features:
|
||||
* Added back Tidal streaming support.
|
||||
@@ -655,7 +752,7 @@ Version 0.6.7 (2019.11.27)
|
||||
* Fixed "Pressing Previous in player" behaviour setting
|
||||
* 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
|
||||
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 scrobbler to also scrobble songs without album title
|
||||
* 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)
|
||||
* Fixed compliation with clang compiler
|
||||
* Fixed compilation with clang compiler
|
||||
* 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
|
||||
* Fetch tags from MusicBrainz
|
||||
* Album cover art from [Last.fm](https://www.last.fm/), [Musicbrainz](https://musicbrainz.org/), [Discogs](https://www.discogs.com/), [Musixmatch](https://www.musixmatch.com/), [Deezer](https://www.deezer.com/), [Tidal](https://www.tidal.com/), [Qobuz](https://www.qobuz.com/) and [Spotify](https://www.spotify.com/)
|
||||
* Song lyrics from [AudD](https://audd.io/), [Genius](https://genius.com/), [Musixmatch](https://www.musixmatch.com/), [ChartLyrics](http://www.chartlyrics.com/), [lyrics.ovh](https://lyrics.ovh/) and [lololyrics.com](https://www.lololyrics.com/)
|
||||
* Song lyrics from [Lyrics.com](https://www.lyrics.com/), [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
|
||||
* Audio analyzer
|
||||
* 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:
|
||||
|
||||
* [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/)
|
||||
* [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)
|
||||
* [Protobuf](https://developers.google.com/protocol-buffers/)
|
||||
* [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:
|
||||
|
||||
cd strawberry
|
||||
mkdir build && cd build
|
||||
mkdir build
|
||||
cd build
|
||||
cmake .. -DBUILD_WITH_QT6=ON
|
||||
make -j$(nproc)
|
||||
make -j $(nproc)
|
||||
sudo make install
|
||||
|
||||
Strawberry is backwards compatible with Qt 5, to compile with Qt 5 use:
|
||||
|
||||
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
|
||||
|
||||
[](https://repology.org/metapackage/strawberry/versions)
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
set(STRAWBERRY_VERSION_MAJOR 1)
|
||||
set(STRAWBERRY_VERSION_MINOR 0)
|
||||
set(STRAWBERRY_VERSION_PATCH 10)
|
||||
set(STRAWBERRY_VERSION_PATCH 17)
|
||||
#set(STRAWBERRY_VERSION_PRERELEASE rc1)
|
||||
|
||||
set(INCLUDE_GIT_REVISION OFF)
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
<file>schema/schema-13.sql</file>
|
||||
<file>schema/schema-14.sql</file>
|
||||
<file>schema/schema-15.sql</file>
|
||||
<file>schema/schema-16.sql</file>
|
||||
<file>schema/device-schema.sql</file>
|
||||
<file>style/strawberry.css</file>
|
||||
<file>style/smartplaylistsearchterm.css</file>
|
||||
|
||||
@@ -67,7 +67,21 @@ CREATE TABLE device_%deviceid_songs (
|
||||
|
||||
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"
|
||||
);
|
||||
|
||||
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;
|
||||
|
||||
INSERT INTO schema_version (version) VALUES (15);
|
||||
INSERT INTO schema_version (version) VALUES (16);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS directories (
|
||||
path TEXT NOT NULL,
|
||||
@@ -75,7 +75,21 @@ CREATE TABLE IF NOT EXISTS songs (
|
||||
|
||||
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,
|
||||
|
||||
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,
|
||||
|
||||
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,
|
||||
|
||||
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,
|
||||
|
||||
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,
|
||||
|
||||
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,
|
||||
|
||||
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,
|
||||
|
||||
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,
|
||||
|
||||
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
|
||||
- Automatically retrieve tags from MusicBrainz
|
||||
- Album cover art from Last.fm, Musicbrainz, Discogs, Musixmatch, Deezer, Tidal, Qobuz and Spotify
|
||||
- Song lyrics from AudD, Genius, Musixmatch, ChartLyrics, lyrics.ovh and lololyrics.com
|
||||
- Song lyrics from Lyrics.com, AudD, Genius, Musixmatch, ChartLyrics, lyrics.ovh and lololyrics.com
|
||||
- Audio analyzer
|
||||
- Audio equalizer
|
||||
- Transfer music to mass-storage USB players, MTP compatible devices and iPod Nano/Classic
|
||||
|
||||
20
debian/copyright
vendored
20
debian/copyright
vendored
@@ -5,10 +5,10 @@ Source: https://github.com/strawberrymusicplayer/strawberry
|
||||
|
||||
Files: *
|
||||
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+
|
||||
|
||||
Files: src/core/timeconstants.h
|
||||
Files: src/utilities/timeconstants.h
|
||||
ext/libstrawberry-common/core/logging.cpp
|
||||
ext/libstrawberry-common/core/logging.h
|
||||
ext/libstrawberry-common/core/messagehandler.cpp
|
||||
@@ -98,7 +98,7 @@ Files: src/core/main.h
|
||||
ext/macdeploycheck/*
|
||||
src/widgets/resizabletextedit.cpp
|
||||
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+
|
||||
|
||||
Files: src/engine/enginebase.cpp
|
||||
@@ -130,11 +130,6 @@ Copyright: 2012, David Sansome <me@davidsansome.com>
|
||||
2018-2021, Jonas Kvinge <jonas@jkvinge.net>
|
||||
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
|
||||
src/covermanager/discogscoverprovider.h
|
||||
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>
|
||||
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
|
||||
Copyright: 2005, Gábor Lehel
|
||||
Copyright: 2018-2023, Jonas Kvinge <jonas@jkvinge.net>
|
||||
2005, Gábor Lehel
|
||||
2003, Mark Kretschmann <markey@web.de>
|
||||
License: GPL-2+
|
||||
|
||||
|
||||
@@ -29,7 +29,7 @@
|
||||
<li>Edit tags on audio files</li>
|
||||
<li>Automatically retrieve tags from MusicBrainz</li>
|
||||
<li>Album cover art from Last.fm, Musicbrainz, Discogs, Musixmatch, Deezer, Tidal, Qobuz and Spotify</li>
|
||||
<li>Song lyrics from AudD, Genius, Musixmatch, ChartLyrics, lyrics.ovh and lololyrics.com</li>
|
||||
<li>Song lyrics from Lyrics.com, AudD, Genius, Musixmatch, ChartLyrics, lyrics.ovh and lololyrics.com</li>
|
||||
<li>Support for multiple backends</li>
|
||||
<li>Audio analyzer and equalizer</li>
|
||||
<li>Transfer music to mass-storage USB players, MTP compatible devices and iPod Nano/Classic</li>
|
||||
|
||||
@@ -9,6 +9,7 @@ TryExec=strawberry
|
||||
Icon=strawberry
|
||||
Terminal=false
|
||||
Categories=AudioVideo;Player;Qt;Audio;
|
||||
Keywords=Audio;Player;
|
||||
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;
|
||||
StartupWMClass=strawberry
|
||||
|
||||
2
dist/unix/strawberry.1
vendored
2
dist/unix/strawberry.1
vendored
@@ -29,7 +29,7 @@ Features:
|
||||
.br
|
||||
- Album cover art from Last.fm, Musicbrainz, Discogs, Musixmatch, Deezer, Tidal, Qobuz and Spotify
|
||||
.br
|
||||
- Song lyrics from AudD, Genius, Musixmatch, ChartLyrics, lyrics.ovh and lololyrics.com
|
||||
- Song lyrics from Lyrics.com, AudD, Genius, Musixmatch, ChartLyrics, lyrics.ovh and lololyrics.com
|
||||
.br
|
||||
- Support for multiple backends
|
||||
.br
|
||||
|
||||
2
dist/unix/strawberry.spec.in
vendored
2
dist/unix/strawberry.spec.in
vendored
@@ -119,7 +119,7 @@ Features:
|
||||
- Edit tags on audio files
|
||||
- Automatically retrieve tags from MusicBrainz
|
||||
- Album cover art from Last.fm, Musicbrainz, Discogs, Musixmatch, Deezer, Tidal, Qobuz and Spotify
|
||||
- Song lyrics from AudD, Genius, Musixmatch, ChartLyrics, lyrics.ovh and lololyrics.com
|
||||
- Song lyrics from Lyrics.com, AudD, Genius, Musixmatch, ChartLyrics, lyrics.ovh and lololyrics.com
|
||||
- Support for multiple backends
|
||||
- Audio analyzer
|
||||
- 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"
|
||||
!define mingw
|
||||
!undef compiler
|
||||
!define compiler "mingw"
|
||||
!endif
|
||||
|
||||
!if "@MSVC@" == "1"
|
||||
!define msvc
|
||||
!undef compiler
|
||||
!define compiler "msvc"
|
||||
!endif
|
||||
|
||||
|
||||
!if "@ARCH@" == "x86"
|
||||
!define arch_x86
|
||||
!else if "@ARCH@" == "i686"
|
||||
!define arch_x86
|
||||
!else if "@ARCH@" == "i686-w64-mingw32.shared"
|
||||
!define arch_x86
|
||||
!else if "@ARCH@" == "x64"
|
||||
!define arch_x64
|
||||
!else if "@ARCH@" == "x86_64"
|
||||
!define arch_x64
|
||||
!else if "@ARCH@" == "x86_64-w64-mingw32.shared"
|
||||
!define arch_x64
|
||||
!endif
|
||||
|
||||
!ifdef arch_x86
|
||||
!define arch "x86"
|
||||
!endif
|
||||
|
||||
!ifdef arch_x64
|
||||
!define arch "x64"
|
||||
!endif
|
||||
|
||||
|
||||
!if "@CMAKE_BUILD_TYPE@" == "Release"
|
||||
!define release
|
||||
!endif
|
||||
|
||||
!if "@CMAKE_BUILD_TYPE@" == "RelWithDebInfo"
|
||||
!else if "@CMAKE_BUILD_TYPE@" == "RelWithDebInfo"
|
||||
!define release
|
||||
!else if "@CMAKE_BUILD_TYPE@" == "Debug"
|
||||
!define debug
|
||||
!endif
|
||||
|
||||
!if "@CMAKE_BUILD_TYPE@" == "Debug"
|
||||
!define debug
|
||||
!undef build_type
|
||||
!ifdef release
|
||||
!define build_type ""
|
||||
!endif
|
||||
|
||||
!ifdef debug
|
||||
!define build_type "-Debug"
|
||||
!endif
|
||||
|
||||
|
||||
!ifndef compiler
|
||||
!error "Missing compiler."
|
||||
!endif
|
||||
|
||||
!ifndef build_type
|
||||
!error "Missing build type."
|
||||
!endif
|
||||
|
||||
!ifndef arch
|
||||
!error "Missing arch."
|
||||
!endif
|
||||
|
||||
|
||||
|
||||
!ifdef debug
|
||||
!define PRODUCT_NAME "Strawberry Music Player Debug"
|
||||
!define PRODUCT_NAME_SHORT "Strawberry"
|
||||
@@ -241,10 +252,10 @@ Section "Strawberry" Strawberry
|
||||
File "libssl-3-x64.dll"
|
||||
!endif
|
||||
|
||||
File "avcodec-59.dll"
|
||||
File "avfilter-8.dll"
|
||||
File "avformat-59.dll"
|
||||
File "avutil-57.dll"
|
||||
File "avcodec-60.dll"
|
||||
File "avfilter-9.dll"
|
||||
File "avformat-60.dll"
|
||||
File "avutil-58.dll"
|
||||
File "libFLAC-12.dll"
|
||||
File "libbrotlicommon.dll"
|
||||
File "libbrotlidec.dll"
|
||||
@@ -301,7 +312,6 @@ Section "Strawberry" Strawberry
|
||||
File "libopus-0.dll"
|
||||
File "liborc-0.4-0.dll"
|
||||
File "libpng16-16.dll"
|
||||
File "libprotobuf-32.dll"
|
||||
File "libpsl-5.dll"
|
||||
File "libqtsparkle-qt6.dll"
|
||||
File "libsoup-3.0-0.dll"
|
||||
@@ -320,11 +330,51 @@ Section "Strawberry" Strawberry
|
||||
File "libwinpthread-1.dll"
|
||||
File "libxml2-2.dll"
|
||||
File "libzstd.dll"
|
||||
File "postproc-56.dll"
|
||||
File "postproc-57.dll"
|
||||
File "swresample-4.dll"
|
||||
File "swscale-6.dll"
|
||||
File "swscale-7.dll"
|
||||
File "zlib1.dll"
|
||||
|
||||
File "libabsl_base.dll"
|
||||
File "libabsl_city.dll"
|
||||
File "libabsl_cord.dll"
|
||||
File "libabsl_cord_internal.dll"
|
||||
File "libabsl_cordz_handle.dll"
|
||||
File "libabsl_cordz_info.dll"
|
||||
File "libabsl_crc32c.dll"
|
||||
File "libabsl_crc_cord_state.dll"
|
||||
File "libabsl_crc_internal.dll"
|
||||
File "libabsl_die_if_null.dll"
|
||||
File "libabsl_examine_stack.dll"
|
||||
File "libabsl_hash.dll"
|
||||
File "libabsl_int128.dll"
|
||||
File "libabsl_log_globals.dll"
|
||||
File "libabsl_log_internal_check_op.dll"
|
||||
File "libabsl_log_internal_format.dll"
|
||||
File "libabsl_log_internal_globals.dll"
|
||||
File "libabsl_log_internal_log_sink_set.dll"
|
||||
File "libabsl_log_internal_message.dll"
|
||||
File "libabsl_log_internal_nullguard.dll"
|
||||
File "libabsl_log_internal_proto.dll"
|
||||
File "libabsl_log_sink.dll"
|
||||
File "libabsl_low_level_hash.dll"
|
||||
File "libabsl_malloc_internal.dll"
|
||||
File "libabsl_raw_hash_set.dll"
|
||||
File "libabsl_raw_logging_internal.dll"
|
||||
File "libabsl_spinlock_wait.dll"
|
||||
File "libabsl_stacktrace.dll"
|
||||
File "libabsl_status.dll"
|
||||
File "libabsl_statusor.dll"
|
||||
File "libabsl_strerror.dll"
|
||||
File "libabsl_str_format_internal.dll"
|
||||
File "libabsl_strings.dll"
|
||||
File "libabsl_strings_internal.dll"
|
||||
File "libabsl_symbolize.dll"
|
||||
File "libabsl_synchronization.dll"
|
||||
File "libabsl_throw_delegate.dll"
|
||||
File "libabsl_time.dll"
|
||||
File "libabsl_time_zone.dll"
|
||||
|
||||
!ifdef debug
|
||||
File "gdb.exe"
|
||||
File "libexpat-1.dll"
|
||||
@@ -334,6 +384,7 @@ Section "Strawberry" Strawberry
|
||||
File "libpcre2-16d.dll"
|
||||
File "libreadline8.dll"
|
||||
File "libtermcap.dll"
|
||||
File "libabsl_graphcycles_internal.dll"
|
||||
!else
|
||||
File "libpcre2-8.dll"
|
||||
File "libpcre2-16.dll"
|
||||
@@ -393,7 +444,6 @@ Section "Strawberry" Strawberry
|
||||
File "jpeg62.dll"
|
||||
File "libbs2b.dll"
|
||||
File "libfaac_dll.dll"
|
||||
File "libiconv.dll"
|
||||
File "liblzma.dll"
|
||||
File "libmp3lame.dll"
|
||||
File "libopenmpt.dll"
|
||||
@@ -415,11 +465,12 @@ Section "Strawberry" Strawberry
|
||||
File "vorbis.dll"
|
||||
File "vorbisfile.dll"
|
||||
File "wavpackdll.dll"
|
||||
File "abseil_dll.dll"
|
||||
|
||||
!ifdef release
|
||||
File "freetype.dll"
|
||||
File "libiconv.dll"
|
||||
File "libpng16.dll"
|
||||
File "libprotobuf.dll"
|
||||
File "libxml2.dll"
|
||||
File "pcre2-8.dll"
|
||||
File "pcre2-16.dll"
|
||||
@@ -428,8 +479,8 @@ Section "Strawberry" Strawberry
|
||||
!endif
|
||||
!ifdef debug
|
||||
File "freetyped.dll"
|
||||
File "libiconvd.dll"
|
||||
File "libpng16d.dll"
|
||||
File "libprotobufd.dll"
|
||||
File "libxml2d.dll"
|
||||
File "pcre2-8d.dll"
|
||||
File "pcre2-16d.dll"
|
||||
@@ -443,6 +494,11 @@ Section "Strawberry" Strawberry
|
||||
|
||||
File "icudt72.dll"
|
||||
File "libfftw3-3.dll"
|
||||
!ifdef debug
|
||||
File "libprotobufd.dll"
|
||||
!else
|
||||
File "libprotobuf.dll"
|
||||
!endif
|
||||
!ifdef msvc && debug
|
||||
File "icuin72d.dll"
|
||||
File "icuuc72d.dll"
|
||||
@@ -743,10 +799,10 @@ Section "Uninstall"
|
||||
Delete "$INSTDIR\libssl-3-x64.dll"
|
||||
!endif
|
||||
|
||||
Delete "$INSTDIR\avcodec-59.dll"
|
||||
Delete "$INSTDIR\avfilter-8.dll"
|
||||
Delete "$INSTDIR\avformat-59.dll"
|
||||
Delete "$INSTDIR\avutil-57.dll"
|
||||
Delete "$INSTDIR\avcodec-60.dll"
|
||||
Delete "$INSTDIR\avfilter-9.dll"
|
||||
Delete "$INSTDIR\avformat-60.dll"
|
||||
Delete "$INSTDIR\avutil-58.dll"
|
||||
Delete "$INSTDIR\libFLAC-12.dll"
|
||||
Delete "$INSTDIR\libbrotlicommon.dll"
|
||||
Delete "$INSTDIR\libbrotlidec.dll"
|
||||
@@ -803,7 +859,6 @@ Section "Uninstall"
|
||||
Delete "$INSTDIR\libopus-0.dll"
|
||||
Delete "$INSTDIR\liborc-0.4-0.dll"
|
||||
Delete "$INSTDIR\libpng16-16.dll"
|
||||
Delete "$INSTDIR\libprotobuf-32.dll"
|
||||
Delete "$INSTDIR\libpsl-5.dll"
|
||||
Delete "$INSTDIR\libqtsparkle-qt6.dll"
|
||||
Delete "$INSTDIR\libsoup-3.0-0.dll"
|
||||
@@ -822,11 +877,51 @@ Section "Uninstall"
|
||||
Delete "$INSTDIR\libwinpthread-1.dll"
|
||||
Delete "$INSTDIR\libxml2-2.dll"
|
||||
Delete "$INSTDIR\libzstd.dll"
|
||||
Delete "$INSTDIR\postproc-56.dll"
|
||||
Delete "$INSTDIR\postproc-57.dll"
|
||||
Delete "$INSTDIR\swresample-4.dll"
|
||||
Delete "$INSTDIR\swscale-6.dll"
|
||||
Delete "$INSTDIR\swscale-7.dll"
|
||||
Delete "$INSTDIR\zlib1.dll"
|
||||
|
||||
Delete "$INSTDIR\libabsl_base.dll"
|
||||
Delete "$INSTDIR\libabsl_city.dll"
|
||||
Delete "$INSTDIR\libabsl_cord.dll"
|
||||
Delete "$INSTDIR\libabsl_cord_internal.dll"
|
||||
Delete "$INSTDIR\libabsl_cordz_handle.dll"
|
||||
Delete "$INSTDIR\libabsl_cordz_info.dll"
|
||||
Delete "$INSTDIR\libabsl_crc32c.dll"
|
||||
Delete "$INSTDIR\libabsl_crc_cord_state.dll"
|
||||
Delete "$INSTDIR\libabsl_crc_internal.dll"
|
||||
Delete "$INSTDIR\libabsl_die_if_null.dll"
|
||||
Delete "$INSTDIR\libabsl_examine_stack.dll"
|
||||
Delete "$INSTDIR\libabsl_hash.dll"
|
||||
Delete "$INSTDIR\libabsl_int128.dll"
|
||||
Delete "$INSTDIR\libabsl_log_globals.dll"
|
||||
Delete "$INSTDIR\libabsl_log_internal_check_op.dll"
|
||||
Delete "$INSTDIR\libabsl_log_internal_format.dll"
|
||||
Delete "$INSTDIR\libabsl_log_internal_globals.dll"
|
||||
Delete "$INSTDIR\libabsl_log_internal_log_sink_set.dll"
|
||||
Delete "$INSTDIR\libabsl_log_internal_message.dll"
|
||||
Delete "$INSTDIR\libabsl_log_internal_nullguard.dll"
|
||||
Delete "$INSTDIR\libabsl_log_internal_proto.dll"
|
||||
Delete "$INSTDIR\libabsl_log_sink.dll"
|
||||
Delete "$INSTDIR\libabsl_low_level_hash.dll"
|
||||
Delete "$INSTDIR\libabsl_malloc_internal.dll"
|
||||
Delete "$INSTDIR\libabsl_raw_hash_set.dll"
|
||||
Delete "$INSTDIR\libabsl_raw_logging_internal.dll"
|
||||
Delete "$INSTDIR\libabsl_spinlock_wait.dll"
|
||||
Delete "$INSTDIR\libabsl_stacktrace.dll"
|
||||
Delete "$INSTDIR\libabsl_status.dll"
|
||||
Delete "$INSTDIR\libabsl_statusor.dll"
|
||||
Delete "$INSTDIR\libabsl_strerror.dll"
|
||||
Delete "$INSTDIR\libabsl_str_format_internal.dll"
|
||||
Delete "$INSTDIR\libabsl_strings.dll"
|
||||
Delete "$INSTDIR\libabsl_strings_internal.dll"
|
||||
Delete "$INSTDIR\libabsl_symbolize.dll"
|
||||
Delete "$INSTDIR\libabsl_synchronization.dll"
|
||||
Delete "$INSTDIR\libabsl_throw_delegate.dll"
|
||||
Delete "$INSTDIR\libabsl_time.dll"
|
||||
Delete "$INSTDIR\libabsl_time_zone.dll"
|
||||
|
||||
!ifdef debug
|
||||
Delete "$INSTDIR\gdb.exe"
|
||||
Delete "$INSTDIR\libexpat-1.dll"
|
||||
@@ -836,6 +931,7 @@ Section "Uninstall"
|
||||
Delete "$INSTDIR\libpcre2-16d.dll"
|
||||
Delete "$INSTDIR\libreadline8.dll"
|
||||
Delete "$INSTDIR\libtermcap.dll"
|
||||
Delete "$INSTDIR\libabsl_graphcycles_internal.dll"
|
||||
!else
|
||||
Delete "$INSTDIR\libpcre2-8.dll"
|
||||
Delete "$INSTDIR\libpcre2-16.dll"
|
||||
@@ -895,7 +991,6 @@ Section "Uninstall"
|
||||
Delete "$INSTDIR\jpeg62.dll"
|
||||
Delete "$INSTDIR\libbs2b.dll"
|
||||
Delete "$INSTDIR\libfaac_dll.dll"
|
||||
Delete "$INSTDIR\libiconv.dll"
|
||||
Delete "$INSTDIR\liblzma.dll"
|
||||
Delete "$INSTDIR\libmp3lame.dll"
|
||||
Delete "$INSTDIR\libopenmpt.dll"
|
||||
@@ -917,11 +1012,12 @@ Section "Uninstall"
|
||||
Delete "$INSTDIR\vorbis.dll"
|
||||
Delete "$INSTDIR\vorbisfile.dll"
|
||||
Delete "$INSTDIR\wavpackdll.dll"
|
||||
Delete "$INSTDIR\abseil_dll.dll"
|
||||
|
||||
!ifdef release
|
||||
Delete "$INSTDIR\freetype.dll"
|
||||
Delete "$INSTDIR\libiconv.dll"
|
||||
Delete "$INSTDIR\libpng16.dll"
|
||||
Delete "$INSTDIR\libprotobuf.dll"
|
||||
Delete "$INSTDIR\libxml2.dll"
|
||||
Delete "$INSTDIR\pcre2-8.dll"
|
||||
Delete "$INSTDIR\pcre2-16.dll"
|
||||
@@ -930,8 +1026,8 @@ Section "Uninstall"
|
||||
!endif
|
||||
!ifdef debug
|
||||
Delete "$INSTDIR\freetyped.dll"
|
||||
Delete "$INSTDIR\libiconvd.dll"
|
||||
Delete "$INSTDIR\libpng16d.dll"
|
||||
Delete "$INSTDIR\libprotobufd.dll"
|
||||
Delete "$INSTDIR\libxml2d.dll"
|
||||
Delete "$INSTDIR\pcre2-8d.dll"
|
||||
Delete "$INSTDIR\pcre2-16d.dll"
|
||||
@@ -945,6 +1041,11 @@ Section "Uninstall"
|
||||
|
||||
Delete "$INSTDIR\icudt72.dll"
|
||||
Delete "$INSTDIR\libfftw3-3.dll"
|
||||
!ifdef debug
|
||||
Delete "$INSTDIR\libprotobufd.dll"
|
||||
!else
|
||||
Delete "$INSTDIR\libprotobuf.dll"
|
||||
!endif
|
||||
!ifdef msvc && debug
|
||||
Delete "$INSTDIR\icuin72d.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);
|
||||
|
||||
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");
|
||||
|
||||
|
||||
@@ -165,10 +165,10 @@ class WorkerPool : public _WorkerPoolBase {
|
||||
template<typename HandlerType>
|
||||
WorkerPool<HandlerType>::WorkerPool(QObject *parent)
|
||||
: _WorkerPoolBase(parent),
|
||||
worker_count_(1),
|
||||
next_worker_(0),
|
||||
next_id_(0) {
|
||||
|
||||
worker_count_ = qBound(1, QThread::idealThreadCount() / 2, 4);
|
||||
local_server_name_ = qApp->applicationName().toLower();
|
||||
|
||||
if (local_server_name_.isEmpty()) {
|
||||
|
||||
@@ -43,12 +43,21 @@ target_include_directories(libstrawberry-tagreader PRIVATE
|
||||
|
||||
target_link_libraries(libstrawberry-tagreader PRIVATE
|
||||
${GLIB_LIBRARIES}
|
||||
${PROTOBUF_LIBRARY}
|
||||
${Protobuf_LIBRARIES}
|
||||
${QtCore_LIBRARIES}
|
||||
${QtNetwork_LIBRARIES}
|
||||
${QtGui_LIBRARIES}
|
||||
libstrawberry-common
|
||||
)
|
||||
|
||||
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)
|
||||
target_include_directories(libstrawberry-tagreader SYSTEM PRIVATE ${TAGLIB_INCLUDE_DIRS})
|
||||
target_link_libraries(libstrawberry-tagreader PRIVATE ${TAGLIB_LIBRARIES})
|
||||
|
||||
@@ -19,6 +19,15 @@
|
||||
|
||||
#include <string>
|
||||
|
||||
#include <QByteArray>
|
||||
#include <QString>
|
||||
#include <QIODevice>
|
||||
#include <QFile>
|
||||
#include <QBuffer>
|
||||
#include <QImage>
|
||||
#include <QMimeDatabase>
|
||||
|
||||
#include "core/logging.h"
|
||||
#include "tagreaderbase.h"
|
||||
|
||||
const std::string TagReaderBase::kEmbeddedCover = "(embedded)";
|
||||
@@ -26,12 +35,6 @@ const std::string TagReaderBase::kEmbeddedCover = "(embedded)";
|
||||
TagReaderBase::TagReaderBase() = default;
|
||||
TagReaderBase::~TagReaderBase() = default;
|
||||
|
||||
void TagReaderBase::Decode(const QString &tag, std::string *output) {
|
||||
|
||||
output->assign(DataCommaSizeFromQString(tag));
|
||||
|
||||
}
|
||||
|
||||
float TagReaderBase::ConvertPOPMRating(const int POPM_rating) {
|
||||
|
||||
if (POPM_rating < 0x01) return 0.0F;
|
||||
@@ -55,3 +58,86 @@ int TagReaderBase::ConvertToPOPMRating(const float rating) {
|
||||
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"
|
||||
|
||||
#define QStringFromStdString(x) QString::fromUtf8((x).data(), (x).size())
|
||||
#define DataCommaSizeFromQString(x) (x).toUtf8().constData(), (x).toUtf8().length()
|
||||
|
||||
/*
|
||||
* This class holds all useful methods to read and write tags from/to files.
|
||||
* You should not use it directly in the main process but rather use a TagReaderWorker process (using TagReaderClient)
|
||||
@@ -42,19 +39,23 @@ class TagReaderBase {
|
||||
virtual bool IsMediaFile(const QString &filename) const = 0;
|
||||
|
||||
virtual bool ReadFile(const QString &filename, spb::tagreader::SongMetadata *song) const = 0;
|
||||
virtual bool SaveFile(const QString &filename, const spb::tagreader::SongMetadata &song) const = 0;
|
||||
virtual bool SaveFile(const spb::tagreader::SaveFileRequest &request) const = 0;
|
||||
|
||||
virtual QByteArray LoadEmbeddedArt(const QString &filename) const = 0;
|
||||
virtual bool SaveEmbeddedArt(const QString &filename, const QByteArray &data) = 0;
|
||||
virtual bool SaveEmbeddedArt(const spb::tagreader::SaveEmbeddedArtRequest &request) const = 0;
|
||||
|
||||
virtual bool SaveSongPlaycountToFile(const QString &filename, const spb::tagreader::SongMetadata &song) const = 0;
|
||||
virtual bool SaveSongRatingToFile(const QString &filename, const spb::tagreader::SongMetadata &song) const = 0;
|
||||
|
||||
static void Decode(const QString &tag, std::string *output);
|
||||
|
||||
static float ConvertPOPMRating(const int POPM_rating);
|
||||
static int ConvertToPOPMRating(const float rating);
|
||||
|
||||
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:
|
||||
static const std::string kEmbeddedCover;
|
||||
|
||||
|
||||
@@ -29,8 +29,8 @@
|
||||
#include <QFile>
|
||||
#include <QTextStream>
|
||||
|
||||
#include "utilities/timeconstants.h"
|
||||
#include "core/logging.h"
|
||||
#include "core/timeconstants.h"
|
||||
#include "core/messagehandler.h"
|
||||
#include "tagreaderbase.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!
|
||||
|
||||
file.seek(HAS_ID6_OFFSET);
|
||||
bool has_id6 = (file.read(1)[0] == static_cast<char>(xID6_STATUS::ON));
|
||||
const QByteArray id6_status = file.read(1);
|
||||
const bool has_id6 = id6_status.length() >= 1 && id6_status[0] == static_cast<char>(xID6_STATUS::ON);
|
||||
|
||||
file.seek(SONG_TITLE_OFFSET);
|
||||
song_info->set_title(QString::fromLatin1(file.read(32)).toStdString());
|
||||
@@ -156,10 +157,10 @@ void GME::SPC::Read(const QFileInfo &file_info, spb::tagreader::SongMetadata *so
|
||||
TagLib::Tag *tag = ape.tag();
|
||||
if (!tag) return;
|
||||
|
||||
TagReaderTagLib::Decode(tag->artist(), song_info->mutable_artist());
|
||||
TagReaderTagLib::Decode(tag->album(), song_info->mutable_album());
|
||||
TagReaderTagLib::Decode(tag->title(), song_info->mutable_title());
|
||||
TagReaderTagLib::Decode(tag->genre(), song_info->mutable_genre());
|
||||
TagReaderTagLib::TStringToStdString(tag->artist(), song_info->mutable_artist());
|
||||
TagReaderTagLib::TStringToStdString(tag->album(), song_info->mutable_album());
|
||||
TagReaderTagLib::TStringToStdString(tag->title(), song_info->mutable_title());
|
||||
TagReaderTagLib::TStringToStdString(tag->genre(), song_info->mutable_genre());
|
||||
song_info->set_track(tag->track());
|
||||
song_info->set_year(tag->year());
|
||||
}
|
||||
@@ -278,7 +279,7 @@ bool TagReaderGME::ReadFile(const QString &filename, spb::tagreader::SongMetadat
|
||||
return GME::ReadFile(fileinfo, song);
|
||||
}
|
||||
|
||||
bool TagReaderGME::SaveFile(const QString&, const spb::tagreader::SongMetadata&) const {
|
||||
bool TagReaderGME::SaveFile(const spb::tagreader::SaveFileRequest&) const {
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -286,7 +287,7 @@ QByteArray TagReaderGME::LoadEmbeddedArt(const QString&) const {
|
||||
return QByteArray();
|
||||
}
|
||||
|
||||
bool TagReaderGME::SaveEmbeddedArt(const QString&, const QByteArray&) {
|
||||
bool TagReaderGME::SaveEmbeddedArt(const spb::tagreader::SaveEmbeddedArtRequest&) const {
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
@@ -55,14 +55,22 @@ constexpr int XID6_OFFSET = (0x101C0 + 64);
|
||||
|
||||
constexpr int NANO_PER_MS = 1000000;
|
||||
|
||||
enum xID6_STATUS {
|
||||
enum class xID6_STATUS {
|
||||
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);
|
||||
qint16 GetNextMemAddressAlign32bit(qint16 input);
|
||||
@@ -99,13 +107,13 @@ class TagReaderGME : public TagReaderBase {
|
||||
bool IsMediaFile(const QString &filename) const override;
|
||||
|
||||
bool ReadFile(const QString &filename, spb::tagreader::SongMetadata *song) const override;
|
||||
bool SaveFile(const QString &filename, const spb::tagreader::SongMetadata &song) const override;
|
||||
bool SaveFile(const spb::tagreader::SaveFileRequest &request) const override;
|
||||
|
||||
QByteArray LoadEmbeddedArt(const QString &filename) const override;
|
||||
bool SaveEmbeddedArt(const QString &filename, const QByteArray &data) override;
|
||||
bool SaveEmbeddedArt(const spb::tagreader::SaveEmbeddedArtRequest &request) const override;
|
||||
|
||||
bool SaveSongPlaycountToFile(const QString &filename, const spb::tagreader::SongMetadata &song) const override;
|
||||
bool SaveSongRatingToFile(const QString &filename, const spb::tagreader::SongMetadata &song) const override;
|
||||
};
|
||||
|
||||
#endif
|
||||
#endif // TAGREADERGME_H
|
||||
|
||||
@@ -73,8 +73,30 @@ message SongMetadata {
|
||||
|
||||
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 {
|
||||
@@ -87,21 +109,20 @@ message ReadFileResponse {
|
||||
|
||||
message SaveFileRequest {
|
||||
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 {
|
||||
optional bool success = 1;
|
||||
}
|
||||
|
||||
message IsMediaFileRequest {
|
||||
optional string filename = 1;
|
||||
}
|
||||
|
||||
message IsMediaFileResponse {
|
||||
optional bool success = 1;
|
||||
}
|
||||
|
||||
message LoadEmbeddedArtRequest {
|
||||
optional string filename = 1;
|
||||
}
|
||||
@@ -112,7 +133,9 @@ message LoadEmbeddedArtResponse {
|
||||
|
||||
message SaveEmbeddedArtRequest {
|
||||
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 {
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -29,8 +29,12 @@
|
||||
#include <taglib/tstring.h>
|
||||
#include <taglib/fileref.h>
|
||||
#include <taglib/xiphcomment.h>
|
||||
#include <taglib/flacfile.h>
|
||||
#include <taglib/mpegfile.h>
|
||||
#include <taglib/mp4file.h>
|
||||
#include <taglib/apetag.h>
|
||||
#include <taglib/apefile.h>
|
||||
#include <taglib/asffile.h>
|
||||
#include <taglib/id3v2tag.h>
|
||||
#include <taglib/popularimeterframe.h>
|
||||
|
||||
@@ -51,15 +55,15 @@ class TagReaderTagLib : public TagReaderBase {
|
||||
bool IsMediaFile(const QString &filename) const override;
|
||||
|
||||
bool ReadFile(const QString &filename, spb::tagreader::SongMetadata *song) const override;
|
||||
bool SaveFile(const QString &filename, const spb::tagreader::SongMetadata &song) const override;
|
||||
bool SaveFile(const spb::tagreader::SaveFileRequest &request) const override;
|
||||
|
||||
QByteArray LoadEmbeddedArt(const QString &filename) const override;
|
||||
bool SaveEmbeddedArt(const QString &filename, const QByteArray &data) override;
|
||||
bool SaveEmbeddedArt(const spb::tagreader::SaveEmbeddedArtRequest &request) const override;
|
||||
|
||||
bool SaveSongPlaycountToFile(const QString &filename, const spb::tagreader::SongMetadata &song) const override;
|
||||
bool SaveSongRatingToFile(const QString &filename, const spb::tagreader::SongMetadata &song) const override;
|
||||
|
||||
static void Decode(const TagLib::String &tag, std::string *output);
|
||||
static void TStringToStdString(const TagLib::String &tag, std::string *output);
|
||||
|
||||
private:
|
||||
spb::tagreader::SongMetadata_FileType GuessFileType(TagLib::FileRef *fileref) const;
|
||||
@@ -67,7 +71,7 @@ class TagReaderTagLib : public TagReaderBase {
|
||||
void ParseOggTag(const TagLib::Ogg::FieldListMap &map, QString *disc, QString *compilation, spb::tagreader::SongMetadata *song) const;
|
||||
void ParseAPETag(const TagLib::APE::ItemListMap &map, QString *disc, QString *compilation, spb::tagreader::SongMetadata *song) const;
|
||||
|
||||
void SetVorbisComments(TagLib::Ogg::XiphComment *vorbis_comments, const spb::tagreader::SongMetadata &song) const;
|
||||
void SetVorbisComments(TagLib::Ogg::XiphComment *vorbis_comment, const spb::tagreader::SongMetadata &song) const;
|
||||
void SaveAPETag(TagLib::APE::Tag *tag, const spb::tagreader::SongMetadata &song) const;
|
||||
|
||||
void SetTextFrame(const char *id, const QString &value, TagLib::ID3v2::Tag *tag) const;
|
||||
@@ -80,6 +84,23 @@ class TagReaderTagLib : public TagReaderBase {
|
||||
|
||||
static TagLib::ID3v2::PopularimeterFrame *GetPOPMFrameFromTag(TagLib::ID3v2::Tag *tag);
|
||||
|
||||
void SetPlaycount(TagLib::Ogg::XiphComment *xiph_comment, const spb::tagreader::SongMetadata &song) const;
|
||||
void SetPlaycount(TagLib::APE::Tag *tag, const spb::tagreader::SongMetadata &song) const;
|
||||
void SetPlaycount(TagLib::ID3v2::Tag *tag, const spb::tagreader::SongMetadata &song) const;
|
||||
void SetPlaycount(TagLib::MP4::Tag *tag, const spb::tagreader::SongMetadata &song) const;
|
||||
void SetPlaycount(TagLib::ASF::Tag *tag, const spb::tagreader::SongMetadata &song) const;
|
||||
|
||||
void SetRating(TagLib::Ogg::XiphComment *xiph_comment, const spb::tagreader::SongMetadata &song) const;
|
||||
void SetRating(TagLib::APE::Tag *tag, const spb::tagreader::SongMetadata &song) const;
|
||||
void SetRating(TagLib::ID3v2::Tag *tag, const spb::tagreader::SongMetadata &song) const;
|
||||
void SetRating(TagLib::MP4::Tag *tag, const spb::tagreader::SongMetadata &song) const;
|
||||
void SetRating(TagLib::ASF::Tag *tag, const spb::tagreader::SongMetadata &song) const;
|
||||
|
||||
void SetEmbeddedArt(TagLib::FLAC::File *flac_file, TagLib::Ogg::XiphComment *xiph_comment, const QByteArray &data) const;
|
||||
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:
|
||||
FileRefFactory *factory_;
|
||||
|
||||
|
||||
@@ -22,6 +22,7 @@
|
||||
#include <string>
|
||||
#include <memory>
|
||||
#include <algorithm>
|
||||
#include <vector>
|
||||
#include <sys/stat.h>
|
||||
|
||||
#include <tagparser/mediafileinfo.h>
|
||||
@@ -41,7 +42,7 @@
|
||||
|
||||
#include "core/logging.h"
|
||||
#include "core/messagehandler.h"
|
||||
#include "core/timeconstants.h"
|
||||
#include "utilities/timeconstants.h"
|
||||
|
||||
TagReaderTagParser::TagReaderTagParser() = default;
|
||||
|
||||
@@ -79,7 +80,7 @@ bool TagReaderTagParser::IsMediaFile(const QString &filename) const {
|
||||
}
|
||||
|
||||
const auto tracks = taginfo.tracks();
|
||||
for (const auto track : tracks) {
|
||||
for (TagParser::AbstractTrack *track : tracks) {
|
||||
if (track->mediaType() == TagParser::MediaType::Audio) {
|
||||
taginfo.close();
|
||||
return true;
|
||||
@@ -102,8 +103,9 @@ bool TagReaderTagParser::ReadFile(const QString &filename, spb::tagreader::SongM
|
||||
if (!fileinfo.exists() || fileinfo.suffix().compare("bak", Qt::CaseInsensitive) == 0) return false;
|
||||
|
||||
const QByteArray url(QUrl::fromLocalFile(filename).toEncoded());
|
||||
const QByteArray basefilename = fileinfo.fileName().toUtf8();
|
||||
|
||||
song->set_basefilename(DataCommaSizeFromQString(fileinfo.fileName()));
|
||||
song->set_basefilename(basefilename.constData(), basefilename.size());
|
||||
song->set_url(url.constData(), url.size());
|
||||
song->set_filesize(fileinfo.size());
|
||||
song->set_mtime(fileinfo.lastModified().isValid() ? std::max(fileinfo.lastModified().toSecsSinceEpoch(), 0LL) : 0LL);
|
||||
@@ -154,8 +156,8 @@ bool TagReaderTagParser::ReadFile(const QString &filename, spb::tagreader::SongM
|
||||
qLog(Debug) << QString::fromStdString(msg.message());
|
||||
}
|
||||
|
||||
const auto tracks = taginfo.tracks();
|
||||
for (const auto track : tracks) {
|
||||
std::vector<TagParser::AbstractTrack*> tracks = taginfo.tracks();
|
||||
for (TagParser::AbstractTrack *track : tracks) {
|
||||
switch (track->format().general) {
|
||||
case TagParser::GeneralMediaFormat::Flac:
|
||||
song->set_filetype(spb::tagreader::SongMetadata_FileType::SongMetadata_FileType_FLAC);
|
||||
@@ -209,7 +211,7 @@ bool TagReaderTagParser::ReadFile(const QString &filename, spb::tagreader::SongM
|
||||
return false;
|
||||
}
|
||||
|
||||
for (const auto tag : taginfo.tags()) {
|
||||
for (TagParser::Tag *tag : taginfo.tags()) {
|
||||
song->set_albumartist(tag->value(TagParser::KnownField::AlbumArtist).toString(TagParser::TagTextEncoding::Utf8));
|
||||
song->set_artist(tag->value(TagParser::KnownField::Artist).toString(TagParser::TagTextEncoding::Utf8));
|
||||
song->set_album(tag->value(TagParser::KnownField::Album).toString(TagParser::TagTextEncoding::Utf8));
|
||||
@@ -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 {
|
||||
TagParser::MediaFileInfo taginfo;
|
||||
@@ -295,22 +320,34 @@ bool TagReaderTagParser::SaveFile(const QString &filename, const spb::tagreader:
|
||||
taginfo.createAppropriateTags();
|
||||
}
|
||||
|
||||
for (const auto tag : taginfo.tags()) {
|
||||
tag->setValue(TagParser::KnownField::AlbumArtist, TagParser::TagValue(song.albumartist(), TagParser::TagTextEncoding::Utf8, tag->proposedTextEncoding()));
|
||||
tag->setValue(TagParser::KnownField::Artist, TagParser::TagValue(song.artist(), TagParser::TagTextEncoding::Utf8, tag->proposedTextEncoding()));
|
||||
tag->setValue(TagParser::KnownField::Album, TagParser::TagValue(song.album(), TagParser::TagTextEncoding::Utf8, tag->proposedTextEncoding()));
|
||||
tag->setValue(TagParser::KnownField::Title, TagParser::TagValue(song.title(), TagParser::TagTextEncoding::Utf8, tag->proposedTextEncoding()));
|
||||
tag->setValue(TagParser::KnownField::Genre, TagParser::TagValue(song.genre(), TagParser::TagTextEncoding::Utf8, tag->proposedTextEncoding()));
|
||||
tag->setValue(TagParser::KnownField::Composer, TagParser::TagValue(song.composer(), TagParser::TagTextEncoding::Utf8, tag->proposedTextEncoding()));
|
||||
tag->setValue(TagParser::KnownField::Performers, TagParser::TagValue(song.performer(), TagParser::TagTextEncoding::Utf8, tag->proposedTextEncoding()));
|
||||
tag->setValue(TagParser::KnownField::Grouping, TagParser::TagValue(song.grouping(), TagParser::TagTextEncoding::Utf8, tag->proposedTextEncoding()));
|
||||
tag->setValue(TagParser::KnownField::Comment, TagParser::TagValue(song.comment(), TagParser::TagTextEncoding::Utf8, tag->proposedTextEncoding()));
|
||||
tag->setValue(TagParser::KnownField::Lyrics, TagParser::TagValue(song.lyrics(), TagParser::TagTextEncoding::Utf8, tag->proposedTextEncoding()));
|
||||
tag->setValue(TagParser::KnownField::TrackPosition, TagParser::TagValue(song.track()));
|
||||
tag->setValue(TagParser::KnownField::DiskPosition, TagParser::TagValue(song.disc()));
|
||||
tag->setValue(TagParser::KnownField::RecordDate, TagParser::TagValue(song.year()));
|
||||
tag->setValue(TagParser::KnownField::ReleaseDate, TagParser::TagValue(song.originalyear()));
|
||||
for (TagParser::Tag *tag : taginfo.tags()) {
|
||||
if (save_tags) {
|
||||
tag->setValue(TagParser::KnownField::AlbumArtist, TagParser::TagValue(song.albumartist(), TagParser::TagTextEncoding::Utf8, tag->proposedTextEncoding()));
|
||||
tag->setValue(TagParser::KnownField::Artist, TagParser::TagValue(song.artist(), TagParser::TagTextEncoding::Utf8, tag->proposedTextEncoding()));
|
||||
tag->setValue(TagParser::KnownField::Album, TagParser::TagValue(song.album(), TagParser::TagTextEncoding::Utf8, tag->proposedTextEncoding()));
|
||||
tag->setValue(TagParser::KnownField::Title, TagParser::TagValue(song.title(), TagParser::TagTextEncoding::Utf8, tag->proposedTextEncoding()));
|
||||
tag->setValue(TagParser::KnownField::Genre, TagParser::TagValue(song.genre(), TagParser::TagTextEncoding::Utf8, tag->proposedTextEncoding()));
|
||||
tag->setValue(TagParser::KnownField::Composer, TagParser::TagValue(song.composer(), TagParser::TagTextEncoding::Utf8, tag->proposedTextEncoding()));
|
||||
tag->setValue(TagParser::KnownField::Performers, TagParser::TagValue(song.performer(), TagParser::TagTextEncoding::Utf8, tag->proposedTextEncoding()));
|
||||
tag->setValue(TagParser::KnownField::Grouping, TagParser::TagValue(song.grouping(), TagParser::TagTextEncoding::Utf8, tag->proposedTextEncoding()));
|
||||
tag->setValue(TagParser::KnownField::Comment, TagParser::TagValue(song.comment(), TagParser::TagTextEncoding::Utf8, tag->proposedTextEncoding()));
|
||||
tag->setValue(TagParser::KnownField::Lyrics, TagParser::TagValue(song.lyrics(), TagParser::TagTextEncoding::Utf8, tag->proposedTextEncoding()));
|
||||
tag->setValue(TagParser::KnownField::TrackPosition, TagParser::TagValue(song.track()));
|
||||
tag->setValue(TagParser::KnownField::DiskPosition, TagParser::TagValue(song.disc()));
|
||||
tag->setValue(TagParser::KnownField::RecordDate, TagParser::TagValue(song.year()));
|
||||
tag->setValue(TagParser::KnownField::ReleaseDate, TagParser::TagValue(song.originalyear()));
|
||||
}
|
||||
if (save_playcount) {
|
||||
SaveSongPlaycountToFile(tag, song);
|
||||
}
|
||||
if (save_rating) {
|
||||
SaveSongRatingToFile(tag, song);
|
||||
}
|
||||
if (save_cover) {
|
||||
SaveEmbeddedArt(tag, cover_data);
|
||||
}
|
||||
}
|
||||
|
||||
taginfo.applyChanges(diag, progress);
|
||||
taginfo.close();
|
||||
|
||||
@@ -358,7 +395,7 @@ QByteArray TagReaderTagParser::LoadEmbeddedArt(const QString &filename) const {
|
||||
return QByteArray();
|
||||
}
|
||||
|
||||
for (const auto tag : taginfo.tags()) {
|
||||
for (TagParser::Tag *tag : taginfo.tags()) {
|
||||
if (!tag->value(TagParser::KnownField::Cover).empty() && tag->value(TagParser::KnownField::Cover).dataSize() > 0) {
|
||||
QByteArray data(tag->value(TagParser::KnownField::Cover).dataPointer(), tag->value(TagParser::KnownField::Cover).dataSize());
|
||||
taginfo.close();
|
||||
@@ -379,12 +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;
|
||||
|
||||
const QByteArray cover_data = LoadCoverDataFromRequest(request);
|
||||
|
||||
try {
|
||||
|
||||
TagParser::MediaFileInfo taginfo;
|
||||
@@ -415,8 +462,8 @@ bool TagReaderTagParser::SaveEmbeddedArt(const QString &filename, const QByteArr
|
||||
taginfo.createAppropriateTags();
|
||||
}
|
||||
|
||||
for (const auto tag : taginfo.tags()) {
|
||||
tag->setValue(TagParser::KnownField::Cover, TagParser::TagValue(data.toStdString()));
|
||||
for (TagParser::Tag *tag : taginfo.tags()) {
|
||||
SaveEmbeddedArt(tag, cover_data);
|
||||
}
|
||||
|
||||
taginfo.applyChanges(diag, progress);
|
||||
@@ -435,8 +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; }
|
||||
|
||||
void TagReaderTagParser::SaveSongRatingToFile(TagParser::Tag *tag, const spb::tagreader::SongMetadata &song) const {
|
||||
|
||||
tag->setValue(TagParser::KnownField::Rating, TagParser::TagValue(ConvertToPOPMRating(song.rating())));
|
||||
|
||||
}
|
||||
|
||||
bool TagReaderTagParser::SaveSongRatingToFile(const QString &filename, const spb::tagreader::SongMetadata &song) const {
|
||||
|
||||
if (filename.isEmpty()) return false;
|
||||
@@ -476,9 +531,10 @@ bool TagReaderTagParser::SaveSongRatingToFile(const QString &filename, const spb
|
||||
taginfo.createAppropriateTags();
|
||||
}
|
||||
|
||||
for (const auto tag : taginfo.tags()) {
|
||||
tag->setValue(TagParser::KnownField::Rating, TagParser::TagValue(ConvertToPOPMRating(song.rating())));
|
||||
for (TagParser::Tag *tag : taginfo.tags()) {
|
||||
SaveSongRatingToFile(tag, song);
|
||||
}
|
||||
|
||||
taginfo.applyChanges(diag, progress);
|
||||
taginfo.close();
|
||||
|
||||
|
||||
@@ -22,6 +22,8 @@
|
||||
|
||||
#include <string>
|
||||
|
||||
#include <tagparser/tag.h>
|
||||
|
||||
#include <QByteArray>
|
||||
#include <QString>
|
||||
|
||||
@@ -40,14 +42,20 @@ class TagReaderTagParser : public TagReaderBase {
|
||||
bool IsMediaFile(const QString &filename) const override;
|
||||
|
||||
bool ReadFile(const QString &filename, spb::tagreader::SongMetadata *song) const override;
|
||||
bool SaveFile(const QString &filename, const spb::tagreader::SongMetadata &song) const override;
|
||||
bool SaveFile(const spb::tagreader::SaveFileRequest &request) const override;
|
||||
|
||||
QByteArray LoadEmbeddedArt(const QString &filename) const override;
|
||||
bool SaveEmbeddedArt(const QString &filename, const QByteArray &data) override;
|
||||
bool SaveEmbeddedArt(const spb::tagreader::SaveEmbeddedArtRequest &request) const override;
|
||||
|
||||
bool SaveSongPlaycountToFile(const QString &filename, const spb::tagreader::SongMetadata &song) const override;
|
||||
bool SaveSongRatingToFile(const QString &filename, const spb::tagreader::SongMetadata &song) const override;
|
||||
|
||||
private:
|
||||
void SaveSongPlaycountToFile(TagParser::Tag *tag, const spb::tagreader::SongMetadata &song) const;
|
||||
void SaveSongRatingToFile(TagParser::Tag *tag, const spb::tagreader::SongMetadata &song) const;
|
||||
void SaveEmbeddedArt(TagParser::Tag *tag, const QByteArray &data) const;
|
||||
|
||||
public:
|
||||
Q_DISABLE_COPY(TagReaderTagParser)
|
||||
};
|
||||
|
||||
|
||||
@@ -56,36 +56,41 @@ void TagReaderWorker::DeviceClosed() {
|
||||
bool TagReaderWorker::HandleMessage(const spb::tagreader::Message &message, spb::tagreader::Message &reply, TagReaderBase *reader) {
|
||||
|
||||
if (message.has_is_media_file_request()) {
|
||||
bool success = reader->IsMediaFile(QStringFromStdString(message.is_media_file_request().filename()));
|
||||
const QString filename = QString::fromUtf8(message.is_media_file_request().filename().data(), message.is_media_file_request().filename().size());
|
||||
bool success = reader->IsMediaFile(filename);
|
||||
reply.mutable_is_media_file_response()->set_success(success);
|
||||
return success;
|
||||
}
|
||||
else if (message.has_read_file_request()) {
|
||||
bool success = reader->ReadFile(QStringFromStdString(message.read_file_request().filename()), reply.mutable_read_file_response()->mutable_metadata());
|
||||
const QString filename = QString::fromUtf8(message.read_file_request().filename().data(), message.read_file_request().filename().size());
|
||||
bool success = reader->ReadFile(filename, reply.mutable_read_file_response()->mutable_metadata());
|
||||
return success;
|
||||
}
|
||||
else if (message.has_save_file_request()) {
|
||||
bool success = reader->SaveFile(QStringFromStdString(message.save_file_request().filename()), message.save_file_request().metadata());
|
||||
bool success = reader->SaveFile(message.save_file_request());
|
||||
reply.mutable_save_file_response()->set_success(success);
|
||||
return success;
|
||||
}
|
||||
else if (message.has_load_embedded_art_request()) {
|
||||
QByteArray data = reader->LoadEmbeddedArt(QStringFromStdString(message.load_embedded_art_request().filename()));
|
||||
const QString filename = QString::fromUtf8(message.load_embedded_art_request().filename().data(), message.load_embedded_art_request().filename().size());
|
||||
QByteArray data = reader->LoadEmbeddedArt(filename);
|
||||
reply.mutable_load_embedded_art_response()->set_data(data.constData(), data.size());
|
||||
return true;
|
||||
}
|
||||
else if (message.has_save_embedded_art_request()) {
|
||||
bool success = reader->SaveEmbeddedArt(QStringFromStdString(message.save_embedded_art_request().filename()), QByteArray(message.save_embedded_art_request().data().data(), static_cast<qint64>(message.save_embedded_art_request().data().size())));
|
||||
bool success = reader->SaveEmbeddedArt(message.save_embedded_art_request());
|
||||
reply.mutable_save_embedded_art_response()->set_success(success);
|
||||
return success;
|
||||
}
|
||||
else if (message.has_save_song_playcount_to_file_request()) {
|
||||
bool success = reader->SaveSongPlaycountToFile(QStringFromStdString(message.save_song_playcount_to_file_request().filename()), message.save_song_playcount_to_file_request().metadata());
|
||||
const QString filename = QString::fromUtf8(message.save_song_playcount_to_file_request().filename().data(), message.save_song_playcount_to_file_request().filename().size());
|
||||
bool success = reader->SaveSongPlaycountToFile(filename, message.save_song_playcount_to_file_request().metadata());
|
||||
reply.mutable_save_song_playcount_to_file_response()->set_success(success);
|
||||
return success;
|
||||
}
|
||||
else if (message.has_save_song_rating_to_file_request()) {
|
||||
bool success = reader->SaveSongRatingToFile(QStringFromStdString(message.save_song_rating_to_file_request().filename()), message.save_song_rating_to_file_request().metadata());
|
||||
const QString filename = QString::fromUtf8(message.save_song_rating_to_file_request().filename().data(), message.save_song_rating_to_file_request().filename().size());
|
||||
bool success = reader->SaveSongRatingToFile(filename, message.save_song_rating_to_file_request().metadata());
|
||||
reply.mutable_save_song_rating_to_file_response()->set_success(success);
|
||||
return success;
|
||||
}
|
||||
|
||||
@@ -7,7 +7,6 @@ endif()
|
||||
set(SOURCES
|
||||
core/mainwindow.cpp
|
||||
core/application.cpp
|
||||
core/appearance.cpp
|
||||
core/player.cpp
|
||||
core/commandlineoptions.cpp
|
||||
core/database.cpp
|
||||
@@ -35,14 +34,29 @@ set(SOURCES
|
||||
core/taskmanager.cpp
|
||||
core/thread.cpp
|
||||
core/urlhandler.cpp
|
||||
core/utilities.cpp
|
||||
core/imageutils.cpp
|
||||
core/iconloader.cpp
|
||||
core/standarditemiconloader.cpp
|
||||
core/scopedtransaction.cpp
|
||||
core/translations.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/enginebase.cpp
|
||||
engine/devicefinders.cpp
|
||||
@@ -54,6 +68,7 @@ set(SOURCES
|
||||
analyzer/blockanalyzer.cpp
|
||||
analyzer/boomanalyzer.cpp
|
||||
analyzer/rainbowanalyzer.cpp
|
||||
analyzer/sonogram.cpp
|
||||
|
||||
equalizer/equalizer.cpp
|
||||
equalizer/equalizerslider.cpp
|
||||
@@ -69,9 +84,11 @@ set(SOURCES
|
||||
collection/collectionitemdelegate.cpp
|
||||
collection/collectionviewcontainer.cpp
|
||||
collection/collectiondirectorymodel.cpp
|
||||
collection/collectionfilteroptions.cpp
|
||||
collection/collectionfilterwidget.cpp
|
||||
collection/collectionplaylistitem.cpp
|
||||
collection/collectionquery.cpp
|
||||
collection/collectionqueryoptions.cpp
|
||||
collection/savedgroupingmanager.cpp
|
||||
collection/groupbydialog.cpp
|
||||
collection/collectiontask.cpp
|
||||
@@ -152,6 +169,8 @@ set(SOURCES
|
||||
|
||||
lyrics/lyricsproviders.cpp
|
||||
lyrics/lyricsprovider.cpp
|
||||
lyrics/lyricssearchrequest.h
|
||||
lyrics/lyricssearchresult.h
|
||||
lyrics/lyricsfetcher.cpp
|
||||
lyrics/lyricsfetchersearch.cpp
|
||||
lyrics/jsonlyricsprovider.cpp
|
||||
@@ -161,6 +180,7 @@ set(SOURCES
|
||||
lyrics/geniuslyricsprovider.cpp
|
||||
lyrics/musixmatchlyricsprovider.cpp
|
||||
lyrics/chartlyricsprovider.cpp
|
||||
lyrics/lyricscomlyricsprovider.cpp
|
||||
|
||||
providers/musixmatchprovider.cpp
|
||||
|
||||
@@ -205,6 +225,8 @@ set(SOURCES
|
||||
widgets/multiloadingindicator.cpp
|
||||
widgets/playingwidget.cpp
|
||||
widgets/renametablineedit.cpp
|
||||
widgets/sliderslider.cpp
|
||||
widgets/prettyslider.cpp
|
||||
widgets/volumeslider.cpp
|
||||
widgets/stickyslider.cpp
|
||||
widgets/stretchheaderview.cpp
|
||||
@@ -248,6 +270,7 @@ set(SOURCES
|
||||
scrobbler/scrobblerservice.cpp
|
||||
scrobbler/scrobblercache.cpp
|
||||
scrobbler/scrobblercacheitem.cpp
|
||||
scrobbler/scrobblemetadata.cpp
|
||||
scrobbler/scrobblingapi20.cpp
|
||||
scrobbler/lastfmscrobbler.cpp
|
||||
scrobbler/librefmscrobbler.cpp
|
||||
@@ -264,7 +287,6 @@ set(SOURCES
|
||||
set(HEADERS
|
||||
core/mainwindow.h
|
||||
core/application.h
|
||||
core/appearance.h
|
||||
core/player.h
|
||||
core/database.h
|
||||
core/deletefiles.h
|
||||
@@ -294,6 +316,7 @@ set(HEADERS
|
||||
analyzer/blockanalyzer.h
|
||||
analyzer/boomanalyzer.h
|
||||
analyzer/rainbowanalyzer.h
|
||||
analyzer/sonogram.h
|
||||
|
||||
equalizer/equalizer.h
|
||||
equalizer/equalizerslider.h
|
||||
@@ -395,6 +418,7 @@ set(HEADERS
|
||||
lyrics/geniuslyricsprovider.h
|
||||
lyrics/musixmatchlyricsprovider.h
|
||||
lyrics/chartlyricsprovider.h
|
||||
lyrics/lyricscomlyricsprovider.h
|
||||
|
||||
settings/settingsdialog.h
|
||||
settings/settingspage.h
|
||||
@@ -436,6 +460,8 @@ set(HEADERS
|
||||
widgets/multiloadingindicator.h
|
||||
widgets/playingwidget.h
|
||||
widgets/renametablineedit.h
|
||||
widgets/sliderslider.h
|
||||
widgets/prettyslider.h
|
||||
widgets/volumeslider.h
|
||||
widgets/stickyslider.h
|
||||
widgets/stretchheaderview.h
|
||||
@@ -446,7 +472,6 @@ set(HEADERS
|
||||
widgets/qsearchfield.h
|
||||
widgets/ratingwidget.h
|
||||
widgets/forcescrollperpixel.h
|
||||
widgets/resizabletextedit.h
|
||||
|
||||
osd/osdbase.h
|
||||
osd/osdpretty.h
|
||||
@@ -478,7 +503,6 @@ set(HEADERS
|
||||
scrobbler/scrobblerservices.h
|
||||
scrobbler/scrobblerservice.h
|
||||
scrobbler/scrobblercache.h
|
||||
scrobbler/scrobblercacheitem.h
|
||||
scrobbler/scrobblingapi20.h
|
||||
scrobbler/lastfmscrobbler.h
|
||||
scrobbler/librefmscrobbler.h
|
||||
@@ -783,8 +807,8 @@ optional_source(HAVE_AUDIOCD
|
||||
# Platform specific - macOS
|
||||
optional_source(APPLE
|
||||
SOURCES
|
||||
utilities/macosutils.mm
|
||||
core/scoped_nsautorelease_pool.mm
|
||||
core/mac_utilities.mm
|
||||
core/mac_startup.mm
|
||||
core/macsystemtrayicon.mm
|
||||
core/macfslistener.mm
|
||||
@@ -805,8 +829,10 @@ optional_source(APPLE
|
||||
# Platform specific - Windows
|
||||
optional_source(WIN32
|
||||
SOURCES
|
||||
utilities/winutils.cpp
|
||||
engine/directsounddevicefinder.cpp
|
||||
engine/mmdevicefinder.cpp
|
||||
core/scopedwchararray.cpp
|
||||
core/windows7thumbbar.cpp
|
||||
HEADERS
|
||||
core/windows7thumbbar.h
|
||||
@@ -943,6 +969,7 @@ link_directories(
|
||||
${GOBJECT_LIBRARY_DIRS}
|
||||
${GNUTLS_LIBRARY_DIRS}
|
||||
${SQLITE_LIBRARY_DIRS}
|
||||
${PROTOBUF_LIBRARY_DIRS}
|
||||
${SINGLEAPPLICATION_LIBRARY_DIRS}
|
||||
${SINGLECOREAPPLICATION_LIBRARY_DIRS}
|
||||
)
|
||||
@@ -1062,6 +1089,7 @@ target_link_libraries(strawberry_lib PUBLIC
|
||||
${GNUTLS_LIBRARIES}
|
||||
${SQLITE_LIBRARIES}
|
||||
${QT_LIBRARIES}
|
||||
${Protobuf_LIBRARIES}
|
||||
${SINGLEAPPLICATION_LIBRARIES}
|
||||
${SINGLECOREAPPLICATION_LIBRARIES}
|
||||
libstrawberry-common
|
||||
|
||||
@@ -108,7 +108,7 @@ void Analyzer::Base::paintEvent(QPaintEvent *e) {
|
||||
p.fillRect(e->rect(), palette().color(QPalette::Window));
|
||||
|
||||
switch (engine_->state()) {
|
||||
case Engine::Playing: {
|
||||
case Engine::State::Playing: {
|
||||
const Engine::Scope &thescope = engine_->scope(timeout_);
|
||||
int i = 0;
|
||||
|
||||
@@ -126,7 +126,7 @@ void Analyzer::Base::paintEvent(QPaintEvent *e) {
|
||||
|
||||
break;
|
||||
}
|
||||
case Engine::Paused:
|
||||
case Engine::State::Paused:
|
||||
is_playing_ = false;
|
||||
analyze(p, lastscope_, new_frame_);
|
||||
break;
|
||||
|
||||
@@ -40,6 +40,7 @@
|
||||
#include "blockanalyzer.h"
|
||||
#include "boomanalyzer.h"
|
||||
#include "rainbowanalyzer.h"
|
||||
#include "sonogram.h"
|
||||
|
||||
#include "core/logging.h"
|
||||
#include "engine/enginebase.h"
|
||||
@@ -85,6 +86,7 @@ AnalyzerContainer::AnalyzerContainer(QWidget *parent)
|
||||
AddAnalyzerType<BoomAnalyzer>();
|
||||
AddAnalyzerType<Rainbow::NyanCatAnalyzer>();
|
||||
AddAnalyzerType<Rainbow::RainbowDashAnalyzer>();
|
||||
AddAnalyzerType<Sonogram>();
|
||||
|
||||
disable_action_ = context_menu_->addAction(tr("No analyzer"), this, &AnalyzerContainer::DisableAnalyzer);
|
||||
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) {
|
||||
|
||||
if (!new_frame || engine_->state() == Engine::Paused) {
|
||||
if (!new_frame || engine_->state() == Engine::State::Paused) {
|
||||
p.drawPixmap(0, 0, canvas_);
|
||||
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/tagreaderclient.h"
|
||||
#include "core/thread.h"
|
||||
#include "core/utilities.h"
|
||||
#include "core/song.h"
|
||||
#include "core/logging.h"
|
||||
#include "utilities/threadutils.h"
|
||||
#include "collection.h"
|
||||
#include "collectionwatcher.h"
|
||||
#include "collectionbackend.h"
|
||||
@@ -58,8 +58,6 @@ SCollection::SCollection(Application *app, QObject *parent)
|
||||
watcher_(nullptr),
|
||||
watcher_thread_(nullptr),
|
||||
original_thread_(nullptr),
|
||||
io_priority_(Utilities::IoPriority::IOPRIO_CLASS_IDLE),
|
||||
thread_priority_(QThread::Priority::IdlePriority),
|
||||
save_playcounts_to_files_(false),
|
||||
save_ratings_to_files_(false) {
|
||||
|
||||
@@ -69,7 +67,7 @@ SCollection::SCollection(Application *app, QObject *parent)
|
||||
backend()->moveToThread(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);
|
||||
|
||||
@@ -93,20 +91,16 @@ SCollection::~SCollection() {
|
||||
|
||||
void SCollection::Init() {
|
||||
|
||||
watcher_ = new CollectionWatcher(Song::Source_Collection);
|
||||
watcher_ = new CollectionWatcher(Song::Source::Collection);
|
||||
watcher_thread_ = new Thread(this);
|
||||
|
||||
#ifndef Q_OS_WIN32
|
||||
if (io_priority_ != Utilities::IoPriority::IOPRIO_CLASS_NONE) {
|
||||
watcher_thread_->SetIoPriority(io_priority_);
|
||||
}
|
||||
#endif
|
||||
watcher_thread_->SetIoPriority(Utilities::IoPriority::IOPRIO_CLASS_IDLE);
|
||||
|
||||
watcher_->moveToThread(watcher_thread_);
|
||||
|
||||
qLog(Debug) << watcher_ << "moved to thread" << watcher_thread_ << "with I/O priority" << io_priority_ << "and thread priority" << thread_priority_;
|
||||
qLog(Debug) << watcher_ << "moved to thread" << watcher_thread_;
|
||||
|
||||
watcher_thread_->start(thread_priority_);
|
||||
watcher_thread_->start(QThread::IdlePriority);
|
||||
|
||||
watcher_->set_backend(backend_);
|
||||
watcher_->set_task_manager(app_->task_manager());
|
||||
@@ -169,7 +163,9 @@ void SCollection::AbortScan() { watcher_->Stop(); }
|
||||
void SCollection::Rescan(const SongList &songs) {
|
||||
|
||||
qLog(Debug) << "Rescan" << songs.size() << "songs";
|
||||
if (!songs.isEmpty()) watcher_->RescanTracksAsync(songs);
|
||||
if (!songs.isEmpty()) {
|
||||
watcher_->RescanSongsAsync(songs);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -184,8 +180,6 @@ void SCollection::ReloadSettings() {
|
||||
|
||||
QSettings s;
|
||||
s.beginGroup(CollectionSettingsPage::kSettingsGroup);
|
||||
io_priority_ = static_cast<Utilities::IoPriority>(s.value("io_priority", Utilities::IOPRIO_CLASS_IDLE).toInt());
|
||||
thread_priority_ = static_cast<QThread::Priority>(s.value("thread_priority", QThread::Priority::IdlePriority).toInt());
|
||||
save_playcounts_to_files_ = s.value("save_playcounts", false).toBool();
|
||||
save_ratings_to_files_ = s.value("save_ratings", false).toBool();
|
||||
s.endGroup();
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -28,11 +28,10 @@
|
||||
#include <QList>
|
||||
#include <QHash>
|
||||
#include <QString>
|
||||
#include <QThread>
|
||||
|
||||
#include "core/song.h"
|
||||
#include "core/utilities.h"
|
||||
|
||||
class QThread;
|
||||
class Application;
|
||||
class Thread;
|
||||
class CollectionBackend;
|
||||
@@ -78,7 +77,7 @@ class SCollection : public QObject {
|
||||
|
||||
private slots:
|
||||
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);
|
||||
|
||||
signals:
|
||||
@@ -99,8 +98,6 @@ class SCollection : public QObject {
|
||||
|
||||
QList<QObject*> wait_for_exit_;
|
||||
|
||||
Utilities::IoPriority io_priority_;
|
||||
QThread::Priority thread_priority_;
|
||||
bool save_playcounts_to_files_;
|
||||
bool save_ratings_to_files_;
|
||||
};
|
||||
|
||||
@@ -50,8 +50,9 @@
|
||||
#include "core/sqlrow.h"
|
||||
#include "smartplaylists/smartplaylistsearch.h"
|
||||
|
||||
#include "directory.h"
|
||||
#include "collectiondirectory.h"
|
||||
#include "collectionbackend.h"
|
||||
#include "collectionfilteroptions.h"
|
||||
#include "collectionquery.h"
|
||||
#include "collectiontask.h"
|
||||
|
||||
@@ -59,7 +60,7 @@ CollectionBackend::CollectionBackend(QObject *parent)
|
||||
: CollectionBackendInterface(parent),
|
||||
db_(nullptr),
|
||||
task_manager_(nullptr),
|
||||
source_(Song::Source_Unknown),
|
||||
source_(Song::Source::Unknown),
|
||||
original_thread_(nullptr) {
|
||||
|
||||
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));
|
||||
}
|
||||
|
||||
void CollectionBackend::ResetStatisticsAsync(const int id) {
|
||||
QMetaObject::invokeMethod(this, "ResetStatistics", Qt::QueuedConnection, Q_ARG(int, id));
|
||||
void CollectionBackend::ResetPlayStatisticsAsync(const int id, const bool save_tags) {
|
||||
QMetaObject::invokeMethod(this, "ResetPlayStatistics", Qt::QueuedConnection, Q_ARG(int, id), Q_ARG(bool, save_tags));
|
||||
}
|
||||
|
||||
void CollectionBackend::ResetPlayStatisticsAsync(const QList<int> &id_list, const bool save_tags) {
|
||||
QMetaObject::invokeMethod(this, "ResetPlayStatistics", Qt::QueuedConnection, Q_ARG(QList<int>, id_list), Q_ARG(bool, save_tags));
|
||||
}
|
||||
|
||||
void CollectionBackend::LoadDirectories() {
|
||||
|
||||
DirectoryList dirs = GetAllDirectories();
|
||||
CollectionDirectoryList dirs = GetAllDirectories();
|
||||
|
||||
QMutexLocker l(db_->Mutex());
|
||||
QSqlDatabase db(db_->Connect());
|
||||
|
||||
for (const Directory &dir : dirs) {
|
||||
for (const CollectionDirectory &dir : dirs) {
|
||||
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());
|
||||
QSqlDatabase db(db_->Connect());
|
||||
|
||||
DirectoryList ret;
|
||||
CollectionDirectoryList ret;
|
||||
|
||||
SqlQuery q(db);
|
||||
q.prepare(QString("SELECT ROWID, path FROM %1").arg(dirs_table_));
|
||||
@@ -222,7 +227,7 @@ DirectoryList CollectionBackend::GetAllDirectories() {
|
||||
}
|
||||
|
||||
while (q.next()) {
|
||||
Directory dir;
|
||||
CollectionDirectory dir;
|
||||
dir.id = q.value(0).toInt();
|
||||
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());
|
||||
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);
|
||||
q.prepare(QString("SELECT path, mtime FROM %1 WHERE directory_id = :dir").arg(subdirs_table_));
|
||||
q.BindValue(":dir", id);
|
||||
if (!q.Exec()) {
|
||||
db_->ReportErrors(q);
|
||||
return SubdirectoryList();
|
||||
return CollectionSubdirectoryList();
|
||||
}
|
||||
|
||||
SubdirectoryList subdirs;
|
||||
CollectionSubdirectoryList subdirs;
|
||||
while (q.next()) {
|
||||
Subdirectory subdir;
|
||||
CollectionSubdirectory subdir;
|
||||
subdir.directory_id = id;
|
||||
subdir.path = q.value(0).toString();
|
||||
subdir.mtime = q.value(1).toLongLong();
|
||||
@@ -339,15 +344,15 @@ void CollectionBackend::AddDirectory(const QString &path) {
|
||||
return;
|
||||
}
|
||||
|
||||
Directory dir;
|
||||
CollectionDirectory dir;
|
||||
dir.path = canonical_path;
|
||||
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());
|
||||
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());
|
||||
QSqlDatabase db(db_->Connect());
|
||||
|
||||
ScopedTransaction transaction(&db);
|
||||
for (const Subdirectory &subdir : subdirs) {
|
||||
for (const CollectionSubdirectory &subdir : subdirs) {
|
||||
if (subdir.mtime == 0) {
|
||||
// Delete the subdirectory
|
||||
SqlQuery q(db);
|
||||
@@ -713,7 +718,7 @@ void CollectionBackend::UpdateSongsBySongID(const SongMap &new_songs) {
|
||||
|
||||
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);
|
||||
@@ -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());
|
||||
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.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);
|
||||
}
|
||||
|
||||
QStringList CollectionBackend::GetAllArtistsWithAlbums(const QueryOptions &opt) {
|
||||
QStringList CollectionBackend::GetAllArtistsWithAlbums(const CollectionFilterOptions &opt) {
|
||||
|
||||
QMutexLocker l(db_->Mutex());
|
||||
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);
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
SongList CollectionBackend::GetArtistSongs(const QString &effective_albumartist, const QueryOptions &opt) {
|
||||
SongList CollectionBackend::GetArtistSongs(const QString &effective_albumartist, const CollectionFilterOptions &opt) {
|
||||
|
||||
QSqlDatabase db(db_->Connect());
|
||||
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());
|
||||
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());
|
||||
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);
|
||||
}
|
||||
|
||||
SongList CollectionBackend::GetCompilationSongs(const QString &album, const QueryOptions &opt) {
|
||||
SongList CollectionBackend::GetCompilationSongs(const QString &album, const CollectionFilterOptions &opt) {
|
||||
|
||||
QMutexLocker l(db_->Mutex());
|
||||
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());
|
||||
QSqlDatabase db(db_->Connect());
|
||||
@@ -1516,7 +1521,7 @@ CollectionBackend::Album CollectionBackend::GetAlbumArt(const QString &effective
|
||||
ret.album = album;
|
||||
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");
|
||||
if (!effective_albumartist.isEmpty()) {
|
||||
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;
|
||||
|
||||
ResetPlayStatistics(QList<int>() << id, save_tags);
|
||||
|
||||
}
|
||||
|
||||
void CollectionBackend::ResetPlayStatistics(const QList<int> &id_list, const bool save_tags) {
|
||||
|
||||
if (id_list.isEmpty()) return;
|
||||
|
||||
QStringList id_str_list;
|
||||
id_str_list.reserve(id_list.count());
|
||||
for (const int id : id_list) {
|
||||
id_str_list << QString::number(id);
|
||||
}
|
||||
|
||||
const bool success = ResetPlayStatistics(id_str_list);
|
||||
if (success) {
|
||||
const SongList songs = GetSongsById(id_list);
|
||||
emit SongsStatisticsChanged(songs, save_tags);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
bool CollectionBackend::ResetPlayStatistics(const QStringList &id_str_list) {
|
||||
|
||||
if (id_str_list.isEmpty()) return false;
|
||||
|
||||
QMutexLocker l(db_->Mutex());
|
||||
QSqlDatabase db(db_->Connect());
|
||||
|
||||
SqlQuery q(db);
|
||||
q.prepare(QString("UPDATE %1 SET playcount = 0, skipcount = 0, lastplayed = -1 WHERE ROWID = :id").arg(songs_table_));
|
||||
q.BindValue(":id", id);
|
||||
q.prepare(QString("UPDATE %1 SET playcount = 0, skipcount = 0, lastplayed = -1 WHERE ROWID IN (:ids)").arg(songs_table_));
|
||||
q.BindValue(":ids", id_str_list.join(","));
|
||||
if (!q.Exec()) {
|
||||
db_->ReportErrors(q);
|
||||
return;
|
||||
return false;
|
||||
}
|
||||
|
||||
Song new_song = GetSongById(id, db);
|
||||
emit SongsStatisticsChanged(SongList() << new_song);
|
||||
return true;
|
||||
|
||||
}
|
||||
|
||||
@@ -1863,7 +1893,7 @@ SongList CollectionBackend::SmartPlaylistsFindSongs(const SmartPlaylistSearch &s
|
||||
SongList CollectionBackend::SmartPlaylistsGetAllSongs() {
|
||||
|
||||
// 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);
|
||||
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/sqlquery.h"
|
||||
#include "collectionfilteroptions.h"
|
||||
#include "collectionquery.h"
|
||||
#include "directory.h"
|
||||
#include "collectiondirectory.h"
|
||||
|
||||
class QThread;
|
||||
class TaskManager;
|
||||
@@ -53,7 +54,7 @@ class CollectionBackendInterface : public QObject {
|
||||
explicit CollectionBackendInterface(QObject *parent = nullptr) : QObject(parent) {}
|
||||
|
||||
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_artist(_album_artist),
|
||||
album(_album),
|
||||
@@ -90,23 +91,23 @@ class CollectionBackendInterface : public QObject {
|
||||
|
||||
virtual SongList FindSongsInDirectory(const int id) = 0;
|
||||
virtual SongList SongsWithMissingFingerprint(const int id) = 0;
|
||||
virtual SubdirectoryList SubdirsInDirectory(const int id) = 0;
|
||||
virtual DirectoryList GetAllDirectories() = 0;
|
||||
virtual CollectionSubdirectoryList SubdirsInDirectory(const int id) = 0;
|
||||
virtual CollectionDirectoryList GetAllDirectories() = 0;
|
||||
virtual void ChangeDirPath(const int id, const QString &old_path, const QString &new_path) = 0;
|
||||
|
||||
virtual SongList GetAllSongs() = 0;
|
||||
|
||||
virtual QStringList GetAllArtists(const QueryOptions &opt = QueryOptions()) = 0;
|
||||
virtual QStringList GetAllArtistsWithAlbums(const QueryOptions &opt = QueryOptions()) = 0;
|
||||
virtual SongList GetArtistSongs(const QString &effective_albumartist, const QueryOptions &opt = QueryOptions()) = 0;
|
||||
virtual SongList GetAlbumSongs(const QString &effective_albumartist, const QString &album, const QueryOptions &opt = QueryOptions()) = 0;
|
||||
virtual SongList GetSongsByAlbum(const QString &album, const QueryOptions &opt = QueryOptions()) = 0;
|
||||
virtual QStringList GetAllArtists(const CollectionFilterOptions &opt = CollectionFilterOptions()) = 0;
|
||||
virtual QStringList GetAllArtistsWithAlbums(const CollectionFilterOptions &opt = CollectionFilterOptions()) = 0;
|
||||
virtual SongList GetArtistSongs(const QString &effective_albumartist, const CollectionFilterOptions &opt = CollectionFilterOptions()) = 0;
|
||||
virtual SongList GetAlbumSongs(const QString &effective_albumartist, const QString &album, const CollectionFilterOptions &opt = CollectionFilterOptions()) = 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 GetAlbumsByArtist(const QString &artist, const QueryOptions &opt = QueryOptions()) = 0;
|
||||
virtual AlbumList GetCompilationAlbums(const QueryOptions &opt = QueryOptions()) = 0;
|
||||
virtual AlbumList GetAllAlbums(const CollectionFilterOptions &opt = CollectionFilterOptions()) = 0;
|
||||
virtual AlbumList GetAlbumsByArtist(const QString &artist, const CollectionFilterOptions &opt = CollectionFilterOptions()) = 0;
|
||||
virtual AlbumList GetCompilationAlbums(const CollectionFilterOptions &opt = CollectionFilterOptions()) = 0;
|
||||
|
||||
virtual 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;
|
||||
@@ -124,7 +125,7 @@ class CollectionBackendInterface : public QObject {
|
||||
virtual Song GetSongByUrl(const QUrl &url, const qint64 beginning = 0) = 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 {
|
||||
@@ -159,24 +160,24 @@ class CollectionBackend : public CollectionBackendInterface {
|
||||
|
||||
SongList FindSongsInDirectory(const int id) override;
|
||||
SongList SongsWithMissingFingerprint(const int id) override;
|
||||
SubdirectoryList SubdirsInDirectory(const int id) override;
|
||||
DirectoryList GetAllDirectories() override;
|
||||
CollectionSubdirectoryList SubdirsInDirectory(const int id) override;
|
||||
CollectionDirectoryList GetAllDirectories() override;
|
||||
void ChangeDirPath(const int id, const QString &old_path, const QString &new_path) override;
|
||||
|
||||
SongList GetAllSongs() override;
|
||||
|
||||
QStringList GetAll(const QString &column, const QueryOptions &opt = QueryOptions());
|
||||
QStringList GetAllArtists(const QueryOptions &opt = QueryOptions()) override;
|
||||
QStringList GetAllArtistsWithAlbums(const QueryOptions &opt = QueryOptions()) override;
|
||||
SongList GetArtistSongs(const QString &effective_albumartist, const QueryOptions &opt = QueryOptions()) override;
|
||||
SongList GetAlbumSongs(const QString &effective_albumartist, const QString &album, const QueryOptions &opt = QueryOptions()) override;
|
||||
SongList GetSongsByAlbum(const QString &album, const QueryOptions &opt = QueryOptions()) override;
|
||||
QStringList GetAll(const QString &column, const CollectionFilterOptions &filter_options = CollectionFilterOptions());
|
||||
QStringList GetAllArtists(const CollectionFilterOptions &opt = CollectionFilterOptions()) override;
|
||||
QStringList GetAllArtistsWithAlbums(const CollectionFilterOptions &opt = CollectionFilterOptions()) override;
|
||||
SongList GetArtistSongs(const QString &effective_albumartist, const CollectionFilterOptions &opt = CollectionFilterOptions()) override;
|
||||
SongList GetAlbumSongs(const QString &effective_albumartist, const QString &album, const CollectionFilterOptions &opt = CollectionFilterOptions()) 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 GetCompilationAlbums(const QueryOptions &opt = QueryOptions()) override;
|
||||
AlbumList GetAlbumsByArtist(const QString &artist, const QueryOptions &opt = QueryOptions()) override;
|
||||
AlbumList GetAllAlbums(const CollectionFilterOptions &opt = CollectionFilterOptions()) override;
|
||||
AlbumList GetCompilationAlbums(const CollectionFilterOptions &opt = CollectionFilterOptions()) override;
|
||||
AlbumList GetAlbumsByArtist(const QString &artist, const CollectionFilterOptions &opt = CollectionFilterOptions()) override;
|
||||
|
||||
void UpdateManualAlbumArtAsync(const QString &effective_albumartist, const QString &album, const QUrl &cover_url, const bool clear_art_automatic = false) override;
|
||||
void UpdateAutomaticAlbumArtAsync(const QString &effective_albumartist, const QString &album, const QUrl &cover_url, const bool clear_art_manual = false) override;
|
||||
@@ -192,14 +193,15 @@ class CollectionBackend : public CollectionBackendInterface {
|
||||
Song GetSongByUrl(const QUrl &url, qint64 beginning = 0) 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, SongMap &songs);
|
||||
|
||||
void IncrementPlayCountAsync(const int id);
|
||||
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();
|
||||
|
||||
@@ -228,20 +230,22 @@ class CollectionBackend : public CollectionBackendInterface {
|
||||
void UpdateMTimesOnly(const SongList &songs);
|
||||
void DeleteSongs(const SongList &songs);
|
||||
void MarkSongsUnavailable(const SongList &songs, const bool unavailable = true);
|
||||
void AddOrUpdateSubdirs(const SubdirectoryList &subdirs);
|
||||
void AddOrUpdateSubdirs(const CollectionSubdirectoryList &subdirs);
|
||||
void CompilationsNeedUpdating();
|
||||
void UpdateManualAlbumArt(const QString &effective_albumartist, const QString &album, const QUrl &cover_url, const bool clear_art_automatic = false);
|
||||
void UpdateAutomaticAlbumArt(const QString &effective_albumartist, const QString &album, const QUrl &cover_url, const bool clear_art_manual = false);
|
||||
void ForceCompilation(const QString &album, const QList<QString> &artists, const bool on);
|
||||
void IncrementPlayCount(const int id);
|
||||
void IncrementSkipCount(const int id, const float progress);
|
||||
void ResetStatistics(const int id);
|
||||
void ResetPlayStatistics(const int id, const bool save_tags = false);
|
||||
void ResetPlayStatistics(const QList<int> &id_list, const bool save_tags = false);
|
||||
bool ResetPlayStatistics(const QStringList &id_str_list);
|
||||
void DeleteAll();
|
||||
void SongPathChanged(const Song &song, const QFileInfo &new_file, const std::optional<int> new_collection_directory_id);
|
||||
|
||||
SongList GetSongsBy(const QString &artist, const QString &album, const QString &title);
|
||||
void UpdateLastPlayed(const QString &artist, const QString &album, const QString &title, const qint64 lastplayed);
|
||||
void UpdatePlayCount(const QString &artist, const QString &title, const int playcount);
|
||||
void 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 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);
|
||||
|
||||
signals:
|
||||
void DirectoryDiscovered(Directory, SubdirectoryList);
|
||||
void DirectoryDeleted(Directory);
|
||||
void DirectoryDiscovered(CollectionDirectory, CollectionSubdirectoryList);
|
||||
void DirectoryDeleted(CollectionDirectory);
|
||||
|
||||
void SongsDiscovered(SongList);
|
||||
void SongsDeleted(SongList);
|
||||
void SongsStatisticsChanged(SongList);
|
||||
void SongsStatisticsChanged(SongList, bool = false);
|
||||
|
||||
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);
|
||||
AlbumList GetAlbums(const QString &artist, const QString &album_artist, const bool compilation_required = false, const QueryOptions &opt = QueryOptions());
|
||||
AlbumList GetAlbums(const QString &artist, const bool compilation_required, const QueryOptions &opt = QueryOptions());
|
||||
SubdirectoryList SubdirsInDirectory(const int id, QSqlDatabase &db);
|
||||
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 CollectionFilterOptions &opt = CollectionFilterOptions());
|
||||
CollectionSubdirectoryList SubdirsInDirectory(const int id, QSqlDatabase &db);
|
||||
|
||||
Song GetSongById(const int id, 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/iconloader.h"
|
||||
#include "core/musicstorage.h"
|
||||
#include "core/utilities.h"
|
||||
#include "directory.h"
|
||||
#include "utilities/diskutils.h"
|
||||
#include "collectiondirectory.h"
|
||||
#include "collectionbackend.h"
|
||||
#include "collectiondirectorymodel.h"
|
||||
|
||||
@@ -46,7 +46,7 @@ CollectionDirectoryModel::CollectionDirectoryModel(CollectionBackend *backend, Q
|
||||
|
||||
CollectionDirectoryModel::~CollectionDirectoryModel() = default;
|
||||
|
||||
void CollectionDirectoryModel::DirectoryDiscovered(const Directory &dir) {
|
||||
void CollectionDirectoryModel::DirectoryDiscovered(const CollectionDirectory &dir) {
|
||||
|
||||
QStandardItem *item = new QStandardItem(dir.path);
|
||||
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) {
|
||||
if (item(i, 0)->data(kIdRole).toInt() == dir.id) {
|
||||
@@ -80,7 +80,7 @@ void CollectionDirectoryModel::RemoveDirectory(const QModelIndex &idx) {
|
||||
|
||||
if (!backend_ || !idx.isValid()) return;
|
||||
|
||||
Directory dir;
|
||||
CollectionDirectory dir;
|
||||
dir.path = idx.data().toString();
|
||||
dir.id = idx.data(kIdRole).toInt();
|
||||
|
||||
|
||||
@@ -34,7 +34,7 @@
|
||||
|
||||
class QModelIndex;
|
||||
|
||||
struct Directory;
|
||||
struct CollectionDirectory;
|
||||
class CollectionBackend;
|
||||
class MusicStorage;
|
||||
|
||||
@@ -53,8 +53,8 @@ class CollectionDirectoryModel : public QStandardItemModel {
|
||||
|
||||
private slots:
|
||||
// To be called by the backend
|
||||
void DirectoryDiscovered(const Directory &directories);
|
||||
void DirectoryDeleted(const Directory &directories);
|
||||
void DirectoryDiscovered(const CollectionDirectory &directories);
|
||||
void DirectoryDeleted(const CollectionDirectory &directories);
|
||||
|
||||
private:
|
||||
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/song.h"
|
||||
#include "core/logging.h"
|
||||
#include "collectionfilteroptions.h"
|
||||
#include "collectionmodel.h"
|
||||
#include "collectionquery.h"
|
||||
#include "savedgroupingmanager.h"
|
||||
@@ -53,6 +54,7 @@
|
||||
#include "groupbydialog.h"
|
||||
#include "ui_collectionfilterwidget.h"
|
||||
#include "widgets/qsearchfield.h"
|
||||
#include "settings/collectionsettingspage.h"
|
||||
#include "settings/appearancesettingspage.h"
|
||||
|
||||
CollectionFilterWidget::CollectionFilterWidget(QWidget *parent)
|
||||
@@ -67,7 +69,7 @@ CollectionFilterWidget::CollectionFilterWidget(QWidget *parent)
|
||||
group_by_group_(nullptr),
|
||||
filter_delay_(new QTimer(this)),
|
||||
filter_applies_to_model_(true),
|
||||
delay_behaviour_(DelayedOnLargeLibraries) {
|
||||
delay_behaviour_(DelayBehaviour::DelayedOnLargeLibraries) {
|
||||
|
||||
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 (version == 1) {
|
||||
model_->SetGroupBy(CollectionModel::Grouping(
|
||||
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()),
|
||||
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(1), static_cast<int>(CollectionModel::GroupBy::AlbumArtist)).toInt()),
|
||||
static_cast<CollectionModel::GroupBy>(s.value(group_by_key(2), static_cast<int>(CollectionModel::GroupBy::AlbumDisc)).toInt()),
|
||||
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 {
|
||||
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();
|
||||
}
|
||||
@@ -271,24 +274,24 @@ QActionGroup *CollectionFilterWidget::CreateGroupByActions(const QString &saved_
|
||||
|
||||
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 - 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 - Disc"), parent, CollectionModel::Grouping(CollectionModel::GroupBy_AlbumArtist, CollectionModel::GroupBy_YearAlbumDisc)));
|
||||
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/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 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/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/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/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 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/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 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 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 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 Album"), parent, CollectionModel::Grouping(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);
|
||||
sep1->setSeparator(true);
|
||||
@@ -333,7 +336,7 @@ QAction *CollectionFilterWidget::CreateGroupByAction(const QString &text, QObjec
|
||||
QAction *ret = new QAction(text, parent);
|
||||
ret->setCheckable(true);
|
||||
|
||||
if (grouping.first != CollectionModel::GroupBy_None) {
|
||||
if (grouping.first != CollectionModel::GroupBy::None) {
|
||||
ret->setProperty("group_by", QVariant::fromValue(grouping));
|
||||
}
|
||||
|
||||
@@ -455,12 +458,12 @@ void CollectionFilterWidget::SetFilterHint(const QString &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->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,
|
||||
// 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.
|
||||
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) {
|
||||
filter_delay_->start();
|
||||
|
||||
@@ -32,6 +32,7 @@
|
||||
#include <QString>
|
||||
|
||||
#include "collectionquery.h"
|
||||
#include "collectionqueryoptions.h"
|
||||
#include "collectionmodel.h"
|
||||
|
||||
class QTimer;
|
||||
@@ -53,7 +54,7 @@ class CollectionFilterWidget : public QWidget {
|
||||
|
||||
static const int kFilterDelay = 500; // msec
|
||||
|
||||
enum DelayBehaviour {
|
||||
enum class DelayBehaviour {
|
||||
AlwaysInstant,
|
||||
DelayedOnLargeLibraries,
|
||||
AlwaysDelayed,
|
||||
@@ -88,7 +89,7 @@ class CollectionFilterWidget : public QWidget {
|
||||
|
||||
public slots:
|
||||
void UpdateGroupByActions();
|
||||
void SetQueryMode(QueryOptions::QueryMode query_mode);
|
||||
void SetFilterMode(CollectionFilterOptions::FilterMode filter_mode);
|
||||
void FocusOnFilter(QKeyEvent *e);
|
||||
|
||||
signals:
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -49,7 +49,9 @@
|
||||
#include "core/song.h"
|
||||
#include "core/sqlrow.h"
|
||||
#include "covermanager/albumcoverloader.h"
|
||||
#include "collectionfilteroptions.h"
|
||||
#include "collectionquery.h"
|
||||
#include "collectionqueryoptions.h"
|
||||
#include "collectionitem.h"
|
||||
#include "covermanager/albumcoverloaderoptions.h"
|
||||
|
||||
@@ -81,34 +83,34 @@ class CollectionModel : public SimpleTreeModel<CollectionItem> {
|
||||
};
|
||||
|
||||
// These values get saved in QSettings - don't change them
|
||||
enum GroupBy {
|
||||
GroupBy_None = 0,
|
||||
GroupBy_AlbumArtist = 1,
|
||||
GroupBy_Artist = 2,
|
||||
GroupBy_Album = 3,
|
||||
GroupBy_AlbumDisc = 4,
|
||||
GroupBy_YearAlbum = 5,
|
||||
GroupBy_YearAlbumDisc = 6,
|
||||
GroupBy_OriginalYearAlbum = 7,
|
||||
GroupBy_OriginalYearAlbumDisc = 8,
|
||||
GroupBy_Disc = 9,
|
||||
GroupBy_Year = 10,
|
||||
GroupBy_OriginalYear = 11,
|
||||
GroupBy_Genre = 12,
|
||||
GroupBy_Composer = 13,
|
||||
GroupBy_Performer = 14,
|
||||
GroupBy_Grouping = 15,
|
||||
GroupBy_FileType = 16,
|
||||
GroupBy_Format = 17,
|
||||
GroupBy_Samplerate = 18,
|
||||
GroupBy_Bitdepth = 19,
|
||||
GroupBy_Bitrate = 20,
|
||||
enum class GroupBy {
|
||||
None = 0,
|
||||
AlbumArtist = 1,
|
||||
Artist = 2,
|
||||
Album = 3,
|
||||
AlbumDisc = 4,
|
||||
YearAlbum = 5,
|
||||
YearAlbumDisc = 6,
|
||||
OriginalYearAlbum = 7,
|
||||
OriginalYearAlbumDisc = 8,
|
||||
Disc = 9,
|
||||
Year = 10,
|
||||
OriginalYear = 11,
|
||||
Genre = 12,
|
||||
Composer = 13,
|
||||
Performer = 14,
|
||||
Grouping = 15,
|
||||
FileType = 16,
|
||||
Format = 17,
|
||||
Samplerate = 18,
|
||||
Bitdepth = 19,
|
||||
Bitrate = 20,
|
||||
GroupByCount = 21,
|
||||
};
|
||||
Q_ENUM(GroupBy)
|
||||
|
||||
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) {}
|
||||
|
||||
GroupBy first;
|
||||
@@ -179,9 +181,9 @@ class CollectionModel : public SimpleTreeModel<CollectionItem> {
|
||||
quint64 icon_cache_disk_size() { return sIconCache->cacheSize(); }
|
||||
|
||||
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; }
|
||||
|
||||
@@ -203,9 +205,9 @@ class CollectionModel : public SimpleTreeModel<CollectionItem> {
|
||||
void GroupingChanged(CollectionModel::Grouping g, bool separate_albums_by_grouping);
|
||||
|
||||
public slots:
|
||||
void SetFilterAge(const int age);
|
||||
void SetFilterText(const QString &text);
|
||||
void SetFilterQueryMode(QueryOptions::QueryMode query_mode);
|
||||
void SetFilterMode(CollectionFilterOptions::FilterMode filter_mode);
|
||||
void SetFilterAge(const int filter_age);
|
||||
void SetFilterText(const QString &filter_text);
|
||||
|
||||
void Init(const bool async = true);
|
||||
void Reset();
|
||||
@@ -232,20 +234,21 @@ class CollectionModel : public SimpleTreeModel<CollectionItem> {
|
||||
void AlbumCoverLoaded(const quint64 id, const AlbumCoverLoaderResult &result);
|
||||
|
||||
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.
|
||||
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);
|
||||
|
||||
bool HasCompilations(const QSqlDatabase &db, const CollectionQuery &query);
|
||||
bool HasCompilations(const QSqlDatabase &db, const CollectionFilterOptions &filter_options, const CollectionQueryOptions &query_options);
|
||||
|
||||
void BeginReset();
|
||||
|
||||
// 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.
|
||||
// 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 FilterQuery(const GroupBy group_by, const bool separate_albums_by_grouping, CollectionItem *item, CollectionQuery *q);
|
||||
static void SetQueryColumnSpec(const GroupBy group_by, const bool separate_albums_by_grouping, CollectionQueryOptions *query_options);
|
||||
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.
|
||||
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_album_count_;
|
||||
|
||||
QueryOptions query_options_;
|
||||
CollectionFilterOptions filter_options_;
|
||||
Grouping group_by_;
|
||||
bool separate_albums_by_grouping_;
|
||||
|
||||
|
||||
@@ -29,12 +29,12 @@
|
||||
|
||||
class SqlRow;
|
||||
|
||||
CollectionPlaylistItem::CollectionPlaylistItem() : PlaylistItem(Song::Source_Collection) {
|
||||
song_.set_source(Song::Source_Collection);
|
||||
CollectionPlaylistItem::CollectionPlaylistItem() : PlaylistItem(Song::Source::Collection) {
|
||||
song_.set_source(Song::Source::Collection);
|
||||
}
|
||||
|
||||
CollectionPlaylistItem::CollectionPlaylistItem(const Song &song) : PlaylistItem(Song::Source_Collection), song_(song) {
|
||||
song_.set_source(Song::Source_Collection);
|
||||
CollectionPlaylistItem::CollectionPlaylistItem(const Song &song) : PlaylistItem(Song::Source::Collection), song_(song) {
|
||||
song_.set_source(Song::Source::Collection);
|
||||
}
|
||||
|
||||
QUrl CollectionPlaylistItem::Url() const { return song_.url(); }
|
||||
@@ -50,7 +50,7 @@ bool CollectionPlaylistItem::InitFromQuery(const SqlRow &query) {
|
||||
|
||||
// Rows from the songs tables come first
|
||||
song_.InitFromQuery(query, true);
|
||||
song_.set_source(Song::Source_Collection);
|
||||
song_.set_source(Song::Source::Collection);
|
||||
return song_.is_valid();
|
||||
|
||||
}
|
||||
|
||||
@@ -52,7 +52,7 @@ class CollectionPlaylistItem : public PlaylistItem {
|
||||
|
||||
protected:
|
||||
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:
|
||||
Song song_;
|
||||
|
||||
@@ -31,16 +31,16 @@
|
||||
#include <QRegularExpression>
|
||||
#include <QSqlDatabase>
|
||||
#include <QSqlQuery>
|
||||
#include <QSqlError>
|
||||
|
||||
#include "core/logging.h"
|
||||
#include "core/sqlquery.h"
|
||||
#include "core/song.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 QueryOptions &options)
|
||||
CollectionQuery::CollectionQuery(const QSqlDatabase &db, const QString &songs_table, const QString &fts_table, const CollectionFilterOptions &filter_options)
|
||||
: QSqlQuery(db),
|
||||
songs_table_(songs_table),
|
||||
fts_table_(fts_table),
|
||||
@@ -49,7 +49,7 @@ CollectionQuery::CollectionQuery(const QSqlDatabase &db, const QString &songs_ta
|
||||
duplicates_only_(false),
|
||||
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:
|
||||
// 1) Append * to all tokens.
|
||||
// 2) Prefix "fts" to column names.
|
||||
@@ -57,9 +57,9 @@ CollectionQuery::CollectionQuery(const QSqlDatabase &db, const QString &songs_ta
|
||||
|
||||
// Split on whitespace
|
||||
#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
|
||||
QStringList tokens(options.filter().split(QRegularExpression("\\s+"), QString::SkipEmptyParts));
|
||||
QStringList tokens(filter_options.filter_text().split(QRegularExpression("\\s+"), QString::SkipEmptyParts));
|
||||
#endif
|
||||
QString query;
|
||||
for (QString token : tokens) {
|
||||
@@ -100,49 +100,40 @@ CollectionQuery::CollectionQuery(const QSqlDatabase &db, const QString &songs_ta
|
||||
}
|
||||
}
|
||||
|
||||
if (options.max_age() != -1) {
|
||||
qint64 cutoff = QDateTime::currentDateTime().toSecsSinceEpoch() - options.max_age();
|
||||
if (filter_options.max_age() != -1) {
|
||||
qint64 cutoff = QDateTime::currentDateTime().toSecsSinceEpoch() - filter_options.max_age();
|
||||
|
||||
where_clauses_ << "ctime > ?";
|
||||
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.
|
||||
// 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
|
||||
// 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.
|
||||
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 ='')";
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
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) {
|
||||
|
||||
// Ignore 'literal' for IN
|
||||
if (op.compare("IN", Qt::CaseInsensitive) == 0) {
|
||||
QStringList values = value.toStringList();
|
||||
QStringList final;
|
||||
final.reserve(values.count());
|
||||
QStringList final_values;
|
||||
final_values.reserve(values.count());
|
||||
for (const QString &single_value : values) {
|
||||
final.append("?");
|
||||
final_values.append("?");
|
||||
bound_values_ << single_value;
|
||||
}
|
||||
|
||||
where_clauses_ << QString("%1 IN (" + final.join(",") + ")").arg(column);
|
||||
where_clauses_ << QString("%1 IN (" + final_values.join(",") + ")").arg(column);
|
||||
}
|
||||
else {
|
||||
// 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() {
|
||||
|
||||
QString sql;
|
||||
@@ -213,32 +213,17 @@ bool CollectionQuery::Exec() {
|
||||
sql.replace("%fts_table_noprefix", fts_table_.section('.', -1, -1));
|
||||
sql.replace("%fts_table", fts_table_);
|
||||
|
||||
prepare(sql);
|
||||
QSqlQuery::prepare(sql);
|
||||
|
||||
// Bind 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); }
|
||||
|
||||
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;
|
||||
|
||||
}
|
||||
QVariant CollectionQuery::Value(const int column) const { return QSqlQuery::value(column); }
|
||||
|
||||
@@ -28,75 +28,23 @@
|
||||
#include <QVariant>
|
||||
#include <QString>
|
||||
#include <QStringList>
|
||||
#include <QMap>
|
||||
#include <QSqlDatabase>
|
||||
#include <QSqlQuery>
|
||||
|
||||
class Song;
|
||||
|
||||
// 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_;
|
||||
};
|
||||
#include "collectionfilteroptions.h"
|
||||
#include "collectionqueryoptions.h"
|
||||
|
||||
class CollectionQuery : public QSqlQuery {
|
||||
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).
|
||||
void SetColumnSpec(const QString &spec) { column_spec_ = spec; }
|
||||
|
||||
// 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);
|
||||
QVariant Value(const int column) const;
|
||||
QVariant value(const int column) const { return Value(column); }
|
||||
|
||||
bool Exec();
|
||||
bool exec() { return QSqlQuery::exec(); }
|
||||
|
||||
bool Next();
|
||||
QVariant Value(const int column) const;
|
||||
|
||||
QString column_spec() const { return column_spec_; }
|
||||
QString order_by() const { return order_by_; }
|
||||
@@ -107,6 +55,24 @@ class CollectionQuery : public QSqlQuery {
|
||||
bool duplicates_only() const { return duplicates_only_; }
|
||||
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:
|
||||
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/iconloader.h"
|
||||
#include "core/mimedata.h"
|
||||
#include "core/utilities.h"
|
||||
#include "core/musicstorage.h"
|
||||
#include "core/deletefiles.h"
|
||||
#include "utilities/filemanagerutils.h"
|
||||
#include "collection.h"
|
||||
#include "collectionbackend.h"
|
||||
#include "collectiondirectorymodel.h"
|
||||
@@ -699,7 +699,7 @@ void CollectionView::DeleteFilesFinished(const SongList &songs_with_errors) {
|
||||
if (songs_with_errors.isEmpty()) return;
|
||||
|
||||
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
|
||||
|
||||
}
|
||||
|
||||
@@ -46,11 +46,11 @@
|
||||
|
||||
#include "core/filesystemwatcherinterface.h"
|
||||
#include "core/logging.h"
|
||||
#include "core/timeconstants.h"
|
||||
#include "core/tagreaderclient.h"
|
||||
#include "core/taskmanager.h"
|
||||
#include "core/imageutils.h"
|
||||
#include "directory.h"
|
||||
#include "utilities/imageutils.h"
|
||||
#include "utilities/timeconstants.h"
|
||||
#include "collectiondirectory.h"
|
||||
#include "collectionbackend.h"
|
||||
#include "collectionwatcher.h"
|
||||
#include "playlistparsers/cueparser.h"
|
||||
@@ -78,13 +78,12 @@ CollectionWatcher::CollectionWatcher(Song::Source source, QObject *parent)
|
||||
scan_on_startup_(true),
|
||||
monitor_(true),
|
||||
song_tracking_(false),
|
||||
mark_songs_unavailable_(source_ == Song::Source_Collection),
|
||||
mark_songs_unavailable_(source_ == Song::Source::Collection),
|
||||
expire_unavailable_songs_days_(60),
|
||||
overwrite_playcount_(false),
|
||||
overwrite_rating_(false),
|
||||
stop_requested_(false),
|
||||
abort_requested_(false),
|
||||
rescan_in_progress_(false),
|
||||
rescan_timer_(new QTimer(this)),
|
||||
periodic_scan_timer_(new QTimer(this)),
|
||||
rescan_paused_(false),
|
||||
@@ -143,7 +142,7 @@ void CollectionWatcher::ReloadSettings() {
|
||||
scan_on_startup_ = s.value("startup_scan", true).toBool();
|
||||
monitor_ = s.value("monitor", true).toBool();
|
||||
QStringList filters = s.value("cover_art_patterns", QStringList() << "front" << "cover").toStringList();
|
||||
if (source_ == Song::Source_Collection) {
|
||||
if (source_ == Song::Source::Collection) {
|
||||
song_tracking_ = s.value("song_tracking", false).toBool();
|
||||
mark_songs_unavailable_ = song_tracking_ ? true : s.value("mark_songs_unavailable", true).toBool();
|
||||
}
|
||||
@@ -167,15 +166,15 @@ void CollectionWatcher::ReloadSettings() {
|
||||
}
|
||||
else if (monitor_ && !was_monitoring_before) {
|
||||
// Add all directories to all QFileSystemWatchers again
|
||||
for (const Directory &dir : std::as_const(watched_dirs_)) {
|
||||
SubdirectoryList subdirs = backend_->SubdirsInDirectory(dir.id);
|
||||
for (const Subdirectory &subdir : subdirs) {
|
||||
for (const CollectionDirectory &dir : std::as_const(watched_dirs_)) {
|
||||
CollectionSubdirectoryList subdirs = backend_->SubdirsInDirectory(dir.id);
|
||||
for (const CollectionSubdirectory &subdir : subdirs) {
|
||||
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();
|
||||
}
|
||||
else if (!mark_songs_unavailable_ && periodic_scan_timer_->isActive()) {
|
||||
@@ -239,7 +238,7 @@ void CollectionWatcher::ScanTransaction::AddToProgressMax(const quint64 n) {
|
||||
void CollectionWatcher::ScanTransaction::CommitNewOrUpdatedSongs() {
|
||||
|
||||
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);
|
||||
}
|
||||
else {
|
||||
@@ -272,7 +271,7 @@ void CollectionWatcher::ScanTransaction::CommitNewOrUpdatedSongs() {
|
||||
touched_subdirs.clear();
|
||||
}
|
||||
|
||||
for (const Subdirectory &subdir : deleted_subdirs) {
|
||||
for (const CollectionSubdirectory &subdir : deleted_subdirs) {
|
||||
if (watcher_->watched_dirs_.contains(dir_)) {
|
||||
watcher_->RemoveWatch(watcher_->watched_dirs_[dir_], subdir);
|
||||
}
|
||||
@@ -281,7 +280,7 @@ void CollectionWatcher::ScanTransaction::CommitNewOrUpdatedSongs() {
|
||||
|
||||
if (watcher_->monitor_) {
|
||||
// Watch the new subdirectories
|
||||
for (const Subdirectory &subdir : new_subdirs) {
|
||||
for (const CollectionSubdirectory &subdir : new_subdirs) {
|
||||
if (watcher_->watched_dirs_.contains(dir_)) {
|
||||
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_dirty_ = false;
|
||||
@@ -342,18 +341,18 @@ bool CollectionWatcher::ScanTransaction::HasSeenSubdir(const QString &path) {
|
||||
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_) {
|
||||
SetKnownSubdirs(watcher_->backend_->SubdirsInDirectory(dir_));
|
||||
}
|
||||
|
||||
SubdirectoryList ret;
|
||||
for (const Subdirectory &subdir : known_subdirs_) {
|
||||
CollectionSubdirectoryList ret;
|
||||
for (const CollectionSubdirectory &subdir : known_subdirs_) {
|
||||
if (subdir.path.left(subdir.path.lastIndexOf(QDir::separator())) == path && subdir.mtime != 0) {
|
||||
ret << subdir;
|
||||
}
|
||||
@@ -363,7 +362,7 @@ SubdirectoryList CollectionWatcher::ScanTransaction::GetImmediateSubdirs(const Q
|
||||
|
||||
}
|
||||
|
||||
SubdirectoryList CollectionWatcher::ScanTransaction::GetAllSubdirs() {
|
||||
CollectionSubdirectoryList CollectionWatcher::ScanTransaction::GetAllSubdirs() {
|
||||
|
||||
if (known_subdirs_dirty_) {
|
||||
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;
|
||||
|
||||
@@ -385,7 +384,7 @@ void CollectionWatcher::AddDirectory(const Directory &dir, const SubdirectoryLis
|
||||
const quint64 files_count = FilesCountForPath(&transaction, dir.path);
|
||||
transaction.SetKnownSubdirs(subdirs);
|
||||
transaction.AddToProgressMax(files_count);
|
||||
ScanSubdirectory(dir.path, Subdirectory(), files_count, &transaction);
|
||||
ScanSubdirectory(dir.path, CollectionSubdirectory(), files_count, &transaction);
|
||||
last_scan_time_ = QDateTime::currentDateTime().toSecsSinceEpoch();
|
||||
}
|
||||
else {
|
||||
@@ -395,7 +394,7 @@ void CollectionWatcher::AddDirectory(const Directory &dir, const SubdirectoryLis
|
||||
const quint64 files_count = FilesCountForSubdirs(&transaction, subdirs, subdir_files_count);
|
||||
transaction.SetKnownSubdirs(subdirs);
|
||||
transaction.AddToProgressMax(files_count);
|
||||
for (const Subdirectory &subdir : subdirs) {
|
||||
for (const CollectionSubdirectory &subdir : subdirs) {
|
||||
if (stop_requested_ || abort_requested_) break;
|
||||
|
||||
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);
|
||||
|
||||
// Do not scan symlinked dirs that are already in collection
|
||||
if (path_info.isSymLink()) {
|
||||
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)) {
|
||||
return;
|
||||
}
|
||||
@@ -440,12 +439,12 @@ void CollectionWatcher::ScanSubdirectory(const QString &path, const Subdirectory
|
||||
|
||||
QMap<QString, QStringList> album_art;
|
||||
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 one has been removed, "rescan" it to get the deleted songs
|
||||
SubdirectoryList previous_subdirs = t->GetImmediateSubdirs(path);
|
||||
for (const Subdirectory &prev_subdir : previous_subdirs) {
|
||||
CollectionSubdirectoryList previous_subdirs = t->GetImmediateSubdirs(path);
|
||||
for (const CollectionSubdirectory &prev_subdir : previous_subdirs) {
|
||||
if (!QFile::exists(prev_subdir.path) && prev_subdir.path != path) {
|
||||
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 (!t->HasSeenSubdir(child)) {
|
||||
// We haven't seen this subdirectory before - add it to a list, and later we'll tell the backend about it and scan it.
|
||||
Subdirectory new_subdir;
|
||||
CollectionSubdirectory new_subdir;
|
||||
new_subdir.directory_id = -1;
|
||||
new_subdir.path = child;
|
||||
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
|
||||
Subdirectory updated_subdir;
|
||||
CollectionSubdirectory updated_subdir;
|
||||
updated_subdir.directory_id = t->dir();
|
||||
updated_subdir.mtime = path_info.exists() ? path_info.lastModified().toSecsSinceEpoch() : 0;
|
||||
updated_subdir.path = path;
|
||||
@@ -688,12 +687,12 @@ void CollectionWatcher::ScanSubdirectory(const QString &path, const Subdirectory
|
||||
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;
|
||||
}
|
||||
|
||||
// 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;
|
||||
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();
|
||||
if (cue_deleted) {
|
||||
for (const Song &song : matching_songs) {
|
||||
if (!song.IsMetadataAndMoreEqual(matching_song)) {
|
||||
if (!song.IsAllMetadataEqual(matching_song)) {
|
||||
t->deleted_songs << song;
|
||||
}
|
||||
}
|
||||
@@ -855,10 +854,26 @@ void CollectionWatcher::AddChangedSong(const QString &file, const Song &matching
|
||||
changes << "metadata";
|
||||
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";
|
||||
notify_new = true;
|
||||
}
|
||||
if (!matching_song.IsAcoustIdEqual(new_song)) {
|
||||
changes << "acoustid";
|
||||
notify_new = true;
|
||||
}
|
||||
if (!matching_song.IsMusicBrainzEqual(new_song)) {
|
||||
changes << "musicbrainz";
|
||||
notify_new = true;
|
||||
}
|
||||
if (matching_song.mtime() != new_song.mtime()) {
|
||||
changes << "mtime";
|
||||
}
|
||||
@@ -897,7 +912,7 @@ quint64 CollectionWatcher::GetMtimeForCue(const QString &cue_path) {
|
||||
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;
|
||||
|
||||
@@ -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);
|
||||
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);
|
||||
watched_dirs_.remove(dir.id);
|
||||
@@ -979,11 +994,11 @@ bool CollectionWatcher::FindSongsByFingerprint(const QString &file, const SongLi
|
||||
void CollectionWatcher::DirectoryChanged(const QString &subdir) {
|
||||
|
||||
// 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()) {
|
||||
return;
|
||||
}
|
||||
Directory dir = *it;
|
||||
CollectionDirectory dir = *it;
|
||||
|
||||
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]) {
|
||||
if (stop_requested_ || abort_requested_) break;
|
||||
Subdirectory subdir;
|
||||
CollectionSubdirectory subdir;
|
||||
subdir.directory_id = dir;
|
||||
subdir.mtime = 0;
|
||||
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() {
|
||||
|
||||
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::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) {
|
||||
|
||||
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;
|
||||
|
||||
ScanTransaction transaction(this, dir.id, incremental, ignore_mtimes, mark_songs_unavailable_);
|
||||
SubdirectoryList subdirs(transaction.GetAllSubdirs());
|
||||
CollectionSubdirectoryList subdirs(transaction.GetAllSubdirs());
|
||||
|
||||
if (subdirs.isEmpty()) {
|
||||
qLog(Debug) << "Collection directory wasn't in subdir list.";
|
||||
Subdirectory subdir;
|
||||
CollectionSubdirectory subdir;
|
||||
subdir.path = dir.path;
|
||||
subdir.directory_id = dir.id;
|
||||
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);
|
||||
transaction.AddToProgressMax(files_count);
|
||||
|
||||
for (const Subdirectory &subdir : subdirs) {
|
||||
for (const CollectionSubdirectory &subdir : subdirs) {
|
||||
if (stop_requested_ || abort_requested_) break;
|
||||
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.isSymLink()) {
|
||||
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)) {
|
||||
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;
|
||||
for (const Subdirectory &subdir : subdirs) {
|
||||
for (const CollectionSubdirectory &subdir : subdirs) {
|
||||
if (stop_requested_ || abort_requested_) break;
|
||||
const quint64 files_count = FilesCountForPath(t, subdir.path);
|
||||
subdir_files_count[subdir.path] = files_count;
|
||||
@@ -1252,3 +1227,34 @@ quint64 CollectionWatcher::FilesCountForSubdirs(ScanTransaction *t, const Subdir
|
||||
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 <QUrl>
|
||||
|
||||
#include "directory.h"
|
||||
#include "collectiondirectory.h"
|
||||
#include "core/song.h"
|
||||
|
||||
class QThread;
|
||||
@@ -59,7 +59,6 @@ class CollectionWatcher : public QObject {
|
||||
|
||||
void IncrementalScanAsync();
|
||||
void FullScanAsync();
|
||||
void RescanTracksAsync(const SongList &songs);
|
||||
void SetRescanPausedAsync(const bool pause);
|
||||
void ReloadSettingsAsync();
|
||||
|
||||
@@ -68,14 +67,16 @@ class CollectionWatcher : public QObject {
|
||||
|
||||
void ExitAsync();
|
||||
|
||||
void RescanSongsAsync(const SongList &songs);
|
||||
|
||||
signals:
|
||||
void NewOrUpdatedSongs(SongList);
|
||||
void SongsMTimeUpdated(SongList);
|
||||
void SongsDeleted(SongList);
|
||||
void SongsUnavailable(SongList songs, bool unavailable = true);
|
||||
void SongsReadded(SongList songs, bool unavailable = false);
|
||||
void SubdirsDiscovered(SubdirectoryList subdirs);
|
||||
void SubdirsMTimeUpdated(SubdirectoryList subdirs);
|
||||
void SubdirsDiscovered(CollectionSubdirectoryList subdirs);
|
||||
void SubdirsMTimeUpdated(CollectionSubdirectoryList subdirs);
|
||||
void CompilationsNeedUpdating();
|
||||
void UpdateLastSeen(int directory_id, int expire_unavailable_songs_days);
|
||||
void ExitFinished();
|
||||
@@ -83,8 +84,8 @@ class CollectionWatcher : public QObject {
|
||||
void ScanStarted(int task_id);
|
||||
|
||||
public slots:
|
||||
void AddDirectory(const Directory &dir, const SubdirectoryList &subdirs);
|
||||
void RemoveDirectory(const Directory &dir);
|
||||
void AddDirectory(const CollectionDirectory &dir, const CollectionSubdirectoryList &subdirs);
|
||||
void RemoveDirectory(const CollectionDirectory &dir);
|
||||
void SetRescanPaused(bool pause);
|
||||
|
||||
private:
|
||||
@@ -102,9 +103,9 @@ class CollectionWatcher : public QObject {
|
||||
SongList FindSongsInSubdirectory(const QString &path);
|
||||
bool HasSongsWithMissingFingerprint(const QString &path);
|
||||
bool HasSeenSubdir(const QString &path);
|
||||
void SetKnownSubdirs(const SubdirectoryList &subdirs);
|
||||
SubdirectoryList GetImmediateSubdirs(const QString &path);
|
||||
SubdirectoryList GetAllSubdirs();
|
||||
void SetKnownSubdirs(const CollectionSubdirectoryList &subdirs);
|
||||
CollectionSubdirectoryList GetImmediateSubdirs(const QString &path);
|
||||
CollectionSubdirectoryList GetAllSubdirs();
|
||||
|
||||
void AddToProgress(const quint64 n = 1);
|
||||
void AddToProgressMax(const quint64 n);
|
||||
@@ -120,9 +121,9 @@ class CollectionWatcher : public QObject {
|
||||
SongList readded_songs;
|
||||
SongList new_songs;
|
||||
SongList touched_songs;
|
||||
SubdirectoryList new_subdirs;
|
||||
SubdirectoryList touched_subdirs;
|
||||
SubdirectoryList deleted_subdirs;
|
||||
CollectionSubdirectoryList new_subdirs;
|
||||
CollectionSubdirectoryList touched_subdirs;
|
||||
CollectionSubdirectoryList deleted_subdirs;
|
||||
|
||||
QStringList files_changed_path_;
|
||||
|
||||
@@ -155,7 +156,7 @@ class CollectionWatcher : public QObject {
|
||||
QMultiMap<QString, Song> cached_songs_missing_fingerprint_;
|
||||
bool cached_songs_missing_fingerprint_dirty_;
|
||||
|
||||
SubdirectoryList known_subdirs_;
|
||||
CollectionSubdirectoryList known_subdirs_;
|
||||
bool known_subdirs_dirty_;
|
||||
};
|
||||
|
||||
@@ -166,9 +167,9 @@ class CollectionWatcher : public QObject {
|
||||
void IncrementalScanCheck();
|
||||
void IncrementalScanNow();
|
||||
void FullScanNow();
|
||||
void RescanTracksNow();
|
||||
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:
|
||||
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);
|
||||
QString PickBestImage(const QStringList &images);
|
||||
QUrl ImageForSong(const QString &path, QMap<QString, QStringList> &album_art);
|
||||
void AddWatch(const Directory &dir, const QString &path);
|
||||
void RemoveWatch(const Directory &dir, const Subdirectory &subdir);
|
||||
void AddWatch(const CollectionDirectory &dir, const QString &path);
|
||||
void RemoveWatch(const CollectionDirectory &dir, const CollectionSubdirectory &subdir);
|
||||
static quint64 GetMtimeForCue(const QString &cue_path);
|
||||
void PerformScan(const bool incremental, const bool ignore_mtimes);
|
||||
|
||||
@@ -195,7 +196,7 @@ class CollectionWatcher : public QObject {
|
||||
static void AddChangedSong(const QString &file, const Song &matching_song, const Song &new_song, ScanTransaction *t);
|
||||
|
||||
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);
|
||||
|
||||
@@ -207,7 +208,7 @@ class CollectionWatcher : public QObject {
|
||||
|
||||
FileSystemWatcherInterface *fs_watcher_;
|
||||
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.
|
||||
// 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 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 *periodic_scan_timer_;
|
||||
QMap<int, QStringList> rescan_queue_; // dir id -> list of subdirs to be scanned
|
||||
@@ -237,8 +237,6 @@ class CollectionWatcher : public QObject {
|
||||
|
||||
static QStringList sValidImages;
|
||||
|
||||
SongList song_rescan_queue_; // Set by UI thread
|
||||
|
||||
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);
|
||||
Reset();
|
||||
|
||||
p_->mapping_.insert(Mapping(CollectionModel::GroupBy_None, 0));
|
||||
p_->mapping_.insert(Mapping(CollectionModel::GroupBy_Artist, 1));
|
||||
p_->mapping_.insert(Mapping(CollectionModel::GroupBy_AlbumArtist, 2));
|
||||
p_->mapping_.insert(Mapping(CollectionModel::GroupBy_Album, 3));
|
||||
p_->mapping_.insert(Mapping(CollectionModel::GroupBy_AlbumDisc, 4));
|
||||
p_->mapping_.insert(Mapping(CollectionModel::GroupBy_Disc, 5));
|
||||
p_->mapping_.insert(Mapping(CollectionModel::GroupBy_Format, 6));
|
||||
p_->mapping_.insert(Mapping(CollectionModel::GroupBy_Genre, 7));
|
||||
p_->mapping_.insert(Mapping(CollectionModel::GroupBy_Year, 8));
|
||||
p_->mapping_.insert(Mapping(CollectionModel::GroupBy_YearAlbum, 9));
|
||||
p_->mapping_.insert(Mapping(CollectionModel::GroupBy_YearAlbumDisc, 10));
|
||||
p_->mapping_.insert(Mapping(CollectionModel::GroupBy_OriginalYear, 11));
|
||||
p_->mapping_.insert(Mapping(CollectionModel::GroupBy_OriginalYearAlbum, 12));
|
||||
p_->mapping_.insert(Mapping(CollectionModel::GroupBy_Composer, 13));
|
||||
p_->mapping_.insert(Mapping(CollectionModel::GroupBy_Performer, 14));
|
||||
p_->mapping_.insert(Mapping(CollectionModel::GroupBy_Grouping, 15));
|
||||
p_->mapping_.insert(Mapping(CollectionModel::GroupBy_FileType, 16));
|
||||
p_->mapping_.insert(Mapping(CollectionModel::GroupBy_Samplerate, 17));
|
||||
p_->mapping_.insert(Mapping(CollectionModel::GroupBy_Bitdepth, 18));
|
||||
p_->mapping_.insert(Mapping(CollectionModel::GroupBy_Bitrate, 19));
|
||||
p_->mapping_.insert(Mapping(CollectionModel::GroupBy::None, 0));
|
||||
p_->mapping_.insert(Mapping(CollectionModel::GroupBy::Artist, 1));
|
||||
p_->mapping_.insert(Mapping(CollectionModel::GroupBy::AlbumArtist, 2));
|
||||
p_->mapping_.insert(Mapping(CollectionModel::GroupBy::Album, 3));
|
||||
p_->mapping_.insert(Mapping(CollectionModel::GroupBy::AlbumDisc, 4));
|
||||
p_->mapping_.insert(Mapping(CollectionModel::GroupBy::Disc, 5));
|
||||
p_->mapping_.insert(Mapping(CollectionModel::GroupBy::Format, 6));
|
||||
p_->mapping_.insert(Mapping(CollectionModel::GroupBy::Genre, 7));
|
||||
p_->mapping_.insert(Mapping(CollectionModel::GroupBy::Year, 8));
|
||||
p_->mapping_.insert(Mapping(CollectionModel::GroupBy::YearAlbum, 9));
|
||||
p_->mapping_.insert(Mapping(CollectionModel::GroupBy::YearAlbumDisc, 10));
|
||||
p_->mapping_.insert(Mapping(CollectionModel::GroupBy::OriginalYear, 11));
|
||||
p_->mapping_.insert(Mapping(CollectionModel::GroupBy::OriginalYearAlbum, 12));
|
||||
p_->mapping_.insert(Mapping(CollectionModel::GroupBy::Composer, 13));
|
||||
p_->mapping_.insert(Mapping(CollectionModel::GroupBy::Performer, 14));
|
||||
p_->mapping_.insert(Mapping(CollectionModel::GroupBy::Grouping, 15));
|
||||
p_->mapping_.insert(Mapping(CollectionModel::GroupBy::FileType, 16));
|
||||
p_->mapping_.insert(Mapping(CollectionModel::GroupBy::Samplerate, 17));
|
||||
p_->mapping_.insert(Mapping(CollectionModel::GroupBy::Bitdepth, 18));
|
||||
p_->mapping_.insert(Mapping(CollectionModel::GroupBy::Bitrate, 19));
|
||||
|
||||
QObject::connect(ui_->buttonbox->button(QDialogButtonBox::Reset), &QPushButton::clicked, this, &GroupByDialog::Reset);
|
||||
|
||||
|
||||
@@ -36,6 +36,7 @@
|
||||
|
||||
#include "core/logging.h"
|
||||
#include "core/iconloader.h"
|
||||
#include "settings/collectionsettingspage.h"
|
||||
#include "collectionmodel.h"
|
||||
#include "savedgroupingmanager.h"
|
||||
#include "ui_savedgroupingmanager.h"
|
||||
@@ -83,68 +84,68 @@ QString SavedGroupingManager::GetSavedGroupingsSettingsGroup(const QString &sett
|
||||
QString SavedGroupingManager::GroupByToString(const CollectionModel::GroupBy g) {
|
||||
|
||||
switch (g) {
|
||||
case CollectionModel::GroupBy_None:
|
||||
case CollectionModel::GroupByCount: {
|
||||
case CollectionModel::GroupBy::None:
|
||||
case CollectionModel::GroupBy::GroupByCount: {
|
||||
return tr("None");
|
||||
}
|
||||
case CollectionModel::GroupBy_AlbumArtist: {
|
||||
case CollectionModel::GroupBy::AlbumArtist: {
|
||||
return tr("Album artist");
|
||||
}
|
||||
case CollectionModel::GroupBy_Artist: {
|
||||
case CollectionModel::GroupBy::Artist: {
|
||||
return tr("Artist");
|
||||
}
|
||||
case CollectionModel::GroupBy_Album: {
|
||||
case CollectionModel::GroupBy::Album: {
|
||||
return tr("Album");
|
||||
}
|
||||
case CollectionModel::GroupBy_AlbumDisc: {
|
||||
case CollectionModel::GroupBy::AlbumDisc: {
|
||||
return tr("Album - Disc");
|
||||
}
|
||||
case CollectionModel::GroupBy_YearAlbum: {
|
||||
case CollectionModel::GroupBy::YearAlbum: {
|
||||
return tr("Year - Album");
|
||||
}
|
||||
case CollectionModel::GroupBy_YearAlbumDisc: {
|
||||
case CollectionModel::GroupBy::YearAlbumDisc: {
|
||||
return tr("Year - Album - Disc");
|
||||
}
|
||||
case CollectionModel::GroupBy_OriginalYearAlbum: {
|
||||
case CollectionModel::GroupBy::OriginalYearAlbum: {
|
||||
return tr("Original year - Album");
|
||||
}
|
||||
case CollectionModel::GroupBy_OriginalYearAlbumDisc: {
|
||||
case CollectionModel::GroupBy::OriginalYearAlbumDisc: {
|
||||
return tr("Original year - Album - Disc");
|
||||
}
|
||||
case CollectionModel::GroupBy_Disc: {
|
||||
case CollectionModel::GroupBy::Disc: {
|
||||
return tr("Disc");
|
||||
}
|
||||
case CollectionModel::GroupBy_Year: {
|
||||
case CollectionModel::GroupBy::Year: {
|
||||
return tr("Year");
|
||||
}
|
||||
case CollectionModel::GroupBy_OriginalYear: {
|
||||
case CollectionModel::GroupBy::OriginalYear: {
|
||||
return tr("Original year");
|
||||
}
|
||||
case CollectionModel::GroupBy_Genre: {
|
||||
case CollectionModel::GroupBy::Genre: {
|
||||
return tr("Genre");
|
||||
}
|
||||
case CollectionModel::GroupBy_Composer: {
|
||||
case CollectionModel::GroupBy::Composer: {
|
||||
return tr("Composer");
|
||||
}
|
||||
case CollectionModel::GroupBy_Performer: {
|
||||
case CollectionModel::GroupBy::Performer: {
|
||||
return tr("Performer");
|
||||
}
|
||||
case CollectionModel::GroupBy_Grouping: {
|
||||
case CollectionModel::GroupBy::Grouping: {
|
||||
return tr("Grouping");
|
||||
}
|
||||
case CollectionModel::GroupBy_FileType: {
|
||||
case CollectionModel::GroupBy::FileType: {
|
||||
return tr("File type");
|
||||
}
|
||||
case CollectionModel::GroupBy_Format: {
|
||||
case CollectionModel::GroupBy::Format: {
|
||||
return tr("Format");
|
||||
}
|
||||
case CollectionModel::GroupBy_Samplerate: {
|
||||
case CollectionModel::GroupBy::Samplerate: {
|
||||
return tr("Sample rate");
|
||||
}
|
||||
case CollectionModel::GroupBy_Bitdepth: {
|
||||
case CollectionModel::GroupBy::Bitdepth: {
|
||||
return tr("Bit depth");
|
||||
}
|
||||
case CollectionModel::GroupBy_Bitrate: {
|
||||
case CollectionModel::GroupBy::Bitrate: {
|
||||
return tr("Bitrate");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -37,7 +37,7 @@
|
||||
#include <QContextMenuEvent>
|
||||
#include <QPaintEvent>
|
||||
|
||||
#include "core/imageutils.h"
|
||||
#include "utilities/imageutils.h"
|
||||
#include "covermanager/albumcoverchoicecontroller.h"
|
||||
|
||||
#include "contextview.h"
|
||||
@@ -63,7 +63,7 @@ ContextAlbum::ContextAlbum(QWidget *parent)
|
||||
cover_loader_options_.desired_height_ = width();
|
||||
cover_loader_options_.pad_output_image_ = true;
|
||||
cover_loader_options_.scale_output_image_ = true;
|
||||
QImage image = ImageUtils::ScaleAndPad(image_strawberry_, cover_loader_options_.scale_output_image_, cover_loader_options_.pad_output_image_, cover_loader_options_.desired_height_);
|
||||
QImage image = ImageUtils::ScaleAndPad(image_strawberry_, cover_loader_options_.scale_output_image_, cover_loader_options_.pad_output_image_, cover_loader_options_.desired_height_, devicePixelRatioF());
|
||||
if (!image.isNull()) {
|
||||
pixmap_current_ = QPixmap::fromImage(image);
|
||||
}
|
||||
@@ -91,7 +91,7 @@ void ContextAlbum::Init(ContextView *context_view, AlbumCoverChoiceController *a
|
||||
|
||||
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;
|
||||
|
||||
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() {
|
||||
|
||||
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()) {
|
||||
pixmap_current_ = QPixmap();
|
||||
}
|
||||
@@ -248,7 +248,7 @@ void ContextAlbum::ScaleCover() {
|
||||
void ContextAlbum::ScalePreviousCovers() {
|
||||
|
||||
for (std::shared_ptr<PreviousCover> previous_cover : previous_covers_) {
|
||||
QImage image = ImageUtils::ScaleAndPad(previous_cover->image, cover_loader_options_.scale_output_image_, cover_loader_options_.pad_output_image_, cover_loader_options_.desired_height_);
|
||||
QImage image = ImageUtils::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()) {
|
||||
previous_cover->pixmap = QPixmap();
|
||||
}
|
||||
|
||||
@@ -51,8 +51,9 @@
|
||||
#include "core/application.h"
|
||||
#include "core/player.h"
|
||||
#include "core/song.h"
|
||||
#include "core/utilities.h"
|
||||
#include "core/iconloader.h"
|
||||
#include "utilities/strutils.h"
|
||||
#include "utilities/timeutils.h"
|
||||
#include "widgets/resizabletextedit.h"
|
||||
#include "engine/engine_fwd.h"
|
||||
#include "engine/enginebase.h"
|
||||
@@ -337,11 +338,11 @@ void ContextView::ReloadSettings() {
|
||||
s.beginGroup(ContextSettingsPage::kSettingsGroup);
|
||||
title_fmt_ = s.value(ContextSettingsPage::kSettingsTitleFmt, "%title% - %artist%").toString();
|
||||
summary_fmt_ = s.value(ContextSettingsPage::kSettingsSummaryFmt, "%album%").toString();
|
||||
action_show_album_->setChecked(s.value(ContextSettingsPage::kSettingsGroupEnable[ContextSettingsPage::ContextSettingsOrder::ALBUM], true).toBool());
|
||||
action_show_data_->setChecked(s.value(ContextSettingsPage::kSettingsGroupEnable[ContextSettingsPage::ContextSettingsOrder::TECHNICAL_DATA], false).toBool());
|
||||
action_show_output_->setChecked(s.value(ContextSettingsPage::kSettingsGroupEnable[ContextSettingsPage::ContextSettingsOrder::ENGINE_AND_DEVICE], false).toBool());
|
||||
action_show_lyrics_->setChecked(s.value(ContextSettingsPage::kSettingsGroupEnable[ContextSettingsPage::ContextSettingsOrder::SONG_LYRICS], true).toBool());
|
||||
action_search_lyrics_->setChecked(s.value(ContextSettingsPage::kSettingsGroupEnable[ContextSettingsPage::ContextSettingsOrder::SEARCH_LYRICS], true).toBool());
|
||||
action_show_album_->setChecked(s.value(ContextSettingsPage::kSettingsGroupEnable[static_cast<int>(ContextSettingsPage::ContextSettingsOrder::ALBUM)], true).toBool());
|
||||
action_show_data_->setChecked(s.value(ContextSettingsPage::kSettingsGroupEnable[static_cast<int>(ContextSettingsPage::ContextSettingsOrder::TECHNICAL_DATA)], false).toBool());
|
||||
action_show_output_->setChecked(s.value(ContextSettingsPage::kSettingsGroupEnable[static_cast<int>(ContextSettingsPage::ContextSettingsOrder::ENGINE_AND_DEVICE)], false).toBool());
|
||||
action_show_lyrics_->setChecked(s.value(ContextSettingsPage::kSettingsGroupEnable[static_cast<int>(ContextSettingsPage::ContextSettingsOrder::SONG_LYRICS)], true).toBool());
|
||||
action_search_lyrics_->setChecked(s.value(ContextSettingsPage::kSettingsGroupEnable[static_cast<int>(ContextSettingsPage::ContextSettingsOrder::SEARCH_LYRICS)], true).toBool());
|
||||
font_headline_ = s.value("font_headline", font().family()).toString();
|
||||
font_normal_ = s.value("font_normal", font().family()).toString();
|
||||
font_size_headline_ = s.value("font_size_headline", ContextSettingsPage::kDefaultFontSizeHeadline).toReal();
|
||||
@@ -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) {
|
||||
lyrics_fetcher_->Clear();
|
||||
lyrics_tried_ = true;
|
||||
lyrics_id_ = static_cast<qint64>(lyrics_fetcher_->Search(song_playing_.effective_albumartist(), song_playing_.album(), song_playing_.title()));
|
||||
lyrics_id_ = static_cast<qint64>(lyrics_fetcher_->Search(song_playing_.effective_albumartist(), song_playing_.artist(), song_playing_.album(), song_playing_.title()));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -434,8 +435,8 @@ void ContextView::NoSong() {
|
||||
widget_album_->show();
|
||||
}
|
||||
|
||||
textedit_top_->setFont(QFont(font_headline_, font_size_headline_ * 1.6));
|
||||
textedit_top_->setText(tr("No song playing"));
|
||||
textedit_top_->setFont(QFont(font_headline_, static_cast<int>(font_size_headline_ * 1.6)));
|
||||
textedit_top_->SetText(tr("No song playing"));
|
||||
|
||||
QString html;
|
||||
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());
|
||||
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);
|
||||
|
||||
}
|
||||
|
||||
void ContextView::UpdateFonts() {
|
||||
|
||||
QFont font(font_normal_, font_size_normal_);
|
||||
QFont font(font_normal_, static_cast<int>(font_size_normal_));
|
||||
font.setBold(false);
|
||||
for (QLabel *l : labels_play_all_) {
|
||||
l->setFont(font);
|
||||
@@ -470,9 +471,8 @@ void ContextView::UpdateFonts() {
|
||||
|
||||
void ContextView::SetSong() {
|
||||
|
||||
|
||||
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_->setFont(QFont(font_headline_, static_cast<int>(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)));
|
||||
|
||||
label_stop_summary_->clear();
|
||||
|
||||
@@ -544,9 +544,9 @@ void ContextView::SetSong() {
|
||||
|
||||
if (action_show_output_->isChecked()) {
|
||||
widget_play_output_->show();
|
||||
Engine::EngineType enginetype(Engine::None);
|
||||
Engine::EngineType enginetype(Engine::EngineType::None);
|
||||
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_->setText(EngineDescription(enginetype));
|
||||
@@ -564,7 +564,7 @@ void ContextView::SetSong() {
|
||||
label_device_title_->show();
|
||||
label_device_icon_->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_->setText(device.description);
|
||||
}
|
||||
@@ -586,7 +586,7 @@ void ContextView::SetSong() {
|
||||
}
|
||||
|
||||
if (action_show_lyrics_->isChecked() && !lyrics_.isEmpty()) {
|
||||
textedit_play_lyrics_->setText(lyrics_);
|
||||
textedit_play_lyrics_->SetText(lyrics_);
|
||||
textedit_play_lyrics_->show();
|
||||
}
|
||||
else {
|
||||
@@ -601,7 +601,7 @@ void ContextView::SetSong() {
|
||||
|
||||
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 (song.filetype() != song_playing_.filetype()) label_filetype_->setText(song.TextForFiletype());
|
||||
@@ -657,6 +657,8 @@ void ContextView::UpdateSong(const Song &song) {
|
||||
|
||||
song_playing_ = song;
|
||||
|
||||
widget_stacked_->updateGeometry();
|
||||
|
||||
}
|
||||
|
||||
void ContextView::ResetSong() {
|
||||
@@ -683,7 +685,7 @@ void ContextView::UpdateLyrics(const quint64 id, const QString &provider, const
|
||||
lyrics_id_ = -1;
|
||||
|
||||
if (action_show_lyrics_->isChecked() && !lyrics_.isEmpty()) {
|
||||
textedit_play_lyrics_->setText(lyrics_);
|
||||
textedit_play_lyrics_->SetText(lyrics_);
|
||||
textedit_play_lyrics_->show();
|
||||
}
|
||||
else {
|
||||
@@ -737,7 +739,7 @@ void ContextView::ActionShowAlbum() {
|
||||
|
||||
QSettings s;
|
||||
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();
|
||||
if (song_playing_.is_valid()) SetSong();
|
||||
|
||||
@@ -747,7 +749,7 @@ void ContextView::ActionShowData() {
|
||||
|
||||
QSettings s;
|
||||
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();
|
||||
if (song_playing_.is_valid()) SetSong();
|
||||
|
||||
@@ -757,7 +759,7 @@ void ContextView::ActionShowOutput() {
|
||||
|
||||
QSettings s;
|
||||
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();
|
||||
if (song_playing_.is_valid()) SetSong();
|
||||
|
||||
@@ -767,7 +769,7 @@ void ContextView::ActionShowLyrics() {
|
||||
|
||||
QSettings s;
|
||||
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();
|
||||
|
||||
if (song_playing_.is_valid()) SetSong();
|
||||
@@ -780,7 +782,7 @@ void ContextView::ActionSearchLyrics() {
|
||||
|
||||
QSettings s;
|
||||
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();
|
||||
|
||||
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 "taskmanager.h"
|
||||
#include "player.h"
|
||||
#include "appearance.h"
|
||||
|
||||
#include "engine/devicefinders.h"
|
||||
#ifndef Q_OS_WIN
|
||||
@@ -64,6 +63,7 @@
|
||||
#include "lyrics/lololyricsprovider.h"
|
||||
#include "lyrics/musixmatchlyricsprovider.h"
|
||||
#include "lyrics/chartlyricsprovider.h"
|
||||
#include "lyrics/lyricscomlyricsprovider.h"
|
||||
|
||||
#include "scrobbler/audioscrobbler.h"
|
||||
#include "scrobbler/lastfmimport.h"
|
||||
@@ -109,7 +109,6 @@ class ApplicationImpl {
|
||||
QTimer::singleShot(30s, db, &Database::DoBackup);
|
||||
return db;
|
||||
}),
|
||||
appearance_([app]() { return new Appearance(app); }),
|
||||
task_manager_([app]() { return new TaskManager(app); }),
|
||||
player_([app]() { return new Player(app, 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 MusixmatchLyricsProvider(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();
|
||||
return lyrics_providers;
|
||||
}),
|
||||
@@ -183,7 +183,6 @@ class ApplicationImpl {
|
||||
|
||||
Lazy<TagReaderClient> tag_reader_client_;
|
||||
Lazy<Database> database_;
|
||||
Lazy<Appearance> appearance_;
|
||||
Lazy<TaskManager> task_manager_;
|
||||
Lazy<Player> player_;
|
||||
Lazy<DeviceFinders> device_finders_;
|
||||
@@ -315,7 +314,6 @@ void Application::ReloadSettings() { emit SettingsChanged(); }
|
||||
void Application::OpenSettingsDialogAtPage(SettingsDialog::Page page) { emit SettingsDialogRequested(page); }
|
||||
|
||||
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(); }
|
||||
TaskManager *Application::task_manager() const { return p_->task_manager_.get(); }
|
||||
Player *Application::player() const { return p_->player_.get(); }
|
||||
|
||||
@@ -41,7 +41,6 @@ class TagReaderClient;
|
||||
class Database;
|
||||
class DeviceFinders;
|
||||
class Player;
|
||||
class Appearance;
|
||||
class SCollection;
|
||||
class CollectionBackend;
|
||||
class CollectionModel;
|
||||
@@ -73,7 +72,6 @@ class Application : public QObject {
|
||||
|
||||
TagReaderClient *tag_reader_client() const;
|
||||
Database *database() const;
|
||||
Appearance *appearance() const;
|
||||
TaskManager *task_manager() const;
|
||||
Player *player() const;
|
||||
DeviceFinders *device_finders() const;
|
||||
|
||||
@@ -81,8 +81,8 @@ const char *CommandlineOptions::kVersionText = "Strawberry %1";
|
||||
CommandlineOptions::CommandlineOptions(int argc, char **argv)
|
||||
: argc_(argc),
|
||||
argv_(argv),
|
||||
url_list_action_(UrlList_None),
|
||||
player_action_(Player_None),
|
||||
url_list_action_(UrlListAction::None),
|
||||
player_action_(PlayerAction::None),
|
||||
set_volume_(-1),
|
||||
volume_modifier_(0),
|
||||
seek_to_(-1),
|
||||
@@ -128,13 +128,13 @@ bool CommandlineOptions::Parse() {
|
||||
{"previous", no_argument, nullptr, 'r'},
|
||||
{"next", no_argument, nullptr, 'f'},
|
||||
{"volume", required_argument, nullptr, 'v'},
|
||||
{"volume-up", no_argument, nullptr, VolumeUp},
|
||||
{"volume-down", no_argument, nullptr, VolumeDown},
|
||||
{"volume-increase-by", required_argument, nullptr, VolumeIncreaseBy},
|
||||
{"volume-decrease-by", required_argument, nullptr, VolumeDecreaseBy},
|
||||
{"seek-to", required_argument, nullptr, SeekTo},
|
||||
{"seek-by", required_argument, nullptr, SeekBy},
|
||||
{"restart-or-previous", no_argument, nullptr, RestartOrPrevious},
|
||||
{"volume-up", no_argument, nullptr, LongOptions::VolumeUp},
|
||||
{"volume-down", no_argument, nullptr, LongOptions::VolumeDown},
|
||||
{"volume-increase-by", required_argument, nullptr, LongOptions::VolumeIncreaseBy},
|
||||
{"volume-decrease-by", required_argument, nullptr, LongOptions::VolumeDecreaseBy},
|
||||
{"seek-to", required_argument, nullptr, LongOptions::SeekTo},
|
||||
{"seek-by", required_argument, nullptr, LongOptions::SeekBy},
|
||||
{"restart-or-previous", no_argument, nullptr, LongOptions::RestartOrPrevious},
|
||||
{"create", required_argument, nullptr, 'c'},
|
||||
{"append", no_argument, nullptr, 'a'},
|
||||
{"load", no_argument, nullptr, 'l'},
|
||||
@@ -144,10 +144,10 @@ bool CommandlineOptions::Parse() {
|
||||
{"toggle-pretty-osd", no_argument, nullptr, 'y'},
|
||||
{"language", required_argument, nullptr, 'g'},
|
||||
{"resize-window", required_argument, nullptr, 'w'},
|
||||
{"quiet", no_argument, nullptr, Quiet},
|
||||
{"verbose", no_argument, nullptr, Verbose},
|
||||
{"log-levels", required_argument, nullptr, LogLevels},
|
||||
{"version", no_argument, nullptr, Version},
|
||||
{"quiet", no_argument, nullptr, LongOptions::Quiet},
|
||||
{"verbose", no_argument, nullptr, LongOptions::Verbose},
|
||||
{"log-levels", required_argument, nullptr, LongOptions::LogLevels},
|
||||
{"version", no_argument, nullptr, LongOptions::Version},
|
||||
{nullptr, 0, nullptr, 0}};
|
||||
|
||||
// Parse the arguments
|
||||
@@ -198,39 +198,39 @@ bool CommandlineOptions::Parse() {
|
||||
}
|
||||
|
||||
case 'p':
|
||||
player_action_ = Player_Play;
|
||||
player_action_ = PlayerAction::Play;
|
||||
break;
|
||||
case 't':
|
||||
player_action_ = Player_PlayPause;
|
||||
player_action_ = PlayerAction::PlayPause;
|
||||
break;
|
||||
case 'u':
|
||||
player_action_ = Player_Pause;
|
||||
player_action_ = PlayerAction::Pause;
|
||||
break;
|
||||
case 's':
|
||||
player_action_ = Player_Stop;
|
||||
player_action_ = PlayerAction::Stop;
|
||||
break;
|
||||
case 'q':
|
||||
player_action_ = Player_StopAfterCurrent;
|
||||
player_action_ = PlayerAction::StopAfterCurrent;
|
||||
break;
|
||||
case 'r':
|
||||
player_action_ = Player_Previous;
|
||||
player_action_ = PlayerAction::Previous;
|
||||
break;
|
||||
case 'f':
|
||||
player_action_ = Player_Next;
|
||||
player_action_ = PlayerAction::Next;
|
||||
break;
|
||||
case 'i':
|
||||
player_action_ = Player_PlayPlaylist;
|
||||
player_action_ = PlayerAction::PlayPlaylist;
|
||||
playlist_name_ = QString(optarg);
|
||||
break;
|
||||
case 'c':
|
||||
url_list_action_ = UrlList_CreateNew;
|
||||
url_list_action_ = UrlListAction::CreateNew;
|
||||
playlist_name_ = QString(optarg);
|
||||
break;
|
||||
case 'a':
|
||||
url_list_action_ = UrlList_Append;
|
||||
url_list_action_ = UrlListAction::Append;
|
||||
break;
|
||||
case 'l':
|
||||
url_list_action_ = UrlList_Load;
|
||||
url_list_action_ = UrlListAction::Load;
|
||||
break;
|
||||
case 'o':
|
||||
show_osd_ = true;
|
||||
@@ -241,22 +241,22 @@ bool CommandlineOptions::Parse() {
|
||||
case 'g':
|
||||
language_ = QString(optarg);
|
||||
break;
|
||||
case VolumeUp:
|
||||
case LongOptions::VolumeUp:
|
||||
volume_modifier_ = +4;
|
||||
break;
|
||||
case VolumeDown:
|
||||
case LongOptions::VolumeDown:
|
||||
volume_modifier_ = -4;
|
||||
break;
|
||||
case Quiet:
|
||||
case LongOptions::Quiet:
|
||||
log_levels_ = "1";
|
||||
break;
|
||||
case Verbose:
|
||||
case LongOptions::Verbose:
|
||||
log_levels_ = "3";
|
||||
break;
|
||||
case LogLevels:
|
||||
case LongOptions::LogLevels:
|
||||
log_levels_ = QString(optarg);
|
||||
break;
|
||||
case Version: {
|
||||
case LongOptions::Version: {
|
||||
QString version_text = QString(kVersionText).arg(STRAWBERRY_VERSION_DISPLAY);
|
||||
std::cout << version_text.toLocal8Bit().constData() << std::endl;
|
||||
std::exit(0);
|
||||
@@ -266,28 +266,28 @@ bool CommandlineOptions::Parse() {
|
||||
if (!ok) set_volume_ = -1;
|
||||
break;
|
||||
|
||||
case VolumeIncreaseBy:
|
||||
case LongOptions::VolumeIncreaseBy:
|
||||
volume_modifier_ = QString(optarg).toInt(&ok);
|
||||
if (!ok) volume_modifier_ = 0;
|
||||
break;
|
||||
|
||||
case VolumeDecreaseBy:
|
||||
case LongOptions::VolumeDecreaseBy:
|
||||
volume_modifier_ = -QString(optarg).toInt(&ok);
|
||||
if (!ok) volume_modifier_ = 0;
|
||||
break;
|
||||
|
||||
case SeekTo:
|
||||
case LongOptions::SeekTo:
|
||||
seek_to_ = QString(optarg).toInt(&ok);
|
||||
if (!ok) seek_to_ = -1;
|
||||
break;
|
||||
|
||||
case SeekBy:
|
||||
case LongOptions::SeekBy:
|
||||
seek_by_ = QString(optarg).toInt(&ok);
|
||||
if (!ok) seek_by_ = 0;
|
||||
break;
|
||||
|
||||
case RestartOrPrevious:
|
||||
player_action_ = Player_RestartOrPrevious;
|
||||
case LongOptions::RestartOrPrevious:
|
||||
player_action_ = PlayerAction::RestartOrPrevious;
|
||||
break;
|
||||
|
||||
case 'k':
|
||||
@@ -297,7 +297,7 @@ bool CommandlineOptions::Parse() {
|
||||
|
||||
case 'w':
|
||||
window_size_ = QString(optarg);
|
||||
player_action_ = Player_ResizeWindow;
|
||||
player_action_ = PlayerAction::ResizeWindow;
|
||||
break;
|
||||
|
||||
case '?':
|
||||
@@ -323,7 +323,7 @@ bool CommandlineOptions::Parse() {
|
||||
}
|
||||
|
||||
bool CommandlineOptions::is_empty() const {
|
||||
return player_action_ == Player_None &&
|
||||
return player_action_ == PlayerAction::None &&
|
||||
set_volume_ == -1 &&
|
||||
volume_modifier_ == 0 &&
|
||||
seek_to_ == -1 &&
|
||||
@@ -335,7 +335,7 @@ bool CommandlineOptions::is_empty() 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 {
|
||||
|
||||
@@ -41,24 +41,24 @@ class CommandlineOptions {
|
||||
|
||||
// Don't change the values or order, these get serialised and sent to
|
||||
// possibly a different version of Strawberry
|
||||
enum UrlListAction {
|
||||
UrlList_Append = 0,
|
||||
UrlList_Load = 1,
|
||||
UrlList_None = 2,
|
||||
UrlList_CreateNew = 3,
|
||||
enum class UrlListAction {
|
||||
Append = 0,
|
||||
Load = 1,
|
||||
None = 2,
|
||||
CreateNew = 3
|
||||
};
|
||||
enum PlayerAction {
|
||||
Player_None = 0,
|
||||
Player_Play = 1,
|
||||
Player_PlayPause = 2,
|
||||
Player_Pause = 3,
|
||||
Player_Stop = 4,
|
||||
Player_Previous = 5,
|
||||
Player_Next = 6,
|
||||
Player_RestartOrPrevious = 7,
|
||||
Player_StopAfterCurrent = 8,
|
||||
Player_PlayPlaylist = 9,
|
||||
Player_ResizeWindow = 10
|
||||
enum class PlayerAction {
|
||||
None = 0,
|
||||
Play = 1,
|
||||
PlayPause = 2,
|
||||
Pause = 3,
|
||||
Stop = 4,
|
||||
Previous = 5,
|
||||
Next = 6,
|
||||
RestartOrPrevious = 7,
|
||||
StopAfterCurrent = 8,
|
||||
PlayPlaylist = 9,
|
||||
ResizeWindow = 10
|
||||
};
|
||||
|
||||
bool Parse();
|
||||
|
||||
@@ -48,7 +48,7 @@
|
||||
#include "scopedtransaction.h"
|
||||
|
||||
const char *Database::kDatabaseFilename = "strawberry.db";
|
||||
const int Database::kSchemaVersion = 15;
|
||||
const int Database::kSchemaVersion = 16;
|
||||
const int Database::kMinSupportedSchemaVersion = 10;
|
||||
const char *Database::kMagicAllSongsTables = "%allsongstables";
|
||||
|
||||
|
||||
@@ -29,7 +29,7 @@
|
||||
#include <QString>
|
||||
|
||||
#include "core/logging.h"
|
||||
#include "utilities.h"
|
||||
#include "utilities/fileutils.h"
|
||||
#include "musicstorage.h"
|
||||
|
||||
#include "filesystemmusicstorage.h"
|
||||
@@ -106,12 +106,7 @@ bool FilesystemMusicStorage::DeleteFromStorage(const DeleteJob &job) {
|
||||
|
||||
if (job.use_trash_) {
|
||||
#if QT_VERSION >= QT_VERSION_CHECK(5, 15, 0)
|
||||
if (fileInfo.isDir()) {
|
||||
return Utilities::MoveToTrashRecursive(path);
|
||||
}
|
||||
else {
|
||||
return QFile::moveToTrash(path);
|
||||
}
|
||||
return QFile::moveToTrash(path);
|
||||
#else
|
||||
return false;
|
||||
#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;
|
||||
|
||||
@@ -69,7 +69,7 @@ QIcon IconLoader::Load(const QString &name, const int fixed_size, const int min_
|
||||
sizes << fixed_size;
|
||||
}
|
||||
|
||||
if (system_icons_) {
|
||||
if (system_icon && system_icons_) {
|
||||
IconMapper::IconProperties icon_prop;
|
||||
if (IconMapper::iconmapper_.contains(name)) {
|
||||
icon_prop = IconMapper::iconmapper_[name];
|
||||
|
||||
@@ -26,7 +26,7 @@
|
||||
class IconLoader {
|
||||
public:
|
||||
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:
|
||||
explicit IconLoader() {}
|
||||
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
|
||||
#define MAC_STARTUP_H
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#include <CoreFoundation/CFDictionary.h>
|
||||
|
||||
#include <QString>
|
||||
#include <QWidget>
|
||||
#include <QKeySequence>
|
||||
|
||||
class QObject;
|
||||
class QWidget;
|
||||
#ifdef __OBJC__
|
||||
@class NSEvent;
|
||||
#else
|
||||
class NSEvent;
|
||||
#endif
|
||||
|
||||
class PlatformInterface;
|
||||
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 {
|
||||
|
||||
void MacMain();
|
||||
@@ -32,6 +47,9 @@ void SetApplicationHandler(PlatformInterface *handler);
|
||||
|
||||
void EnableFullScreen(const QWidget &main_window);
|
||||
|
||||
QKeySequence KeySequenceFromNSEvent(NSEvent *event);
|
||||
void DumpDictionary(CFDictionaryRef dict);
|
||||
|
||||
} // namespace mac
|
||||
|
||||
#endif
|
||||
|
||||
@@ -45,13 +45,12 @@
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#include "platforminterface.h"
|
||||
#include "mac_delegate.h"
|
||||
#include "mac_startup.h"
|
||||
#include "mac_utilities.h"
|
||||
#include "utilities.h"
|
||||
#include "scoped_cftyperef.h"
|
||||
#include "core/logging.h"
|
||||
#include "core/scoped_nsautorelease_pool.h"
|
||||
#include "scoped_nsautorelease_pool.h"
|
||||
#include "globalshortcuts/globalshortcutsmanager.h"
|
||||
#include "globalshortcuts/globalshortcutsbackend-macos.h"
|
||||
|
||||
|
||||
@@ -59,6 +59,7 @@
|
||||
#include <QActionGroup>
|
||||
#include <QShortcut>
|
||||
#include <QMessageBox>
|
||||
#include <QErrorMessage>
|
||||
#include <QtEvents>
|
||||
#include <QSettings>
|
||||
#include <QColor>
|
||||
@@ -74,13 +75,10 @@
|
||||
#include <QClipboard>
|
||||
|
||||
#include "core/logging.h"
|
||||
#include "core/networkaccessmanager.h"
|
||||
|
||||
#include "mainwindow.h"
|
||||
#include "ui_mainwindow.h"
|
||||
|
||||
#include "utilities.h"
|
||||
#include "timeconstants.h"
|
||||
#include "commandlineoptions.h"
|
||||
#include "mimedata.h"
|
||||
#include "iconloader.h"
|
||||
@@ -91,14 +89,19 @@
|
||||
#include "application.h"
|
||||
#include "database.h"
|
||||
#include "player.h"
|
||||
#include "appearance.h"
|
||||
#include "filesystemmusicstorage.h"
|
||||
#include "deletefiles.h"
|
||||
#ifdef Q_OS_MACOS
|
||||
# include "mac_startup.h"
|
||||
# include "macsystemtrayicon.h"
|
||||
# include "utilities/macosutils.h"
|
||||
#else
|
||||
# include "qtsystemtrayicon.h"
|
||||
#endif
|
||||
#include "networkaccessmanager.h"
|
||||
#include "utilities/envutils.h"
|
||||
#include "utilities/filemanagerutils.h"
|
||||
#include "utilities/timeconstants.h"
|
||||
#include "engine/enginetype.h"
|
||||
#include "engine/enginebase.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)),
|
||||
#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
|
||||
#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
|
||||
#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
|
||||
radio_view_(new RadioViewContainer(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)),
|
||||
keep_running_(false),
|
||||
playing_widget_(true),
|
||||
doubleclick_addmode_(BehaviourSettingsPage::AddBehaviour_Append),
|
||||
doubleclick_playmode_(BehaviourSettingsPage::PlayBehaviour_Never),
|
||||
doubleclick_playlist_addmode_(BehaviourSettingsPage::PlaylistAddBehaviour_Play),
|
||||
menu_playmode_(BehaviourSettingsPage::PlayBehaviour_Never),
|
||||
doubleclick_addmode_(BehaviourSettingsPage::AddBehaviour::Append),
|
||||
doubleclick_playmode_(BehaviourSettingsPage::PlayBehaviour::Never),
|
||||
doubleclick_playlist_addmode_(BehaviourSettingsPage::PlaylistAddBehaviour::Play),
|
||||
menu_playmode_(BehaviourSettingsPage::PlayBehaviour::Never),
|
||||
initialized_(false),
|
||||
was_maximized_(true),
|
||||
was_minimized_(false),
|
||||
@@ -363,24 +366,24 @@ MainWindow::MainWindow(Application *app, std::shared_ptr<SystemTrayIcon> tray_ic
|
||||
StyleHelper::setBaseColor(palette().color(QPalette::Highlight).darker());
|
||||
|
||||
// Add tabs to the fancy tab widget
|
||||
ui_->tabs->AddTab(context_view_, "context", IconLoader::Load("strawberry"), tr("Context"));
|
||||
ui_->tabs->AddTab(collection_view_, "collection", IconLoader::Load("library-music"), tr("Collection"));
|
||||
ui_->tabs->AddTab(queue_view_, "queue", IconLoader::Load("footsteps"), tr("Queue"));
|
||||
ui_->tabs->AddTab(playlist_list_, "playlists", IconLoader::Load("view-media-playlist"), tr("Playlists"));
|
||||
ui_->tabs->AddTab(smartplaylists_view_, "smartplaylists", IconLoader::Load("view-media-playlist"), tr("Smart playlists"));
|
||||
ui_->tabs->AddTab(file_view_, "files", IconLoader::Load("document-open"), tr("Files"));
|
||||
ui_->tabs->AddTab(radio_view_, "radios", IconLoader::Load("radio"), tr("Radios"));
|
||||
ui_->tabs->AddTab(context_view_, "context", IconLoader::Load("strawberry", true, 0, 32), tr("Context"));
|
||||
ui_->tabs->AddTab(collection_view_, "collection", IconLoader::Load("library-music", true, 0, 32), tr("Collection"));
|
||||
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", true, 0, 32), tr("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", true, 0, 32), tr("Files"));
|
||||
ui_->tabs->AddTab(radio_view_, "radios", IconLoader::Load("radio", true, 0, 32), tr("Radios"));
|
||||
#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
|
||||
#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
|
||||
#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
|
||||
#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
|
||||
|
||||
// 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();
|
||||
EngineChanged(app_->player()->engine()->type());
|
||||
const uint volume = app_->player()->GetVolume();
|
||||
ui_->volume->SetValueFromVolume(volume);
|
||||
ui_->volume->SetValue(volume);
|
||||
VolumeChanged(volume);
|
||||
|
||||
// Models
|
||||
@@ -583,7 +586,7 @@ MainWindow::MainWindow(Application *app, std::shared_ptr<SystemTrayIcon> tray_ic
|
||||
ui_->stop_button->setMenu(stop_menu);
|
||||
|
||||
// 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::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::PlaylistFinished, osd_, &OSDBase::PlaylistFinished);
|
||||
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_->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_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);
|
||||
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_->songs_collection_view(), &InternetCollectionView::AddToPlaylistSignal, 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);
|
||||
}
|
||||
#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);
|
||||
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);
|
||||
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());
|
||||
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>(tab_mode_int);
|
||||
if (tab_mode == FancyTabWidget::Mode_None) tab_mode = default_mode;
|
||||
FancyTabWidget::Mode default_mode = FancyTabWidget::Mode::LargeSidebar;
|
||||
FancyTabWidget::Mode tab_mode = static_cast<FancyTabWidget::Mode>(settings_.value("tab_mode", static_cast<int>(default_mode)).toInt());
|
||||
if (tab_mode == FancyTabWidget::Mode::None) tab_mode = default_mode;
|
||||
ui_->tabs->SetMode(tab_mode);
|
||||
|
||||
TabSwitched();
|
||||
@@ -950,26 +947,26 @@ MainWindow::MainWindow(Application *app, std::shared_ptr<SystemTrayIcon> tray_ic
|
||||
#else
|
||||
QSettings s;
|
||||
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();
|
||||
switch (behaviour) {
|
||||
case BehaviourSettingsPage::Startup_Show:
|
||||
switch (startupbehaviour) {
|
||||
case BehaviourSettingsPage::StartupBehaviour::Show:
|
||||
show();
|
||||
break;
|
||||
case BehaviourSettingsPage::Startup_ShowMaximized:
|
||||
case BehaviourSettingsPage::StartupBehaviour::ShowMaximized:
|
||||
setWindowState(windowState() | Qt::WindowMaximized);
|
||||
show();
|
||||
break;
|
||||
case BehaviourSettingsPage::Startup_ShowMinimized:
|
||||
case BehaviourSettingsPage::StartupBehaviour::ShowMinimized:
|
||||
setWindowState(windowState() | Qt::WindowMinimized);
|
||||
show();
|
||||
break;
|
||||
case BehaviourSettingsPage::Startup_Hide:
|
||||
case BehaviourSettingsPage::StartupBehaviour::Hide:
|
||||
if (tray_icon_->IsSystemTrayAvailable() && tray_icon_->isVisible()) {
|
||||
break;
|
||||
}
|
||||
[[fallthrough]];
|
||||
case BehaviourSettingsPage::Startup_Remember:
|
||||
case BehaviourSettingsPage::StartupBehaviour::Remember:
|
||||
default: {
|
||||
|
||||
was_maximized_ = settings_.value("maximized", true).toBool();
|
||||
@@ -1041,6 +1038,14 @@ MainWindow::MainWindow(Application *app, std::shared_ptr<SystemTrayIcon> tray_ic
|
||||
}
|
||||
#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();
|
||||
initialized_ = true;
|
||||
|
||||
@@ -1071,10 +1076,10 @@ void MainWindow::ReloadSettings() {
|
||||
playing_widget_ = s.value("playing_widget", true).toBool();
|
||||
bool trayicon_progress = s.value("trayicon_progress", false).toBool();
|
||||
if (playing_widget_ != ui_->widget_playing->IsEnabled()) TabSwitched();
|
||||
doubleclick_addmode_ = BehaviourSettingsPage::AddBehaviour(s.value("doubleclick_addmode", BehaviourSettingsPage::AddBehaviour_Append).toInt());
|
||||
doubleclick_playmode_ = BehaviourSettingsPage::PlayBehaviour(s.value("doubleclick_playmode", BehaviourSettingsPage::PlayBehaviour_Never).toInt());
|
||||
doubleclick_playlist_addmode_ = BehaviourSettingsPage::PlaylistAddBehaviour(s.value("doubleclick_playlist_addmode", BehaviourSettingsPage::PlayBehaviour_Never).toInt());
|
||||
menu_playmode_ = BehaviourSettingsPage::PlayBehaviour(s.value("menu_playmode", BehaviourSettingsPage::PlayBehaviour_Never).toInt());
|
||||
doubleclick_addmode_ = static_cast<BehaviourSettingsPage::AddBehaviour>(s.value("doubleclick_addmode", static_cast<int>(BehaviourSettingsPage::AddBehaviour::Append)).toInt());
|
||||
doubleclick_playmode_ = static_cast<BehaviourSettingsPage::PlayBehaviour>(s.value("doubleclick_playmode", static_cast<int>(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_ = static_cast<BehaviourSettingsPage::PlayBehaviour>(s.value("menu_playmode", static_cast<int>(BehaviourSettingsPage::PlayBehaviour::Never)).toInt());
|
||||
s.endGroup();
|
||||
|
||||
s.beginGroup(AppearanceSettingsPage::kSettingsGroup);
|
||||
@@ -1164,7 +1169,6 @@ void MainWindow::ReloadAllSettings() {
|
||||
collection_view_->ReloadSettings();
|
||||
ui_->playlist->view()->ReloadSettings();
|
||||
app_->playlist_manager()->playlist_container()->ReloadSettings();
|
||||
app_->album_cover_loader()->ReloadSettings();
|
||||
album_cover_choice_controller_->ReloadSettings();
|
||||
context_view_->ReloadSettings();
|
||||
file_view_->ReloadSettings();
|
||||
@@ -1201,6 +1205,7 @@ void MainWindow::SaveSettings() {
|
||||
|
||||
SaveGeometry();
|
||||
SavePlaybackStatus();
|
||||
app_->player()->SaveVolume();
|
||||
ui_->tabs->SaveSettings(kSettingsGroup);
|
||||
ui_->playlist->view()->SaveSettings();
|
||||
app_->scrobbler()->WriteCache();
|
||||
@@ -1227,7 +1232,7 @@ void MainWindow::Exit() {
|
||||
if (app_->player()->engine()->is_fadeout_enabled()) {
|
||||
// To shut down the application when fadeout will be finished
|
||||
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();
|
||||
ignore_close_ = true;
|
||||
close();
|
||||
@@ -1325,8 +1330,8 @@ void MainWindow::MediaPlaying() {
|
||||
|
||||
PlaylistItemPtr item(app_->player()->GetCurrentItem());
|
||||
if (item) {
|
||||
enable_play_pause = !(item->options() & PlaylistItem::PauseDisabled);
|
||||
can_seek = !(item->options() & PlaylistItem::SeekDisabled);
|
||||
enable_play_pause = !(item->options() & PlaylistItem::Option::PauseDisabled);
|
||||
can_seek = !(item->options() & PlaylistItem::Option::SeekDisabled);
|
||||
}
|
||||
ui_->action_play_pause->setEnabled(enable_play_pause);
|
||||
ui_->track_slider->SetCanSeek(can_seek);
|
||||
@@ -1440,8 +1445,8 @@ void MainWindow::SavePlaybackStatus() {
|
||||
QSettings s;
|
||||
|
||||
s.beginGroup(Player::kSettingsGroup);
|
||||
s.setValue("playback_state", app_->player()->GetState());
|
||||
if (app_->player()->GetState() == Engine::Playing || app_->player()->GetState() == Engine::Paused) {
|
||||
s.setValue("playback_state", static_cast<int>(app_->player()->GetState()));
|
||||
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_position", app_->player()->engine()->position_nanosec() / kNsecPerSec);
|
||||
}
|
||||
@@ -1459,14 +1464,14 @@ void MainWindow::LoadPlaybackStatus() {
|
||||
QSettings s;
|
||||
|
||||
s.beginGroup(BehaviourSettingsPage::kSettingsGroup);
|
||||
bool resume_playback = s.value("resumeplayback", false).toBool();
|
||||
const bool resume_playback = s.value("resumeplayback", false).toBool();
|
||||
s.endGroup();
|
||||
|
||||
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();
|
||||
|
||||
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>();
|
||||
*connection = QObject::connect(app_->playlist_manager(), &PlaylistManager::AllPlaylistsLoaded, this, [this, connection]() {
|
||||
QObject::disconnect(*connection);
|
||||
@@ -1482,7 +1487,7 @@ void MainWindow::ResumePlayback() {
|
||||
|
||||
QSettings s;
|
||||
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_position = s.value("playback_position", 0).toInt();
|
||||
s.endGroup();
|
||||
@@ -1490,7 +1495,7 @@ void MainWindow::ResumePlayback() {
|
||||
if (playback_playlist == app_->playlist_manager()->current()->id()) {
|
||||
// Set active to current to resume playback on correct playlist.
|
||||
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>();
|
||||
*connection = QObject::connect(app_->player(), &Player::Playing, app_->player(), [this, 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.
|
||||
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_position", 0);
|
||||
s.endGroup();
|
||||
@@ -1520,7 +1525,7 @@ void MainWindow::PlayIndex(const QModelIndex &idx, Playlist::AutoScroll autoscro
|
||||
}
|
||||
|
||||
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_) {
|
||||
case BehaviourSettingsPage::PlaylistAddBehaviour_Play:
|
||||
case BehaviourSettingsPage::PlaylistAddBehaviour::Play:
|
||||
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;
|
||||
|
||||
case BehaviourSettingsPage::PlaylistAddBehaviour_Enqueue:
|
||||
case BehaviourSettingsPage::PlaylistAddBehaviour::Enqueue:
|
||||
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_->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;
|
||||
}
|
||||
@@ -1697,22 +1702,22 @@ void MainWindow::UpdateTrackSliderPosition() {
|
||||
void MainWindow::ApplyAddBehaviour(const BehaviourSettingsPage::AddBehaviour b, MimeData *mimedata) {
|
||||
|
||||
switch (b) {
|
||||
case BehaviourSettingsPage::AddBehaviour_Append:
|
||||
case BehaviourSettingsPage::AddBehaviour::Append:
|
||||
mimedata->clear_first_ = false;
|
||||
mimedata->enqueue_now_ = false;
|
||||
break;
|
||||
|
||||
case BehaviourSettingsPage::AddBehaviour_Enqueue:
|
||||
case BehaviourSettingsPage::AddBehaviour::Enqueue:
|
||||
mimedata->clear_first_ = false;
|
||||
mimedata->enqueue_now_ = true;
|
||||
break;
|
||||
|
||||
case BehaviourSettingsPage::AddBehaviour_Load:
|
||||
case BehaviourSettingsPage::AddBehaviour::Load:
|
||||
mimedata->clear_first_ = true;
|
||||
mimedata->enqueue_now_ = false;
|
||||
break;
|
||||
|
||||
case BehaviourSettingsPage::AddBehaviour_OpenInNew:
|
||||
case BehaviourSettingsPage::AddBehaviour::OpenInNew:
|
||||
mimedata->open_in_new_playlist_ = true;
|
||||
break;
|
||||
}
|
||||
@@ -1721,16 +1726,16 @@ void MainWindow::ApplyAddBehaviour(const BehaviourSettingsPage::AddBehaviour b,
|
||||
void MainWindow::ApplyPlayBehaviour(const BehaviourSettingsPage::PlayBehaviour b, MimeData *mimedata) const {
|
||||
|
||||
switch (b) {
|
||||
case BehaviourSettingsPage::PlayBehaviour_Always:
|
||||
case BehaviourSettingsPage::PlayBehaviour::Always:
|
||||
mimedata->play_now_ = true;
|
||||
break;
|
||||
|
||||
case BehaviourSettingsPage::PlayBehaviour_Never:
|
||||
case BehaviourSettingsPage::PlayBehaviour::Never:
|
||||
mimedata->play_now_ = false;
|
||||
break;
|
||||
|
||||
case BehaviourSettingsPage::PlayBehaviour_IfStopped:
|
||||
mimedata->play_now_ = !(app_->player()->GetState() == Engine::Playing);
|
||||
case BehaviourSettingsPage::PlayBehaviour::IfStopped:
|
||||
mimedata->play_now_ = !(app_->player()->GetState() == Engine::State::Playing);
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -1765,7 +1770,7 @@ void MainWindow::AddToPlaylist(QMimeData *q_mimedata) {
|
||||
void MainWindow::AddToPlaylistFromAction(QAction *action) {
|
||||
|
||||
const int destination = action->data().toInt();
|
||||
PlaylistItemList items;
|
||||
PlaylistItemPtrList items;
|
||||
SongList songs;
|
||||
|
||||
// Get the selected playlist items
|
||||
@@ -1816,7 +1821,7 @@ void MainWindow::PlaylistRightClick(const QPoint global_pos, const QModelIndex &
|
||||
playlist_menu_index_ = source_index;
|
||||
|
||||
// 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_->setIcon(IconLoader::Load("media-playback-pause"));
|
||||
}
|
||||
@@ -1827,7 +1832,7 @@ void MainWindow::PlaylistRightClick(const QPoint global_pos, const QModelIndex &
|
||||
|
||||
// Are we allowed to pause?
|
||||
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 {
|
||||
playlist_play_pause_->setEnabled(false);
|
||||
@@ -1856,7 +1861,7 @@ void MainWindow::PlaylistRightClick(const QPoint global_pos, const QModelIndex &
|
||||
if (!item) continue;
|
||||
|
||||
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()) {
|
||||
cue_selected = true;
|
||||
@@ -2036,10 +2041,10 @@ void MainWindow::PlaylistRightClick(const QPoint global_pos, const QModelIndex &
|
||||
void MainWindow::PlaylistPlay() {
|
||||
|
||||
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 {
|
||||
PlayIndex(playlist_menu_index_, Playlist::AutoScroll_Never);
|
||||
PlayIndex(playlist_menu_index_, Playlist::AutoScroll::Never);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -2060,7 +2065,7 @@ void MainWindow::RescanSongs() {
|
||||
if (item->IsLocalCollectionItem()) {
|
||||
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);
|
||||
app_->playlist_manager()->current()->ItemReload(persistent_index, item->OriginalMetadata(), false);
|
||||
}
|
||||
@@ -2075,7 +2080,7 @@ void MainWindow::RescanSongs() {
|
||||
void MainWindow::EditTracks() {
|
||||
|
||||
SongList songs;
|
||||
PlaylistItemList items;
|
||||
PlaylistItemPtrList items;
|
||||
|
||||
for (const QModelIndex &proxy_index : ui_->playlist->view()->selectionModel()->selectedRows()) {
|
||||
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()) {
|
||||
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);
|
||||
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);
|
||||
}
|
||||
}
|
||||
@@ -2202,7 +2207,7 @@ void MainWindow::AddFile() {
|
||||
PlaylistParser parser(app_->collection_backend());
|
||||
|
||||
// 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;
|
||||
|
||||
@@ -2338,30 +2343,30 @@ void MainWindow::CommandlineOptionsReceived(const quint32 instanceId, const QByt
|
||||
void MainWindow::CommandlineOptionsReceived(const CommandlineOptions &options) {
|
||||
|
||||
switch (options.player_action()) {
|
||||
case CommandlineOptions::Player_Play:
|
||||
case CommandlineOptions::PlayerAction::Play:
|
||||
if (options.urls().empty()) {
|
||||
app_->player()->Play();
|
||||
}
|
||||
break;
|
||||
case CommandlineOptions::Player_PlayPause:
|
||||
app_->player()->PlayPause(Playlist::AutoScroll_Maybe);
|
||||
case CommandlineOptions::PlayerAction::PlayPause:
|
||||
app_->player()->PlayPause(0, Playlist::AutoScroll::Maybe);
|
||||
break;
|
||||
case CommandlineOptions::Player_Pause:
|
||||
case CommandlineOptions::PlayerAction::Pause:
|
||||
app_->player()->Pause();
|
||||
break;
|
||||
case CommandlineOptions::Player_Stop:
|
||||
case CommandlineOptions::PlayerAction::Stop:
|
||||
app_->player()->Stop();
|
||||
break;
|
||||
case CommandlineOptions::Player_StopAfterCurrent:
|
||||
case CommandlineOptions::PlayerAction::StopAfterCurrent:
|
||||
app_->player()->StopAfterCurrent();
|
||||
break;
|
||||
case CommandlineOptions::Player_Previous:
|
||||
case CommandlineOptions::PlayerAction::Previous:
|
||||
app_->player()->Previous();
|
||||
break;
|
||||
case CommandlineOptions::Player_Next:
|
||||
case CommandlineOptions::PlayerAction::Next:
|
||||
app_->player()->Next();
|
||||
break;
|
||||
case CommandlineOptions::Player_PlayPlaylist:
|
||||
case CommandlineOptions::PlayerAction::PlayPlaylist:
|
||||
if (options.playlist_name().isEmpty()) {
|
||||
qLog(Error) << "ERROR: playlist name missing";
|
||||
}
|
||||
@@ -2369,11 +2374,11 @@ void MainWindow::CommandlineOptionsReceived(const CommandlineOptions &options) {
|
||||
app_->player()->PlayPlaylist(options.playlist_name());
|
||||
}
|
||||
break;
|
||||
case CommandlineOptions::Player_RestartOrPrevious:
|
||||
case CommandlineOptions::PlayerAction::RestartOrPrevious:
|
||||
app_->player()->RestartOrPrevious();
|
||||
break;
|
||||
|
||||
case CommandlineOptions::Player_ResizeWindow:{
|
||||
case CommandlineOptions::PlayerAction::ResizeWindow:{
|
||||
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_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;
|
||||
}
|
||||
|
||||
case CommandlineOptions::Player_None:
|
||||
case CommandlineOptions::PlayerAction::None:
|
||||
break;
|
||||
|
||||
}
|
||||
@@ -2430,22 +2435,22 @@ void MainWindow::CommandlineOptionsReceived(const CommandlineOptions &options) {
|
||||
// Behaviour depends on command line options, so set it here
|
||||
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);
|
||||
|
||||
switch (options.url_list_action()) {
|
||||
case CommandlineOptions::UrlList_Load:
|
||||
case CommandlineOptions::UrlListAction::Load:
|
||||
mimedata->clear_first_ = true;
|
||||
break;
|
||||
case CommandlineOptions::UrlList_Append:
|
||||
case CommandlineOptions::UrlListAction::Append:
|
||||
// Nothing to do
|
||||
break;
|
||||
case CommandlineOptions::UrlList_None:
|
||||
case CommandlineOptions::UrlListAction::None:
|
||||
ApplyAddBehaviour(doubleclick_addmode_, mimedata);
|
||||
break;
|
||||
case CommandlineOptions::UrlList_CreateNew:
|
||||
case CommandlineOptions::UrlListAction::CreateNew:
|
||||
mimedata->name_for_new_playlist_ = options.playlist_name();
|
||||
ApplyAddBehaviour(BehaviourSettingsPage::AddBehaviour_OpenInNew, mimedata);
|
||||
ApplyAddBehaviour(BehaviourSettingsPage::AddBehaviour::OpenInNew, mimedata);
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -2465,7 +2470,7 @@ void MainWindow::CommandlineOptionsReceived(const CommandlineOptions &options) {
|
||||
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();
|
||||
|
||||
@@ -2543,7 +2548,7 @@ void MainWindow::AddFilesToTranscoder() {
|
||||
}
|
||||
|
||||
void MainWindow::ShowCollectionConfig() {
|
||||
settings_dialog_->OpenAtPage(SettingsDialog::Page_Collection);
|
||||
settings_dialog_->OpenAtPage(SettingsDialog::Page::Collection);
|
||||
}
|
||||
|
||||
void MainWindow::TaskCountChanged(const int count) {
|
||||
@@ -2613,7 +2618,7 @@ void MainWindow::EditFileTags(const QList<QUrl> &urls) {
|
||||
Song song;
|
||||
song.set_url(url);
|
||||
song.set_valid(true);
|
||||
song.set_filetype(Song::FileType_MPEG);
|
||||
song.set_filetype(Song::FileType::MPEG);
|
||||
songs << song;
|
||||
}
|
||||
|
||||
@@ -2755,16 +2760,16 @@ void MainWindow::PlaylistCopyToDevice() {
|
||||
|
||||
}
|
||||
|
||||
void MainWindow::ChangeCollectionQueryMode(QAction *action) {
|
||||
void MainWindow::ChangeCollectionFilterMode(QAction *action) {
|
||||
|
||||
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_) {
|
||||
collection_view_->filter_widget()->SetQueryMode(QueryOptions::QueryMode_Untagged);
|
||||
collection_view_->filter_widget()->SetFilterMode(CollectionFilterOptions::FilterMode::Untagged);
|
||||
}
|
||||
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 {
|
||||
qLog(Debug) << "The current playlist is empty, showing a fake song";
|
||||
// Create a fake song
|
||||
Song fake(Song::Source_LocalFile);
|
||||
Song fake(Song::Source::LocalFile);
|
||||
fake.Init("Title", "Artist", "Album", 123);
|
||||
fake.set_genre("Classical");
|
||||
fake.set_composer("Anonymous");
|
||||
@@ -2999,7 +3004,7 @@ void MainWindow::ShowConsole() {
|
||||
void MainWindow::keyPressEvent(QKeyEvent *e) {
|
||||
|
||||
if (e->key() == Qt::Key_Space) {
|
||||
app_->player()->PlayPause(Playlist::AutoScroll_Never);
|
||||
app_->player()->PlayPause(0, Playlist::AutoScroll::Never);
|
||||
e->accept();
|
||||
}
|
||||
else if (e->key() == Qt::Key_Left) {
|
||||
@@ -3122,12 +3127,12 @@ void MainWindow::SetToggleScrobblingIcon(const bool value) {
|
||||
|
||||
if (value) {
|
||||
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
|
||||
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 {
|
||||
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 (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();
|
||||
}
|
||||
|
||||
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();
|
||||
}
|
||||
|
||||
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);
|
||||
//QObject::connect(delete_files, &DeleteFiles::Finished, this, &MainWindow::DeleteFinished);
|
||||
delete_files->Start(selected_songs);
|
||||
|
||||
@@ -48,12 +48,12 @@
|
||||
#include <QSettings>
|
||||
#include <QtEvents>
|
||||
|
||||
#include "core/lazy.h"
|
||||
#include "core/tagreaderclient.h"
|
||||
#include "core/song.h"
|
||||
#include "lazy.h"
|
||||
#include "platforminterface.h"
|
||||
#include "song.h"
|
||||
#include "tagreaderclient.h"
|
||||
#include "engine/enginetype.h"
|
||||
#include "engine/engine_fwd.h"
|
||||
#include "mac_startup.h"
|
||||
#include "osd/osdbase.h"
|
||||
#include "collection/collectionmodel.h"
|
||||
#include "playlist/playlist.h"
|
||||
@@ -180,7 +180,7 @@ class MainWindow : public QMainWindow, public PlatformInterface {
|
||||
void PlaylistCopyUrl();
|
||||
void ShowInCollection();
|
||||
|
||||
void ChangeCollectionQueryMode(QAction *action);
|
||||
void ChangeCollectionFilterMode(QAction *action);
|
||||
|
||||
void PlayIndex(const QModelIndex &idx, Playlist::AutoScroll autoscroll);
|
||||
void PlaylistDoubleClick(const QModelIndex &idx);
|
||||
@@ -331,7 +331,7 @@ class MainWindow : public QMainWindow, public PlatformInterface {
|
||||
std::unique_ptr<TagFetcher> tag_fetcher_;
|
||||
#endif
|
||||
std::unique_ptr<TrackSelectionDialog> track_selection_dialog_;
|
||||
PlaylistItemList autocomplete_tag_items_;
|
||||
PlaylistItemPtrList autocomplete_tag_items_;
|
||||
|
||||
SmartPlaylistsViewContainer *smartplaylists_view_;
|
||||
|
||||
|
||||
@@ -38,7 +38,6 @@
|
||||
#include <QByteArray>
|
||||
#include <QUrl>
|
||||
#include <QImage>
|
||||
#include <QNetworkCookie>
|
||||
#include <QNetworkReply>
|
||||
#include <QItemSelection>
|
||||
#ifdef HAVE_DBUS
|
||||
@@ -54,7 +53,7 @@
|
||||
#ifdef HAVE_GSTREAMER
|
||||
# include "engine/gstenginepipeline.h"
|
||||
#endif
|
||||
#include "collection/directory.h"
|
||||
#include "collection/collectiondirectory.h"
|
||||
#include "playlist/playlistitem.h"
|
||||
#include "playlist/playlistsequence.h"
|
||||
#include "covermanager/albumcoverloaderresult.h"
|
||||
@@ -78,17 +77,18 @@
|
||||
# include "device/mtpconnection.h"
|
||||
#endif
|
||||
|
||||
#include "settings/playlistsettingspage.h"
|
||||
|
||||
#include "smartplaylists/smartplaylistsearchterm.h"
|
||||
#include "smartplaylists/smartplaylistsitem.h"
|
||||
|
||||
void RegisterMetaTypes() {
|
||||
|
||||
qRegisterMetaType<const char*>("const char*");
|
||||
qRegisterMetaType<QList<int>>("QList<int>");
|
||||
qRegisterMetaType<QList<QUrl>>("QList<QUrl>");
|
||||
qRegisterMetaType<QVector<int>>("QVector<int>");
|
||||
qRegisterMetaType<QList<QUrl>>("QList<QUrl>");
|
||||
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<QItemSelection>("QItemSelection");
|
||||
@@ -98,16 +98,12 @@ void RegisterMetaTypes() {
|
||||
qRegisterMetaTypeStreamOperators<QMap<int, Qt::Alignment>>("ColumnAlignmentMap");
|
||||
qRegisterMetaTypeStreamOperators<QMap<int, int>>("ColumnAlignmentIntMap");
|
||||
#endif
|
||||
qRegisterMetaType<Directory>("Directory");
|
||||
qRegisterMetaType<DirectoryList>("DirectoryList");
|
||||
qRegisterMetaType<Subdirectory>("Subdirectory");
|
||||
qRegisterMetaType<SubdirectoryList>("SubdirectoryList");
|
||||
qRegisterMetaType<Song>("Song");
|
||||
qRegisterMetaType<SongList>("SongList");
|
||||
qRegisterMetaType<SongMap>("SongMap");
|
||||
qRegisterMetaType<QList<Song>>("QList<Song>");
|
||||
qRegisterMetaType<QMap<QString, Song>>("QMap<QString, Song>");
|
||||
qRegisterMetaType<Engine::EngineType>("EngineType");
|
||||
qRegisterMetaType<Song::Source>("Song::Source");
|
||||
qRegisterMetaType<Song::FileType>("Song::FileType");
|
||||
qRegisterMetaType<Engine::EngineType>("Engine::EngineType");
|
||||
qRegisterMetaType<Engine::SimpleMetaBundle>("Engine::SimpleMetaBundle");
|
||||
qRegisterMetaType<Engine::State>("Engine::State");
|
||||
qRegisterMetaType<Engine::TrackChangeFlags>("Engine::TrackChangeFlags");
|
||||
@@ -117,23 +113,29 @@ void RegisterMetaTypes() {
|
||||
qRegisterMetaType<GstElement*>("GstElement*");
|
||||
qRegisterMetaType<GstEnginePipeline*>("GstEnginePipeline*");
|
||||
#endif
|
||||
qRegisterMetaType<CollectionDirectory>("CollectionDirectory");
|
||||
qRegisterMetaType<CollectionDirectoryList>("CollectionDirectoryList");
|
||||
qRegisterMetaType<CollectionSubdirectory>("CollectionSubdirectory");
|
||||
qRegisterMetaType<CollectionSubdirectoryList>("CollectionSubdirectoryList");
|
||||
qRegisterMetaType<CollectionModel::Grouping>("CollectionModel::Grouping");
|
||||
qRegisterMetaType<PlaylistItemPtr>("PlaylistItemPtr");
|
||||
qRegisterMetaType<PlaylistItemList>("PlaylistItemList");
|
||||
qRegisterMetaType<QList<PlaylistItemPtr>>("QList<PlaylistItemPtr>");
|
||||
qRegisterMetaType<PlaylistItemPtrList>("PlaylistItemPtrList");
|
||||
qRegisterMetaType<PlaylistSequence::RepeatMode>("PlaylistSequence::RepeatMode");
|
||||
qRegisterMetaType<PlaylistSequence::ShuffleMode>("PlaylistSequence::ShuffleMode");
|
||||
qRegisterMetaType<AlbumCoverLoaderResult>("AlbumCoverLoaderResult");
|
||||
qRegisterMetaType<AlbumCoverLoaderResult::Type>("AlbumCoverLoaderResult::Type");
|
||||
qRegisterMetaType<CoverProviderSearchResult>("CoverProviderSearchResult");
|
||||
qRegisterMetaType<CoverSearchStatistics>("CoverSearchStatistics");
|
||||
qRegisterMetaType<QList<CoverProviderSearchResult>>("QList<CoverProviderSearchResult>");
|
||||
qRegisterMetaType<CoverProviderSearchResults>("CoverProviderSearchResults");
|
||||
qRegisterMetaType<CoverSearchStatistics>("CoverSearchStatistics");
|
||||
|
||||
qRegisterMetaType<Equalizer::Params>("Equalizer::Params");
|
||||
|
||||
#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
|
||||
qRegisterMetaTypeStreamOperators<Equalizer::Params>("Equalizer::Params");
|
||||
#endif
|
||||
|
||||
#ifdef HAVE_DBUS
|
||||
qDBusRegisterMetaType<QList<QByteArray>>();
|
||||
qDBusRegisterMetaType<QByteArrayList>();
|
||||
qDBusRegisterMetaType<QImage>();
|
||||
qDBusRegisterMetaType<TrackMetadata>();
|
||||
qDBusRegisterMetaType<Track_Ids>();
|
||||
@@ -144,15 +146,21 @@ void RegisterMetaTypes() {
|
||||
qDBusRegisterMetaType<ManagedObjectList>();
|
||||
#endif
|
||||
|
||||
qRegisterMetaType<InternetSearchView::ResultList>("InternetSearchView::ResultList");
|
||||
qRegisterMetaType<InternetSearchView::Result>("InternetSearchView::Result");
|
||||
qRegisterMetaType<InternetSearchView::ResultList>("InternetSearchView::ResultList");
|
||||
|
||||
qRegisterMetaType<PlaylistGeneratorPtr>("PlaylistGeneratorPtr");
|
||||
|
||||
qRegisterMetaType<RadioChannel>("RadioChannel");
|
||||
qRegisterMetaType<RadioChannelList>("RadioChannelList");
|
||||
|
||||
#ifdef HAVE_LIBMTP
|
||||
qRegisterMetaType<MtpConnection*>("MtpConnection*");
|
||||
#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 <algorithm>
|
||||
#include <cmath>
|
||||
|
||||
#include <QApplication>
|
||||
#include <QCoreApplication>
|
||||
@@ -45,10 +46,10 @@
|
||||
#include "mpris_common.h"
|
||||
#include "mpris2.h"
|
||||
|
||||
#include "timeconstants.h"
|
||||
#include "song.h"
|
||||
#include "application.h"
|
||||
#include "player.h"
|
||||
#include "utilities/timeconstants.h"
|
||||
#include "engine/enginebase.h"
|
||||
#include "playlist/playlist.h"
|
||||
#include "playlist/playlistitem.h"
|
||||
@@ -163,7 +164,7 @@ void Mpris2::PlaylistManagerInitialized() {
|
||||
|
||||
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();
|
||||
EmitNotification("Metadata");
|
||||
}
|
||||
@@ -171,11 +172,13 @@ void Mpris2::EngineStateChanged(Engine::State newState) {
|
||||
EmitNotification("CanPlay");
|
||||
EmitNotification("CanPause");
|
||||
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"); }
|
||||
|
||||
@@ -187,15 +190,15 @@ void Mpris2::RepeatModeChanged() {
|
||||
|
||||
}
|
||||
|
||||
void Mpris2::EmitNotification(const QString &name, const QVariant &val) {
|
||||
EmitNotification(name, val, "org.mpris.MediaPlayer2.Player");
|
||||
void Mpris2::EmitNotification(const QString &name, const QVariant &value) {
|
||||
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");
|
||||
QVariantMap map;
|
||||
map.insert(name, val);
|
||||
map.insert(name, value);
|
||||
QVariantList args = QVariantList() << mprisEntity << map << QStringList();
|
||||
msg.setArguments(args);
|
||||
QDBusConnection::sessionBus().send(msg);
|
||||
@@ -304,8 +307,8 @@ QString Mpris2::PlaybackStatus() const {
|
||||
QString Mpris2::PlaybackStatus(Engine::State state) const {
|
||||
|
||||
switch (state) {
|
||||
case Engine::Playing: return "Playing";
|
||||
case Engine::Paused: return "Paused";
|
||||
case Engine::State::Playing: return "Playing";
|
||||
case Engine::State::Paused: return "Paused";
|
||||
default: return "Stopped";
|
||||
}
|
||||
|
||||
@@ -318,9 +321,9 @@ QString Mpris2::LoopStatus() const {
|
||||
}
|
||||
|
||||
switch (app_->playlist_manager()->sequence()->repeat_mode()) {
|
||||
case PlaylistSequence::Repeat_Album:
|
||||
case PlaylistSequence::Repeat_Playlist: return "Playlist";
|
||||
case PlaylistSequence::Repeat_Track: return "Track";
|
||||
case PlaylistSequence::RepeatMode::Album:
|
||||
case PlaylistSequence::RepeatMode::Playlist: return "Playlist";
|
||||
case PlaylistSequence::RepeatMode::Track: return "Track";
|
||||
default: return "None";
|
||||
}
|
||||
|
||||
@@ -328,16 +331,16 @@ QString Mpris2::LoopStatus() const {
|
||||
|
||||
void Mpris2::SetLoopStatus(const QString &value) {
|
||||
|
||||
PlaylistSequence::RepeatMode mode = PlaylistSequence::Repeat_Off;
|
||||
PlaylistSequence::RepeatMode mode = PlaylistSequence::RepeatMode::Off;
|
||||
|
||||
if (value == "None") {
|
||||
mode = PlaylistSequence::Repeat_Off;
|
||||
mode = PlaylistSequence::RepeatMode::Off;
|
||||
}
|
||||
else if (value == "Track") {
|
||||
mode = PlaylistSequence::Repeat_Track;
|
||||
mode = PlaylistSequence::RepeatMode::Track;
|
||||
}
|
||||
else if (value == "Playlist") {
|
||||
mode = PlaylistSequence::Repeat_Playlist;
|
||||
mode = PlaylistSequence::RepeatMode::Playlist;
|
||||
}
|
||||
|
||||
app_->playlist_manager()->active()->sequence()->SetRepeatMode(mode);
|
||||
@@ -356,12 +359,12 @@ void Mpris2::SetRate(double rate) {
|
||||
|
||||
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) {
|
||||
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_; }
|
||||
@@ -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()) {
|
||||
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_);
|
||||
|
||||
AddMetadata("year", song.year(), &last_metadata_);
|
||||
@@ -411,8 +421,8 @@ double Mpris2::Volume() const {
|
||||
return app_->player()->GetVolume() / 100.0;
|
||||
}
|
||||
|
||||
void Mpris2::SetVolume(const double value) {
|
||||
app_->player()->SetVolume(static_cast<uint>(std::max(std::min(lround(value * 100.0), 100L), 0L)));
|
||||
void Mpris2::SetVolume(const double volume) {
|
||||
app_->player()->SetVolume(static_cast<uint>(qBound(0L, lround(volume * 100.0), 100L)));
|
||||
}
|
||||
|
||||
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.
|
||||
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(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; }
|
||||
@@ -461,7 +471,7 @@ void Mpris2::Previous() {
|
||||
}
|
||||
|
||||
void Mpris2::Pause() {
|
||||
if (CanPause() && app_->player()->GetState() != Engine::Paused) {
|
||||
if (CanPause() && app_->player()->GetState() != Engine::State::Paused) {
|
||||
app_->player()->Pause();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -145,7 +145,7 @@ class Mpris2 : public QObject {
|
||||
void SetShuffle(bool enable);
|
||||
QVariantMap Metadata() const;
|
||||
double Volume() const;
|
||||
void SetVolume(const double value);
|
||||
void SetVolume(const double volume);
|
||||
qint64 Position() const;
|
||||
double MaximumRate() const;
|
||||
double MinimumRate() const;
|
||||
@@ -215,8 +215,8 @@ class Mpris2 : public QObject {
|
||||
|
||||
private:
|
||||
void EmitNotification(const QString &name);
|
||||
void EmitNotification(const QString &name, const QVariant &val);
|
||||
void EmitNotification(const QString &name, const QVariant &val, const QString &mprisEntity);
|
||||
void EmitNotification(const QString &name, const QVariant &value);
|
||||
void EmitNotification(const QString &name, const QVariant &value, const QString &mprisEntity);
|
||||
|
||||
QString PlaybackStatus(Engine::State state) const;
|
||||
|
||||
|
||||
@@ -50,7 +50,7 @@ class MusicStorage {
|
||||
};
|
||||
|
||||
// Values are saved in the database - don't change
|
||||
enum TranscodeMode {
|
||||
enum class TranscodeMode {
|
||||
Transcode_Always = 1,
|
||||
Transcode_Never = 2,
|
||||
Transcode_Unsupported = 3,
|
||||
@@ -83,8 +83,8 @@ class MusicStorage {
|
||||
virtual QString LocalPath() const { return QString(); }
|
||||
virtual std::optional<int> collection_directory_id() const { return std::optional<int>(); }
|
||||
|
||||
virtual TranscodeMode GetTranscodeMode() const { return Transcode_Never; }
|
||||
virtual Song::FileType GetTranscodeFormat() const { return Song::FileType_Unknown; }
|
||||
virtual TranscodeMode GetTranscodeMode() const { return TranscodeMode::Transcode_Never; }
|
||||
virtual Song::FileType GetTranscodeFormat() const { return Song::FileType::Unknown; }
|
||||
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; }
|
||||
|
||||
@@ -35,7 +35,7 @@ NetworkProxyFactory *NetworkProxyFactory::sInstance = nullptr;
|
||||
const char *NetworkProxyFactory::kSettingsGroup = "NetworkProxy";
|
||||
|
||||
NetworkProxyFactory::NetworkProxyFactory()
|
||||
: mode_(Mode_System),
|
||||
: mode_(Mode::System),
|
||||
type_(QNetworkProxy::HttpProxy),
|
||||
port_(8080),
|
||||
use_authentication_(false) {
|
||||
@@ -81,7 +81,7 @@ void NetworkProxyFactory::ReloadSettings() {
|
||||
QSettings s;
|
||||
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());
|
||||
hostname_ = s.value("hostname").toString();
|
||||
port_ = s.value("port", 8080).toInt();
|
||||
@@ -100,7 +100,7 @@ QList<QNetworkProxy> NetworkProxyFactory::queryProxy(const QNetworkProxyQuery &q
|
||||
QNetworkProxy ret;
|
||||
|
||||
switch (mode_) {
|
||||
case Mode_System:
|
||||
case Mode::System:
|
||||
#ifdef Q_OS_LINUX
|
||||
Q_UNUSED(query);
|
||||
|
||||
@@ -125,11 +125,11 @@ QList<QNetworkProxy> NetworkProxyFactory::queryProxy(const QNetworkProxyQuery &q
|
||||
return systemProxyForQuery(query);
|
||||
#endif
|
||||
|
||||
case Mode_Direct:
|
||||
case Mode::Direct:
|
||||
ret.setType(QNetworkProxy::NoProxy);
|
||||
break;
|
||||
|
||||
case Mode_Manual:
|
||||
case Mode::Manual:
|
||||
ret.setType(type_);
|
||||
ret.setHostName(hostname_);
|
||||
ret.setPort(port_);
|
||||
|
||||
@@ -34,7 +34,11 @@
|
||||
class NetworkProxyFactory : public QNetworkProxyFactory {
|
||||
public:
|
||||
// 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 const char *kSettingsGroup;
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
/*
|
||||
* Strawberry Music Player
|
||||
* This file was part of Clementine.
|
||||
* Copyright 2012, Arnaud Bienner <arnaud.bienner@gmail.com>
|
||||
* 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
|
||||
@@ -18,31 +18,23 @@
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef APPEARANCE_H
|
||||
#define APPEARANCE_H
|
||||
|
||||
#include "config.h"
|
||||
#ifndef PLATFORMINTERFACE_H
|
||||
#define PLATFORMINTERFACE_H
|
||||
|
||||
#include <QObject>
|
||||
#include <QColor>
|
||||
#include <QPalette>
|
||||
|
||||
class Appearance : public QObject {
|
||||
Q_OBJECT
|
||||
#include <QString>
|
||||
|
||||
class PlatformInterface {
|
||||
public:
|
||||
explicit Appearance(QObject *parent = nullptr);
|
||||
PlatformInterface() = default;
|
||||
virtual ~PlatformInterface() {}
|
||||
|
||||
static const QPalette kDefaultPalette;
|
||||
|
||||
void LoadUserTheme();
|
||||
static void ResetToSystemDefaultTheme();
|
||||
void ChangeForegroundColor(const QColor &color);
|
||||
void ChangeBackgroundColor(const QColor &color);
|
||||
// Called when the application should show itself.
|
||||
virtual void Activate() = 0;
|
||||
virtual bool LoadUrl(const QString &url) = 0;
|
||||
|
||||
private:
|
||||
QColor foreground_color_;
|
||||
QColor background_color_;
|
||||
Q_DISABLE_COPY(PlatformInterface)
|
||||
};
|
||||
|
||||
#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