Compare commits
137 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c658a77b05 | ||
|
|
1880dc8153 | ||
|
|
b5fd3d5717 | ||
|
|
3c3480fb84 | ||
|
|
f628914173 | ||
|
|
c100fb1bb8 | ||
|
|
8c804c4fba | ||
|
|
912a7c7da9 | ||
|
|
0a5815c82e | ||
|
|
6513b3032b | ||
|
|
8c51401bdc | ||
|
|
45fc9c83d4 | ||
|
|
be57d8147a | ||
|
|
a97908fb6b | ||
|
|
c0417d4bb3 | ||
|
|
062e2cfb84 | ||
|
|
700f7dbe36 | ||
|
|
0487118dad | ||
|
|
f3d088e48b | ||
|
|
f8afd49fcf | ||
|
|
363fcb5aba | ||
|
|
183aba4181 | ||
|
|
742be01aa6 | ||
|
|
38c8054873 | ||
|
|
da9e9840b8 | ||
|
|
c4646531b0 | ||
|
|
65d9b6a9e9 | ||
|
|
046f40fbca | ||
|
|
a0ca50ac30 | ||
|
|
d939733675 | ||
|
|
61a8a3a84a | ||
|
|
d4858a338c | ||
|
|
31380a5bd4 | ||
|
|
e45b9aabeb | ||
|
|
27e782d8cf | ||
|
|
0bfc2ee198 | ||
|
|
e7fc4b1706 | ||
|
|
6dea1a2149 | ||
|
|
7844a2b932 | ||
|
|
96a53bfbe5 | ||
|
|
fe5fbae4b4 | ||
|
|
a9140232e5 | ||
|
|
835090dd96 | ||
|
|
af5590dcb1 | ||
|
|
26b5588d7d | ||
|
|
390bf049f2 | ||
|
|
321272b695 | ||
|
|
342805e0f3 | ||
|
|
e614626913 | ||
|
|
2ddacf2f98 | ||
|
|
a47531d4ce | ||
|
|
84b758e395 | ||
|
|
51b69a85c4 | ||
|
|
52774a3222 | ||
|
|
9030b2567b | ||
|
|
ee7bb449a5 | ||
|
|
d901258f11 | ||
|
|
6372c5ee7d | ||
|
|
75f0402793 | ||
|
|
20e5c014ef | ||
|
|
1ebc32c3aa | ||
|
|
a5f94b608b | ||
|
|
e0d61223a4 | ||
|
|
459eea5bc4 | ||
|
|
09d02c53a3 | ||
|
|
61a701554e | ||
|
|
d280e6426f | ||
|
|
5b9bb3efa7 | ||
|
|
b8cbe49f8c | ||
|
|
633e5707ef | ||
|
|
d54290c3a7 | ||
|
|
3ef2b53e46 | ||
|
|
d3a4dd6da6 | ||
|
|
0158f7f08a | ||
|
|
8cea020fac | ||
|
|
f6b38fecb0 | ||
|
|
5e2729fafe | ||
|
|
19dce1c25d | ||
|
|
00bb722e25 | ||
|
|
cbaf4d3121 | ||
|
|
4b5370044b | ||
|
|
ffbe1ec9fd | ||
|
|
53e43db91b | ||
|
|
2858cdabc2 | ||
|
|
cf74eeb120 | ||
|
|
790a1b4dbf | ||
|
|
ee6332af1e | ||
|
|
bf0704f6b2 | ||
|
|
ae13fe7f52 | ||
|
|
90678e72ac | ||
|
|
a0ec244008 | ||
|
|
fba4f84fb6 | ||
|
|
950774f1c8 | ||
|
|
340bc21537 | ||
|
|
a86ba4dffc | ||
|
|
d6bc6e33c0 | ||
|
|
7e128a9af5 | ||
|
|
0f0746be9d | ||
|
|
bec3fe9fd5 | ||
|
|
83c666baf9 | ||
|
|
b9b54e6e96 | ||
|
|
b2ff6240eb | ||
|
|
26a7c74a24 | ||
|
|
a34954ec4a | ||
|
|
349ab62e75 | ||
|
|
65e960f2c5 | ||
|
|
e22fef8ca4 | ||
|
|
3e99045e2c | ||
|
|
4fcade273e | ||
|
|
5eaff0d26e | ||
|
|
5b22f12b4a | ||
|
|
5f85c2e7a5 | ||
|
|
4fb5a7b6bc | ||
|
|
04c6c862c4 | ||
|
|
baec45f742 | ||
|
|
9efdbd2c10 | ||
|
|
d8800b80d5 | ||
|
|
ec715abb0d | ||
|
|
1485801efb | ||
|
|
4f9ac3d33a | ||
|
|
1577ce4d67 | ||
|
|
7eee74a2e9 | ||
|
|
d9e38fb3be | ||
|
|
81cc90e54a | ||
|
|
bd9771a88f | ||
|
|
f5cd81fe09 | ||
|
|
277e2cff59 | ||
|
|
6fa9514059 | ||
|
|
c5e38b71f7 | ||
|
|
3746915ae7 | ||
|
|
21bdf88d09 | ||
|
|
ff032c3cd7 | ||
|
|
c083110051 | ||
|
|
a7dbeb5d76 | ||
|
|
634f6ea9f5 | ||
|
|
f9e4f9a09a | ||
|
|
aab9889174 |
128
.github/workflows/build.yml
vendored
128
.github/workflows/build.yml
vendored
@@ -97,7 +97,7 @@ jobs:
|
|||||||
cmake --build build --config Release --parallel 4
|
cmake --build build --config Release --parallel 4
|
||||||
cmake --install build
|
cmake --install build
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v5
|
||||||
with:
|
with:
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
submodules: recursive
|
submodules: recursive
|
||||||
@@ -156,7 +156,7 @@ jobs:
|
|||||||
strategy:
|
strategy:
|
||||||
fail-fast: false
|
fail-fast: false
|
||||||
matrix:
|
matrix:
|
||||||
fedora_version: [ '39', '40', '41', '42' ]
|
fedora_version: [ '41', '42', '43' ]
|
||||||
container:
|
container:
|
||||||
image: fedora:${{matrix.fedora_version}}
|
image: fedora:${{matrix.fedora_version}}
|
||||||
steps:
|
steps:
|
||||||
@@ -209,7 +209,7 @@ jobs:
|
|||||||
sparsehash-devel
|
sparsehash-devel
|
||||||
rapidjson-devel
|
rapidjson-devel
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v5
|
||||||
with:
|
with:
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
submodules: recursive
|
submodules: recursive
|
||||||
@@ -307,7 +307,7 @@ jobs:
|
|||||||
- name: Remove files
|
- name: Remove files
|
||||||
run: rm -rf /usr/lib64/qt6/lib/cmake/Qt6Sql/{Qt6QMYSQL*,Qt6QODBCD*,Qt6QPSQL*,Qt6QIBase*}
|
run: rm -rf /usr/lib64/qt6/lib/cmake/Qt6Sql/{Qt6QMYSQL*,Qt6QODBCD*,Qt6QPSQL*,Qt6QIBase*}
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v5
|
||||||
with:
|
with:
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
submodules: recursive
|
submodules: recursive
|
||||||
@@ -409,7 +409,7 @@ jobs:
|
|||||||
cmake --build build --config Release --parallel 4
|
cmake --build build --config Release --parallel 4
|
||||||
cmake --install build
|
cmake --install build
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v5
|
||||||
with:
|
with:
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
submodules: recursive
|
submodules: recursive
|
||||||
@@ -507,7 +507,7 @@ jobs:
|
|||||||
cmake --build build --config Release --parallel 4
|
cmake --build build --config Release --parallel 4
|
||||||
cmake --install build
|
cmake --install build
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v5
|
||||||
with:
|
with:
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
submodules: recursive
|
submodules: recursive
|
||||||
@@ -538,7 +538,7 @@ jobs:
|
|||||||
strategy:
|
strategy:
|
||||||
fail-fast: false
|
fail-fast: false
|
||||||
matrix:
|
matrix:
|
||||||
ubuntu_version: [ 'noble', 'oracular', 'plucky' ]
|
ubuntu_version: [ 'noble', 'plucky' ]
|
||||||
container:
|
container:
|
||||||
image: ubuntu:${{matrix.ubuntu_version}}
|
image: ubuntu:${{matrix.ubuntu_version}}
|
||||||
steps:
|
steps:
|
||||||
@@ -599,7 +599,7 @@ jobs:
|
|||||||
cmake --build build --config Release --parallel 4
|
cmake --build build --config Release --parallel 4
|
||||||
cmake --install build
|
cmake --install build
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v5
|
||||||
with:
|
with:
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
submodules: recursive
|
submodules: recursive
|
||||||
@@ -631,7 +631,7 @@ jobs:
|
|||||||
strategy:
|
strategy:
|
||||||
fail-fast: false
|
fail-fast: false
|
||||||
matrix:
|
matrix:
|
||||||
ubuntu_version: [ 'noble', 'oracular', 'plucky' ]
|
ubuntu_version: [ 'noble', 'plucky' ]
|
||||||
container:
|
container:
|
||||||
image: ubuntu:${{matrix.ubuntu_version}}
|
image: ubuntu:${{matrix.ubuntu_version}}
|
||||||
steps:
|
steps:
|
||||||
@@ -691,7 +691,7 @@ jobs:
|
|||||||
DEBIAN_FRONTEND: noninteractive
|
DEBIAN_FRONTEND: noninteractive
|
||||||
run: apt install -y keyboxd
|
run: apt install -y keyboxd
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v5
|
||||||
with:
|
with:
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
submodules: recursive
|
submodules: recursive
|
||||||
@@ -727,13 +727,13 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v5
|
||||||
with:
|
with:
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
submodules: recursive
|
submodules: recursive
|
||||||
- name: Build FreeBSD
|
- name: Build FreeBSD
|
||||||
id: build-freebsd
|
id: build-freebsd
|
||||||
uses: vmactions/freebsd-vm@v1.2.0
|
uses: vmactions/freebsd-vm@v1.2.3
|
||||||
with:
|
with:
|
||||||
usesh: true
|
usesh: true
|
||||||
mem: 4096
|
mem: 4096
|
||||||
@@ -752,13 +752,13 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v5
|
||||||
with:
|
with:
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
submodules: recursive
|
submodules: recursive
|
||||||
- name: Build OpenBSD
|
- name: Build OpenBSD
|
||||||
id: build-openbsd
|
id: build-openbsd
|
||||||
uses: vmactions/openbsd-vm@v1.1.7
|
uses: vmactions/openbsd-vm@v1.2.0
|
||||||
with:
|
with:
|
||||||
usesh: true
|
usesh: true
|
||||||
mem: 4096
|
mem: 4096
|
||||||
@@ -788,7 +788,7 @@ jobs:
|
|||||||
|
|
||||||
- name: Set MACOSX_DEPLOYMENT_TARGET
|
- name: Set MACOSX_DEPLOYMENT_TARGET
|
||||||
run: |
|
run: |
|
||||||
for i in 13 14 15; do
|
for i in 12 13 14 15; do
|
||||||
if [ -d "/Library/Developer/CommandLineTools/SDKs/MacOSX${i}.sdk" ]; then
|
if [ -d "/Library/Developer/CommandLineTools/SDKs/MacOSX${i}.sdk" ]; then
|
||||||
echo "Using macOS SDK ${i}"
|
echo "Using macOS SDK ${i}"
|
||||||
echo "MACOSX_DEPLOYMENT_TARGET=${i}.0" >> $GITHUB_ENV
|
echo "MACOSX_DEPLOYMENT_TARGET=${i}.0" >> $GITHUB_ENV
|
||||||
@@ -818,7 +818,7 @@ jobs:
|
|||||||
rm -f uninstall.sh
|
rm -f uninstall.sh
|
||||||
|
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v5
|
||||||
with:
|
with:
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
submodules: recursive
|
submodules: recursive
|
||||||
@@ -946,7 +946,7 @@ jobs:
|
|||||||
|
|
||||||
- name: Set MACOSX_DEPLOYMENT_TARGET
|
- name: Set MACOSX_DEPLOYMENT_TARGET
|
||||||
run: |
|
run: |
|
||||||
for i in 13 14 15; do
|
for i in 12 13 14 15; do
|
||||||
if [ -d "/Library/Developer/CommandLineTools/SDKs/MacOSX${i}.sdk" ]; then
|
if [ -d "/Library/Developer/CommandLineTools/SDKs/MacOSX${i}.sdk" ]; then
|
||||||
echo "Using macOS SDK ${i}"
|
echo "Using macOS SDK ${i}"
|
||||||
echo "MACOSX_DEPLOYMENT_TARGET=${i}.0" >> $GITHUB_ENV
|
echo "MACOSX_DEPLOYMENT_TARGET=${i}.0" >> $GITHUB_ENV
|
||||||
@@ -969,7 +969,7 @@ jobs:
|
|||||||
run: echo "cmake_buildtype=$(echo ${{env.buildtype}} | awk '{print toupper(substr($0,0,1))tolower(substr($0,2))}')" >> $GITHUB_ENV
|
run: echo "cmake_buildtype=$(echo ${{env.buildtype}} | awk '{print toupper(substr($0,0,1))tolower(substr($0,2))}')" >> $GITHUB_ENV
|
||||||
|
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v5
|
||||||
with:
|
with:
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
submodules: recursive
|
submodules: recursive
|
||||||
@@ -1072,7 +1072,7 @@ jobs:
|
|||||||
run: echo "cmake_buildtype=$(echo ${{matrix.buildtype}} | sed 's/.*/\u&/')" >> $GITHUB_ENV
|
run: echo "cmake_buildtype=$(echo ${{matrix.buildtype}} | sed 's/.*/\u&/')" >> $GITHUB_ENV
|
||||||
|
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v5
|
||||||
with:
|
with:
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
submodules: recursive
|
submodules: recursive
|
||||||
@@ -1246,12 +1246,42 @@ jobs:
|
|||||||
build-windows-msvc:
|
build-windows-msvc:
|
||||||
name: Build Windows MSVC
|
name: Build Windows MSVC
|
||||||
if: github.repository != 'strawberrymusicplayer/strawberry-private' && github.ref != 'refs/heads/l10n_master'
|
if: github.repository != 'strawberrymusicplayer/strawberry-private' && github.ref != 'refs/heads/l10n_master'
|
||||||
runs-on: windows-2022
|
|
||||||
strategy:
|
strategy:
|
||||||
fail-fast: false
|
fail-fast: false
|
||||||
matrix:
|
matrix:
|
||||||
arch: [ 'x86', 'x86_64' ]
|
include:
|
||||||
buildtype: [ 'release' ]
|
- name: "x86_64 debug"
|
||||||
|
runner: windows-2022
|
||||||
|
arch: x86_64
|
||||||
|
buildtype: debug
|
||||||
|
|
||||||
|
- name: "x86_64 release"
|
||||||
|
runner: windows-2022
|
||||||
|
arch: x86_64
|
||||||
|
buildtype: release
|
||||||
|
|
||||||
|
- name: "x86 debug"
|
||||||
|
runner: windows-2022
|
||||||
|
arch: x86
|
||||||
|
buildtype: debug
|
||||||
|
|
||||||
|
- name: "x86 release"
|
||||||
|
runner: windows-2022
|
||||||
|
arch: x86
|
||||||
|
buildtype: release
|
||||||
|
|
||||||
|
- name: "arm64 debug"
|
||||||
|
runner: windows-11-arm
|
||||||
|
arch: arm64
|
||||||
|
buildtype: debug
|
||||||
|
|
||||||
|
- name: "arm64 release"
|
||||||
|
runner: windows-11-arm
|
||||||
|
arch: arm64
|
||||||
|
buildtype: release
|
||||||
|
|
||||||
|
runs-on: ${{matrix.runner}}
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
|
|
||||||
- name: Set prefix path
|
- name: Set prefix path
|
||||||
@@ -1265,6 +1295,20 @@ jobs:
|
|||||||
shell: bash
|
shell: bash
|
||||||
run: echo "cmake_buildtype=$(echo ${{matrix.buildtype}} | sed 's/.*/\u&/')" >> $GITHUB_ENV
|
run: echo "cmake_buildtype=$(echo ${{matrix.buildtype}} | sed 's/.*/\u&/')" >> $GITHUB_ENV
|
||||||
|
|
||||||
|
- name: Show SDK versions
|
||||||
|
shell: bash
|
||||||
|
run: ls -la "c:/Program Files (x86)/Windows Kits/10/include"
|
||||||
|
|
||||||
|
- name: Set SDK version
|
||||||
|
if: matrix.arch != 'arm64'
|
||||||
|
shell: bash
|
||||||
|
run: echo "sdk_version=10.0.19041.0" >> $GITHUB_ENV
|
||||||
|
|
||||||
|
- name: Set SDK version
|
||||||
|
if: matrix.arch == 'arm64'
|
||||||
|
shell: bash
|
||||||
|
run: echo "sdk_version=10.0.26100.0" >> $GITHUB_ENV
|
||||||
|
|
||||||
- name: Install rsync
|
- name: Install rsync
|
||||||
shell: cmd
|
shell: cmd
|
||||||
run: choco install --no-progress rsync
|
run: choco install --no-progress rsync
|
||||||
@@ -1347,11 +1391,11 @@ jobs:
|
|||||||
uses: ilammy/msvc-dev-cmd@v1
|
uses: ilammy/msvc-dev-cmd@v1
|
||||||
with:
|
with:
|
||||||
arch: ${{matrix.arch}}
|
arch: ${{matrix.arch}}
|
||||||
sdk: 10.0.20348.0
|
sdk: ${{env.sdk_version}}
|
||||||
vsversion: 2022
|
vsversion: 2022
|
||||||
|
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v5
|
||||||
with:
|
with:
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
submodules: recursive
|
submodules: recursive
|
||||||
@@ -1364,15 +1408,18 @@ jobs:
|
|||||||
shell: cmd
|
shell: cmd
|
||||||
run: cmake -E make_directory build
|
run: cmake -E make_directory build
|
||||||
|
|
||||||
- name: Set ENABLE_WIN32_CONSOLE (debug)
|
- name: Set ENABLE_WIN32_CONSOLE
|
||||||
if: matrix.buildtype == 'debug'
|
|
||||||
shell: bash
|
shell: bash
|
||||||
run: echo "win32_console=ON" >> $GITHUB_ENV
|
run: echo "enable_win32_console=$(test "${{matrix.buildtype}}" = "debug" && echo "ON" || echo "OFF")" >> $GITHUB_ENV
|
||||||
|
|
||||||
- name: Set ENABLE_WIN32_CONSOLE (release)
|
- name: Set ENABLE_SPOTIFY
|
||||||
if: matrix.buildtype == 'release'
|
|
||||||
shell: bash
|
shell: bash
|
||||||
run: echo "win32_console=OFF" >> $GITHUB_ENV
|
run: echo "enable_spotify=$(test -f "${{env.prefix_path_unix}}/lib/gstreamer-1.0/gstspotify.dll" && echo "ON" || echo "OFF")" >> $GITHUB_ENV
|
||||||
|
|
||||||
|
- name: Remove -lm from .pc files
|
||||||
|
if: matrix.arch == 'arm64'
|
||||||
|
shell: bash
|
||||||
|
run: sed -i 's/\-lm$//g' ${{env.prefix_path_unix}}/lib/pkgconfig/*.pc
|
||||||
|
|
||||||
- name: Run CMake
|
- name: Run CMake
|
||||||
shell: cmd
|
shell: cmd
|
||||||
@@ -1384,14 +1431,14 @@ jobs:
|
|||||||
-DCMAKE_BUILD_TYPE="${{env.cmake_buildtype}}"
|
-DCMAKE_BUILD_TYPE="${{env.cmake_buildtype}}"
|
||||||
-DCMAKE_PREFIX_PATH="${{env.prefix_path_forwardslash}}/lib/cmake"
|
-DCMAKE_PREFIX_PATH="${{env.prefix_path_forwardslash}}/lib/cmake"
|
||||||
-DARCH="${{matrix.arch}}"
|
-DARCH="${{matrix.arch}}"
|
||||||
-DENABLE_WIN32_CONSOLE=${{env.win32_console}}
|
-DENABLE_WIN32_CONSOLE=${{env.enable_win32_console}}
|
||||||
-DPKG_CONFIG_EXECUTABLE="${{env.prefix_path_forwardslash}}/bin/pkg-config.exe"
|
-DPKG_CONFIG_EXECUTABLE="${{env.prefix_path_forwardslash}}/bin/pkg-config.exe"
|
||||||
-DICU_ROOT="${{env.prefix_path_forwardslash}}"
|
-DICU_ROOT="${{env.prefix_path_forwardslash}}"
|
||||||
-DENABLE_GIO=OFF
|
-DENABLE_GIO=OFF
|
||||||
-DENABLE_AUDIOCD=OFF
|
-DENABLE_AUDIOCD=OFF
|
||||||
-DENABLE_MTP=OFF
|
-DENABLE_MTP=OFF
|
||||||
-DENABLE_GPOD=OFF
|
-DENABLE_GPOD=OFF
|
||||||
-DENABLE_SPOTIFY=ON
|
-DENABLE_SPOTIFY=${{env.enable_spotify}}
|
||||||
|
|
||||||
- name: Run Make
|
- name: Run Make
|
||||||
shell: cmd
|
shell: cmd
|
||||||
@@ -1460,11 +1507,14 @@ jobs:
|
|||||||
run: copy ${{env.prefix_path_backslash}}\lib\gstreamer-1.0\*.dll .\gstreamer-plugins\
|
run: copy ${{env.prefix_path_backslash}}\lib\gstreamer-1.0\*.dll .\gstreamer-plugins\
|
||||||
|
|
||||||
- name: Download copydlldeps.sh
|
- name: Download copydlldeps.sh
|
||||||
|
if: matrix.arch != 'arm64'
|
||||||
shell: bash
|
shell: bash
|
||||||
working-directory: build
|
working-directory: build
|
||||||
run: curl -f -O -L https://raw.githubusercontent.com/strawberrymusicplayer/strawberry-mxe/master/tools/copydlldeps.sh
|
run: curl -f -O -L https://raw.githubusercontent.com/strawberrymusicplayer/strawberry-mxe/master/tools/copydlldeps.sh
|
||||||
|
|
||||||
- name: Copy dependencies
|
- name: Copy dependencies
|
||||||
|
# copydlldeps.sh doesn't work with arm64 binaries.
|
||||||
|
if: matrix.arch != 'arm64'
|
||||||
shell: bash
|
shell: bash
|
||||||
working-directory: build
|
working-directory: build
|
||||||
run: >
|
run: >
|
||||||
@@ -1481,6 +1531,12 @@ jobs:
|
|||||||
-F ./gstreamer-plugins
|
-F ./gstreamer-plugins
|
||||||
-R ${{env.prefix_path_unix}}/bin
|
-R ${{env.prefix_path_unix}}/bin
|
||||||
|
|
||||||
|
- name: Copy dependencies
|
||||||
|
if: matrix.arch == 'arm64'
|
||||||
|
shell: bash
|
||||||
|
working-directory: build
|
||||||
|
run: cp -v ${{env.prefix_path_unix}}/bin/{avcodec*.dll,avfilter*.dll,avformat*.dll,avutil*.dll,brotlicommon.dll,brotlidec.dll,chromaprint.dll,ebur128.dll,faad-2.dll,fdk-aac.dll,ffi-7.dll,FLAC.dll,freetype*.dll,getopt.dll,gio-2.0-0.dll,glib-2.0-0.dll,gme.dll,gmodule-2.0-0.dll,gobject-2.0-0.dll,gst-discoverer-1.0.exe,gst-launch-1.0.exe,gst-play-1.0.exe,gstadaptivedemux-1.0-0.dll,gstapp-1.0-0.dll,gstaudio-1.0-0.dll,gstbadaudio-1.0-0.dll,gstbase-1.0-0.dll,gstcodecparsers-1.0-0.dll,gstfft-1.0-0.dll,gstisoff-1.0-0.dll,gstmpegts-1.0-0.dll,gstnet-1.0-0.dll,gstpbutils-1.0-0.dll,gstreamer-1.0-0.dll,gstriff-1.0-0.dll,gstrtp-1.0-0.dll,gstrtsp-1.0-0.dll,gstsdp-1.0-0.dll,gsttag-1.0-0.dll,gsturidownloader-1.0-0.dll,gstvideo-1.0-0.dll,gstwinrt-1.0-0.dll,harfbuzz.dll,icudt*.dll,icuin*.dll,icuuc*.dll,intl-8.dll,jpeg62.dll,kdsingleapplication*.dll,libbs2b.dll,libcrypto-3-*.dll,fftw3.dll,libiconv*.dll,liblzma.dll,libmp3lame.dll,libopenmpt.dll,libpng16*.dll,libspeex*.dll,libssl-3-*.dll,libxml2*.dll,mpcdec.dll,mpg123.dll,nghttp2.dll,ogg.dll,opus.dll,orc-0.4-0.dll,pcre2-16*.dll,pcre2-8*.dll,postproc*.dll,psl-5.dll,Qt6Concurrent*.dll,Qt6Core*.dll,Qt6Gui*.dll,Qt6Network*.dll,Qt6Sql*.dll,Qt6Widgets*.dll,qtsparkle-qt6.dll,soup-3.0-0.dll,sqlite3.dll,sqlite3.exe,swresample*.dll,swscale*.dll,tag.dll,vorbis.dll,vorbisfile.dll,wavpackdll.dll,zlib*.dll} .
|
||||||
|
|
||||||
- name: Copy nsis files
|
- name: Copy nsis files
|
||||||
shell: cmd
|
shell: cmd
|
||||||
working-directory: build
|
working-directory: build
|
||||||
@@ -1581,11 +1637,11 @@ jobs:
|
|||||||
DEBIAN_FRONTEND: noninteractive
|
DEBIAN_FRONTEND: noninteractive
|
||||||
run: sudo apt install -y git rsync
|
run: sudo apt install -y git rsync
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v5
|
||||||
with:
|
with:
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
- name: Download artifacts
|
- name: Download artifacts
|
||||||
uses: actions/download-artifact@v4
|
uses: actions/download-artifact@v5
|
||||||
with:
|
with:
|
||||||
path: artifacts
|
path: artifacts
|
||||||
- name: SSH Setup
|
- name: SSH Setup
|
||||||
@@ -1629,7 +1685,7 @@ jobs:
|
|||||||
DEBIAN_FRONTEND: noninteractive
|
DEBIAN_FRONTEND: noninteractive
|
||||||
run: sudo apt install -y git jq gh
|
run: sudo apt install -y git jq gh
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v5
|
||||||
with:
|
with:
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
- name: Show release assets
|
- name: Show release assets
|
||||||
@@ -1637,7 +1693,7 @@ jobs:
|
|||||||
GITHUB_TOKEN: ${{secrets.GITHUB_TOKEN}}
|
GITHUB_TOKEN: ${{secrets.GITHUB_TOKEN}}
|
||||||
run: gh release view "${{github.event.release.tag_name}}" --json assets | jq -r '.assets[].name'
|
run: gh release view "${{github.event.release.tag_name}}" --json assets | jq -r '.assets[].name'
|
||||||
- name: Download artifacts
|
- name: Download artifacts
|
||||||
uses: actions/download-artifact@v4
|
uses: actions/download-artifact@v5
|
||||||
with:
|
with:
|
||||||
path: artifacts
|
path: artifacts
|
||||||
- name: Add artifacts to release
|
- name: Add artifacts to release
|
||||||
|
|||||||
42
3rdparty/discord-rpc/CMakeLists.txt
vendored
42
3rdparty/discord-rpc/CMakeLists.txt
vendored
@@ -1 +1,41 @@
|
|||||||
add_subdirectory(src)
|
set(DISCORD_RPC_SOURCES
|
||||||
|
discord_rpc.h
|
||||||
|
discord_register.h
|
||||||
|
discord_rpc.cpp
|
||||||
|
discord_rpc_connection.h
|
||||||
|
discord_rpc_connection.cpp
|
||||||
|
discord_serialization.h
|
||||||
|
discord_serialization.cpp
|
||||||
|
discord_connection.h
|
||||||
|
discord_backoff.h
|
||||||
|
discord_msg_queue.h
|
||||||
|
)
|
||||||
|
|
||||||
|
if(UNIX)
|
||||||
|
list(APPEND DISCORD_RPC_SOURCES discord_connection_unix.cpp)
|
||||||
|
if(APPLE)
|
||||||
|
list(APPEND DISCORD_RPC_SOURCES discord_register_osx.m)
|
||||||
|
add_definitions(-DDISCORD_OSX)
|
||||||
|
else()
|
||||||
|
list(APPEND DISCORD_RPC_SOURCES discord_register_linux.cpp)
|
||||||
|
add_definitions(-DDISCORD_LINUX)
|
||||||
|
endif()
|
||||||
|
endif()
|
||||||
|
|
||||||
|
if(WIN32)
|
||||||
|
list(APPEND DISCORD_RPC_SOURCES discord_connection_win.cpp discord_register_win.cpp)
|
||||||
|
add_definitions(-DDISCORD_WINDOWS)
|
||||||
|
endif()
|
||||||
|
|
||||||
|
add_library(discord-rpc STATIC ${DISCORD_RPC_SOURCES})
|
||||||
|
|
||||||
|
if(APPLE)
|
||||||
|
target_link_libraries(discord-rpc PRIVATE "-framework AppKit")
|
||||||
|
endif()
|
||||||
|
|
||||||
|
if(WIN32)
|
||||||
|
target_link_libraries(discord-rpc PRIVATE psapi advapi32)
|
||||||
|
endif()
|
||||||
|
|
||||||
|
target_include_directories(discord-rpc SYSTEM PRIVATE ${RapidJSON_INCLUDE_DIRS})
|
||||||
|
target_include_directories(discord-rpc PRIVATE ${CMAKE_CURRENT_SOURCE_DIR})
|
||||||
|
|||||||
63
3rdparty/discord-rpc/discord_backoff.h
vendored
Normal file
63
3rdparty/discord-rpc/discord_backoff.h
vendored
Normal file
@@ -0,0 +1,63 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2017 Discord, Inc.
|
||||||
|
*
|
||||||
|
* 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.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef DISCORD_BACKOFF_H
|
||||||
|
#define DISCORD_BACKOFF_H
|
||||||
|
|
||||||
|
#include <algorithm>
|
||||||
|
#include <random>
|
||||||
|
#include <cstdint>
|
||||||
|
#include <ctime>
|
||||||
|
|
||||||
|
namespace discord_rpc {
|
||||||
|
|
||||||
|
struct Backoff {
|
||||||
|
int64_t minAmount;
|
||||||
|
int64_t maxAmount;
|
||||||
|
int64_t current;
|
||||||
|
int fails;
|
||||||
|
std::mt19937_64 randGenerator;
|
||||||
|
std::uniform_real_distribution<> randDistribution;
|
||||||
|
|
||||||
|
double rand01() { return randDistribution(randGenerator); }
|
||||||
|
|
||||||
|
Backoff(int64_t min, int64_t max)
|
||||||
|
: minAmount(min), maxAmount(max), current(min), fails(0), randGenerator(static_cast<uint64_t>(time(0))) {
|
||||||
|
}
|
||||||
|
|
||||||
|
void reset() {
|
||||||
|
fails = 0;
|
||||||
|
current = minAmount;
|
||||||
|
}
|
||||||
|
|
||||||
|
int64_t nextDelay() {
|
||||||
|
++fails;
|
||||||
|
int64_t delay = static_cast<int64_t>(static_cast<double>(current) * 2.0 * rand01());
|
||||||
|
current = std::min(current + delay, maxAmount);
|
||||||
|
return current;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace discord_rpc
|
||||||
|
|
||||||
|
#endif // DISCORD_BACKOFF_H
|
||||||
48
3rdparty/discord-rpc/discord_connection.h
vendored
Normal file
48
3rdparty/discord-rpc/discord_connection.h
vendored
Normal file
@@ -0,0 +1,48 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2017 Discord, Inc.
|
||||||
|
*
|
||||||
|
* 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.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef DISCORD_CONNECTION_H
|
||||||
|
#define DISCORD_CONNECTION_H
|
||||||
|
|
||||||
|
// This is to wrap the platform specific kinds of connect/read/write.
|
||||||
|
|
||||||
|
#include <cstdlib>
|
||||||
|
|
||||||
|
namespace discord_rpc {
|
||||||
|
|
||||||
|
// not really connectiony, but need per-platform
|
||||||
|
int GetProcessId();
|
||||||
|
|
||||||
|
struct BaseConnection {
|
||||||
|
static BaseConnection *Create();
|
||||||
|
static void Destroy(BaseConnection *&);
|
||||||
|
bool isOpen = false;
|
||||||
|
bool Open();
|
||||||
|
bool Close();
|
||||||
|
bool Write(const void *data, size_t length);
|
||||||
|
bool Read(void *data, size_t length);
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace discord_rpc
|
||||||
|
|
||||||
|
#endif // DISCORD_CONNECTION_H
|
||||||
@@ -1,4 +1,27 @@
|
|||||||
#include "connection.h"
|
/*
|
||||||
|
* Copyright 2017 Discord, Inc.
|
||||||
|
*
|
||||||
|
* 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.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "discord_connection.h"
|
||||||
|
|
||||||
#include <cerrno>
|
#include <cerrno>
|
||||||
#include <fcntl.h>
|
#include <fcntl.h>
|
||||||
@@ -28,28 +51,34 @@ static int MsgFlags = 0;
|
|||||||
#endif
|
#endif
|
||||||
|
|
||||||
static const char *GetTempPath() {
|
static const char *GetTempPath() {
|
||||||
|
|
||||||
const char *temp = getenv("XDG_RUNTIME_DIR");
|
const char *temp = getenv("XDG_RUNTIME_DIR");
|
||||||
temp = temp ? temp : getenv("TMPDIR");
|
temp = temp ? temp : getenv("TMPDIR");
|
||||||
temp = temp ? temp : getenv("TMP");
|
temp = temp ? temp : getenv("TMP");
|
||||||
temp = temp ? temp : getenv("TEMP");
|
temp = temp ? temp : getenv("TEMP");
|
||||||
temp = temp ? temp : "/tmp";
|
temp = temp ? temp : "/tmp";
|
||||||
|
|
||||||
return temp;
|
return temp;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/*static*/ BaseConnection *BaseConnection::Create() {
|
BaseConnection *BaseConnection::Create() {
|
||||||
PipeAddr.sun_family = AF_UNIX;
|
PipeAddr.sun_family = AF_UNIX;
|
||||||
return &Connection;
|
return &Connection;
|
||||||
}
|
}
|
||||||
|
|
||||||
/*static*/ void BaseConnection::Destroy(BaseConnection *&c) {
|
void BaseConnection::Destroy(BaseConnection *&c) {
|
||||||
auto self = reinterpret_cast<BaseConnectionUnix *>(c);
|
|
||||||
|
auto self = reinterpret_cast<BaseConnectionUnix*>(c);
|
||||||
self->Close();
|
self->Close();
|
||||||
c = nullptr;
|
c = nullptr;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bool BaseConnection::Open() {
|
bool BaseConnection::Open() {
|
||||||
|
|
||||||
const char *tempPath = GetTempPath();
|
const char *tempPath = GetTempPath();
|
||||||
auto self = reinterpret_cast<BaseConnectionUnix *>(this);
|
auto self = reinterpret_cast<BaseConnectionUnix*>(this);
|
||||||
self->sock = socket(AF_UNIX, SOCK_STREAM, 0);
|
self->sock = socket(AF_UNIX, SOCK_STREAM, 0);
|
||||||
if (self->sock == -1) {
|
if (self->sock == -1) {
|
||||||
return false;
|
return false;
|
||||||
@@ -61,8 +90,7 @@ bool BaseConnection::Open() {
|
|||||||
#endif
|
#endif
|
||||||
|
|
||||||
for (int pipeNum = 0; pipeNum < 10; ++pipeNum) {
|
for (int pipeNum = 0; pipeNum < 10; ++pipeNum) {
|
||||||
snprintf(
|
snprintf(PipeAddr.sun_path, sizeof(PipeAddr.sun_path), "%s/discord-ipc-%d", tempPath, pipeNum);
|
||||||
PipeAddr.sun_path, sizeof(PipeAddr.sun_path), "%s/discord-ipc-%d", tempPath, pipeNum);
|
|
||||||
int err = connect(self->sock, reinterpret_cast<const sockaddr*>(&PipeAddr), sizeof(PipeAddr));
|
int err = connect(self->sock, reinterpret_cast<const sockaddr*>(&PipeAddr), sizeof(PipeAddr));
|
||||||
if (err == 0) {
|
if (err == 0) {
|
||||||
self->isOpen = true;
|
self->isOpen = true;
|
||||||
@@ -70,10 +98,13 @@ bool BaseConnection::Open() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
self->Close();
|
self->Close();
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bool BaseConnection::Close() {
|
bool BaseConnection::Close() {
|
||||||
|
|
||||||
auto self = reinterpret_cast<BaseConnectionUnix *>(this);
|
auto self = reinterpret_cast<BaseConnectionUnix *>(this);
|
||||||
if (self->sock == -1) {
|
if (self->sock == -1) {
|
||||||
return false;
|
return false;
|
||||||
@@ -81,11 +112,14 @@ bool BaseConnection::Close() {
|
|||||||
close(self->sock);
|
close(self->sock);
|
||||||
self->sock = -1;
|
self->sock = -1;
|
||||||
self->isOpen = false;
|
self->isOpen = false;
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bool BaseConnection::Write(const void *data, size_t length) {
|
bool BaseConnection::Write(const void *data, size_t length) {
|
||||||
auto self = reinterpret_cast<BaseConnectionUnix *>(this);
|
|
||||||
|
auto self = reinterpret_cast<BaseConnectionUnix*>(this);
|
||||||
|
|
||||||
if (self->sock == -1) {
|
if (self->sock == -1) {
|
||||||
return false;
|
return false;
|
||||||
@@ -95,11 +129,14 @@ bool BaseConnection::Write(const void *data, size_t length) {
|
|||||||
if (sentBytes < 0) {
|
if (sentBytes < 0) {
|
||||||
Close();
|
Close();
|
||||||
}
|
}
|
||||||
|
|
||||||
return sentBytes == static_cast<ssize_t>(length);
|
return sentBytes == static_cast<ssize_t>(length);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bool BaseConnection::Read(void *data, size_t length) {
|
bool BaseConnection::Read(void *data, size_t length) {
|
||||||
auto self = reinterpret_cast<BaseConnectionUnix *>(this);
|
|
||||||
|
auto self = reinterpret_cast<BaseConnectionUnix*>(this);
|
||||||
|
|
||||||
if (self->sock == -1) {
|
if (self->sock == -1) {
|
||||||
return false;
|
return false;
|
||||||
@@ -115,8 +152,9 @@ bool BaseConnection::Read(void *data, size_t length) {
|
|||||||
else if (res == 0) {
|
else if (res == 0) {
|
||||||
Close();
|
Close();
|
||||||
}
|
}
|
||||||
|
|
||||||
return static_cast<size_t>(res) == length;
|
return static_cast<size_t>(res) == length;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace discord_rpc
|
} // namespace discord_rpc
|
||||||
|
|
||||||
@@ -1,9 +1,33 @@
|
|||||||
#include "connection.h"
|
/*
|
||||||
|
* Copyright 2017 Discord, Inc.
|
||||||
|
*
|
||||||
|
* 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.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "discord_connection.h"
|
||||||
|
|
||||||
#define WIN32_LEAN_AND_MEAN
|
#define WIN32_LEAN_AND_MEAN
|
||||||
#define NOMCX
|
#define NOMCX
|
||||||
#define NOSERVICE
|
#define NOSERVICE
|
||||||
#define NOIME
|
#define NOIME
|
||||||
|
|
||||||
#include <cassert>
|
#include <cassert>
|
||||||
#include <windows.h>
|
#include <windows.h>
|
||||||
|
|
||||||
@@ -19,24 +43,26 @@ struct BaseConnectionWin : public BaseConnection {
|
|||||||
|
|
||||||
static BaseConnectionWin Connection;
|
static BaseConnectionWin Connection;
|
||||||
|
|
||||||
/*static*/ BaseConnection *BaseConnection::Create() {
|
BaseConnection *BaseConnection::Create() {
|
||||||
return &Connection;
|
return &Connection;
|
||||||
}
|
}
|
||||||
|
|
||||||
/*static*/ void BaseConnection::Destroy(BaseConnection *&c) {
|
void BaseConnection::Destroy(BaseConnection *&c) {
|
||||||
|
|
||||||
auto self = reinterpret_cast<BaseConnectionWin*>(c);
|
auto self = reinterpret_cast<BaseConnectionWin*>(c);
|
||||||
self->Close();
|
self->Close();
|
||||||
c = nullptr;
|
c = nullptr;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bool BaseConnection::Open() {
|
bool BaseConnection::Open() {
|
||||||
|
|
||||||
wchar_t pipeName[] { L"\\\\?\\pipe\\discord-ipc-0" };
|
wchar_t pipeName[] { L"\\\\?\\pipe\\discord-ipc-0" };
|
||||||
const size_t pipeDigit = sizeof(pipeName) / sizeof(wchar_t) - 2;
|
const size_t pipeDigit = sizeof(pipeName) / sizeof(wchar_t) - 2;
|
||||||
pipeName[pipeDigit] = L'0';
|
pipeName[pipeDigit] = L'0';
|
||||||
auto self = reinterpret_cast<BaseConnectionWin *>(this);
|
auto self = reinterpret_cast<BaseConnectionWin *>(this);
|
||||||
for (;;) {
|
for (;;) {
|
||||||
self->pipe = ::CreateFileW(
|
self->pipe = ::CreateFileW(pipeName, GENERIC_READ | GENERIC_WRITE, 0, nullptr, OPEN_EXISTING, 0, nullptr);
|
||||||
pipeName, GENERIC_READ | GENERIC_WRITE, 0, nullptr, OPEN_EXISTING, 0, nullptr);
|
|
||||||
if (self->pipe != INVALID_HANDLE_VALUE) {
|
if (self->pipe != INVALID_HANDLE_VALUE) {
|
||||||
self->isOpen = true;
|
self->isOpen = true;
|
||||||
return true;
|
return true;
|
||||||
@@ -57,17 +83,22 @@ bool BaseConnection::Open() {
|
|||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bool BaseConnection::Close() {
|
bool BaseConnection::Close() {
|
||||||
|
|
||||||
auto self = reinterpret_cast<BaseConnectionWin *>(this);
|
auto self = reinterpret_cast<BaseConnectionWin *>(this);
|
||||||
::CloseHandle(self->pipe);
|
::CloseHandle(self->pipe);
|
||||||
self->pipe = INVALID_HANDLE_VALUE;
|
self->pipe = INVALID_HANDLE_VALUE;
|
||||||
self->isOpen = false;
|
self->isOpen = false;
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bool BaseConnection::Write(const void *data, size_t length) {
|
bool BaseConnection::Write(const void *data, size_t length) {
|
||||||
|
|
||||||
if (length == 0) {
|
if (length == 0) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@@ -85,11 +116,13 @@ bool BaseConnection::Write(const void *data, size_t length) {
|
|||||||
}
|
}
|
||||||
const DWORD bytesLength = static_cast<DWORD>(length);
|
const DWORD bytesLength = static_cast<DWORD>(length);
|
||||||
DWORD bytesWritten = 0;
|
DWORD bytesWritten = 0;
|
||||||
return ::WriteFile(self->pipe, data, bytesLength, &bytesWritten, nullptr) == TRUE &&
|
|
||||||
bytesWritten == bytesLength;
|
return ::WriteFile(self->pipe, data, bytesLength, &bytesWritten, nullptr) == TRUE && bytesWritten == bytesLength;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bool BaseConnection::Read(void *data, size_t length) {
|
bool BaseConnection::Read(void *data, size_t length) {
|
||||||
|
|
||||||
assert(data);
|
assert(data);
|
||||||
if (!data) {
|
if (!data) {
|
||||||
return false;
|
return false;
|
||||||
@@ -119,8 +152,9 @@ bool BaseConnection::Read(void *data, size_t length) {
|
|||||||
else {
|
else {
|
||||||
Close();
|
Close();
|
||||||
}
|
}
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace discord_rpc
|
} // namespace discord_rpc
|
||||||
|
|
||||||
64
3rdparty/discord-rpc/discord_msg_queue.h
vendored
Normal file
64
3rdparty/discord-rpc/discord_msg_queue.h
vendored
Normal file
@@ -0,0 +1,64 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2017 Discord, Inc.
|
||||||
|
*
|
||||||
|
* 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.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef DISCORD_MSG_QUEUE_H
|
||||||
|
#define DISCORD_MSG_QUEUE_H
|
||||||
|
|
||||||
|
#include <atomic>
|
||||||
|
|
||||||
|
// A simple queue. No locks, but only works with a single thread as producer and a single thread as
|
||||||
|
// a consumer. Mutex up as needed.
|
||||||
|
|
||||||
|
namespace discord_rpc {
|
||||||
|
|
||||||
|
template<typename ElementType, std::size_t QueueSize>
|
||||||
|
class MsgQueue {
|
||||||
|
ElementType queue_[QueueSize];
|
||||||
|
std::atomic_uint nextAdd_ { 0 };
|
||||||
|
std::atomic_uint nextSend_ { 0 };
|
||||||
|
std::atomic_uint pendingSends_ { 0 };
|
||||||
|
|
||||||
|
public:
|
||||||
|
MsgQueue() {}
|
||||||
|
|
||||||
|
ElementType *GetNextAddMessage() {
|
||||||
|
// if we are falling behind, bail
|
||||||
|
if (pendingSends_.load() >= QueueSize) {
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
auto index = (nextAdd_++) % QueueSize;
|
||||||
|
return &queue_[index];
|
||||||
|
}
|
||||||
|
void CommitAdd() { ++pendingSends_; }
|
||||||
|
|
||||||
|
bool HavePendingSends() const { return pendingSends_.load() != 0; }
|
||||||
|
ElementType *GetNextSendMessage() {
|
||||||
|
auto index = (nextSend_++) % QueueSize;
|
||||||
|
return &queue_[index];
|
||||||
|
}
|
||||||
|
void CommitSend() { --pendingSends_; }
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace discord_rpc
|
||||||
|
|
||||||
|
#endif // DISCORD_MSG_QUEUE_H
|
||||||
37
3rdparty/discord-rpc/discord_register.h
vendored
Normal file
37
3rdparty/discord-rpc/discord_register.h
vendored
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2017 Discord, Inc.
|
||||||
|
*
|
||||||
|
* 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.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef DISCORD_REGISTER_H
|
||||||
|
#define DISCORD_REGISTER_H
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
extern "C" {
|
||||||
|
#endif
|
||||||
|
|
||||||
|
void Discord_Register(const char *applicationId, const char *command);
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#endif // DISCORD_REGISTER_H
|
||||||
120
3rdparty/discord-rpc/discord_register_linux.cpp
vendored
Normal file
120
3rdparty/discord-rpc/discord_register_linux.cpp
vendored
Normal file
@@ -0,0 +1,120 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2017 Discord, Inc.
|
||||||
|
*
|
||||||
|
* 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.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "discord_rpc.h"
|
||||||
|
#include "discord_register.h"
|
||||||
|
|
||||||
|
#include <cstdio>
|
||||||
|
#include <errno.h>
|
||||||
|
#include <cstdlib>
|
||||||
|
#include <cstring>
|
||||||
|
#include <sys/stat.h>
|
||||||
|
#include <sys/types.h>
|
||||||
|
#include <unistd.h>
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
|
||||||
|
static bool Mkdir(const char *path) {
|
||||||
|
int result = mkdir(path, 0755);
|
||||||
|
if (result == 0) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (errno == EEXIST) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
// We want to register games so we can run them from Discord client as discord-<appid>://
|
||||||
|
extern "C" void Discord_Register(const char *applicationId, const char *command) {
|
||||||
|
|
||||||
|
// Add a desktop file and update some mime handlers so that xdg-open does the right thing.
|
||||||
|
|
||||||
|
const char *home = getenv("HOME");
|
||||||
|
if (!home) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
char exePath[1024]{};
|
||||||
|
if (!command || !command[0]) {
|
||||||
|
const ssize_t size = readlink("/proc/self/exe", exePath, sizeof(exePath));
|
||||||
|
if (size <= 0 || size >= static_cast<ssize_t>(sizeof(exePath))) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
exePath[size] = '\0';
|
||||||
|
command = exePath;
|
||||||
|
}
|
||||||
|
|
||||||
|
constexpr char desktopFileFormat[] = "[Desktop Entry]\n"
|
||||||
|
"Name=Game %s\n"
|
||||||
|
"Exec=%s %%u\n" // note: it really wants that %u in there
|
||||||
|
"Type=Application\n"
|
||||||
|
"NoDisplay=true\n"
|
||||||
|
"Categories=Discord;Games;\n"
|
||||||
|
"MimeType=x-scheme-handler/discord-%s;\n";
|
||||||
|
char desktopFile[2048]{};
|
||||||
|
int fileLen = snprintf(desktopFile, sizeof(desktopFile), desktopFileFormat, applicationId, command, applicationId);
|
||||||
|
if (fileLen <= 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
char desktopFilename[256]{};
|
||||||
|
(void)snprintf(desktopFilename, sizeof(desktopFilename), "/discord-%s.desktop", applicationId);
|
||||||
|
|
||||||
|
char desktopFilePath[1024]{};
|
||||||
|
(void)snprintf(desktopFilePath, sizeof(desktopFilePath), "%s/.local", home);
|
||||||
|
if (!Mkdir(desktopFilePath)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
strcat(desktopFilePath, "/share");
|
||||||
|
if (!Mkdir(desktopFilePath)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
strcat(desktopFilePath, "/applications");
|
||||||
|
if (!Mkdir(desktopFilePath)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
strcat(desktopFilePath, desktopFilename);
|
||||||
|
|
||||||
|
FILE *fp = fopen(desktopFilePath, "w");
|
||||||
|
if (fp) {
|
||||||
|
fwrite(desktopFile, 1, fileLen, fp);
|
||||||
|
fclose(fp);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
char xdgMimeCommand[1024]{};
|
||||||
|
snprintf(xdgMimeCommand,
|
||||||
|
sizeof(xdgMimeCommand),
|
||||||
|
"xdg-mime default discord-%s.desktop x-scheme-handler/discord-%s",
|
||||||
|
applicationId,
|
||||||
|
applicationId);
|
||||||
|
if (system(xdgMimeCommand) < 0) {
|
||||||
|
fprintf(stderr, "Failed to register mime handler\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
99
3rdparty/discord-rpc/discord_register_osx.m
vendored
Normal file
99
3rdparty/discord-rpc/discord_register_osx.m
vendored
Normal file
@@ -0,0 +1,99 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2017 Discord, Inc.
|
||||||
|
*
|
||||||
|
* 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.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <sys/stat.h>
|
||||||
|
|
||||||
|
#import <AppKit/AppKit.h>
|
||||||
|
|
||||||
|
#include "discord_register.h"
|
||||||
|
|
||||||
|
static void RegisterCommand(const char *applicationId, const char *command) {
|
||||||
|
|
||||||
|
// There does not appear to be a way to register arbitrary commands on OSX, so instead we'll save the command
|
||||||
|
// to a file in the Discord config path, and when it is needed, Discord can try to load the file there, open
|
||||||
|
// the command therein (will pass to js's window.open, so requires a url-like thing)
|
||||||
|
|
||||||
|
// Note: will not work for sandboxed apps
|
||||||
|
NSString *home = NSHomeDirectory();
|
||||||
|
if (!home) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
NSString *path = [[[[[[home stringByAppendingPathComponent:@"Library"]
|
||||||
|
stringByAppendingPathComponent:@"Application Support"]
|
||||||
|
stringByAppendingPathComponent:@"discord"]
|
||||||
|
stringByAppendingPathComponent:@"games"]
|
||||||
|
stringByAppendingPathComponent:[NSString stringWithUTF8String:applicationId]]
|
||||||
|
stringByAppendingPathExtension:@"json"];
|
||||||
|
[[NSFileManager defaultManager] createDirectoryAtPath:[path stringByDeletingLastPathComponent] withIntermediateDirectories:YES attributes:nil error:nil];
|
||||||
|
|
||||||
|
NSString *jsonBuffer = [NSString stringWithFormat:@"{\"command\": \"%s\"}", command];
|
||||||
|
[jsonBuffer writeToFile:path atomically:NO encoding:NSUTF8StringEncoding error:nil];
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
static void RegisterURL(const char *applicationId) {
|
||||||
|
|
||||||
|
char url[256];
|
||||||
|
snprintf(url, sizeof(url), "discord-%s", applicationId);
|
||||||
|
CFStringRef cfURL = CFStringCreateWithCString(NULL, url, kCFStringEncodingUTF8);
|
||||||
|
|
||||||
|
NSString* myBundleId = [[NSBundle mainBundle] bundleIdentifier];
|
||||||
|
if (!myBundleId) {
|
||||||
|
fprintf(stderr, "No bundle id found\n");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
NSURL* myURL = [[NSBundle mainBundle] bundleURL];
|
||||||
|
if (!myURL) {
|
||||||
|
fprintf(stderr, "No bundle url found\n");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
OSStatus status = LSSetDefaultHandlerForURLScheme(cfURL, (__bridge CFStringRef)myBundleId);
|
||||||
|
if (status != noErr) {
|
||||||
|
fprintf(stderr, "Error in LSSetDefaultHandlerForURLScheme: %d\n", (int)status);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
status = LSRegisterURL((__bridge CFURLRef)myURL, true);
|
||||||
|
if (status != noErr) {
|
||||||
|
fprintf(stderr, "Error in LSRegisterURL: %d\n", (int)status);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
void Discord_Register(const char *applicationId, const char *command) {
|
||||||
|
|
||||||
|
if (command) {
|
||||||
|
RegisterCommand(applicationId, command);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
// raii lite
|
||||||
|
@autoreleasepool {
|
||||||
|
RegisterURL(applicationId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -1,3 +1,26 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2017 Discord, Inc.
|
||||||
|
*
|
||||||
|
* 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.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
#include "discord_rpc.h"
|
#include "discord_rpc.h"
|
||||||
#include "discord_register.h"
|
#include "discord_register.h"
|
||||||
|
|
||||||
@@ -5,6 +28,7 @@
|
|||||||
#define NOMCX
|
#define NOMCX
|
||||||
#define NOSERVICE
|
#define NOSERVICE
|
||||||
#define NOIME
|
#define NOIME
|
||||||
|
|
||||||
#include <windows.h>
|
#include <windows.h>
|
||||||
#include <psapi.h>
|
#include <psapi.h>
|
||||||
#include <cstdio>
|
#include <cstdio>
|
||||||
@@ -46,12 +70,8 @@ static HRESULT StringCbPrintfW(LPWSTR pszDest, size_t cbDest, LPCWSTR pszFormat,
|
|||||||
#endif
|
#endif
|
||||||
#define RegSetKeyValueW regset
|
#define RegSetKeyValueW regset
|
||||||
|
|
||||||
static LSTATUS regset(HKEY hkey,
|
static LSTATUS regset(HKEY hkey, LPCWSTR subkey, LPCWSTR name, DWORD type, const void *data, DWORD len) {
|
||||||
LPCWSTR subkey,
|
|
||||||
LPCWSTR name,
|
|
||||||
DWORD type,
|
|
||||||
const void *data,
|
|
||||||
DWORD len) {
|
|
||||||
HKEY htkey = hkey, hsubkey = nullptr;
|
HKEY htkey = hkey, hsubkey = nullptr;
|
||||||
LSTATUS ret;
|
LSTATUS ret;
|
||||||
if (subkey && subkey[0]) {
|
if (subkey && subkey[0]) {
|
||||||
@@ -64,16 +84,18 @@ static LSTATUS regset(HKEY hkey,
|
|||||||
if (hsubkey && hsubkey != hkey)
|
if (hsubkey && hsubkey != hkey)
|
||||||
RegCloseKey(hsubkey);
|
RegCloseKey(hsubkey);
|
||||||
return ret;
|
return ret;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static void Discord_RegisterW(const wchar_t *applicationId, const wchar_t *command) {
|
static void Discord_RegisterW(const wchar_t *applicationId, const wchar_t *command) {
|
||||||
|
|
||||||
// https://msdn.microsoft.com/en-us/library/aa767914(v=vs.85).aspx
|
// https://msdn.microsoft.com/en-us/library/aa767914(v=vs.85).aspx
|
||||||
// we want to register games so we can run them as discord-<appid>://
|
// we want to register games so we can run them as discord-<appid>://
|
||||||
// Update the HKEY_CURRENT_USER, because it doesn't seem to require special permissions.
|
// Update the HKEY_CURRENT_USER, because it doesn't seem to require special permissions.
|
||||||
|
|
||||||
wchar_t exeFilePath[MAX_PATH];
|
wchar_t exeFilePath[MAX_PATH]{};
|
||||||
DWORD exeLen = GetModuleFileNameW(nullptr, exeFilePath, MAX_PATH);
|
DWORD exeLen = GetModuleFileNameW(nullptr, exeFilePath, MAX_PATH);
|
||||||
wchar_t openCommand[1024];
|
wchar_t openCommand[1024]{};
|
||||||
|
|
||||||
if (command && command[0]) {
|
if (command && command[0]) {
|
||||||
StringCbPrintfW(openCommand, sizeof(openCommand), L"%s", command);
|
StringCbPrintfW(openCommand, sizeof(openCommand), L"%s", command);
|
||||||
@@ -83,18 +105,16 @@ static void Discord_RegisterW(const wchar_t *applicationId, const wchar_t *comma
|
|||||||
StringCbPrintfW(openCommand, sizeof(openCommand), L"%s", exeFilePath);
|
StringCbPrintfW(openCommand, sizeof(openCommand), L"%s", exeFilePath);
|
||||||
}
|
}
|
||||||
|
|
||||||
wchar_t protocolName[64];
|
wchar_t protocolName[64]{};
|
||||||
StringCbPrintfW(protocolName, sizeof(protocolName), L"discord-%s", applicationId);
|
StringCbPrintfW(protocolName, sizeof(protocolName), L"discord-%s", applicationId);
|
||||||
wchar_t protocolDescription[128];
|
wchar_t protocolDescription[128]{};
|
||||||
StringCbPrintfW(
|
StringCbPrintfW(protocolDescription, sizeof(protocolDescription), L"URL:Run game %s protocol", applicationId);
|
||||||
protocolDescription, sizeof(protocolDescription), L"URL:Run game %s protocol", applicationId);
|
|
||||||
wchar_t urlProtocol = 0;
|
wchar_t urlProtocol = 0;
|
||||||
|
|
||||||
wchar_t keyName[256];
|
wchar_t keyName[256]{};
|
||||||
StringCbPrintfW(keyName, sizeof(keyName), L"Software\\Classes\\%s", protocolName);
|
StringCbPrintfW(keyName, sizeof(keyName), L"Software\\Classes\\%s", protocolName);
|
||||||
HKEY key;
|
HKEY key;
|
||||||
auto status =
|
auto status = RegCreateKeyExW(HKEY_CURRENT_USER, keyName, 0, nullptr, 0, KEY_WRITE, nullptr, &key, nullptr);
|
||||||
RegCreateKeyExW(HKEY_CURRENT_USER, keyName, 0, nullptr, 0, KEY_WRITE, nullptr, &key, nullptr);
|
|
||||||
if (status != ERROR_SUCCESS) {
|
if (status != ERROR_SUCCESS) {
|
||||||
fprintf(stderr, "Error creating key\n");
|
fprintf(stderr, "Error creating key\n");
|
||||||
return;
|
return;
|
||||||
@@ -102,8 +122,7 @@ static void Discord_RegisterW(const wchar_t *applicationId, const wchar_t *comma
|
|||||||
DWORD len;
|
DWORD len;
|
||||||
LSTATUS result;
|
LSTATUS result;
|
||||||
len = static_cast<DWORD>(lstrlenW(protocolDescription) + 1);
|
len = static_cast<DWORD>(lstrlenW(protocolDescription) + 1);
|
||||||
result =
|
result = RegSetKeyValueW(key, nullptr, nullptr, REG_SZ, protocolDescription, len * sizeof(wchar_t));
|
||||||
RegSetKeyValueW(key, nullptr, nullptr, REG_SZ, protocolDescription, len * sizeof(wchar_t));
|
|
||||||
if (FAILED(result)) {
|
if (FAILED(result)) {
|
||||||
fprintf(stderr, "Error writing description\n");
|
fprintf(stderr, "Error writing description\n");
|
||||||
}
|
}
|
||||||
@@ -114,26 +133,26 @@ static void Discord_RegisterW(const wchar_t *applicationId, const wchar_t *comma
|
|||||||
fprintf(stderr, "Error writing description\n");
|
fprintf(stderr, "Error writing description\n");
|
||||||
}
|
}
|
||||||
|
|
||||||
result = RegSetKeyValueW(
|
result = RegSetKeyValueW(key, L"DefaultIcon", nullptr, REG_SZ, exeFilePath, (exeLen + 1) * sizeof(wchar_t));
|
||||||
key, L"DefaultIcon", nullptr, REG_SZ, exeFilePath, (exeLen + 1) * sizeof(wchar_t));
|
|
||||||
if (FAILED(result)) {
|
if (FAILED(result)) {
|
||||||
fprintf(stderr, "Error writing icon\n");
|
fprintf(stderr, "Error writing icon\n");
|
||||||
}
|
}
|
||||||
|
|
||||||
len = static_cast<DWORD>(lstrlenW(openCommand) + 1);
|
len = static_cast<DWORD>(lstrlenW(openCommand) + 1);
|
||||||
result = RegSetKeyValueW(
|
result = RegSetKeyValueW(key, L"shell\\open\\command", nullptr, REG_SZ, openCommand, len * sizeof(wchar_t));
|
||||||
key, L"shell\\open\\command", nullptr, REG_SZ, openCommand, len * sizeof(wchar_t));
|
|
||||||
if (FAILED(result)) {
|
if (FAILED(result)) {
|
||||||
fprintf(stderr, "Error writing command\n");
|
fprintf(stderr, "Error writing command\n");
|
||||||
}
|
}
|
||||||
RegCloseKey(key);
|
RegCloseKey(key);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
extern "C" void Discord_Register(const char *applicationId, const char *command) {
|
extern "C" void Discord_Register(const char *applicationId, const char *command) {
|
||||||
wchar_t appId[32];
|
|
||||||
|
wchar_t appId[32]{};
|
||||||
MultiByteToWideChar(CP_UTF8, 0, applicationId, -1, appId, 32);
|
MultiByteToWideChar(CP_UTF8, 0, applicationId, -1, appId, 32);
|
||||||
|
|
||||||
wchar_t openCommand[1024];
|
wchar_t openCommand[1024]{};
|
||||||
const wchar_t *wcommand = nullptr;
|
const wchar_t *wcommand = nullptr;
|
||||||
if (command && command[0]) {
|
if (command && command[0]) {
|
||||||
const auto commandBufferLen = sizeof(openCommand) / sizeof(*openCommand);
|
const auto commandBufferLen = sizeof(openCommand) / sizeof(*openCommand);
|
||||||
@@ -142,42 +161,6 @@ extern "C" void Discord_Register(const char *applicationId, const char *command)
|
|||||||
}
|
}
|
||||||
|
|
||||||
Discord_RegisterW(appId, wcommand);
|
Discord_RegisterW(appId, wcommand);
|
||||||
}
|
|
||||||
|
|
||||||
extern "C" void Discord_RegisterSteamGame(const char *applicationId,
|
|
||||||
const char *steamId) {
|
|
||||||
wchar_t appId[32];
|
|
||||||
MultiByteToWideChar(CP_UTF8, 0, applicationId, -1, appId, 32);
|
|
||||||
|
|
||||||
wchar_t wSteamId[32];
|
|
||||||
MultiByteToWideChar(CP_UTF8, 0, steamId, -1, wSteamId, 32);
|
|
||||||
|
|
||||||
HKEY key;
|
|
||||||
auto status = RegOpenKeyExW(HKEY_CURRENT_USER, L"Software\\Valve\\Steam", 0, KEY_READ, &key);
|
|
||||||
if (status != ERROR_SUCCESS) {
|
|
||||||
fprintf(stderr, "Error opening Steam key\n");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
wchar_t steamPath[MAX_PATH];
|
|
||||||
DWORD pathBytes = sizeof(steamPath);
|
|
||||||
status = RegQueryValueExW(key, L"SteamExe", nullptr, nullptr, (BYTE *)steamPath, &pathBytes);
|
|
||||||
RegCloseKey(key);
|
|
||||||
if (status != ERROR_SUCCESS || pathBytes < 1) {
|
|
||||||
fprintf(stderr, "Error reading SteamExe key\n");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
DWORD pathChars = pathBytes / sizeof(wchar_t);
|
|
||||||
for (DWORD i = 0; i < pathChars; ++i) {
|
|
||||||
if (steamPath[i] == L'/') {
|
|
||||||
steamPath[i] = L'\\';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
wchar_t command[1024];
|
|
||||||
StringCbPrintfW(command, sizeof(command), L"\"%s\" steam://rungameid/%s", steamPath, wSteamId);
|
|
||||||
|
|
||||||
Discord_RegisterW(appId, command);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1,19 +1,44 @@
|
|||||||
#include "discord_rpc.h"
|
/*
|
||||||
|
* Copyright 2017 Discord, Inc.
|
||||||
#include "backoff.h"
|
*
|
||||||
#include "discord_register.h"
|
* Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||||
#include "msg_queue.h"
|
* this software and associated documentation files (the "Software"), to deal in
|
||||||
#include "rpc_connection.h"
|
* the Software without restriction, including without limitation the rights to
|
||||||
#include "serialization.h"
|
* 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.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
#include <atomic>
|
#include <atomic>
|
||||||
#include <chrono>
|
#include <chrono>
|
||||||
#include <mutex>
|
#include <mutex>
|
||||||
|
|
||||||
#include <condition_variable>
|
#include <condition_variable>
|
||||||
#include <thread>
|
#include <thread>
|
||||||
|
|
||||||
namespace discord_rpc {
|
#include "discord_rpc.h"
|
||||||
|
#include "discord_backoff.h"
|
||||||
|
#include "discord_register.h"
|
||||||
|
#include "discord_msg_queue.h"
|
||||||
|
#include "discord_rpc_connection.h"
|
||||||
|
#include "discord_serialization.h"
|
||||||
|
|
||||||
|
using namespace discord_rpc;
|
||||||
|
|
||||||
|
static void Discord_UpdateConnection();
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
|
||||||
constexpr size_t MaxMessageSize { 16 * 1024 };
|
constexpr size_t MaxMessageSize { 16 * 1024 };
|
||||||
constexpr size_t MessageQueueSize { 8 };
|
constexpr size_t MessageQueueSize { 8 };
|
||||||
@@ -67,14 +92,12 @@ static MsgQueue<QueuedMessage, MessageQueueSize> SendQueue;
|
|||||||
static MsgQueue<User, JoinQueueSize> JoinAskQueue;
|
static MsgQueue<User, JoinQueueSize> JoinAskQueue;
|
||||||
static User connectedUser;
|
static User connectedUser;
|
||||||
|
|
||||||
// We want to auto connect, and retry on failure, but not as fast as possible. This does expoential
|
// We want to auto connect, and retry on failure, but not as fast as possible. This does expoential backoff from 0.5 seconds to 1 minute
|
||||||
// backoff from 0.5 seconds to 1 minute
|
|
||||||
static Backoff ReconnectTimeMs(500, 60 * 1000);
|
static Backoff ReconnectTimeMs(500, 60 * 1000);
|
||||||
static auto NextConnect = std::chrono::system_clock::now();
|
static auto NextConnect = std::chrono::system_clock::now();
|
||||||
static int Pid { 0 };
|
static int Pid { 0 };
|
||||||
static int Nonce { 1 };
|
static int Nonce { 1 };
|
||||||
|
|
||||||
static void Discord_UpdateConnection(void);
|
|
||||||
class IoThreadHolder {
|
class IoThreadHolder {
|
||||||
private:
|
private:
|
||||||
std::atomic_bool keepRunning { true };
|
std::atomic_bool keepRunning { true };
|
||||||
@@ -108,14 +131,55 @@ class IoThreadHolder {
|
|||||||
|
|
||||||
~IoThreadHolder() { Stop(); }
|
~IoThreadHolder() { Stop(); }
|
||||||
};
|
};
|
||||||
|
|
||||||
static IoThreadHolder *IoThread { nullptr };
|
static IoThreadHolder *IoThread { nullptr };
|
||||||
|
|
||||||
static void UpdateReconnectTime() {
|
static void UpdateReconnectTime() {
|
||||||
NextConnect = std::chrono::system_clock::now() +
|
|
||||||
std::chrono::duration<int64_t, std::milli> { ReconnectTimeMs.nextDelay() };
|
NextConnect = std::chrono::system_clock::now() + std::chrono::duration<int64_t, std::milli> { ReconnectTimeMs.nextDelay() };
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static void Discord_UpdateConnection(void) {
|
static void SignalIOActivity() {
|
||||||
|
|
||||||
|
if (IoThread != nullptr) {
|
||||||
|
IoThread->Notify();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool RegisterForEvent(const char *evtName) {
|
||||||
|
|
||||||
|
auto qmessage = SendQueue.GetNextAddMessage();
|
||||||
|
if (qmessage) {
|
||||||
|
qmessage->length = JsonWriteSubscribeCommand(qmessage->buffer, sizeof(qmessage->buffer), Nonce++, evtName);
|
||||||
|
SendQueue.CommitAdd();
|
||||||
|
SignalIOActivity();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool DeregisterForEvent(const char *evtName) {
|
||||||
|
|
||||||
|
auto qmessage = SendQueue.GetNextAddMessage();
|
||||||
|
if (qmessage) {
|
||||||
|
qmessage->length = JsonWriteUnsubscribeCommand(qmessage->buffer, sizeof(qmessage->buffer), Nonce++, evtName);
|
||||||
|
SendQueue.CommitAdd();
|
||||||
|
SignalIOActivity();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
static void Discord_UpdateConnection() {
|
||||||
|
|
||||||
if (!Connection) {
|
if (!Connection) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -217,54 +281,18 @@ static void Discord_UpdateConnection(void) {
|
|||||||
SendQueue.CommitSend();
|
SendQueue.CommitSend();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static void SignalIOActivity() {
|
extern "C" void Discord_Initialize(const char *applicationId, DiscordEventHandlers *handlers, const int autoRegister) {
|
||||||
if (IoThread != nullptr) {
|
|
||||||
IoThread->Notify();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static bool RegisterForEvent(const char *evtName) {
|
|
||||||
auto qmessage = SendQueue.GetNextAddMessage();
|
|
||||||
if (qmessage) {
|
|
||||||
qmessage->length =
|
|
||||||
JsonWriteSubscribeCommand(qmessage->buffer, sizeof(qmessage->buffer), Nonce++, evtName);
|
|
||||||
SendQueue.CommitAdd();
|
|
||||||
SignalIOActivity();
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
static bool DeregisterForEvent(const char *evtName) {
|
|
||||||
auto qmessage = SendQueue.GetNextAddMessage();
|
|
||||||
if (qmessage) {
|
|
||||||
qmessage->length =
|
|
||||||
JsonWriteUnsubscribeCommand(qmessage->buffer, sizeof(qmessage->buffer), Nonce++, evtName);
|
|
||||||
SendQueue.CommitAdd();
|
|
||||||
SignalIOActivity();
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
extern "C" void Discord_Initialize(const char *applicationId,
|
|
||||||
DiscordEventHandlers *handlers,
|
|
||||||
int autoRegister,
|
|
||||||
const char *optionalSteamId) {
|
|
||||||
IoThread = new (std::nothrow) IoThreadHolder();
|
IoThread = new (std::nothrow) IoThreadHolder();
|
||||||
if (IoThread == nullptr) {
|
if (IoThread == nullptr) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (autoRegister) {
|
if (autoRegister) {
|
||||||
if (optionalSteamId && optionalSteamId[0]) {
|
Discord_Register(applicationId, nullptr);
|
||||||
Discord_RegisterSteamGame(applicationId, optionalSteamId);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
Discord_Register(applicationId, nullptr);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Pid = GetProcessId();
|
Pid = GetProcessId();
|
||||||
@@ -323,9 +351,11 @@ extern "C" void Discord_Initialize(const char *applicationId,
|
|||||||
};
|
};
|
||||||
|
|
||||||
IoThread->Start();
|
IoThread->Start();
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
extern "C" void Discord_Shutdown(void) {
|
extern "C" void Discord_Shutdown() {
|
||||||
|
|
||||||
if (!Connection) {
|
if (!Connection) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -341,16 +371,19 @@ extern "C" void Discord_Shutdown(void) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
RpcConnection::Destroy(Connection);
|
RpcConnection::Destroy(Connection);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
extern "C" void Discord_UpdatePresence(const DiscordRichPresence *presence) {
|
extern "C" void Discord_UpdatePresence(const DiscordRichPresence *presence) {
|
||||||
|
|
||||||
{
|
{
|
||||||
std::lock_guard<std::mutex> guard(PresenceMutex);
|
std::lock_guard<std::mutex> guard(PresenceMutex);
|
||||||
QueuedPresence.length = JsonWriteRichPresenceObj(
|
QueuedPresence.length = JsonWriteRichPresenceObj(QueuedPresence.buffer, sizeof(QueuedPresence.buffer), Nonce++, Pid, presence);
|
||||||
QueuedPresence.buffer, sizeof(QueuedPresence.buffer), Nonce++, Pid, presence);
|
|
||||||
UpdatePresence.exchange(true);
|
UpdatePresence.exchange(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
SignalIOActivity();
|
SignalIOActivity();
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
extern "C" void Discord_ClearPresence(void) {
|
extern "C" void Discord_ClearPresence(void) {
|
||||||
@@ -358,20 +391,22 @@ extern "C" void Discord_ClearPresence(void) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
extern "C" void Discord_Respond(const char *userId, /* DISCORD_REPLY_ */ int reply) {
|
extern "C" void Discord_Respond(const char *userId, /* DISCORD_REPLY_ */ int reply) {
|
||||||
|
|
||||||
// if we are not connected, let's not batch up stale messages for later
|
// if we are not connected, let's not batch up stale messages for later
|
||||||
if (!Connection || !Connection->IsOpen()) {
|
if (!Connection || !Connection->IsOpen()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
auto qmessage = SendQueue.GetNextAddMessage();
|
auto qmessage = SendQueue.GetNextAddMessage();
|
||||||
if (qmessage) {
|
if (qmessage) {
|
||||||
qmessage->length =
|
qmessage->length = JsonWriteJoinReply(qmessage->buffer, sizeof(qmessage->buffer), userId, reply, Nonce++);
|
||||||
JsonWriteJoinReply(qmessage->buffer, sizeof(qmessage->buffer), userId, reply, Nonce++);
|
|
||||||
SendQueue.CommitAdd();
|
SendQueue.CommitAdd();
|
||||||
SignalIOActivity();
|
SignalIOActivity();
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
extern "C" void Discord_RunCallbacks(void) {
|
extern "C" void Discord_RunCallbacks() {
|
||||||
|
|
||||||
// Note on some weirdness: internally we might connect, get other signals, disconnect any number
|
// Note on some weirdness: internally we might connect, get other signals, disconnect any number
|
||||||
// of times inbetween calls here. Externally, we want the sequence to seem sane, so any other
|
// of times inbetween calls here. Externally, we want the sequence to seem sane, so any other
|
||||||
// signals are book-ended by calls to ready and disconnect.
|
// signals are book-ended by calls to ready and disconnect.
|
||||||
@@ -380,8 +415,8 @@ extern "C" void Discord_RunCallbacks(void) {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool wasDisconnected = WasJustDisconnected.exchange(false);
|
const bool wasDisconnected = WasJustDisconnected.exchange(false);
|
||||||
bool isConnected = Connection->IsOpen();
|
const bool isConnected = Connection->IsOpen();
|
||||||
|
|
||||||
if (isConnected) {
|
if (isConnected) {
|
||||||
// if we are connected, disconnect cb first
|
// if we are connected, disconnect cb first
|
||||||
@@ -394,10 +429,7 @@ extern "C" void Discord_RunCallbacks(void) {
|
|||||||
if (WasJustConnected.exchange(false)) {
|
if (WasJustConnected.exchange(false)) {
|
||||||
std::lock_guard<std::mutex> guard(HandlerMutex);
|
std::lock_guard<std::mutex> guard(HandlerMutex);
|
||||||
if (Handlers.ready) {
|
if (Handlers.ready) {
|
||||||
DiscordUser du { connectedUser.userId,
|
DiscordUser du { connectedUser.userId, connectedUser.username, connectedUser.discriminator, connectedUser.avatar };
|
||||||
connectedUser.username,
|
|
||||||
connectedUser.discriminator,
|
|
||||||
connectedUser.avatar };
|
|
||||||
Handlers.ready(&du);
|
Handlers.ready(&du);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -429,7 +461,7 @@ extern "C" void Discord_RunCallbacks(void) {
|
|||||||
// maybe show them in one common dialog and/or start fetching the avatars in parallel, and if
|
// maybe show them in one common dialog and/or start fetching the avatars in parallel, and if
|
||||||
// not it should be trivial for the implementer to make a queue themselves.
|
// not it should be trivial for the implementer to make a queue themselves.
|
||||||
while (JoinAskQueue.HavePendingSends()) {
|
while (JoinAskQueue.HavePendingSends()) {
|
||||||
auto req = JoinAskQueue.GetNextSendMessage();
|
const auto req = JoinAskQueue.GetNextSendMessage();
|
||||||
{
|
{
|
||||||
std::lock_guard<std::mutex> guard(HandlerMutex);
|
std::lock_guard<std::mutex> guard(HandlerMutex);
|
||||||
if (Handlers.joinRequest) {
|
if (Handlers.joinRequest) {
|
||||||
@@ -447,9 +479,11 @@ extern "C" void Discord_RunCallbacks(void) {
|
|||||||
Handlers.disconnected(LastDisconnectErrorCode, LastDisconnectErrorMessage);
|
Handlers.disconnected(LastDisconnectErrorCode, LastDisconnectErrorMessage);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
extern "C" void Discord_UpdateHandlers(DiscordEventHandlers *newHandlers) {
|
extern "C" void Discord_UpdateHandlers(DiscordEventHandlers *newHandlers) {
|
||||||
|
|
||||||
if (newHandlers) {
|
if (newHandlers) {
|
||||||
#define HANDLE_EVENT_REGISTRATION(handler_name, event) \
|
#define HANDLE_EVENT_REGISTRATION(handler_name, event) \
|
||||||
if (!Handlers.handler_name && newHandlers->handler_name) { \
|
if (!Handlers.handler_name && newHandlers->handler_name) { \
|
||||||
@@ -472,8 +506,5 @@ extern "C" void Discord_UpdateHandlers(DiscordEventHandlers *newHandlers) {
|
|||||||
std::lock_guard<std::mutex> guard(HandlerMutex);
|
std::lock_guard<std::mutex> guard(HandlerMutex);
|
||||||
Handlers = {};
|
Handlers = {};
|
||||||
}
|
}
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace discord_rpc
|
|
||||||
|
|
||||||
94
3rdparty/discord-rpc/discord_rpc.h
vendored
Normal file
94
3rdparty/discord-rpc/discord_rpc.h
vendored
Normal file
@@ -0,0 +1,94 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2017 Discord, Inc.
|
||||||
|
*
|
||||||
|
* 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.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef DISCORD_RPC_H
|
||||||
|
#define DISCORD_RPC_H
|
||||||
|
|
||||||
|
#include <cstdint>
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
extern "C" {
|
||||||
|
#endif
|
||||||
|
|
||||||
|
typedef struct DiscordRichPresence {
|
||||||
|
int type;
|
||||||
|
int status_display_type;
|
||||||
|
const char *name; /* max 128 bytes */
|
||||||
|
const char *state; /* max 128 bytes */
|
||||||
|
const char *details; /* max 128 bytes */
|
||||||
|
int64_t startTimestamp;
|
||||||
|
int64_t endTimestamp;
|
||||||
|
const char *largeImageKey; /* max 32 bytes */
|
||||||
|
const char *largeImageText; /* max 128 bytes */
|
||||||
|
const char *smallImageKey; /* max 32 bytes */
|
||||||
|
const char *smallImageText; /* max 128 bytes */
|
||||||
|
const char *partyId; /* max 128 bytes */
|
||||||
|
int partySize;
|
||||||
|
int partyMax;
|
||||||
|
int partyPrivacy;
|
||||||
|
const char *matchSecret; /* max 128 bytes */
|
||||||
|
const char *joinSecret; /* max 128 bytes */
|
||||||
|
const char *spectateSecret; /* max 128 bytes */
|
||||||
|
int8_t instance;
|
||||||
|
} DiscordRichPresence;
|
||||||
|
|
||||||
|
typedef struct DiscordUser {
|
||||||
|
const char *userId;
|
||||||
|
const char *username;
|
||||||
|
const char *discriminator;
|
||||||
|
const char *avatar;
|
||||||
|
} DiscordUser;
|
||||||
|
|
||||||
|
typedef struct DiscordEventHandlers {
|
||||||
|
void (*ready)(const DiscordUser *request);
|
||||||
|
void (*disconnected)(int errorCode, const char *message);
|
||||||
|
void (*errored)(int errorCode, const char *message);
|
||||||
|
void (*joinGame)(const char *joinSecret);
|
||||||
|
void (*spectateGame)(const char *spectateSecret);
|
||||||
|
void (*joinRequest)(const DiscordUser *request);
|
||||||
|
} DiscordEventHandlers;
|
||||||
|
|
||||||
|
#define DISCORD_REPLY_NO 0
|
||||||
|
#define DISCORD_REPLY_YES 1
|
||||||
|
#define DISCORD_REPLY_IGNORE 2
|
||||||
|
#define DISCORD_PARTY_PRIVATE 0
|
||||||
|
#define DISCORD_PARTY_PUBLIC 1
|
||||||
|
|
||||||
|
void Discord_Initialize(const char *applicationId, DiscordEventHandlers *handlers, const int autoRegister);
|
||||||
|
void Discord_Shutdown(void);
|
||||||
|
|
||||||
|
// checks for incoming messages, dispatches callbacks
|
||||||
|
void Discord_RunCallbacks(void);
|
||||||
|
|
||||||
|
void Discord_UpdatePresence(const DiscordRichPresence *presence);
|
||||||
|
void Discord_ClearPresence(void);
|
||||||
|
|
||||||
|
void Discord_Respond(const char *userid, /* DISCORD_REPLY_ */ int reply);
|
||||||
|
|
||||||
|
void Discord_UpdateHandlers(DiscordEventHandlers *handlers);
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
} /* extern "C" */
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#endif // DISCORD_RPC_H
|
||||||
@@ -1,24 +1,52 @@
|
|||||||
#include "rpc_connection.h"
|
/*
|
||||||
#include "serialization.h"
|
* Copyright 2017 Discord, Inc.
|
||||||
|
*
|
||||||
|
* 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.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "discord_rpc_connection.h"
|
||||||
|
#include "discord_serialization.h"
|
||||||
|
|
||||||
namespace discord_rpc {
|
namespace discord_rpc {
|
||||||
|
|
||||||
static const int RpcVersion = 1;
|
static const int RpcVersion = 1;
|
||||||
static RpcConnection Instance;
|
static RpcConnection Instance;
|
||||||
|
|
||||||
/*static*/ RpcConnection *RpcConnection::Create(const char *applicationId) {
|
RpcConnection *RpcConnection::Create(const char *applicationId) {
|
||||||
|
|
||||||
Instance.connection = BaseConnection::Create();
|
Instance.connection = BaseConnection::Create();
|
||||||
StringCopy(Instance.appId, applicationId);
|
StringCopy(Instance.appId, applicationId);
|
||||||
return &Instance;
|
return &Instance;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/*static*/ void RpcConnection::Destroy(RpcConnection *&c) {
|
void RpcConnection::Destroy(RpcConnection *&c) {
|
||||||
|
|
||||||
c->Close();
|
c->Close();
|
||||||
BaseConnection::Destroy(c->connection);
|
BaseConnection::Destroy(c->connection);
|
||||||
c = nullptr;
|
c = nullptr;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void RpcConnection::Open() {
|
void RpcConnection::Open() {
|
||||||
|
|
||||||
if (state == State::Connected) {
|
if (state == State::Connected) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -51,17 +79,21 @@ void RpcConnection::Open() {
|
|||||||
Close();
|
Close();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void RpcConnection::Close() {
|
void RpcConnection::Close() {
|
||||||
|
|
||||||
if (onDisconnect && (state == State::Connected || state == State::SentHandshake)) {
|
if (onDisconnect && (state == State::Connected || state == State::SentHandshake)) {
|
||||||
onDisconnect(lastErrorCode, lastErrorMessage);
|
onDisconnect(lastErrorCode, lastErrorMessage);
|
||||||
}
|
}
|
||||||
connection->Close();
|
connection->Close();
|
||||||
state = State::Disconnected;
|
state = State::Disconnected;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bool RpcConnection::Write(const void *data, size_t length) {
|
bool RpcConnection::Write(const void *data, size_t length) {
|
||||||
|
|
||||||
sendFrame.opcode = Opcode::Frame;
|
sendFrame.opcode = Opcode::Frame;
|
||||||
memcpy(sendFrame.message, data, length);
|
memcpy(sendFrame.message, data, length);
|
||||||
sendFrame.length = static_cast<uint32_t>(length);
|
sendFrame.length = static_cast<uint32_t>(length);
|
||||||
@@ -69,14 +101,17 @@ bool RpcConnection::Write(const void *data, size_t length) {
|
|||||||
Close();
|
Close();
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bool RpcConnection::Read(JsonDocument &message) {
|
bool RpcConnection::Read(JsonDocument &message) {
|
||||||
|
|
||||||
if (state != State::Connected && state != State::SentHandshake) {
|
if (state != State::Connected && state != State::SentHandshake) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
MessageFrame readFrame;
|
MessageFrame readFrame{};
|
||||||
for (;;) {
|
for (;;) {
|
||||||
bool didRead = connection->Read(&readFrame, sizeof(MessageFrameHeader));
|
bool didRead = connection->Read(&readFrame, sizeof(MessageFrameHeader));
|
||||||
if (!didRead) {
|
if (!didRead) {
|
||||||
@@ -127,7 +162,7 @@ bool RpcConnection::Read(JsonDocument &message) {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace discord_rpc
|
} // namespace discord_rpc
|
||||||
|
|
||||||
88
3rdparty/discord-rpc/discord_rpc_connection.h
vendored
Normal file
88
3rdparty/discord-rpc/discord_rpc_connection.h
vendored
Normal file
@@ -0,0 +1,88 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2017 Discord, Inc.
|
||||||
|
*
|
||||||
|
* 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.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef DISCORD_RPC_CONNECTION_H
|
||||||
|
#define DISCORD_RPC_CONNECTION_H
|
||||||
|
|
||||||
|
#include "discord_connection.h"
|
||||||
|
#include "discord_serialization.h"
|
||||||
|
|
||||||
|
namespace discord_rpc {
|
||||||
|
|
||||||
|
// I took this from the buffer size libuv uses for named pipes; I suspect ours would usually be much smaller.
|
||||||
|
constexpr size_t MaxRpcFrameSize = 64 * 1024;
|
||||||
|
|
||||||
|
struct RpcConnection {
|
||||||
|
enum class ErrorCode : int {
|
||||||
|
Success = 0,
|
||||||
|
PipeClosed = 1,
|
||||||
|
ReadCorrupt = 2,
|
||||||
|
};
|
||||||
|
|
||||||
|
enum class Opcode : uint32_t {
|
||||||
|
Handshake = 0,
|
||||||
|
Frame = 1,
|
||||||
|
Close = 2,
|
||||||
|
Ping = 3,
|
||||||
|
Pong = 4,
|
||||||
|
};
|
||||||
|
|
||||||
|
struct MessageFrameHeader {
|
||||||
|
Opcode opcode;
|
||||||
|
uint32_t length;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct MessageFrame : public MessageFrameHeader {
|
||||||
|
char message[MaxRpcFrameSize - sizeof(MessageFrameHeader)];
|
||||||
|
};
|
||||||
|
|
||||||
|
enum class State : uint32_t {
|
||||||
|
Disconnected,
|
||||||
|
SentHandshake,
|
||||||
|
AwaitingResponse,
|
||||||
|
Connected,
|
||||||
|
};
|
||||||
|
|
||||||
|
BaseConnection *connection { nullptr };
|
||||||
|
State state { State::Disconnected };
|
||||||
|
void (*onConnect)(JsonDocument &message) { nullptr };
|
||||||
|
void (*onDisconnect)(int errorCode, const char *message) { nullptr };
|
||||||
|
char appId[64] {};
|
||||||
|
int lastErrorCode { 0 };
|
||||||
|
char lastErrorMessage[256] {};
|
||||||
|
RpcConnection::MessageFrame sendFrame;
|
||||||
|
|
||||||
|
static RpcConnection *Create(const char *applicationId);
|
||||||
|
static void Destroy(RpcConnection *&);
|
||||||
|
|
||||||
|
inline bool IsOpen() const { return state == State::Connected; }
|
||||||
|
|
||||||
|
void Open();
|
||||||
|
void Close();
|
||||||
|
bool Write(const void *data, size_t length);
|
||||||
|
bool Read(JsonDocument &message);
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace discord_rpc
|
||||||
|
|
||||||
|
#endif // DISCORD_RPC_CONNECTION_H
|
||||||
@@ -1,11 +1,35 @@
|
|||||||
#include "serialization.h"
|
/*
|
||||||
#include "connection.h"
|
* Copyright 2017 Discord, Inc.
|
||||||
|
*
|
||||||
|
* 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.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "discord_serialization.h"
|
||||||
|
#include "discord_connection.h"
|
||||||
#include "discord_rpc.h"
|
#include "discord_rpc.h"
|
||||||
|
|
||||||
namespace discord_rpc {
|
namespace discord_rpc {
|
||||||
|
|
||||||
template<typename T>
|
template<typename T>
|
||||||
void NumberToString(char *dest, T number) {
|
void NumberToString(char *dest, T number) {
|
||||||
|
|
||||||
if (!number) {
|
if (!number) {
|
||||||
*dest++ = '0';
|
*dest++ = '0';
|
||||||
*dest++ = 0;
|
*dest++ = 0;
|
||||||
@@ -26,6 +50,7 @@ void NumberToString(char *dest, T number) {
|
|||||||
*dest++ = temp[place];
|
*dest++ = temp[place];
|
||||||
}
|
}
|
||||||
*dest = 0;
|
*dest = 0;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// it's ever so slightly faster to not have to strlen the key
|
// it's ever so slightly faster to not have to strlen the key
|
||||||
@@ -62,24 +87,25 @@ struct WriteArray {
|
|||||||
|
|
||||||
template<typename T>
|
template<typename T>
|
||||||
void WriteOptionalString(JsonWriter &w, T &k, const char *value) {
|
void WriteOptionalString(JsonWriter &w, T &k, const char *value) {
|
||||||
|
|
||||||
if (value && value[0]) {
|
if (value && value[0]) {
|
||||||
w.Key(k, sizeof(T) - 1);
|
w.Key(k, sizeof(T) - 1);
|
||||||
w.String(value);
|
w.String(value);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static void JsonWriteNonce(JsonWriter &writer, int nonce) {
|
static void JsonWriteNonce(JsonWriter &writer, const int nonce) {
|
||||||
|
|
||||||
WriteKey(writer, "nonce");
|
WriteKey(writer, "nonce");
|
||||||
char nonceBuffer[32];
|
char nonceBuffer[32];
|
||||||
NumberToString(nonceBuffer, nonce);
|
NumberToString(nonceBuffer, nonce);
|
||||||
writer.String(nonceBuffer);
|
writer.String(nonceBuffer);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
size_t JsonWriteRichPresenceObj(char *dest,
|
size_t JsonWriteRichPresenceObj(char *dest, const size_t maxLen, const int nonce, const int pid, const DiscordRichPresence *presence) {
|
||||||
size_t maxLen,
|
|
||||||
int nonce,
|
|
||||||
int pid,
|
|
||||||
const DiscordRichPresence *presence) {
|
|
||||||
JsonWriter writer(dest, maxLen);
|
JsonWriter writer(dest, maxLen);
|
||||||
|
|
||||||
{
|
{
|
||||||
@@ -102,6 +128,9 @@ size_t JsonWriteRichPresenceObj(char *dest,
|
|||||||
if (presence->type >= 0 && presence->type <= 5) {
|
if (presence->type >= 0 && presence->type <= 5) {
|
||||||
WriteKey(writer, "type");
|
WriteKey(writer, "type");
|
||||||
writer.Int(presence->type);
|
writer.Int(presence->type);
|
||||||
|
|
||||||
|
WriteKey(writer, "status_display_type");
|
||||||
|
writer.Int(presence->status_display_type);
|
||||||
}
|
}
|
||||||
|
|
||||||
WriteOptionalString(writer, "name", presence->name);
|
WriteOptionalString(writer, "name", presence->name);
|
||||||
@@ -168,6 +197,7 @@ size_t JsonWriteRichPresenceObj(char *dest,
|
|||||||
}
|
}
|
||||||
|
|
||||||
size_t JsonWriteHandshakeObj(char *dest, size_t maxLen, int version, const char *applicationId) {
|
size_t JsonWriteHandshakeObj(char *dest, size_t maxLen, int version, const char *applicationId) {
|
||||||
|
|
||||||
JsonWriter writer(dest, maxLen);
|
JsonWriter writer(dest, maxLen);
|
||||||
|
|
||||||
{
|
{
|
||||||
@@ -179,9 +209,11 @@ size_t JsonWriteHandshakeObj(char *dest, size_t maxLen, int version, const char
|
|||||||
}
|
}
|
||||||
|
|
||||||
return writer.Size();
|
return writer.Size();
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
size_t JsonWriteSubscribeCommand(char *dest, size_t maxLen, int nonce, const char *evtName) {
|
size_t JsonWriteSubscribeCommand(char *dest, size_t maxLen, int nonce, const char *evtName) {
|
||||||
|
|
||||||
JsonWriter writer(dest, maxLen);
|
JsonWriter writer(dest, maxLen);
|
||||||
|
|
||||||
{
|
{
|
||||||
@@ -197,9 +229,11 @@ size_t JsonWriteSubscribeCommand(char *dest, size_t maxLen, int nonce, const cha
|
|||||||
}
|
}
|
||||||
|
|
||||||
return writer.Size();
|
return writer.Size();
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
size_t JsonWriteUnsubscribeCommand(char *dest, size_t maxLen, int nonce, const char *evtName) {
|
size_t JsonWriteUnsubscribeCommand(char *dest, size_t maxLen, int nonce, const char *evtName) {
|
||||||
|
|
||||||
JsonWriter writer(dest, maxLen);
|
JsonWriter writer(dest, maxLen);
|
||||||
|
|
||||||
{
|
{
|
||||||
@@ -215,9 +249,11 @@ size_t JsonWriteUnsubscribeCommand(char *dest, size_t maxLen, int nonce, const c
|
|||||||
}
|
}
|
||||||
|
|
||||||
return writer.Size();
|
return writer.Size();
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
size_t JsonWriteJoinReply(char *dest, size_t maxLen, const char *userId, int reply, int nonce) {
|
size_t JsonWriteJoinReply(char *dest, size_t maxLen, const char *userId, const int reply, const int nonce) {
|
||||||
|
|
||||||
JsonWriter writer(dest, maxLen);
|
JsonWriter writer(dest, maxLen);
|
||||||
|
|
||||||
{
|
{
|
||||||
@@ -243,7 +279,7 @@ size_t JsonWriteJoinReply(char *dest, size_t maxLen, const char *userId, int rep
|
|||||||
}
|
}
|
||||||
|
|
||||||
return writer.Size();
|
return writer.Size();
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace discord_rpc
|
} // namespace discord_rpc
|
||||||
|
|
||||||
@@ -1,9 +1,35 @@
|
|||||||
#pragma once
|
/*
|
||||||
|
* Copyright 2017 Discord, Inc.
|
||||||
|
*
|
||||||
|
* 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.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef DISCORD_SERIALIZATION_H
|
||||||
|
#define DISCORD_SERIALIZATION_H
|
||||||
|
|
||||||
#include <rapidjson/document.h>
|
#include <rapidjson/document.h>
|
||||||
#include <rapidjson/stringbuffer.h>
|
#include <rapidjson/stringbuffer.h>
|
||||||
#include <rapidjson/writer.h>
|
#include <rapidjson/writer.h>
|
||||||
|
|
||||||
|
struct DiscordRichPresence;
|
||||||
|
|
||||||
namespace discord_rpc {
|
namespace discord_rpc {
|
||||||
|
|
||||||
// if only there was a standard library function for this
|
// if only there was a standard library function for this
|
||||||
@@ -24,12 +50,7 @@ inline size_t StringCopy(char (&dest)[Len], const char *src) {
|
|||||||
size_t JsonWriteHandshakeObj(char *dest, size_t maxLen, int version, const char *applicationId);
|
size_t JsonWriteHandshakeObj(char *dest, size_t maxLen, int version, const char *applicationId);
|
||||||
|
|
||||||
// Commands
|
// Commands
|
||||||
struct DiscordRichPresence;
|
size_t JsonWriteRichPresenceObj(char *dest, const size_t maxLen, const int nonce, const int pid, const DiscordRichPresence *presence);
|
||||||
size_t JsonWriteRichPresenceObj(char *dest,
|
|
||||||
size_t maxLen,
|
|
||||||
int nonce,
|
|
||||||
int pid,
|
|
||||||
const DiscordRichPresence *presence);
|
|
||||||
size_t JsonWriteSubscribeCommand(char *dest, size_t maxLen, int nonce, const char *evtName);
|
size_t JsonWriteSubscribeCommand(char *dest, size_t maxLen, int nonce, const char *evtName);
|
||||||
|
|
||||||
size_t JsonWriteUnsubscribeCommand(char *dest, size_t maxLen, int nonce, const char *evtName);
|
size_t JsonWriteUnsubscribeCommand(char *dest, size_t maxLen, int nonce, const char *evtName);
|
||||||
@@ -149,35 +170,44 @@ class JsonDocument : public JsonDocumentBase {
|
|||||||
using JsonValue = rapidjson::GenericValue<UTF8, PoolAllocator>;
|
using JsonValue = rapidjson::GenericValue<UTF8, PoolAllocator>;
|
||||||
|
|
||||||
inline JsonValue *GetObjMember(JsonValue *obj, const char *name) {
|
inline JsonValue *GetObjMember(JsonValue *obj, const char *name) {
|
||||||
|
|
||||||
if (obj) {
|
if (obj) {
|
||||||
auto member = obj->FindMember(name);
|
auto member = obj->FindMember(name);
|
||||||
if (member != obj->MemberEnd() && member->value.IsObject()) {
|
if (member != obj->MemberEnd() && member->value.IsObject()) {
|
||||||
return &member->value;
|
return &member->value;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return nullptr;
|
return nullptr;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
inline int GetIntMember(JsonValue *obj, const char *name, int notFoundDefault = 0) {
|
inline int GetIntMember(JsonValue *obj, const char *name, int notFoundDefault = 0) {
|
||||||
|
|
||||||
if (obj) {
|
if (obj) {
|
||||||
auto member = obj->FindMember(name);
|
auto member = obj->FindMember(name);
|
||||||
if (member != obj->MemberEnd() && member->value.IsInt()) {
|
if (member != obj->MemberEnd() && member->value.IsInt()) {
|
||||||
return member->value.GetInt();
|
return member->value.GetInt();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return notFoundDefault;
|
return notFoundDefault;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
inline const char *GetStrMember(JsonValue *obj,
|
inline const char *GetStrMember(JsonValue *obj, const char *name, const char *notFoundDefault = nullptr) {
|
||||||
const char *name,
|
|
||||||
const char *notFoundDefault = nullptr) {
|
|
||||||
if (obj) {
|
if (obj) {
|
||||||
auto member = obj->FindMember(name);
|
auto member = obj->FindMember(name);
|
||||||
if (member != obj->MemberEnd() && member->value.IsString()) {
|
if (member != obj->MemberEnd() && member->value.IsString()) {
|
||||||
return member->value.GetString();
|
return member->value.GetString();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return notFoundDefault;
|
return notFoundDefault;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace discord_rpc
|
} // namespace discord_rpc
|
||||||
|
|
||||||
|
#endif // DISCORD_SERIALIZATION_H
|
||||||
12
3rdparty/discord-rpc/include/discord_register.h
vendored
12
3rdparty/discord-rpc/include/discord_register.h
vendored
@@ -1,12 +0,0 @@
|
|||||||
#pragma once
|
|
||||||
|
|
||||||
#ifdef __cplusplus
|
|
||||||
extern "C" {
|
|
||||||
#endif
|
|
||||||
|
|
||||||
void Discord_Register(const char* applicationId, const char* command);
|
|
||||||
void Discord_RegisterSteamGame(const char* applicationId, const char* steamId);
|
|
||||||
|
|
||||||
#ifdef __cplusplus
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
77
3rdparty/discord-rpc/include/discord_rpc.h
vendored
77
3rdparty/discord-rpc/include/discord_rpc.h
vendored
@@ -1,77 +0,0 @@
|
|||||||
#pragma once
|
|
||||||
#include <stdint.h>
|
|
||||||
|
|
||||||
// clang-format off
|
|
||||||
|
|
||||||
// clang-format on
|
|
||||||
|
|
||||||
namespace discord_rpc {
|
|
||||||
|
|
||||||
#ifdef __cplusplus
|
|
||||||
extern "C" {
|
|
||||||
#endif
|
|
||||||
|
|
||||||
typedef struct DiscordRichPresence {
|
|
||||||
int type;
|
|
||||||
const char* name; /* max 128 bytes */
|
|
||||||
const char* state; /* max 128 bytes */
|
|
||||||
const char* details; /* max 128 bytes */
|
|
||||||
int64_t startTimestamp;
|
|
||||||
int64_t endTimestamp;
|
|
||||||
const char* largeImageKey; /* max 32 bytes */
|
|
||||||
const char* largeImageText; /* max 128 bytes */
|
|
||||||
const char* smallImageKey; /* max 32 bytes */
|
|
||||||
const char* smallImageText; /* max 128 bytes */
|
|
||||||
const char* partyId; /* max 128 bytes */
|
|
||||||
int partySize;
|
|
||||||
int partyMax;
|
|
||||||
int partyPrivacy;
|
|
||||||
const char* matchSecret; /* max 128 bytes */
|
|
||||||
const char* joinSecret; /* max 128 bytes */
|
|
||||||
const char* spectateSecret; /* max 128 bytes */
|
|
||||||
int8_t instance;
|
|
||||||
} DiscordRichPresence;
|
|
||||||
|
|
||||||
typedef struct DiscordUser {
|
|
||||||
const char* userId;
|
|
||||||
const char* username;
|
|
||||||
const char* discriminator;
|
|
||||||
const char* avatar;
|
|
||||||
} DiscordUser;
|
|
||||||
|
|
||||||
typedef struct DiscordEventHandlers {
|
|
||||||
void (*ready)(const DiscordUser* request);
|
|
||||||
void (*disconnected)(int errorCode, const char* message);
|
|
||||||
void (*errored)(int errorCode, const char* message);
|
|
||||||
void (*joinGame)(const char* joinSecret);
|
|
||||||
void (*spectateGame)(const char* spectateSecret);
|
|
||||||
void (*joinRequest)(const DiscordUser* request);
|
|
||||||
} DiscordEventHandlers;
|
|
||||||
|
|
||||||
#define DISCORD_REPLY_NO 0
|
|
||||||
#define DISCORD_REPLY_YES 1
|
|
||||||
#define DISCORD_REPLY_IGNORE 2
|
|
||||||
#define DISCORD_PARTY_PRIVATE 0
|
|
||||||
#define DISCORD_PARTY_PUBLIC 1
|
|
||||||
|
|
||||||
void Discord_Initialize(const char* applicationId,
|
|
||||||
DiscordEventHandlers* handlers,
|
|
||||||
int autoRegister,
|
|
||||||
const char* optionalSteamId);
|
|
||||||
void Discord_Shutdown(void);
|
|
||||||
|
|
||||||
/* checks for incoming messages, dispatches callbacks */
|
|
||||||
void Discord_RunCallbacks(void);
|
|
||||||
|
|
||||||
void Discord_UpdatePresence(const DiscordRichPresence* presence);
|
|
||||||
void Discord_ClearPresence(void);
|
|
||||||
|
|
||||||
void Discord_Respond(const char* userid, /* DISCORD_REPLY_ */ int reply);
|
|
||||||
|
|
||||||
void Discord_UpdateHandlers(DiscordEventHandlers* handlers);
|
|
||||||
|
|
||||||
#ifdef __cplusplus
|
|
||||||
} /* extern "C" */
|
|
||||||
} // namespace discord_rpc
|
|
||||||
|
|
||||||
#endif
|
|
||||||
41
3rdparty/discord-rpc/src/CMakeLists.txt
vendored
41
3rdparty/discord-rpc/src/CMakeLists.txt
vendored
@@ -1,41 +0,0 @@
|
|||||||
set(DISCORD_RPC_SOURCES
|
|
||||||
../include/discord_rpc.h
|
|
||||||
../include/discord_register.h
|
|
||||||
discord_rpc.cpp
|
|
||||||
rpc_connection.h
|
|
||||||
rpc_connection.cpp
|
|
||||||
serialization.h
|
|
||||||
serialization.cpp
|
|
||||||
connection.h
|
|
||||||
backoff.h
|
|
||||||
msg_queue.h
|
|
||||||
)
|
|
||||||
|
|
||||||
if(UNIX)
|
|
||||||
list(APPEND DISCORD_RPC_SOURCES connection_unix.cpp)
|
|
||||||
if(APPLE)
|
|
||||||
list(APPEND DISCORD_RPC_SOURCES discord_register_osx.m)
|
|
||||||
add_definitions(-DDISCORD_OSX)
|
|
||||||
else()
|
|
||||||
list(APPEND DISCORD_RPC_SOURCES discord_register_linux.cpp)
|
|
||||||
add_definitions(-DDISCORD_LINUX)
|
|
||||||
endif()
|
|
||||||
endif()
|
|
||||||
|
|
||||||
if(WIN32)
|
|
||||||
list(APPEND DISCORD_RPC_SOURCES connection_win.cpp discord_register_win.cpp)
|
|
||||||
add_definitions(-DDISCORD_WINDOWS)
|
|
||||||
endif()
|
|
||||||
|
|
||||||
add_library(discord-rpc STATIC ${DISCORD_RPC_SOURCES})
|
|
||||||
|
|
||||||
if(APPLE)
|
|
||||||
target_link_libraries(discord-rpc PRIVATE "-framework AppKit")
|
|
||||||
endif()
|
|
||||||
|
|
||||||
if(WIN32)
|
|
||||||
target_link_libraries(discord-rpc PRIVATE psapi advapi32)
|
|
||||||
endif()
|
|
||||||
|
|
||||||
target_include_directories(discord-rpc SYSTEM PRIVATE ${RapidJSON_INCLUDE_DIRS})
|
|
||||||
target_include_directories(discord-rpc PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/../include)
|
|
||||||
44
3rdparty/discord-rpc/src/backoff.h
vendored
44
3rdparty/discord-rpc/src/backoff.h
vendored
@@ -1,44 +0,0 @@
|
|||||||
#pragma once
|
|
||||||
|
|
||||||
#include <algorithm>
|
|
||||||
#include <random>
|
|
||||||
#include <cstdint>
|
|
||||||
#include <ctime>
|
|
||||||
|
|
||||||
namespace discord_rpc {
|
|
||||||
|
|
||||||
struct Backoff {
|
|
||||||
int64_t minAmount;
|
|
||||||
int64_t maxAmount;
|
|
||||||
int64_t current;
|
|
||||||
int fails;
|
|
||||||
std::mt19937_64 randGenerator;
|
|
||||||
std::uniform_real_distribution<> randDistribution;
|
|
||||||
|
|
||||||
double rand01() { return randDistribution(randGenerator); }
|
|
||||||
|
|
||||||
Backoff(int64_t min, int64_t max)
|
|
||||||
: minAmount(min)
|
|
||||||
, maxAmount(max)
|
|
||||||
, current(min)
|
|
||||||
, fails(0)
|
|
||||||
, randGenerator(static_cast<uint64_t>(time(0)))
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
void reset()
|
|
||||||
{
|
|
||||||
fails = 0;
|
|
||||||
current = minAmount;
|
|
||||||
}
|
|
||||||
|
|
||||||
int64_t nextDelay()
|
|
||||||
{
|
|
||||||
++fails;
|
|
||||||
int64_t delay = static_cast<int64_t>(static_cast<double>(current) * 2.0 * rand01());
|
|
||||||
current = std::min(current + delay, maxAmount);
|
|
||||||
return current;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
} // namespace discord_rpc
|
|
||||||
22
3rdparty/discord-rpc/src/connection.h
vendored
22
3rdparty/discord-rpc/src/connection.h
vendored
@@ -1,22 +0,0 @@
|
|||||||
#pragma once
|
|
||||||
|
|
||||||
// This is to wrap the platform specific kinds of connect/read/write.
|
|
||||||
|
|
||||||
#include <cstdlib>
|
|
||||||
|
|
||||||
namespace discord_rpc {
|
|
||||||
|
|
||||||
// not really connectiony, but need per-platform
|
|
||||||
int GetProcessId();
|
|
||||||
|
|
||||||
struct BaseConnection {
|
|
||||||
static BaseConnection *Create();
|
|
||||||
static void Destroy(BaseConnection *&);
|
|
||||||
bool isOpen { false };
|
|
||||||
bool Open();
|
|
||||||
bool Close();
|
|
||||||
bool Write(const void *data, size_t length);
|
|
||||||
bool Read(void *data, size_t length);
|
|
||||||
};
|
|
||||||
|
|
||||||
} // namespace discord_rpc
|
|
||||||
104
3rdparty/discord-rpc/src/discord_register_linux.cpp
vendored
104
3rdparty/discord-rpc/src/discord_register_linux.cpp
vendored
@@ -1,104 +0,0 @@
|
|||||||
#include "discord_rpc.h"
|
|
||||||
#include "discord_register.h"
|
|
||||||
|
|
||||||
#include <cstdio>
|
|
||||||
#include <errno.h>
|
|
||||||
#include <cstdlib>
|
|
||||||
#include <cstring>
|
|
||||||
#include <sys/stat.h>
|
|
||||||
#include <sys/types.h>
|
|
||||||
#include <unistd.h>
|
|
||||||
|
|
||||||
namespace {
|
|
||||||
|
|
||||||
static bool Mkdir(const char *path) {
|
|
||||||
int result = mkdir(path, 0755);
|
|
||||||
if (result == 0) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
if (errno == EEXIST) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
} // namespace
|
|
||||||
|
|
||||||
// we want to register games so we can run them from Discord client as discord-<appid>://
|
|
||||||
extern "C" void Discord_Register(const char *applicationId, const char *command) {
|
|
||||||
// Add a desktop file and update some mime handlers so that xdg-open does the right thing.
|
|
||||||
|
|
||||||
const char *home = getenv("HOME");
|
|
||||||
if (!home) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
char exePath[1024];
|
|
||||||
if (!command || !command[0]) {
|
|
||||||
ssize_t size = readlink("/proc/self/exe", exePath, sizeof(exePath));
|
|
||||||
if (size <= 0 || size >= static_cast<ssize_t>(sizeof(exePath))) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
exePath[size] = '\0';
|
|
||||||
command = exePath;
|
|
||||||
}
|
|
||||||
|
|
||||||
constexpr char desktopFileFormat[] = "[Desktop Entry]\n"
|
|
||||||
"Name=Game %s\n"
|
|
||||||
"Exec=%s %%u\n" // note: it really wants that %u in there
|
|
||||||
"Type=Application\n"
|
|
||||||
"NoDisplay=true\n"
|
|
||||||
"Categories=Discord;Games;\n"
|
|
||||||
"MimeType=x-scheme-handler/discord-%s;\n";
|
|
||||||
char desktopFile[2048];
|
|
||||||
int fileLen = snprintf(
|
|
||||||
desktopFile, sizeof(desktopFile), desktopFileFormat, applicationId, command, applicationId);
|
|
||||||
if (fileLen <= 0) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
char desktopFilename[256];
|
|
||||||
(void)snprintf(desktopFilename, sizeof(desktopFilename), "/discord-%s.desktop", applicationId);
|
|
||||||
|
|
||||||
char desktopFilePath[1024];
|
|
||||||
(void)snprintf(desktopFilePath, sizeof(desktopFilePath), "%s/.local", home);
|
|
||||||
if (!Mkdir(desktopFilePath)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
strcat(desktopFilePath, "/share");
|
|
||||||
if (!Mkdir(desktopFilePath)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
strcat(desktopFilePath, "/applications");
|
|
||||||
if (!Mkdir(desktopFilePath)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
strcat(desktopFilePath, desktopFilename);
|
|
||||||
|
|
||||||
FILE *fp = fopen(desktopFilePath, "w");
|
|
||||||
if (fp) {
|
|
||||||
fwrite(desktopFile, 1, fileLen, fp);
|
|
||||||
fclose(fp);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
char xdgMimeCommand[1024];
|
|
||||||
snprintf(xdgMimeCommand,
|
|
||||||
sizeof(xdgMimeCommand),
|
|
||||||
"xdg-mime default discord-%s.desktop x-scheme-handler/discord-%s",
|
|
||||||
applicationId,
|
|
||||||
applicationId);
|
|
||||||
if (system(xdgMimeCommand) < 0) {
|
|
||||||
fprintf(stderr, "Failed to register mime handler\n");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
extern "C" void Discord_RegisterSteamGame(const char *applicationId,
|
|
||||||
const char *steamId) {
|
|
||||||
char command[256];
|
|
||||||
sprintf(command, "xdg-open steam://rungameid/%s", steamId);
|
|
||||||
Discord_Register(applicationId, command);
|
|
||||||
}
|
|
||||||
|
|
||||||
80
3rdparty/discord-rpc/src/discord_register_osx.m
vendored
80
3rdparty/discord-rpc/src/discord_register_osx.m
vendored
@@ -1,80 +0,0 @@
|
|||||||
#include <stdio.h>
|
|
||||||
#include <sys/stat.h>
|
|
||||||
|
|
||||||
#import <AppKit/AppKit.h>
|
|
||||||
|
|
||||||
#include "discord_register.h"
|
|
||||||
|
|
||||||
static void RegisterCommand(const char* applicationId, const char* command)
|
|
||||||
{
|
|
||||||
// There does not appear to be a way to register arbitrary commands on OSX, so instead we'll save the command
|
|
||||||
// to a file in the Discord config path, and when it is needed, Discord can try to load the file there, open
|
|
||||||
// the command therein (will pass to js's window.open, so requires a url-like thing)
|
|
||||||
|
|
||||||
// Note: will not work for sandboxed apps
|
|
||||||
NSString *home = NSHomeDirectory();
|
|
||||||
if (!home) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
NSString *path = [[[[[[home stringByAppendingPathComponent:@"Library"]
|
|
||||||
stringByAppendingPathComponent:@"Application Support"]
|
|
||||||
stringByAppendingPathComponent:@"discord"]
|
|
||||||
stringByAppendingPathComponent:@"games"]
|
|
||||||
stringByAppendingPathComponent:[NSString stringWithUTF8String:applicationId]]
|
|
||||||
stringByAppendingPathExtension:@"json"];
|
|
||||||
[[NSFileManager defaultManager] createDirectoryAtPath:[path stringByDeletingLastPathComponent] withIntermediateDirectories:YES attributes:nil error:nil];
|
|
||||||
|
|
||||||
NSString *jsonBuffer = [NSString stringWithFormat:@"{\"command\": \"%s\"}", command];
|
|
||||||
[jsonBuffer writeToFile:path atomically:NO encoding:NSUTF8StringEncoding error:nil];
|
|
||||||
}
|
|
||||||
|
|
||||||
static void RegisterURL(const char* applicationId)
|
|
||||||
{
|
|
||||||
char url[256];
|
|
||||||
snprintf(url, sizeof(url), "discord-%s", applicationId);
|
|
||||||
CFStringRef cfURL = CFStringCreateWithCString(NULL, url, kCFStringEncodingUTF8);
|
|
||||||
|
|
||||||
NSString* myBundleId = [[NSBundle mainBundle] bundleIdentifier];
|
|
||||||
if (!myBundleId) {
|
|
||||||
fprintf(stderr, "No bundle id found\n");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
NSURL* myURL = [[NSBundle mainBundle] bundleURL];
|
|
||||||
if (!myURL) {
|
|
||||||
fprintf(stderr, "No bundle url found\n");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
OSStatus status = LSSetDefaultHandlerForURLScheme(cfURL, (__bridge CFStringRef)myBundleId);
|
|
||||||
if (status != noErr) {
|
|
||||||
fprintf(stderr, "Error in LSSetDefaultHandlerForURLScheme: %d\n", (int)status);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
status = LSRegisterURL((__bridge CFURLRef)myURL, true);
|
|
||||||
if (status != noErr) {
|
|
||||||
fprintf(stderr, "Error in LSRegisterURL: %d\n", (int)status);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void Discord_Register(const char* applicationId, const char* command)
|
|
||||||
{
|
|
||||||
if (command) {
|
|
||||||
RegisterCommand(applicationId, command);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
// raii lite
|
|
||||||
@autoreleasepool {
|
|
||||||
RegisterURL(applicationId);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void Discord_RegisterSteamGame(const char* applicationId, const char* steamId)
|
|
||||||
{
|
|
||||||
char command[256];
|
|
||||||
snprintf(command, 256, "steam://rungameid/%s", steamId);
|
|
||||||
Discord_Register(applicationId, command);
|
|
||||||
}
|
|
||||||
40
3rdparty/discord-rpc/src/msg_queue.h
vendored
40
3rdparty/discord-rpc/src/msg_queue.h
vendored
@@ -1,40 +0,0 @@
|
|||||||
#pragma once
|
|
||||||
|
|
||||||
#include <atomic>
|
|
||||||
|
|
||||||
// A simple queue. No locks, but only works with a single thread as producer and a single thread as
|
|
||||||
// a consumer. Mutex up as needed.
|
|
||||||
|
|
||||||
namespace discord_rpc {
|
|
||||||
|
|
||||||
template <typename ElementType, std::size_t QueueSize>
|
|
||||||
class MsgQueue {
|
|
||||||
ElementType queue_[QueueSize];
|
|
||||||
std::atomic_uint nextAdd_{0};
|
|
||||||
std::atomic_uint nextSend_{0};
|
|
||||||
std::atomic_uint pendingSends_{0};
|
|
||||||
|
|
||||||
public:
|
|
||||||
MsgQueue() {}
|
|
||||||
|
|
||||||
ElementType* GetNextAddMessage()
|
|
||||||
{
|
|
||||||
// if we are falling behind, bail
|
|
||||||
if (pendingSends_.load() >= QueueSize) {
|
|
||||||
return nullptr;
|
|
||||||
}
|
|
||||||
auto index = (nextAdd_++) % QueueSize;
|
|
||||||
return &queue_[index];
|
|
||||||
}
|
|
||||||
void CommitAdd() { ++pendingSends_; }
|
|
||||||
|
|
||||||
bool HavePendingSends() const { return pendingSends_.load() != 0; }
|
|
||||||
ElementType* GetNextSendMessage()
|
|
||||||
{
|
|
||||||
auto index = (nextSend_++) % QueueSize;
|
|
||||||
return &queue_[index];
|
|
||||||
}
|
|
||||||
void CommitSend() { --pendingSends_; }
|
|
||||||
};
|
|
||||||
|
|
||||||
} // namespace discord_rpc
|
|
||||||
64
3rdparty/discord-rpc/src/rpc_connection.h
vendored
64
3rdparty/discord-rpc/src/rpc_connection.h
vendored
@@ -1,64 +0,0 @@
|
|||||||
#pragma once
|
|
||||||
|
|
||||||
#include "connection.h"
|
|
||||||
#include "serialization.h"
|
|
||||||
|
|
||||||
namespace discord_rpc {
|
|
||||||
|
|
||||||
// I took this from the buffer size libuv uses for named pipes; I suspect ours would usually be much
|
|
||||||
// smaller.
|
|
||||||
constexpr size_t MaxRpcFrameSize = 64 * 1024;
|
|
||||||
|
|
||||||
struct RpcConnection {
|
|
||||||
enum class ErrorCode : int {
|
|
||||||
Success = 0,
|
|
||||||
PipeClosed = 1,
|
|
||||||
ReadCorrupt = 2,
|
|
||||||
};
|
|
||||||
|
|
||||||
enum class Opcode : uint32_t {
|
|
||||||
Handshake = 0,
|
|
||||||
Frame = 1,
|
|
||||||
Close = 2,
|
|
||||||
Ping = 3,
|
|
||||||
Pong = 4,
|
|
||||||
};
|
|
||||||
|
|
||||||
struct MessageFrameHeader {
|
|
||||||
Opcode opcode;
|
|
||||||
uint32_t length;
|
|
||||||
};
|
|
||||||
|
|
||||||
struct MessageFrame : public MessageFrameHeader {
|
|
||||||
char message[MaxRpcFrameSize - sizeof(MessageFrameHeader)];
|
|
||||||
};
|
|
||||||
|
|
||||||
enum class State : uint32_t {
|
|
||||||
Disconnected,
|
|
||||||
SentHandshake,
|
|
||||||
AwaitingResponse,
|
|
||||||
Connected,
|
|
||||||
};
|
|
||||||
|
|
||||||
BaseConnection *connection { nullptr };
|
|
||||||
State state { State::Disconnected };
|
|
||||||
void (*onConnect)(JsonDocument &message) { nullptr };
|
|
||||||
void (*onDisconnect)(int errorCode, const char *message) { nullptr };
|
|
||||||
char appId[64] {};
|
|
||||||
int lastErrorCode { 0 };
|
|
||||||
char lastErrorMessage[256] {};
|
|
||||||
RpcConnection::MessageFrame sendFrame;
|
|
||||||
|
|
||||||
static RpcConnection *Create(const char *applicationId);
|
|
||||||
static void Destroy(RpcConnection *&);
|
|
||||||
|
|
||||||
inline bool IsOpen() const { return state == State::Connected; }
|
|
||||||
|
|
||||||
void Open();
|
|
||||||
void Close();
|
|
||||||
bool Write(const void *data, size_t length);
|
|
||||||
bool Read(JsonDocument &message);
|
|
||||||
};
|
|
||||||
|
|
||||||
} // namespace discord_rpc
|
|
||||||
|
|
||||||
@@ -259,7 +259,16 @@ if(APPLE)
|
|||||||
endif()
|
endif()
|
||||||
|
|
||||||
if(WIN32)
|
if(WIN32)
|
||||||
find_package(getopt-win REQUIRED)
|
find_package(getopt NAMES getopt getopt-win unofficial-getopt-win32 REQUIRED)
|
||||||
|
if(TARGET getopt::getopt)
|
||||||
|
set(GETOPT_LIBRARIES getopt::getopt)
|
||||||
|
elseif(TARGET getopt-win::getopt)
|
||||||
|
set(GETOPT_LIBRARIES getopt-win::getopt)
|
||||||
|
elseif(TARGET getopt::getopt_shared)
|
||||||
|
set(GETOPT_LIBRARIES getopt::getopt_shared)
|
||||||
|
else()
|
||||||
|
message(FATAL_ERROR "Missing getopt")
|
||||||
|
endif()
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
if(APPLE OR WIN32)
|
if(APPLE OR WIN32)
|
||||||
@@ -1494,7 +1503,7 @@ endif()
|
|||||||
|
|
||||||
if(HAVE_DISCORD_RPC)
|
if(HAVE_DISCORD_RPC)
|
||||||
add_subdirectory(3rdparty/discord-rpc)
|
add_subdirectory(3rdparty/discord-rpc)
|
||||||
target_include_directories(strawberry_lib PUBLIC 3rdparty/discord-rpc/include)
|
target_include_directories(strawberry_lib PUBLIC 3rdparty/discord-rpc)
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
if(HAVE_TRANSLATIONS)
|
if(HAVE_TRANSLATIONS)
|
||||||
@@ -1554,9 +1563,10 @@ target_link_libraries(strawberry_lib PUBLIC
|
|||||||
$<$<BOOL:${HAVE_MTP}>:PkgConfig::LIBMTP>
|
$<$<BOOL:${HAVE_MTP}>:PkgConfig::LIBMTP>
|
||||||
$<$<BOOL:${HAVE_GPOD}>:PkgConfig::LIBGPOD PkgConfig::GDK_PIXBUF>
|
$<$<BOOL:${HAVE_GPOD}>:PkgConfig::LIBGPOD PkgConfig::GDK_PIXBUF>
|
||||||
$<$<BOOL:${HAVE_QTSPARKLE}>:qtsparkle-qt${QT_VERSION_MAJOR}::qtsparkle>
|
$<$<BOOL:${HAVE_QTSPARKLE}>:qtsparkle-qt${QT_VERSION_MAJOR}::qtsparkle>
|
||||||
$<$<BOOL:${WIN32}>:dsound dwmapi getopt-win::getopt>
|
$<$<BOOL:${WIN32}>:dsound dwmapi ${GETOPT_LIBRARIES}>
|
||||||
$<$<BOOL:${MSVC}>:WindowsApp>
|
$<$<BOOL:${MSVC}>:WindowsApp>
|
||||||
KDAB::kdsingleapplication
|
KDAB::kdsingleapplication
|
||||||
|
$<$<BOOL:${HAVE_DISCORD_RPC}>:discord-rpc>
|
||||||
)
|
)
|
||||||
|
|
||||||
if(APPLE)
|
if(APPLE)
|
||||||
@@ -1575,10 +1585,6 @@ if(APPLE)
|
|||||||
endif()
|
endif()
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
if(HAVE_DISCORD_RPC)
|
|
||||||
target_link_libraries(strawberry_lib PRIVATE discord-rpc)
|
|
||||||
endif()
|
|
||||||
|
|
||||||
target_link_libraries(strawberry PUBLIC strawberry_lib)
|
target_link_libraries(strawberry PUBLIC strawberry_lib)
|
||||||
|
|
||||||
if(NOT APPLE)
|
if(NOT APPLE)
|
||||||
|
|||||||
62
Changelog
62
Changelog
@@ -2,6 +2,68 @@ Strawberry Music Player
|
|||||||
=======================
|
=======================
|
||||||
ChangeLog
|
ChangeLog
|
||||||
|
|
||||||
|
Version 1.2.13 (2025.08.31):
|
||||||
|
|
||||||
|
Bugfixes:
|
||||||
|
* Fixed playlist alternating row colors no longer working with some styles (#1806)
|
||||||
|
* Fixed "Open Audio CD" no longer working (#1803)
|
||||||
|
* Fixed systemtray icon playback status not working with scaling (#1782)
|
||||||
|
* Fixed build without MusicBrainz (#1799)
|
||||||
|
* Fixed build without MTP (#1804)
|
||||||
|
|
||||||
|
Enhancements:
|
||||||
|
* Added Discord status text option (#1796)
|
||||||
|
* Read Vorbis/FLAC "Other" embedded covers if front cover is not available (#1793)
|
||||||
|
|
||||||
|
Version 1.2.12 (2025.08.12):
|
||||||
|
|
||||||
|
Bugfixes:
|
||||||
|
* Fixed scrobbling for radio streams.
|
||||||
|
* Fixed CDDA memory leaks.
|
||||||
|
* Fixed device view CDDA loading (#1676).
|
||||||
|
* Fixed collection directory editing (#1767).
|
||||||
|
* Fixed devices sometimes being duplicated in the database.
|
||||||
|
* Fixed alternating playlist row colors with Windows 11 style.
|
||||||
|
* Fixed broken file filter for GME formats.
|
||||||
|
* Fixed collection scanning for GME formats.
|
||||||
|
* Fixed Chartlyrics.
|
||||||
|
* Fixed network cache file descriptor leak on lyrics search with workaround for QTBUG-135641.
|
||||||
|
* Fixed parsing Tidal urls with certain stream URL replies.
|
||||||
|
* Fixed pixelated window icon on Wayland (#1753).
|
||||||
|
* Fixed saving collection grouping with special characters in the name (#1758).
|
||||||
|
* Fixed Spotify token not automatically updated on renewal when playing (#1769).
|
||||||
|
* (macOS/Windows) Fixed network cache file descriptor leak with patch for QTBUG-135641.
|
||||||
|
* (Windows|MSVC) Fixed installer to not restart the computer after installing Visual C++ Redistributable.
|
||||||
|
|
||||||
|
Enhancements:
|
||||||
|
* Implemented edit tag dialog reset for year, track, disc and rating.
|
||||||
|
* Added ALAC to supported filetypes for iPods.
|
||||||
|
* Added CD-TEXT support.
|
||||||
|
* Added back Genius lyrics.
|
||||||
|
* Added support for reporting more info to ListenBrainz.
|
||||||
|
* Added support for BPM, mood and initial key tags.
|
||||||
|
* Added support for sort tags to collection, playlists and smart playlists.
|
||||||
|
|
||||||
|
Version 1.2.11 (2025.05.15):
|
||||||
|
|
||||||
|
* Fixed playlist songs sometimes not updated with new cover.
|
||||||
|
* Fixed context album cover showing even when it's disabled in the setting (#1744).
|
||||||
|
* Fixed crash when dragging songs to a closed playlist (#1741).
|
||||||
|
* Enable startup notify in desktop file.
|
||||||
|
* (Windows|MSVC) Add experimental support for native ARM64 builds.
|
||||||
|
* (Windows|MinGW) Fixed crash on exit.
|
||||||
|
|
||||||
|
Version 1.2.10 (2025.04.18):
|
||||||
|
|
||||||
|
Bugfixes:
|
||||||
|
* Fixed Discord rich presence showing bogus artist and album.
|
||||||
|
* Fixed incorrect ID3v2 comment tag.
|
||||||
|
* (macOS|Windows MSVC) Fixed stuck playback of some streams.
|
||||||
|
|
||||||
|
Enhancements:
|
||||||
|
* Removed Genius lyrics (longer working properly because of website changes).
|
||||||
|
* (macOS|Windows MSVC) Added back Spotify
|
||||||
|
|
||||||
Version 1.2.9 (2025.04.08):
|
Version 1.2.9 (2025.04.08):
|
||||||
|
|
||||||
Bugfixes:
|
Bugfixes:
|
||||||
|
|||||||
@@ -53,7 +53,7 @@ Funding developers is a way to contribute to open source projects you appreciate
|
|||||||
* Edit tags on audio files
|
* Edit tags on audio files
|
||||||
* Fetch tags from MusicBrainz
|
* Fetch tags from MusicBrainz
|
||||||
* Album cover art from [Last.fm](https://www.last.fm/), [Musicbrainz](https://musicbrainz.org/), [Discogs](https://www.discogs.com/), [Musixmatch](https://www.musixmatch.com/), [Deezer](https://www.deezer.com/), [Tidal](https://www.tidal.com/), [Qobuz](https://www.qobuz.com/) and [Spotify](https://www.spotify.com/)
|
* Album cover art from [Last.fm](https://www.last.fm/), [Musicbrainz](https://musicbrainz.org/), [Discogs](https://www.discogs.com/), [Musixmatch](https://www.musixmatch.com/), [Deezer](https://www.deezer.com/), [Tidal](https://www.tidal.com/), [Qobuz](https://www.qobuz.com/) and [Spotify](https://www.spotify.com/)
|
||||||
* Song lyrics from [Genius](https://genius.com/), [Musixmatch](https://www.musixmatch.com/), [ChartLyrics](http://www.chartlyrics.com/), [lyrics.ovh](https://lyrics.ovh/), [lololyrics.com](https://www.lololyrics.com/), [songlyrics.com](https://www.songlyrics.com/), [azlyrics.com](https://www.azlyrics.com/) and [elyrics.net](https://www.elyrics.net/)
|
* Song lyrics from [Genius](https://genius.com/), [Musixmatch](https://www.musixmatch.com/), [ChartLyrics](http://www.chartlyrics.com/), [lyrics.ovh](https://lyrics.ovh/), [lololyrics.com](https://www.lololyrics.com/), [songlyrics.com](https://www.songlyrics.com/), [azlyrics.com](https://www.azlyrics.com/), [elyrics.net](https://www.elyrics.net/), [letras.mus.br](https://www.letras.mus.br) and [LyricFind](https://lyrics.lyricfind.com)
|
||||||
* Support for multiple backends
|
* Support for multiple backends
|
||||||
* Audio analyzer
|
* Audio analyzer
|
||||||
* Audio equalizer
|
* Audio equalizer
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
set(STRAWBERRY_VERSION_MAJOR 1)
|
set(STRAWBERRY_VERSION_MAJOR 1)
|
||||||
set(STRAWBERRY_VERSION_MINOR 2)
|
set(STRAWBERRY_VERSION_MINOR 2)
|
||||||
set(STRAWBERRY_VERSION_PATCH 9)
|
set(STRAWBERRY_VERSION_PATCH 13)
|
||||||
#set(STRAWBERRY_VERSION_PRERELEASE rc1)
|
#set(STRAWBERRY_VERSION_PRERELEASE rc1)
|
||||||
|
|
||||||
set(INCLUDE_GIT_REVISION OFF)
|
set(INCLUDE_GIT_REVISION OFF)
|
||||||
|
|||||||
@@ -12,6 +12,7 @@
|
|||||||
<file>schema/schema-18.sql</file>
|
<file>schema/schema-18.sql</file>
|
||||||
<file>schema/schema-19.sql</file>
|
<file>schema/schema-19.sql</file>
|
||||||
<file>schema/schema-20.sql</file>
|
<file>schema/schema-20.sql</file>
|
||||||
|
<file>schema/schema-21.sql</file>
|
||||||
<file>schema/device-schema.sql</file>
|
<file>schema/device-schema.sql</file>
|
||||||
<file>style/strawberry.css</file>
|
<file>style/strawberry.css</file>
|
||||||
<file>style/smartplaylistsearchterm.css</file>
|
<file>style/smartplaylistsearchterm.css</file>
|
||||||
|
|||||||
@@ -12,9 +12,13 @@ CREATE TABLE device_%deviceid_subdirectories (
|
|||||||
CREATE TABLE device_%deviceid_songs (
|
CREATE TABLE device_%deviceid_songs (
|
||||||
|
|
||||||
title TEXT,
|
title TEXT,
|
||||||
|
titlesort TEXT,
|
||||||
album TEXT,
|
album TEXT,
|
||||||
|
albumsort TEXT,
|
||||||
artist TEXT,
|
artist TEXT,
|
||||||
|
artistsort TEXT,
|
||||||
albumartist TEXT,
|
albumartist TEXT,
|
||||||
|
albumartistsort TEXT,
|
||||||
track INTEGER NOT NULL DEFAULT -1,
|
track INTEGER NOT NULL DEFAULT -1,
|
||||||
disc INTEGER NOT NULL DEFAULT -1,
|
disc INTEGER NOT NULL DEFAULT -1,
|
||||||
year INTEGER NOT NULL DEFAULT -1,
|
year INTEGER NOT NULL DEFAULT -1,
|
||||||
@@ -22,7 +26,9 @@ CREATE TABLE device_%deviceid_songs (
|
|||||||
genre TEXT,
|
genre TEXT,
|
||||||
compilation INTEGER NOT NULL DEFAULT 0,
|
compilation INTEGER NOT NULL DEFAULT 0,
|
||||||
composer TEXT,
|
composer TEXT,
|
||||||
|
composersort TEXT,
|
||||||
performer TEXT,
|
performer TEXT,
|
||||||
|
performersort TEXT,
|
||||||
grouping TEXT,
|
grouping TEXT,
|
||||||
comment TEXT,
|
comment TEXT,
|
||||||
lyrics TEXT,
|
lyrics TEXT,
|
||||||
@@ -86,7 +92,11 @@ CREATE TABLE device_%deviceid_songs (
|
|||||||
musicbrainz_work_id TEXT,
|
musicbrainz_work_id TEXT,
|
||||||
|
|
||||||
ebur128_integrated_loudness_lufs REAL,
|
ebur128_integrated_loudness_lufs REAL,
|
||||||
ebur128_loudness_range_lu REAL
|
ebur128_loudness_range_lu REAL,
|
||||||
|
|
||||||
|
bpm REAL,
|
||||||
|
mood TEXT,
|
||||||
|
initial_key TEXT
|
||||||
|
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -94,4 +104,4 @@ CREATE INDEX idx_device_%deviceid_songs_album ON device_%deviceid_songs (album);
|
|||||||
|
|
||||||
CREATE INDEX idx_device_%deviceid_songs_comp_artist ON device_%deviceid_songs (compilation_effective, artist);
|
CREATE INDEX idx_device_%deviceid_songs_comp_artist ON device_%deviceid_songs (compilation_effective, artist);
|
||||||
|
|
||||||
UPDATE devices SET schema_version=5 WHERE ROWID=%deviceid;
|
UPDATE devices SET schema_version=6 WHERE ROWID=%deviceid;
|
||||||
|
|||||||
43
data/schema/schema-21.sql
Normal file
43
data/schema/schema-21.sql
Normal file
@@ -0,0 +1,43 @@
|
|||||||
|
DROP INDEX IF EXISTS idx_albumartistsort;
|
||||||
|
|
||||||
|
DROP INDEX IF EXISTS idx_albumsort;
|
||||||
|
|
||||||
|
DROP INDEX IF EXISTS idx_artistsort;
|
||||||
|
|
||||||
|
DROP INDEX IF EXISTS idx_composersort;
|
||||||
|
|
||||||
|
DROP INDEX IF EXISTS idx_performersort;
|
||||||
|
|
||||||
|
DROP INDEX IF EXISTS idx_titlesort;
|
||||||
|
|
||||||
|
ALTER TABLE %allsongstables ADD COLUMN albumartistsort TEXT;
|
||||||
|
|
||||||
|
ALTER TABLE %allsongstables ADD COLUMN albumsort TEXT;
|
||||||
|
|
||||||
|
ALTER TABLE %allsongstables ADD COLUMN artistsort TEXT;
|
||||||
|
|
||||||
|
ALTER TABLE %allsongstables ADD COLUMN composersort TEXT;
|
||||||
|
|
||||||
|
ALTER TABLE %allsongstables ADD COLUMN performersort TEXT;
|
||||||
|
|
||||||
|
ALTER TABLE %allsongstables ADD COLUMN titlesort TEXT;
|
||||||
|
|
||||||
|
ALTER TABLE %allsongstables ADD COLUMN bpm REAL;
|
||||||
|
|
||||||
|
ALTER TABLE %allsongstables ADD COLUMN mood TEXT;
|
||||||
|
|
||||||
|
ALTER TABLE %allsongstables ADD COLUMN initial_key TEXT;
|
||||||
|
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_albumartistsort ON songs (albumartistsort);
|
||||||
|
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_albumsort ON songs (album);
|
||||||
|
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_artistsort ON songs (artistsort);
|
||||||
|
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_composersort ON songs (title);
|
||||||
|
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_performersort ON songs (title);
|
||||||
|
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_titlesort ON songs (title);
|
||||||
|
|
||||||
|
UPDATE schema_version SET version=21;
|
||||||
@@ -4,7 +4,7 @@ CREATE TABLE IF NOT EXISTS schema_version (
|
|||||||
|
|
||||||
DELETE FROM schema_version;
|
DELETE FROM schema_version;
|
||||||
|
|
||||||
INSERT INTO schema_version (version) VALUES (20);
|
INSERT INTO schema_version (version) VALUES (21);
|
||||||
|
|
||||||
CREATE TABLE IF NOT EXISTS directories (
|
CREATE TABLE IF NOT EXISTS directories (
|
||||||
path TEXT NOT NULL,
|
path TEXT NOT NULL,
|
||||||
@@ -20,9 +20,13 @@ CREATE TABLE IF NOT EXISTS subdirectories (
|
|||||||
CREATE TABLE IF NOT EXISTS songs (
|
CREATE TABLE IF NOT EXISTS songs (
|
||||||
|
|
||||||
title TEXT,
|
title TEXT,
|
||||||
|
titlesort TEXT,
|
||||||
album TEXT,
|
album TEXT,
|
||||||
|
albumsort TEXT,
|
||||||
artist TEXT,
|
artist TEXT,
|
||||||
|
artistsort TEXT,
|
||||||
albumartist TEXT,
|
albumartist TEXT,
|
||||||
|
albumartistsort TEXT,
|
||||||
track INTEGER NOT NULL DEFAULT -1,
|
track INTEGER NOT NULL DEFAULT -1,
|
||||||
disc INTEGER NOT NULL DEFAULT -1,
|
disc INTEGER NOT NULL DEFAULT -1,
|
||||||
year INTEGER NOT NULL DEFAULT -1,
|
year INTEGER NOT NULL DEFAULT -1,
|
||||||
@@ -30,7 +34,9 @@ CREATE TABLE IF NOT EXISTS songs (
|
|||||||
genre TEXT,
|
genre TEXT,
|
||||||
compilation INTEGER NOT NULL DEFAULT 0,
|
compilation INTEGER NOT NULL DEFAULT 0,
|
||||||
composer TEXT,
|
composer TEXT,
|
||||||
|
composersort TEXT,
|
||||||
performer TEXT,
|
performer TEXT,
|
||||||
|
performersort TEXT,
|
||||||
grouping TEXT,
|
grouping TEXT,
|
||||||
comment TEXT,
|
comment TEXT,
|
||||||
lyrics TEXT,
|
lyrics TEXT,
|
||||||
@@ -94,16 +100,24 @@ CREATE TABLE IF NOT EXISTS songs (
|
|||||||
musicbrainz_work_id TEXT,
|
musicbrainz_work_id TEXT,
|
||||||
|
|
||||||
ebur128_integrated_loudness_lufs REAL,
|
ebur128_integrated_loudness_lufs REAL,
|
||||||
ebur128_loudness_range_lu REAL
|
ebur128_loudness_range_lu REAL,
|
||||||
|
|
||||||
|
bpm REAL,
|
||||||
|
mood TEXT,
|
||||||
|
initial_key TEXT
|
||||||
|
|
||||||
);
|
);
|
||||||
|
|
||||||
CREATE TABLE IF NOT EXISTS subsonic_songs (
|
CREATE TABLE IF NOT EXISTS subsonic_songs (
|
||||||
|
|
||||||
title TEXT,
|
title TEXT,
|
||||||
|
titlesort TEXT,
|
||||||
album TEXT,
|
album TEXT,
|
||||||
|
albumsort TEXT,
|
||||||
artist TEXT,
|
artist TEXT,
|
||||||
|
artistsort TEXT,
|
||||||
albumartist TEXT,
|
albumartist TEXT,
|
||||||
|
albumartistsort TEXT,
|
||||||
track INTEGER NOT NULL DEFAULT -1,
|
track INTEGER NOT NULL DEFAULT -1,
|
||||||
disc INTEGER NOT NULL DEFAULT -1,
|
disc INTEGER NOT NULL DEFAULT -1,
|
||||||
year INTEGER NOT NULL DEFAULT -1,
|
year INTEGER NOT NULL DEFAULT -1,
|
||||||
@@ -111,7 +125,9 @@ CREATE TABLE IF NOT EXISTS subsonic_songs (
|
|||||||
genre TEXT,
|
genre TEXT,
|
||||||
compilation INTEGER NOT NULL DEFAULT 0,
|
compilation INTEGER NOT NULL DEFAULT 0,
|
||||||
composer TEXT,
|
composer TEXT,
|
||||||
|
composersort TEXT,
|
||||||
performer TEXT,
|
performer TEXT,
|
||||||
|
performersort TEXT,
|
||||||
grouping TEXT,
|
grouping TEXT,
|
||||||
comment TEXT,
|
comment TEXT,
|
||||||
lyrics TEXT,
|
lyrics TEXT,
|
||||||
@@ -175,16 +191,24 @@ CREATE TABLE IF NOT EXISTS subsonic_songs (
|
|||||||
musicbrainz_work_id TEXT,
|
musicbrainz_work_id TEXT,
|
||||||
|
|
||||||
ebur128_integrated_loudness_lufs REAL,
|
ebur128_integrated_loudness_lufs REAL,
|
||||||
ebur128_loudness_range_lu REAL
|
ebur128_loudness_range_lu REAL,
|
||||||
|
|
||||||
|
bpm REAL,
|
||||||
|
mood TEXT,
|
||||||
|
initial_key TEXT
|
||||||
|
|
||||||
);
|
);
|
||||||
|
|
||||||
CREATE TABLE IF NOT EXISTS tidal_artists_songs (
|
CREATE TABLE IF NOT EXISTS tidal_artists_songs (
|
||||||
|
|
||||||
title TEXT,
|
title TEXT,
|
||||||
|
titlesort TEXT,
|
||||||
album TEXT,
|
album TEXT,
|
||||||
|
albumsort TEXT,
|
||||||
artist TEXT,
|
artist TEXT,
|
||||||
|
artistsort TEXT,
|
||||||
albumartist TEXT,
|
albumartist TEXT,
|
||||||
|
albumartistsort TEXT,
|
||||||
track INTEGER NOT NULL DEFAULT -1,
|
track INTEGER NOT NULL DEFAULT -1,
|
||||||
disc INTEGER NOT NULL DEFAULT -1,
|
disc INTEGER NOT NULL DEFAULT -1,
|
||||||
year INTEGER NOT NULL DEFAULT -1,
|
year INTEGER NOT NULL DEFAULT -1,
|
||||||
@@ -192,7 +216,9 @@ CREATE TABLE IF NOT EXISTS tidal_artists_songs (
|
|||||||
genre TEXT,
|
genre TEXT,
|
||||||
compilation INTEGER NOT NULL DEFAULT 0,
|
compilation INTEGER NOT NULL DEFAULT 0,
|
||||||
composer TEXT,
|
composer TEXT,
|
||||||
|
composersort TEXT,
|
||||||
performer TEXT,
|
performer TEXT,
|
||||||
|
performersort TEXT,
|
||||||
grouping TEXT,
|
grouping TEXT,
|
||||||
comment TEXT,
|
comment TEXT,
|
||||||
lyrics TEXT,
|
lyrics TEXT,
|
||||||
@@ -256,16 +282,24 @@ CREATE TABLE IF NOT EXISTS tidal_artists_songs (
|
|||||||
musicbrainz_work_id TEXT,
|
musicbrainz_work_id TEXT,
|
||||||
|
|
||||||
ebur128_integrated_loudness_lufs REAL,
|
ebur128_integrated_loudness_lufs REAL,
|
||||||
ebur128_loudness_range_lu REAL
|
ebur128_loudness_range_lu REAL,
|
||||||
|
|
||||||
|
bpm REAL,
|
||||||
|
mood TEXT,
|
||||||
|
initial_key TEXT
|
||||||
|
|
||||||
);
|
);
|
||||||
|
|
||||||
CREATE TABLE IF NOT EXISTS tidal_albums_songs (
|
CREATE TABLE IF NOT EXISTS tidal_albums_songs (
|
||||||
|
|
||||||
title TEXT,
|
title TEXT,
|
||||||
|
titlesort TEXT,
|
||||||
album TEXT,
|
album TEXT,
|
||||||
|
albumsort TEXT,
|
||||||
artist TEXT,
|
artist TEXT,
|
||||||
|
artistsort TEXT,
|
||||||
albumartist TEXT,
|
albumartist TEXT,
|
||||||
|
albumartistsort TEXT,
|
||||||
track INTEGER NOT NULL DEFAULT -1,
|
track INTEGER NOT NULL DEFAULT -1,
|
||||||
disc INTEGER NOT NULL DEFAULT -1,
|
disc INTEGER NOT NULL DEFAULT -1,
|
||||||
year INTEGER NOT NULL DEFAULT -1,
|
year INTEGER NOT NULL DEFAULT -1,
|
||||||
@@ -273,7 +307,9 @@ CREATE TABLE IF NOT EXISTS tidal_albums_songs (
|
|||||||
genre TEXT,
|
genre TEXT,
|
||||||
compilation INTEGER NOT NULL DEFAULT 0,
|
compilation INTEGER NOT NULL DEFAULT 0,
|
||||||
composer TEXT,
|
composer TEXT,
|
||||||
|
composersort TEXT,
|
||||||
performer TEXT,
|
performer TEXT,
|
||||||
|
performersort TEXT,
|
||||||
grouping TEXT,
|
grouping TEXT,
|
||||||
comment TEXT,
|
comment TEXT,
|
||||||
lyrics TEXT,
|
lyrics TEXT,
|
||||||
@@ -337,16 +373,24 @@ CREATE TABLE IF NOT EXISTS tidal_albums_songs (
|
|||||||
musicbrainz_work_id TEXT,
|
musicbrainz_work_id TEXT,
|
||||||
|
|
||||||
ebur128_integrated_loudness_lufs REAL,
|
ebur128_integrated_loudness_lufs REAL,
|
||||||
ebur128_loudness_range_lu REAL
|
ebur128_loudness_range_lu REAL,
|
||||||
|
|
||||||
|
bpm REAL,
|
||||||
|
mood TEXT,
|
||||||
|
initial_key TEXT
|
||||||
|
|
||||||
);
|
);
|
||||||
|
|
||||||
CREATE TABLE IF NOT EXISTS tidal_songs (
|
CREATE TABLE IF NOT EXISTS tidal_songs (
|
||||||
|
|
||||||
title TEXT,
|
title TEXT,
|
||||||
|
titlesort TEXT,
|
||||||
album TEXT,
|
album TEXT,
|
||||||
|
albumsort TEXT,
|
||||||
artist TEXT,
|
artist TEXT,
|
||||||
|
artistsort TEXT,
|
||||||
albumartist TEXT,
|
albumartist TEXT,
|
||||||
|
albumartistsort TEXT,
|
||||||
track INTEGER NOT NULL DEFAULT -1,
|
track INTEGER NOT NULL DEFAULT -1,
|
||||||
disc INTEGER NOT NULL DEFAULT -1,
|
disc INTEGER NOT NULL DEFAULT -1,
|
||||||
year INTEGER NOT NULL DEFAULT -1,
|
year INTEGER NOT NULL DEFAULT -1,
|
||||||
@@ -354,7 +398,9 @@ CREATE TABLE IF NOT EXISTS tidal_songs (
|
|||||||
genre TEXT,
|
genre TEXT,
|
||||||
compilation INTEGER NOT NULL DEFAULT 0,
|
compilation INTEGER NOT NULL DEFAULT 0,
|
||||||
composer TEXT,
|
composer TEXT,
|
||||||
|
composersort TEXT,
|
||||||
performer TEXT,
|
performer TEXT,
|
||||||
|
performersort TEXT,
|
||||||
grouping TEXT,
|
grouping TEXT,
|
||||||
comment TEXT,
|
comment TEXT,
|
||||||
lyrics TEXT,
|
lyrics TEXT,
|
||||||
@@ -418,16 +464,24 @@ CREATE TABLE IF NOT EXISTS tidal_songs (
|
|||||||
musicbrainz_work_id TEXT,
|
musicbrainz_work_id TEXT,
|
||||||
|
|
||||||
ebur128_integrated_loudness_lufs REAL,
|
ebur128_integrated_loudness_lufs REAL,
|
||||||
ebur128_loudness_range_lu REAL
|
ebur128_loudness_range_lu REAL,
|
||||||
|
|
||||||
|
bpm REAL,
|
||||||
|
mood TEXT,
|
||||||
|
initial_key TEXT
|
||||||
|
|
||||||
);
|
);
|
||||||
|
|
||||||
CREATE TABLE IF NOT EXISTS spotify_artists_songs (
|
CREATE TABLE IF NOT EXISTS spotify_artists_songs (
|
||||||
|
|
||||||
title TEXT,
|
title TEXT,
|
||||||
|
titlesort TEXT,
|
||||||
album TEXT,
|
album TEXT,
|
||||||
|
albumsort TEXT,
|
||||||
artist TEXT,
|
artist TEXT,
|
||||||
|
artistsort TEXT,
|
||||||
albumartist TEXT,
|
albumartist TEXT,
|
||||||
|
albumartistsort TEXT,
|
||||||
track INTEGER NOT NULL DEFAULT -1,
|
track INTEGER NOT NULL DEFAULT -1,
|
||||||
disc INTEGER NOT NULL DEFAULT -1,
|
disc INTEGER NOT NULL DEFAULT -1,
|
||||||
year INTEGER NOT NULL DEFAULT -1,
|
year INTEGER NOT NULL DEFAULT -1,
|
||||||
@@ -435,7 +489,9 @@ CREATE TABLE IF NOT EXISTS spotify_artists_songs (
|
|||||||
genre TEXT,
|
genre TEXT,
|
||||||
compilation INTEGER NOT NULL DEFAULT 0,
|
compilation INTEGER NOT NULL DEFAULT 0,
|
||||||
composer TEXT,
|
composer TEXT,
|
||||||
|
composersort TEXT,
|
||||||
performer TEXT,
|
performer TEXT,
|
||||||
|
performersort TEXT,
|
||||||
grouping TEXT,
|
grouping TEXT,
|
||||||
comment TEXT,
|
comment TEXT,
|
||||||
lyrics TEXT,
|
lyrics TEXT,
|
||||||
@@ -499,16 +555,24 @@ CREATE TABLE IF NOT EXISTS spotify_artists_songs (
|
|||||||
musicbrainz_work_id TEXT,
|
musicbrainz_work_id TEXT,
|
||||||
|
|
||||||
ebur128_integrated_loudness_lufs REAL,
|
ebur128_integrated_loudness_lufs REAL,
|
||||||
ebur128_loudness_range_lu REAL
|
ebur128_loudness_range_lu REAL,
|
||||||
|
|
||||||
|
bpm REAL,
|
||||||
|
mood TEXT,
|
||||||
|
initial_key TEXT
|
||||||
|
|
||||||
);
|
);
|
||||||
|
|
||||||
CREATE TABLE IF NOT EXISTS spotify_albums_songs (
|
CREATE TABLE IF NOT EXISTS spotify_albums_songs (
|
||||||
|
|
||||||
title TEXT,
|
title TEXT,
|
||||||
|
titlesort TEXT,
|
||||||
album TEXT,
|
album TEXT,
|
||||||
|
albumsort TEXT,
|
||||||
artist TEXT,
|
artist TEXT,
|
||||||
|
artistsort TEXT,
|
||||||
albumartist TEXT,
|
albumartist TEXT,
|
||||||
|
albumartistsort TEXT,
|
||||||
track INTEGER NOT NULL DEFAULT -1,
|
track INTEGER NOT NULL DEFAULT -1,
|
||||||
disc INTEGER NOT NULL DEFAULT -1,
|
disc INTEGER NOT NULL DEFAULT -1,
|
||||||
year INTEGER NOT NULL DEFAULT -1,
|
year INTEGER NOT NULL DEFAULT -1,
|
||||||
@@ -516,7 +580,9 @@ CREATE TABLE IF NOT EXISTS spotify_albums_songs (
|
|||||||
genre TEXT,
|
genre TEXT,
|
||||||
compilation INTEGER NOT NULL DEFAULT 0,
|
compilation INTEGER NOT NULL DEFAULT 0,
|
||||||
composer TEXT,
|
composer TEXT,
|
||||||
|
composersort TEXT,
|
||||||
performer TEXT,
|
performer TEXT,
|
||||||
|
performersort TEXT,
|
||||||
grouping TEXT,
|
grouping TEXT,
|
||||||
comment TEXT,
|
comment TEXT,
|
||||||
lyrics TEXT,
|
lyrics TEXT,
|
||||||
@@ -580,16 +646,24 @@ CREATE TABLE IF NOT EXISTS spotify_albums_songs (
|
|||||||
musicbrainz_work_id TEXT,
|
musicbrainz_work_id TEXT,
|
||||||
|
|
||||||
ebur128_integrated_loudness_lufs REAL,
|
ebur128_integrated_loudness_lufs REAL,
|
||||||
ebur128_loudness_range_lu REAL
|
ebur128_loudness_range_lu REAL,
|
||||||
|
|
||||||
|
bpm REAL,
|
||||||
|
mood TEXT,
|
||||||
|
initial_key TEXT
|
||||||
|
|
||||||
);
|
);
|
||||||
|
|
||||||
CREATE TABLE IF NOT EXISTS spotify_songs (
|
CREATE TABLE IF NOT EXISTS spotify_songs (
|
||||||
|
|
||||||
title TEXT,
|
title TEXT,
|
||||||
|
titlesort TEXT,
|
||||||
album TEXT,
|
album TEXT,
|
||||||
|
albumsort TEXT,
|
||||||
artist TEXT,
|
artist TEXT,
|
||||||
|
artistsort TEXT,
|
||||||
albumartist TEXT,
|
albumartist TEXT,
|
||||||
|
albumartistsort TEXT,
|
||||||
track INTEGER NOT NULL DEFAULT -1,
|
track INTEGER NOT NULL DEFAULT -1,
|
||||||
disc INTEGER NOT NULL DEFAULT -1,
|
disc INTEGER NOT NULL DEFAULT -1,
|
||||||
year INTEGER NOT NULL DEFAULT -1,
|
year INTEGER NOT NULL DEFAULT -1,
|
||||||
@@ -597,7 +671,9 @@ CREATE TABLE IF NOT EXISTS spotify_songs (
|
|||||||
genre TEXT,
|
genre TEXT,
|
||||||
compilation INTEGER NOT NULL DEFAULT 0,
|
compilation INTEGER NOT NULL DEFAULT 0,
|
||||||
composer TEXT,
|
composer TEXT,
|
||||||
|
composersort TEXT,
|
||||||
performer TEXT,
|
performer TEXT,
|
||||||
|
performersort TEXT,
|
||||||
grouping TEXT,
|
grouping TEXT,
|
||||||
comment TEXT,
|
comment TEXT,
|
||||||
lyrics TEXT,
|
lyrics TEXT,
|
||||||
@@ -661,16 +737,24 @@ CREATE TABLE IF NOT EXISTS spotify_songs (
|
|||||||
musicbrainz_work_id TEXT,
|
musicbrainz_work_id TEXT,
|
||||||
|
|
||||||
ebur128_integrated_loudness_lufs REAL,
|
ebur128_integrated_loudness_lufs REAL,
|
||||||
ebur128_loudness_range_lu REAL
|
ebur128_loudness_range_lu REAL,
|
||||||
|
|
||||||
|
bpm REAL,
|
||||||
|
mood TEXT,
|
||||||
|
initial_key TEXT
|
||||||
|
|
||||||
);
|
);
|
||||||
|
|
||||||
CREATE TABLE IF NOT EXISTS qobuz_artists_songs (
|
CREATE TABLE IF NOT EXISTS qobuz_artists_songs (
|
||||||
|
|
||||||
title TEXT,
|
title TEXT,
|
||||||
|
titlesort TEXT,
|
||||||
album TEXT,
|
album TEXT,
|
||||||
|
albumsort TEXT,
|
||||||
artist TEXT,
|
artist TEXT,
|
||||||
|
artistsort TEXT,
|
||||||
albumartist TEXT,
|
albumartist TEXT,
|
||||||
|
albumartistsort TEXT,
|
||||||
track INTEGER NOT NULL DEFAULT -1,
|
track INTEGER NOT NULL DEFAULT -1,
|
||||||
disc INTEGER NOT NULL DEFAULT -1,
|
disc INTEGER NOT NULL DEFAULT -1,
|
||||||
year INTEGER NOT NULL DEFAULT -1,
|
year INTEGER NOT NULL DEFAULT -1,
|
||||||
@@ -678,7 +762,9 @@ CREATE TABLE IF NOT EXISTS qobuz_artists_songs (
|
|||||||
genre TEXT,
|
genre TEXT,
|
||||||
compilation INTEGER NOT NULL DEFAULT 0,
|
compilation INTEGER NOT NULL DEFAULT 0,
|
||||||
composer TEXT,
|
composer TEXT,
|
||||||
|
composersort TEXT,
|
||||||
performer TEXT,
|
performer TEXT,
|
||||||
|
performersort TEXT,
|
||||||
grouping TEXT,
|
grouping TEXT,
|
||||||
comment TEXT,
|
comment TEXT,
|
||||||
lyrics TEXT,
|
lyrics TEXT,
|
||||||
@@ -742,16 +828,24 @@ CREATE TABLE IF NOT EXISTS qobuz_artists_songs (
|
|||||||
musicbrainz_work_id TEXT,
|
musicbrainz_work_id TEXT,
|
||||||
|
|
||||||
ebur128_integrated_loudness_lufs REAL,
|
ebur128_integrated_loudness_lufs REAL,
|
||||||
ebur128_loudness_range_lu REAL
|
ebur128_loudness_range_lu REAL,
|
||||||
|
|
||||||
|
bpm REAL,
|
||||||
|
mood TEXT,
|
||||||
|
initial_key TEXT
|
||||||
|
|
||||||
);
|
);
|
||||||
|
|
||||||
CREATE TABLE IF NOT EXISTS qobuz_albums_songs (
|
CREATE TABLE IF NOT EXISTS qobuz_albums_songs (
|
||||||
|
|
||||||
title TEXT,
|
title TEXT,
|
||||||
|
titlesort TEXT,
|
||||||
album TEXT,
|
album TEXT,
|
||||||
|
albumsort TEXT,
|
||||||
artist TEXT,
|
artist TEXT,
|
||||||
|
artistsort TEXT,
|
||||||
albumartist TEXT,
|
albumartist TEXT,
|
||||||
|
albumartistsort TEXT,
|
||||||
track INTEGER NOT NULL DEFAULT -1,
|
track INTEGER NOT NULL DEFAULT -1,
|
||||||
disc INTEGER NOT NULL DEFAULT -1,
|
disc INTEGER NOT NULL DEFAULT -1,
|
||||||
year INTEGER NOT NULL DEFAULT -1,
|
year INTEGER NOT NULL DEFAULT -1,
|
||||||
@@ -759,7 +853,9 @@ CREATE TABLE IF NOT EXISTS qobuz_albums_songs (
|
|||||||
genre TEXT,
|
genre TEXT,
|
||||||
compilation INTEGER NOT NULL DEFAULT 0,
|
compilation INTEGER NOT NULL DEFAULT 0,
|
||||||
composer TEXT,
|
composer TEXT,
|
||||||
|
composersort TEXT,
|
||||||
performer TEXT,
|
performer TEXT,
|
||||||
|
performersort TEXT,
|
||||||
grouping TEXT,
|
grouping TEXT,
|
||||||
comment TEXT,
|
comment TEXT,
|
||||||
lyrics TEXT,
|
lyrics TEXT,
|
||||||
@@ -823,16 +919,24 @@ CREATE TABLE IF NOT EXISTS qobuz_albums_songs (
|
|||||||
musicbrainz_work_id TEXT,
|
musicbrainz_work_id TEXT,
|
||||||
|
|
||||||
ebur128_integrated_loudness_lufs REAL,
|
ebur128_integrated_loudness_lufs REAL,
|
||||||
ebur128_loudness_range_lu REAL
|
ebur128_loudness_range_lu REAL,
|
||||||
|
|
||||||
|
bpm REAL,
|
||||||
|
mood TEXT,
|
||||||
|
initial_key TEXT
|
||||||
|
|
||||||
);
|
);
|
||||||
|
|
||||||
CREATE TABLE IF NOT EXISTS qobuz_songs (
|
CREATE TABLE IF NOT EXISTS qobuz_songs (
|
||||||
|
|
||||||
title TEXT,
|
title TEXT,
|
||||||
|
titlesort TEXT,
|
||||||
album TEXT,
|
album TEXT,
|
||||||
|
albumsort TEXT,
|
||||||
artist TEXT,
|
artist TEXT,
|
||||||
|
artistsort TEXT,
|
||||||
albumartist TEXT,
|
albumartist TEXT,
|
||||||
|
albumartistsort TEXT,
|
||||||
track INTEGER NOT NULL DEFAULT -1,
|
track INTEGER NOT NULL DEFAULT -1,
|
||||||
disc INTEGER NOT NULL DEFAULT -1,
|
disc INTEGER NOT NULL DEFAULT -1,
|
||||||
year INTEGER NOT NULL DEFAULT -1,
|
year INTEGER NOT NULL DEFAULT -1,
|
||||||
@@ -840,7 +944,9 @@ CREATE TABLE IF NOT EXISTS qobuz_songs (
|
|||||||
genre TEXT,
|
genre TEXT,
|
||||||
compilation INTEGER NOT NULL DEFAULT 0,
|
compilation INTEGER NOT NULL DEFAULT 0,
|
||||||
composer TEXT,
|
composer TEXT,
|
||||||
|
composersort TEXT,
|
||||||
performer TEXT,
|
performer TEXT,
|
||||||
|
performersort TEXT,
|
||||||
grouping TEXT,
|
grouping TEXT,
|
||||||
comment TEXT,
|
comment TEXT,
|
||||||
lyrics TEXT,
|
lyrics TEXT,
|
||||||
@@ -904,7 +1010,11 @@ CREATE TABLE IF NOT EXISTS qobuz_songs (
|
|||||||
musicbrainz_work_id TEXT,
|
musicbrainz_work_id TEXT,
|
||||||
|
|
||||||
ebur128_integrated_loudness_lufs REAL,
|
ebur128_integrated_loudness_lufs REAL,
|
||||||
ebur128_loudness_range_lu REAL
|
ebur128_loudness_range_lu REAL,
|
||||||
|
|
||||||
|
bpm REAL,
|
||||||
|
mood TEXT,
|
||||||
|
initial_key TEXT
|
||||||
|
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -931,9 +1041,13 @@ CREATE TABLE IF NOT EXISTS playlist_items (
|
|||||||
playlist_url TEXT,
|
playlist_url TEXT,
|
||||||
|
|
||||||
title TEXT,
|
title TEXT,
|
||||||
|
titlesort TEXT,
|
||||||
album TEXT,
|
album TEXT,
|
||||||
|
albumsort TEXT,
|
||||||
artist TEXT,
|
artist TEXT,
|
||||||
|
artistsort TEXT,
|
||||||
albumartist TEXT,
|
albumartist TEXT,
|
||||||
|
albumartistsort TEXT,
|
||||||
track INTEGER,
|
track INTEGER,
|
||||||
disc INTEGER,
|
disc INTEGER,
|
||||||
year INTEGER,
|
year INTEGER,
|
||||||
@@ -941,7 +1055,9 @@ CREATE TABLE IF NOT EXISTS playlist_items (
|
|||||||
genre TEXT,
|
genre TEXT,
|
||||||
compilation INTEGER DEFAULT 0,
|
compilation INTEGER DEFAULT 0,
|
||||||
composer TEXT,
|
composer TEXT,
|
||||||
|
composersort TEXT,
|
||||||
performer TEXT,
|
performer TEXT,
|
||||||
|
performersort TEXT,
|
||||||
grouping TEXT,
|
grouping TEXT,
|
||||||
comment TEXT,
|
comment TEXT,
|
||||||
lyrics TEXT,
|
lyrics TEXT,
|
||||||
@@ -1005,7 +1121,11 @@ CREATE TABLE IF NOT EXISTS playlist_items (
|
|||||||
musicbrainz_work_id TEXT,
|
musicbrainz_work_id TEXT,
|
||||||
|
|
||||||
ebur128_integrated_loudness_lufs REAL,
|
ebur128_integrated_loudness_lufs REAL,
|
||||||
ebur128_loudness_range_lu REAL
|
ebur128_loudness_range_lu REAL,
|
||||||
|
|
||||||
|
bpm REAL,
|
||||||
|
mood TEXT,
|
||||||
|
initial_key TEXT
|
||||||
|
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -1032,10 +1152,22 @@ CREATE INDEX IF NOT EXISTS idx_comp_artist ON songs (compilation_effective, arti
|
|||||||
|
|
||||||
CREATE INDEX IF NOT EXISTS idx_albumartist ON songs (albumartist);
|
CREATE INDEX IF NOT EXISTS idx_albumartist ON songs (albumartist);
|
||||||
|
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_albumartistsort ON songs (albumartistsort);
|
||||||
|
|
||||||
CREATE INDEX IF NOT EXISTS idx_artist ON songs (artist);
|
CREATE INDEX IF NOT EXISTS idx_artist ON songs (artist);
|
||||||
|
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_artistsort ON songs (artistsort);
|
||||||
|
|
||||||
CREATE INDEX IF NOT EXISTS idx_album ON songs (album);
|
CREATE INDEX IF NOT EXISTS idx_album ON songs (album);
|
||||||
|
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_albumsort ON songs (album);
|
||||||
|
|
||||||
CREATE INDEX IF NOT EXISTS idx_title ON songs (title);
|
CREATE INDEX IF NOT EXISTS idx_title ON songs (title);
|
||||||
|
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_titlesort ON songs (title);
|
||||||
|
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_composersort ON songs (title);
|
||||||
|
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_performersort ON songs (title);
|
||||||
|
|
||||||
CREATE VIEW IF NOT EXISTS duplicated_songs as select artist dup_artist, album dup_album, title dup_title from songs as inner_songs where artist != '' and album != '' and title != '' and unavailable = 0 group by artist, album , title having count(*) > 1;
|
CREATE VIEW IF NOT EXISTS duplicated_songs as select artist dup_artist, album dup_album, title dup_title from songs as inner_songs where artist != '' and album != '' and title != '' and unavailable = 0 group by artist, album , title having count(*) > 1;
|
||||||
|
|||||||
2
debian/control
vendored
2
debian/control
vendored
@@ -60,7 +60,7 @@ Description: music player and music collection organizer
|
|||||||
- Edit tags on audio files
|
- Edit tags on audio files
|
||||||
- Automatically retrieve tags from MusicBrainz
|
- Automatically retrieve tags from MusicBrainz
|
||||||
- Album cover art from Last.fm, Musicbrainz, Discogs, Musixmatch, Deezer, Tidal, Qobuz and Spotify
|
- Album cover art from Last.fm, Musicbrainz, Discogs, Musixmatch, Deezer, Tidal, Qobuz and Spotify
|
||||||
- Song lyrics from Genius, Musixmatch, ChartLyrics, lyrics.ovh, lololyrics.com, songlyrics.com, azlyrics.com and elyrics.net
|
- Song lyrics from Genius, Musixmatch, ChartLyrics, lyrics.ovh, lololyrics.com, songlyrics.com, azlyrics.com, elyrics.net, letras.mus.br and LyricFind
|
||||||
- Audio analyzer
|
- Audio analyzer
|
||||||
- Audio equalizer
|
- Audio equalizer
|
||||||
- Transfer music to mass-storage USB players, MTP compatible devices and iPod Nano/Classic
|
- Transfer music to mass-storage USB players, MTP compatible devices and iPod Nano/Classic
|
||||||
|
|||||||
@@ -31,7 +31,7 @@
|
|||||||
<li>Edit tags on audio files</li>
|
<li>Edit tags on audio files</li>
|
||||||
<li>Automatically retrieve tags from MusicBrainz</li>
|
<li>Automatically retrieve tags from MusicBrainz</li>
|
||||||
<li>Album cover art from Last.fm, Musicbrainz, Discogs, Musixmatch, Deezer, Tidal, Qobuz and Spotify</li>
|
<li>Album cover art from Last.fm, Musicbrainz, Discogs, Musixmatch, Deezer, Tidal, Qobuz and Spotify</li>
|
||||||
<li>Song lyrics from Genius, Musixmatch, ChartLyrics, lyrics.ovh, lololyrics.com, songlyrics.com, azlyrics.com and elyrics.net</li>
|
<li>Song lyrics from Genius, Musixmatch, ChartLyrics, lyrics.ovh, lololyrics.com, songlyrics.com, azlyrics.com, elyrics.net, letras.mus.br and LyricFind</li>
|
||||||
<li>Audio analyzer and equalizer</li>
|
<li>Audio analyzer and equalizer</li>
|
||||||
<li>Transfer music to mass-storage USB players, MTP compatible devices and iPod Nano/Classic</li>
|
<li>Transfer music to mass-storage USB players, MTP compatible devices and iPod Nano/Classic</li>
|
||||||
<li>Scrobbler with support for Last.fm, Libre.fm and ListenBrainz</li>
|
<li>Scrobbler with support for Last.fm, Libre.fm and ListenBrainz</li>
|
||||||
@@ -51,6 +51,10 @@
|
|||||||
</screenshots>
|
</screenshots>
|
||||||
<update_contact>eclipseo@fedoraproject.org</update_contact>
|
<update_contact>eclipseo@fedoraproject.org</update_contact>
|
||||||
<releases>
|
<releases>
|
||||||
|
<release version="1.2.13" date="2025-08-31"/>
|
||||||
|
<release version="1.2.12" date="2025-08-12"/>
|
||||||
|
<release version="1.2.11" date="2025-05-15"/>
|
||||||
|
<release version="1.2.10" date="2025-04-18"/>
|
||||||
<release version="1.2.9" date="2025-04-08"/>
|
<release version="1.2.9" date="2025-04-08"/>
|
||||||
<release version="1.2.8" date="2025-04-05"/>
|
<release version="1.2.8" date="2025-04-05"/>
|
||||||
<release version="1.2.7" date="2025-01-31"/>
|
<release version="1.2.7" date="2025-01-31"/>
|
||||||
|
|||||||
@@ -13,8 +13,7 @@ TryExec=strawberry
|
|||||||
Icon=strawberry
|
Icon=strawberry
|
||||||
Terminal=false
|
Terminal=false
|
||||||
Categories=AudioVideo;Player;Qt;Audio;
|
Categories=AudioVideo;Player;Qt;Audio;
|
||||||
Keywords=Audio;Player;
|
Keywords=Audio;Player;Clementine;
|
||||||
StartupNotify=false
|
|
||||||
MimeType=x-content/audio-player;application/ogg;application/x-ogg;application/x-ogm-audio;audio/flac;audio/ogg;audio/vorbis;audio/aac;audio/mp4;audio/mpeg;audio/mpegurl;audio/vnd.rn-realaudio;audio/x-flac;audio/x-oggflac;audio/x-vorbis;audio/x-vorbis+ogg;audio/x-speex;audio/x-wav;audio/x-wavpack;audio/x-ape;audio/x-mp3;audio/x-mpeg;audio/x-mpegurl;audio/x-ms-wma;audio/x-musepack;audio/x-pn-realaudio;audio/x-scpls;video/x-ms-asf;x-scheme-handler/tidal;
|
MimeType=x-content/audio-player;application/ogg;application/x-ogg;application/x-ogm-audio;audio/flac;audio/ogg;audio/vorbis;audio/aac;audio/mp4;audio/mpeg;audio/mpegurl;audio/vnd.rn-realaudio;audio/x-flac;audio/x-oggflac;audio/x-vorbis;audio/x-vorbis+ogg;audio/x-speex;audio/x-wav;audio/x-wavpack;audio/x-ape;audio/x-mp3;audio/x-mpeg;audio/x-mpegurl;audio/x-ms-wma;audio/x-musepack;audio/x-pn-realaudio;audio/x-scpls;video/x-ms-asf;x-scheme-handler/tidal;
|
||||||
StartupWMClass=strawberry
|
StartupWMClass=strawberry
|
||||||
Actions=Play-Pause;Stop;StopAfterCurrent;Previous;Next;
|
Actions=Play-Pause;Stop;StopAfterCurrent;Previous;Next;
|
||||||
|
|||||||
2
dist/unix/strawberry.1
vendored
2
dist/unix/strawberry.1
vendored
@@ -29,7 +29,7 @@ Features:
|
|||||||
.br
|
.br
|
||||||
- Album cover art from Last.fm, Musicbrainz, Discogs, Musixmatch, Deezer, Tidal, Qobuz and Spotify
|
- Album cover art from Last.fm, Musicbrainz, Discogs, Musixmatch, Deezer, Tidal, Qobuz and Spotify
|
||||||
.br
|
.br
|
||||||
- Song lyrics from Lyrics.com, Genius, Musixmatch, ChartLyrics, lyrics.ovh and lololyrics.com
|
- Song lyrics from Genius, Musixmatch, ChartLyrics, lyrics.ovh, lololyrics.com, songlyrics.com, azlyrics.com, elyrics.net, letras.mus.br and LyricFind
|
||||||
.br
|
.br
|
||||||
- Support for multiple backends
|
- Support for multiple backends
|
||||||
.br
|
.br
|
||||||
|
|||||||
2
dist/unix/strawberry.spec.in
vendored
2
dist/unix/strawberry.spec.in
vendored
@@ -93,7 +93,7 @@ Features:
|
|||||||
- Edit tags on audio files
|
- Edit tags on audio files
|
||||||
- Automatically retrieve tags from MusicBrainz
|
- Automatically retrieve tags from MusicBrainz
|
||||||
- Album cover art from Last.fm, Musicbrainz, Discogs, Musixmatch, Deezer, Tidal, Qobuz and Spotify
|
- Album cover art from Last.fm, Musicbrainz, Discogs, Musixmatch, Deezer, Tidal, Qobuz and Spotify
|
||||||
- Song lyrics from Genius, Musixmatch, ChartLyrics, lyrics.ovh, lololyrics.com, songlyrics.com, azlyrics.com and elyrics.net
|
- Song lyrics from Genius, Musixmatch, ChartLyrics, lyrics.ovh, lololyrics.com, songlyrics.com, azlyrics.com, elyrics.net, letras.mus.br and LyricFind
|
||||||
- Support for multiple backends
|
- Support for multiple backends
|
||||||
- Audio analyzer
|
- Audio analyzer
|
||||||
- Audio equalizer
|
- Audio equalizer
|
||||||
|
|||||||
95
dist/windows/strawberry.nsi.in
vendored
95
dist/windows/strawberry.nsi.in
vendored
@@ -21,6 +21,10 @@
|
|||||||
!define arch_x64
|
!define arch_x64
|
||||||
!else if "@ARCH@" == "x86_64-w64-mingw32.shared"
|
!else if "@ARCH@" == "x86_64-w64-mingw32.shared"
|
||||||
!define arch_x64
|
!define arch_x64
|
||||||
|
!else if "@ARCH@" == "arm64"
|
||||||
|
!define arch_arm64
|
||||||
|
!else
|
||||||
|
!error "Missing ARCH"
|
||||||
!endif
|
!endif
|
||||||
|
|
||||||
!ifdef arch_x86
|
!ifdef arch_x86
|
||||||
@@ -31,6 +35,10 @@
|
|||||||
!define arch "x64"
|
!define arch "x64"
|
||||||
!endif
|
!endif
|
||||||
|
|
||||||
|
!ifdef arch_arm64
|
||||||
|
!define arch "arm64"
|
||||||
|
!endif
|
||||||
|
|
||||||
|
|
||||||
!if "@CMAKE_BUILD_TYPE@" == "Release"
|
!if "@CMAKE_BUILD_TYPE@" == "Release"
|
||||||
!define release
|
!define release
|
||||||
@@ -38,6 +46,8 @@
|
|||||||
!define release
|
!define release
|
||||||
!else if "@CMAKE_BUILD_TYPE@" == "Debug"
|
!else if "@CMAKE_BUILD_TYPE@" == "Debug"
|
||||||
!define debug
|
!define debug
|
||||||
|
!else
|
||||||
|
!error "Missing CMAKE_BUILD_TYPE"
|
||||||
!endif
|
!endif
|
||||||
|
|
||||||
!ifdef release
|
!ifdef release
|
||||||
@@ -70,7 +80,7 @@
|
|||||||
!ifdef arch_x86
|
!ifdef arch_x86
|
||||||
!define PRODUCT_INSTALL_DIR "$PROGRAMFILES\Strawberry Music Player Debug"
|
!define PRODUCT_INSTALL_DIR "$PROGRAMFILES\Strawberry Music Player Debug"
|
||||||
!endif
|
!endif
|
||||||
!ifdef arch_x64
|
!ifdef arch_x64 || arch_arm64
|
||||||
!define PRODUCT_INSTALL_DIR "$PROGRAMFILES64\Strawberry Music Player Debug"
|
!define PRODUCT_INSTALL_DIR "$PROGRAMFILES64\Strawberry Music Player Debug"
|
||||||
!endif
|
!endif
|
||||||
!else
|
!else
|
||||||
@@ -80,7 +90,7 @@
|
|||||||
!ifdef arch_x86
|
!ifdef arch_x86
|
||||||
!define PRODUCT_INSTALL_DIR "$PROGRAMFILES\Strawberry Music Player"
|
!define PRODUCT_INSTALL_DIR "$PROGRAMFILES\Strawberry Music Player"
|
||||||
!endif
|
!endif
|
||||||
!ifdef arch_x64
|
!ifdef arch_x64 || arch_arm64
|
||||||
!define PRODUCT_INSTALL_DIR "$PROGRAMFILES64\Strawberry Music Player"
|
!define PRODUCT_INSTALL_DIR "$PROGRAMFILES64\Strawberry Music Player"
|
||||||
!endif
|
!endif
|
||||||
!endif
|
!endif
|
||||||
@@ -214,7 +224,7 @@ Function InstallMSVCRuntime
|
|||||||
; ${If} $R0 == ""
|
; ${If} $R0 == ""
|
||||||
SetDetailsView hide
|
SetDetailsView hide
|
||||||
; inetc::get /caption "Downloading..." "https://aka.ms/vs/17/release/${vc_redist_file}" "$TEMP\${vc_redist_file}" /end
|
; inetc::get /caption "Downloading..." "https://aka.ms/vs/17/release/${vc_redist_file}" "$TEMP\${vc_redist_file}" /end
|
||||||
ExecWait '"$TEMP\${vc_redist_file}" /install /passive'
|
ExecWait '"$TEMP\${vc_redist_file}" /install /passive /norestart'
|
||||||
Delete "$TEMP\${vc_redist_file}"
|
Delete "$TEMP\${vc_redist_file}"
|
||||||
SetDetailsView show
|
SetDetailsView show
|
||||||
; ${EndIf}
|
; ${EndIf}
|
||||||
@@ -324,7 +334,7 @@ Section "Strawberry" Strawberry
|
|||||||
File "libqtsparkle-qt6.dll"
|
File "libqtsparkle-qt6.dll"
|
||||||
File "libsoup-3.0-0.dll"
|
File "libsoup-3.0-0.dll"
|
||||||
File "libspeex-1.dll"
|
File "libspeex-1.dll"
|
||||||
File "libsqlite3.dll"
|
File "libsqlite3-0.dll"
|
||||||
File "libssp-0.dll"
|
File "libssp-0.dll"
|
||||||
File "libstdc++-6.dll"
|
File "libstdc++-6.dll"
|
||||||
File "libtag.dll"
|
File "libtag.dll"
|
||||||
@@ -367,6 +377,10 @@ Section "Strawberry" Strawberry
|
|||||||
File "libcrypto-3-x64.dll"
|
File "libcrypto-3-x64.dll"
|
||||||
File "libssl-3-x64.dll"
|
File "libssl-3-x64.dll"
|
||||||
!endif
|
!endif
|
||||||
|
!ifdef arch_arm64
|
||||||
|
File "libcrypto-3-arm64.dll"
|
||||||
|
File "libssl-3-arm64.dll"
|
||||||
|
!endif
|
||||||
|
|
||||||
File "FLAC.dll"
|
File "FLAC.dll"
|
||||||
File "brotlicommon.dll"
|
File "brotlicommon.dll"
|
||||||
@@ -381,7 +395,9 @@ Section "Strawberry" Strawberry
|
|||||||
File "glib-2.0-0.dll"
|
File "glib-2.0-0.dll"
|
||||||
File "gme.dll"
|
File "gme.dll"
|
||||||
File "gmodule-2.0-0.dll"
|
File "gmodule-2.0-0.dll"
|
||||||
|
!ifndef arch_arm64
|
||||||
File "gnutls.dll"
|
File "gnutls.dll"
|
||||||
|
!endif
|
||||||
File "gobject-2.0-0.dll"
|
File "gobject-2.0-0.dll"
|
||||||
File "gstadaptivedemux-1.0-0.dll"
|
File "gstadaptivedemux-1.0-0.dll"
|
||||||
File "gstapp-1.0-0.dll"
|
File "gstapp-1.0-0.dll"
|
||||||
@@ -402,13 +418,17 @@ Section "Strawberry" Strawberry
|
|||||||
File "gsttag-1.0-0.dll"
|
File "gsttag-1.0-0.dll"
|
||||||
File "gsturidownloader-1.0-0.dll"
|
File "gsturidownloader-1.0-0.dll"
|
||||||
File "gstvideo-1.0-0.dll"
|
File "gstvideo-1.0-0.dll"
|
||||||
|
!ifdef arch_arm64
|
||||||
File "gstwinrt-1.0-0.dll"
|
File "gstwinrt-1.0-0.dll"
|
||||||
|
!endif
|
||||||
File "harfbuzz.dll"
|
File "harfbuzz.dll"
|
||||||
File "intl-8.dll"
|
File "intl-8.dll"
|
||||||
File "jpeg62.dll"
|
File "jpeg62.dll"
|
||||||
File "kdsingleapplication-qt6.dll"
|
File "kdsingleapplication-qt6.dll"
|
||||||
File "libbs2b.dll"
|
File "libbs2b.dll"
|
||||||
|
!ifndef arch_arm64
|
||||||
File "libfaac_dll.dll"
|
File "libfaac_dll.dll"
|
||||||
|
!endif
|
||||||
File "liblzma.dll"
|
File "liblzma.dll"
|
||||||
File "libmp3lame.dll"
|
File "libmp3lame.dll"
|
||||||
File "libopenmpt.dll"
|
File "libopenmpt.dll"
|
||||||
@@ -434,8 +454,10 @@ Section "Strawberry" Strawberry
|
|||||||
File "libspeex.dll"
|
File "libspeex.dll"
|
||||||
File "pcre2-8.dll"
|
File "pcre2-8.dll"
|
||||||
File "pcre2-16.dll"
|
File "pcre2-16.dll"
|
||||||
|
!ifndef arch_arm64
|
||||||
File "twolame.dll"
|
File "twolame.dll"
|
||||||
File "zlib.dll"
|
!endif
|
||||||
|
File "zlib1.dll"
|
||||||
!endif
|
!endif
|
||||||
!ifdef debug
|
!ifdef debug
|
||||||
File "freetyped.dll"
|
File "freetyped.dll"
|
||||||
@@ -444,8 +466,10 @@ Section "Strawberry" Strawberry
|
|||||||
File "libspeexd.dll"
|
File "libspeexd.dll"
|
||||||
File "pcre2-8d.dll"
|
File "pcre2-8d.dll"
|
||||||
File "pcre2-16d.dll"
|
File "pcre2-16d.dll"
|
||||||
|
!ifndef arch_arm64
|
||||||
File "twolamed.dll"
|
File "twolamed.dll"
|
||||||
File "zlibd.dll"
|
!endif
|
||||||
|
File "zlibd1.dll"
|
||||||
!endif
|
!endif
|
||||||
|
|
||||||
; Used by libfftw3-3.dll because fftw is compiled with MinGW.
|
; Used by libfftw3-3.dll because fftw is compiled with MinGW.
|
||||||
@@ -459,7 +483,11 @@ Section "Strawberry" Strawberry
|
|||||||
; Common files
|
; Common files
|
||||||
|
|
||||||
File "icudt77.dll"
|
File "icudt77.dll"
|
||||||
|
!ifdef msvc && arch_arm64
|
||||||
|
File "fftw3.dll"
|
||||||
|
!else
|
||||||
File "libfftw3-3.dll"
|
File "libfftw3-3.dll"
|
||||||
|
!endif
|
||||||
!ifdef msvc && debug
|
!ifdef msvc && debug
|
||||||
File "icuin77d.dll"
|
File "icuin77d.dll"
|
||||||
File "icuuc77d.dll"
|
File "icuuc77d.dll"
|
||||||
@@ -526,11 +554,13 @@ Section "GIO modules" gio-modules
|
|||||||
SetOutPath "$INSTDIR\gio-modules"
|
SetOutPath "$INSTDIR\gio-modules"
|
||||||
!ifdef mingw
|
!ifdef mingw
|
||||||
File "/oname=libgiognutls.dll" "gio-modules\libgiognutls.dll"
|
File "/oname=libgiognutls.dll" "gio-modules\libgiognutls.dll"
|
||||||
File "/oname=libgioopenssl.dll" "gio-modules\libgioopenssl.dll"
|
|
||||||
!endif
|
!endif
|
||||||
!ifdef msvc
|
!ifdef msvc
|
||||||
File "/oname=giognutls.dll" "gio-modules\giognutls.dll"
|
!ifdef arch_arm64
|
||||||
File "/oname=gioopenssl.dll" "gio-modules\gioopenssl.dll"
|
File "/oname=gioopenssl.dll" "gio-modules\gioopenssl.dll"
|
||||||
|
!else
|
||||||
|
File "/oname=giognutls.dll" "gio-modules\giognutls.dll"
|
||||||
|
!endif
|
||||||
!endif
|
!endif
|
||||||
SectionEnd
|
SectionEnd
|
||||||
|
|
||||||
@@ -674,7 +704,6 @@ Section "Gstreamer plugins" gstreamer-plugins
|
|||||||
File "/oname=gstdirectsound.dll" "gstreamer-plugins\gstdirectsound.dll"
|
File "/oname=gstdirectsound.dll" "gstreamer-plugins\gstdirectsound.dll"
|
||||||
File "/oname=gstdsd.dll" "gstreamer-plugins\gstdsd.dll"
|
File "/oname=gstdsd.dll" "gstreamer-plugins\gstdsd.dll"
|
||||||
File "/oname=gstequalizer.dll" "gstreamer-plugins\gstequalizer.dll"
|
File "/oname=gstequalizer.dll" "gstreamer-plugins\gstequalizer.dll"
|
||||||
File "/oname=gstfaac.dll" "gstreamer-plugins\gstfaac.dll"
|
|
||||||
File "/oname=gstfaad.dll" "gstreamer-plugins\gstfaad.dll"
|
File "/oname=gstfaad.dll" "gstreamer-plugins\gstfaad.dll"
|
||||||
File "/oname=gstfdkaac.dll" "gstreamer-plugins\gstfdkaac.dll"
|
File "/oname=gstfdkaac.dll" "gstreamer-plugins\gstfdkaac.dll"
|
||||||
File "/oname=gstflac.dll" "gstreamer-plugins\gstflac.dll"
|
File "/oname=gstflac.dll" "gstreamer-plugins\gstflac.dll"
|
||||||
@@ -707,7 +736,6 @@ Section "Gstreamer plugins" gstreamer-plugins
|
|||||||
File "/oname=gstspeex.dll" "gstreamer-plugins\gstspeex.dll"
|
File "/oname=gstspeex.dll" "gstreamer-plugins\gstspeex.dll"
|
||||||
File "/oname=gsttaglib.dll" "gstreamer-plugins\gsttaglib.dll"
|
File "/oname=gsttaglib.dll" "gstreamer-plugins\gsttaglib.dll"
|
||||||
File "/oname=gsttcp.dll" "gstreamer-plugins\gsttcp.dll"
|
File "/oname=gsttcp.dll" "gstreamer-plugins\gsttcp.dll"
|
||||||
File "/oname=gsttwolame.dll" "gstreamer-plugins\gsttwolame.dll"
|
|
||||||
File "/oname=gsttypefindfunctions.dll" "gstreamer-plugins\gsttypefindfunctions.dll"
|
File "/oname=gsttypefindfunctions.dll" "gstreamer-plugins\gsttypefindfunctions.dll"
|
||||||
File "/oname=gstudp.dll" "gstreamer-plugins\gstudp.dll"
|
File "/oname=gstudp.dll" "gstreamer-plugins\gstudp.dll"
|
||||||
File "/oname=gstvolume.dll" "gstreamer-plugins\gstvolume.dll"
|
File "/oname=gstvolume.dll" "gstreamer-plugins\gstvolume.dll"
|
||||||
@@ -719,8 +747,12 @@ Section "Gstreamer plugins" gstreamer-plugins
|
|||||||
File "/oname=gstwavpack.dll" "gstreamer-plugins\gstwavpack.dll"
|
File "/oname=gstwavpack.dll" "gstreamer-plugins\gstwavpack.dll"
|
||||||
File "/oname=gstwavparse.dll" "gstreamer-plugins\gstwavparse.dll"
|
File "/oname=gstwavparse.dll" "gstreamer-plugins\gstwavparse.dll"
|
||||||
File "/oname=gstxingmux.dll" "gstreamer-plugins\gstxingmux.dll"
|
File "/oname=gstxingmux.dll" "gstreamer-plugins\gstxingmux.dll"
|
||||||
|
!ifndef arch_arm64
|
||||||
|
File "/oname=gstfaac.dll" "gstreamer-plugins\gstfaac.dll"
|
||||||
|
File "/oname=gsttwolame.dll" "gstreamer-plugins\gsttwolame.dll"
|
||||||
|
!endif
|
||||||
!ifdef arch_x64
|
!ifdef arch_x64
|
||||||
;File "/oname=gstspotify.dll" "gstreamer-plugins\gstspotify.dll"
|
File "/oname=gstspotify.dll" "gstreamer-plugins\gstspotify.dll"
|
||||||
!endif
|
!endif
|
||||||
!endif ; MSVC
|
!endif ; MSVC
|
||||||
|
|
||||||
@@ -849,7 +881,7 @@ Section "Uninstall"
|
|||||||
Delete "$INSTDIR\libqtsparkle-qt6.dll"
|
Delete "$INSTDIR\libqtsparkle-qt6.dll"
|
||||||
Delete "$INSTDIR\libsoup-3.0-0.dll"
|
Delete "$INSTDIR\libsoup-3.0-0.dll"
|
||||||
Delete "$INSTDIR\libspeex-1.dll"
|
Delete "$INSTDIR\libspeex-1.dll"
|
||||||
Delete "$INSTDIR\libsqlite3.dll"
|
Delete "$INSTDIR\libsqlite3-0.dll"
|
||||||
Delete "$INSTDIR\libssp-0.dll"
|
Delete "$INSTDIR\libssp-0.dll"
|
||||||
Delete "$INSTDIR\libstdc++-6.dll"
|
Delete "$INSTDIR\libstdc++-6.dll"
|
||||||
Delete "$INSTDIR\libtag.dll"
|
Delete "$INSTDIR\libtag.dll"
|
||||||
@@ -892,6 +924,10 @@ Section "Uninstall"
|
|||||||
Delete "$INSTDIR\libcrypto-3-x64.dll"
|
Delete "$INSTDIR\libcrypto-3-x64.dll"
|
||||||
Delete "$INSTDIR\libssl-3-x64.dll"
|
Delete "$INSTDIR\libssl-3-x64.dll"
|
||||||
!endif
|
!endif
|
||||||
|
!ifdef arch_arm64
|
||||||
|
Delete "$INSTDIR\libcrypto-3-arm64.dll"
|
||||||
|
Delete "$INSTDIR\libssl-3-arm64.dll"
|
||||||
|
!endif
|
||||||
|
|
||||||
Delete "$INSTDIR\FLAC.dll"
|
Delete "$INSTDIR\FLAC.dll"
|
||||||
Delete "$INSTDIR\brotlicommon.dll"
|
Delete "$INSTDIR\brotlicommon.dll"
|
||||||
@@ -906,7 +942,9 @@ Section "Uninstall"
|
|||||||
Delete "$INSTDIR\glib-2.0-0.dll"
|
Delete "$INSTDIR\glib-2.0-0.dll"
|
||||||
Delete "$INSTDIR\gme.dll"
|
Delete "$INSTDIR\gme.dll"
|
||||||
Delete "$INSTDIR\gmodule-2.0-0.dll"
|
Delete "$INSTDIR\gmodule-2.0-0.dll"
|
||||||
|
!ifndef arch_arm64
|
||||||
Delete "$INSTDIR\gnutls.dll"
|
Delete "$INSTDIR\gnutls.dll"
|
||||||
|
!endif
|
||||||
Delete "$INSTDIR\gobject-2.0-0.dll"
|
Delete "$INSTDIR\gobject-2.0-0.dll"
|
||||||
Delete "$INSTDIR\gstadaptivedemux-1.0-0.dll"
|
Delete "$INSTDIR\gstadaptivedemux-1.0-0.dll"
|
||||||
Delete "$INSTDIR\gstapp-1.0-0.dll"
|
Delete "$INSTDIR\gstapp-1.0-0.dll"
|
||||||
@@ -927,13 +965,17 @@ Section "Uninstall"
|
|||||||
Delete "$INSTDIR\gsttag-1.0-0.dll"
|
Delete "$INSTDIR\gsttag-1.0-0.dll"
|
||||||
Delete "$INSTDIR\gsturidownloader-1.0-0.dll"
|
Delete "$INSTDIR\gsturidownloader-1.0-0.dll"
|
||||||
Delete "$INSTDIR\gstvideo-1.0-0.dll"
|
Delete "$INSTDIR\gstvideo-1.0-0.dll"
|
||||||
|
!ifdef arch_arm64
|
||||||
Delete "$INSTDIR\gstwinrt-1.0-0.dll"
|
Delete "$INSTDIR\gstwinrt-1.0-0.dll"
|
||||||
|
!endif
|
||||||
Delete "$INSTDIR\harfbuzz.dll"
|
Delete "$INSTDIR\harfbuzz.dll"
|
||||||
Delete "$INSTDIR\intl-8.dll"
|
Delete "$INSTDIR\intl-8.dll"
|
||||||
Delete "$INSTDIR\jpeg62.dll"
|
Delete "$INSTDIR\jpeg62.dll"
|
||||||
Delete "$INSTDIR\kdsingleapplication-qt6.dll"
|
Delete "$INSTDIR\kdsingleapplication-qt6.dll"
|
||||||
Delete "$INSTDIR\libbs2b.dll"
|
Delete "$INSTDIR\libbs2b.dll"
|
||||||
|
!ifndef arch_arm64
|
||||||
Delete "$INSTDIR\libfaac_dll.dll"
|
Delete "$INSTDIR\libfaac_dll.dll"
|
||||||
|
!endif
|
||||||
Delete "$INSTDIR\liblzma.dll"
|
Delete "$INSTDIR\liblzma.dll"
|
||||||
Delete "$INSTDIR\libmp3lame.dll"
|
Delete "$INSTDIR\libmp3lame.dll"
|
||||||
Delete "$INSTDIR\libopenmpt.dll"
|
Delete "$INSTDIR\libopenmpt.dll"
|
||||||
@@ -959,8 +1001,10 @@ Section "Uninstall"
|
|||||||
Delete "$INSTDIR\libspeex.dll"
|
Delete "$INSTDIR\libspeex.dll"
|
||||||
Delete "$INSTDIR\pcre2-8.dll"
|
Delete "$INSTDIR\pcre2-8.dll"
|
||||||
Delete "$INSTDIR\pcre2-16.dll"
|
Delete "$INSTDIR\pcre2-16.dll"
|
||||||
|
!ifndef arch_arm64
|
||||||
Delete "$INSTDIR\twolame.dll"
|
Delete "$INSTDIR\twolame.dll"
|
||||||
Delete "$INSTDIR\zlib.dll"
|
!endif
|
||||||
|
Delete "$INSTDIR\zlib1.dll"
|
||||||
!endif
|
!endif
|
||||||
!ifdef debug
|
!ifdef debug
|
||||||
Delete "$INSTDIR\freetyped.dll"
|
Delete "$INSTDIR\freetyped.dll"
|
||||||
@@ -969,8 +1013,10 @@ Section "Uninstall"
|
|||||||
Delete "$INSTDIR\libspeexd.dll"
|
Delete "$INSTDIR\libspeexd.dll"
|
||||||
Delete "$INSTDIR\pcre2-8d.dll"
|
Delete "$INSTDIR\pcre2-8d.dll"
|
||||||
Delete "$INSTDIR\pcre2-16d.dll"
|
Delete "$INSTDIR\pcre2-16d.dll"
|
||||||
|
!ifndef arch_arm64
|
||||||
Delete "$INSTDIR\twolamed.dll"
|
Delete "$INSTDIR\twolamed.dll"
|
||||||
Delete "$INSTDIR\zlibd.dll"
|
!endif
|
||||||
|
Delete "$INSTDIR\zlibd1.dll"
|
||||||
!endif
|
!endif
|
||||||
|
|
||||||
!ifdef arch_x86
|
!ifdef arch_x86
|
||||||
@@ -983,7 +1029,11 @@ Section "Uninstall"
|
|||||||
; Common files
|
; Common files
|
||||||
|
|
||||||
Delete "$INSTDIR\icudt77.dll"
|
Delete "$INSTDIR\icudt77.dll"
|
||||||
|
!ifdef msvc && arch_arm64
|
||||||
|
Delete "$INSTDIR\fftw3.dll"
|
||||||
|
!else
|
||||||
Delete "$INSTDIR\libfftw3-3.dll"
|
Delete "$INSTDIR\libfftw3-3.dll"
|
||||||
|
!endif
|
||||||
!ifdef msvc && debug
|
!ifdef msvc && debug
|
||||||
Delete "$INSTDIR\icuin77d.dll"
|
Delete "$INSTDIR\icuin77d.dll"
|
||||||
Delete "$INSTDIR\icuuc77d.dll"
|
Delete "$INSTDIR\icuuc77d.dll"
|
||||||
@@ -1016,11 +1066,13 @@ Section "Uninstall"
|
|||||||
|
|
||||||
!ifdef mingw
|
!ifdef mingw
|
||||||
Delete "$INSTDIR\gio-modules\libgiognutls.dll"
|
Delete "$INSTDIR\gio-modules\libgiognutls.dll"
|
||||||
Delete "$INSTDIR\gio-modules\libgioopenssl.dll"
|
|
||||||
!endif
|
!endif
|
||||||
!ifdef msvc
|
!ifdef msvc
|
||||||
Delete "$INSTDIR\gio-modules\giognutls.dll"
|
!ifdef arch_arm64
|
||||||
Delete "$INSTDIR\gio-modules\gioopenssl.dll"
|
Delete "$INSTDIR\gio-modules\gioopenssl.dll"
|
||||||
|
!else
|
||||||
|
Delete "$INSTDIR\gio-modules\giognutls.dll"
|
||||||
|
!endif
|
||||||
!endif
|
!endif
|
||||||
|
|
||||||
!ifdef msvc && debug
|
!ifdef msvc && debug
|
||||||
@@ -1133,7 +1185,6 @@ Section "Uninstall"
|
|||||||
Delete "$INSTDIR\gstreamer-plugins\gstdirectsound.dll"
|
Delete "$INSTDIR\gstreamer-plugins\gstdirectsound.dll"
|
||||||
Delete "$INSTDIR\gstreamer-plugins\gstdsd.dll"
|
Delete "$INSTDIR\gstreamer-plugins\gstdsd.dll"
|
||||||
Delete "$INSTDIR\gstreamer-plugins\gstequalizer.dll"
|
Delete "$INSTDIR\gstreamer-plugins\gstequalizer.dll"
|
||||||
Delete "$INSTDIR\gstreamer-plugins\gstfaac.dll"
|
|
||||||
Delete "$INSTDIR\gstreamer-plugins\gstfaad.dll"
|
Delete "$INSTDIR\gstreamer-plugins\gstfaad.dll"
|
||||||
Delete "$INSTDIR\gstreamer-plugins\gstfdkaac.dll"
|
Delete "$INSTDIR\gstreamer-plugins\gstfdkaac.dll"
|
||||||
Delete "$INSTDIR\gstreamer-plugins\gstflac.dll"
|
Delete "$INSTDIR\gstreamer-plugins\gstflac.dll"
|
||||||
@@ -1166,7 +1217,6 @@ Section "Uninstall"
|
|||||||
Delete "$INSTDIR\gstreamer-plugins\gstspeex.dll"
|
Delete "$INSTDIR\gstreamer-plugins\gstspeex.dll"
|
||||||
Delete "$INSTDIR\gstreamer-plugins\gsttaglib.dll"
|
Delete "$INSTDIR\gstreamer-plugins\gsttaglib.dll"
|
||||||
Delete "$INSTDIR\gstreamer-plugins\gsttcp.dll"
|
Delete "$INSTDIR\gstreamer-plugins\gsttcp.dll"
|
||||||
Delete "$INSTDIR\gstreamer-plugins\gsttwolame.dll"
|
|
||||||
Delete "$INSTDIR\gstreamer-plugins\gsttypefindfunctions.dll"
|
Delete "$INSTDIR\gstreamer-plugins\gsttypefindfunctions.dll"
|
||||||
Delete "$INSTDIR\gstreamer-plugins\gstudp.dll"
|
Delete "$INSTDIR\gstreamer-plugins\gstudp.dll"
|
||||||
Delete "$INSTDIR\gstreamer-plugins\gstvolume.dll"
|
Delete "$INSTDIR\gstreamer-plugins\gstvolume.dll"
|
||||||
@@ -1178,9 +1228,14 @@ Section "Uninstall"
|
|||||||
Delete "$INSTDIR\gstreamer-plugins\gstwavpack.dll"
|
Delete "$INSTDIR\gstreamer-plugins\gstwavpack.dll"
|
||||||
Delete "$INSTDIR\gstreamer-plugins\gstwavparse.dll"
|
Delete "$INSTDIR\gstreamer-plugins\gstwavparse.dll"
|
||||||
Delete "$INSTDIR\gstreamer-plugins\gstxingmux.dll"
|
Delete "$INSTDIR\gstreamer-plugins\gstxingmux.dll"
|
||||||
!ifdef arch_x64
|
!ifndef arch_arm64
|
||||||
;Delete "$INSTDIR\gstreamer-plugins\gstspotify.dll"
|
Delete "$INSTDIR\gstreamer-plugins\gstfaac.dll"
|
||||||
|
Delete "$INSTDIR\gstreamer-plugins\gsttwolame.dll"
|
||||||
!endif
|
!endif
|
||||||
|
!ifdef arch_x64
|
||||||
|
Delete "$INSTDIR\gstreamer-plugins\gstspotify.dll"
|
||||||
|
!endif
|
||||||
|
|
||||||
!endif ; msvc
|
!endif ; msvc
|
||||||
|
|
||||||
Delete "$INSTDIR\Uninstall.exe"
|
Delete "$INSTDIR\Uninstall.exe"
|
||||||
|
|||||||
@@ -623,6 +623,7 @@ void CollectionBackend::AddOrUpdateSongs(const SongList &songs) {
|
|||||||
QMutexLocker l(db_->Mutex());
|
QMutexLocker l(db_->Mutex());
|
||||||
QSqlDatabase db(db_->Connect());
|
QSqlDatabase db(db_->Connect());
|
||||||
|
|
||||||
|
CollectionTask task(task_manager_, tr("Updating %1 database.").arg(Song::TextForSource(source_)));
|
||||||
ScopedTransaction transaction(&db);
|
ScopedTransaction transaction(&db);
|
||||||
|
|
||||||
SongList added_songs;
|
SongList added_songs;
|
||||||
|
|||||||
@@ -34,6 +34,7 @@
|
|||||||
#include <QVariant>
|
#include <QVariant>
|
||||||
#include <QString>
|
#include <QString>
|
||||||
#include <QStringList>
|
#include <QStringList>
|
||||||
|
#include <QUrl>
|
||||||
#include <QRegularExpression>
|
#include <QRegularExpression>
|
||||||
#include <QInputDialog>
|
#include <QInputDialog>
|
||||||
#include <QList>
|
#include <QList>
|
||||||
@@ -295,19 +296,21 @@ QActionGroup *CollectionFilterWidget::CreateGroupByActions(const QString &saved_
|
|||||||
if (version == 1) {
|
if (version == 1) {
|
||||||
QStringList saved = s.childKeys();
|
QStringList saved = s.childKeys();
|
||||||
for (int i = 0; i < saved.size(); ++i) {
|
for (int i = 0; i < saved.size(); ++i) {
|
||||||
if (saved.at(i) == "version"_L1) continue;
|
const QString &name = saved.at(i);
|
||||||
QByteArray bytes = s.value(saved.at(i)).toByteArray();
|
if (name == "version"_L1) continue;
|
||||||
|
QByteArray bytes = s.value(name).toByteArray();
|
||||||
QDataStream ds(&bytes, QIODevice::ReadOnly);
|
QDataStream ds(&bytes, QIODevice::ReadOnly);
|
||||||
CollectionModel::Grouping g;
|
CollectionModel::Grouping g;
|
||||||
ds >> g;
|
ds >> g;
|
||||||
ret->addAction(CreateGroupByAction(saved.at(i), parent, g));
|
ret->addAction(CreateGroupByAction(QUrl::fromPercentEncoding(name.toUtf8()), parent, g));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
QStringList saved = s.childKeys();
|
QStringList saved = s.childKeys();
|
||||||
for (int i = 0; i < saved.size(); ++i) {
|
for (int i = 0; i < saved.size(); ++i) {
|
||||||
if (saved.at(i) == "version"_L1) continue;
|
const QString &name = saved.at(i);
|
||||||
s.remove(saved.at(i));
|
if (name == "version"_L1) continue;
|
||||||
|
s.remove(name);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
s.endGroup();
|
s.endGroup();
|
||||||
@@ -339,7 +342,7 @@ void CollectionFilterWidget::SaveGroupBy() {
|
|||||||
|
|
||||||
if (!model_) return;
|
if (!model_) return;
|
||||||
|
|
||||||
QString name = QInputDialog::getText(this, tr("Grouping Name"), tr("Grouping name:"));
|
const QString name = QInputDialog::getText(this, tr("Grouping Name"), tr("Grouping name:"));
|
||||||
if (name.isEmpty()) return;
|
if (name.isEmpty()) return;
|
||||||
|
|
||||||
qLog(Debug) << "Saving current grouping to" << name;
|
qLog(Debug) << "Saving current grouping to" << name;
|
||||||
@@ -355,7 +358,7 @@ void CollectionFilterWidget::SaveGroupBy() {
|
|||||||
QDataStream datastream(&buffer, QIODevice::WriteOnly);
|
QDataStream datastream(&buffer, QIODevice::WriteOnly);
|
||||||
datastream << model_->GetGroupBy();
|
datastream << model_->GetGroupBy();
|
||||||
s.setValue("version", u"1"_s);
|
s.setValue("version", u"1"_s);
|
||||||
s.setValue(name, buffer);
|
s.setValue(QUrl::toPercentEncoding(name), buffer);
|
||||||
s.endGroup();
|
s.endGroup();
|
||||||
|
|
||||||
UpdateGroupByActions();
|
UpdateGroupByActions();
|
||||||
|
|||||||
@@ -78,6 +78,8 @@ CollectionLibrary::CollectionLibrary(const SharedPtr<Database> database,
|
|||||||
|
|
||||||
model_ = new CollectionModel(backend_, albumcover_loader, this);
|
model_ = new CollectionModel(backend_, albumcover_loader, this);
|
||||||
|
|
||||||
|
full_rescan_revisions_[21] = tr("Support for sort tags artist, album, album artist, title, composer, and performer");
|
||||||
|
|
||||||
ReloadSettings();
|
ReloadSettings();
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
* Strawberry Music Player
|
* Strawberry Music Player
|
||||||
* Copyright 2018-2024, Jonas Kvinge <jonas@jkvinge.net>
|
* Copyright 2018-2025, Jonas Kvinge <jonas@jkvinge.net>
|
||||||
*
|
*
|
||||||
* Strawberry is free software: you can redistribute it and/or modify
|
* Strawberry is free software: you can redistribute it and/or modify
|
||||||
* it under the terms of the GNU General Public License as published by
|
* it under the terms of the GNU General Public License as published by
|
||||||
@@ -54,6 +54,7 @@
|
|||||||
|
|
||||||
#include "includes/scoped_ptr.h"
|
#include "includes/scoped_ptr.h"
|
||||||
#include "includes/shared_ptr.h"
|
#include "includes/shared_ptr.h"
|
||||||
|
#include "constants/collectionsettings.h"
|
||||||
#include "core/logging.h"
|
#include "core/logging.h"
|
||||||
#include "core/standardpaths.h"
|
#include "core/standardpaths.h"
|
||||||
#include "core/database.h"
|
#include "core/database.h"
|
||||||
@@ -71,12 +72,12 @@
|
|||||||
#include "covermanager/albumcoverloaderoptions.h"
|
#include "covermanager/albumcoverloaderoptions.h"
|
||||||
#include "covermanager/albumcoverloaderresult.h"
|
#include "covermanager/albumcoverloaderresult.h"
|
||||||
#include "covermanager/albumcoverloader.h"
|
#include "covermanager/albumcoverloader.h"
|
||||||
#include "constants/collectionsettings.h"
|
|
||||||
|
|
||||||
using namespace std::chrono_literals;
|
using namespace std::chrono_literals;
|
||||||
using namespace Qt::Literals::StringLiterals;
|
using namespace Qt::Literals::StringLiterals;
|
||||||
|
|
||||||
const int CollectionModel::kPrettyCoverSize = 32;
|
const int CollectionModel::kPrettyCoverSize = 32;
|
||||||
|
|
||||||
namespace {
|
namespace {
|
||||||
constexpr char kPixmapDiskCacheDir[] = "pixmapcache";
|
constexpr char kPixmapDiskCacheDir[] = "pixmapcache";
|
||||||
constexpr char kVariousArtists[] = QT_TR_NOOP("Various artists");
|
constexpr char kVariousArtists[] = QT_TR_NOOP("Various artists");
|
||||||
@@ -88,7 +89,6 @@ CollectionModel::CollectionModel(const SharedPtr<CollectionBackend> backend, con
|
|||||||
albumcover_loader_(albumcover_loader),
|
albumcover_loader_(albumcover_loader),
|
||||||
dir_model_(new CollectionDirectoryModel(backend, this)),
|
dir_model_(new CollectionDirectoryModel(backend, this)),
|
||||||
filter_(new CollectionFilter(this)),
|
filter_(new CollectionFilter(this)),
|
||||||
timer_reload_(new QTimer(this)),
|
|
||||||
timer_update_(new QTimer(this)),
|
timer_update_(new QTimer(this)),
|
||||||
icon_artist_(IconLoader::Load(u"folder-sound"_s)),
|
icon_artist_(IconLoader::Load(u"folder-sound"_s)),
|
||||||
use_disk_cache_(false),
|
use_disk_cache_(false),
|
||||||
@@ -130,10 +130,6 @@ CollectionModel::CollectionModel(const SharedPtr<CollectionBackend> backend, con
|
|||||||
backend_->UpdateTotalArtistCountAsync();
|
backend_->UpdateTotalArtistCountAsync();
|
||||||
backend_->UpdateTotalAlbumCountAsync();
|
backend_->UpdateTotalAlbumCountAsync();
|
||||||
|
|
||||||
timer_reload_->setSingleShot(true);
|
|
||||||
timer_reload_->setInterval(300ms);
|
|
||||||
QObject::connect(timer_reload_, &QTimer::timeout, this, &CollectionModel::Reload);
|
|
||||||
|
|
||||||
timer_update_->setSingleShot(false);
|
timer_update_->setSingleShot(false);
|
||||||
timer_update_->setInterval(20ms);
|
timer_update_->setInterval(20ms);
|
||||||
QObject::connect(timer_update_, &QTimer::timeout, this, &CollectionModel::ProcessUpdate);
|
QObject::connect(timer_update_, &QTimer::timeout, this, &CollectionModel::ProcessUpdate);
|
||||||
@@ -191,13 +187,9 @@ void CollectionModel::EndReset() {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void CollectionModel::Reload() {
|
void CollectionModel::ResetInternal() {
|
||||||
|
|
||||||
loading_ = true;
|
loading_ = true;
|
||||||
if (timer_reload_->isActive()) {
|
|
||||||
timer_reload_->stop();
|
|
||||||
}
|
|
||||||
updates_.clear();
|
|
||||||
|
|
||||||
options_active_ = options_current_;
|
options_active_ = options_current_;
|
||||||
|
|
||||||
@@ -211,22 +203,15 @@ void CollectionModel::Reload() {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void CollectionModel::ScheduleReset() {
|
|
||||||
|
|
||||||
if (!timer_reload_->isActive()) {
|
|
||||||
timer_reload_->start();
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
void CollectionModel::ReloadSettings() {
|
void CollectionModel::ReloadSettings() {
|
||||||
|
|
||||||
Settings settings;
|
Settings settings;
|
||||||
settings.beginGroup(CollectionSettings::kSettingsGroup);
|
settings.beginGroup(CollectionSettings::kSettingsGroup);
|
||||||
const bool show_pretty_covers = settings.value(CollectionSettings::kPrettyCovers, true).toBool();
|
const bool show_pretty_covers = settings.value(CollectionSettings::kPrettyCovers, true).toBool();
|
||||||
const bool show_dividers= settings.value(CollectionSettings::kShowDividers, true).toBool();
|
const bool show_dividers = settings.value(CollectionSettings::kShowDividers, true).toBool();
|
||||||
const bool show_various_artists = settings.value(CollectionSettings::kVariousArtists, true).toBool();
|
const bool show_various_artists = settings.value(CollectionSettings::kVariousArtists, true).toBool();
|
||||||
const bool sort_skips_articles = settings.value(CollectionSettings::kSortSkipsArticles, true).toBool();
|
const bool sort_skip_articles_for_artists = settings.value(CollectionSettings::kSkipArticlesForArtists, true).toBool();
|
||||||
|
const bool sort_skip_articles_for_albums = settings.value(CollectionSettings::kSkipArticlesForAlbums, false).toBool();
|
||||||
|
|
||||||
use_disk_cache_ = settings.value(CollectionSettings::kSettingsDiskCacheEnable, false).toBool();
|
use_disk_cache_ = settings.value(CollectionSettings::kSettingsDiskCacheEnable, false).toBool();
|
||||||
QPixmapCache::setCacheLimit(static_cast<int>(MaximumCacheSize(&settings, CollectionSettings::kSettingsCacheSize, CollectionSettings::kSettingsCacheSizeUnit, CollectionSettings::kSettingsCacheSizeDefault) / 1024));
|
QPixmapCache::setCacheLimit(static_cast<int>(MaximumCacheSize(&settings, CollectionSettings::kSettingsCacheSize, CollectionSettings::kSettingsCacheSizeUnit, CollectionSettings::kSettingsCacheSizeDefault) / 1024));
|
||||||
@@ -241,11 +226,13 @@ void CollectionModel::ReloadSettings() {
|
|||||||
if (show_pretty_covers != options_current_.show_pretty_covers ||
|
if (show_pretty_covers != options_current_.show_pretty_covers ||
|
||||||
show_dividers != options_current_.show_dividers ||
|
show_dividers != options_current_.show_dividers ||
|
||||||
show_various_artists != options_current_.show_various_artists ||
|
show_various_artists != options_current_.show_various_artists ||
|
||||||
sort_skips_articles != options_current_.sort_skips_articles) {
|
sort_skip_articles_for_artists != options_current_.sort_skip_articles_for_artists ||
|
||||||
|
sort_skip_articles_for_albums != options_current_.sort_skip_articles_for_albums) {
|
||||||
options_current_.show_pretty_covers = show_pretty_covers;
|
options_current_.show_pretty_covers = show_pretty_covers;
|
||||||
options_current_.show_dividers = show_dividers;
|
options_current_.show_dividers = show_dividers;
|
||||||
options_current_.show_various_artists = show_various_artists;
|
options_current_.show_various_artists = show_various_artists;
|
||||||
options_current_.sort_skips_articles = sort_skips_articles;
|
options_current_.sort_skip_articles_for_artists = sort_skip_articles_for_artists;
|
||||||
|
options_current_.sort_skip_articles_for_albums = sort_skip_articles_for_albums;
|
||||||
ScheduleReset();
|
ScheduleReset();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -421,10 +408,15 @@ void CollectionModel::RemoveSongs(const SongList &songs) {
|
|||||||
|
|
||||||
void CollectionModel::ScheduleUpdate(const CollectionModelUpdate::Type type, const SongList &songs) {
|
void CollectionModel::ScheduleUpdate(const CollectionModelUpdate::Type type, const SongList &songs) {
|
||||||
|
|
||||||
for (qint64 i = 0; i < songs.count(); i += 400LL) {
|
if (type == CollectionModelUpdate::Type::Reset) {
|
||||||
const qint64 number = std::min(songs.count() - i, 400LL);
|
updates_.enqueue(CollectionModelUpdate(type));
|
||||||
const SongList songs_to_queue = songs.mid(i, number);
|
}
|
||||||
updates_.enqueue(CollectionModelUpdate(type, songs_to_queue));
|
else {
|
||||||
|
for (qint64 i = 0; i < songs.count(); i += 400LL) {
|
||||||
|
const qint64 number = std::min(songs.count() - i, 400LL);
|
||||||
|
const SongList songs_to_queue = songs.mid(i, number);
|
||||||
|
updates_.enqueue(CollectionModelUpdate(type, songs_to_queue));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!timer_update_->isActive()) {
|
if (!timer_update_->isActive()) {
|
||||||
@@ -433,6 +425,12 @@ void CollectionModel::ScheduleUpdate(const CollectionModelUpdate::Type type, con
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void CollectionModel::ScheduleReset() {
|
||||||
|
|
||||||
|
ScheduleUpdate(CollectionModelUpdate::Type::Reset);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
void CollectionModel::ScheduleAddSongs(const SongList &songs) {
|
void CollectionModel::ScheduleAddSongs(const SongList &songs) {
|
||||||
|
|
||||||
ScheduleUpdate(CollectionModelUpdate::Type::Add, songs);
|
ScheduleUpdate(CollectionModelUpdate::Type::Add, songs);
|
||||||
@@ -465,6 +463,9 @@ void CollectionModel::ProcessUpdate() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
switch (update.type) {
|
switch (update.type) {
|
||||||
|
case CollectionModelUpdate::Type::Reset:
|
||||||
|
ResetInternal();
|
||||||
|
break;
|
||||||
case CollectionModelUpdate::Type::AddReAddOrUpdate:
|
case CollectionModelUpdate::Type::AddReAddOrUpdate:
|
||||||
AddReAddOrUpdateSongsInternal(update.songs);
|
AddReAddOrUpdateSongsInternal(update.songs);
|
||||||
break;
|
break;
|
||||||
@@ -699,7 +700,7 @@ CollectionItem *CollectionModel::CreateContainerItem(const GroupBy group_by, con
|
|||||||
|
|
||||||
QString divider_key;
|
QString divider_key;
|
||||||
if (options_active_.show_dividers && container_level == 0) {
|
if (options_active_.show_dividers && container_level == 0) {
|
||||||
divider_key = DividerKey(group_by, song, SortText(group_by, song, options_active_.sort_skips_articles));
|
divider_key = DividerKey(group_by, song, SortText(group_by, song, options_active_.sort_skip_articles_for_artists, options_active_.sort_skip_articles_for_albums));
|
||||||
if (!divider_key.isEmpty()) {
|
if (!divider_key.isEmpty()) {
|
||||||
if (!divider_nodes_.contains(divider_key)) {
|
if (!divider_nodes_.contains(divider_key)) {
|
||||||
CreateDividerItem(divider_key, DividerDisplayText(group_by, divider_key), parent);
|
CreateDividerItem(divider_key, DividerDisplayText(group_by, divider_key), parent);
|
||||||
@@ -713,7 +714,7 @@ CollectionItem *CollectionModel::CreateContainerItem(const GroupBy group_by, con
|
|||||||
item->container_level = container_level;
|
item->container_level = container_level;
|
||||||
item->container_key = container_key;
|
item->container_key = container_key;
|
||||||
item->display_text = DisplayText(group_by, song);
|
item->display_text = DisplayText(group_by, song);
|
||||||
item->sort_text = SortText(group_by, song, options_active_.sort_skips_articles);
|
item->sort_text = SortText(group_by, song, options_active_.sort_skip_articles_for_artists, options_active_.sort_skip_articles_for_albums);
|
||||||
if (!divider_key.isEmpty()) {
|
if (!divider_key.isEmpty()) {
|
||||||
item->sort_text.prepend(divider_key + QLatin1Char(' '));
|
item->sort_text.prepend(divider_key + QLatin1Char(' '));
|
||||||
}
|
}
|
||||||
@@ -1068,25 +1069,25 @@ QString CollectionModel::PrettyFormat(const Song &song) {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
QString CollectionModel::SortText(const GroupBy group_by, const Song &song, const bool sort_skips_articles) {
|
QString CollectionModel::SortText(const GroupBy group_by, const Song &song, const bool sort_skip_articles_for_artists, const bool sort_skip_articles_for_albums) {
|
||||||
|
|
||||||
switch (group_by) {
|
switch (group_by) {
|
||||||
case GroupBy::AlbumArtist:
|
case GroupBy::AlbumArtist:
|
||||||
return SortTextForArtist(song.effective_albumartist(), sort_skips_articles);
|
return SortTextForName(song.effective_albumartistsort(), sort_skip_articles_for_artists);
|
||||||
case GroupBy::Artist:
|
case GroupBy::Artist:
|
||||||
return SortTextForArtist(song.artist(), sort_skips_articles);
|
return SortTextForName(song.effective_artistsort(), sort_skip_articles_for_artists);
|
||||||
case GroupBy::Album:
|
case GroupBy::Album:
|
||||||
return SortText(song.album());
|
return SortTextForName(song.effective_albumsort(), sort_skip_articles_for_albums);
|
||||||
case GroupBy::AlbumDisc:
|
case GroupBy::AlbumDisc:
|
||||||
return song.album() + SortTextForNumber(std::max(0, song.disc()));
|
return SortTextForName(song.effective_albumsort(), sort_skip_articles_for_albums) + SortTextForNumber(std::max(0, song.disc()));
|
||||||
case GroupBy::YearAlbum:
|
case GroupBy::YearAlbum:
|
||||||
return SortTextForNumber(std::max(0, song.year())) + song.grouping() + song.album();
|
return SortTextForNumber(std::max(0, song.year())) + song.grouping() + SortTextForName(song.effective_albumsort(), sort_skip_articles_for_albums);
|
||||||
case GroupBy::YearAlbumDisc:
|
case GroupBy::YearAlbumDisc:
|
||||||
return SortTextForNumber(std::max(0, song.year())) + song.album() + SortTextForNumber(std::max(0, song.disc()));
|
return SortTextForNumber(std::max(0, song.year())) + SortTextForName(song.effective_albumsort(), sort_skip_articles_for_albums) + SortTextForNumber(std::max(0, song.disc()));
|
||||||
case GroupBy::OriginalYearAlbum:
|
case GroupBy::OriginalYearAlbum:
|
||||||
return SortTextForNumber(std::max(0, song.effective_originalyear())) + song.grouping() + song.album();
|
return SortTextForNumber(std::max(0, song.effective_originalyear())) + song.grouping() + SortTextForName(song.effective_albumsort(), sort_skip_articles_for_albums);
|
||||||
case GroupBy::OriginalYearAlbumDisc:
|
case GroupBy::OriginalYearAlbumDisc:
|
||||||
return SortTextForNumber(std::max(0, song.effective_originalyear())) + song.album() + SortTextForNumber(std::max(0, song.disc()));
|
return SortTextForNumber(std::max(0, song.effective_originalyear())) + SortTextForName(song.effective_albumsort(), sort_skip_articles_for_albums) + SortTextForNumber(std::max(0, song.disc()));
|
||||||
case GroupBy::Disc:
|
case GroupBy::Disc:
|
||||||
return SortTextForNumber(std::max(0, song.disc()));
|
return SortTextForNumber(std::max(0, song.disc()));
|
||||||
case GroupBy::Year:
|
case GroupBy::Year:
|
||||||
@@ -1094,13 +1095,13 @@ QString CollectionModel::SortText(const GroupBy group_by, const Song &song, cons
|
|||||||
case GroupBy::OriginalYear:
|
case GroupBy::OriginalYear:
|
||||||
return SortTextForNumber(std::max(0, song.effective_originalyear())) + QLatin1Char(' ');
|
return SortTextForNumber(std::max(0, song.effective_originalyear())) + QLatin1Char(' ');
|
||||||
case GroupBy::Genre:
|
case GroupBy::Genre:
|
||||||
return SortTextForArtist(song.genre(), sort_skips_articles);
|
return SortText(song.genre());
|
||||||
case GroupBy::Composer:
|
case GroupBy::Composer:
|
||||||
return SortTextForArtist(song.composer(), sort_skips_articles);
|
return SortTextForName(song.effective_composersort(), sort_skip_articles_for_artists);
|
||||||
case GroupBy::Performer:
|
case GroupBy::Performer:
|
||||||
return SortTextForArtist(song.performer(), sort_skips_articles);
|
return SortTextForName(song.effective_performersort(), sort_skip_articles_for_artists);
|
||||||
case GroupBy::Grouping:
|
case GroupBy::Grouping:
|
||||||
return SortTextForArtist(song.grouping(), sort_skips_articles);
|
return SortText(song.grouping());
|
||||||
case GroupBy::FileType:
|
case GroupBy::FileType:
|
||||||
return song.TextForFiletype();
|
return song.TextForFiletype();
|
||||||
case GroupBy::Format:
|
case GroupBy::Format:
|
||||||
@@ -1135,21 +1136,9 @@ QString CollectionModel::SortText(QString text) {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
QString CollectionModel::SortTextForArtist(QString artist, const bool skip_articles) {
|
QString CollectionModel::SortTextForName(const QString &name, const bool sort_skip_articles) {
|
||||||
|
|
||||||
artist = SortText(artist);
|
return sort_skip_articles ? SkipArticles(SortText(name)) : SortText(name);
|
||||||
|
|
||||||
if (skip_articles) {
|
|
||||||
for (const auto &i : Song::kArticles) {
|
|
||||||
if (artist.startsWith(i)) {
|
|
||||||
qint64 ilen = i.length();
|
|
||||||
artist = artist.right(artist.length() - ilen) + ", "_L1 + i.left(ilen - 1);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return artist;
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1180,6 +1169,20 @@ QString CollectionModel::SortTextForBitrate(const int bitrate) {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
QString CollectionModel::SkipArticles(QString name) {
|
||||||
|
|
||||||
|
for (const auto &i : Song::kArticles) {
|
||||||
|
if (name.startsWith(i)) {
|
||||||
|
qint64 ilen = i.length();
|
||||||
|
name = name.right(name.length() - ilen) + ", "_L1 + i.left(ilen - 1);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return name;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
bool CollectionModel::IsSongTitleDataChanged(const Song &song1, const Song &song2) {
|
bool CollectionModel::IsSongTitleDataChanged(const Song &song1, const Song &song2) {
|
||||||
|
|
||||||
return song1.url() != song2.url() ||
|
return song1.url() != song2.url() ||
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
* Strawberry Music Player
|
* Strawberry Music Player
|
||||||
* Copyright 2018-2024, Jonas Kvinge <jonas@jkvinge.net>
|
* Copyright 2018-2025, Jonas Kvinge <jonas@jkvinge.net>
|
||||||
*
|
*
|
||||||
* Strawberry is free software: you can redistribute it and/or modify
|
* Strawberry is free software: you can redistribute it and/or modify
|
||||||
* it under the terms of the GNU General Public License as published by
|
* it under the terms of the GNU General Public License as published by
|
||||||
@@ -129,14 +129,16 @@ class CollectionModel : public SimpleTreeModel<CollectionItem> {
|
|||||||
show_dividers(true),
|
show_dividers(true),
|
||||||
show_pretty_covers(true),
|
show_pretty_covers(true),
|
||||||
show_various_artists(true),
|
show_various_artists(true),
|
||||||
sort_skips_articles(true),
|
sort_skip_articles_for_artists(false),
|
||||||
|
sort_skip_articles_for_albums(false),
|
||||||
separate_albums_by_grouping(false) {}
|
separate_albums_by_grouping(false) {}
|
||||||
|
|
||||||
Grouping group_by;
|
Grouping group_by;
|
||||||
bool show_dividers;
|
bool show_dividers;
|
||||||
bool show_pretty_covers;
|
bool show_pretty_covers;
|
||||||
bool show_various_artists;
|
bool show_various_artists;
|
||||||
bool sort_skips_articles;
|
bool sort_skip_articles_for_artists;
|
||||||
|
bool sort_skip_articles_for_albums;
|
||||||
bool separate_albums_by_grouping;
|
bool separate_albums_by_grouping;
|
||||||
CollectionFilterOptions filter_options;
|
CollectionFilterOptions filter_options;
|
||||||
};
|
};
|
||||||
@@ -176,20 +178,21 @@ class CollectionModel : public SimpleTreeModel<CollectionItem> {
|
|||||||
QMimeData *mimeData(const QModelIndexList &indexes) const override;
|
QMimeData *mimeData(const QModelIndexList &indexes) const override;
|
||||||
|
|
||||||
// Utility functions for manipulating text
|
// Utility functions for manipulating text
|
||||||
static QString DisplayText(const GroupBy group_by, const Song &song);
|
QString DisplayText(const GroupBy group_by, const Song &song);
|
||||||
static QString TextOrUnknown(const QString &text);
|
static QString TextOrUnknown(const QString &text);
|
||||||
static QString PrettyYearAlbum(const int year, const QString &album);
|
static QString PrettyYearAlbum(const int year, const QString &album);
|
||||||
static QString PrettyAlbumDisc(const QString &album, const int disc);
|
static QString PrettyAlbumDisc(const QString &album, const int disc);
|
||||||
static QString PrettyYearAlbumDisc(const int year, const QString &album, const int disc);
|
static QString PrettyYearAlbumDisc(const int year, const QString &album, const int disc);
|
||||||
static QString PrettyDisc(const int disc);
|
static QString PrettyDisc(const int disc);
|
||||||
static QString PrettyFormat(const Song &song);
|
static QString PrettyFormat(const Song &song);
|
||||||
QString SortText(const GroupBy group_by, const Song &song, const bool sort_skips_articles);
|
QString SortText(const GroupBy group_by, const Song &song, const bool sort_skip_articles_for_artists, const bool sort_skip_articles_for_albums);
|
||||||
static QString SortText(QString text);
|
static QString SortText(QString text);
|
||||||
|
static QString SortTextForName(const QString &name, const bool sort_skip_articles);
|
||||||
static QString SortTextForNumber(const int number);
|
static QString SortTextForNumber(const int number);
|
||||||
static QString SortTextForArtist(QString artist, const bool skip_articles);
|
|
||||||
static QString SortTextForSong(const Song &song);
|
static QString SortTextForSong(const Song &song);
|
||||||
static QString SortTextForYear(const int year);
|
static QString SortTextForYear(const int year);
|
||||||
static QString SortTextForBitrate(const int bitrate);
|
static QString SortTextForBitrate(const int bitrate);
|
||||||
|
static QString SkipArticles(QString name);
|
||||||
static bool IsSongTitleDataChanged(const Song &song1, const Song &song2);
|
static bool IsSongTitleDataChanged(const Song &song1, const Song &song2);
|
||||||
QString ContainerKey(const GroupBy group_by, const Song &song, bool &has_unique_album_identifier) const;
|
QString ContainerKey(const GroupBy group_by, const Song &song, bool &has_unique_album_identifier) const;
|
||||||
|
|
||||||
@@ -228,7 +231,7 @@ class CollectionModel : public SimpleTreeModel<CollectionItem> {
|
|||||||
|
|
||||||
QVariant data(CollectionItem *item, const int role) const;
|
QVariant data(CollectionItem *item, const int role) const;
|
||||||
|
|
||||||
void ScheduleUpdate(const CollectionModelUpdate::Type type, const SongList &songs);
|
void ScheduleUpdate(const CollectionModelUpdate::Type type, const SongList &songs = SongList());
|
||||||
void ScheduleAddSongs(const SongList &songs);
|
void ScheduleAddSongs(const SongList &songs);
|
||||||
void ScheduleUpdateSongs(const SongList &songs);
|
void ScheduleUpdateSongs(const SongList &songs);
|
||||||
void ScheduleRemoveSongs(const SongList &songs);
|
void ScheduleRemoveSongs(const SongList &songs);
|
||||||
@@ -259,7 +262,7 @@ class CollectionModel : public SimpleTreeModel<CollectionItem> {
|
|||||||
static qint64 MaximumCacheSize(Settings *s, const char *size_id, const char *size_unit_id, const qint64 cache_size_default);
|
static qint64 MaximumCacheSize(Settings *s, const char *size_id, const char *size_unit_id, const qint64 cache_size_default);
|
||||||
|
|
||||||
private Q_SLOTS:
|
private Q_SLOTS:
|
||||||
void Reload();
|
void ResetInternal();
|
||||||
void ScheduleReset();
|
void ScheduleReset();
|
||||||
void ProcessUpdate();
|
void ProcessUpdate();
|
||||||
void LoadSongsFromSqlAsyncFinished();
|
void LoadSongsFromSqlAsyncFinished();
|
||||||
@@ -278,7 +281,6 @@ class CollectionModel : public SimpleTreeModel<CollectionItem> {
|
|||||||
const SharedPtr<AlbumCoverLoader> albumcover_loader_;
|
const SharedPtr<AlbumCoverLoader> albumcover_loader_;
|
||||||
CollectionDirectoryModel *dir_model_;
|
CollectionDirectoryModel *dir_model_;
|
||||||
CollectionFilter *filter_;
|
CollectionFilter *filter_;
|
||||||
QTimer *timer_reload_;
|
|
||||||
QTimer *timer_update_;
|
QTimer *timer_update_;
|
||||||
|
|
||||||
QPixmap pixmap_no_cover_;
|
QPixmap pixmap_no_cover_;
|
||||||
|
|||||||
@@ -25,12 +25,13 @@
|
|||||||
class CollectionModelUpdate {
|
class CollectionModelUpdate {
|
||||||
public:
|
public:
|
||||||
enum class Type {
|
enum class Type {
|
||||||
|
Reset,
|
||||||
AddReAddOrUpdate,
|
AddReAddOrUpdate,
|
||||||
Add,
|
Add,
|
||||||
Update,
|
Update,
|
||||||
Remove,
|
Remove,
|
||||||
};
|
};
|
||||||
explicit CollectionModelUpdate(const Type _type, const SongList &_songs);
|
explicit CollectionModelUpdate(const Type _type, const SongList &_songs = SongList());
|
||||||
Type type;
|
Type type;
|
||||||
SongList songs;
|
SongList songs;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -34,8 +34,6 @@ CollectionPlaylistItem::CollectionPlaylistItem(const Song::Source source) : Play
|
|||||||
|
|
||||||
CollectionPlaylistItem::CollectionPlaylistItem(const Song &song) : PlaylistItem(song.source()), song_(song) {}
|
CollectionPlaylistItem::CollectionPlaylistItem(const Song &song) : PlaylistItem(song.source()), song_(song) {}
|
||||||
|
|
||||||
QUrl CollectionPlaylistItem::Url() const { return song_.url(); }
|
|
||||||
|
|
||||||
bool CollectionPlaylistItem::InitFromQuery(const SqlRow &query) {
|
bool CollectionPlaylistItem::InitFromQuery(const SqlRow &query) {
|
||||||
|
|
||||||
int col = 0;
|
int col = 0;
|
||||||
@@ -62,7 +60,7 @@ void CollectionPlaylistItem::Reload() {
|
|||||||
qLog(Error) << "Could not reload file" << song_.url() << result.error_string();
|
qLog(Error) << "Could not reload file" << song_.url() << result.error_string();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
UpdateTemporaryMetadata(song_);
|
UpdateStreamMetadata(song_);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -78,16 +76,9 @@ QVariant CollectionPlaylistItem::DatabaseValue(const DatabaseColumn database_col
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Song CollectionPlaylistItem::Metadata() const {
|
|
||||||
|
|
||||||
if (HasTemporaryMetadata()) return temp_metadata_;
|
|
||||||
return song_;
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
void CollectionPlaylistItem::SetArtManual(const QUrl &cover_url) {
|
void CollectionPlaylistItem::SetArtManual(const QUrl &cover_url) {
|
||||||
|
|
||||||
song_.set_art_manual(cover_url);
|
song_.set_art_manual(cover_url);
|
||||||
if (HasTemporaryMetadata()) temp_metadata_.set_art_manual(cover_url);
|
if (HasStreamMetadata()) stream_song_.set_art_manual(cover_url);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -35,19 +35,17 @@ class CollectionPlaylistItem : public PlaylistItem {
|
|||||||
explicit CollectionPlaylistItem(const Song::Source source);
|
explicit CollectionPlaylistItem(const Song::Source source);
|
||||||
explicit CollectionPlaylistItem(const Song &song);
|
explicit CollectionPlaylistItem(const Song &song);
|
||||||
|
|
||||||
QUrl Url() const override;
|
Song OriginalMetadata() const override { return song_; }
|
||||||
|
void SetOriginalMetadata(const Song &song) override { song_ = song; }
|
||||||
|
|
||||||
|
QUrl OriginalUrl() const override { return song_.url(); }
|
||||||
|
bool IsLocalCollectionItem() const override { return song_.source() == Song::Source::Collection; }
|
||||||
|
|
||||||
bool InitFromQuery(const SqlRow &query) override;
|
bool InitFromQuery(const SqlRow &query) override;
|
||||||
void Reload() override;
|
void Reload() override;
|
||||||
|
|
||||||
Song Metadata() const override;
|
|
||||||
Song OriginalMetadata() const override { return song_; }
|
|
||||||
void SetMetadata(const Song &song) override { song_ = song; }
|
|
||||||
|
|
||||||
void SetArtManual(const QUrl &cover_url) override;
|
void SetArtManual(const QUrl &cover_url) override;
|
||||||
|
|
||||||
bool IsLocalCollectionItem() const override { return song_.source() == Song::Source::Collection; }
|
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
QVariant DatabaseValue(const DatabaseColumn database_column) const override;
|
QVariant DatabaseValue(const DatabaseColumn database_column) const override;
|
||||||
Song DatabaseSongMetadata() const override { return Song(source_); }
|
Song DatabaseSongMetadata() const override { return Song(source_); }
|
||||||
|
|||||||
@@ -65,10 +65,8 @@
|
|||||||
#include "collectionitem.h"
|
#include "collectionitem.h"
|
||||||
#include "collectionitemdelegate.h"
|
#include "collectionitemdelegate.h"
|
||||||
#include "collectionview.h"
|
#include "collectionview.h"
|
||||||
#ifndef Q_OS_WIN32
|
#include "device/devicemanager.h"
|
||||||
# include "device/devicemanager.h"
|
#include "device/devicestatefiltermodel.h"
|
||||||
# include "device/devicestatefiltermodel.h"
|
|
||||||
#endif
|
|
||||||
#include "dialogs/edittagdialog.h"
|
#include "dialogs/edittagdialog.h"
|
||||||
#include "dialogs/deleteconfirmationdialog.h"
|
#include "dialogs/deleteconfirmationdialog.h"
|
||||||
#include "organize/organizedialog.h"
|
#include "organize/organizedialog.h"
|
||||||
@@ -95,9 +93,7 @@ CollectionView::CollectionView(QWidget *parent)
|
|||||||
action_open_in_new_playlist_(nullptr),
|
action_open_in_new_playlist_(nullptr),
|
||||||
action_organize_(nullptr),
|
action_organize_(nullptr),
|
||||||
action_search_for_this_(nullptr),
|
action_search_for_this_(nullptr),
|
||||||
#ifndef Q_OS_WIN32
|
|
||||||
action_copy_to_device_(nullptr),
|
action_copy_to_device_(nullptr),
|
||||||
#endif
|
|
||||||
action_edit_track_(nullptr),
|
action_edit_track_(nullptr),
|
||||||
action_edit_tracks_(nullptr),
|
action_edit_tracks_(nullptr),
|
||||||
action_rescan_songs_(nullptr),
|
action_rescan_songs_(nullptr),
|
||||||
@@ -417,9 +413,7 @@ void CollectionView::contextMenuEvent(QContextMenuEvent *e) {
|
|||||||
|
|
||||||
context_menu_->addSeparator();
|
context_menu_->addSeparator();
|
||||||
action_organize_ = context_menu_->addAction(IconLoader::Load(u"edit-copy"_s), tr("Organize files..."), this, &CollectionView::Organize);
|
action_organize_ = context_menu_->addAction(IconLoader::Load(u"edit-copy"_s), tr("Organize files..."), this, &CollectionView::Organize);
|
||||||
#ifndef Q_OS_WIN32
|
|
||||||
action_copy_to_device_ = context_menu_->addAction(IconLoader::Load(u"device"_s), tr("Copy to device..."), this, &CollectionView::CopyToDevice);
|
action_copy_to_device_ = context_menu_->addAction(IconLoader::Load(u"device"_s), tr("Copy to device..."), this, &CollectionView::CopyToDevice);
|
||||||
#endif
|
|
||||||
action_delete_files_ = context_menu_->addAction(IconLoader::Load(u"edit-delete"_s), tr("Delete from disk..."), this, &CollectionView::Delete);
|
action_delete_files_ = context_menu_->addAction(IconLoader::Load(u"edit-delete"_s), tr("Delete from disk..."), this, &CollectionView::Delete);
|
||||||
|
|
||||||
context_menu_->addSeparator();
|
context_menu_->addSeparator();
|
||||||
@@ -439,10 +433,8 @@ void CollectionView::contextMenuEvent(QContextMenuEvent *e) {
|
|||||||
|
|
||||||
context_menu_->addMenu(filter_widget_->menu());
|
context_menu_->addMenu(filter_widget_->menu());
|
||||||
|
|
||||||
#ifndef Q_OS_WIN32
|
|
||||||
action_copy_to_device_->setDisabled(device_manager_->connected_devices_model()->rowCount() == 0);
|
action_copy_to_device_->setDisabled(device_manager_->connected_devices_model()->rowCount() == 0);
|
||||||
QObject::connect(device_manager_->connected_devices_model(), &DeviceStateFilterModel::IsEmptyChanged, action_copy_to_device_, &QAction::setDisabled);
|
QObject::connect(device_manager_->connected_devices_model(), &DeviceStateFilterModel::IsEmptyChanged, action_copy_to_device_, &QAction::setDisabled);
|
||||||
#endif
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -481,9 +473,7 @@ void CollectionView::contextMenuEvent(QContextMenuEvent *e) {
|
|||||||
action_rescan_songs_->setEnabled(regular_editable > 0);
|
action_rescan_songs_->setEnabled(regular_editable > 0);
|
||||||
|
|
||||||
action_organize_->setVisible(regular_elements == regular_editable);
|
action_organize_->setVisible(regular_elements == regular_editable);
|
||||||
#ifndef Q_OS_WIN32
|
|
||||||
action_copy_to_device_->setVisible(regular_elements == regular_editable);
|
action_copy_to_device_->setVisible(regular_elements == regular_editable);
|
||||||
#endif
|
|
||||||
|
|
||||||
action_delete_files_->setVisible(delete_files_);
|
action_delete_files_->setVisible(delete_files_);
|
||||||
|
|
||||||
@@ -492,9 +482,7 @@ void CollectionView::contextMenuEvent(QContextMenuEvent *e) {
|
|||||||
|
|
||||||
// only when all selected items are editable
|
// only when all selected items are editable
|
||||||
action_organize_->setEnabled(regular_elements == regular_editable);
|
action_organize_->setEnabled(regular_elements == regular_editable);
|
||||||
#ifndef Q_OS_WIN32
|
|
||||||
action_copy_to_device_->setEnabled(regular_elements == regular_editable);
|
action_copy_to_device_->setEnabled(regular_elements == regular_editable);
|
||||||
#endif
|
|
||||||
|
|
||||||
action_delete_files_->setEnabled(delete_files_);
|
action_delete_files_->setEnabled(delete_files_);
|
||||||
|
|
||||||
@@ -759,7 +747,6 @@ void CollectionView::RescanSongs() {
|
|||||||
|
|
||||||
void CollectionView::CopyToDevice() {
|
void CollectionView::CopyToDevice() {
|
||||||
|
|
||||||
#ifndef Q_OS_WIN32
|
|
||||||
if (!organize_dialog_) {
|
if (!organize_dialog_) {
|
||||||
organize_dialog_ = make_unique<OrganizeDialog>(task_manager_, tagreader_client_, nullptr, this);
|
organize_dialog_ = make_unique<OrganizeDialog>(task_manager_, tagreader_client_, nullptr, this);
|
||||||
}
|
}
|
||||||
@@ -768,7 +755,6 @@ void CollectionView::CopyToDevice() {
|
|||||||
organize_dialog_->SetCopy(true);
|
organize_dialog_->SetCopy(true);
|
||||||
organize_dialog_->SetSongs(GetSelectedSongs());
|
organize_dialog_->SetSongs(GetSelectedSongs());
|
||||||
organize_dialog_->show();
|
organize_dialog_->show();
|
||||||
#endif
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -138,7 +138,6 @@ class CollectionView : public AutoExpandingTreeView {
|
|||||||
void DeleteFilesFinished(const SongList &songs_with_errors);
|
void DeleteFilesFinished(const SongList &songs_with_errors);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void RecheckIsEmpty();
|
|
||||||
void SetShowInVarious(const bool on);
|
void SetShowInVarious(const bool on);
|
||||||
bool RestoreLevelFocus(const QModelIndex &parent = QModelIndex());
|
bool RestoreLevelFocus(const QModelIndex &parent = QModelIndex());
|
||||||
void SaveContainerPath(const QModelIndex &child);
|
void SaveContainerPath(const QModelIndex &child);
|
||||||
@@ -176,9 +175,7 @@ class CollectionView : public AutoExpandingTreeView {
|
|||||||
QAction *action_organize_;
|
QAction *action_organize_;
|
||||||
QAction *action_search_for_this_;
|
QAction *action_search_for_this_;
|
||||||
|
|
||||||
#ifndef Q_OS_WIN32
|
|
||||||
QAction *action_copy_to_device_;
|
QAction *action_copy_to_device_;
|
||||||
#endif
|
|
||||||
QAction *action_edit_track_;
|
QAction *action_edit_track_;
|
||||||
QAction *action_edit_tracks_;
|
QAction *action_edit_tracks_;
|
||||||
QAction *action_rescan_songs_;
|
QAction *action_rescan_songs_;
|
||||||
|
|||||||
@@ -999,6 +999,18 @@ void CollectionWatcher::AddChangedSong(const QString &file, const Song &matching
|
|||||||
changes << u"file path"_s;
|
changes << u"file path"_s;
|
||||||
notify_new = true;
|
notify_new = true;
|
||||||
}
|
}
|
||||||
|
if (matching_song.filetype() != new_song.filetype()) {
|
||||||
|
changes << u"filetype"_s;
|
||||||
|
notify_new = true;
|
||||||
|
}
|
||||||
|
if (matching_song.filesize() != new_song.filesize()) {
|
||||||
|
changes << u"filesize"_s;
|
||||||
|
notify_new = true;
|
||||||
|
}
|
||||||
|
if (matching_song.length_nanosec() != new_song.length_nanosec()) {
|
||||||
|
changes << u"length"_s;
|
||||||
|
notify_new = true;
|
||||||
|
}
|
||||||
if (matching_song.fingerprint() != new_song.fingerprint()) {
|
if (matching_song.fingerprint() != new_song.fingerprint()) {
|
||||||
changes << u"fingerprint"_s;
|
changes << u"fingerprint"_s;
|
||||||
notify_new = true;
|
notify_new = true;
|
||||||
@@ -1034,6 +1046,9 @@ void CollectionWatcher::AddChangedSong(const QString &file, const Song &matching
|
|||||||
if (matching_song.mtime() != new_song.mtime()) {
|
if (matching_song.mtime() != new_song.mtime()) {
|
||||||
changes << u"mtime"_s;
|
changes << u"mtime"_s;
|
||||||
}
|
}
|
||||||
|
if (matching_song.ctime() != new_song.ctime()) {
|
||||||
|
changes << u"ctime"_s;
|
||||||
|
}
|
||||||
|
|
||||||
if (changes.isEmpty()) {
|
if (changes.isEmpty()) {
|
||||||
qLog(Debug) << "Song" << file << "unchanged.";
|
qLog(Debug) << "Song" << file << "unchanged.";
|
||||||
|
|||||||
@@ -30,6 +30,7 @@
|
|||||||
#include <QByteArray>
|
#include <QByteArray>
|
||||||
#include <QString>
|
#include <QString>
|
||||||
#include <QStringList>
|
#include <QStringList>
|
||||||
|
#include <QUrl>
|
||||||
#include <QIODevice>
|
#include <QIODevice>
|
||||||
#include <QDataStream>
|
#include <QDataStream>
|
||||||
#include <QKeySequence>
|
#include <QKeySequence>
|
||||||
@@ -167,14 +168,20 @@ void SavedGroupingManager::UpdateModel() {
|
|||||||
if (version == 1) {
|
if (version == 1) {
|
||||||
QStringList saved = s.childKeys();
|
QStringList saved = s.childKeys();
|
||||||
for (int i = 0; i < saved.size(); ++i) {
|
for (int i = 0; i < saved.size(); ++i) {
|
||||||
if (saved.at(i) == "version"_L1) continue;
|
const QString &name = saved.at(i);
|
||||||
QByteArray bytes = s.value(saved.at(i)).toByteArray();
|
if (name == "version"_L1) continue;
|
||||||
|
QByteArray bytes = s.value(name).toByteArray();
|
||||||
QDataStream ds(&bytes, QIODevice::ReadOnly);
|
QDataStream ds(&bytes, QIODevice::ReadOnly);
|
||||||
CollectionModel::Grouping g;
|
CollectionModel::Grouping g;
|
||||||
ds >> g;
|
ds >> g;
|
||||||
|
|
||||||
QList<QStandardItem*> list;
|
QList<QStandardItem*> list;
|
||||||
list << new QStandardItem(saved.at(i))
|
|
||||||
|
QStandardItem *item = new QStandardItem();
|
||||||
|
item->setText(QUrl::fromPercentEncoding(name.toUtf8()));
|
||||||
|
item->setData(name);
|
||||||
|
|
||||||
|
list << item
|
||||||
<< new QStandardItem(GroupByToString(g.first))
|
<< new QStandardItem(GroupByToString(g.first))
|
||||||
<< new QStandardItem(GroupByToString(g.second))
|
<< new QStandardItem(GroupByToString(g.second))
|
||||||
<< new QStandardItem(GroupByToString(g.third));
|
<< new QStandardItem(GroupByToString(g.third));
|
||||||
@@ -185,8 +192,9 @@ void SavedGroupingManager::UpdateModel() {
|
|||||||
else {
|
else {
|
||||||
QStringList saved = s.childKeys();
|
QStringList saved = s.childKeys();
|
||||||
for (int i = 0; i < saved.size(); ++i) {
|
for (int i = 0; i < saved.size(); ++i) {
|
||||||
if (saved.at(i) == "version"_L1) continue;
|
const QString &name = saved.at(i);
|
||||||
s.remove(saved.at(i));
|
if (name == "version"_L1) continue;
|
||||||
|
s.remove(name);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
s.endGroup();
|
s.endGroup();
|
||||||
@@ -202,7 +210,7 @@ void SavedGroupingManager::Remove() {
|
|||||||
for (const QModelIndex &idx : indexes) {
|
for (const QModelIndex &idx : indexes) {
|
||||||
if (idx.isValid()) {
|
if (idx.isValid()) {
|
||||||
qLog(Debug) << "Remove saved grouping: " << model_->item(idx.row(), 0)->text();
|
qLog(Debug) << "Remove saved grouping: " << model_->item(idx.row(), 0)->text();
|
||||||
s.remove(model_->item(idx.row(), 0)->text());
|
s.remove(model_->item(idx.row(), 0)->data().toString());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
s.endGroup();
|
s.endGroup();
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
* Strawberry Music Player
|
* Strawberry Music Player
|
||||||
* Copyright 2024, Jonas Kvinge <jonas@jkvinge.net>
|
* Copyright 2024-2025, Jonas Kvinge <jonas@jkvinge.net>
|
||||||
*
|
*
|
||||||
* Strawberry is free software: you can redistribute it and/or modify
|
* Strawberry is free software: you can redistribute it and/or modify
|
||||||
* it under the terms of the GNU General Public License as published by
|
* it under the terms of the GNU General Public License as published by
|
||||||
@@ -24,18 +24,20 @@ namespace CollectionSettings {
|
|||||||
|
|
||||||
constexpr char kSettingsGroup[] = "Collection";
|
constexpr char kSettingsGroup[] = "Collection";
|
||||||
|
|
||||||
|
constexpr char kStartupScan[] = "startup_scan";
|
||||||
|
constexpr char kMonitor[] = "monitor";
|
||||||
|
constexpr char kSongTracking[] = "song_tracking";
|
||||||
|
constexpr char kMarkSongsUnavailable[] = "mark_songs_unavailable";
|
||||||
|
constexpr char kSongENUR128LoudnessAnalysis[] = "song_ebur128_loudness_analysis";
|
||||||
|
constexpr char kExpireUnavailableSongs[] = "expire_unavailable_songs";
|
||||||
|
constexpr char kCoverArtPatterns[] = "cover_art_patterns";
|
||||||
constexpr char kAutoOpen[] = "auto_open";
|
constexpr char kAutoOpen[] = "auto_open";
|
||||||
constexpr char kShowDividers[] = "show_dividers";
|
constexpr char kShowDividers[] = "show_dividers";
|
||||||
constexpr char kPrettyCovers[] = "pretty_covers";
|
constexpr char kPrettyCovers[] = "pretty_covers";
|
||||||
constexpr char kVariousArtists[] = "various_artists";
|
constexpr char kVariousArtists[] = "various_artists";
|
||||||
constexpr char kSortSkipsArticles[] = "sort_skips_articles";
|
constexpr char kSkipArticlesForArtists[] = "skip_articles_for_artists";
|
||||||
constexpr char kStartupScan[] = "startup_scan";
|
constexpr char kSkipArticlesForAlbums[] = "skip_articles_for_albums";
|
||||||
constexpr char kMonitor[] = "monitor";
|
constexpr char kShowSortText[] = "show_sort_text";
|
||||||
constexpr char kSongTracking[] = "song_tracking";
|
|
||||||
constexpr char kSongENUR128LoudnessAnalysis[] = "song_ebur128_loudness_analysis";
|
|
||||||
constexpr char kMarkSongsUnavailable[] = "mark_songs_unavailable";
|
|
||||||
constexpr char kExpireUnavailableSongs[] = "expire_unavailable_songs";
|
|
||||||
constexpr char kCoverArtPatterns[] = "cover_art_patterns";
|
|
||||||
constexpr char kSettingsCacheSize[] = "cache_size";
|
constexpr char kSettingsCacheSize[] = "cache_size";
|
||||||
constexpr char kSettingsCacheSizeUnit[] = "cache_size_unit";
|
constexpr char kSettingsCacheSizeUnit[] = "cache_size_unit";
|
||||||
constexpr char kSettingsDiskCacheEnable[] = "disk_cache_enable";
|
constexpr char kSettingsDiskCacheEnable[] = "disk_cache_enable";
|
||||||
|
|||||||
@@ -30,7 +30,7 @@ constexpr char kFileFilter[] =
|
|||||||
"*.aif *.aiff *.mka *.tta *.dsf *.dsd "
|
"*.aif *.aiff *.mka *.tta *.dsf *.dsd "
|
||||||
"*.cue *.m3u *.m3u8 *.pls *.xspf *.asxini "
|
"*.cue *.m3u *.m3u8 *.pls *.xspf *.asxini "
|
||||||
"*.ac3 *.dts "
|
"*.ac3 *.dts "
|
||||||
"*.mod *.s3m *.xm *.it"
|
"*.mod *.s3m *.xm *.it "
|
||||||
"*.spc *.vgm";
|
"*.spc *.vgm";
|
||||||
|
|
||||||
constexpr char kLoadImageFileFilter[] = QT_TRANSLATE_NOOP("FileFilter", "Images (*.png *.jpg *.jpeg *.bmp *.gif *.xpm *.pbm *.pgm *.ppm *.xbm)");
|
constexpr char kLoadImageFileFilter[] = QT_TRANSLATE_NOOP("FileFilter", "Images (*.png *.jpg *.jpeg *.bmp *.gif *.xpm *.pbm *.pgm *.ppm *.xbm)");
|
||||||
|
|||||||
@@ -71,6 +71,14 @@ constexpr char kSettingsGroup[] = "DiscordRPC";
|
|||||||
|
|
||||||
constexpr char kEnabled[] = "enabled";
|
constexpr char kEnabled[] = "enabled";
|
||||||
|
|
||||||
|
constexpr char kStatusDisplayType[] = "StatusDisplayType";
|
||||||
|
|
||||||
|
enum class StatusDisplayType {
|
||||||
|
App = 0,
|
||||||
|
Artist,
|
||||||
|
Song
|
||||||
|
};
|
||||||
|
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
#endif // NOTIFICATIONSSETTINGS_H
|
#endif // NOTIFICATIONSSETTINGS_H
|
||||||
|
|||||||
@@ -396,7 +396,7 @@ void ContextView::UpdateNoSong() {
|
|||||||
|
|
||||||
void ContextView::NoSong() {
|
void ContextView::NoSong() {
|
||||||
|
|
||||||
if (!widget_album_->isVisible()) {
|
if (!widget_album_->isVisibleTo(this)) {
|
||||||
widget_album_->show();
|
widget_album_->show();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -440,11 +440,11 @@ void ContextView::SetSong() {
|
|||||||
label_stop_summary_->clear();
|
label_stop_summary_->clear();
|
||||||
|
|
||||||
bool widget_album_changed = !song_prev_.is_valid();
|
bool widget_album_changed = !song_prev_.is_valid();
|
||||||
if (action_show_album_->isChecked() && !widget_album_->isVisible()) {
|
if (action_show_album_->isChecked() && !widget_album_->isVisibleTo(this)) {
|
||||||
widget_album_->show();
|
widget_album_->show();
|
||||||
widget_album_changed = true;
|
widget_album_changed = true;
|
||||||
}
|
}
|
||||||
else if (!action_show_album_->isChecked() && widget_album_->isVisible()) {
|
else if (!action_show_album_->isChecked() && widget_album_->isVisibleTo(this)) {
|
||||||
widget_album_->hide();
|
widget_album_->hide();
|
||||||
widget_album_changed = true;
|
widget_album_changed = true;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -50,7 +50,7 @@
|
|||||||
|
|
||||||
using namespace Qt::Literals::StringLiterals;
|
using namespace Qt::Literals::StringLiterals;
|
||||||
|
|
||||||
const int Database::kSchemaVersion = 20;
|
const int Database::kSchemaVersion = 21;
|
||||||
|
|
||||||
namespace {
|
namespace {
|
||||||
constexpr char kDatabaseFilename[] = "strawberry.db";
|
constexpr char kDatabaseFilename[] = "strawberry.db";
|
||||||
@@ -414,11 +414,6 @@ void Database::ExecSongTablesCommands(QSqlDatabase &db, const QStringList &song_
|
|||||||
// We allow a magic value in the schema files to update all songs tables at once.
|
// We allow a magic value in the schema files to update all songs tables at once.
|
||||||
if (command.contains(QLatin1String(kMagicAllSongsTables))) {
|
if (command.contains(QLatin1String(kMagicAllSongsTables))) {
|
||||||
for (const QString &table : song_tables) {
|
for (const QString &table : song_tables) {
|
||||||
// Another horrible hack: device songs tables don't have matching _fts tables, so if this command tries to touch one, ignore it.
|
|
||||||
if (table.startsWith("device_"_L1) && command.contains(QLatin1String(kMagicAllSongsTables) + "_fts"_L1)) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
qLog(Info) << "Updating" << table << "for" << kMagicAllSongsTables;
|
qLog(Info) << "Updating" << table << "for" << kMagicAllSongsTables;
|
||||||
QString new_command(command);
|
QString new_command(command);
|
||||||
new_command.replace(QLatin1String(kMagicAllSongsTables), table);
|
new_command.replace(QLatin1String(kMagicAllSongsTables), table);
|
||||||
|
|||||||
@@ -157,12 +157,16 @@ void HttpBaseRequest::HandleSSLErrors(const QList<QSslError> &ssl_errors) {
|
|||||||
HttpBaseRequest::ReplyDataResult HttpBaseRequest::GetReplyData(QNetworkReply *reply) {
|
HttpBaseRequest::ReplyDataResult HttpBaseRequest::GetReplyData(QNetworkReply *reply) {
|
||||||
|
|
||||||
if (reply->error() != QNetworkReply::NoError) {
|
if (reply->error() != QNetworkReply::NoError) {
|
||||||
|
if (reply->error() >= 200) {
|
||||||
|
reply->readAll(); // QTBUG-135641
|
||||||
|
}
|
||||||
return ReplyDataResult(ErrorCode::NetworkError, QStringLiteral("%1 (%2)").arg(reply->errorString()).arg(reply->error()));
|
return ReplyDataResult(ErrorCode::NetworkError, QStringLiteral("%1 (%2)").arg(reply->errorString()).arg(reply->error()));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).isValid()) {
|
if (reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).isValid()) {
|
||||||
const int http_status_code = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();
|
const int http_status_code = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();
|
||||||
if (http_status_code < 200 || http_status_code > 207) {
|
if (http_status_code < 200 || http_status_code > 207) {
|
||||||
|
reply->readAll(); // QTBUG-135641
|
||||||
return ReplyDataResult(ErrorCode::HttpError, QStringLiteral("Received HTTP code %1").arg(http_status_code));
|
return ReplyDataResult(ErrorCode::HttpError, QStringLiteral("Received HTTP code %1").arg(http_status_code));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -156,10 +156,8 @@
|
|||||||
#include "lyrics/lyricsproviders.h"
|
#include "lyrics/lyricsproviders.h"
|
||||||
#include "device/devicemanager.h"
|
#include "device/devicemanager.h"
|
||||||
#include "device/devicestatefiltermodel.h"
|
#include "device/devicestatefiltermodel.h"
|
||||||
#ifndef Q_OS_WIN32
|
#include "device/deviceview.h"
|
||||||
# include "device/deviceview.h"
|
#include "device/deviceviewcontainer.h"
|
||||||
# include "device/deviceviewcontainer.h"
|
|
||||||
#endif
|
|
||||||
#include "transcoder/transcodedialog.h"
|
#include "transcoder/transcodedialog.h"
|
||||||
#include "settings/settingsdialog.h"
|
#include "settings/settingsdialog.h"
|
||||||
#include "constants/behavioursettings.h"
|
#include "constants/behavioursettings.h"
|
||||||
@@ -175,6 +173,7 @@
|
|||||||
# include "constants/tidalsettings.h"
|
# include "constants/tidalsettings.h"
|
||||||
#endif
|
#endif
|
||||||
#ifdef HAVE_SPOTIFY
|
#ifdef HAVE_SPOTIFY
|
||||||
|
# include "spotify/spotifyservice.h"
|
||||||
# include "constants/spotifysettings.h"
|
# include "constants/spotifysettings.h"
|
||||||
#endif
|
#endif
|
||||||
#ifdef HAVE_QOBUZ
|
#ifdef HAVE_QOBUZ
|
||||||
@@ -280,7 +279,7 @@ constexpr char QTSPARKLE_URL[] = "https://www.strawberrymusicplayer.org/sparkle-
|
|||||||
#endif // HAVE_QTSPARKLE
|
#endif // HAVE_QTSPARKLE
|
||||||
|
|
||||||
MainWindow::MainWindow(Application *app,
|
MainWindow::MainWindow(Application *app,
|
||||||
SharedPtr<SystemTrayIcon> tray_icon, OSDBase *osd,
|
SharedPtr<SystemTrayIcon> systemtrayicon, OSDBase *osd,
|
||||||
#ifdef HAVE_DISCORD_RPC
|
#ifdef HAVE_DISCORD_RPC
|
||||||
discord::RichPresence *discord_rich_presence,
|
discord::RichPresence *discord_rich_presence,
|
||||||
#endif
|
#endif
|
||||||
@@ -292,7 +291,7 @@ MainWindow::MainWindow(Application *app,
|
|||||||
thumbbar_(new Windows7ThumbBar(this)),
|
thumbbar_(new Windows7ThumbBar(this)),
|
||||||
#endif
|
#endif
|
||||||
app_(app),
|
app_(app),
|
||||||
tray_icon_(tray_icon),
|
systemtrayicon_(systemtrayicon),
|
||||||
osd_(osd),
|
osd_(osd),
|
||||||
#ifdef HAVE_DISCORD_RPC
|
#ifdef HAVE_DISCORD_RPC
|
||||||
discord_rich_presence_(discord_rich_presence),
|
discord_rich_presence_(discord_rich_presence),
|
||||||
@@ -310,9 +309,7 @@ MainWindow::MainWindow(Application *app,
|
|||||||
context_view_(new ContextView(this)),
|
context_view_(new ContextView(this)),
|
||||||
collection_view_(new CollectionViewContainer(this)),
|
collection_view_(new CollectionViewContainer(this)),
|
||||||
file_view_(new FileView(this)),
|
file_view_(new FileView(this)),
|
||||||
#ifndef Q_OS_WIN32
|
|
||||||
device_view_(new DeviceViewContainer(this)),
|
device_view_(new DeviceViewContainer(this)),
|
||||||
#endif
|
|
||||||
playlist_list_(new PlaylistListContainer(this)),
|
playlist_list_(new PlaylistListContainer(this)),
|
||||||
queue_view_(new QueueView(this)),
|
queue_view_(new QueueView(this)),
|
||||||
settings_dialog_(std::bind(&MainWindow::CreateSettingsDialog, this)),
|
settings_dialog_(std::bind(&MainWindow::CreateSettingsDialog, this)),
|
||||||
@@ -375,9 +372,7 @@ MainWindow::MainWindow(Application *app,
|
|||||||
playlist_move_to_collection_(nullptr),
|
playlist_move_to_collection_(nullptr),
|
||||||
playlist_open_in_browser_(nullptr),
|
playlist_open_in_browser_(nullptr),
|
||||||
playlist_organize_(nullptr),
|
playlist_organize_(nullptr),
|
||||||
#ifndef Q_OS_WIN32
|
|
||||||
playlist_copy_to_device_(nullptr),
|
playlist_copy_to_device_(nullptr),
|
||||||
#endif
|
|
||||||
playlist_delete_(nullptr),
|
playlist_delete_(nullptr),
|
||||||
playlist_queue_(nullptr),
|
playlist_queue_(nullptr),
|
||||||
playlist_queue_play_next_(nullptr),
|
playlist_queue_play_next_(nullptr),
|
||||||
@@ -409,7 +404,11 @@ MainWindow::MainWindow(Application *app,
|
|||||||
// Initialize the UI
|
// Initialize the UI
|
||||||
ui_->setupUi(this);
|
ui_->setupUi(this);
|
||||||
|
|
||||||
setWindowIcon(IconLoader::Load(u"strawberry"_s));
|
if (QGuiApplication::platformName() != "wayland"_L1) {
|
||||||
|
setWindowIcon(IconLoader::Load(u"strawberry"_s));
|
||||||
|
}
|
||||||
|
|
||||||
|
systemtrayicon_->SetDevicePixelRatioF(devicePixelRatioF());
|
||||||
|
|
||||||
QObject::connect(&*app->database(), &Database::Error, this, &MainWindow::ShowErrorDialog);
|
QObject::connect(&*app->database(), &Database::Error, this, &MainWindow::ShowErrorDialog);
|
||||||
|
|
||||||
@@ -430,9 +429,7 @@ MainWindow::MainWindow(Application *app,
|
|||||||
ui_->tabs->AddTab(smartplaylists_view_, u"smartplaylists"_s, IconLoader::Load(u"view-media-playlist"_s, true, 0, 32), tr("Smart playlists"));
|
ui_->tabs->AddTab(smartplaylists_view_, u"smartplaylists"_s, IconLoader::Load(u"view-media-playlist"_s, true, 0, 32), tr("Smart playlists"));
|
||||||
ui_->tabs->AddTab(file_view_, u"files"_s, IconLoader::Load(u"document-open"_s, true, 0, 32), tr("Files"));
|
ui_->tabs->AddTab(file_view_, u"files"_s, IconLoader::Load(u"document-open"_s, true, 0, 32), tr("Files"));
|
||||||
ui_->tabs->AddTab(radio_view_, u"radios"_s, IconLoader::Load(u"radio"_s, true, 0, 32), tr("Radios"));
|
ui_->tabs->AddTab(radio_view_, u"radios"_s, IconLoader::Load(u"radio"_s, true, 0, 32), tr("Radios"));
|
||||||
#ifndef Q_OS_WIN32
|
|
||||||
ui_->tabs->AddTab(device_view_, u"devices"_s, IconLoader::Load(u"device"_s, true, 0, 32), tr("Devices"));
|
ui_->tabs->AddTab(device_view_, u"devices"_s, IconLoader::Load(u"device"_s, true, 0, 32), tr("Devices"));
|
||||||
#endif
|
|
||||||
#ifdef HAVE_SUBSONIC
|
#ifdef HAVE_SUBSONIC
|
||||||
ui_->tabs->AddTab(subsonic_view_, u"subsonic"_s, IconLoader::Load(u"subsonic"_s, true, 0, 32), tr("Subsonic"));
|
ui_->tabs->AddTab(subsonic_view_, u"subsonic"_s, IconLoader::Load(u"subsonic"_s, true, 0, 32), tr("Subsonic"));
|
||||||
#endif
|
#endif
|
||||||
@@ -480,9 +477,7 @@ MainWindow::MainWindow(Application *app,
|
|||||||
|
|
||||||
collection_view_->view()->setModel(app_->collection()->model()->filter());
|
collection_view_->view()->setModel(app_->collection()->model()->filter());
|
||||||
collection_view_->view()->Init(app->task_manager(), app->tagreader_client(), app->network(), app->albumcover_loader(), app->current_albumcover_loader(), app->cover_providers(), app->lyrics_providers(), app->collection(), app->device_manager(), app->streaming_services());
|
collection_view_->view()->Init(app->task_manager(), app->tagreader_client(), app->network(), app->albumcover_loader(), app->current_albumcover_loader(), app->cover_providers(), app->lyrics_providers(), app->collection(), app->device_manager(), app->streaming_services());
|
||||||
#ifndef Q_OS_WIN32
|
|
||||||
device_view_->view()->Init(app->task_manager(), app->tagreader_client(), app->device_manager(), app->collection_model()->directory_model());
|
device_view_->view()->Init(app->task_manager(), app->tagreader_client(), app->device_manager(), app->collection_model()->directory_model());
|
||||||
#endif
|
|
||||||
playlist_list_->Init(app_->task_manager(), app->tagreader_client(), app_->playlist_manager(), app_->playlist_backend(), app_->device_manager());
|
playlist_list_->Init(app_->task_manager(), app->tagreader_client(), app_->playlist_manager(), app_->playlist_backend(), app_->device_manager());
|
||||||
|
|
||||||
organize_dialog_->SetDestinationModel(app_->collection()->model()->directory_model());
|
organize_dialog_->SetDestinationModel(app_->collection()->model()->directory_model());
|
||||||
@@ -554,9 +549,7 @@ MainWindow::MainWindow(Application *app,
|
|||||||
QObject::connect(file_view_, &FileView::CopyToCollection, this, &MainWindow::CopyFilesToCollection);
|
QObject::connect(file_view_, &FileView::CopyToCollection, this, &MainWindow::CopyFilesToCollection);
|
||||||
QObject::connect(file_view_, &FileView::MoveToCollection, this, &MainWindow::MoveFilesToCollection);
|
QObject::connect(file_view_, &FileView::MoveToCollection, this, &MainWindow::MoveFilesToCollection);
|
||||||
QObject::connect(file_view_, &FileView::EditTags, this, &MainWindow::EditFileTags);
|
QObject::connect(file_view_, &FileView::EditTags, this, &MainWindow::EditFileTags);
|
||||||
#ifndef Q_OS_WIN32
|
|
||||||
QObject::connect(file_view_, &FileView::CopyToDevice, this, &MainWindow::CopyFilesToDevice);
|
QObject::connect(file_view_, &FileView::CopyToDevice, this, &MainWindow::CopyFilesToDevice);
|
||||||
#endif
|
|
||||||
file_view_->SetTaskManager(app_->task_manager());
|
file_view_->SetTaskManager(app_->task_manager());
|
||||||
|
|
||||||
// Action connections
|
// Action connections
|
||||||
@@ -718,10 +711,8 @@ MainWindow::MainWindow(Application *app,
|
|||||||
QObject::connect(album_cover_choice_controller_->search_cover_auto_action(), &QAction::triggered, this, &MainWindow::SearchCoverAutomatically);
|
QObject::connect(album_cover_choice_controller_->search_cover_auto_action(), &QAction::triggered, this, &MainWindow::SearchCoverAutomatically);
|
||||||
QObject::connect(album_cover_choice_controller_->search_cover_auto_action(), &QAction::toggled, this, &MainWindow::ToggleSearchCoverAuto);
|
QObject::connect(album_cover_choice_controller_->search_cover_auto_action(), &QAction::toggled, this, &MainWindow::ToggleSearchCoverAuto);
|
||||||
|
|
||||||
#ifndef Q_OS_WIN32
|
|
||||||
// Devices connections
|
// Devices connections
|
||||||
QObject::connect(device_view_->view(), &DeviceView::AddToPlaylistSignal, this, &MainWindow::AddToPlaylist);
|
QObject::connect(device_view_->view(), &DeviceView::AddToPlaylistSignal, this, &MainWindow::AddToPlaylist);
|
||||||
#endif
|
|
||||||
|
|
||||||
// Collection filter widget
|
// Collection filter widget
|
||||||
QActionGroup *collection_view_group = new QActionGroup(this);
|
QActionGroup *collection_view_group = new QActionGroup(this);
|
||||||
@@ -784,6 +775,9 @@ MainWindow::MainWindow(Application *app,
|
|||||||
QObject::connect(spotify_view_->songs_collection_view(), &StreamingCollectionView::AddToPlaylistSignal, this, &MainWindow::AddToPlaylist);
|
QObject::connect(spotify_view_->songs_collection_view(), &StreamingCollectionView::AddToPlaylistSignal, this, &MainWindow::AddToPlaylist);
|
||||||
QObject::connect(spotify_view_->search_view(), &StreamingSearchView::OpenSettingsDialog, this, &MainWindow::OpenServiceSettingsDialog);
|
QObject::connect(spotify_view_->search_view(), &StreamingSearchView::OpenSettingsDialog, this, &MainWindow::OpenServiceSettingsDialog);
|
||||||
QObject::connect(spotify_view_->search_view(), &StreamingSearchView::AddToPlaylist, this, &MainWindow::AddToPlaylist);
|
QObject::connect(spotify_view_->search_view(), &StreamingSearchView::AddToPlaylist, this, &MainWindow::AddToPlaylist);
|
||||||
|
if (SpotifyServicePtr spotifyservice = app_->streaming_services()->Service<SpotifyService>()) {
|
||||||
|
QObject::connect(&*spotifyservice, &SpotifyService::UpdateSpotifyAccessToken, &*app_->player()->engine(), &EngineBase::UpdateSpotifyAccessToken);
|
||||||
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
QObject::connect(radio_view_, &RadioViewContainer::Refresh, &*app_->radio_services(), &RadioServices::RefreshChannels);
|
QObject::connect(radio_view_, &RadioViewContainer::Refresh, &*app_->radio_services(), &RadioServices::RefreshChannels);
|
||||||
@@ -824,9 +818,7 @@ MainWindow::MainWindow(Application *app,
|
|||||||
playlist_organize_ = playlist_menu_->addAction(IconLoader::Load(u"edit-copy"_s), tr("Organize files..."), this, &MainWindow::PlaylistMoveToCollection);
|
playlist_organize_ = playlist_menu_->addAction(IconLoader::Load(u"edit-copy"_s), tr("Organize files..."), this, &MainWindow::PlaylistMoveToCollection);
|
||||||
playlist_copy_to_collection_ = playlist_menu_->addAction(IconLoader::Load(u"edit-copy"_s), tr("Copy to collection..."), this, &MainWindow::PlaylistCopyToCollection);
|
playlist_copy_to_collection_ = playlist_menu_->addAction(IconLoader::Load(u"edit-copy"_s), tr("Copy to collection..."), this, &MainWindow::PlaylistCopyToCollection);
|
||||||
playlist_move_to_collection_ = playlist_menu_->addAction(IconLoader::Load(u"go-jump"_s), tr("Move to collection..."), this, &MainWindow::PlaylistMoveToCollection);
|
playlist_move_to_collection_ = playlist_menu_->addAction(IconLoader::Load(u"go-jump"_s), tr("Move to collection..."), this, &MainWindow::PlaylistMoveToCollection);
|
||||||
#ifndef Q_OS_WIN32
|
|
||||||
playlist_copy_to_device_ = playlist_menu_->addAction(IconLoader::Load(u"device"_s), tr("Copy to device..."), this, &MainWindow::PlaylistCopyToDevice);
|
playlist_copy_to_device_ = playlist_menu_->addAction(IconLoader::Load(u"device"_s), tr("Copy to device..."), this, &MainWindow::PlaylistCopyToDevice);
|
||||||
#endif
|
|
||||||
playlist_delete_ = playlist_menu_->addAction(IconLoader::Load(u"edit-delete"_s), tr("Delete from disk..."), this, &MainWindow::PlaylistDelete);
|
playlist_delete_ = playlist_menu_->addAction(IconLoader::Load(u"edit-delete"_s), tr("Delete from disk..."), this, &MainWindow::PlaylistDelete);
|
||||||
playlist_menu_->addSeparator();
|
playlist_menu_->addSeparator();
|
||||||
playlistitem_actions_separator_ = playlist_menu_->addSeparator();
|
playlistitem_actions_separator_ = playlist_menu_->addSeparator();
|
||||||
@@ -845,10 +837,8 @@ MainWindow::MainWindow(Application *app,
|
|||||||
QObject::connect(ui_->playlist, &PlaylistContainer::UndoRedoActionsChanged, this, &MainWindow::PlaylistUndoRedoChanged);
|
QObject::connect(ui_->playlist, &PlaylistContainer::UndoRedoActionsChanged, this, &MainWindow::PlaylistUndoRedoChanged);
|
||||||
|
|
||||||
QObject::connect(&*app_->device_manager(), &DeviceManager::DeviceError, this, &MainWindow::ShowErrorDialog);
|
QObject::connect(&*app_->device_manager(), &DeviceManager::DeviceError, this, &MainWindow::ShowErrorDialog);
|
||||||
#ifndef WIN32
|
|
||||||
QObject::connect(app_->device_manager()->connected_devices_model(), &DeviceStateFilterModel::IsEmptyChanged, playlist_copy_to_device_, &QAction::setDisabled);
|
QObject::connect(app_->device_manager()->connected_devices_model(), &DeviceStateFilterModel::IsEmptyChanged, playlist_copy_to_device_, &QAction::setDisabled);
|
||||||
playlist_copy_to_device_->setDisabled(app_->device_manager()->connected_devices_model()->rowCount() == 0);
|
playlist_copy_to_device_->setDisabled(app_->device_manager()->connected_devices_model()->rowCount() == 0);
|
||||||
#endif
|
|
||||||
|
|
||||||
QObject::connect(&*app_->scrobbler()->settings(), &ScrobblerSettingsService::ScrobblingEnabledChanged, this, &MainWindow::ScrobblingEnabledChanged);
|
QObject::connect(&*app_->scrobbler()->settings(), &ScrobblerSettingsService::ScrobblingEnabledChanged, this, &MainWindow::ScrobblingEnabledChanged);
|
||||||
QObject::connect(&*app_->scrobbler()->settings(), &ScrobblerSettingsService::ScrobbleButtonVisibilityChanged, this, &MainWindow::ScrobbleButtonVisibilityChanged);
|
QObject::connect(&*app_->scrobbler()->settings(), &ScrobblerSettingsService::ScrobbleButtonVisibilityChanged, this, &MainWindow::ScrobbleButtonVisibilityChanged);
|
||||||
@@ -858,14 +848,14 @@ MainWindow::MainWindow(Application *app,
|
|||||||
mac::SetApplicationHandler(this);
|
mac::SetApplicationHandler(this);
|
||||||
#endif
|
#endif
|
||||||
// Tray icon
|
// Tray icon
|
||||||
tray_icon_->SetupMenu(ui_->action_previous_track, ui_->action_play_pause, ui_->action_stop, ui_->action_stop_after_this_track, ui_->action_next_track, ui_->action_mute, ui_->action_love, ui_->action_quit);
|
systemtrayicon_->SetupMenu(ui_->action_previous_track, ui_->action_play_pause, ui_->action_stop, ui_->action_stop_after_this_track, ui_->action_next_track, ui_->action_mute, ui_->action_love, ui_->action_quit);
|
||||||
QObject::connect(&*tray_icon_, &SystemTrayIcon::PlayPause, &*app_->player(), &Player::PlayPauseHelper);
|
QObject::connect(&*systemtrayicon_, &SystemTrayIcon::PlayPause, &*app_->player(), &Player::PlayPauseHelper);
|
||||||
QObject::connect(&*tray_icon_, &SystemTrayIcon::SeekForward, &*app_->player(), &Player::SeekForward);
|
QObject::connect(&*systemtrayicon_, &SystemTrayIcon::SeekForward, &*app_->player(), &Player::SeekForward);
|
||||||
QObject::connect(&*tray_icon_, &SystemTrayIcon::SeekBackward, &*app_->player(), &Player::SeekBackward);
|
QObject::connect(&*systemtrayicon_, &SystemTrayIcon::SeekBackward, &*app_->player(), &Player::SeekBackward);
|
||||||
QObject::connect(&*tray_icon_, &SystemTrayIcon::NextTrack, &*app_->player(), &Player::Next);
|
QObject::connect(&*systemtrayicon_, &SystemTrayIcon::NextTrack, &*app_->player(), &Player::Next);
|
||||||
QObject::connect(&*tray_icon_, &SystemTrayIcon::PreviousTrack, &*app_->player(), &Player::Previous);
|
QObject::connect(&*systemtrayicon_, &SystemTrayIcon::PreviousTrack, &*app_->player(), &Player::Previous);
|
||||||
QObject::connect(&*tray_icon_, &SystemTrayIcon::ShowHide, this, &MainWindow::ToggleShowHide);
|
QObject::connect(&*systemtrayicon_, &SystemTrayIcon::ShowHide, this, &MainWindow::ToggleShowHide);
|
||||||
QObject::connect(&*tray_icon_, &SystemTrayIcon::ChangeVolume, this, &MainWindow::VolumeWheelEvent);
|
QObject::connect(&*systemtrayicon_, &SystemTrayIcon::ChangeVolume, this, &MainWindow::VolumeWheelEvent);
|
||||||
|
|
||||||
// Windows 7 thumbbar buttons
|
// Windows 7 thumbbar buttons
|
||||||
#ifdef Q_OS_WIN32
|
#ifdef Q_OS_WIN32
|
||||||
@@ -980,7 +970,7 @@ MainWindow::MainWindow(Application *app,
|
|||||||
QObject::connect(&*app_->lastfm_import(), &LastFMImport::UpdateLastPlayed, &*app_->collection_backend(), &CollectionBackend::UpdateLastPlayed);
|
QObject::connect(&*app_->lastfm_import(), &LastFMImport::UpdateLastPlayed, &*app_->collection_backend(), &CollectionBackend::UpdateLastPlayed);
|
||||||
QObject::connect(&*app_->lastfm_import(), &LastFMImport::UpdatePlayCount, &*app_->collection_backend(), &CollectionBackend::UpdatePlayCount);
|
QObject::connect(&*app_->lastfm_import(), &LastFMImport::UpdatePlayCount, &*app_->collection_backend(), &CollectionBackend::UpdatePlayCount);
|
||||||
|
|
||||||
#if !defined(HAVE_AUDIOCD) || defined(Q_OS_WIN32)
|
#if !defined(HAVE_AUDIOCD)
|
||||||
ui_->action_open_cd->setEnabled(false);
|
ui_->action_open_cd->setEnabled(false);
|
||||||
ui_->action_open_cd->setVisible(false);
|
ui_->action_open_cd->setVisible(false);
|
||||||
#endif
|
#endif
|
||||||
@@ -1043,7 +1033,7 @@ MainWindow::MainWindow(Application *app,
|
|||||||
show();
|
show();
|
||||||
break;
|
break;
|
||||||
case BehaviourSettings::StartupBehaviour::Hide:
|
case BehaviourSettings::StartupBehaviour::Hide:
|
||||||
if (tray_icon_->IsSystemTrayAvailable() && tray_icon_->isVisible()) {
|
if (systemtrayicon_->IsSystemTrayAvailable() && systemtrayicon_->isVisible()) {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
[[fallthrough]];
|
[[fallthrough]];
|
||||||
@@ -1056,7 +1046,7 @@ MainWindow::MainWindow(Application *app,
|
|||||||
was_minimized_ = settings_.value(MainWindowSettings::kMinimized, false).toBool();
|
was_minimized_ = settings_.value(MainWindowSettings::kMinimized, false).toBool();
|
||||||
if (was_minimized_) setWindowState(windowState() | Qt::WindowMinimized);
|
if (was_minimized_) setWindowState(windowState() | Qt::WindowMinimized);
|
||||||
|
|
||||||
if (!tray_icon_->IsSystemTrayAvailable() || !tray_icon_->isVisible() || !settings_.value(MainWindowSettings::kHidden, false).toBool()) {
|
if (!systemtrayicon_->IsSystemTrayAvailable() || !systemtrayicon_->isVisible() || !settings_.value(MainWindowSettings::kHidden, false).toBool()) {
|
||||||
show();
|
show();
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
@@ -1168,13 +1158,13 @@ void MainWindow::ReloadSettings() {
|
|||||||
#ifdef Q_OS_MACOS
|
#ifdef Q_OS_MACOS
|
||||||
constexpr bool keeprunning_available = true;
|
constexpr bool keeprunning_available = true;
|
||||||
#else
|
#else
|
||||||
const bool systemtray_available = tray_icon_->IsSystemTrayAvailable();
|
const bool systemtray_available = systemtrayicon_->IsSystemTrayAvailable();
|
||||||
s.beginGroup(BehaviourSettings::kSettingsGroup);
|
s.beginGroup(BehaviourSettings::kSettingsGroup);
|
||||||
const bool showtrayicon = s.value(BehaviourSettings::kShowTrayIcon, systemtray_available).toBool();
|
const bool showtrayicon = s.value(BehaviourSettings::kShowTrayIcon, systemtray_available).toBool();
|
||||||
s.endGroup();
|
s.endGroup();
|
||||||
const bool keeprunning_available = systemtray_available && showtrayicon;
|
const bool keeprunning_available = systemtray_available && showtrayicon;
|
||||||
if (systemtray_available) {
|
if (systemtray_available) {
|
||||||
tray_icon_->setVisible(showtrayicon);
|
systemtrayicon_->setVisible(showtrayicon);
|
||||||
}
|
}
|
||||||
if ((!showtrayicon || !systemtray_available) && !isVisible()) {
|
if ((!showtrayicon || !systemtray_available) && !isVisible()) {
|
||||||
show();
|
show();
|
||||||
@@ -1199,7 +1189,7 @@ void MainWindow::ReloadSettings() {
|
|||||||
int iconsize = s.value(AppearanceSettings::kIconSizePlayControlButtons, 32).toInt();
|
int iconsize = s.value(AppearanceSettings::kIconSizePlayControlButtons, 32).toInt();
|
||||||
s.endGroup();
|
s.endGroup();
|
||||||
|
|
||||||
tray_icon_->SetTrayiconProgress(trayicon_progress);
|
systemtrayicon_->SetTrayiconProgress(trayicon_progress);
|
||||||
|
|
||||||
#ifdef HAVE_DBUS
|
#ifdef HAVE_DBUS
|
||||||
if (taskbar_progress_ && !taskbar_progress) {
|
if (taskbar_progress_ && !taskbar_progress) {
|
||||||
@@ -1221,11 +1211,11 @@ void MainWindow::ReloadSettings() {
|
|||||||
ui_->volume->SetEnabled(volume_control);
|
ui_->volume->SetEnabled(volume_control);
|
||||||
if (volume_control) {
|
if (volume_control) {
|
||||||
if (!ui_->action_mute->isVisible()) ui_->action_mute->setVisible(true);
|
if (!ui_->action_mute->isVisible()) ui_->action_mute->setVisible(true);
|
||||||
if (!tray_icon_->MuteEnabled()) tray_icon_->SetMuteEnabled(true);
|
if (!systemtrayicon_->MuteEnabled()) systemtrayicon_->SetMuteEnabled(true);
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
if (ui_->action_mute->isVisible()) ui_->action_mute->setVisible(false);
|
if (ui_->action_mute->isVisible()) ui_->action_mute->setVisible(false);
|
||||||
if (tray_icon_->MuteEnabled()) tray_icon_->SetMuteEnabled(false);
|
if (systemtrayicon_->MuteEnabled()) systemtrayicon_->SetMuteEnabled(false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1377,8 +1367,8 @@ void MainWindow::Exit() {
|
|||||||
if (app_->player()->GetState() == EngineBase::State::Playing) {
|
if (app_->player()->GetState() == EngineBase::State::Playing) {
|
||||||
app_->player()->Stop();
|
app_->player()->Stop();
|
||||||
hide();
|
hide();
|
||||||
if (tray_icon_->IsSystemTrayAvailable()) {
|
if (systemtrayicon_->IsSystemTrayAvailable()) {
|
||||||
tray_icon_->setVisible(false);
|
systemtrayicon_->setVisible(false);
|
||||||
}
|
}
|
||||||
return; // Don't quit the application now: wait for the fadeout finished signal
|
return; // Don't quit the application now: wait for the fadeout finished signal
|
||||||
}
|
}
|
||||||
@@ -1435,7 +1425,7 @@ void MainWindow::MediaStopped() {
|
|||||||
|
|
||||||
ui_->action_love->setEnabled(false);
|
ui_->action_love->setEnabled(false);
|
||||||
ui_->button_love->setEnabled(false);
|
ui_->button_love->setEnabled(false);
|
||||||
tray_icon_->LoveStateChanged(false);
|
systemtrayicon_->LoveStateChanged(false);
|
||||||
|
|
||||||
if (track_position_timer_->isActive()) {
|
if (track_position_timer_->isActive()) {
|
||||||
track_position_timer_->stop();
|
track_position_timer_->stop();
|
||||||
@@ -1444,8 +1434,8 @@ void MainWindow::MediaStopped() {
|
|||||||
track_slider_timer_->stop();
|
track_slider_timer_->stop();
|
||||||
}
|
}
|
||||||
ui_->track_slider->SetStopped();
|
ui_->track_slider->SetStopped();
|
||||||
tray_icon_->SetProgress(0);
|
systemtrayicon_->SetProgress(0);
|
||||||
tray_icon_->SetStopped();
|
systemtrayicon_->SetStopped();
|
||||||
|
|
||||||
#ifdef HAVE_DBUS
|
#ifdef HAVE_DBUS
|
||||||
if (taskbar_progress_) {
|
if (taskbar_progress_) {
|
||||||
@@ -1477,7 +1467,7 @@ void MainWindow::MediaPaused() {
|
|||||||
track_slider_timer_->start();
|
track_slider_timer_->start();
|
||||||
}
|
}
|
||||||
|
|
||||||
tray_icon_->SetPaused();
|
systemtrayicon_->SetPaused();
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1498,7 +1488,7 @@ void MainWindow::MediaPlaying() {
|
|||||||
}
|
}
|
||||||
ui_->action_play_pause->setEnabled(enable_play_pause);
|
ui_->action_play_pause->setEnabled(enable_play_pause);
|
||||||
ui_->track_slider->SetCanSeek(can_seek);
|
ui_->track_slider->SetCanSeek(can_seek);
|
||||||
tray_icon_->SetPlaying(enable_play_pause);
|
systemtrayicon_->SetPlaying(enable_play_pause);
|
||||||
|
|
||||||
if (!track_position_timer_->isActive()) {
|
if (!track_position_timer_->isActive()) {
|
||||||
track_position_timer_->start();
|
track_position_timer_->start();
|
||||||
@@ -1515,18 +1505,18 @@ void MainWindow::SendNowPlaying() {
|
|||||||
|
|
||||||
// Send now playing to scrobble services
|
// Send now playing to scrobble services
|
||||||
Playlist *playlist = app_->playlist_manager()->active();
|
Playlist *playlist = app_->playlist_manager()->active();
|
||||||
if (app_->scrobbler()->enabled() && playlist && playlist->current_item() && playlist->current_item()->Metadata().is_metadata_good()) {
|
if (app_->scrobbler()->enabled() && playlist && playlist->current_item() && playlist->current_item()->EffectiveMetadata().is_metadata_good()) {
|
||||||
app_->scrobbler()->UpdateNowPlaying(playlist->current_item()->Metadata());
|
app_->scrobbler()->UpdateNowPlaying(playlist->current_item()->EffectiveMetadata());
|
||||||
ui_->action_love->setEnabled(true);
|
ui_->action_love->setEnabled(true);
|
||||||
ui_->button_love->setEnabled(true);
|
ui_->button_love->setEnabled(true);
|
||||||
tray_icon_->LoveStateChanged(true);
|
systemtrayicon_->LoveStateChanged(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void MainWindow::VolumeChanged(const uint volume) {
|
void MainWindow::VolumeChanged(const uint volume) {
|
||||||
ui_->action_mute->setChecked(volume == 0);
|
ui_->action_mute->setChecked(volume == 0);
|
||||||
tray_icon_->MuteButtonStateChanged(volume == 0);
|
systemtrayicon_->MuteButtonStateChanged(volume == 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
void MainWindow::SongChanged(const Song &song) {
|
void MainWindow::SongChanged(const Song &song) {
|
||||||
@@ -1536,7 +1526,7 @@ void MainWindow::SongChanged(const Song &song) {
|
|||||||
song_playing_ = song;
|
song_playing_ = song;
|
||||||
song_ = song;
|
song_ = song;
|
||||||
setWindowTitle(song.PrettyTitleWithArtist());
|
setWindowTitle(song.PrettyTitleWithArtist());
|
||||||
tray_icon_->SetProgress(0);
|
systemtrayicon_->SetProgress(0);
|
||||||
|
|
||||||
#ifdef HAVE_DBUS
|
#ifdef HAVE_DBUS
|
||||||
if (taskbar_progress_) {
|
if (taskbar_progress_) {
|
||||||
@@ -1562,9 +1552,9 @@ void MainWindow::TrackSkipped(PlaylistItemPtr item) {
|
|||||||
|
|
||||||
// If it was a collection item then we have to increment its skipped count in the database.
|
// If it was a collection item then we have to increment its skipped count in the database.
|
||||||
|
|
||||||
if (item && item->IsLocalCollectionItem() && item->Metadata().id() != -1) {
|
if (item && item->IsLocalCollectionItem() && item->EffectiveMetadata().id() != -1) {
|
||||||
|
|
||||||
Song song = item->Metadata();
|
Song song = item->EffectiveMetadata();
|
||||||
const qint64 position = app_->player()->engine()->position_nanosec();
|
const qint64 position = app_->player()->engine()->position_nanosec();
|
||||||
const qint64 length = app_->player()->engine()->length_nanosec();
|
const qint64 length = app_->player()->engine()->length_nanosec();
|
||||||
const float percentage = (length == 0 ? 1 : static_cast<float>(position) / static_cast<float>(length));
|
const float percentage = (length == 0 ? 1 : static_cast<float>(position) / static_cast<float>(length));
|
||||||
@@ -1719,7 +1709,7 @@ void MainWindow::hideEvent(QHideEvent *e) {
|
|||||||
|
|
||||||
void MainWindow::closeEvent(QCloseEvent *e) {
|
void MainWindow::closeEvent(QCloseEvent *e) {
|
||||||
|
|
||||||
if (!exit_ && (!tray_icon_->IsSystemTrayAvailable() || !tray_icon_->isVisible() || !keep_running_)) {
|
if (!exit_ && (!systemtrayicon_->IsSystemTrayAvailable() || !systemtrayicon_->isVisible() || !keep_running_)) {
|
||||||
Exit();
|
Exit();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1730,7 +1720,7 @@ void MainWindow::closeEvent(QCloseEvent *e) {
|
|||||||
void MainWindow::SetHiddenInTray(const bool hidden) {
|
void MainWindow::SetHiddenInTray(const bool hidden) {
|
||||||
|
|
||||||
if (hidden && isVisible()) {
|
if (hidden && isVisible()) {
|
||||||
if (tray_icon_->IsSystemTrayAvailable() && tray_icon_->isVisible() && keep_running_) {
|
if (systemtrayicon_->IsSystemTrayAvailable() && systemtrayicon_->isVisible() && keep_running_) {
|
||||||
close();
|
close();
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
@@ -1758,8 +1748,8 @@ void MainWindow::FilePathChanged(const QString &path) {
|
|||||||
void MainWindow::Seeked(const qint64 microseconds) {
|
void MainWindow::Seeked(const qint64 microseconds) {
|
||||||
|
|
||||||
const qint64 position = microseconds / kUsecPerSec;
|
const qint64 position = microseconds / kUsecPerSec;
|
||||||
const qint64 length = app_->player()->GetCurrentItem()->Metadata().length_nanosec() / kNsecPerSec;
|
const qint64 length = app_->player()->GetCurrentItem()->EffectiveMetadata().length_nanosec() / kNsecPerSec;
|
||||||
tray_icon_->SetProgress(static_cast<int>(static_cast<double>(position) / static_cast<double>(length) * 100.0));
|
systemtrayicon_->SetProgress(static_cast<int>(static_cast<double>(position) / static_cast<double>(length) * 100.0));
|
||||||
|
|
||||||
#ifdef HAVE_DBUS
|
#ifdef HAVE_DBUS
|
||||||
if (taskbar_progress_) {
|
if (taskbar_progress_) {
|
||||||
@@ -1774,12 +1764,12 @@ void MainWindow::UpdateTrackPosition() {
|
|||||||
PlaylistItemPtr item(app_->player()->GetCurrentItem());
|
PlaylistItemPtr item(app_->player()->GetCurrentItem());
|
||||||
if (!item) return;
|
if (!item) return;
|
||||||
|
|
||||||
const qint64 length = (item->Metadata().length_nanosec() / kNsecPerSec);
|
const qint64 length = (item->EffectiveMetadata().length_nanosec() / kNsecPerSec);
|
||||||
if (length <= 0) return;
|
if (length <= 0) return;
|
||||||
const int position = std::floor(static_cast<float>(app_->player()->engine()->position_nanosec()) / static_cast<float>(kNsecPerSec) + 0.5);
|
const int position = std::floor(static_cast<float>(app_->player()->engine()->position_nanosec()) / static_cast<float>(kNsecPerSec) + 0.5);
|
||||||
|
|
||||||
// Update the tray icon every 10 seconds
|
// Update the tray icon every 10 seconds
|
||||||
if (position % 10 == 0) tray_icon_->SetProgress(static_cast<int>(static_cast<double>(position) / static_cast<double>(length) * 100.0));
|
if (position % 10 == 0) systemtrayicon_->SetProgress(static_cast<int>(static_cast<double>(position) / static_cast<double>(length) * 100.0));
|
||||||
|
|
||||||
#ifdef HAVE_DBUS
|
#ifdef HAVE_DBUS
|
||||||
if (taskbar_progress_) {
|
if (taskbar_progress_) {
|
||||||
@@ -1788,12 +1778,12 @@ void MainWindow::UpdateTrackPosition() {
|
|||||||
#endif
|
#endif
|
||||||
|
|
||||||
// Send Scrobble
|
// Send Scrobble
|
||||||
if (app_->scrobbler()->enabled() && item->Metadata().is_metadata_good()) {
|
if (app_->scrobbler()->enabled() && item->EffectiveMetadata().is_metadata_good()) {
|
||||||
Playlist *playlist = app_->playlist_manager()->active();
|
Playlist *playlist = app_->playlist_manager()->active();
|
||||||
if (playlist && !playlist->scrobbled()) {
|
if (playlist && !playlist->scrobbled()) {
|
||||||
const qint64 scrobble_point = (playlist->scrobble_point_nanosec() / kNsecPerSec);
|
const qint64 scrobble_point = (playlist->scrobble_point_nanosec() / kNsecPerSec);
|
||||||
if (position >= scrobble_point) {
|
if (position >= scrobble_point) {
|
||||||
app_->scrobbler()->Scrobble(item->Metadata(), scrobble_point);
|
app_->scrobbler()->Scrobble(item->EffectiveMetadata(), scrobble_point);
|
||||||
playlist->set_scrobbled(true);
|
playlist->set_scrobbled(true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1910,7 +1900,7 @@ void MainWindow::AddToPlaylistFromAction(QAction *action) {
|
|||||||
PlaylistItemPtr item = app_->playlist_manager()->current()->item_at(source_index.row());
|
PlaylistItemPtr item = app_->playlist_manager()->current()->item_at(source_index.row());
|
||||||
if (!item) continue;
|
if (!item) continue;
|
||||||
items << item;
|
items << item;
|
||||||
songs << item->Metadata();
|
songs << item->EffectiveMetadata();
|
||||||
}
|
}
|
||||||
|
|
||||||
// We're creating a new playlist
|
// We're creating a new playlist
|
||||||
@@ -1989,12 +1979,12 @@ void MainWindow::PlaylistRightClick(const QPoint global_pos, const QModelIndex &
|
|||||||
PlaylistItemPtr item = app_->playlist_manager()->current()->item_at(src_idx.row());
|
PlaylistItemPtr item = app_->playlist_manager()->current()->item_at(src_idx.row());
|
||||||
if (!item) continue;
|
if (!item) continue;
|
||||||
|
|
||||||
if (item->Metadata().url().isLocalFile()) ++local_songs;
|
if (item->EffectiveMetadata().url().isLocalFile()) ++local_songs;
|
||||||
|
|
||||||
if (item->Metadata().has_cue()) {
|
if (item->EffectiveMetadata().has_cue()) {
|
||||||
cue_selected = true;
|
cue_selected = true;
|
||||||
}
|
}
|
||||||
else if (item->Metadata().IsEditable()) {
|
else if (item->EffectiveMetadata().IsEditable()) {
|
||||||
++editable;
|
++editable;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2032,9 +2022,7 @@ void MainWindow::PlaylistRightClick(const QPoint global_pos, const QModelIndex &
|
|||||||
playlist_show_in_collection_->setVisible(false);
|
playlist_show_in_collection_->setVisible(false);
|
||||||
playlist_copy_to_collection_->setVisible(false);
|
playlist_copy_to_collection_->setVisible(false);
|
||||||
playlist_move_to_collection_->setVisible(false);
|
playlist_move_to_collection_->setVisible(false);
|
||||||
#ifndef Q_OS_WIN32
|
|
||||||
playlist_copy_to_device_->setVisible(false);
|
playlist_copy_to_device_->setVisible(false);
|
||||||
#endif
|
|
||||||
playlist_organize_->setVisible(false);
|
playlist_organize_->setVisible(false);
|
||||||
playlist_delete_->setVisible(false);
|
playlist_delete_->setVisible(false);
|
||||||
|
|
||||||
@@ -2097,7 +2085,7 @@ void MainWindow::PlaylistRightClick(const QPoint global_pos, const QModelIndex &
|
|||||||
|
|
||||||
// Is it a collection item?
|
// Is it a collection item?
|
||||||
PlaylistItemPtr item = app_->playlist_manager()->current()->item_at(source_index.row());
|
PlaylistItemPtr item = app_->playlist_manager()->current()->item_at(source_index.row());
|
||||||
if (item && item->IsLocalCollectionItem() && item->Metadata().id() != -1) {
|
if (item && item->IsLocalCollectionItem() && item->EffectiveMetadata().id() != -1) {
|
||||||
playlist_organize_->setVisible(local_songs > 0 && editable > 0 && !cue_selected);
|
playlist_organize_->setVisible(local_songs > 0 && editable > 0 && !cue_selected);
|
||||||
playlist_show_in_collection_->setVisible(true);
|
playlist_show_in_collection_->setVisible(true);
|
||||||
playlist_open_in_browser_->setVisible(true);
|
playlist_open_in_browser_->setVisible(true);
|
||||||
@@ -2107,9 +2095,7 @@ void MainWindow::PlaylistRightClick(const QPoint global_pos, const QModelIndex &
|
|||||||
playlist_move_to_collection_->setVisible(local_songs > 0);
|
playlist_move_to_collection_->setVisible(local_songs > 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
#ifndef Q_OS_WIN32
|
|
||||||
playlist_copy_to_device_->setVisible(local_songs > 0);
|
playlist_copy_to_device_->setVisible(local_songs > 0);
|
||||||
#endif
|
|
||||||
|
|
||||||
playlist_delete_->setVisible(delete_files_ && local_songs > 0);
|
playlist_delete_->setVisible(delete_files_ && local_songs > 0);
|
||||||
|
|
||||||
@@ -2189,9 +2175,9 @@ void MainWindow::RescanSongs() {
|
|||||||
PlaylistItemPtr item(app_->playlist_manager()->current()->item_at(source_index.row()));
|
PlaylistItemPtr item(app_->playlist_manager()->current()->item_at(source_index.row()));
|
||||||
if (!item) continue;
|
if (!item) continue;
|
||||||
if (item->IsLocalCollectionItem()) {
|
if (item->IsLocalCollectionItem()) {
|
||||||
songs << item->Metadata();
|
songs << item->EffectiveMetadata();
|
||||||
}
|
}
|
||||||
else if (item->Metadata().source() == Song::Source::LocalFile) {
|
else if (item->EffectiveMetadata().source() == Song::Source::LocalFile) {
|
||||||
QPersistentModelIndex persistent_index = QPersistentModelIndex(source_index);
|
QPersistentModelIndex persistent_index = QPersistentModelIndex(source_index);
|
||||||
app_->playlist_manager()->current()->ItemReload(persistent_index, item->OriginalMetadata(), false);
|
app_->playlist_manager()->current()->ItemReload(persistent_index, item->OriginalMetadata(), false);
|
||||||
}
|
}
|
||||||
@@ -2751,7 +2737,6 @@ void MainWindow::MoveFilesToCollection(const QList<QUrl> &urls) {
|
|||||||
|
|
||||||
void MainWindow::CopyFilesToDevice(const QList<QUrl> &urls) {
|
void MainWindow::CopyFilesToDevice(const QList<QUrl> &urls) {
|
||||||
|
|
||||||
#ifndef Q_OS_WIN32
|
|
||||||
organize_dialog_->SetDestinationModel(app_->device_manager()->connected_devices_model(), true);
|
organize_dialog_->SetDestinationModel(app_->device_manager()->connected_devices_model(), true);
|
||||||
organize_dialog_->SetCopy(true);
|
organize_dialog_->SetCopy(true);
|
||||||
if (organize_dialog_->SetUrls(urls)) {
|
if (organize_dialog_->SetUrls(urls)) {
|
||||||
@@ -2761,9 +2746,6 @@ void MainWindow::CopyFilesToDevice(const QList<QUrl> &urls) {
|
|||||||
else {
|
else {
|
||||||
QMessageBox::warning(this, tr("Error"), tr("None of the selected songs were suitable for copying to a device"));
|
QMessageBox::warning(this, tr("Error"), tr("None of the selected songs were suitable for copying to a device"));
|
||||||
}
|
}
|
||||||
#else
|
|
||||||
Q_UNUSED(urls);
|
|
||||||
#endif
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2823,7 +2805,7 @@ void MainWindow::PlaylistOpenInBrowser() {
|
|||||||
for (const QModelIndex &proxy_index : proxy_indexes) {
|
for (const QModelIndex &proxy_index : proxy_indexes) {
|
||||||
const QModelIndex source_index = app_->playlist_manager()->current()->filter()->mapToSource(proxy_index);
|
const QModelIndex source_index = app_->playlist_manager()->current()->filter()->mapToSource(proxy_index);
|
||||||
if (!source_index.isValid()) continue;
|
if (!source_index.isValid()) continue;
|
||||||
urls << QUrl(source_index.sibling(source_index.row(), static_cast<int>(Playlist::Column::Filename)).data().toString());
|
urls << QUrl(source_index.sibling(source_index.row(), static_cast<int>(Playlist::Column::URL)).data().toString());
|
||||||
}
|
}
|
||||||
|
|
||||||
Utilities::OpenInFileBrowser(urls);
|
Utilities::OpenInFileBrowser(urls);
|
||||||
@@ -2839,7 +2821,7 @@ void MainWindow::PlaylistCopyUrl() {
|
|||||||
if (!source_index.isValid()) continue;
|
if (!source_index.isValid()) continue;
|
||||||
PlaylistItemPtr item = app_->playlist_manager()->current()->item_at(source_index.row());
|
PlaylistItemPtr item = app_->playlist_manager()->current()->item_at(source_index.row());
|
||||||
if (!item) continue;
|
if (!item) continue;
|
||||||
urls << item->StreamUrl();
|
urls << item->EffectiveUrl();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (urls.count() > 0) {
|
if (urls.count() > 0) {
|
||||||
@@ -2891,8 +2873,6 @@ void MainWindow::PlaylistSkip() {
|
|||||||
|
|
||||||
void MainWindow::PlaylistCopyToDevice() {
|
void MainWindow::PlaylistCopyToDevice() {
|
||||||
|
|
||||||
#ifndef Q_OS_WIN32
|
|
||||||
|
|
||||||
SongList songs;
|
SongList songs;
|
||||||
|
|
||||||
const QModelIndexList proxy_indexes = ui_->playlist->view()->selectionModel()->selectedRows();
|
const QModelIndexList proxy_indexes = ui_->playlist->view()->selectionModel()->selectedRows();
|
||||||
@@ -2917,8 +2897,6 @@ void MainWindow::PlaylistCopyToDevice() {
|
|||||||
QMessageBox::warning(this, tr("Error"), tr("None of the selected songs were suitable for copying to a device"));
|
QMessageBox::warning(this, tr("Error"), tr("None of the selected songs were suitable for copying to a device"));
|
||||||
}
|
}
|
||||||
|
|
||||||
#endif
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void MainWindow::ChangeCollectionFilterMode(QAction *action) {
|
void MainWindow::ChangeCollectionFilterMode(QAction *action) {
|
||||||
@@ -3283,7 +3261,7 @@ void MainWindow::LoveButtonVisibilityChanged(const bool value) {
|
|||||||
else
|
else
|
||||||
ui_->widget_love->hide();
|
ui_->widget_love->hide();
|
||||||
|
|
||||||
tray_icon_->LoveVisibilityChanged(value);
|
systemtrayicon_->LoveVisibilityChanged(value);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -3306,7 +3284,7 @@ void MainWindow::Love() {
|
|||||||
app_->scrobbler()->Love();
|
app_->scrobbler()->Love();
|
||||||
ui_->button_love->setEnabled(false);
|
ui_->button_love->setEnabled(false);
|
||||||
ui_->action_love->setEnabled(false);
|
ui_->action_love->setEnabled(false);
|
||||||
tray_icon_->LoveStateChanged(false);
|
systemtrayicon_->LoveStateChanged(false);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -3321,10 +3299,10 @@ void MainWindow::PlaylistDelete() {
|
|||||||
for (const QModelIndex &proxy_idx : proxy_indexes) {
|
for (const QModelIndex &proxy_idx : proxy_indexes) {
|
||||||
QModelIndex source_idx = app_->playlist_manager()->current()->filter()->mapToSource(proxy_idx);
|
QModelIndex source_idx = app_->playlist_manager()->current()->filter()->mapToSource(proxy_idx);
|
||||||
PlaylistItemPtr item = app_->playlist_manager()->current()->item_at(source_idx.row());
|
PlaylistItemPtr item = app_->playlist_manager()->current()->item_at(source_idx.row());
|
||||||
if (!item || !item->Metadata().url().isLocalFile()) continue;
|
if (!item || !item->EffectiveMetadata().url().isLocalFile()) continue;
|
||||||
QString filename = item->Metadata().url().toLocalFile();
|
QString filename = item->EffectiveMetadata().url().toLocalFile();
|
||||||
if (files.contains(filename)) continue;
|
if (files.contains(filename)) continue;
|
||||||
selected_songs << item->Metadata();
|
selected_songs << item->EffectiveMetadata();
|
||||||
files << filename;
|
files << filename;
|
||||||
if (item == app_->player()->GetCurrentItem()) is_current_item = true;
|
if (item == app_->player()->GetCurrentItem()) is_current_item = true;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -73,9 +73,7 @@ class CollectionViewContainer;
|
|||||||
class CollectionFilter;
|
class CollectionFilter;
|
||||||
class AlbumCoverChoiceController;
|
class AlbumCoverChoiceController;
|
||||||
class CommandlineOptions;
|
class CommandlineOptions;
|
||||||
#ifndef Q_OS_WIN32
|
|
||||||
class DeviceViewContainer;
|
class DeviceViewContainer;
|
||||||
#endif
|
|
||||||
class EditTagDialog;
|
class EditTagDialog;
|
||||||
class Equalizer;
|
class Equalizer;
|
||||||
class ErrorDialog;
|
class ErrorDialog;
|
||||||
@@ -113,7 +111,7 @@ class MainWindow : public QMainWindow, public PlatformInterface {
|
|||||||
|
|
||||||
public:
|
public:
|
||||||
explicit MainWindow(Application *app,
|
explicit MainWindow(Application *app,
|
||||||
SharedPtr<SystemTrayIcon> tray_icon,
|
SharedPtr<SystemTrayIcon> systemtrayicon,
|
||||||
OSDBase *osd,
|
OSDBase *osd,
|
||||||
#ifdef HAVE_DISCORD_RPC
|
#ifdef HAVE_DISCORD_RPC
|
||||||
discord::RichPresence *discord_rich_presence,
|
discord::RichPresence *discord_rich_presence,
|
||||||
@@ -312,7 +310,7 @@ class MainWindow : public QMainWindow, public PlatformInterface {
|
|||||||
#endif
|
#endif
|
||||||
|
|
||||||
Application *app_;
|
Application *app_;
|
||||||
SharedPtr<SystemTrayIcon> tray_icon_;
|
SharedPtr<SystemTrayIcon> systemtrayicon_;
|
||||||
OSDBase *osd_;
|
OSDBase *osd_;
|
||||||
#ifdef HAVE_DISCORD_RPC
|
#ifdef HAVE_DISCORD_RPC
|
||||||
discord::RichPresence *discord_rich_presence_;
|
discord::RichPresence *discord_rich_presence_;
|
||||||
@@ -327,9 +325,7 @@ class MainWindow : public QMainWindow, public PlatformInterface {
|
|||||||
ContextView *context_view_;
|
ContextView *context_view_;
|
||||||
CollectionViewContainer *collection_view_;
|
CollectionViewContainer *collection_view_;
|
||||||
FileView *file_view_;
|
FileView *file_view_;
|
||||||
#ifndef Q_OS_WIN32
|
|
||||||
DeviceViewContainer *device_view_;
|
DeviceViewContainer *device_view_;
|
||||||
#endif
|
|
||||||
PlaylistListContainer *playlist_list_;
|
PlaylistListContainer *playlist_list_;
|
||||||
QueueView *queue_view_;
|
QueueView *queue_view_;
|
||||||
|
|
||||||
@@ -380,9 +376,7 @@ class MainWindow : public QMainWindow, public PlatformInterface {
|
|||||||
QAction *playlist_move_to_collection_;
|
QAction *playlist_move_to_collection_;
|
||||||
QAction *playlist_open_in_browser_;
|
QAction *playlist_open_in_browser_;
|
||||||
QAction *playlist_organize_;
|
QAction *playlist_organize_;
|
||||||
#ifndef Q_OS_WIN32
|
|
||||||
QAction *playlist_copy_to_device_;
|
QAction *playlist_copy_to_device_;
|
||||||
#endif
|
|
||||||
QAction *playlist_delete_;
|
QAction *playlist_delete_;
|
||||||
QAction *playlist_queue_;
|
QAction *playlist_queue_;
|
||||||
QAction *playlist_queue_play_next_;
|
QAction *playlist_queue_play_next_;
|
||||||
|
|||||||
@@ -13,10 +13,6 @@
|
|||||||
<property name="windowTitle">
|
<property name="windowTitle">
|
||||||
<string>Strawberry Music Player</string>
|
<string>Strawberry Music Player</string>
|
||||||
</property>
|
</property>
|
||||||
<property name="windowIcon">
|
|
||||||
<iconset resource="../../data/icons.qrc">
|
|
||||||
<normaloff>:/icons/128x128/strawberry.png</normaloff>:/icons/128x128/strawberry.png</iconset>
|
|
||||||
</property>
|
|
||||||
<widget class="QWidget" name="centralWidget">
|
<widget class="QWidget" name="centralWidget">
|
||||||
<layout class="QVBoxLayout" name="layout_centralWidget">
|
<layout class="QVBoxLayout" name="layout_centralWidget">
|
||||||
<property name="spacing">
|
<property name="spacing">
|
||||||
@@ -37,7 +33,7 @@
|
|||||||
<item>
|
<item>
|
||||||
<widget class="QSplitter" name="splitter">
|
<widget class="QSplitter" name="splitter">
|
||||||
<property name="orientation">
|
<property name="orientation">
|
||||||
<enum>Qt::Orientation::Horizontal</enum>
|
<enum>Qt::Horizontal</enum>
|
||||||
</property>
|
</property>
|
||||||
<widget class="QWidget" name="sidebar_layout">
|
<widget class="QWidget" name="sidebar_layout">
|
||||||
<layout class="QVBoxLayout" name="layout_left">
|
<layout class="QVBoxLayout" name="layout_left">
|
||||||
@@ -77,7 +73,7 @@
|
|||||||
<item>
|
<item>
|
||||||
<widget class="Line" name="line_6">
|
<widget class="Line" name="line_6">
|
||||||
<property name="orientation">
|
<property name="orientation">
|
||||||
<enum>Qt::Orientation::Vertical</enum>
|
<enum>Qt::Vertical</enum>
|
||||||
</property>
|
</property>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
@@ -102,7 +98,7 @@
|
|||||||
<item>
|
<item>
|
||||||
<widget class="QFrame" name="player_controls">
|
<widget class="QFrame" name="player_controls">
|
||||||
<property name="frameShape">
|
<property name="frameShape">
|
||||||
<enum>QFrame::Shape::NoFrame</enum>
|
<enum>QFrame::NoFrame</enum>
|
||||||
</property>
|
</property>
|
||||||
<layout class="QHBoxLayout" name="layout_player_controls">
|
<layout class="QHBoxLayout" name="layout_player_controls">
|
||||||
<property name="spacing">
|
<property name="spacing">
|
||||||
@@ -167,7 +163,7 @@
|
|||||||
</size>
|
</size>
|
||||||
</property>
|
</property>
|
||||||
<property name="popupMode">
|
<property name="popupMode">
|
||||||
<enum>QToolButton::ToolButtonPopupMode::MenuButtonPopup</enum>
|
<enum>QToolButton::MenuButtonPopup</enum>
|
||||||
</property>
|
</property>
|
||||||
<property name="autoRaise">
|
<property name="autoRaise">
|
||||||
<bool>true</bool>
|
<bool>true</bool>
|
||||||
@@ -211,7 +207,7 @@
|
|||||||
<item>
|
<item>
|
||||||
<widget class="Line" name="line_love">
|
<widget class="Line" name="line_love">
|
||||||
<property name="orientation">
|
<property name="orientation">
|
||||||
<enum>Qt::Orientation::Vertical</enum>
|
<enum>Qt::Vertical</enum>
|
||||||
</property>
|
</property>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
@@ -237,7 +233,7 @@
|
|||||||
<item>
|
<item>
|
||||||
<widget class="Line" name="line_buttons">
|
<widget class="Line" name="line_buttons">
|
||||||
<property name="orientation">
|
<property name="orientation">
|
||||||
<enum>Qt::Orientation::Vertical</enum>
|
<enum>Qt::Vertical</enum>
|
||||||
</property>
|
</property>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
@@ -260,10 +256,10 @@
|
|||||||
<item>
|
<item>
|
||||||
<spacer name="horizontalSpacer">
|
<spacer name="horizontalSpacer">
|
||||||
<property name="orientation">
|
<property name="orientation">
|
||||||
<enum>Qt::Orientation::Horizontal</enum>
|
<enum>Qt::Horizontal</enum>
|
||||||
</property>
|
</property>
|
||||||
<property name="sizeType">
|
<property name="sizeType">
|
||||||
<enum>QSizePolicy::Policy::Expanding</enum>
|
<enum>QSizePolicy::Expanding</enum>
|
||||||
</property>
|
</property>
|
||||||
<property name="sizeHint" stdset="0">
|
<property name="sizeHint" stdset="0">
|
||||||
<size>
|
<size>
|
||||||
@@ -276,7 +272,7 @@
|
|||||||
<item>
|
<item>
|
||||||
<widget class="Line" name="line_volume">
|
<widget class="Line" name="line_volume">
|
||||||
<property name="orientation">
|
<property name="orientation">
|
||||||
<enum>Qt::Orientation::Vertical</enum>
|
<enum>Qt::Vertical</enum>
|
||||||
</property>
|
</property>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
@@ -292,7 +288,7 @@
|
|||||||
<number>100</number>
|
<number>100</number>
|
||||||
</property>
|
</property>
|
||||||
<property name="orientation">
|
<property name="orientation">
|
||||||
<enum>Qt::Orientation::Horizontal</enum>
|
<enum>Qt::Horizontal</enum>
|
||||||
</property>
|
</property>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
@@ -326,7 +322,7 @@
|
|||||||
<item>
|
<item>
|
||||||
<widget class="Line" name="status_bar_line">
|
<widget class="Line" name="status_bar_line">
|
||||||
<property name="orientation">
|
<property name="orientation">
|
||||||
<enum>Qt::Orientation::Horizontal</enum>
|
<enum>Qt::Horizontal</enum>
|
||||||
</property>
|
</property>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
@@ -380,7 +376,7 @@
|
|||||||
<item>
|
<item>
|
||||||
<widget class="QLabel" name="playlist_summary">
|
<widget class="QLabel" name="playlist_summary">
|
||||||
<property name="alignment">
|
<property name="alignment">
|
||||||
<set>Qt::AlignmentFlag::AlignRight|Qt::AlignmentFlag::AlignTrailing|Qt::AlignmentFlag::AlignVCenter</set>
|
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
|
||||||
</property>
|
</property>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
@@ -391,7 +387,7 @@
|
|||||||
<item>
|
<item>
|
||||||
<widget class="Line" name="line_5">
|
<widget class="Line" name="line_5">
|
||||||
<property name="orientation">
|
<property name="orientation">
|
||||||
<enum>Qt::Orientation::Vertical</enum>
|
<enum>Qt::Vertical</enum>
|
||||||
</property>
|
</property>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
@@ -401,7 +397,7 @@
|
|||||||
<item>
|
<item>
|
||||||
<widget class="Line" name="line_2">
|
<widget class="Line" name="line_2">
|
||||||
<property name="orientation">
|
<property name="orientation">
|
||||||
<enum>Qt::Orientation::Vertical</enum>
|
<enum>Qt::Vertical</enum>
|
||||||
</property>
|
</property>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
@@ -580,7 +576,7 @@
|
|||||||
<string>Ctrl+Q</string>
|
<string>Ctrl+Q</string>
|
||||||
</property>
|
</property>
|
||||||
<property name="menuRole">
|
<property name="menuRole">
|
||||||
<enum>QAction::MenuRole::QuitRole</enum>
|
<enum>QAction::QuitRole</enum>
|
||||||
</property>
|
</property>
|
||||||
</action>
|
</action>
|
||||||
<action name="action_stop_after_this_track">
|
<action name="action_stop_after_this_track">
|
||||||
@@ -644,7 +640,7 @@
|
|||||||
<string>Ctrl+P</string>
|
<string>Ctrl+P</string>
|
||||||
</property>
|
</property>
|
||||||
<property name="menuRole">
|
<property name="menuRole">
|
||||||
<enum>QAction::MenuRole::PreferencesRole</enum>
|
<enum>QAction::PreferencesRole</enum>
|
||||||
</property>
|
</property>
|
||||||
</action>
|
</action>
|
||||||
<action name="action_about_strawberry">
|
<action name="action_about_strawberry">
|
||||||
@@ -659,7 +655,7 @@
|
|||||||
<string>F1</string>
|
<string>F1</string>
|
||||||
</property>
|
</property>
|
||||||
<property name="menuRole">
|
<property name="menuRole">
|
||||||
<enum>QAction::MenuRole::AboutRole</enum>
|
<enum>QAction::AboutRole</enum>
|
||||||
</property>
|
</property>
|
||||||
</action>
|
</action>
|
||||||
<action name="action_shuffle">
|
<action name="action_shuffle">
|
||||||
@@ -785,7 +781,7 @@
|
|||||||
<string>About &Qt</string>
|
<string>About &Qt</string>
|
||||||
</property>
|
</property>
|
||||||
<property name="menuRole">
|
<property name="menuRole">
|
||||||
<enum>QAction::MenuRole::AboutQtRole</enum>
|
<enum>QAction::AboutQtRole</enum>
|
||||||
</property>
|
</property>
|
||||||
</action>
|
</action>
|
||||||
<action name="action_mute">
|
<action name="action_mute">
|
||||||
|
|||||||
@@ -43,29 +43,29 @@ NetworkAccessManager::NetworkAccessManager(QObject *parent)
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
QNetworkReply *NetworkAccessManager::createRequest(Operation op, const QNetworkRequest &request, QIODevice *outgoingData) {
|
QNetworkReply *NetworkAccessManager::createRequest(Operation op, const QNetworkRequest &network_request, QIODevice *outgoing_data) {
|
||||||
|
|
||||||
QByteArray user_agent;
|
QByteArray user_agent;
|
||||||
if (request.hasRawHeader("User-Agent")) {
|
if (network_request.hasRawHeader("User-Agent")) {
|
||||||
user_agent = request.header(QNetworkRequest::UserAgentHeader).toByteArray();
|
user_agent = network_request.header(QNetworkRequest::UserAgentHeader).toByteArray();
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
user_agent = QStringLiteral("%1 %2").arg(QCoreApplication::applicationName(), QCoreApplication::applicationVersion()).toUtf8();
|
user_agent = QStringLiteral("%1 %2").arg(QCoreApplication::applicationName(), QCoreApplication::applicationVersion()).toUtf8();
|
||||||
}
|
}
|
||||||
|
|
||||||
QNetworkRequest new_request(request);
|
QNetworkRequest new_network_request(network_request);
|
||||||
new_request.setAttribute(QNetworkRequest::RedirectPolicyAttribute, QNetworkRequest::NoLessSafeRedirectPolicy);
|
new_network_request.setAttribute(QNetworkRequest::RedirectPolicyAttribute, QNetworkRequest::NoLessSafeRedirectPolicy);
|
||||||
new_request.setHeader(QNetworkRequest::UserAgentHeader, user_agent);
|
new_network_request.setHeader(QNetworkRequest::UserAgentHeader, user_agent);
|
||||||
|
|
||||||
if (op == QNetworkAccessManager::PostOperation && !new_request.header(QNetworkRequest::ContentTypeHeader).isValid()) {
|
if (op == QNetworkAccessManager::PostOperation && !new_network_request.header(QNetworkRequest::ContentTypeHeader).isValid()) {
|
||||||
new_request.setHeader(QNetworkRequest::ContentTypeHeader, u"application/x-www-form-urlencoded"_s);
|
new_network_request.setHeader(QNetworkRequest::ContentTypeHeader, u"application/x-www-form-urlencoded"_s);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Prefer the cache unless the caller has changed the setting already
|
// Prefer the cache unless the caller has changed the setting already
|
||||||
if (request.attribute(QNetworkRequest::CacheLoadControlAttribute).toInt() == QNetworkRequest::PreferNetwork) {
|
if (!network_request.attribute(QNetworkRequest::CacheLoadControlAttribute).isValid()) {
|
||||||
new_request.setAttribute(QNetworkRequest::CacheLoadControlAttribute, QNetworkRequest::PreferCache);
|
new_network_request.setAttribute(QNetworkRequest::CacheLoadControlAttribute, QNetworkRequest::PreferCache);
|
||||||
}
|
}
|
||||||
|
|
||||||
return QNetworkAccessManager::createRequest(op, new_request, outgoingData);
|
return QNetworkAccessManager::createRequest(op, new_network_request, outgoing_data);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -38,7 +38,7 @@ class NetworkAccessManager : public QNetworkAccessManager {
|
|||||||
explicit NetworkAccessManager(QObject *parent = nullptr);
|
explicit NetworkAccessManager(QObject *parent = nullptr);
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
QNetworkReply *createRequest(Operation op, const QNetworkRequest &request, QIODevice *outgoingData) override;
|
QNetworkReply *createRequest(Operation op, const QNetworkRequest &network_request, QIODevice *outgoing_data) override;
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif // NETWORKACCESSMANAGER_H
|
#endif // NETWORKACCESSMANAGER_H
|
||||||
|
|||||||
@@ -288,10 +288,10 @@ void Player::HandleLoadResult(const UrlHandler::LoadResult &result) {
|
|||||||
bool is_current = false;
|
bool is_current = false;
|
||||||
bool is_next = false;
|
bool is_next = false;
|
||||||
|
|
||||||
if (result.media_url_ == current_item->Url()) {
|
if (result.media_url_ == current_item->OriginalUrl()) {
|
||||||
is_current = true;
|
is_current = true;
|
||||||
}
|
}
|
||||||
else if (has_next_row && next_item->Url() == result.media_url_) {
|
else if (has_next_row && next_item->OriginalUrl() == result.media_url_) {
|
||||||
is_next = true;
|
is_next = true;
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
@@ -316,8 +316,8 @@ void Player::HandleLoadResult(const UrlHandler::LoadResult &result) {
|
|||||||
qLog(Debug) << "URL handler for" << result.media_url_ << "returned" << result.stream_url_;
|
qLog(Debug) << "URL handler for" << result.media_url_ << "returned" << result.stream_url_;
|
||||||
|
|
||||||
Song song;
|
Song song;
|
||||||
if (is_current) song = current_item->Metadata();
|
if (is_current) song = current_item->EffectiveMetadata();
|
||||||
else if (is_next) song = next_item->Metadata();
|
else if (is_next) song = next_item->EffectiveMetadata();
|
||||||
|
|
||||||
bool update = false;
|
bool update = false;
|
||||||
|
|
||||||
@@ -325,7 +325,7 @@ void Player::HandleLoadResult(const UrlHandler::LoadResult &result) {
|
|||||||
if (
|
if (
|
||||||
(result.stream_url_.isValid())
|
(result.stream_url_.isValid())
|
||||||
&&
|
&&
|
||||||
(result.stream_url_ != song.url())
|
(result.stream_url_ != song.effective_url())
|
||||||
)
|
)
|
||||||
{
|
{
|
||||||
song.set_stream_url(result.stream_url_);
|
song.set_stream_url(result.stream_url_);
|
||||||
@@ -371,14 +371,14 @@ void Player::HandleLoadResult(const UrlHandler::LoadResult &result) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (is_current) {
|
if (is_current) {
|
||||||
qLog(Debug) << "Playing song" << current_item->Metadata().title() << result.stream_url_ << "position" << play_offset_nanosec_;
|
qLog(Debug) << "Playing song" << current_item->EffectiveMetadata().title() << result.stream_url_ << "position" << play_offset_nanosec_;
|
||||||
engine_->Play(result.media_url_, result.stream_url_, pause_, stream_change_type_, song.has_cue(), static_cast<quint64>(song.beginning_nanosec()), song.end_nanosec(), play_offset_nanosec_, song.ebur128_integrated_loudness_lufs());
|
engine_->Play(result.media_url_, result.stream_url_, pause_, stream_change_type_, song.has_cue(), static_cast<quint64>(song.beginning_nanosec()), song.end_nanosec(), play_offset_nanosec_, song.ebur128_integrated_loudness_lufs());
|
||||||
current_item_ = current_item;
|
current_item_ = current_item;
|
||||||
play_offset_nanosec_ = 0;
|
play_offset_nanosec_ = 0;
|
||||||
}
|
}
|
||||||
else if (is_next && !current_item->Metadata().is_module_music()) {
|
else if (is_next && !current_item->EffectiveMetadata().is_module_music()) {
|
||||||
qLog(Debug) << "Preloading next song" << next_item->Metadata().title() << result.stream_url_;
|
qLog(Debug) << "Preloading next song" << next_item->EffectiveMetadata().title() << result.stream_url_;
|
||||||
engine_->StartPreloading(next_item->Url(), result.stream_url_, song.has_cue(), song.beginning_nanosec(), song.end_nanosec());
|
engine_->StartPreloading(next_item->OriginalUrl(), result.stream_url_, song.has_cue(), song.beginning_nanosec(), song.end_nanosec());
|
||||||
}
|
}
|
||||||
|
|
||||||
break;
|
break;
|
||||||
@@ -504,8 +504,8 @@ bool Player::HandleStopAfter(const Playlist::AutoScroll autoscroll) {
|
|||||||
|
|
||||||
void Player::TrackEnded() {
|
void Player::TrackEnded() {
|
||||||
|
|
||||||
if (current_item_ && current_item_->IsLocalCollectionItem() && current_item_->Metadata().id() != -1) {
|
if (current_item_ && current_item_->IsLocalCollectionItem() && current_item_->EffectiveMetadata().id() != -1) {
|
||||||
playlist_manager_->collection_backend()->IncrementPlayCountAsync(current_item_->Metadata().id());
|
playlist_manager_->collection_backend()->IncrementPlayCountAsync(current_item_->EffectiveMetadata().id());
|
||||||
}
|
}
|
||||||
|
|
||||||
if (HandleStopAfter(Playlist::AutoScroll::Maybe)) return;
|
if (HandleStopAfter(Playlist::AutoScroll::Maybe)) return;
|
||||||
@@ -554,7 +554,7 @@ void Player::PlayPause(const quint64 offset_nanosec, const Playlist::AutoScroll
|
|||||||
void Player::UnPause() {
|
void Player::UnPause() {
|
||||||
|
|
||||||
if (current_item_ && pause_time_.isValid()) {
|
if (current_item_ && pause_time_.isValid()) {
|
||||||
const Song &song = current_item_->Metadata();
|
const Song &song = current_item_->EffectiveMetadata();
|
||||||
if (url_handlers_->CanHandle(song.url()) && song.stream_url_can_expire()) {
|
if (url_handlers_->CanHandle(song.url()) && song.stream_url_can_expire()) {
|
||||||
const qint64 time = QDateTime::currentSecsSinceEpoch() - pause_time_.toSecsSinceEpoch();
|
const qint64 time = QDateTime::currentSecsSinceEpoch() - pause_time_.toSecsSinceEpoch();
|
||||||
if (time >= 30) { // Stream URL might be expired.
|
if (time >= 30) { // Stream URL might be expired.
|
||||||
@@ -745,7 +745,7 @@ void Player::PlayAt(const int index, const bool pause, const quint64 offset_nano
|
|||||||
Q_EMIT TrackSkipped(current_item_);
|
Q_EMIT TrackSkipped(current_item_);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (current_item_ && playlist_manager_->active()->has_item_at(index) && current_item_->Metadata().IsOnSameAlbum(playlist_manager_->active()->item_at(index)->Metadata())) {
|
if (current_item_ && playlist_manager_->active()->has_item_at(index) && current_item_->EffectiveMetadata().IsOnSameAlbum(playlist_manager_->active()->item_at(index)->EffectiveMetadata())) {
|
||||||
change |= EngineBase::TrackChangeType::SameAlbum;
|
change |= EngineBase::TrackChangeType::SameAlbum;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -758,7 +758,7 @@ void Player::PlayAt(const int index, const bool pause, const quint64 offset_nano
|
|||||||
}
|
}
|
||||||
|
|
||||||
current_item_ = playlist_manager_->active()->current_item();
|
current_item_ = playlist_manager_->active()->current_item();
|
||||||
const QUrl url = current_item_->StreamUrl();
|
const QUrl url = current_item_->EffectiveUrl();
|
||||||
|
|
||||||
if (url_handlers_->CanHandle(url)) {
|
if (url_handlers_->CanHandle(url)) {
|
||||||
// It's already loading
|
// It's already loading
|
||||||
@@ -773,8 +773,8 @@ void Player::PlayAt(const int index, const bool pause, const quint64 offset_nano
|
|||||||
HandleLoadResult(url_handler->StartLoading(url));
|
HandleLoadResult(url_handler->StartLoading(url));
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
qLog(Debug) << "Playing song" << current_item_->Metadata().title() << url << "position" << offset_nanosec;
|
qLog(Debug) << "Playing song" << current_item_->EffectiveMetadata().title() << url << "position" << offset_nanosec;
|
||||||
engine_->Play(current_item_->Url(), url, pause, change, current_item_->Metadata().has_cue(), static_cast<quint64>(current_item_->effective_beginning_nanosec()), current_item_->effective_end_nanosec(), offset_nanosec, current_item_->effective_ebur128_integrated_loudness_lufs());
|
engine_->Play(current_item_->OriginalUrl(), url, pause, change, current_item_->EffectiveMetadata().has_cue(), static_cast<quint64>(current_item_->effective_beginning_nanosec()), current_item_->effective_end_nanosec(), offset_nanosec, current_item_->EffectiveMetadata().ebur128_integrated_loudness_lufs());
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -823,8 +823,8 @@ void Player::EngineMetadataReceived(const EngineMetadata &engine_metadata) {
|
|||||||
const int current_row = playlist_manager_->active()->current_row();
|
const int current_row = playlist_manager_->active()->current_row();
|
||||||
if (current_row != -1) {
|
if (current_row != -1) {
|
||||||
PlaylistItemPtr item = playlist_manager_->active()->current_item();
|
PlaylistItemPtr item = playlist_manager_->active()->current_item();
|
||||||
if (item && engine_metadata.media_url == item->Url()) {
|
if (item && engine_metadata.media_url == item->OriginalUrl()) {
|
||||||
Song song = item->Metadata();
|
Song song = item->EffectiveMetadata();
|
||||||
song.MergeFromEngineMetadata(engine_metadata);
|
song.MergeFromEngineMetadata(engine_metadata);
|
||||||
playlist_manager_->active()->UpdateItemMetadata(current_row, item, song, true);
|
playlist_manager_->active()->UpdateItemMetadata(current_row, item, song, true);
|
||||||
return;
|
return;
|
||||||
@@ -836,8 +836,8 @@ void Player::EngineMetadataReceived(const EngineMetadata &engine_metadata) {
|
|||||||
const int next_row = playlist_manager_->active()->next_row();
|
const int next_row = playlist_manager_->active()->next_row();
|
||||||
if (next_row != -1) {
|
if (next_row != -1) {
|
||||||
PlaylistItemPtr next_item = playlist_manager_->active()->item_at(next_row);
|
PlaylistItemPtr next_item = playlist_manager_->active()->item_at(next_row);
|
||||||
if (engine_metadata.media_url == next_item->Url()) {
|
if (engine_metadata.media_url == next_item->OriginalUrl()) {
|
||||||
Song song = next_item->Metadata();
|
Song song = next_item->EffectiveMetadata();
|
||||||
song.MergeFromEngineMetadata(engine_metadata);
|
song.MergeFromEngineMetadata(engine_metadata);
|
||||||
playlist_manager_->active()->UpdateItemMetadata(next_row, next_item, song, true);
|
playlist_manager_->active()->UpdateItemMetadata(next_row, next_item, song, true);
|
||||||
}
|
}
|
||||||
@@ -905,11 +905,11 @@ void Player::PlayWithPause(const quint64 offset_nanosec) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void Player::ShowOSD() {
|
void Player::ShowOSD() {
|
||||||
if (current_item_) Q_EMIT ForceShowOSD(current_item_->Metadata(), false);
|
if (current_item_) Q_EMIT ForceShowOSD(current_item_->EffectiveMetadata(), false);
|
||||||
}
|
}
|
||||||
|
|
||||||
void Player::TogglePrettyOSD() {
|
void Player::TogglePrettyOSD() {
|
||||||
if (current_item_) Q_EMIT ForceShowOSD(current_item_->Metadata(), true);
|
if (current_item_) Q_EMIT ForceShowOSD(current_item_->EffectiveMetadata(), true);
|
||||||
}
|
}
|
||||||
|
|
||||||
void Player::TrackAboutToEnd() {
|
void Player::TrackAboutToEnd() {
|
||||||
@@ -932,7 +932,7 @@ void Player::TrackAboutToEnd() {
|
|||||||
|
|
||||||
// If the next track is on the same album (or same cue file),
|
// If the next track is on the same album (or same cue file),
|
||||||
// and the user doesn't want to crossfade between tracks on the same album, then don't do this automatic crossfading.
|
// and the user doesn't want to crossfade between tracks on the same album, then don't do this automatic crossfading.
|
||||||
if (engine_->crossfade_same_album() || !has_next_row || !next_item || !current_item_->Metadata().IsOnSameAlbum(next_item->Metadata())) {
|
if (engine_->crossfade_same_album() || !has_next_row || !next_item || !current_item_->EffectiveMetadata().IsOnSameAlbum(next_item->EffectiveMetadata())) {
|
||||||
TrackEnded();
|
TrackEnded();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -941,7 +941,7 @@ void Player::TrackAboutToEnd() {
|
|||||||
// Crossfade is off, so start preloading the next track, so we don't get a gap between songs.
|
// Crossfade is off, so start preloading the next track, so we don't get a gap between songs.
|
||||||
if (!has_next_row || !next_item) return;
|
if (!has_next_row || !next_item) return;
|
||||||
|
|
||||||
QUrl url = next_item->StreamUrl();
|
QUrl url = next_item->EffectiveUrl();
|
||||||
|
|
||||||
// Get the actual track URL rather than the stream URL.
|
// Get the actual track URL rather than the stream URL.
|
||||||
if (url_handlers_->CanHandle(url)) {
|
if (url_handlers_->CanHandle(url)) {
|
||||||
@@ -961,20 +961,20 @@ void Player::TrackAboutToEnd() {
|
|||||||
case UrlHandler::LoadResult::Type::TrackAvailable:
|
case UrlHandler::LoadResult::Type::TrackAvailable:
|
||||||
qLog(Debug) << "URL handler for" << result.media_url_ << "returned" << result.stream_url_;
|
qLog(Debug) << "URL handler for" << result.media_url_ << "returned" << result.stream_url_;
|
||||||
url = result.stream_url_;
|
url = result.stream_url_;
|
||||||
Song song = next_item->Metadata();
|
Song song = next_item->EffectiveMetadata();
|
||||||
song.set_stream_url(url);
|
song.set_stream_url(url);
|
||||||
next_item->SetTemporaryMetadata(song);
|
next_item->SetStreamMetadata(song);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Preloading any format while currently playing module music is broken in GStreamer.
|
// Preloading any format while currently playing module music is broken in GStreamer.
|
||||||
// See: https://gitlab.freedesktop.org/gstreamer/gstreamer/-/issues/769
|
// See: https://gitlab.freedesktop.org/gstreamer/gstreamer/-/issues/769
|
||||||
if (current_item_ && current_item_->Metadata().is_module_music()) {
|
if (current_item_ && current_item_->EffectiveMetadata().is_module_music()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
engine_->StartPreloading(next_item->Url(), url, next_item->Metadata().has_cue(), next_item->effective_beginning_nanosec(), next_item->effective_end_nanosec());
|
engine_->StartPreloading(next_item->OriginalUrl(), url, next_item->EffectiveMetadata().has_cue(), next_item->effective_beginning_nanosec(), next_item->effective_end_nanosec());
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
* Strawberry Music Player
|
* Strawberry Music Player
|
||||||
* This file was part of Clementine.
|
* This file was part of Clementine.
|
||||||
* Copyright 2010, David Sansome <me@davidsansome.com>
|
* Copyright 2010, David Sansome <me@davidsansome.com>
|
||||||
* Copyright 2018-2024, Jonas Kvinge <jonas@jkvinge.net>
|
* Copyright 2018-2025, Jonas Kvinge <jonas@jkvinge.net>
|
||||||
*
|
*
|
||||||
* Strawberry is free software: you can redistribute it and/or modify
|
* Strawberry is free software: you can redistribute it and/or modify
|
||||||
* it under the terms of the GNU General Public License as published by
|
* it under the terms of the GNU General Public License as published by
|
||||||
@@ -68,9 +68,13 @@
|
|||||||
using namespace Qt::Literals::StringLiterals;
|
using namespace Qt::Literals::StringLiterals;
|
||||||
|
|
||||||
const QStringList Song::kColumns = QStringList() << u"title"_s
|
const QStringList Song::kColumns = QStringList() << u"title"_s
|
||||||
|
<< u"titlesort"_s
|
||||||
<< u"album"_s
|
<< u"album"_s
|
||||||
|
<< u"albumsort"_s
|
||||||
<< u"artist"_s
|
<< u"artist"_s
|
||||||
|
<< u"artistsort"_s
|
||||||
<< u"albumartist"_s
|
<< u"albumartist"_s
|
||||||
|
<< u"albumartistsort"_s
|
||||||
<< u"track"_s
|
<< u"track"_s
|
||||||
<< u"disc"_s
|
<< u"disc"_s
|
||||||
<< u"year"_s
|
<< u"year"_s
|
||||||
@@ -78,7 +82,9 @@ const QStringList Song::kColumns = QStringList() << u"title"_s
|
|||||||
<< u"genre"_s
|
<< u"genre"_s
|
||||||
<< u"compilation"_s
|
<< u"compilation"_s
|
||||||
<< u"composer"_s
|
<< u"composer"_s
|
||||||
|
<< u"composersort"_s
|
||||||
<< u"performer"_s
|
<< u"performer"_s
|
||||||
|
<< u"performersort"_s
|
||||||
<< u"grouping"_s
|
<< u"grouping"_s
|
||||||
<< u"comment"_s
|
<< u"comment"_s
|
||||||
<< u"lyrics"_s
|
<< u"lyrics"_s
|
||||||
@@ -126,6 +132,9 @@ const QStringList Song::kColumns = QStringList() << u"title"_s
|
|||||||
<< u"cue_path"_s
|
<< u"cue_path"_s
|
||||||
|
|
||||||
<< u"rating"_s
|
<< u"rating"_s
|
||||||
|
<< u"bpm"_s
|
||||||
|
<< u"mood"_s
|
||||||
|
<< u"initial_key"_s
|
||||||
|
|
||||||
<< u"acoustid_id"_s
|
<< u"acoustid_id"_s
|
||||||
<< u"acoustid_fingerprint"_s
|
<< u"acoustid_fingerprint"_s
|
||||||
@@ -261,9 +270,13 @@ struct Song::Private : public QSharedData {
|
|||||||
bool valid_;
|
bool valid_;
|
||||||
|
|
||||||
QString title_;
|
QString title_;
|
||||||
|
QString titlesort_;
|
||||||
QString album_;
|
QString album_;
|
||||||
|
QString albumsort_;
|
||||||
QString artist_;
|
QString artist_;
|
||||||
|
QString artistsort_;
|
||||||
QString albumartist_;
|
QString albumartist_;
|
||||||
|
QString albumartistsort_;
|
||||||
int track_;
|
int track_;
|
||||||
int disc_;
|
int disc_;
|
||||||
int year_;
|
int year_;
|
||||||
@@ -271,7 +284,9 @@ struct Song::Private : public QSharedData {
|
|||||||
QString genre_;
|
QString genre_;
|
||||||
bool compilation_; // From the file tag
|
bool compilation_; // From the file tag
|
||||||
QString composer_;
|
QString composer_;
|
||||||
|
QString composersort_;
|
||||||
QString performer_;
|
QString performer_;
|
||||||
|
QString performersort_;
|
||||||
QString grouping_;
|
QString grouping_;
|
||||||
QString comment_;
|
QString comment_;
|
||||||
QString lyrics_;
|
QString lyrics_;
|
||||||
@@ -316,6 +331,9 @@ struct Song::Private : public QSharedData {
|
|||||||
QString cue_path_; // If the song has a CUE, this contains it's path.
|
QString cue_path_; // If the song has a CUE, this contains it's path.
|
||||||
|
|
||||||
float rating_; // Database rating, initial rating read from tag.
|
float rating_; // Database rating, initial rating read from tag.
|
||||||
|
float bpm_;
|
||||||
|
QString mood_;
|
||||||
|
QString initial_key_;
|
||||||
|
|
||||||
QString acoustid_id_;
|
QString acoustid_id_;
|
||||||
QString acoustid_fingerprint_;
|
QString acoustid_fingerprint_;
|
||||||
@@ -337,12 +355,7 @@ struct Song::Private : public QSharedData {
|
|||||||
bool init_from_file_; // Whether this song was loaded from a file using taglib.
|
bool init_from_file_; // Whether this song was loaded from a file using taglib.
|
||||||
bool suspicious_tags_; // Whether our encoding guesser thinks these tags might be incorrectly encoded.
|
bool suspicious_tags_; // Whether our encoding guesser thinks these tags might be incorrectly encoded.
|
||||||
|
|
||||||
QString title_sortable_;
|
QUrl stream_url_; // Temporary stream URL set by the URL handler.
|
||||||
QString album_sortable_;
|
|
||||||
QString artist_sortable_;
|
|
||||||
QString albumartist_sortable_;
|
|
||||||
|
|
||||||
QUrl stream_url_; // Temporary stream url set by the URL handler.
|
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -384,6 +397,7 @@ Song::Private::Private(const Source source)
|
|||||||
art_unset_(false),
|
art_unset_(false),
|
||||||
|
|
||||||
rating_(-1),
|
rating_(-1),
|
||||||
|
bpm_(-1),
|
||||||
|
|
||||||
init_from_file_(false),
|
init_from_file_(false),
|
||||||
suspicious_tags_(false)
|
suspicious_tags_(false)
|
||||||
@@ -411,9 +425,13 @@ int Song::id() const { return d->id_; }
|
|||||||
bool Song::is_valid() const { return d->valid_; }
|
bool Song::is_valid() const { return d->valid_; }
|
||||||
|
|
||||||
const QString &Song::title() const { return d->title_; }
|
const QString &Song::title() const { return d->title_; }
|
||||||
|
const QString &Song::titlesort() const { return d->titlesort_; }
|
||||||
const QString &Song::album() const { return d->album_; }
|
const QString &Song::album() const { return d->album_; }
|
||||||
|
const QString &Song::albumsort() const { return d->albumsort_; }
|
||||||
const QString &Song::artist() const { return d->artist_; }
|
const QString &Song::artist() const { return d->artist_; }
|
||||||
|
const QString &Song::artistsort() const { return d->artistsort_; }
|
||||||
const QString &Song::albumartist() const { return d->albumartist_; }
|
const QString &Song::albumartist() const { return d->albumartist_; }
|
||||||
|
const QString &Song::albumartistsort() const { return d->albumartistsort_; }
|
||||||
int Song::track() const { return d->track_; }
|
int Song::track() const { return d->track_; }
|
||||||
int Song::disc() const { return d->disc_; }
|
int Song::disc() const { return d->disc_; }
|
||||||
int Song::year() const { return d->year_; }
|
int Song::year() const { return d->year_; }
|
||||||
@@ -421,7 +439,9 @@ int Song::originalyear() const { return d->originalyear_; }
|
|||||||
const QString &Song::genre() const { return d->genre_; }
|
const QString &Song::genre() const { return d->genre_; }
|
||||||
bool Song::compilation() const { return d->compilation_; }
|
bool Song::compilation() const { return d->compilation_; }
|
||||||
const QString &Song::composer() const { return d->composer_; }
|
const QString &Song::composer() const { return d->composer_; }
|
||||||
|
const QString &Song::composersort() const { return d->composersort_; }
|
||||||
const QString &Song::performer() const { return d->performer_; }
|
const QString &Song::performer() const { return d->performer_; }
|
||||||
|
const QString &Song::performersort() const { return d->performersort_; }
|
||||||
const QString &Song::grouping() const { return d->grouping_; }
|
const QString &Song::grouping() const { return d->grouping_; }
|
||||||
const QString &Song::comment() const { return d->comment_; }
|
const QString &Song::comment() const { return d->comment_; }
|
||||||
const QString &Song::lyrics() const { return d->lyrics_; }
|
const QString &Song::lyrics() const { return d->lyrics_; }
|
||||||
@@ -468,6 +488,9 @@ bool Song::art_unset() const { return d->art_unset_; }
|
|||||||
const QString &Song::cue_path() const { return d->cue_path_; }
|
const QString &Song::cue_path() const { return d->cue_path_; }
|
||||||
|
|
||||||
float Song::rating() const { return d->rating_; }
|
float Song::rating() const { return d->rating_; }
|
||||||
|
float Song::bpm() const { return d->bpm_; }
|
||||||
|
const QString &Song::mood() const { return d->mood_; }
|
||||||
|
const QString &Song::initial_key() const { return d->initial_key_; }
|
||||||
|
|
||||||
const QString &Song::acoustid_id() const { return d->acoustid_id_; }
|
const QString &Song::acoustid_id() const { return d->acoustid_id_; }
|
||||||
const QString &Song::acoustid_fingerprint() const { return d->acoustid_fingerprint_; }
|
const QString &Song::acoustid_fingerprint() const { return d->acoustid_fingerprint_; }
|
||||||
@@ -511,20 +534,19 @@ QString *Song::mutable_musicbrainz_work_id() { return &d->musicbrainz_work_id_;
|
|||||||
|
|
||||||
bool Song::init_from_file() const { return d->init_from_file_; }
|
bool Song::init_from_file() const { return d->init_from_file_; }
|
||||||
|
|
||||||
const QString &Song::title_sortable() const { return d->title_sortable_; }
|
|
||||||
const QString &Song::album_sortable() const { return d->album_sortable_; }
|
|
||||||
const QString &Song::artist_sortable() const { return d->artist_sortable_; }
|
|
||||||
const QString &Song::albumartist_sortable() const { return d->albumartist_sortable_; }
|
|
||||||
|
|
||||||
const QUrl &Song::stream_url() const { return d->stream_url_; }
|
const QUrl &Song::stream_url() const { return d->stream_url_; }
|
||||||
|
|
||||||
void Song::set_id(const int id) { d->id_ = id; }
|
void Song::set_id(const int id) { d->id_ = id; }
|
||||||
void Song::set_valid(const bool v) { d->valid_ = v; }
|
void Song::set_valid(const bool v) { d->valid_ = v; }
|
||||||
|
|
||||||
void Song::set_title(const QString &v) { d->title_sortable_ = sortable(v); d->title_ = v; }
|
void Song::set_title(const QString &v) { d->title_ = v; }
|
||||||
void Song::set_album(const QString &v) { d->album_sortable_ = sortable(v); d->album_ = v; }
|
void Song::set_titlesort(const QString &v) { d->titlesort_ = v; }
|
||||||
void Song::set_artist(const QString &v) { d->artist_sortable_ = sortable(v); d->artist_ = v; }
|
void Song::set_album(const QString &v) { d->album_ = v; }
|
||||||
void Song::set_albumartist(const QString &v) { d->albumartist_sortable_ = sortable(v); d->albumartist_ = v; }
|
void Song::set_albumsort(const QString &v) { d->albumsort_ = v; }
|
||||||
|
void Song::set_artist(const QString &v) { d->artist_ = v; }
|
||||||
|
void Song::set_artistsort(const QString &v) { d->artistsort_ = v; }
|
||||||
|
void Song::set_albumartist(const QString &v) { d->albumartist_ = v; }
|
||||||
|
void Song::set_albumartistsort(const QString &v) { d->albumartistsort_ = v; }
|
||||||
void Song::set_track(const int v) { d->track_ = v; }
|
void Song::set_track(const int v) { d->track_ = v; }
|
||||||
void Song::set_disc(const int v) { d->disc_ = v; }
|
void Song::set_disc(const int v) { d->disc_ = v; }
|
||||||
void Song::set_year(const int v) { d->year_ = v; }
|
void Song::set_year(const int v) { d->year_ = v; }
|
||||||
@@ -532,7 +554,9 @@ void Song::set_originalyear(const int v) { d->originalyear_ = v; }
|
|||||||
void Song::set_genre(const QString &v) { d->genre_ = v; }
|
void Song::set_genre(const QString &v) { d->genre_ = v; }
|
||||||
void Song::set_compilation(const bool v) { d->compilation_ = v; }
|
void Song::set_compilation(const bool v) { d->compilation_ = v; }
|
||||||
void Song::set_composer(const QString &v) { d->composer_ = v; }
|
void Song::set_composer(const QString &v) { d->composer_ = v; }
|
||||||
|
void Song::set_composersort(const QString &v) { d->composersort_ = v; }
|
||||||
void Song::set_performer(const QString &v) { d->performer_ = v; }
|
void Song::set_performer(const QString &v) { d->performer_ = v; }
|
||||||
|
void Song::set_performersort(const QString &v) { d->performersort_ = v; }
|
||||||
void Song::set_grouping(const QString &v) { d->grouping_ = v; }
|
void Song::set_grouping(const QString &v) { d->grouping_ = v; }
|
||||||
void Song::set_comment(const QString &v) { d->comment_ = v; }
|
void Song::set_comment(const QString &v) { d->comment_ = v; }
|
||||||
void Song::set_lyrics(const QString &v) { d->lyrics_ = v; }
|
void Song::set_lyrics(const QString &v) { d->lyrics_ = v; }
|
||||||
@@ -578,6 +602,9 @@ void Song::set_art_unset(const bool v) { d->art_unset_ = v; }
|
|||||||
void Song::set_cue_path(const QString &v) { d->cue_path_ = v; }
|
void Song::set_cue_path(const QString &v) { d->cue_path_ = v; }
|
||||||
|
|
||||||
void Song::set_rating(const float v) { d->rating_ = v; }
|
void Song::set_rating(const float v) { d->rating_ = v; }
|
||||||
|
void Song::set_bpm(const float v) { d->bpm_ = v; }
|
||||||
|
void Song::set_mood(const QString &v) { d->mood_ = v; }
|
||||||
|
void Song::set_initial_key(const QString &v) { d->initial_key_ = v; }
|
||||||
|
|
||||||
void Song::set_acoustid_id(const QString &v) { d->acoustid_id_ = v; }
|
void Song::set_acoustid_id(const QString &v) { d->acoustid_id_ = v; }
|
||||||
void Song::set_acoustid_fingerprint(const QString &v) { d->acoustid_fingerprint_ = v; }
|
void Song::set_acoustid_fingerprint(const QString &v) { d->acoustid_fingerprint_ = v; }
|
||||||
@@ -600,40 +627,19 @@ void Song::set_init_from_file(const bool v) { d->init_from_file_ = v; }
|
|||||||
|
|
||||||
void Song::set_stream_url(const QUrl &v) { d->stream_url_ = v; }
|
void Song::set_stream_url(const QUrl &v) { d->stream_url_ = v; }
|
||||||
|
|
||||||
void Song::set_title(const TagLib::String &v) {
|
void Song::set_title(const TagLib::String &v) { d->title_ = TagLibStringToQString(v); }
|
||||||
|
void Song::set_titlesort(const TagLib::String &v) { d->titlesort_ = TagLibStringToQString(v); }
|
||||||
const QString title = TagLibStringToQString(v);
|
void Song::set_album(const TagLib::String &v) { d->album_ = TagLibStringToQString(v); }
|
||||||
d->title_sortable_ = sortable(title);
|
void Song::set_albumsort(const TagLib::String &v) { d->albumsort_ = TagLibStringToQString(v); }
|
||||||
d->title_ = title;
|
void Song::set_artist(const TagLib::String &v) { d->artist_ = TagLibStringToQString(v); }
|
||||||
|
void Song::set_artistsort(const TagLib::String &v) { d->artistsort_ = TagLibStringToQString(v); }
|
||||||
}
|
void Song::set_albumartist(const TagLib::String &v) { d->albumartist_ = TagLibStringToQString(v); }
|
||||||
|
void Song::set_albumartistsort(const TagLib::String &v) { d->albumartistsort_ = TagLibStringToQString(v); }
|
||||||
void Song::set_album(const TagLib::String &v) {
|
|
||||||
|
|
||||||
const QString album = TagLibStringToQString(v);
|
|
||||||
d->album_sortable_ = sortable(album);
|
|
||||||
d->album_ = album;
|
|
||||||
|
|
||||||
}
|
|
||||||
void Song::set_artist(const TagLib::String &v) {
|
|
||||||
|
|
||||||
const QString artist = TagLibStringToQString(v);
|
|
||||||
d->artist_sortable_ = sortable(artist);
|
|
||||||
d->artist_ = artist;
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
void Song::set_albumartist(const TagLib::String &v) {
|
|
||||||
|
|
||||||
const QString albumartist = TagLibStringToQString(v);
|
|
||||||
d->albumartist_sortable_ = sortable(albumartist);
|
|
||||||
d->albumartist_ = albumartist;
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
void Song::set_genre(const TagLib::String &v) { d->genre_ = TagLibStringToQString(v); }
|
void Song::set_genre(const TagLib::String &v) { d->genre_ = TagLibStringToQString(v); }
|
||||||
void Song::set_composer(const TagLib::String &v) { d->composer_ = TagLibStringToQString(v); }
|
void Song::set_composer(const TagLib::String &v) { d->composer_ = TagLibStringToQString(v); }
|
||||||
|
void Song::set_composersort(const TagLib::String &v) { d->composersort_ = TagLibStringToQString(v); }
|
||||||
void Song::set_performer(const TagLib::String &v) { d->performer_ = TagLibStringToQString(v); }
|
void Song::set_performer(const TagLib::String &v) { d->performer_ = TagLibStringToQString(v); }
|
||||||
|
void Song::set_performersort(const TagLib::String &v) { d->performersort_ = TagLibStringToQString(v); }
|
||||||
void Song::set_grouping(const TagLib::String &v) { d->grouping_ = TagLibStringToQString(v); }
|
void Song::set_grouping(const TagLib::String &v) { d->grouping_ = TagLibStringToQString(v); }
|
||||||
void Song::set_comment(const TagLib::String &v) { d->comment_ = TagLibStringToQString(v); }
|
void Song::set_comment(const TagLib::String &v) { d->comment_ = TagLibStringToQString(v); }
|
||||||
void Song::set_lyrics(const TagLib::String &v) { d->lyrics_ = TagLibStringToQString(v); }
|
void Song::set_lyrics(const TagLib::String &v) { d->lyrics_ = TagLibStringToQString(v); }
|
||||||
@@ -652,14 +658,21 @@ void Song::set_musicbrainz_track_id(const TagLib::String &v) { d->musicbrainz_tr
|
|||||||
void Song::set_musicbrainz_disc_id(const TagLib::String &v) { d->musicbrainz_disc_id_ = TagLibStringToQString(v).remove(u' ').replace(u';', u'/'); }
|
void Song::set_musicbrainz_disc_id(const TagLib::String &v) { d->musicbrainz_disc_id_ = TagLibStringToQString(v).remove(u' ').replace(u';', u'/'); }
|
||||||
void Song::set_musicbrainz_release_group_id(const TagLib::String &v) { d->musicbrainz_release_group_id_ = TagLibStringToQString(v).remove(u' ').replace(u';', u'/'); }
|
void Song::set_musicbrainz_release_group_id(const TagLib::String &v) { d->musicbrainz_release_group_id_ = TagLibStringToQString(v).remove(u' ').replace(u';', u'/'); }
|
||||||
void Song::set_musicbrainz_work_id(const TagLib::String &v) { d->musicbrainz_work_id_ = TagLibStringToQString(v).remove(u' ').replace(u';', u'/'); }
|
void Song::set_musicbrainz_work_id(const TagLib::String &v) { d->musicbrainz_work_id_ = TagLibStringToQString(v).remove(u' ').replace(u';', u'/'); }
|
||||||
|
void Song::set_mood(const TagLib::String &v) { d->mood_ = TagLibStringToQString(v); }
|
||||||
|
void Song::set_initial_key(const TagLib::String &v) { d->initial_key_ = TagLibStringToQString(v); }
|
||||||
|
|
||||||
const QUrl &Song::effective_stream_url() const { return !d->stream_url_.isEmpty() && d->stream_url_.isValid() ? d->stream_url_ : d->url_; }
|
const QUrl &Song::effective_url() const { return !d->stream_url_.isEmpty() && d->stream_url_.isValid() ? d->stream_url_ : d->url_; }
|
||||||
|
const QString &Song::effective_titlesort() const { return d->titlesort_.isEmpty() ? d->title_ : d->titlesort_; }
|
||||||
const QString &Song::effective_albumartist() const { return d->albumartist_.isEmpty() ? d->artist_ : d->albumartist_; }
|
const QString &Song::effective_albumartist() const { return d->albumartist_.isEmpty() ? d->artist_ : d->albumartist_; }
|
||||||
const QString &Song::effective_albumartist_sortable() const { return d->albumartist_.isEmpty() ? d->artist_sortable_ : d->albumartist_sortable_; }
|
const QString &Song::effective_albumartistsort() const { return !d->albumartistsort_.isEmpty() ? d->albumartistsort_ : !d->albumartist_.isEmpty() ? d->albumartist_ : effective_artistsort(); }
|
||||||
|
const QString &Song::effective_artistsort() const { return d->artistsort_.isEmpty() ? d->artist_ : d->artistsort_; }
|
||||||
const QString &Song::effective_album() const { return d->album_.isEmpty() ? d->title_ : d->album_; }
|
const QString &Song::effective_album() const { return d->album_.isEmpty() ? d->title_ : d->album_; }
|
||||||
|
const QString &Song::effective_albumsort() const { return d->albumsort_.isEmpty() ? d->album_ : d->albumsort_; }
|
||||||
|
const QString &Song::effective_composersort() const { return d->composersort_.isEmpty() ? d->composer_ : d->composersort_; }
|
||||||
|
const QString &Song::effective_performersort() const { return d->performersort_.isEmpty() ? d->performer_ : d->performersort_; }
|
||||||
int Song::effective_originalyear() const { return d->originalyear_ < 0 ? d->year_ : d->originalyear_; }
|
int Song::effective_originalyear() const { return d->originalyear_ < 0 ? d->year_ : d->originalyear_; }
|
||||||
const QString &Song::playlist_albumartist() const { return is_compilation() ? d->albumartist_ : effective_albumartist(); }
|
const QString &Song::playlist_effective_albumartist() const { return is_compilation() ? d->albumartist_ : effective_albumartist(); }
|
||||||
const QString &Song::playlist_albumartist_sortable() const { return is_compilation() ? d->albumartist_sortable_ : effective_albumartist_sortable(); }
|
const QString &Song::playlist_effective_albumartistsort() const { return is_compilation() ? (!d->albumartistsort_.isEmpty() ? d->albumartistsort_ : d->albumartist_) : effective_albumartistsort(); }
|
||||||
|
|
||||||
bool Song::is_metadata_good() const { return !d->url_.isEmpty() && !d->artist_.isEmpty() && !d->title_.isEmpty(); }
|
bool Song::is_metadata_good() const { return !d->url_.isEmpty() && !d->artist_.isEmpty() && !d->title_.isEmpty(); }
|
||||||
bool Song::is_local_collection_song() const { return d->source_ == Source::Collection; }
|
bool Song::is_local_collection_song() const { return d->source_ == Source::Collection; }
|
||||||
@@ -782,6 +795,31 @@ bool Song::lyrics_supported() const {
|
|||||||
return additional_tags_supported() || d->filetype_ == FileType::ASF;
|
return additional_tags_supported() || d->filetype_ == FileType::ASF;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool Song::albumartistsort_supported() const {
|
||||||
|
return d->filetype_ == FileType::FLAC || d->filetype_ == FileType::OggFlac || d->filetype_ == FileType::OggVorbis || d->filetype_ == FileType::MPEG;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Song::albumsort_supported() const {
|
||||||
|
return d->filetype_ == FileType::FLAC || d->filetype_ == FileType::OggFlac || d->filetype_ == FileType::OggVorbis || d->filetype_ == FileType::MPEG;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Song::artistsort_supported() const {
|
||||||
|
return d->filetype_ == FileType::FLAC || d->filetype_ == FileType::OggFlac || d->filetype_ == FileType::OggVorbis || d->filetype_ == FileType::MPEG;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Song::composersort_supported() const {
|
||||||
|
return d->filetype_ == FileType::FLAC || d->filetype_ == FileType::OggFlac || d->filetype_ == FileType::OggVorbis || d->filetype_ == FileType::MPEG;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Song::performersort_supported() const {
|
||||||
|
// Performer sort is a rare custom field even in vorbis comments, no write support in MPEG formats
|
||||||
|
return d->filetype_ == FileType::FLAC || d->filetype_ == FileType::OggFlac || d->filetype_ == FileType::OggVorbis;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Song::titlesort_supported() const {
|
||||||
|
return d->filetype_ == FileType::FLAC || d->filetype_ == FileType::OggFlac || d->filetype_ == FileType::OggVorbis || d->filetype_ == FileType::MPEG;
|
||||||
|
}
|
||||||
|
|
||||||
bool Song::save_embedded_cover_supported(const FileType filetype) {
|
bool Song::save_embedded_cover_supported(const FileType filetype) {
|
||||||
|
|
||||||
return filetype == FileType::FLAC ||
|
return filetype == FileType::FLAC ||
|
||||||
@@ -794,21 +832,6 @@ bool Song::save_embedded_cover_supported(const FileType filetype) {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
QString Song::sortable(const QString &v) {
|
|
||||||
|
|
||||||
QString copy = v.toLower();
|
|
||||||
|
|
||||||
for (const auto &i : kArticles) {
|
|
||||||
if (copy.startsWith(i)) {
|
|
||||||
qint64 ilen = i.length();
|
|
||||||
return copy.right(copy.length() - ilen) + u", "_s + copy.left(ilen - 1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return copy;
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
int Song::ColumnIndex(const QString &field) {
|
int Song::ColumnIndex(const QString &field) {
|
||||||
|
|
||||||
return static_cast<int>(kRowIdColumns.indexOf(field));
|
return static_cast<int>(kRowIdColumns.indexOf(field));
|
||||||
@@ -923,39 +946,63 @@ bool Song::IsEditable() const {
|
|||||||
return d->valid_ && d->url_.isValid() && ((d->url_.isLocalFile() && write_tags_supported() && !has_cue()) || d->source_ == Source::Stream);
|
return d->valid_ && d->url_.isValid() && ((d->url_.isLocalFile() && write_tags_supported() && !has_cue()) || d->source_ == Source::Stream);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool Song::IsFileInfoEqual(const Song &other) const {
|
||||||
|
|
||||||
|
return d->beginning_ == other.d->beginning_ &&
|
||||||
|
d->end_ == other.d->end_ &&
|
||||||
|
d->url_ == other.d->url_ &&
|
||||||
|
d->basefilename_ == other.d->basefilename_ &&
|
||||||
|
d->filetype_ == other.d->filetype_ &&
|
||||||
|
d->filesize_ == other.d->filesize_ &&
|
||||||
|
d->mtime_ == other.d->mtime_ &&
|
||||||
|
d->ctime_ == other.d->ctime_ &&
|
||||||
|
d->mtime_ == other.d->mtime_ &&
|
||||||
|
d->stream_url_ == other.d->stream_url_;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
bool Song::IsMetadataEqual(const Song &other) const {
|
bool Song::IsMetadataEqual(const Song &other) const {
|
||||||
|
|
||||||
return d->title_ == other.d->title_ &&
|
return d->title_ == other.d->title_ &&
|
||||||
d->album_ == other.d->album_ &&
|
d->titlesort_ == other.d->titlesort_ &&
|
||||||
d->artist_ == other.d->artist_ &&
|
d->album_ == other.d->album_ &&
|
||||||
d->albumartist_ == other.d->albumartist_ &&
|
d->albumsort_ == other.d->albumsort_ &&
|
||||||
d->track_ == other.d->track_ &&
|
d->artist_ == other.d->artist_ &&
|
||||||
d->disc_ == other.d->disc_ &&
|
d->artistsort_ == other.d->artistsort_ &&
|
||||||
d->year_ == other.d->year_ &&
|
d->albumartist_ == other.d->albumartist_ &&
|
||||||
d->originalyear_ == other.d->originalyear_ &&
|
d->albumartistsort_ == other.d->albumartistsort_ &&
|
||||||
d->genre_ == other.d->genre_ &&
|
d->track_ == other.d->track_ &&
|
||||||
d->compilation_ == other.d->compilation_ &&
|
d->disc_ == other.d->disc_ &&
|
||||||
d->composer_ == other.d->composer_ &&
|
d->year_ == other.d->year_ &&
|
||||||
d->performer_ == other.d->performer_ &&
|
d->originalyear_ == other.d->originalyear_ &&
|
||||||
d->grouping_ == other.d->grouping_ &&
|
d->genre_ == other.d->genre_ &&
|
||||||
d->comment_ == other.d->comment_ &&
|
d->compilation_ == other.d->compilation_ &&
|
||||||
d->lyrics_ == other.d->lyrics_ &&
|
d->composer_ == other.d->composer_ &&
|
||||||
d->artist_id_ == other.d->artist_id_ &&
|
d->composersort_ == other.d->composersort_ &&
|
||||||
d->album_id_ == other.d->album_id_ &&
|
d->performer_ == other.d->performer_ &&
|
||||||
d->song_id_ == other.d->song_id_ &&
|
d->performersort_ == other.d->performersort_ &&
|
||||||
d->beginning_ == other.d->beginning_ &&
|
d->grouping_ == other.d->grouping_ &&
|
||||||
length_nanosec() == other.length_nanosec() &&
|
d->comment_ == other.d->comment_ &&
|
||||||
d->bitrate_ == other.d->bitrate_ &&
|
d->lyrics_ == other.d->lyrics_ &&
|
||||||
d->samplerate_ == other.d->samplerate_ &&
|
d->artist_id_ == other.d->artist_id_ &&
|
||||||
d->bitdepth_ == other.d->bitdepth_ &&
|
d->album_id_ == other.d->album_id_ &&
|
||||||
d->cue_path_ == other.d->cue_path_;
|
d->song_id_ == other.d->song_id_ &&
|
||||||
|
d->beginning_ == other.d->beginning_ &&
|
||||||
|
length_nanosec() == other.length_nanosec() &&
|
||||||
|
d->bitrate_ == other.d->bitrate_ &&
|
||||||
|
d->samplerate_ == other.d->samplerate_ &&
|
||||||
|
d->bitdepth_ == other.d->bitdepth_ &&
|
||||||
|
d->bpm_ == other.d->bpm_ &&
|
||||||
|
d->mood_ == other.d->mood_ &&
|
||||||
|
d->initial_key_ == other.d->initial_key_ &&
|
||||||
|
d->cue_path_ == other.d->cue_path_;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool Song::IsPlayStatisticsEqual(const Song &other) const {
|
bool Song::IsPlayStatisticsEqual(const Song &other) const {
|
||||||
|
|
||||||
return d->playcount_ == other.d->playcount_ &&
|
return d->playcount_ == other.d->playcount_ &&
|
||||||
d->skipcount_ == other.d->skipcount_ &&
|
d->skipcount_ == other.d->skipcount_ &&
|
||||||
d->lastplayed_ == other.d->lastplayed_;
|
d->lastplayed_ == other.d->lastplayed_;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -980,42 +1027,70 @@ bool Song::IsAcoustIdEqual(const Song &other) const {
|
|||||||
bool Song::IsMusicBrainzEqual(const Song &other) const {
|
bool Song::IsMusicBrainzEqual(const Song &other) const {
|
||||||
|
|
||||||
return d->musicbrainz_album_artist_id_ == other.d->musicbrainz_album_artist_id_ &&
|
return d->musicbrainz_album_artist_id_ == other.d->musicbrainz_album_artist_id_ &&
|
||||||
d->musicbrainz_artist_id_ == other.d->musicbrainz_artist_id_ &&
|
d->musicbrainz_artist_id_ == other.d->musicbrainz_artist_id_ &&
|
||||||
d->musicbrainz_original_artist_id_ == other.d->musicbrainz_original_artist_id_ &&
|
d->musicbrainz_original_artist_id_ == other.d->musicbrainz_original_artist_id_ &&
|
||||||
d->musicbrainz_album_id_ == other.d->musicbrainz_album_id_ &&
|
d->musicbrainz_album_id_ == other.d->musicbrainz_album_id_ &&
|
||||||
d->musicbrainz_original_album_id_ == other.d->musicbrainz_original_album_id_ &&
|
d->musicbrainz_original_album_id_ == other.d->musicbrainz_original_album_id_ &&
|
||||||
d->musicbrainz_recording_id_ == other.d->musicbrainz_recording_id_ &&
|
d->musicbrainz_recording_id_ == other.d->musicbrainz_recording_id_ &&
|
||||||
d->musicbrainz_track_id_ == other.d->musicbrainz_track_id_ &&
|
d->musicbrainz_track_id_ == other.d->musicbrainz_track_id_ &&
|
||||||
d->musicbrainz_disc_id_ == other.d->musicbrainz_disc_id_ &&
|
d->musicbrainz_disc_id_ == other.d->musicbrainz_disc_id_ &&
|
||||||
d->musicbrainz_release_group_id_ == other.d->musicbrainz_release_group_id_ &&
|
d->musicbrainz_release_group_id_ == other.d->musicbrainz_release_group_id_ &&
|
||||||
d->musicbrainz_work_id_ == other.d->musicbrainz_work_id_;
|
d->musicbrainz_work_id_ == other.d->musicbrainz_work_id_;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bool Song::IsEBUR128Equal(const Song &other) const {
|
bool Song::IsEBUR128Equal(const Song &other) const {
|
||||||
|
|
||||||
return d->ebur128_integrated_loudness_lufs_ == other.d->ebur128_integrated_loudness_lufs_ &&
|
return d->ebur128_integrated_loudness_lufs_ == other.d->ebur128_integrated_loudness_lufs_ &&
|
||||||
d->ebur128_loudness_range_lu_ == other.d->ebur128_loudness_range_lu_;
|
d->ebur128_loudness_range_lu_ == other.d->ebur128_loudness_range_lu_;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bool Song::IsArtEqual(const Song &other) const {
|
bool Song::IsArtEqual(const Song &other) const {
|
||||||
|
|
||||||
return d->art_embedded_ == other.d->art_embedded_ &&
|
return d->art_embedded_ == other.d->art_embedded_ &&
|
||||||
d->art_automatic_ == other.d->art_automatic_ &&
|
d->art_automatic_ == other.d->art_automatic_ &&
|
||||||
d->art_manual_ == other.d->art_manual_ &&
|
d->art_manual_ == other.d->art_manual_ &&
|
||||||
d->art_unset_ == other.d->art_unset_;
|
d->art_unset_ == other.d->art_unset_;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Song::IsCompilationEqual(const Song &other) const {
|
||||||
|
|
||||||
|
return d->compilation_ == other.d->compilation_ &&
|
||||||
|
d->compilation_detected_ == other.d->compilation_detected_ &&
|
||||||
|
d->compilation_on_ == other.d->compilation_on_ &&
|
||||||
|
d->compilation_off_ == other.d->compilation_off_;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Song::IsSettingsEqual(const Song &other) const {
|
||||||
|
|
||||||
|
return d->source_ == other.d->source_ &&
|
||||||
|
d->directory_id_ == other.d->directory_id_ &&
|
||||||
|
d->unavailable_ == other.d->unavailable_;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bool Song::IsAllMetadataEqual(const Song &other) const {
|
bool Song::IsAllMetadataEqual(const Song &other) const {
|
||||||
|
|
||||||
return IsMetadataEqual(other) &&
|
return IsMetadataEqual(other) &&
|
||||||
IsPlayStatisticsEqual(other) &&
|
IsPlayStatisticsEqual(other) &&
|
||||||
IsRatingEqual(other) &&
|
IsRatingEqual(other) &&
|
||||||
IsAcoustIdEqual(other) &&
|
IsAcoustIdEqual(other) &&
|
||||||
IsMusicBrainzEqual(other) &&
|
IsMusicBrainzEqual(other) &&
|
||||||
IsArtEqual(other);
|
IsArtEqual(other) &&
|
||||||
|
IsEBUR128Equal(other);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Song::IsEqual(const Song &other) const {
|
||||||
|
|
||||||
|
return IsFileInfoEqual(other) &&
|
||||||
|
IsSettingsEqual(other) &&
|
||||||
|
IsAllMetadataEqual(other) &&
|
||||||
|
IsFingerprintEqual(other) &&
|
||||||
|
IsCompilationEqual(other);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1139,6 +1214,22 @@ QIcon Song::IconForSource(const Source source) {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Convert a source to a music service domain name, for ListenBrainz.
|
||||||
|
// See the "Music service names" note on https://listenbrainz.readthedocs.io/en/latest/users/json.html.
|
||||||
|
|
||||||
|
QString Song::DomainForSource(const Source source) {
|
||||||
|
|
||||||
|
switch (source) {
|
||||||
|
case Song::Source::Tidal: return u"tidal.com"_s;
|
||||||
|
case Song::Source::Qobuz: return u"qobuz.com"_s;
|
||||||
|
case Song::Source::SomaFM: return u"somafm.com"_s;
|
||||||
|
case Song::Source::RadioParadise: return u"radioparadise.com"_s;
|
||||||
|
case Song::Source::Spotify: return u"spotify.com"_s;
|
||||||
|
default: return QString();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
QString Song::TextForFiletype(const FileType filetype) {
|
QString Song::TextForFiletype(const FileType filetype) {
|
||||||
|
|
||||||
switch (filetype) {
|
switch (filetype) {
|
||||||
@@ -1166,6 +1257,7 @@ QString Song::TextForFiletype(const FileType filetype) {
|
|||||||
case FileType::CDDA: return u"CDDA"_s;
|
case FileType::CDDA: return u"CDDA"_s;
|
||||||
case FileType::SPC: return u"SNES SPC700"_s;
|
case FileType::SPC: return u"SNES SPC700"_s;
|
||||||
case FileType::VGM: return u"VGM"_s;
|
case FileType::VGM: return u"VGM"_s;
|
||||||
|
case FileType::ALAC: return u"ALAC"_s;
|
||||||
case FileType::Stream: return u"Stream"_s;
|
case FileType::Stream: return u"Stream"_s;
|
||||||
case FileType::Unknown:
|
case FileType::Unknown:
|
||||||
default: return QObject::tr("Unknown");
|
default: return QObject::tr("Unknown");
|
||||||
@@ -1198,6 +1290,7 @@ QString Song::ExtensionForFiletype(const FileType filetype) {
|
|||||||
case FileType::IT: return u"it"_s;
|
case FileType::IT: return u"it"_s;
|
||||||
case FileType::SPC: return u"spc"_s;
|
case FileType::SPC: return u"spc"_s;
|
||||||
case FileType::VGM: return u"vgm"_s;
|
case FileType::VGM: return u"vgm"_s;
|
||||||
|
case FileType::ALAC: return u"m4a"_s;
|
||||||
case FileType::Unknown:
|
case FileType::Unknown:
|
||||||
default: return u"dat"_s;
|
default: return u"dat"_s;
|
||||||
}
|
}
|
||||||
@@ -1230,12 +1323,30 @@ QIcon Song::IconForFiletype(const FileType filetype) {
|
|||||||
case FileType::IT: return IconLoader::Load(u"it"_s);
|
case FileType::IT: return IconLoader::Load(u"it"_s);
|
||||||
case FileType::CDDA: return IconLoader::Load(u"cd"_s);
|
case FileType::CDDA: return IconLoader::Load(u"cd"_s);
|
||||||
case FileType::Stream: return IconLoader::Load(u"applications-internet"_s);
|
case FileType::Stream: return IconLoader::Load(u"applications-internet"_s);
|
||||||
|
case FileType::ALAC: return IconLoader::Load(u"alac"_s);
|
||||||
case FileType::Unknown:
|
case FileType::Unknown:
|
||||||
default: return IconLoader::Load(u"edit-delete"_s);
|
default: return IconLoader::Load(u"edit-delete"_s);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Get a URL usable for sharing this song with another user.
|
||||||
|
// This is only applicable when streaming from a streaming service, since we can't link to local content.
|
||||||
|
// Returns a web URL which points to the current streaming track or live stream, or an empty string if that is not applicable.
|
||||||
|
|
||||||
|
QString Song::ShareURL() const {
|
||||||
|
|
||||||
|
switch (source()) {
|
||||||
|
case Song::Source::Stream:
|
||||||
|
case Song::Source::SomaFM: return url().toString();
|
||||||
|
case Song::Source::Tidal: return "https://tidal.com/track/%1"_L1.arg(song_id());
|
||||||
|
case Song::Source::Qobuz: return "https://open.qobuz.com/track/%1"_L1.arg(song_id());
|
||||||
|
case Song::Source::Spotify: return "https://open.spotify.com/track/%1"_L1.arg(song_id());
|
||||||
|
default: return QString();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
bool Song::IsFileLossless() const {
|
bool Song::IsFileLossless() const {
|
||||||
|
|
||||||
switch (filetype()) {
|
switch (filetype()) {
|
||||||
@@ -1250,6 +1361,7 @@ bool Song::IsFileLossless() const {
|
|||||||
case FileType::TrueAudio:
|
case FileType::TrueAudio:
|
||||||
case FileType::PCM:
|
case FileType::PCM:
|
||||||
case FileType::CDDA:
|
case FileType::CDDA:
|
||||||
|
case FileType::ALAC:
|
||||||
return true;
|
return true;
|
||||||
default:
|
default:
|
||||||
return false;
|
return false;
|
||||||
@@ -1279,6 +1391,7 @@ Song::FileType Song::FiletypeByMimetype(const QString &mimetype) {
|
|||||||
if (mimetype.compare("audio/x-s3m"_L1, Qt::CaseInsensitive) == 0) return FileType::S3M;
|
if (mimetype.compare("audio/x-s3m"_L1, Qt::CaseInsensitive) == 0) return FileType::S3M;
|
||||||
if (mimetype.compare("audio/x-spc"_L1, Qt::CaseInsensitive) == 0) return FileType::SPC;
|
if (mimetype.compare("audio/x-spc"_L1, Qt::CaseInsensitive) == 0) return FileType::SPC;
|
||||||
if (mimetype.compare("audio/x-vgm"_L1, Qt::CaseInsensitive) == 0) return FileType::VGM;
|
if (mimetype.compare("audio/x-vgm"_L1, Qt::CaseInsensitive) == 0) return FileType::VGM;
|
||||||
|
if (mimetype.compare("audio/x-alac"_L1, Qt::CaseInsensitive) == 0) return FileType::ALAC;
|
||||||
|
|
||||||
return FileType::Unknown;
|
return FileType::Unknown;
|
||||||
|
|
||||||
@@ -1306,6 +1419,7 @@ Song::FileType Song::FiletypeByDescription(const QString &text) {
|
|||||||
if (text.compare("Module Music Format (MOD)"_L1, Qt::CaseInsensitive) == 0) return FileType::S3M;
|
if (text.compare("Module Music Format (MOD)"_L1, Qt::CaseInsensitive) == 0) return FileType::S3M;
|
||||||
if (text.compare("SNES SPC700"_L1, Qt::CaseInsensitive) == 0) return FileType::SPC;
|
if (text.compare("SNES SPC700"_L1, Qt::CaseInsensitive) == 0) return FileType::SPC;
|
||||||
if (text.compare("VGM"_L1, Qt::CaseInsensitive) == 0) return FileType::VGM;
|
if (text.compare("VGM"_L1, Qt::CaseInsensitive) == 0) return FileType::VGM;
|
||||||
|
if (text.compare("Apple Lossless Audio Codec (ALAC)"_L1, Qt::CaseInsensitive) == 0) return FileType::ALAC;
|
||||||
|
|
||||||
return FileType::Unknown;
|
return FileType::Unknown;
|
||||||
|
|
||||||
@@ -1416,9 +1530,13 @@ void Song::InitFromQuery(const QSqlRecord &r, const bool reliable_metadata, cons
|
|||||||
d->id_ = SqlHelper::ValueToInt(r, ColumnIndex(u"ROWID"_s) + col);
|
d->id_ = SqlHelper::ValueToInt(r, ColumnIndex(u"ROWID"_s) + col);
|
||||||
|
|
||||||
set_title(SqlHelper::ValueToString(r, ColumnIndex(u"title"_s) + col));
|
set_title(SqlHelper::ValueToString(r, ColumnIndex(u"title"_s) + col));
|
||||||
|
set_titlesort(SqlHelper::ValueToString(r, ColumnIndex(u"titlesort"_s) + col));
|
||||||
set_album(SqlHelper::ValueToString(r, ColumnIndex(u"album"_s) + col));
|
set_album(SqlHelper::ValueToString(r, ColumnIndex(u"album"_s) + col));
|
||||||
|
set_albumsort(SqlHelper::ValueToString(r, ColumnIndex(u"albumsort"_s) + col));
|
||||||
set_artist(SqlHelper::ValueToString(r, ColumnIndex(u"artist"_s) + col));
|
set_artist(SqlHelper::ValueToString(r, ColumnIndex(u"artist"_s) + col));
|
||||||
|
set_artistsort(SqlHelper::ValueToString(r, ColumnIndex(u"artistsort"_s) + col));
|
||||||
set_albumartist(SqlHelper::ValueToString(r, ColumnIndex(u"albumartist"_s) + col));
|
set_albumartist(SqlHelper::ValueToString(r, ColumnIndex(u"albumartist"_s) + col));
|
||||||
|
set_albumartistsort(SqlHelper::ValueToString(r, ColumnIndex(u"albumartistsort"_s) + col));
|
||||||
d->track_ = SqlHelper::ValueToInt(r, ColumnIndex(u"track"_s) + col);
|
d->track_ = SqlHelper::ValueToInt(r, ColumnIndex(u"track"_s) + col);
|
||||||
d->disc_ = SqlHelper::ValueToInt(r, ColumnIndex(u"disc"_s) + col);
|
d->disc_ = SqlHelper::ValueToInt(r, ColumnIndex(u"disc"_s) + col);
|
||||||
d->year_ = SqlHelper::ValueToInt(r, ColumnIndex(u"year"_s) + col);
|
d->year_ = SqlHelper::ValueToInt(r, ColumnIndex(u"year"_s) + col);
|
||||||
@@ -1426,7 +1544,9 @@ void Song::InitFromQuery(const QSqlRecord &r, const bool reliable_metadata, cons
|
|||||||
d->genre_ = SqlHelper::ValueToString(r, ColumnIndex(u"genre"_s) + col);
|
d->genre_ = SqlHelper::ValueToString(r, ColumnIndex(u"genre"_s) + col);
|
||||||
d->compilation_ = r.value(ColumnIndex(u"compilation"_s) + col).toBool();
|
d->compilation_ = r.value(ColumnIndex(u"compilation"_s) + col).toBool();
|
||||||
d->composer_ = SqlHelper::ValueToString(r, ColumnIndex(u"composer"_s) + col);
|
d->composer_ = SqlHelper::ValueToString(r, ColumnIndex(u"composer"_s) + col);
|
||||||
|
d->composersort_ = SqlHelper::ValueToString(r, ColumnIndex(u"composersort"_s) + col);
|
||||||
d->performer_ = SqlHelper::ValueToString(r, ColumnIndex(u"performer"_s) + col);
|
d->performer_ = SqlHelper::ValueToString(r, ColumnIndex(u"performer"_s) + col);
|
||||||
|
d->performersort_ = SqlHelper::ValueToString(r, ColumnIndex(u"performersort"_s) + col);
|
||||||
d->grouping_ = SqlHelper::ValueToString(r, ColumnIndex(u"grouping"_s) + col);
|
d->grouping_ = SqlHelper::ValueToString(r, ColumnIndex(u"grouping"_s) + col);
|
||||||
d->comment_ = SqlHelper::ValueToString(r, ColumnIndex(u"comment"_s) + col);
|
d->comment_ = SqlHelper::ValueToString(r, ColumnIndex(u"comment"_s) + col);
|
||||||
d->lyrics_ = SqlHelper::ValueToString(r, ColumnIndex(u"lyrics"_s) + col);
|
d->lyrics_ = SqlHelper::ValueToString(r, ColumnIndex(u"lyrics"_s) + col);
|
||||||
@@ -1468,7 +1588,11 @@ void Song::InitFromQuery(const QSqlRecord &r, const bool reliable_metadata, cons
|
|||||||
d->art_unset_ = SqlHelper::ValueToBool(r, ColumnIndex(u"art_unset"_s) + col);
|
d->art_unset_ = SqlHelper::ValueToBool(r, ColumnIndex(u"art_unset"_s) + col);
|
||||||
|
|
||||||
d->cue_path_ = SqlHelper::ValueToString(r, ColumnIndex(u"cue_path"_s) + col);
|
d->cue_path_ = SqlHelper::ValueToString(r, ColumnIndex(u"cue_path"_s) + col);
|
||||||
|
|
||||||
d->rating_ = SqlHelper::ValueToFloat(r, ColumnIndex(u"rating"_s) + col);
|
d->rating_ = SqlHelper::ValueToFloat(r, ColumnIndex(u"rating"_s) + col);
|
||||||
|
d->bpm_ = SqlHelper::ValueToFloat(r, ColumnIndex(u"bpm"_s) + col);
|
||||||
|
d->mood_ = SqlHelper::ValueToString(r, ColumnIndex(u"mood"_s) + col);
|
||||||
|
d->initial_key_ = SqlHelper::ValueToString(r, ColumnIndex(u"initial_key"_s) + col);
|
||||||
|
|
||||||
d->acoustid_id_ = SqlHelper::ValueToString(r, ColumnIndex(u"acoustid_id"_s) + col);
|
d->acoustid_id_ = SqlHelper::ValueToString(r, ColumnIndex(u"acoustid_id"_s) + col);
|
||||||
d->acoustid_fingerprint_ = SqlHelper::ValueToString(r, ColumnIndex(u"acoustid_fingerprint"_s) + col);
|
d->acoustid_fingerprint_ = SqlHelper::ValueToString(r, ColumnIndex(u"acoustid_fingerprint"_s) + col);
|
||||||
@@ -1734,9 +1858,13 @@ void Song::BindToQuery(SqlQuery *query) const {
|
|||||||
// Remember to bind these in the same order as kBindSpec
|
// Remember to bind these in the same order as kBindSpec
|
||||||
|
|
||||||
query->BindStringValue(u":title"_s, d->title_);
|
query->BindStringValue(u":title"_s, d->title_);
|
||||||
|
query->BindStringValue(u":titlesort"_s, d->titlesort_);
|
||||||
query->BindStringValue(u":album"_s, d->album_);
|
query->BindStringValue(u":album"_s, d->album_);
|
||||||
|
query->BindStringValue(u":albumsort"_s, d->albumsort_);
|
||||||
query->BindStringValue(u":artist"_s, d->artist_);
|
query->BindStringValue(u":artist"_s, d->artist_);
|
||||||
|
query->BindStringValue(u":artistsort"_s, d->artistsort_);
|
||||||
query->BindStringValue(u":albumartist"_s, d->albumartist_);
|
query->BindStringValue(u":albumartist"_s, d->albumartist_);
|
||||||
|
query->BindStringValue(u":albumartistsort"_s, d->albumartistsort_);
|
||||||
query->BindIntValue(u":track"_s, d->track_);
|
query->BindIntValue(u":track"_s, d->track_);
|
||||||
query->BindIntValue(u":disc"_s, d->disc_);
|
query->BindIntValue(u":disc"_s, d->disc_);
|
||||||
query->BindIntValue(u":year"_s, d->year_);
|
query->BindIntValue(u":year"_s, d->year_);
|
||||||
@@ -1744,7 +1872,9 @@ void Song::BindToQuery(SqlQuery *query) const {
|
|||||||
query->BindStringValue(u":genre"_s, d->genre_);
|
query->BindStringValue(u":genre"_s, d->genre_);
|
||||||
query->BindBoolValue(u":compilation"_s, d->compilation_);
|
query->BindBoolValue(u":compilation"_s, d->compilation_);
|
||||||
query->BindStringValue(u":composer"_s, d->composer_);
|
query->BindStringValue(u":composer"_s, d->composer_);
|
||||||
|
query->BindStringValue(u":composersort"_s, d->composersort_);
|
||||||
query->BindStringValue(u":performer"_s, d->performer_);
|
query->BindStringValue(u":performer"_s, d->performer_);
|
||||||
|
query->BindStringValue(u":performersort"_s, d->performersort_);
|
||||||
query->BindStringValue(u":grouping"_s, d->grouping_);
|
query->BindStringValue(u":grouping"_s, d->grouping_);
|
||||||
query->BindStringValue(u":comment"_s, d->comment_);
|
query->BindStringValue(u":comment"_s, d->comment_);
|
||||||
query->BindStringValue(u":lyrics"_s, d->lyrics_);
|
query->BindStringValue(u":lyrics"_s, d->lyrics_);
|
||||||
@@ -1792,6 +1922,9 @@ void Song::BindToQuery(SqlQuery *query) const {
|
|||||||
query->BindValue(u":cue_path"_s, d->cue_path_);
|
query->BindValue(u":cue_path"_s, d->cue_path_);
|
||||||
|
|
||||||
query->BindFloatValue(u":rating"_s, d->rating_);
|
query->BindFloatValue(u":rating"_s, d->rating_);
|
||||||
|
query->BindFloatValue(u":bpm"_s, d->bpm_);
|
||||||
|
query->BindStringValue(u":mood"_s, d->mood_);
|
||||||
|
query->BindStringValue(u":initial_key"_s, d->initial_key_);
|
||||||
|
|
||||||
query->BindStringValue(u":acoustid_id"_s, d->acoustid_id_);
|
query->BindStringValue(u":acoustid_id"_s, d->acoustid_id_);
|
||||||
query->BindStringValue(u":acoustid_fingerprint"_s, d->acoustid_fingerprint_);
|
query->BindStringValue(u":acoustid_fingerprint"_s, d->acoustid_fingerprint_);
|
||||||
@@ -1819,7 +1952,7 @@ void Song::ToXesam(QVariantMap *map) const {
|
|||||||
using mpris::AddMetadataAsList;
|
using mpris::AddMetadataAsList;
|
||||||
using mpris::AsMPRISDateTimeType;
|
using mpris::AsMPRISDateTimeType;
|
||||||
|
|
||||||
AddMetadata(u"xesam:url"_s, effective_stream_url().toString(), map);
|
AddMetadata(u"xesam:url"_s, effective_url().toString(), map);
|
||||||
AddMetadata(u"xesam:title"_s, PrettyTitle(), map);
|
AddMetadata(u"xesam:title"_s, PrettyTitle(), map);
|
||||||
AddMetadataAsList(u"xesam:artist"_s, artist(), map);
|
AddMetadataAsList(u"xesam:artist"_s, artist(), map);
|
||||||
AddMetadata(u"xesam:album"_s, album(), map);
|
AddMetadata(u"xesam:album"_s, album(), map);
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
* Strawberry Music Player
|
* Strawberry Music Player
|
||||||
* This file was part of Clementine.
|
* This file was part of Clementine.
|
||||||
* Copyright 2010, David Sansome <me@davidsansome.com>
|
* Copyright 2010, David Sansome <me@davidsansome.com>
|
||||||
* Copyright 2018-2024, Jonas Kvinge <jonas@jkvinge.net>
|
* Copyright 2018-2025, Jonas Kvinge <jonas@jkvinge.net>
|
||||||
*
|
*
|
||||||
* Strawberry is free software: you can redistribute it and/or modify
|
* Strawberry is free software: you can redistribute it and/or modify
|
||||||
* it under the terms of the GNU General Public License as published by
|
* it under the terms of the GNU General Public License as published by
|
||||||
@@ -105,6 +105,7 @@ class Song {
|
|||||||
IT = 21,
|
IT = 21,
|
||||||
SPC = 22,
|
SPC = 22,
|
||||||
VGM = 23,
|
VGM = 23,
|
||||||
|
ALAC = 24, // MP4, with ALAC codec
|
||||||
CDDA = 90,
|
CDDA = 90,
|
||||||
Stream = 91
|
Stream = 91
|
||||||
};
|
};
|
||||||
@@ -149,9 +150,13 @@ class Song {
|
|||||||
bool is_valid() const;
|
bool is_valid() const;
|
||||||
|
|
||||||
const QString &title() const;
|
const QString &title() const;
|
||||||
|
const QString &titlesort() const;
|
||||||
const QString &album() const;
|
const QString &album() const;
|
||||||
|
const QString &albumsort() const;
|
||||||
const QString &artist() const;
|
const QString &artist() const;
|
||||||
|
const QString &artistsort() const;
|
||||||
const QString &albumartist() const;
|
const QString &albumartist() const;
|
||||||
|
const QString &albumartistsort() const;
|
||||||
int track() const;
|
int track() const;
|
||||||
int disc() const;
|
int disc() const;
|
||||||
int year() const;
|
int year() const;
|
||||||
@@ -159,7 +164,9 @@ class Song {
|
|||||||
const QString &genre() const;
|
const QString &genre() const;
|
||||||
bool compilation() const;
|
bool compilation() const;
|
||||||
const QString &composer() const;
|
const QString &composer() const;
|
||||||
|
const QString &composersort() const;
|
||||||
const QString &performer() const;
|
const QString &performer() const;
|
||||||
|
const QString &performersort() const;
|
||||||
const QString &grouping() const;
|
const QString &grouping() const;
|
||||||
const QString &comment() const;
|
const QString &comment() const;
|
||||||
const QString &lyrics() const;
|
const QString &lyrics() const;
|
||||||
@@ -206,6 +213,9 @@ class Song {
|
|||||||
const QString &cue_path() const;
|
const QString &cue_path() const;
|
||||||
|
|
||||||
float rating() const;
|
float rating() const;
|
||||||
|
float bpm() const;
|
||||||
|
const QString &mood() const;
|
||||||
|
const QString &initial_key() const;
|
||||||
|
|
||||||
const QString &acoustid_id() const;
|
const QString &acoustid_id() const;
|
||||||
const QString &acoustid_fingerprint() const;
|
const QString &acoustid_fingerprint() const;
|
||||||
@@ -249,11 +259,6 @@ class Song {
|
|||||||
|
|
||||||
bool init_from_file() const;
|
bool init_from_file() const;
|
||||||
|
|
||||||
const QString &title_sortable() const;
|
|
||||||
const QString &album_sortable() const;
|
|
||||||
const QString &artist_sortable() const;
|
|
||||||
const QString &albumartist_sortable() const;
|
|
||||||
|
|
||||||
const QUrl &stream_url() const;
|
const QUrl &stream_url() const;
|
||||||
|
|
||||||
// Setters
|
// Setters
|
||||||
@@ -261,9 +266,13 @@ class Song {
|
|||||||
void set_valid(const bool v);
|
void set_valid(const bool v);
|
||||||
|
|
||||||
void set_title(const QString &v);
|
void set_title(const QString &v);
|
||||||
|
void set_titlesort(const QString &v);
|
||||||
void set_album(const QString &v);
|
void set_album(const QString &v);
|
||||||
|
void set_albumsort(const QString &v);
|
||||||
void set_artist(const QString &v);
|
void set_artist(const QString &v);
|
||||||
|
void set_artistsort(const QString &v);
|
||||||
void set_albumartist(const QString &v);
|
void set_albumartist(const QString &v);
|
||||||
|
void set_albumartistsort(const QString &v);
|
||||||
void set_track(const int v);
|
void set_track(const int v);
|
||||||
void set_disc(const int v);
|
void set_disc(const int v);
|
||||||
void set_year(const int v);
|
void set_year(const int v);
|
||||||
@@ -271,7 +280,9 @@ class Song {
|
|||||||
void set_genre(const QString &v);
|
void set_genre(const QString &v);
|
||||||
void set_compilation(bool v);
|
void set_compilation(bool v);
|
||||||
void set_composer(const QString &v);
|
void set_composer(const QString &v);
|
||||||
|
void set_composersort(const QString &v);
|
||||||
void set_performer(const QString &v);
|
void set_performer(const QString &v);
|
||||||
|
void set_performersort(const QString &v);
|
||||||
void set_grouping(const QString &v);
|
void set_grouping(const QString &v);
|
||||||
void set_comment(const QString &v);
|
void set_comment(const QString &v);
|
||||||
void set_lyrics(const QString &v);
|
void set_lyrics(const QString &v);
|
||||||
@@ -317,6 +328,9 @@ class Song {
|
|||||||
void set_cue_path(const QString &v);
|
void set_cue_path(const QString &v);
|
||||||
|
|
||||||
void set_rating(const float v);
|
void set_rating(const float v);
|
||||||
|
void set_bpm(const float v);
|
||||||
|
void set_mood(const QString &v);
|
||||||
|
void set_initial_key(const QString &v);
|
||||||
|
|
||||||
void set_acoustid_id(const QString &v);
|
void set_acoustid_id(const QString &v);
|
||||||
void set_acoustid_fingerprint(const QString &v);
|
void set_acoustid_fingerprint(const QString &v);
|
||||||
@@ -340,12 +354,18 @@ class Song {
|
|||||||
void set_stream_url(const QUrl &v);
|
void set_stream_url(const QUrl &v);
|
||||||
|
|
||||||
void set_title(const TagLib::String &v);
|
void set_title(const TagLib::String &v);
|
||||||
|
void set_titlesort(const TagLib::String &v);
|
||||||
void set_album(const TagLib::String &v);
|
void set_album(const TagLib::String &v);
|
||||||
|
void set_albumsort(const TagLib::String &v);
|
||||||
void set_artist(const TagLib::String &v);
|
void set_artist(const TagLib::String &v);
|
||||||
|
void set_artistsort(const TagLib::String &v);
|
||||||
void set_albumartist(const TagLib::String &v);
|
void set_albumartist(const TagLib::String &v);
|
||||||
|
void set_albumartistsort(const TagLib::String &v);
|
||||||
void set_genre(const TagLib::String &v);
|
void set_genre(const TagLib::String &v);
|
||||||
void set_composer(const TagLib::String &v);
|
void set_composer(const TagLib::String &v);
|
||||||
|
void set_composersort(const TagLib::String &v);
|
||||||
void set_performer(const TagLib::String &v);
|
void set_performer(const TagLib::String &v);
|
||||||
|
void set_performersort(const TagLib::String &v);
|
||||||
void set_grouping(const TagLib::String &v);
|
void set_grouping(const TagLib::String &v);
|
||||||
void set_comment(const TagLib::String &v);
|
void set_comment(const TagLib::String &v);
|
||||||
void set_lyrics(const TagLib::String &v);
|
void set_lyrics(const TagLib::String &v);
|
||||||
@@ -364,14 +384,21 @@ class Song {
|
|||||||
void set_musicbrainz_disc_id(const TagLib::String &v);
|
void set_musicbrainz_disc_id(const TagLib::String &v);
|
||||||
void set_musicbrainz_release_group_id(const TagLib::String &v);
|
void set_musicbrainz_release_group_id(const TagLib::String &v);
|
||||||
void set_musicbrainz_work_id(const TagLib::String &v);
|
void set_musicbrainz_work_id(const TagLib::String &v);
|
||||||
|
void set_mood(const TagLib::String &v);
|
||||||
|
void set_initial_key(const TagLib::String &v);
|
||||||
|
|
||||||
const QUrl &effective_stream_url() const;
|
const QUrl &effective_url() const;
|
||||||
|
const QString &effective_titlesort() const;
|
||||||
const QString &effective_albumartist() const;
|
const QString &effective_albumartist() const;
|
||||||
const QString &effective_albumartist_sortable() const;
|
const QString &effective_albumartistsort() const;
|
||||||
|
const QString &effective_artistsort() const;
|
||||||
const QString &effective_album() const;
|
const QString &effective_album() const;
|
||||||
|
const QString &effective_albumsort() const;
|
||||||
|
const QString &effective_composersort() const;
|
||||||
|
const QString &effective_performersort() const;
|
||||||
int effective_originalyear() const;
|
int effective_originalyear() const;
|
||||||
const QString &playlist_albumartist() const;
|
const QString &playlist_effective_albumartist() const;
|
||||||
const QString &playlist_albumartist_sortable() const;
|
const QString &playlist_effective_albumartistsort() const;
|
||||||
|
|
||||||
bool is_metadata_good() const;
|
bool is_metadata_good() const;
|
||||||
bool is_local_collection_song() const;
|
bool is_local_collection_song() const;
|
||||||
@@ -402,6 +429,13 @@ class Song {
|
|||||||
bool comment_supported() const;
|
bool comment_supported() const;
|
||||||
bool lyrics_supported() const;
|
bool lyrics_supported() const;
|
||||||
|
|
||||||
|
bool albumartistsort_supported() const;
|
||||||
|
bool albumsort_supported() const;
|
||||||
|
bool artistsort_supported() const;
|
||||||
|
bool composersort_supported() const;
|
||||||
|
bool performersort_supported() const;
|
||||||
|
bool titlesort_supported() const;
|
||||||
|
|
||||||
static bool save_embedded_cover_supported(const FileType filetype);
|
static bool save_embedded_cover_supported(const FileType filetype);
|
||||||
bool save_embedded_cover_supported() const { return url().isLocalFile() && save_embedded_cover_supported(filetype()) && !has_cue(); };
|
bool save_embedded_cover_supported() const { return url().isLocalFile() && save_embedded_cover_supported(filetype()) && !has_cue(); };
|
||||||
|
|
||||||
@@ -430,6 +464,7 @@ class Song {
|
|||||||
bool IsEditable() const;
|
bool IsEditable() const;
|
||||||
|
|
||||||
// Comparison functions
|
// Comparison functions
|
||||||
|
bool IsFileInfoEqual(const Song &other) const;
|
||||||
bool IsMetadataEqual(const Song &other) const;
|
bool IsMetadataEqual(const Song &other) const;
|
||||||
bool IsPlayStatisticsEqual(const Song &other) const;
|
bool IsPlayStatisticsEqual(const Song &other) const;
|
||||||
bool IsRatingEqual(const Song &other) const;
|
bool IsRatingEqual(const Song &other) const;
|
||||||
@@ -438,7 +473,10 @@ class Song {
|
|||||||
bool IsMusicBrainzEqual(const Song &other) const;
|
bool IsMusicBrainzEqual(const Song &other) const;
|
||||||
bool IsEBUR128Equal(const Song &other) const;
|
bool IsEBUR128Equal(const Song &other) const;
|
||||||
bool IsArtEqual(const Song &other) const;
|
bool IsArtEqual(const Song &other) const;
|
||||||
|
bool IsCompilationEqual(const Song &other) const;
|
||||||
|
bool IsSettingsEqual(const Song &other) const;
|
||||||
bool IsAllMetadataEqual(const Song &other) const;
|
bool IsAllMetadataEqual(const Song &other) const;
|
||||||
|
bool IsEqual(const Song &other) const;
|
||||||
|
|
||||||
bool IsOnSameAlbum(const Song &other) const;
|
bool IsOnSameAlbum(const Song &other) const;
|
||||||
bool IsSimilar(const Song &other) const;
|
bool IsSimilar(const Song &other) const;
|
||||||
@@ -448,6 +486,7 @@ class Song {
|
|||||||
static QString DescriptionForSource(const Source source);
|
static QString DescriptionForSource(const Source source);
|
||||||
static Source SourceFromText(const QString &source);
|
static Source SourceFromText(const QString &source);
|
||||||
static QIcon IconForSource(const Source source);
|
static QIcon IconForSource(const Source source);
|
||||||
|
static QString DomainForSource(const Source source);
|
||||||
static QString TextForFiletype(const FileType filetype);
|
static QString TextForFiletype(const FileType filetype);
|
||||||
static QString ExtensionForFiletype(const FileType filetype);
|
static QString ExtensionForFiletype(const FileType filetype);
|
||||||
static QIcon IconForFiletype(const FileType filetype);
|
static QIcon IconForFiletype(const FileType filetype);
|
||||||
@@ -455,9 +494,12 @@ class Song {
|
|||||||
QString TextForSource() const { return TextForSource(source()); }
|
QString TextForSource() const { return TextForSource(source()); }
|
||||||
QString DescriptionForSource() const { return DescriptionForSource(source()); }
|
QString DescriptionForSource() const { return DescriptionForSource(source()); }
|
||||||
QIcon IconForSource() const { return IconForSource(source()); }
|
QIcon IconForSource() const { return IconForSource(source()); }
|
||||||
|
QString DomainForSource() const { return DomainForSource(source()); }
|
||||||
QString TextForFiletype() const { return TextForFiletype(filetype()); }
|
QString TextForFiletype() const { return TextForFiletype(filetype()); }
|
||||||
QIcon IconForFiletype() const { return IconForFiletype(filetype()); }
|
QIcon IconForFiletype() const { return IconForFiletype(filetype()); }
|
||||||
|
|
||||||
|
QString ShareURL() const;
|
||||||
|
|
||||||
bool IsFileLossless() const;
|
bool IsFileLossless() const;
|
||||||
static FileType FiletypeByMimetype(const QString &mimetype);
|
static FileType FiletypeByMimetype(const QString &mimetype);
|
||||||
static FileType FiletypeByDescription(const QString &text);
|
static FileType FiletypeByDescription(const QString &text);
|
||||||
@@ -521,9 +563,6 @@ class Song {
|
|||||||
|
|
||||||
private:
|
private:
|
||||||
struct Private;
|
struct Private;
|
||||||
|
|
||||||
static QString sortable(const QString &v);
|
|
||||||
|
|
||||||
QSharedDataPointer<Private> d;
|
QSharedDataPointer<Private> d;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -178,9 +178,11 @@ SongLoader::Result SongLoader::LoadLocalPartial(const QString &filename) {
|
|||||||
SongLoader::Result SongLoader::LoadAudioCD() {
|
SongLoader::Result SongLoader::LoadAudioCD() {
|
||||||
|
|
||||||
#ifdef HAVE_AUDIOCD
|
#ifdef HAVE_AUDIOCD
|
||||||
CddaSongLoader *cdda_song_loader = new CddaSongLoader(QUrl(), this);
|
CDDASongLoader *cdda_song_loader = new CDDASongLoader(QUrl(), this);
|
||||||
QObject::connect(cdda_song_loader, &CddaSongLoader::SongsDurationLoaded, this, &SongLoader::AudioCDTracksLoadFinishedSlot);
|
QObject::connect(cdda_song_loader, &CDDASongLoader::LoadError, this, &SongLoader::AudioCDTracksLoadErrorSlot);
|
||||||
QObject::connect(cdda_song_loader, &CddaSongLoader::SongsMetadataLoaded, this, &SongLoader::AudioCDTracksTagsLoaded);
|
QObject::connect(cdda_song_loader, &CDDASongLoader::SongsLoaded, this, &SongLoader::AudioCDTracksLoadedSlot);
|
||||||
|
QObject::connect(cdda_song_loader, &CDDASongLoader::SongsUpdated, this, &SongLoader::AudioCDTracksUpdatedSlot);
|
||||||
|
QObject::connect(cdda_song_loader, &CDDASongLoader::LoadingFinished, this, &SongLoader::AudioCDLoadingFinishedSlot);
|
||||||
cdda_song_loader->LoadSongs();
|
cdda_song_loader->LoadSongs();
|
||||||
return Result::Success;
|
return Result::Success;
|
||||||
#else
|
#else
|
||||||
@@ -192,23 +194,38 @@ SongLoader::Result SongLoader::LoadAudioCD() {
|
|||||||
|
|
||||||
#ifdef HAVE_AUDIOCD
|
#ifdef HAVE_AUDIOCD
|
||||||
|
|
||||||
void SongLoader::AudioCDTracksLoadFinishedSlot(const SongList &songs, const QString &error) {
|
void SongLoader::AudioCDTracksLoadErrorSlot(const QString &error) {
|
||||||
|
|
||||||
songs_ = songs;
|
|
||||||
errors_ << error;
|
errors_ << error;
|
||||||
Q_EMIT AudioCDTracksLoadFinished();
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void SongLoader::AudioCDTracksTagsLoaded(const SongList &songs) {
|
void SongLoader::AudioCDTracksLoadedSlot(const SongList &songs) {
|
||||||
|
|
||||||
CddaSongLoader *cdda_song_loader = qobject_cast<CddaSongLoader*>(sender());
|
|
||||||
cdda_song_loader->deleteLater();
|
|
||||||
songs_ = songs;
|
songs_ = songs;
|
||||||
Q_EMIT LoadAudioCDFinished(true);
|
|
||||||
|
Q_EMIT AudioCDTracksLoaded();
|
||||||
|
|
||||||
}
|
}
|
||||||
#endif
|
|
||||||
|
void SongLoader::AudioCDTracksUpdatedSlot(const SongList &songs) {
|
||||||
|
|
||||||
|
songs_ = songs;
|
||||||
|
|
||||||
|
Q_EMIT AudioCDTracksUpdated();
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
void SongLoader::AudioCDLoadingFinishedSlot() {
|
||||||
|
|
||||||
|
CDDASongLoader *cdda_song_loader = qobject_cast<CDDASongLoader*>(sender());
|
||||||
|
cdda_song_loader->deleteLater();
|
||||||
|
|
||||||
|
Q_EMIT AudioCDLoadingFinished(true);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif // HAVE_AUDIOCD
|
||||||
|
|
||||||
SongLoader::Result SongLoader::LoadLocal(const QString &filename) {
|
SongLoader::Result SongLoader::LoadLocal(const QString &filename) {
|
||||||
|
|
||||||
|
|||||||
@@ -50,7 +50,7 @@ class ParserBase;
|
|||||||
class CueParser;
|
class CueParser;
|
||||||
|
|
||||||
#ifdef HAVE_AUDIOCD
|
#ifdef HAVE_AUDIOCD
|
||||||
class CddaSongLoader;
|
class CDDASongLoader;
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
class SongLoader : public QObject {
|
class SongLoader : public QObject {
|
||||||
@@ -90,17 +90,21 @@ class SongLoader : public QObject {
|
|||||||
QStringList errors() { return errors_; }
|
QStringList errors() { return errors_; }
|
||||||
|
|
||||||
Q_SIGNALS:
|
Q_SIGNALS:
|
||||||
void AudioCDTracksLoadFinished();
|
void AudioCDTracksLoaded();
|
||||||
void LoadAudioCDFinished(const bool success);
|
void AudioCDTracksUpdated();
|
||||||
|
void AudioCDLoadingFinished(const bool success);
|
||||||
void LoadRemoteFinished();
|
void LoadRemoteFinished();
|
||||||
|
|
||||||
private Q_SLOTS:
|
private Q_SLOTS:
|
||||||
void ScheduleTimeout();
|
void ScheduleTimeout();
|
||||||
void Timeout();
|
void Timeout();
|
||||||
void StopTypefind();
|
void StopTypefind();
|
||||||
|
|
||||||
#ifdef HAVE_AUDIOCD
|
#ifdef HAVE_AUDIOCD
|
||||||
void AudioCDTracksLoadFinishedSlot(const SongList &songs, const QString &error);
|
void AudioCDTracksLoadErrorSlot(const QString &error);
|
||||||
void AudioCDTracksTagsLoaded(const SongList &songs);
|
void AudioCDTracksLoadedSlot(const SongList &songs);
|
||||||
|
void AudioCDTracksUpdatedSlot(const SongList &songs);
|
||||||
|
void AudioCDLoadingFinishedSlot();
|
||||||
#endif // HAVE_AUDIOCD
|
#endif // HAVE_AUDIOCD
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
|||||||
@@ -31,8 +31,8 @@
|
|||||||
#include <QTextStream>
|
#include <QTextStream>
|
||||||
#include <QFile>
|
#include <QFile>
|
||||||
#include <QString>
|
#include <QString>
|
||||||
#include <QColor>
|
|
||||||
#include <QPalette>
|
#include <QPalette>
|
||||||
|
#include <QColor>
|
||||||
#include <QEvent>
|
#include <QEvent>
|
||||||
|
|
||||||
#include "includes/shared_ptr.h"
|
#include "includes/shared_ptr.h"
|
||||||
@@ -80,20 +80,13 @@ void StyleSheetLoader::UpdateStyleSheet(QWidget *widget, SharedPtr<StyleSheetDat
|
|||||||
// Replace %palette-role with actual colours
|
// Replace %palette-role with actual colours
|
||||||
QPalette p(widget->palette());
|
QPalette p(widget->palette());
|
||||||
|
|
||||||
{
|
QColor color_altbase = p.color(QPalette::AlternateBase);
|
||||||
QColor alt = p.color(QPalette::AlternateBase);
|
|
||||||
#ifdef Q_OS_MACOS
|
#ifdef Q_OS_MACOS
|
||||||
if (alt.lightness() > 180) {
|
color_altbase.setAlpha(color_altbase.alpha() >= 180 ? (color_altbase.lightness() > 180 ? 130 : 16) : color_altbase.alpha());
|
||||||
alt.setAlpha(130);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
alt.setAlpha(16);
|
|
||||||
}
|
|
||||||
#else
|
#else
|
||||||
alt.setAlpha(130);
|
color_altbase.setAlpha(color_altbase.alpha() >= 180 ? 116 : color_altbase.alpha());
|
||||||
#endif
|
#endif
|
||||||
stylesheet.replace("%palette-alternate-base"_L1, QStringLiteral("rgba(%1,%2,%3,%4)").arg(alt.red()).arg(alt.green()).arg(alt.blue()).arg(alt.alpha()));
|
stylesheet.replace("%palette-alternate-base"_L1, QStringLiteral("rgba(%1,%2,%3,%4)").arg(color_altbase.red()).arg(color_altbase.green()).arg(color_altbase.blue()).arg(color_altbase.alpha()));
|
||||||
}
|
|
||||||
|
|
||||||
ReplaceColor(&stylesheet, u"Window"_s, p, QPalette::Window);
|
ReplaceColor(&stylesheet, u"Window"_s, p, QPalette::Window);
|
||||||
ReplaceColor(&stylesheet, u"Background"_s, p, QPalette::Window);
|
ReplaceColor(&stylesheet, u"Background"_s, p, QPalette::Window);
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
* Strawberry Music Player
|
* Strawberry Music Player
|
||||||
* This file was part of Clementine.
|
* This file was part of Clementine.
|
||||||
* Copyright 2010, David Sansome <me@davidsansome.com>
|
* Copyright 2010, David Sansome <me@davidsansome.com>
|
||||||
* Copyright 2018-2021, Jonas Kvinge <jonas@jkvinge.net>
|
* Copyright 2018-2025, Jonas Kvinge <jonas@jkvinge.net>
|
||||||
*
|
*
|
||||||
* Strawberry is free software: you can redistribute it and/or modify
|
* Strawberry is free software: you can redistribute it and/or modify
|
||||||
* it under the terms of the GNU General Public License as published by
|
* it under the terms of the GNU General Public License as published by
|
||||||
@@ -21,10 +21,19 @@
|
|||||||
|
|
||||||
#include "config.h"
|
#include "config.h"
|
||||||
|
|
||||||
|
#include <cstddef>
|
||||||
|
|
||||||
|
#include <cdio/types.h>
|
||||||
|
#include <cdio/cdio.h>
|
||||||
|
|
||||||
|
#include <chrono>
|
||||||
|
|
||||||
#include <QString>
|
#include <QString>
|
||||||
#include <QUrl>
|
#include <QUrl>
|
||||||
|
#include <QTimer>
|
||||||
|
|
||||||
#include "includes/shared_ptr.h"
|
#include "includes/shared_ptr.h"
|
||||||
|
#include "core/logging.h"
|
||||||
#include "collection/collectionmodel.h"
|
#include "collection/collectionmodel.h"
|
||||||
#include "cddasongloader.h"
|
#include "cddasongloader.h"
|
||||||
#include "connecteddevice.h"
|
#include "connecteddevice.h"
|
||||||
@@ -33,7 +42,9 @@
|
|||||||
class DeviceLister;
|
class DeviceLister;
|
||||||
class DeviceManager;
|
class DeviceManager;
|
||||||
|
|
||||||
CddaDevice::CddaDevice(const QUrl &url,
|
using namespace std::chrono_literals;
|
||||||
|
|
||||||
|
CDDADevice::CDDADevice(const QUrl &url,
|
||||||
DeviceLister *lister,
|
DeviceLister *lister,
|
||||||
const QString &unique_id,
|
const QString &unique_id,
|
||||||
DeviceManager *device_manager,
|
DeviceManager *device_manager,
|
||||||
@@ -45,36 +56,86 @@ CddaDevice::CddaDevice(const QUrl &url,
|
|||||||
const bool first_time,
|
const bool first_time,
|
||||||
QObject *parent)
|
QObject *parent)
|
||||||
: ConnectedDevice(url, lister, unique_id, device_manager, task_manager, database, tagreader_client, albumcover_loader, database_id, first_time, parent),
|
: ConnectedDevice(url, lister, unique_id, device_manager, task_manager, database, tagreader_client, albumcover_loader, database_id, first_time, parent),
|
||||||
cdda_song_loader_(url) {
|
cdda_song_loader_(url),
|
||||||
|
cdio_(nullptr),
|
||||||
|
timer_disc_changed_(new QTimer(this)) {
|
||||||
|
|
||||||
QObject::connect(&cdda_song_loader_, &CddaSongLoader::SongsLoaded, this, &CddaDevice::SongsLoaded);
|
timer_disc_changed_->setInterval(1s);
|
||||||
QObject::connect(&cdda_song_loader_, &CddaSongLoader::SongsDurationLoaded, this, &CddaDevice::SongsLoaded);
|
|
||||||
QObject::connect(&cdda_song_loader_, &CddaSongLoader::SongsMetadataLoaded, this, &CddaDevice::SongsLoaded);
|
QObject::connect(&cdda_song_loader_, &CDDASongLoader::SongsLoaded, this, &CDDADevice::SongsLoaded);
|
||||||
QObject::connect(this, &CddaDevice::SongsDiscovered, collection_model_, &CollectionModel::AddReAddOrUpdate);
|
QObject::connect(&cdda_song_loader_, &CDDASongLoader::SongsUpdated, this, &CDDADevice::SongsLoaded);
|
||||||
|
QObject::connect(&cdda_song_loader_, &CDDASongLoader::LoadingFinished, this, &CDDADevice::SongLoadingFinished);
|
||||||
|
QObject::connect(this, &CDDADevice::SongsDiscovered, collection_model_, &CollectionModel::AddReAddOrUpdate);
|
||||||
|
QObject::connect(timer_disc_changed_, &QTimer::timeout, this, &CDDADevice::CheckDiscChanged);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bool CddaDevice::Init() {
|
CDDADevice::~CDDADevice() {
|
||||||
|
|
||||||
|
if (cdio_) {
|
||||||
|
cdio_destroy(cdio_);
|
||||||
|
cdio_ = nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
bool CDDADevice::Init() {
|
||||||
|
|
||||||
|
if (!cdio_) {
|
||||||
|
cdio_ = cdio_open(url_.path().toLocal8Bit().constData(), DRIVER_DEVICE);
|
||||||
|
if (!cdio_) return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
LoadSongs();
|
||||||
|
|
||||||
|
WatchForDiscChanges(true);
|
||||||
|
|
||||||
song_count_ = 0; // Reset song count, in case it was already set
|
|
||||||
cdda_song_loader_.LoadSongs();
|
|
||||||
return true;
|
return true;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void CddaDevice::Refresh() {
|
void CDDADevice::WatchForDiscChanges(const bool watch) {
|
||||||
|
|
||||||
if (!cdda_song_loader_.HasChanged()) {
|
if (watch && !timer_disc_changed_->isActive()) {
|
||||||
return;
|
timer_disc_changed_->start();
|
||||||
|
}
|
||||||
|
else if (!watch && timer_disc_changed_->isActive()) {
|
||||||
|
timer_disc_changed_->stop();
|
||||||
}
|
}
|
||||||
Init();
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void CddaDevice::SongsLoaded(const SongList &songs) {
|
void CDDADevice::CheckDiscChanged() {
|
||||||
|
|
||||||
|
if (!cdio_ || cdda_song_loader_.IsActive()) return;
|
||||||
|
|
||||||
|
if (cdio_get_media_changed(cdio_) == 1) {
|
||||||
|
qLog(Debug) << "CD changed, reloading songs";
|
||||||
|
SongsLoaded();
|
||||||
|
LoadSongs();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
void CDDADevice::LoadSongs() {
|
||||||
|
|
||||||
|
cdda_song_loader_.LoadSongs();
|
||||||
|
WatchForDiscChanges(false);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
void CDDADevice::SongsLoaded(const SongList &songs) {
|
||||||
|
|
||||||
collection_model_->Reset();
|
collection_model_->Reset();
|
||||||
Q_EMIT SongsDiscovered(songs);
|
Q_EMIT SongsDiscovered(songs);
|
||||||
song_count_ = songs.size();
|
song_count_ = songs.size();
|
||||||
|
(void)cdio_get_media_changed(cdio_);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void CDDADevice::SongLoadingFinished() {
|
||||||
|
|
||||||
|
WatchForDiscChanges(true);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
* Strawberry Music Player
|
* Strawberry Music Player
|
||||||
* This file was part of Clementine.
|
* This file was part of Clementine.
|
||||||
* Copyright 2010, David Sansome <me@davidsansome.com>
|
* Copyright 2010, David Sansome <me@davidsansome.com>
|
||||||
* Copyright 2018-2021, Jonas Kvinge <jonas@jkvinge.net>
|
* Copyright 2018-2025, Jonas Kvinge <jonas@jkvinge.net>
|
||||||
*
|
*
|
||||||
* Strawberry is free software: you can redistribute it and/or modify
|
* Strawberry is free software: you can redistribute it and/or modify
|
||||||
* it under the terms of the GNU General Public License as published by
|
* it under the terms of the GNU General Public License as published by
|
||||||
@@ -24,6 +24,11 @@
|
|||||||
|
|
||||||
#include "config.h"
|
#include "config.h"
|
||||||
|
|
||||||
|
#include <cstddef>
|
||||||
|
|
||||||
|
#include <cdio/types.h>
|
||||||
|
#include <cdio/cdio.h>
|
||||||
|
|
||||||
#include <QObject>
|
#include <QObject>
|
||||||
#include <QString>
|
#include <QString>
|
||||||
#include <QStringList>
|
#include <QStringList>
|
||||||
@@ -35,6 +40,8 @@
|
|||||||
#include "cddasongloader.h"
|
#include "cddasongloader.h"
|
||||||
#include "connecteddevice.h"
|
#include "connecteddevice.h"
|
||||||
|
|
||||||
|
class QTimer;
|
||||||
|
|
||||||
class DeviceLister;
|
class DeviceLister;
|
||||||
class DeviceManager;
|
class DeviceManager;
|
||||||
class TaskManager;
|
class TaskManager;
|
||||||
@@ -42,11 +49,11 @@ class Database;
|
|||||||
class TagReaderClient;
|
class TagReaderClient;
|
||||||
class AlbumCoverLoader;
|
class AlbumCoverLoader;
|
||||||
|
|
||||||
class CddaDevice : public ConnectedDevice {
|
class CDDADevice : public ConnectedDevice {
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
|
|
||||||
public:
|
public:
|
||||||
Q_INVOKABLE explicit CddaDevice(const QUrl &url,
|
Q_INVOKABLE explicit CDDADevice(const QUrl &url,
|
||||||
DeviceLister *lister,
|
DeviceLister *lister,
|
||||||
const QString &unique_id,
|
const QString &unique_id,
|
||||||
DeviceManager *device_manager,
|
DeviceManager *device_manager,
|
||||||
@@ -58,21 +65,29 @@ class CddaDevice : public ConnectedDevice {
|
|||||||
const bool first_time,
|
const bool first_time,
|
||||||
QObject *parent = nullptr);
|
QObject *parent = nullptr);
|
||||||
|
|
||||||
|
~CDDADevice();
|
||||||
|
|
||||||
bool Init() override;
|
bool Init() override;
|
||||||
void Refresh() override;
|
|
||||||
bool CopyToStorage(const CopyJob&, QString&) override { return false; }
|
bool CopyToStorage(const CopyJob&, QString&) override { return false; }
|
||||||
bool DeleteFromStorage(const MusicStorage::DeleteJob&) override { return false; }
|
bool DeleteFromStorage(const MusicStorage::DeleteJob&) override { return false; }
|
||||||
|
|
||||||
static QStringList url_schemes() { return QStringList() << QStringLiteral("cdda"); }
|
static QStringList url_schemes() { return QStringList() << QStringLiteral("cdda"); }
|
||||||
|
|
||||||
|
void LoadSongs();
|
||||||
|
void WatchForDiscChanges(const bool watch);
|
||||||
|
|
||||||
Q_SIGNALS:
|
Q_SIGNALS:
|
||||||
void SongsDiscovered(const SongList &songs);
|
void SongsDiscovered(const SongList &songs);
|
||||||
|
|
||||||
private Q_SLOTS:
|
private Q_SLOTS:
|
||||||
void SongsLoaded(const SongList &songs);
|
void CheckDiscChanged();
|
||||||
|
void SongsLoaded(const SongList &songs = SongList());
|
||||||
|
void SongLoadingFinished();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
CddaSongLoader cdda_song_loader_;
|
CDDASongLoader cdda_song_loader_;
|
||||||
|
CdIo_t *cdio_;
|
||||||
|
QTimer *timer_disc_changed_;
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif // CDDADEVICE_H
|
#endif // CDDADEVICE_H
|
||||||
|
|||||||
@@ -40,9 +40,9 @@
|
|||||||
|
|
||||||
using namespace Qt::Literals::StringLiterals;
|
using namespace Qt::Literals::StringLiterals;
|
||||||
|
|
||||||
QStringList CddaLister::DeviceUniqueIDs() { return devices_list_; }
|
QStringList CDDALister::DeviceUniqueIDs() { return devices_list_; }
|
||||||
|
|
||||||
QVariantList CddaLister::DeviceIcons(const QString &id) {
|
QVariantList CDDALister::DeviceIcons(const QString &id) {
|
||||||
|
|
||||||
Q_UNUSED(id)
|
Q_UNUSED(id)
|
||||||
|
|
||||||
@@ -52,7 +52,7 @@ QVariantList CddaLister::DeviceIcons(const QString &id) {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
QString CddaLister::DeviceManufacturer(const QString &id) {
|
QString CDDALister::DeviceManufacturer(const QString &id) {
|
||||||
|
|
||||||
CdIo_t *cdio = cdio_open(id.toLocal8Bit().constData(), DRIVER_DEVICE);
|
CdIo_t *cdio = cdio_open(id.toLocal8Bit().constData(), DRIVER_DEVICE);
|
||||||
cdio_hwinfo_t cd_info;
|
cdio_hwinfo_t cd_info;
|
||||||
@@ -65,7 +65,7 @@ QString CddaLister::DeviceManufacturer(const QString &id) {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
QString CddaLister::DeviceModel(const QString &id) {
|
QString CDDALister::DeviceModel(const QString &id) {
|
||||||
|
|
||||||
CdIo_t *cdio = cdio_open(id.toLocal8Bit().constData(), DRIVER_DEVICE);
|
CdIo_t *cdio = cdio_open(id.toLocal8Bit().constData(), DRIVER_DEVICE);
|
||||||
cdio_hwinfo_t cd_info;
|
cdio_hwinfo_t cd_info;
|
||||||
@@ -78,7 +78,7 @@ QString CddaLister::DeviceModel(const QString &id) {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
quint64 CddaLister::DeviceCapacity(const QString &id) {
|
quint64 CDDALister::DeviceCapacity(const QString &id) {
|
||||||
|
|
||||||
Q_UNUSED(id)
|
Q_UNUSED(id)
|
||||||
|
|
||||||
@@ -86,7 +86,7 @@ quint64 CddaLister::DeviceCapacity(const QString &id) {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
quint64 CddaLister::DeviceFreeSpace(const QString &id) {
|
quint64 CDDALister::DeviceFreeSpace(const QString &id) {
|
||||||
|
|
||||||
Q_UNUSED(id)
|
Q_UNUSED(id)
|
||||||
|
|
||||||
@@ -94,37 +94,38 @@ quint64 CddaLister::DeviceFreeSpace(const QString &id) {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
QVariantMap CddaLister::DeviceHardwareInfo(const QString &id) {
|
QVariantMap CDDALister::DeviceHardwareInfo(const QString &id) {
|
||||||
Q_UNUSED(id)
|
Q_UNUSED(id)
|
||||||
return QVariantMap();
|
return QVariantMap();
|
||||||
}
|
}
|
||||||
|
|
||||||
QString CddaLister::MakeFriendlyName(const QString &id) {
|
QString CDDALister::MakeFriendlyName(const QString &id) {
|
||||||
|
|
||||||
CdIo_t *cdio = cdio_open(id.toLocal8Bit().constData(), DRIVER_DEVICE);
|
CdIo_t *cdio = cdio_open(id.toLocal8Bit().constData(), DRIVER_DEVICE);
|
||||||
cdio_hwinfo_t cd_info;
|
cdio_hwinfo_t cd_info;
|
||||||
if (cdio_get_hwinfo(cdio, &cd_info)) {
|
if (cdio_get_hwinfo(cdio, &cd_info)) {
|
||||||
|
const QString friendly_name = QString::fromUtf8(cd_info.psz_model).trimmed();
|
||||||
cdio_destroy(cdio);
|
cdio_destroy(cdio);
|
||||||
return QString::fromUtf8(cd_info.psz_model);
|
return friendly_name;
|
||||||
}
|
}
|
||||||
cdio_destroy(cdio);
|
cdio_destroy(cdio);
|
||||||
return u"CD ("_s + id + QLatin1Char(')');
|
return u"CD ("_s + id + QLatin1Char(')');
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
QList<QUrl> CddaLister::MakeDeviceUrls(const QString &id) {
|
QList<QUrl> CDDALister::MakeDeviceUrls(const QString &id) {
|
||||||
return QList<QUrl>() << QUrl(u"cdda://"_s + id);
|
return QList<QUrl>() << QUrl(u"cdda://"_s + id);
|
||||||
}
|
}
|
||||||
|
|
||||||
void CddaLister::UnmountDevice(const QString &id) {
|
void CDDALister::UnmountDevice(const QString &id) {
|
||||||
cdio_eject_media_drive(id.toLocal8Bit().constData());
|
cdio_eject_media_drive(id.toLocal8Bit().constData());
|
||||||
}
|
}
|
||||||
|
|
||||||
void CddaLister::UpdateDeviceFreeSpace(const QString &id) {
|
void CDDALister::UpdateDeviceFreeSpace(const QString &id) {
|
||||||
Q_UNUSED(id)
|
Q_UNUSED(id)
|
||||||
}
|
}
|
||||||
|
|
||||||
bool CddaLister::Init() {
|
bool CDDALister::Init() {
|
||||||
|
|
||||||
cdio_init();
|
cdio_init();
|
||||||
#ifdef Q_OS_MACOS
|
#ifdef Q_OS_MACOS
|
||||||
|
|||||||
@@ -34,11 +34,11 @@
|
|||||||
|
|
||||||
#include "devicelister.h"
|
#include "devicelister.h"
|
||||||
|
|
||||||
class CddaLister : public DeviceLister {
|
class CDDALister : public DeviceLister {
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
|
|
||||||
public:
|
public:
|
||||||
explicit CddaLister(QObject *parent = nullptr) : DeviceLister(parent) {}
|
explicit CDDALister(QObject *parent = nullptr) : DeviceLister(parent) {}
|
||||||
|
|
||||||
QStringList DeviceUniqueIDs() override;
|
QStringList DeviceUniqueIDs() override;
|
||||||
QVariantList DeviceIcons(const QString &id) override;
|
QVariantList DeviceIcons(const QString &id) override;
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
* Strawberry Music Player
|
* Strawberry Music Player
|
||||||
* This file was part of Clementine.
|
* This file was part of Clementine.
|
||||||
* Copyright 2010, David Sansome <me@davidsansome.com>
|
* Copyright 2010, David Sansome <me@davidsansome.com>
|
||||||
* Copyright 2018-2021, Jonas Kvinge <jonas@jkvinge.net>
|
* Copyright 2018-2025, Jonas Kvinge <jonas@jkvinge.net>
|
||||||
*
|
*
|
||||||
* Strawberry is free software: you can redistribute it and/or modify
|
* Strawberry is free software: you can redistribute it and/or modify
|
||||||
* it under the terms of the GNU General Public License as published by
|
* it under the terms of the GNU General Public License as published by
|
||||||
@@ -21,25 +21,24 @@
|
|||||||
|
|
||||||
#include "config.h"
|
#include "config.h"
|
||||||
|
|
||||||
#include <memory>
|
#include <QtGlobal>
|
||||||
|
|
||||||
#include <cstddef>
|
#include <memory>
|
||||||
|
|
||||||
#include <glib.h>
|
#include <glib.h>
|
||||||
#include <glib/gtypes.h>
|
#include <glib/gtypes.h>
|
||||||
#include <glib-object.h>
|
#include <glib-object.h>
|
||||||
|
|
||||||
#include <cdio/cdio.h>
|
|
||||||
|
|
||||||
#include <gst/gst.h>
|
#include <gst/gst.h>
|
||||||
#include <gst/tag/tag.h>
|
#include <gst/tag/tag.h>
|
||||||
|
|
||||||
#include <QtGlobal>
|
|
||||||
#include <QObject>
|
#include <QObject>
|
||||||
#include <QMutex>
|
#include <QMutex>
|
||||||
#include <QByteArray>
|
#include <QByteArray>
|
||||||
#include <QString>
|
#include <QString>
|
||||||
#include <QUrl>
|
#include <QUrl>
|
||||||
|
#include <QtConcurrentRun>
|
||||||
|
#include <QScopeGuard>
|
||||||
|
|
||||||
#include "cddasongloader.h"
|
#include "cddasongloader.h"
|
||||||
#include "includes/shared_ptr.h"
|
#include "includes/shared_ptr.h"
|
||||||
@@ -51,18 +50,21 @@ using std::make_shared;
|
|||||||
|
|
||||||
using namespace Qt::Literals::StringLiterals;
|
using namespace Qt::Literals::StringLiterals;
|
||||||
|
|
||||||
CddaSongLoader::CddaSongLoader(const QUrl &url, QObject *parent)
|
CDDASongLoader::CDDASongLoader(const QUrl &url, QObject *parent)
|
||||||
: QObject(parent),
|
: QObject(parent),
|
||||||
url_(url),
|
url_(url),
|
||||||
network_(make_shared<NetworkAccessManager>()),
|
network_(make_shared<NetworkAccessManager>()) {
|
||||||
cdda_(nullptr),
|
|
||||||
cdio_(nullptr) {}
|
|
||||||
|
|
||||||
CddaSongLoader::~CddaSongLoader() {
|
#ifdef HAVE_MUSICBRAINZ
|
||||||
if (cdio_) cdio_destroy(cdio_);
|
QObject::connect(this, &CDDASongLoader::LoadTagsFromMusicBrainz, this, &CDDASongLoader::LoadTagsFromMusicBrainzSlot);
|
||||||
|
#endif // HAVE_MUSICBRAINZ
|
||||||
}
|
}
|
||||||
|
|
||||||
QUrl CddaSongLoader::GetUrlFromTrack(int track_number) const {
|
CDDASongLoader::~CDDASongLoader() {
|
||||||
|
loading_future_.waitForFinished();
|
||||||
|
}
|
||||||
|
|
||||||
|
QUrl CDDASongLoader::GetUrlFromTrack(int track_number) const {
|
||||||
|
|
||||||
if (url_.isEmpty()) {
|
if (url_.isEmpty()) {
|
||||||
return QUrl(QStringLiteral("cdda://%1a").arg(track_number));
|
return QUrl(QStringLiteral("cdda://%1a").arg(track_number));
|
||||||
@@ -72,72 +74,77 @@ QUrl CddaSongLoader::GetUrlFromTrack(int track_number) const {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void CddaSongLoader::LoadSongs() {
|
void CDDASongLoader::LoadSongs() {
|
||||||
|
|
||||||
QMutexLocker locker(&mutex_load_);
|
if (IsActive()) {
|
||||||
cdio_ = cdio_open(url_.path().toLocal8Bit().constData(), DRIVER_DEVICE);
|
|
||||||
if (cdio_ == nullptr) {
|
|
||||||
Error(u"Unable to open CDIO device."_s);
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create gstreamer cdda element
|
loading_future_ = QtConcurrent::run(&CDDASongLoader::LoadSongsFromCDDA, this);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
void CDDASongLoader::LoadSongsFromCDDA() {
|
||||||
|
|
||||||
|
QMutexLocker l(&mutex_load_);
|
||||||
|
|
||||||
GError *error = nullptr;
|
GError *error = nullptr;
|
||||||
cdda_ = gst_element_make_from_uri(GST_URI_SRC, "cdda://", nullptr, &error);
|
GstElement *cdda = gst_element_factory_make("cdiocddasrc", nullptr);
|
||||||
if (error) {
|
if (error) {
|
||||||
Error(QStringLiteral("%1: %2").arg(error->code).arg(QString::fromUtf8(error->message)));
|
Error(QStringLiteral("%1: %2").arg(error->code).arg(QString::fromUtf8(error->message)));
|
||||||
}
|
}
|
||||||
if (!cdda_) return;
|
if (!cdda) {
|
||||||
|
Error(tr("Could not create cdiocddasrc"));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (!url_.isEmpty()) {
|
if (!url_.isEmpty()) {
|
||||||
g_object_set(cdda_, "device", g_strdup(url_.path().toLocal8Bit().constData()), nullptr);
|
g_object_set(cdda, "device", g_strdup(url_.path().toLocal8Bit().constData()), nullptr);
|
||||||
}
|
}
|
||||||
if (g_object_class_find_property(G_OBJECT_GET_CLASS(cdda_), "paranoia-mode")) {
|
if (g_object_class_find_property(G_OBJECT_GET_CLASS(cdda), "paranoia-mode")) {
|
||||||
g_object_set(cdda_, "paranoia-mode", 0, nullptr);
|
g_object_set(cdda, "paranoia-mode", 0, nullptr);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Change the element's state to ready and paused, to be able to query it
|
// Change the element's state to ready and paused, to be able to query it
|
||||||
if (gst_element_set_state(cdda_, GST_STATE_READY) == GST_STATE_CHANGE_FAILURE) {
|
if (gst_element_set_state(cdda, GST_STATE_READY) == GST_STATE_CHANGE_FAILURE) {
|
||||||
gst_element_set_state(cdda_, GST_STATE_NULL);
|
gst_element_set_state(cdda, GST_STATE_NULL);
|
||||||
gst_object_unref(GST_OBJECT(cdda_));
|
gst_object_unref(GST_OBJECT(cdda));
|
||||||
cdda_ = nullptr;
|
cdda = nullptr;
|
||||||
Error(tr("Error while setting CDDA device to ready state."));
|
Error(tr("Error while setting CDDA device to ready state."));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (gst_element_set_state(cdda_, GST_STATE_PAUSED) == GST_STATE_CHANGE_FAILURE) {
|
if (gst_element_set_state(cdda, GST_STATE_PAUSED) == GST_STATE_CHANGE_FAILURE) {
|
||||||
gst_element_set_state(cdda_, GST_STATE_NULL);
|
gst_element_set_state(cdda, GST_STATE_NULL);
|
||||||
gst_object_unref(GST_OBJECT(cdda_));
|
gst_object_unref(GST_OBJECT(cdda));
|
||||||
cdda_ = nullptr;
|
cdda = nullptr;
|
||||||
Error(tr("Error while setting CDDA device to pause state."));
|
Error(tr("Error while setting CDDA device to pause state."));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get number of tracks
|
// Get number of tracks
|
||||||
GstFormat fmt = gst_format_get_by_nick("track");
|
GstFormat format_track = gst_format_get_by_nick("track");
|
||||||
GstFormat out_fmt = fmt;
|
GstFormat format_duration = format_track;
|
||||||
gint64 num_tracks = 0;
|
gint64 total_tracks = 0;
|
||||||
if (!gst_element_query_duration(cdda_, out_fmt, &num_tracks)) {
|
if (!gst_element_query_duration(cdda, format_duration, &total_tracks)) {
|
||||||
gst_element_set_state(cdda_, GST_STATE_NULL);
|
gst_element_set_state(cdda, GST_STATE_NULL);
|
||||||
gst_object_unref(GST_OBJECT(cdda_));
|
gst_object_unref(GST_OBJECT(cdda));
|
||||||
cdda_ = nullptr;
|
cdda = nullptr;
|
||||||
Error(tr("Error while querying CDDA tracks."));
|
Error(tr("Error while querying CDDA tracks."));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (out_fmt != fmt) {
|
if (format_duration != format_track) {
|
||||||
qLog(Error) << "Error while querying cdda GstElement (2).";
|
qLog(Error) << "Error while querying CDDA GstElement (2).";
|
||||||
gst_element_set_state(cdda_, GST_STATE_NULL);
|
gst_element_set_state(cdda, GST_STATE_NULL);
|
||||||
gst_object_unref(GST_OBJECT(cdda_));
|
gst_object_unref(GST_OBJECT(cdda));
|
||||||
cdda_ = nullptr;
|
cdda = nullptr;
|
||||||
Error(tr("Error while querying CDDA tracks."));
|
Error(tr("Error while querying CDDA tracks."));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
SongList songs;
|
QMap<int, Song> songs;
|
||||||
songs.reserve(num_tracks);
|
for (int track_number = 1; track_number <= total_tracks; ++track_number) {
|
||||||
for (int track_number = 1; track_number <= num_tracks; ++track_number) {
|
|
||||||
// Init song
|
|
||||||
Song song(Song::Source::CDDA);
|
Song song(Song::Source::CDDA);
|
||||||
song.set_id(track_number);
|
song.set_id(track_number);
|
||||||
song.set_valid(true);
|
song.set_valid(true);
|
||||||
@@ -145,129 +152,269 @@ void CddaSongLoader::LoadSongs() {
|
|||||||
song.set_url(GetUrlFromTrack(track_number));
|
song.set_url(GetUrlFromTrack(track_number));
|
||||||
song.set_title(QStringLiteral("Track %1").arg(track_number));
|
song.set_title(QStringLiteral("Track %1").arg(track_number));
|
||||||
song.set_track(track_number);
|
song.set_track(track_number);
|
||||||
songs << song;
|
songs.insert(track_number, song);
|
||||||
}
|
}
|
||||||
Q_EMIT SongsLoaded(songs);
|
|
||||||
|
|
||||||
|
Q_EMIT SongsLoaded(songs.values());
|
||||||
|
|
||||||
|
#ifdef HAVE_MUSICBRAINZ
|
||||||
gst_tag_register_musicbrainz_tags();
|
gst_tag_register_musicbrainz_tags();
|
||||||
|
#endif // HAVE_MUSICBRAINZ
|
||||||
|
|
||||||
GstElement *pipeline = gst_pipeline_new("pipeline");
|
GstElement *pipeline = gst_pipeline_new("pipeline");
|
||||||
GstElement *sink = gst_element_factory_make("fakesink", nullptr);
|
GstElement *sink = gst_element_factory_make("fakesink", nullptr);
|
||||||
gst_bin_add_many(GST_BIN(pipeline), cdda_, sink, nullptr);
|
gst_bin_add_many(GST_BIN(pipeline), cdda, sink, nullptr);
|
||||||
gst_element_link(cdda_, sink);
|
gst_element_link(cdda, sink);
|
||||||
gst_element_set_state(pipeline, GST_STATE_READY);
|
gst_element_set_state(pipeline, GST_STATE_READY);
|
||||||
gst_element_set_state(pipeline, GST_STATE_PAUSED);
|
gst_element_set_state(pipeline, GST_STATE_PAUSED);
|
||||||
|
|
||||||
// Get TOC and TAG messages
|
|
||||||
GstMessage *msg = nullptr;
|
GstMessage *msg = nullptr;
|
||||||
GstMessage *msg_toc = nullptr;
|
int track_artist_tags = 0;
|
||||||
GstMessage *msg_tag = nullptr;
|
int track_album_tags = 0;
|
||||||
while ((msg = gst_bus_timed_pop_filtered(GST_ELEMENT_BUS(pipeline), GST_SECOND, static_cast<GstMessageType>(GST_MESSAGE_TOC | GST_MESSAGE_TAG)))) {
|
int track_title_tags = 0;
|
||||||
if (GST_MESSAGE_TYPE(msg) == GST_MESSAGE_TOC) {
|
#ifdef HAVE_MUSICBRAINZ
|
||||||
if (msg_toc) gst_message_unref(msg_toc); // Shouldn't happen, but just in case
|
QString musicbrainz_discid;
|
||||||
msg_toc = msg;
|
#endif // HAVE_MUSICBRAINZ
|
||||||
}
|
GstMessageType msg_filter = static_cast<GstMessageType>(GST_MESSAGE_TOC|GST_MESSAGE_TAG);
|
||||||
else if (GST_MESSAGE_TYPE(msg) == GST_MESSAGE_TAG) {
|
while (msg_filter != 0 && (msg = gst_bus_timed_pop_filtered(GST_ELEMENT_BUS(pipeline), GST_SECOND * 5, msg_filter))) {
|
||||||
if (msg_tag) gst_message_unref(msg_tag);
|
|
||||||
msg_tag = msg;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Handle TOC message: get tracks duration
|
const QScopeGuard scopeguard_msg = qScopeGuard([msg]() {
|
||||||
if (msg_toc) {
|
gst_message_unref(msg);
|
||||||
GstToc *toc = nullptr;
|
});
|
||||||
gst_message_parse_toc(msg_toc, &toc, nullptr);
|
|
||||||
if (toc) {
|
if (GST_MESSAGE_TYPE(msg) == GST_MESSAGE_TOC) {
|
||||||
|
GstToc *toc = nullptr;
|
||||||
|
gst_message_parse_toc(msg, &toc, nullptr);
|
||||||
|
const QScopeGuard scopeguard_toc = qScopeGuard([toc]() {
|
||||||
|
gst_toc_unref(toc);
|
||||||
|
});
|
||||||
GList *entries = gst_toc_get_entries(toc);
|
GList *entries = gst_toc_get_entries(toc);
|
||||||
if (entries && static_cast<guint>(songs.size()) <= g_list_length(entries)) {
|
int track_number = 0;
|
||||||
int i = 0;
|
for (GList *entry_node = entries; entry_node != nullptr; entry_node = entry_node->next) {
|
||||||
for (GList *node = entries; node != nullptr; node = node->next) {
|
++track_number;
|
||||||
GstTocEntry *entry = static_cast<GstTocEntry*>(node->data);
|
if (songs.contains(track_number)) {
|
||||||
qint64 duration = 0;
|
Song &song = songs[track_number];
|
||||||
|
GstTocEntry *entry = static_cast<GstTocEntry*>(entry_node->data);
|
||||||
gint64 start = 0, stop = 0;
|
gint64 start = 0, stop = 0;
|
||||||
if (gst_toc_entry_get_start_stop_times(entry, &start, &stop)) duration = stop - start;
|
if (gst_toc_entry_get_start_stop_times(entry, &start, &stop)) {
|
||||||
songs[i++].set_length_nanosec(duration);
|
song.set_length_nanosec(static_cast<qint64>(stop - start));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
msg_filter = static_cast<GstMessageType>(static_cast<int>(msg_filter) ^ GST_MESSAGE_TOC);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
gst_message_unref(msg_toc);
|
|
||||||
}
|
else if (GST_MESSAGE_TYPE(msg) == GST_MESSAGE_TAG) {
|
||||||
Q_EMIT SongsDurationLoaded(songs);
|
|
||||||
|
GstTagList *tags = nullptr;
|
||||||
|
gst_message_parse_tag(msg, &tags);
|
||||||
|
const QScopeGuard scopeguard_tags = qScopeGuard([tags]() {
|
||||||
|
gst_tag_list_free(tags);
|
||||||
|
});
|
||||||
|
|
||||||
|
gint64 track_index = 0;
|
||||||
|
gst_element_query_position(cdda, format_track, &track_index);
|
||||||
|
|
||||||
|
char *tag = nullptr;
|
||||||
|
|
||||||
#ifdef HAVE_MUSICBRAINZ
|
#ifdef HAVE_MUSICBRAINZ
|
||||||
// Handle TAG message: generate MusicBrainz DiscId
|
if (musicbrainz_discid.isEmpty()) {
|
||||||
if (msg_tag) {
|
if (gst_tag_list_get_string(tags, GST_TAG_CDDA_MUSICBRAINZ_DISCID, &tag)) {
|
||||||
GstTagList *tags = nullptr;
|
musicbrainz_discid = QString::fromUtf8(tag);
|
||||||
gst_message_parse_tag(msg_tag, &tags);
|
g_free(tag);
|
||||||
char *string_mb = nullptr;
|
tag = nullptr;
|
||||||
if (gst_tag_list_get_string(tags, GST_TAG_CDDA_MUSICBRAINZ_DISCID, &string_mb)) {
|
}
|
||||||
QString musicbrainz_discid = QString::fromUtf8(string_mb);
|
}
|
||||||
qLog(Info) << "MusicBrainz discid: " << musicbrainz_discid;
|
|
||||||
|
|
||||||
MusicBrainzClient *musicbrainz_client = new MusicBrainzClient(network_);
|
|
||||||
QObject::connect(musicbrainz_client, &MusicBrainzClient::DiscIdFinished, this, &CddaSongLoader::AudioCDTagsLoaded);
|
|
||||||
musicbrainz_client->StartDiscIdRequest(musicbrainz_discid);
|
|
||||||
g_free(string_mb);
|
|
||||||
gst_message_unref(msg_tag);
|
|
||||||
gst_tag_list_unref(tags);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
guint track_number = 0;
|
||||||
|
if (!gst_tag_list_get_uint(tags, GST_TAG_TRACK_NUMBER, &track_number)) {
|
||||||
|
qLog(Error) << "Could not get track number";
|
||||||
|
msg_filter = static_cast<GstMessageType>(static_cast<int>(msg_filter) ^GST_MESSAGE_TAG);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!songs.contains(track_number)) {
|
||||||
|
qLog(Error) << "Got invalid track number" << track_number;
|
||||||
|
msg_filter = static_cast<GstMessageType>(static_cast<int>(msg_filter) ^GST_MESSAGE_TAG);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
Song &song = songs[track_number];
|
||||||
|
guint64 duration = 0;
|
||||||
|
if (gst_tag_list_get_uint64(tags, GST_TAG_DURATION, &duration)) {
|
||||||
|
song.set_length_nanosec(static_cast<qint64>(duration));
|
||||||
|
}
|
||||||
|
if (gst_tag_list_get_string(tags, GST_TAG_ALBUM_ARTIST, &tag)) {
|
||||||
|
song.set_albumartist(QString::fromUtf8(tag));
|
||||||
|
g_free(tag);
|
||||||
|
tag = nullptr;
|
||||||
|
}
|
||||||
|
if (gst_tag_list_get_string(tags, GST_TAG_ALBUM_ARTIST_SORTNAME, &tag)) {
|
||||||
|
song.set_albumartistsort(QString::fromUtf8(tag));
|
||||||
|
g_free(tag);
|
||||||
|
tag = nullptr;
|
||||||
|
}
|
||||||
|
if (gst_tag_list_get_string(tags, GST_TAG_ARTIST, &tag)) {
|
||||||
|
song.set_artist(QString::fromUtf8(tag));
|
||||||
|
g_free(tag);
|
||||||
|
tag = nullptr;
|
||||||
|
++track_artist_tags;
|
||||||
|
}
|
||||||
|
if (gst_tag_list_get_string(tags, GST_TAG_ARTIST_SORTNAME, &tag)) {
|
||||||
|
song.set_artistsort(QString::fromUtf8(tag));
|
||||||
|
g_free(tag);
|
||||||
|
tag = nullptr;
|
||||||
|
}
|
||||||
|
if (gst_tag_list_get_string(tags, GST_TAG_ALBUM, &tag)) {
|
||||||
|
song.set_album(QString::fromUtf8(tag));
|
||||||
|
g_free(tag);
|
||||||
|
tag = nullptr;
|
||||||
|
++track_album_tags;
|
||||||
|
}
|
||||||
|
if (gst_tag_list_get_string(tags, GST_TAG_ALBUM_SORTNAME, &tag)) {
|
||||||
|
song.set_albumsort(QString::fromUtf8(tag));
|
||||||
|
g_free(tag);
|
||||||
|
tag = nullptr;
|
||||||
|
}
|
||||||
|
if (gst_tag_list_get_string(tags, GST_TAG_TITLE, &tag)) {
|
||||||
|
song.set_title(QString::fromUtf8(tag));
|
||||||
|
g_free(tag);
|
||||||
|
tag = nullptr;
|
||||||
|
++track_title_tags;
|
||||||
|
}
|
||||||
|
if (gst_tag_list_get_string(tags, GST_TAG_TITLE_SORTNAME, &tag)) {
|
||||||
|
song.set_titlesort(QString::fromUtf8(tag));
|
||||||
|
g_free(tag);
|
||||||
|
tag = nullptr;
|
||||||
|
}
|
||||||
|
if (gst_tag_list_get_string(tags, GST_TAG_GENRE, &tag)) {
|
||||||
|
song.set_genre(QString::fromUtf8(tag));
|
||||||
|
g_free(tag);
|
||||||
|
tag = nullptr;
|
||||||
|
}
|
||||||
|
if (gst_tag_list_get_string(tags, GST_TAG_COMPOSER, &tag)) {
|
||||||
|
song.set_composer(QString::fromUtf8(tag));
|
||||||
|
g_free(tag);
|
||||||
|
tag = nullptr;
|
||||||
|
}
|
||||||
|
if (gst_tag_list_get_string(tags, GST_TAG_COMPOSER_SORTNAME, &tag)) {
|
||||||
|
song.set_composersort(QString::fromUtf8(tag));
|
||||||
|
g_free(tag);
|
||||||
|
tag = nullptr;
|
||||||
|
}
|
||||||
|
if (gst_tag_list_get_string(tags, GST_TAG_PERFORMER, &tag)) {
|
||||||
|
song.set_performer(QString::fromUtf8(tag));
|
||||||
|
g_free(tag);
|
||||||
|
tag = nullptr;
|
||||||
|
}
|
||||||
|
if (gst_tag_list_get_string(tags, GST_TAG_COMMENT, &tag)) {
|
||||||
|
song.set_comment(QString::fromUtf8(tag));
|
||||||
|
g_free(tag);
|
||||||
|
tag = nullptr;
|
||||||
|
}
|
||||||
|
guint bitrate = 0;
|
||||||
|
if (gst_tag_list_get_uint(tags, GST_TAG_BITRATE, &bitrate)) {
|
||||||
|
song.set_bitrate(static_cast<int>(bitrate));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (track_number >= total_tracks) {
|
||||||
|
msg_filter = static_cast<GstMessageType>(static_cast<int>(msg_filter) ^GST_MESSAGE_TAG);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
const gint64 next_track_index = track_index + 1;
|
||||||
|
if (!gst_element_seek_simple(pipeline, format_track, static_cast<GstSeekFlags>(GST_SEEK_FLAG_FLUSH | GST_SEEK_FLAG_TRICKMODE), next_track_index)) {
|
||||||
|
qLog(Error) << "Failed to seek to next track index" << next_track_index;
|
||||||
|
msg_filter = static_cast<GstMessageType>(static_cast<int>(msg_filter) ^GST_MESSAGE_TAG);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
gst_element_set_state(pipeline, GST_STATE_NULL);
|
gst_element_set_state(pipeline, GST_STATE_NULL);
|
||||||
// This will also cause cdda_ to be unref'd.
|
// This will also cause cdda to be unref'd.
|
||||||
gst_object_unref(pipeline);
|
gst_object_unref(pipeline);
|
||||||
|
|
||||||
|
if ((track_artist_tags >= total_tracks && track_album_tags >= total_tracks && track_title_tags >= total_tracks)) {
|
||||||
|
qLog(Info) << "Songs loaded from CD-Text";
|
||||||
|
Q_EMIT SongsUpdated(songs.values());
|
||||||
|
Q_EMIT LoadingFinished();
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
#ifdef HAVE_MUSICBRAINZ
|
||||||
|
if (musicbrainz_discid.isEmpty()) {
|
||||||
|
qLog(Info) << "CD is missing tags";
|
||||||
|
Q_EMIT LoadingFinished();
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
qLog(Info) << "MusicBrainz Disc ID:" << musicbrainz_discid;
|
||||||
|
Q_EMIT LoadTagsFromMusicBrainz(musicbrainz_discid);
|
||||||
|
}
|
||||||
|
#else
|
||||||
|
Q_EMIT LoadingFinished();
|
||||||
|
#endif // HAVE_MUSICBRAINZ
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#ifdef HAVE_MUSICBRAINZ
|
#ifdef HAVE_MUSICBRAINZ
|
||||||
void CddaSongLoader::AudioCDTagsLoaded(const QString &artist, const QString &album, const MusicBrainzClient::ResultList &results) {
|
|
||||||
|
void CDDASongLoader::LoadTagsFromMusicBrainzSlot(const QString &musicbrainz_discid) const {
|
||||||
|
|
||||||
|
MusicBrainzClient *musicbrainz_client = new MusicBrainzClient(network_);
|
||||||
|
QObject::connect(musicbrainz_client, &MusicBrainzClient::DiscIdFinished, this, &CDDASongLoader::LoadTagsFromMusicBrainzFinished);
|
||||||
|
musicbrainz_client->StartDiscIdRequest(musicbrainz_discid);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
void CDDASongLoader::LoadTagsFromMusicBrainzFinished(const QString &artist, const QString &album, const MusicBrainzClient::ResultList &results, const QString &error) {
|
||||||
|
|
||||||
MusicBrainzClient *musicbrainz_client = qobject_cast<MusicBrainzClient*>(sender());
|
MusicBrainzClient *musicbrainz_client = qobject_cast<MusicBrainzClient*>(sender());
|
||||||
musicbrainz_client->deleteLater();
|
musicbrainz_client->deleteLater();
|
||||||
if (results.empty()) return;
|
|
||||||
|
if (!error.isEmpty()) {
|
||||||
|
Error(error);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (results.empty()) {
|
||||||
|
Q_EMIT LoadingFinished();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
SongList songs;
|
SongList songs;
|
||||||
songs.reserve(results.count());
|
songs.reserve(results.count());
|
||||||
int track_number = 1;
|
int track_number = 0;
|
||||||
for (const MusicBrainzClient::Result &ret : results) {
|
for (const MusicBrainzClient::Result &result : results) {
|
||||||
|
++track_number;
|
||||||
Song song(Song::Source::CDDA);
|
Song song(Song::Source::CDDA);
|
||||||
song.set_artist(artist);
|
song.set_artist(artist);
|
||||||
song.set_album(album);
|
song.set_album(album);
|
||||||
song.set_title(ret.title_);
|
song.set_title(result.title_);
|
||||||
song.set_length_nanosec(ret.duration_msec_ * kNsecPerMsec);
|
song.set_length_nanosec(result.duration_msec_ * kNsecPerMsec);
|
||||||
song.set_track(track_number);
|
song.set_track(track_number);
|
||||||
song.set_year(ret.year_);
|
song.set_year(result.year_);
|
||||||
song.set_id(track_number);
|
song.set_id(track_number);
|
||||||
song.set_filetype(Song::FileType::CDDA);
|
song.set_filetype(Song::FileType::CDDA);
|
||||||
song.set_valid(true);
|
song.set_valid(true);
|
||||||
// We need to set url: that's how playlist will find the correct item to update
|
// We need to set URL, that's how playlist will find the correct item to update
|
||||||
song.set_url(GetUrlFromTrack(track_number++));
|
song.set_url(GetUrlFromTrack(track_number));
|
||||||
songs << song;
|
songs << song;
|
||||||
}
|
}
|
||||||
Q_EMIT SongsMetadataLoaded(songs);
|
|
||||||
|
|
||||||
}
|
Q_EMIT SongsUpdated(songs);
|
||||||
#endif
|
Q_EMIT LoadingFinished();
|
||||||
|
|
||||||
bool CddaSongLoader::HasChanged() {
|
|
||||||
|
|
||||||
if (cdio_ && cdio_get_media_changed(cdio_) != 1) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
// Check if mutex is already token (i.e. init is already taking place)
|
|
||||||
if (!mutex_load_.tryLock()) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
mutex_load_.unlock();
|
|
||||||
|
|
||||||
return true;
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void CddaSongLoader::Error(const QString &error) {
|
#endif // HAVE_MUSICBRAINZ
|
||||||
|
|
||||||
|
void CDDASongLoader::Error(const QString &error) {
|
||||||
|
|
||||||
qLog(Error) << error;
|
qLog(Error) << error;
|
||||||
Q_EMIT SongsDurationLoaded(SongList(), error);
|
|
||||||
|
Q_EMIT LoadError(error);
|
||||||
|
Q_EMIT LoadingFinished();
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
* Strawberry Music Player
|
* Strawberry Music Player
|
||||||
* This file was part of Clementine.
|
* This file was part of Clementine.
|
||||||
* Copyright 2010, David Sansome <me@davidsansome.com>
|
* Copyright 2010, David Sansome <me@davidsansome.com>
|
||||||
* Copyright 2018-2021, Jonas Kvinge <jonas@jkvinge.net>
|
* Copyright 2018-2025, Jonas Kvinge <jonas@jkvinge.net>
|
||||||
*
|
*
|
||||||
* Strawberry is free software: you can redistribute it and/or modify
|
* Strawberry is free software: you can redistribute it and/or modify
|
||||||
* it under the terms of the GNU General Public License as published by
|
* it under the terms of the GNU General Public License as published by
|
||||||
@@ -24,11 +24,6 @@
|
|||||||
|
|
||||||
#include "config.h"
|
#include "config.h"
|
||||||
|
|
||||||
#include <cstddef>
|
|
||||||
|
|
||||||
#include <cdio/types.h>
|
|
||||||
#include <cdio/cdio.h>
|
|
||||||
|
|
||||||
#include <gst/gstelement.h>
|
#include <gst/gstelement.h>
|
||||||
#include <gst/audio/gstaudiocdsrc.h>
|
#include <gst/audio/gstaudiocdsrc.h>
|
||||||
|
|
||||||
@@ -36,6 +31,7 @@
|
|||||||
#include <QMutex>
|
#include <QMutex>
|
||||||
#include <QString>
|
#include <QString>
|
||||||
#include <QUrl>
|
#include <QUrl>
|
||||||
|
#include <QFuture>
|
||||||
|
|
||||||
#include "includes/shared_ptr.h"
|
#include "includes/shared_ptr.h"
|
||||||
#include "core/song.h"
|
#include "core/song.h"
|
||||||
@@ -45,39 +41,40 @@
|
|||||||
|
|
||||||
class NetworkAccessManager;
|
class NetworkAccessManager;
|
||||||
|
|
||||||
// This class provides a (hopefully) nice, high level interface to get CD information and load tracks
|
class CDDASongLoader : public QObject {
|
||||||
class CddaSongLoader : public QObject {
|
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
|
|
||||||
public:
|
public:
|
||||||
explicit CddaSongLoader(const QUrl &url, QObject *parent = nullptr);
|
explicit CDDASongLoader(const QUrl &url, QObject *parent = nullptr);
|
||||||
~CddaSongLoader() override;
|
~CDDASongLoader() override;
|
||||||
|
|
||||||
// Load songs. Signals declared below will be emitted anytime new information will be available.
|
|
||||||
void LoadSongs();
|
void LoadSongs();
|
||||||
bool HasChanged();
|
|
||||||
|
bool IsActive() const { return loading_future_.isRunning(); }
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
void LoadSongsFromCDDA();
|
||||||
void Error(const QString &error);
|
void Error(const QString &error);
|
||||||
QUrl GetUrlFromTrack(const int track_number) const;
|
QUrl GetUrlFromTrack(const int track_number) const;
|
||||||
|
|
||||||
Q_SIGNALS:
|
Q_SIGNALS:
|
||||||
void SongsLoadError(const QString &error);
|
|
||||||
void SongsLoaded(const SongList &songs);
|
void SongsLoaded(const SongList &songs);
|
||||||
void SongsDurationLoaded(const SongList &songs, const QString &error = QString());
|
void SongsUpdated(const SongList &songs);
|
||||||
void SongsMetadataLoaded(const SongList &songs);
|
void LoadError(const QString &error);
|
||||||
|
void LoadingFinished();
|
||||||
|
void LoadTagsFromMusicBrainz(const QString &musicbrainz_discid);
|
||||||
|
|
||||||
private Q_SLOTS:
|
private Q_SLOTS:
|
||||||
#ifdef HAVE_MUSICBRAINZ
|
#ifdef HAVE_MUSICBRAINZ
|
||||||
void AudioCDTagsLoaded(const QString &artist, const QString &album, const MusicBrainzClient::ResultList &results);
|
void LoadTagsFromMusicBrainzSlot(const QString &musicbrainz_discid) const;
|
||||||
|
void LoadTagsFromMusicBrainzFinished(const QString &artist, const QString &album, const MusicBrainzClient::ResultList &results, const QString &error);
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
private:
|
private:
|
||||||
const QUrl url_;
|
const QUrl url_;
|
||||||
SharedPtr<NetworkAccessManager> network_;
|
SharedPtr<NetworkAccessManager> network_;
|
||||||
GstElement *cdda_;
|
|
||||||
CdIo_t *cdio_;
|
|
||||||
QMutex mutex_load_;
|
QMutex mutex_load_;
|
||||||
|
QFuture<void> loading_future_;
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif // CDDASONGLOADER_H
|
#endif // CDDASONGLOADER_H
|
||||||
|
|||||||
@@ -67,9 +67,6 @@ class ConnectedDevice : public QObject, public virtual MusicStorage, public enab
|
|||||||
virtual bool IsLoading() { return false; }
|
virtual bool IsLoading() { return false; }
|
||||||
virtual void NewConnection() {}
|
virtual void NewConnection() {}
|
||||||
virtual void ConnectAsync();
|
virtual void ConnectAsync();
|
||||||
// For some devices (e.g. CD devices) we don't have callbacks to be notified when something change:
|
|
||||||
// we can call this method to refresh device's state
|
|
||||||
virtual void Refresh() {}
|
|
||||||
|
|
||||||
TranscodeMode GetTranscodeMode() const override;
|
TranscodeMode GetTranscodeMode() const override;
|
||||||
Song::FileType GetTranscodeFormat() const override;
|
Song::FileType GetTranscodeFormat() const override;
|
||||||
|
|||||||
@@ -41,7 +41,7 @@
|
|||||||
using namespace Qt::Literals::StringLiterals;
|
using namespace Qt::Literals::StringLiterals;
|
||||||
|
|
||||||
namespace {
|
namespace {
|
||||||
constexpr int kDeviceSchemaVersion = 5;
|
constexpr int kDeviceSchemaVersion = 6;
|
||||||
}
|
}
|
||||||
|
|
||||||
DeviceDatabaseBackend::DeviceDatabaseBackend(QObject *parent)
|
DeviceDatabaseBackend::DeviceDatabaseBackend(QObject *parent)
|
||||||
|
|||||||
@@ -36,61 +36,75 @@
|
|||||||
|
|
||||||
using namespace Qt::Literals::StringLiterals;
|
using namespace Qt::Literals::StringLiterals;
|
||||||
|
|
||||||
DeviceDatabaseBackend::Device DeviceInfo::SaveToDb() const {
|
void DeviceInfo::InitFromDb(const DeviceDatabaseBackend::Device &device) {
|
||||||
|
|
||||||
DeviceDatabaseBackend::Device ret;
|
database_id_ = device.id_;
|
||||||
ret.friendly_name_ = friendly_name_;
|
friendly_name_ = device.friendly_name_;
|
||||||
ret.size_ = size_;
|
size_ = device.size_;
|
||||||
ret.id_ = database_id_;
|
transcode_mode_ = device.transcode_mode_;
|
||||||
ret.icon_name_ = icon_name_;
|
transcode_format_ = device.transcode_format_;
|
||||||
ret.transcode_mode_ = transcode_mode_;
|
icon_name_ = device.icon_name_;
|
||||||
ret.transcode_format_ = transcode_format_;
|
|
||||||
|
|
||||||
QStringList unique_ids;
|
InitIcon();
|
||||||
unique_ids.reserve(backends_.count());
|
|
||||||
for (const Backend &backend : backends_) {
|
|
||||||
unique_ids << backend.unique_id_;
|
|
||||||
}
|
|
||||||
ret.unique_id_ = unique_ids.join(u',');
|
|
||||||
|
|
||||||
return ret;
|
const QStringList unique_ids = device.unique_id_.split(u',');
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
void DeviceInfo::InitFromDb(const DeviceDatabaseBackend::Device &dev) {
|
|
||||||
|
|
||||||
database_id_ = dev.id_;
|
|
||||||
friendly_name_ = dev.friendly_name_;
|
|
||||||
size_ = dev.size_;
|
|
||||||
transcode_mode_ = dev.transcode_mode_;
|
|
||||||
transcode_format_ = dev.transcode_format_;
|
|
||||||
icon_name_ = dev.icon_name_;
|
|
||||||
|
|
||||||
const QStringList unique_ids = dev.unique_id_.split(u',');
|
|
||||||
for (const QString &id : unique_ids) {
|
for (const QString &id : unique_ids) {
|
||||||
backends_ << Backend(nullptr, id);
|
backends_ << Backend(nullptr, id);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
DeviceDatabaseBackend::Device DeviceInfo::SaveToDb() const {
|
||||||
|
|
||||||
|
DeviceDatabaseBackend::Device device;
|
||||||
|
device.friendly_name_ = friendly_name_;
|
||||||
|
device.size_ = size_;
|
||||||
|
device.id_ = database_id_;
|
||||||
|
device.icon_name_ = icon_name_;
|
||||||
|
device.transcode_mode_ = transcode_mode_;
|
||||||
|
device.transcode_format_ = transcode_format_;
|
||||||
|
|
||||||
|
QStringList unique_ids;
|
||||||
|
unique_ids.reserve(backends_.count());
|
||||||
|
for (const Backend &backend : backends_) {
|
||||||
|
unique_ids << backend.unique_id_;
|
||||||
|
}
|
||||||
|
device.unique_id_ = unique_ids.join(u',');
|
||||||
|
|
||||||
|
return device;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
const DeviceInfo::Backend *DeviceInfo::BestBackend() const {
|
const DeviceInfo::Backend *DeviceInfo::BestBackend() const {
|
||||||
|
|
||||||
int best_priority = -1;
|
int best_priority = -1;
|
||||||
const Backend *ret = nullptr;
|
const Backend *backend = nullptr;
|
||||||
|
|
||||||
for (int i = 0; i < backends_.count(); ++i) {
|
for (int i = 0; i < backends_.count(); ++i) {
|
||||||
if (backends_[i].lister_ && backends_[i].lister_->priority() > best_priority) {
|
if (backends_[i].lister_ && backends_[i].lister_->priority() > best_priority) {
|
||||||
best_priority = backends_[i].lister_->priority();
|
best_priority = backends_[i].lister_->priority();
|
||||||
ret = &(backends_[i]);
|
backend = &(backends_[i]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!ret && !backends_.isEmpty()) return &(backends_[0]);
|
if (!backend && !backends_.isEmpty()) return &(backends_[0]);
|
||||||
return ret;
|
return backend;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void DeviceInfo::SetIcon(const QVariantList &icons, const QString &name_hint) {
|
void DeviceInfo::InitIcon() {
|
||||||
|
|
||||||
|
const QStringList icon_name_list = icon_name_.split(u',');
|
||||||
|
QVariantList icons;
|
||||||
|
icons.reserve(icon_name_list.count());
|
||||||
|
for (const QString &icon_name : icon_name_list) {
|
||||||
|
icons << icon_name;
|
||||||
|
}
|
||||||
|
LoadIcon(icons, friendly_name_);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
void DeviceInfo::LoadIcon(const QVariantList &icons, const QString &name_hint) {
|
||||||
|
|
||||||
icon_name_ = "device"_L1;
|
icon_name_ = "device"_L1;
|
||||||
|
|
||||||
|
|||||||
@@ -97,8 +97,9 @@ class DeviceInfo : public SimpleTreeItem<DeviceInfo> {
|
|||||||
void InitFromDb(const DeviceDatabaseBackend::Device &dev);
|
void InitFromDb(const DeviceDatabaseBackend::Device &dev);
|
||||||
DeviceDatabaseBackend::Device SaveToDb() const;
|
DeviceDatabaseBackend::Device SaveToDb() const;
|
||||||
|
|
||||||
|
void InitIcon();
|
||||||
// Tries to load a good icon for the device. Sets icon_name_ and icon_.
|
// Tries to load a good icon for the device. Sets icon_name_ and icon_.
|
||||||
void SetIcon(const QVariantList &icons, const QString &name_hint);
|
void LoadIcon(const QVariantList &icons, const QString &name_hint);
|
||||||
|
|
||||||
// Gets the best backend available (the one with the highest priority)
|
// Gets the best backend available (the one with the highest priority)
|
||||||
const Backend *BestBackend() const;
|
const Backend *BestBackend() const;
|
||||||
|
|||||||
@@ -109,7 +109,7 @@ DeviceManager::DeviceManager(const SharedPtr<TaskManager> task_manager,
|
|||||||
backend_->moveToThread(database->thread());
|
backend_->moveToThread(database->thread());
|
||||||
backend_->Init(database);
|
backend_->Init(database);
|
||||||
|
|
||||||
QObject::connect(this, &DeviceManager::DeviceCreatedFromDB, this, &DeviceManager::AddDeviceFromDB);
|
QObject::connect(this, &DeviceManager::DevicesLoaded, this, &DeviceManager::AddDevicesFromDB);
|
||||||
|
|
||||||
// This reads from the database and contents on the database mutex, which can be very slow on startup.
|
// This reads from the database and contents on the database mutex, which can be very slow on startup.
|
||||||
(void)QtConcurrent::run(&thread_pool_, &DeviceManager::LoadAllDevices, this);
|
(void)QtConcurrent::run(&thread_pool_, &DeviceManager::LoadAllDevices, this);
|
||||||
@@ -120,7 +120,7 @@ DeviceManager::DeviceManager(const SharedPtr<TaskManager> task_manager,
|
|||||||
|
|
||||||
// CD devices are detected via the DiskArbitration framework instead on MacOs.
|
// CD devices are detected via the DiskArbitration framework instead on MacOs.
|
||||||
#if defined(HAVE_AUDIOCD) && !defined(Q_OS_MACOS)
|
#if defined(HAVE_AUDIOCD) && !defined(Q_OS_MACOS)
|
||||||
AddLister(new CddaLister);
|
AddLister(new CDDALister);
|
||||||
#endif
|
#endif
|
||||||
#ifdef HAVE_UDISKS2
|
#ifdef HAVE_UDISKS2
|
||||||
AddLister(new Udisks2Lister);
|
AddLister(new Udisks2Lister);
|
||||||
@@ -133,7 +133,7 @@ DeviceManager::DeviceManager(const SharedPtr<TaskManager> task_manager,
|
|||||||
#endif
|
#endif
|
||||||
|
|
||||||
#ifdef HAVE_AUDIOCD
|
#ifdef HAVE_AUDIOCD
|
||||||
AddDeviceClass<CddaDevice>();
|
AddDeviceClass<CDDADevice>();
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
AddDeviceClass<FilesystemDevice>();
|
AddDeviceClass<FilesystemDevice>();
|
||||||
@@ -167,12 +167,12 @@ void DeviceManager::Exit() {
|
|||||||
|
|
||||||
void DeviceManager::CloseDevices() {
|
void DeviceManager::CloseDevices() {
|
||||||
|
|
||||||
for (DeviceInfo *info : std::as_const(devices_)) {
|
for (DeviceInfo *device_info : std::as_const(devices_)) {
|
||||||
if (!info->device_) continue;
|
if (!device_info->device_) continue;
|
||||||
if (wait_for_exit_.contains(&*info->device_)) continue;
|
if (wait_for_exit_.contains(&*device_info->device_)) continue;
|
||||||
wait_for_exit_ << &*info->device_;
|
wait_for_exit_ << &*device_info->device_;
|
||||||
QObject::connect(&*info->device_, &ConnectedDevice::destroyed, this, &DeviceManager::DeviceDestroyed);
|
QObject::connect(&*device_info->device_, &ConnectedDevice::destroyed, this, &DeviceManager::DeviceDestroyed);
|
||||||
info->device_->Close();
|
device_info->device_->Close();
|
||||||
}
|
}
|
||||||
if (wait_for_exit_.isEmpty()) CloseListers();
|
if (wait_for_exit_.isEmpty()) CloseListers();
|
||||||
|
|
||||||
@@ -224,10 +224,10 @@ void DeviceManager::ListerClosed() {
|
|||||||
|
|
||||||
void DeviceManager::DeviceDestroyed() {
|
void DeviceManager::DeviceDestroyed() {
|
||||||
|
|
||||||
ConnectedDevice *device = static_cast<ConnectedDevice*>(sender());
|
ConnectedDevice *connected_device = static_cast<ConnectedDevice*>(sender());
|
||||||
if (!wait_for_exit_.contains(device) || !backend_) return;
|
if (!wait_for_exit_.contains(connected_device) || !backend_) return;
|
||||||
|
|
||||||
wait_for_exit_.removeAll(device);
|
wait_for_exit_.removeAll(connected_device);
|
||||||
if (wait_for_exit_.isEmpty()) CloseListers();
|
if (wait_for_exit_.isEmpty()) CloseListers();
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -237,41 +237,37 @@ void DeviceManager::LoadAllDevices() {
|
|||||||
Q_ASSERT(QThread::currentThread() != qApp->thread());
|
Q_ASSERT(QThread::currentThread() != qApp->thread());
|
||||||
|
|
||||||
const DeviceDatabaseBackend::DeviceList devices = backend_->GetAllDevices();
|
const DeviceDatabaseBackend::DeviceList devices = backend_->GetAllDevices();
|
||||||
for (const DeviceDatabaseBackend::Device &device : devices) {
|
|
||||||
DeviceInfo *info = new DeviceInfo(DeviceInfo::Type::Device, root_);
|
Q_EMIT DevicesLoaded(devices);
|
||||||
info->InitFromDb(device);
|
|
||||||
Q_EMIT DeviceCreatedFromDB(info);
|
|
||||||
}
|
|
||||||
|
|
||||||
// This is done in a concurrent thread so close the unique DB connection.
|
// This is done in a concurrent thread so close the unique DB connection.
|
||||||
backend_->Close();
|
backend_->Close();
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void DeviceManager::AddDeviceFromDB(DeviceInfo *info) {
|
void DeviceManager::AddDevicesFromDB(const DeviceDatabaseBackend::DeviceList &devices) {
|
||||||
|
|
||||||
const QStringList icon_names = info->icon_name_.split(u',');
|
for (const DeviceDatabaseBackend::Device &device : devices) {
|
||||||
QVariantList icons;
|
const QStringList unique_ids = device.unique_id_.split(u',');
|
||||||
icons.reserve(icon_names.count());
|
DeviceInfo *device_info = FindEquivalentDevice(unique_ids);
|
||||||
for (const QString &icon_name : icon_names) {
|
if (device_info && device_info->database_id_ == -1) {
|
||||||
icons << icon_name;
|
qLog(Info) << "Database device linked to physical device:" << device.friendly_name_;
|
||||||
}
|
device_info->database_id_ = device.id_;
|
||||||
info->SetIcon(icons, info->friendly_name_);
|
device_info->icon_name_ = device.icon_name_;
|
||||||
|
device_info->InitIcon();
|
||||||
DeviceInfo *existing = FindEquivalentDevice(info);
|
const QModelIndex idx = ItemToIndex(device_info);
|
||||||
if (existing) {
|
if (idx.isValid()) {
|
||||||
qLog(Info) << "Found existing device: " << info->friendly_name_;
|
Q_EMIT dataChanged(idx, idx);
|
||||||
existing->icon_name_ = info->icon_name_;
|
}
|
||||||
existing->icon_ = info->icon_;
|
}
|
||||||
QModelIndex idx = ItemToIndex(existing);
|
else {
|
||||||
if (idx.isValid()) Q_EMIT dataChanged(idx, idx);
|
qLog(Info) << "Database device:" << device.friendly_name_;
|
||||||
root_->Delete(info->row);
|
device_info = new DeviceInfo(DeviceInfo::Type::Device, root_);
|
||||||
}
|
device_info->InitFromDb(device);
|
||||||
else {
|
beginInsertRows(ItemToIndex(root_), static_cast<int>(devices_.count()), static_cast<int>(devices_.count()));
|
||||||
qLog(Info) << "Device added from database: " << info->friendly_name_;
|
devices_ << device_info;
|
||||||
beginInsertRows(ItemToIndex(root_), static_cast<int>(devices_.count()), static_cast<int>(devices_.count()));
|
endInsertRows();
|
||||||
devices_ << info;
|
}
|
||||||
endInsertRows();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -280,30 +276,29 @@ QVariant DeviceManager::data(const QModelIndex &idx, int role) const {
|
|||||||
|
|
||||||
if (!idx.isValid() || idx.column() != 0) return QVariant();
|
if (!idx.isValid() || idx.column() != 0) return QVariant();
|
||||||
|
|
||||||
DeviceInfo *info = IndexToItem(idx);
|
DeviceInfo *device_info = IndexToItem(idx);
|
||||||
if (!info) return QVariant();
|
if (!device_info) return QVariant();
|
||||||
|
|
||||||
switch (role) {
|
switch (role) {
|
||||||
case Qt::DisplayRole:{
|
case Qt::DisplayRole:{
|
||||||
QString text;
|
QString text;
|
||||||
if (!info->friendly_name_.isEmpty()) {
|
if (!device_info->friendly_name_.isEmpty()) {
|
||||||
text = info->friendly_name_;
|
text = device_info->friendly_name_;
|
||||||
}
|
}
|
||||||
else if (info->BestBackend()) {
|
else if (device_info->BestBackend()) {
|
||||||
text = info->BestBackend()->unique_id_;
|
text = device_info->BestBackend()->unique_id_;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (info->size_ > 0) {
|
if (device_info->size_ > 0) {
|
||||||
text = text + QStringLiteral(" (%1)").arg(Utilities::PrettySize(info->size_));
|
text = text + QStringLiteral(" (%1)").arg(Utilities::PrettySize(device_info->size_));
|
||||||
}
|
}
|
||||||
if (info->device_) info->device_->Refresh();
|
|
||||||
return text;
|
return text;
|
||||||
}
|
}
|
||||||
|
|
||||||
case Qt::DecorationRole:{
|
case Qt::DecorationRole:{
|
||||||
QPixmap pixmap = info->icon_.pixmap(kDeviceIconSize);
|
QPixmap pixmap = device_info->icon_.pixmap(kDeviceIconSize);
|
||||||
|
|
||||||
if (info->backends_.isEmpty() || !info->BestBackend() || !info->BestBackend()->lister_) {
|
if (device_info->backends_.isEmpty() || !device_info->BestBackend() || !device_info->BestBackend()->lister_) {
|
||||||
// Disconnected but remembered
|
// Disconnected but remembered
|
||||||
QPainter p(&pixmap);
|
QPainter p(&pixmap);
|
||||||
p.drawPixmap(kDeviceIconSize - kDeviceIconOverlaySize, kDeviceIconSize - kDeviceIconOverlaySize, not_connected_overlay_.pixmap(kDeviceIconOverlaySize));
|
p.drawPixmap(kDeviceIconSize - kDeviceIconOverlaySize, kDeviceIconSize - kDeviceIconOverlaySize, not_connected_overlay_.pixmap(kDeviceIconOverlaySize));
|
||||||
@@ -313,62 +308,62 @@ QVariant DeviceManager::data(const QModelIndex &idx, int role) const {
|
|||||||
}
|
}
|
||||||
|
|
||||||
case Role_FriendlyName:
|
case Role_FriendlyName:
|
||||||
return info->friendly_name_;
|
return device_info->friendly_name_;
|
||||||
|
|
||||||
case Role_UniqueId:
|
case Role_UniqueId:
|
||||||
if (!info->BestBackend()) return QString();
|
if (!device_info->BestBackend()) return QString();
|
||||||
return info->BestBackend()->unique_id_;
|
return device_info->BestBackend()->unique_id_;
|
||||||
|
|
||||||
case Role_IconName:
|
case Role_IconName:
|
||||||
return info->icon_name_;
|
return device_info->icon_name_;
|
||||||
|
|
||||||
case Role_Capacity:
|
case Role_Capacity:
|
||||||
case MusicStorage::Role_Capacity:
|
case MusicStorage::Role_Capacity:
|
||||||
return info->size_;
|
return device_info->size_;
|
||||||
|
|
||||||
case Role_FreeSpace:
|
case Role_FreeSpace:
|
||||||
case MusicStorage::Role_FreeSpace:
|
case MusicStorage::Role_FreeSpace:
|
||||||
return ((info->BestBackend() && info->BestBackend()->lister_) ? info->BestBackend()->lister_->DeviceFreeSpace(info->BestBackend()->unique_id_) : QVariant());
|
return ((device_info->BestBackend() && device_info->BestBackend()->lister_) ? device_info->BestBackend()->lister_->DeviceFreeSpace(device_info->BestBackend()->unique_id_) : QVariant());
|
||||||
|
|
||||||
case Role_State:
|
case Role_State:
|
||||||
if (info->device_) return State_Connected;
|
if (device_info->device_) return QVariant::fromValue(State::Connected);
|
||||||
if (info->BestBackend() && info->BestBackend()->lister_) {
|
if (device_info->BestBackend() && device_info->BestBackend()->lister_) {
|
||||||
if (info->BestBackend()->lister_->DeviceNeedsMount(info->BestBackend()->unique_id_)) return State_NotMounted;
|
if (device_info->BestBackend()->lister_->DeviceNeedsMount(device_info->BestBackend()->unique_id_)) return QVariant::fromValue(State::NotMounted);
|
||||||
return State_NotConnected;
|
return QVariant::fromValue(State::NotConnected);
|
||||||
}
|
}
|
||||||
return State_Remembered;
|
return QVariant::fromValue(State::Remembered);
|
||||||
|
|
||||||
case Role_UpdatingPercentage:
|
case Role_UpdatingPercentage:
|
||||||
if (info->task_percentage_ == -1) return QVariant();
|
if (device_info->task_percentage_ == -1) return QVariant();
|
||||||
return info->task_percentage_;
|
return device_info->task_percentage_;
|
||||||
|
|
||||||
case MusicStorage::Role_Storage:
|
case MusicStorage::Role_Storage:
|
||||||
if (!info->device_ && info->database_id_ != -1) {
|
if (!device_info->device_ && device_info->database_id_ != -1) {
|
||||||
const_cast<DeviceManager*>(this)->Connect(info);
|
const_cast<DeviceManager*>(this)->Connect(device_info);
|
||||||
}
|
}
|
||||||
if (!info->device_) return QVariant();
|
if (!device_info->device_) return QVariant();
|
||||||
return QVariant::fromValue<SharedPtr<MusicStorage>>(info->device_);
|
return QVariant::fromValue<SharedPtr<MusicStorage>>(device_info->device_);
|
||||||
|
|
||||||
case MusicStorage::Role_StorageForceConnect:
|
case MusicStorage::Role_StorageForceConnect:
|
||||||
if (!info->BestBackend()) return QVariant();
|
if (!device_info->BestBackend()) return QVariant();
|
||||||
if (!info->device_) {
|
if (!device_info->device_) {
|
||||||
if (info->database_id_ == -1 && !info->BestBackend()->lister_->DeviceNeedsMount(info->BestBackend()->unique_id_)) {
|
if (device_info->database_id_ == -1 && !device_info->BestBackend()->lister_->DeviceNeedsMount(device_info->BestBackend()->unique_id_)) {
|
||||||
if (info->BestBackend()->lister_->AskForScan(info->BestBackend()->unique_id_)) {
|
if (device_info->BestBackend()->lister_->AskForScan(device_info->BestBackend()->unique_id_)) {
|
||||||
ScopedPtr<QMessageBox> dialog(new QMessageBox(QMessageBox::Information, tr("Connect device"), tr("This is the first time you have connected this device. Strawberry will now scan the device to find music files - this may take some time."), QMessageBox::Cancel));
|
ScopedPtr<QMessageBox> dialog(new QMessageBox(QMessageBox::Information, tr("Connect device"), tr("This is the first time you have connected this device. Strawberry will now scan the device to find music files - this may take some time."), QMessageBox::Cancel));
|
||||||
QPushButton *pushbutton = dialog->addButton(tr("Connect device"), QMessageBox::AcceptRole);
|
QPushButton *pushbutton = dialog->addButton(tr("Connect device"), QMessageBox::AcceptRole);
|
||||||
dialog->exec();
|
dialog->exec();
|
||||||
if (dialog->clickedButton() != pushbutton) return QVariant();
|
if (dialog->clickedButton() != pushbutton) return QVariant();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
const_cast<DeviceManager*>(this)->Connect(info);
|
const_cast<DeviceManager*>(this)->Connect(device_info);
|
||||||
}
|
}
|
||||||
if (!info->device_) return QVariant();
|
if (!device_info->device_) return QVariant();
|
||||||
return QVariant::fromValue<SharedPtr<MusicStorage>>(info->device_);
|
return QVariant::fromValue<SharedPtr<MusicStorage>>(device_info->device_);
|
||||||
|
|
||||||
case Role_MountPath:{
|
case Role_MountPath:{
|
||||||
if (!info->device_) return QVariant();
|
if (!device_info->device_) return QVariant();
|
||||||
|
|
||||||
QString ret = info->device_->url().path();
|
QString ret = device_info->device_->url().path();
|
||||||
#ifdef Q_OS_WIN32
|
#ifdef Q_OS_WIN32
|
||||||
if (ret.startsWith(u'/')) ret.remove(0, 1);
|
if (ret.startsWith(u'/')) ret.remove(0, 1);
|
||||||
#endif
|
#endif
|
||||||
@@ -376,17 +371,17 @@ QVariant DeviceManager::data(const QModelIndex &idx, int role) const {
|
|||||||
}
|
}
|
||||||
|
|
||||||
case Role_TranscodeMode:
|
case Role_TranscodeMode:
|
||||||
return static_cast<int>(info->transcode_mode_);
|
return static_cast<int>(device_info->transcode_mode_);
|
||||||
|
|
||||||
case Role_TranscodeFormat:
|
case Role_TranscodeFormat:
|
||||||
return static_cast<int>(info->transcode_format_);
|
return static_cast<int>(device_info->transcode_format_);
|
||||||
|
|
||||||
case Role_SongCount:
|
case Role_SongCount:
|
||||||
if (!info->device_) return QVariant();
|
if (!device_info->device_) return QVariant();
|
||||||
return info->device_->song_count();
|
return device_info->device_->song_count();
|
||||||
|
|
||||||
case Role_CopyMusic:
|
case Role_CopyMusic:
|
||||||
if (info->BestBackend() && info->BestBackend()->lister_) return info->BestBackend()->lister_->CopyMusic();
|
if (device_info->BestBackend() && device_info->BestBackend()->lister_) return device_info->BestBackend()->lister_->CopyMusic();
|
||||||
else return false;
|
else return false;
|
||||||
|
|
||||||
default:
|
default:
|
||||||
@@ -410,7 +405,9 @@ DeviceInfo *DeviceManager::FindDeviceById(const QString &id) const {
|
|||||||
|
|
||||||
for (int i = 0; i < devices_.count(); ++i) {
|
for (int i = 0; i < devices_.count(); ++i) {
|
||||||
for (const DeviceInfo::Backend &backend : std::as_const(devices_[i]->backends_)) {
|
for (const DeviceInfo::Backend &backend : std::as_const(devices_[i]->backends_)) {
|
||||||
if (backend.unique_id_ == id) return devices_[i];
|
if (backend.unique_id_ == id) {
|
||||||
|
return devices_[i];
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -425,10 +422,11 @@ DeviceInfo *DeviceManager::FindDeviceByUrl(const QList<QUrl> &urls) const {
|
|||||||
for (int i = 0; i < devices_.count(); ++i) {
|
for (int i = 0; i < devices_.count(); ++i) {
|
||||||
for (const DeviceInfo::Backend &backend : std::as_const(devices_[i]->backends_)) {
|
for (const DeviceInfo::Backend &backend : std::as_const(devices_[i]->backends_)) {
|
||||||
if (!backend.lister_) continue;
|
if (!backend.lister_) continue;
|
||||||
|
|
||||||
const QList<QUrl> device_urls = backend.lister_->MakeDeviceUrls(backend.unique_id_);
|
const QList<QUrl> device_urls = backend.lister_->MakeDeviceUrls(backend.unique_id_);
|
||||||
for (const QUrl &url : device_urls) {
|
for (const QUrl &url : device_urls) {
|
||||||
if (urls.contains(url)) return devices_[i];
|
if (urls.contains(url)) {
|
||||||
|
return devices_[i];
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -437,12 +435,15 @@ DeviceInfo *DeviceManager::FindDeviceByUrl(const QList<QUrl> &urls) const {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
DeviceInfo *DeviceManager::FindEquivalentDevice(DeviceInfo *info) const {
|
DeviceInfo *DeviceManager::FindEquivalentDevice(const QStringList &unique_ids) const {
|
||||||
|
|
||||||
for (const DeviceInfo::Backend &backend : std::as_const(info->backends_)) {
|
for (const QString &unique_id : unique_ids) {
|
||||||
DeviceInfo *match = FindDeviceById(backend.unique_id_);
|
DeviceInfo *device_info_match = FindDeviceById(unique_id);
|
||||||
if (match) return match;
|
if (device_info_match) {
|
||||||
|
return device_info_match;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return nullptr;
|
return nullptr;
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -455,42 +456,42 @@ void DeviceManager::PhysicalDeviceAdded(const QString &id) {
|
|||||||
qLog(Info) << "Device added:" << id << lister->DeviceUniqueIDs();
|
qLog(Info) << "Device added:" << id << lister->DeviceUniqueIDs();
|
||||||
|
|
||||||
// Do we have this device already?
|
// Do we have this device already?
|
||||||
DeviceInfo *info = FindDeviceById(id);
|
DeviceInfo *device_info = FindDeviceById(id);
|
||||||
if (info) {
|
if (device_info) {
|
||||||
for (int backend_index = 0; backend_index < info->backends_.count(); ++backend_index) {
|
for (int backend_index = 0; backend_index < device_info->backends_.count(); ++backend_index) {
|
||||||
if (info->backends_[backend_index].unique_id_ == id) {
|
if (device_info->backends_[backend_index].unique_id_ == id) {
|
||||||
info->backends_[backend_index].lister_ = lister;
|
device_info->backends_[backend_index].lister_ = lister;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
QModelIndex idx = ItemToIndex(info);
|
QModelIndex idx = ItemToIndex(device_info);
|
||||||
if (idx.isValid()) Q_EMIT dataChanged(idx, idx);
|
if (idx.isValid()) Q_EMIT dataChanged(idx, idx);
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
// Check if we have another device with the same URL
|
// Check if we have another device with the same URL
|
||||||
info = FindDeviceByUrl(lister->MakeDeviceUrls(id));
|
device_info = FindDeviceByUrl(lister->MakeDeviceUrls(id));
|
||||||
if (info) {
|
if (device_info) {
|
||||||
// Add this device's lister to the existing device
|
// Add this device's lister to the existing device
|
||||||
info->backends_ << DeviceInfo::Backend(lister, id);
|
device_info->backends_ << DeviceInfo::Backend(lister, id);
|
||||||
|
|
||||||
// If the user hasn't saved the device in the DB yet then overwrite the device's name and icon etc.
|
// If the user hasn't saved the device in the DB yet then overwrite the device's name and icon etc.
|
||||||
if (info->database_id_ == -1 && info->BestBackend() && info->BestBackend()->lister_ == lister) {
|
if (device_info->database_id_ == -1 && device_info->BestBackend() && device_info->BestBackend()->lister_ == lister) {
|
||||||
info->friendly_name_ = lister->MakeFriendlyName(id);
|
device_info->friendly_name_ = lister->MakeFriendlyName(id);
|
||||||
info->size_ = lister->DeviceCapacity(id);
|
device_info->size_ = lister->DeviceCapacity(id);
|
||||||
info->SetIcon(lister->DeviceIcons(id), info->friendly_name_);
|
device_info->LoadIcon(lister->DeviceIcons(id), device_info->friendly_name_);
|
||||||
}
|
}
|
||||||
QModelIndex idx = ItemToIndex(info);
|
QModelIndex idx = ItemToIndex(device_info);
|
||||||
if (idx.isValid()) Q_EMIT dataChanged(idx, idx);
|
if (idx.isValid()) Q_EMIT dataChanged(idx, idx);
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
// It's a completely new device
|
// It's a completely new device
|
||||||
info = new DeviceInfo(DeviceInfo::Type::Device, root_);
|
device_info = new DeviceInfo(DeviceInfo::Type::Device, root_);
|
||||||
info->backends_ << DeviceInfo::Backend(lister, id);
|
device_info->backends_ << DeviceInfo::Backend(lister, id);
|
||||||
info->friendly_name_ = lister->MakeFriendlyName(id);
|
device_info->friendly_name_ = lister->MakeFriendlyName(id);
|
||||||
info->size_ = lister->DeviceCapacity(id);
|
device_info->size_ = lister->DeviceCapacity(id);
|
||||||
info->SetIcon(lister->DeviceIcons(id), info->friendly_name_);
|
device_info->LoadIcon(lister->DeviceIcons(id), device_info->friendly_name_);
|
||||||
beginInsertRows(ItemToIndex(root_), static_cast<int>(devices_.count()), static_cast<int>(devices_.count()));
|
beginInsertRows(ItemToIndex(root_), static_cast<int>(devices_.count()), static_cast<int>(devices_.count()));
|
||||||
devices_ << info;
|
devices_ << device_info;
|
||||||
endInsertRows();
|
endInsertRows();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -503,42 +504,42 @@ void DeviceManager::PhysicalDeviceRemoved(const QString &id) {
|
|||||||
|
|
||||||
qLog(Info) << "Device removed:" << id;
|
qLog(Info) << "Device removed:" << id;
|
||||||
|
|
||||||
DeviceInfo *info = FindDeviceById(id);
|
DeviceInfo *device_info = FindDeviceById(id);
|
||||||
if (!info) return;
|
if (!device_info) return;
|
||||||
|
|
||||||
QModelIndex idx = ItemToIndex(info);
|
const QModelIndex idx = ItemToIndex(device_info);
|
||||||
if (!idx.isValid()) return;
|
if (!idx.isValid()) return;
|
||||||
|
|
||||||
if (info->database_id_ != -1) {
|
if (device_info->database_id_ != -1) {
|
||||||
// Keep the structure around, but just "disconnect" it
|
// Keep the structure around, but just "disconnect" it
|
||||||
for (int backend_index = 0; backend_index < info->backends_.count(); ++backend_index) {
|
for (int backend_index = 0; backend_index < device_info->backends_.count(); ++backend_index) {
|
||||||
if (info->backends_[backend_index].unique_id_ == id) {
|
if (device_info->backends_[backend_index].unique_id_ == id) {
|
||||||
info->backends_[backend_index].lister_ = nullptr;
|
device_info->backends_[backend_index].lister_ = nullptr;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (info->device_ && info->device_->lister() == lister) {
|
if (device_info->device_ && device_info->device_->lister() == lister) {
|
||||||
info->device_->Close();
|
device_info->device_->Close();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!info->device_) Q_EMIT DeviceDisconnected(idx);
|
if (!device_info->device_) Q_EMIT DeviceDisconnected(idx);
|
||||||
|
|
||||||
Q_EMIT dataChanged(idx, idx);
|
Q_EMIT dataChanged(idx, idx);
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
// If this was the last lister for the device then remove it from the model
|
// If this was the last lister for the device then remove it from the model
|
||||||
for (int backend_index = 0; backend_index < info->backends_.count(); ++backend_index) {
|
for (int backend_index = 0; backend_index < device_info->backends_.count(); ++backend_index) {
|
||||||
if (info->backends_[backend_index].unique_id_ == id) {
|
if (device_info->backends_[backend_index].unique_id_ == id) {
|
||||||
info->backends_.removeAt(backend_index);
|
device_info->backends_.removeAt(backend_index);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (info->backends_.isEmpty()) {
|
if (device_info->backends_.isEmpty()) {
|
||||||
beginRemoveRows(ItemToIndex(root_), idx.row(), idx.row());
|
beginRemoveRows(ItemToIndex(root_), idx.row(), idx.row());
|
||||||
devices_.removeAll(info);
|
devices_.removeAll(device_info);
|
||||||
root_->Delete(info->row);
|
root_->Delete(device_info->row);
|
||||||
endRemoveRows();
|
endRemoveRows();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -550,8 +551,8 @@ void DeviceManager::PhysicalDeviceChanged(const QString &id) {
|
|||||||
DeviceLister *lister = qobject_cast<DeviceLister*>(sender());
|
DeviceLister *lister = qobject_cast<DeviceLister*>(sender());
|
||||||
Q_UNUSED(lister);
|
Q_UNUSED(lister);
|
||||||
|
|
||||||
DeviceInfo *info = FindDeviceById(id);
|
DeviceInfo *device_info = FindDeviceById(id);
|
||||||
if (!info) return;
|
if (!device_info) return;
|
||||||
|
|
||||||
// TODO
|
// TODO
|
||||||
|
|
||||||
@@ -561,40 +562,41 @@ SharedPtr<ConnectedDevice> DeviceManager::Connect(const QModelIndex &idx) {
|
|||||||
|
|
||||||
SharedPtr<ConnectedDevice> ret;
|
SharedPtr<ConnectedDevice> ret;
|
||||||
|
|
||||||
DeviceInfo *info = IndexToItem(idx);
|
DeviceInfo *device_info = IndexToItem(idx);
|
||||||
if (!info) return ret;
|
if (!device_info) return ret;
|
||||||
|
|
||||||
return Connect(info);
|
return Connect(device_info);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
SharedPtr<ConnectedDevice> DeviceManager::Connect(DeviceInfo *info) {
|
SharedPtr<ConnectedDevice> DeviceManager::Connect(DeviceInfo *device_info) {
|
||||||
|
|
||||||
SharedPtr<ConnectedDevice> ret;
|
if (!device_info) {
|
||||||
|
return SharedPtr<ConnectedDevice>();
|
||||||
if (!info) return ret;
|
|
||||||
if (info->device_) { // Already connected
|
|
||||||
return info->device_;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!info->BestBackend() || !info->BestBackend()->lister_) { // Not physically connected
|
if (device_info->device_) { // Already connected
|
||||||
return ret;
|
return device_info->device_;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (info->BestBackend()->lister_->DeviceNeedsMount(info->BestBackend()->unique_id_)) { // Mount the device
|
if (!device_info->BestBackend() || !device_info->BestBackend()->lister_) { // Not physically connected
|
||||||
info->BestBackend()->lister_->MountDeviceAsync(info->BestBackend()->unique_id_);
|
return SharedPtr<ConnectedDevice>();
|
||||||
return ret;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bool first_time = (info->database_id_ == -1);
|
if (device_info->BestBackend()->lister_->DeviceNeedsMount(device_info->BestBackend()->unique_id_)) { // Mount the device
|
||||||
|
device_info->BestBackend()->lister_->MountDeviceAsync(device_info->BestBackend()->unique_id_);
|
||||||
|
return SharedPtr<ConnectedDevice>();
|
||||||
|
}
|
||||||
|
|
||||||
|
const bool first_time = device_info->database_id_ == -1;
|
||||||
if (first_time) {
|
if (first_time) {
|
||||||
// We haven't stored this device in the database before
|
// We haven't stored this device in the database before
|
||||||
info->database_id_ = backend_->AddDevice(info->SaveToDb());
|
device_info->database_id_ = backend_->AddDevice(device_info->SaveToDb());
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get the device URLs
|
// Get the device URLs
|
||||||
const QList<QUrl> urls = info->BestBackend()->lister_->MakeDeviceUrls(info->BestBackend()->unique_id_);
|
const QList<QUrl> urls = device_info->BestBackend()->lister_->MakeDeviceUrls(device_info->BestBackend()->unique_id_);
|
||||||
if (urls.isEmpty()) return ret;
|
if (urls.isEmpty()) return SharedPtr<ConnectedDevice>();
|
||||||
|
|
||||||
// Take the first URL that we have a handler for
|
// Take the first URL that we have a handler for
|
||||||
QUrl device_url;
|
QUrl device_url;
|
||||||
@@ -614,7 +616,7 @@ SharedPtr<ConnectedDevice> DeviceManager::Connect(DeviceInfo *info) {
|
|||||||
tr("This is an MTP device, but you compiled Strawberry without libmtp support.") + u" "_s +
|
tr("This is an MTP device, but you compiled Strawberry without libmtp support.") + u" "_s +
|
||||||
tr("If you continue, this device will work slowly and songs copied to it may not work."),
|
tr("If you continue, this device will work slowly and songs copied to it may not work."),
|
||||||
QMessageBox::Abort, QMessageBox::Ignore) == QMessageBox::Abort)
|
QMessageBox::Abort, QMessageBox::Ignore) == QMessageBox::Abort)
|
||||||
return ret;
|
return SharedPtr<ConnectedDevice>();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (url.scheme() == "ipod"_L1) {
|
if (url.scheme() == "ipod"_L1) {
|
||||||
@@ -622,7 +624,7 @@ SharedPtr<ConnectedDevice> DeviceManager::Connect(DeviceInfo *info) {
|
|||||||
tr("This is an iPod, but you compiled Strawberry without libgpod support.") + " "_L1 +
|
tr("This is an iPod, but you compiled Strawberry without libgpod support.") + " "_L1 +
|
||||||
tr("If you continue, this device will work slowly and songs copied to it may not work."),
|
tr("If you continue, this device will work slowly and songs copied to it may not work."),
|
||||||
QMessageBox::Abort, QMessageBox::Ignore) == QMessageBox::Abort)
|
QMessageBox::Abort, QMessageBox::Ignore) == QMessageBox::Abort)
|
||||||
return ret;
|
return SharedPtr<ConnectedDevice>();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -635,114 +637,114 @@ SharedPtr<ConnectedDevice> DeviceManager::Connect(DeviceInfo *info) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Q_EMIT DeviceError(tr("This type of device is not supported: %1").arg(url_strings.join(", "_L1)));
|
Q_EMIT DeviceError(tr("This type of device is not supported: %1").arg(url_strings.join(", "_L1)));
|
||||||
return ret;
|
return SharedPtr<ConnectedDevice>();
|
||||||
}
|
}
|
||||||
|
|
||||||
QMetaObject meta_object = device_classes_.value(device_url.scheme());
|
QMetaObject meta_object = device_classes_.value(device_url.scheme());
|
||||||
QObject *instance = meta_object.newInstance(
|
QObject *instance = meta_object.newInstance(
|
||||||
Q_ARG(QUrl, device_url),
|
Q_ARG(QUrl, device_url),
|
||||||
Q_ARG(DeviceLister*, info->BestBackend()->lister_),
|
Q_ARG(DeviceLister*, device_info->BestBackend()->lister_),
|
||||||
Q_ARG(QString, info->BestBackend()->unique_id_),
|
Q_ARG(QString, device_info->BestBackend()->unique_id_),
|
||||||
Q_ARG(DeviceManager*, this),
|
Q_ARG(DeviceManager*, this),
|
||||||
Q_ARG(SharedPtr<TaskManager>, task_manager_),
|
Q_ARG(SharedPtr<TaskManager>, task_manager_),
|
||||||
Q_ARG(SharedPtr<Database>, database_),
|
Q_ARG(SharedPtr<Database>, database_),
|
||||||
Q_ARG(SharedPtr<TagReaderClient>, tagreader_client_),
|
Q_ARG(SharedPtr<TagReaderClient>, tagreader_client_),
|
||||||
Q_ARG(SharedPtr<AlbumCoverLoader>, albumcover_loader_),
|
Q_ARG(SharedPtr<AlbumCoverLoader>, albumcover_loader_),
|
||||||
Q_ARG(int, info->database_id_),
|
Q_ARG(int, device_info->database_id_),
|
||||||
Q_ARG(bool, first_time));
|
Q_ARG(bool, first_time));
|
||||||
|
|
||||||
ret.reset(qobject_cast<ConnectedDevice*>(instance));
|
SharedPtr<ConnectedDevice> connected_device = SharedPtr<ConnectedDevice>(qobject_cast<ConnectedDevice*>(instance));
|
||||||
|
|
||||||
if (!ret) {
|
if (!connected_device) {
|
||||||
qLog(Warning) << "Could not create device for" << device_url.toString();
|
qLog(Warning) << "Could not create device for" << device_url.toString();
|
||||||
return ret;
|
return connected_device;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool result = ret->Init();
|
bool result = connected_device->Init();
|
||||||
if (!result) {
|
if (!result) {
|
||||||
qLog(Warning) << "Could not connect to device" << device_url.toString();
|
qLog(Warning) << "Could not connect to device" << device_url.toString();
|
||||||
return ret;
|
return connected_device;
|
||||||
}
|
}
|
||||||
info->device_ = ret;
|
device_info->device_ = connected_device;
|
||||||
|
|
||||||
QModelIndex idx = ItemToIndex(info);
|
QModelIndex idx = ItemToIndex(device_info);
|
||||||
if (!idx.isValid()) return ret;
|
if (!idx.isValid()) return connected_device;
|
||||||
|
|
||||||
Q_EMIT dataChanged(idx, idx);
|
Q_EMIT dataChanged(idx, idx);
|
||||||
|
|
||||||
QObject::connect(&*info->device_, &ConnectedDevice::TaskStarted, this, &DeviceManager::DeviceTaskStarted);
|
QObject::connect(&*device_info->device_, &ConnectedDevice::TaskStarted, this, &DeviceManager::DeviceTaskStarted);
|
||||||
QObject::connect(&*info->device_, &ConnectedDevice::SongCountUpdated, this, &DeviceManager::DeviceSongCountUpdated);
|
QObject::connect(&*device_info->device_, &ConnectedDevice::SongCountUpdated, this, &DeviceManager::DeviceSongCountUpdated);
|
||||||
QObject::connect(&*info->device_, &ConnectedDevice::DeviceConnectFinished, this, &DeviceManager::DeviceConnectFinished);
|
QObject::connect(&*device_info->device_, &ConnectedDevice::DeviceConnectFinished, this, &DeviceManager::DeviceConnectFinished);
|
||||||
QObject::connect(&*info->device_, &ConnectedDevice::DeviceCloseFinished, this, &DeviceManager::DeviceCloseFinished);
|
QObject::connect(&*device_info->device_, &ConnectedDevice::DeviceCloseFinished, this, &DeviceManager::DeviceCloseFinished);
|
||||||
QObject::connect(&*info->device_, &ConnectedDevice::Error, this, &DeviceManager::DeviceError);
|
QObject::connect(&*device_info->device_, &ConnectedDevice::Error, this, &DeviceManager::DeviceError);
|
||||||
|
|
||||||
ret->ConnectAsync();
|
connected_device->ConnectAsync();
|
||||||
|
|
||||||
return ret;
|
return connected_device;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void DeviceManager::DeviceConnectFinished(const QString &id, const bool success) {
|
void DeviceManager::DeviceConnectFinished(const QString &id, const bool success) {
|
||||||
|
|
||||||
DeviceInfo *info = FindDeviceById(id);
|
DeviceInfo *device_info = FindDeviceById(id);
|
||||||
if (!info) return;
|
if (!device_info) return;
|
||||||
|
|
||||||
QModelIndex idx = ItemToIndex(info);
|
QModelIndex idx = ItemToIndex(device_info);
|
||||||
if (!idx.isValid()) return;
|
if (!idx.isValid()) return;
|
||||||
|
|
||||||
if (success) {
|
if (success) {
|
||||||
Q_EMIT DeviceConnected(idx);
|
Q_EMIT DeviceConnected(idx);
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
info->device_->Close();
|
device_info->device_->Close();
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void DeviceManager::DeviceCloseFinished(const QString &id) {
|
void DeviceManager::DeviceCloseFinished(const QString &id) {
|
||||||
|
|
||||||
DeviceInfo *info = FindDeviceById(id);
|
DeviceInfo *device_info = FindDeviceById(id);
|
||||||
if (!info) return;
|
if (!device_info) return;
|
||||||
|
|
||||||
info->device_.reset();
|
device_info->device_.reset();
|
||||||
|
|
||||||
QModelIndex idx = ItemToIndex(info);
|
QModelIndex idx = ItemToIndex(device_info);
|
||||||
if (!idx.isValid()) return;
|
if (!idx.isValid()) return;
|
||||||
|
|
||||||
Q_EMIT DeviceDisconnected(idx);
|
Q_EMIT DeviceDisconnected(idx);
|
||||||
Q_EMIT dataChanged(idx, idx);
|
Q_EMIT dataChanged(idx, idx);
|
||||||
|
|
||||||
if (info->unmount_ && info->BestBackend() && info->BestBackend()->lister_) {
|
if (device_info->unmount_ && device_info->BestBackend() && device_info->BestBackend()->lister_) {
|
||||||
info->BestBackend()->lister_->UnmountDeviceAsync(info->BestBackend()->unique_id_);
|
device_info->BestBackend()->lister_->UnmountDeviceAsync(device_info->BestBackend()->unique_id_);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (info->forget_) {
|
if (device_info->forget_) {
|
||||||
RemoveFromDB(info, idx);
|
RemoveFromDB(device_info, idx);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
DeviceInfo *DeviceManager::GetDevice(const QModelIndex &idx) const {
|
DeviceInfo *DeviceManager::GetDevice(const QModelIndex &idx) const {
|
||||||
|
|
||||||
DeviceInfo *info = IndexToItem(idx);
|
DeviceInfo *device_info = IndexToItem(idx);
|
||||||
return info;
|
return device_info;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
SharedPtr<ConnectedDevice> DeviceManager::GetConnectedDevice(const QModelIndex &idx) const {
|
SharedPtr<ConnectedDevice> DeviceManager::GetConnectedDevice(const QModelIndex &idx) const {
|
||||||
|
|
||||||
SharedPtr<ConnectedDevice> ret;
|
SharedPtr<ConnectedDevice> connected_device;
|
||||||
DeviceInfo *info = IndexToItem(idx);
|
DeviceInfo *device_info = IndexToItem(idx);
|
||||||
if (!info) return ret;
|
if (!device_info) return connected_device;
|
||||||
return info->device_;
|
return device_info->device_;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
SharedPtr<ConnectedDevice> DeviceManager::GetConnectedDevice(DeviceInfo *info) const {
|
SharedPtr<ConnectedDevice> DeviceManager::GetConnectedDevice(DeviceInfo *device_info) const {
|
||||||
|
|
||||||
SharedPtr<ConnectedDevice> ret;
|
SharedPtr<ConnectedDevice> connected_device;
|
||||||
if (!info) return ret;
|
if (!device_info) return connected_device;
|
||||||
return info->device_;
|
return device_info->device_;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -750,9 +752,9 @@ int DeviceManager::GetDatabaseId(const QModelIndex &idx) const {
|
|||||||
|
|
||||||
if (!idx.isValid()) return -1;
|
if (!idx.isValid()) return -1;
|
||||||
|
|
||||||
DeviceInfo *info = IndexToItem(idx);
|
DeviceInfo *device_info = IndexToItem(idx);
|
||||||
if (!info) return -1;
|
if (!device_info) return -1;
|
||||||
return info->database_id_;
|
return device_info->database_id_;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -760,17 +762,17 @@ DeviceLister *DeviceManager::GetLister(const QModelIndex &idx) const {
|
|||||||
|
|
||||||
if (!idx.isValid()) return nullptr;
|
if (!idx.isValid()) return nullptr;
|
||||||
|
|
||||||
DeviceInfo *info = IndexToItem(idx);
|
DeviceInfo *device_info = IndexToItem(idx);
|
||||||
if (!info || !info->BestBackend()) return nullptr;
|
if (!device_info || !device_info->BestBackend()) return nullptr;
|
||||||
return info->BestBackend()->lister_;
|
return device_info->BestBackend()->lister_;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void DeviceManager::Disconnect(DeviceInfo *info, const QModelIndex &idx) {
|
void DeviceManager::Disconnect(DeviceInfo *device_info, const QModelIndex &idx) {
|
||||||
|
|
||||||
Q_UNUSED(idx);
|
Q_UNUSED(idx);
|
||||||
|
|
||||||
info->device_->Close();
|
device_info->device_->Close();
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -778,37 +780,37 @@ void DeviceManager::Forget(const QModelIndex &idx) {
|
|||||||
|
|
||||||
if (!idx.isValid()) return;
|
if (!idx.isValid()) return;
|
||||||
|
|
||||||
DeviceInfo *info = IndexToItem(idx);
|
DeviceInfo *device_info = IndexToItem(idx);
|
||||||
if (!info) return;
|
if (!device_info) return;
|
||||||
|
|
||||||
if (info->database_id_ == -1) return;
|
if (device_info->database_id_ == -1) return;
|
||||||
|
|
||||||
if (info->device_) {
|
if (device_info->device_) {
|
||||||
info->forget_ = true;
|
device_info->forget_ = true;
|
||||||
Disconnect(info, idx);
|
Disconnect(device_info, idx);
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
RemoveFromDB(info, idx);
|
RemoveFromDB(device_info, idx);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void DeviceManager::RemoveFromDB(DeviceInfo *info, const QModelIndex &idx) {
|
void DeviceManager::RemoveFromDB(DeviceInfo *device_info, const QModelIndex &idx) {
|
||||||
|
|
||||||
backend_->RemoveDevice(info->database_id_);
|
backend_->RemoveDevice(device_info->database_id_);
|
||||||
info->database_id_ = -1;
|
device_info->database_id_ = -1;
|
||||||
|
|
||||||
if (!info->BestBackend() || !info->BestBackend()->lister_) { // It's not attached any more so remove it from the list
|
if (!device_info->BestBackend() || !device_info->BestBackend()->lister_) { // It's not attached any more so remove it from the list
|
||||||
beginRemoveRows(ItemToIndex(root_), idx.row(), idx.row());
|
beginRemoveRows(ItemToIndex(root_), idx.row(), idx.row());
|
||||||
devices_.removeAll(info);
|
devices_.removeAll(device_info);
|
||||||
root_->Delete(info->row);
|
root_->Delete(device_info->row);
|
||||||
endRemoveRows();
|
endRemoveRows();
|
||||||
}
|
}
|
||||||
else { // It's still attached, set the name and icon back to what they were originally
|
else { // It's still attached, set the name and icon back to what they were originally
|
||||||
const QString id = info->BestBackend()->unique_id_;
|
const QString id = device_info->BestBackend()->unique_id_;
|
||||||
|
|
||||||
info->friendly_name_ = info->BestBackend()->lister_->MakeFriendlyName(id);
|
device_info->friendly_name_ = device_info->BestBackend()->lister_->MakeFriendlyName(id);
|
||||||
info->SetIcon(info->BestBackend()->lister_->DeviceIcons(id), info->friendly_name_);
|
device_info->LoadIcon(device_info->BestBackend()->lister_->DeviceIcons(id), device_info->friendly_name_);
|
||||||
Q_EMIT dataChanged(idx, idx);
|
Q_EMIT dataChanged(idx, idx);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -818,18 +820,18 @@ void DeviceManager::SetDeviceOptions(const QModelIndex &idx, const QString &frie
|
|||||||
|
|
||||||
if (!idx.isValid()) return;
|
if (!idx.isValid()) return;
|
||||||
|
|
||||||
DeviceInfo *info = IndexToItem(idx);
|
DeviceInfo *device_info = IndexToItem(idx);
|
||||||
if (!info) return;
|
if (!device_info) return;
|
||||||
|
|
||||||
info->friendly_name_ = friendly_name;
|
device_info->friendly_name_ = friendly_name;
|
||||||
info->SetIcon(QVariantList() << icon_name, friendly_name);
|
device_info->LoadIcon(QVariantList() << icon_name, friendly_name);
|
||||||
info->transcode_mode_ = mode;
|
device_info->transcode_mode_ = mode;
|
||||||
info->transcode_format_ = format;
|
device_info->transcode_format_ = format;
|
||||||
|
|
||||||
Q_EMIT dataChanged(idx, idx);
|
Q_EMIT dataChanged(idx, idx);
|
||||||
|
|
||||||
if (info->database_id_ != -1) {
|
if (device_info->database_id_ != -1) {
|
||||||
backend_->SetDeviceOptions(info->database_id_, friendly_name, icon_name, mode, format);
|
backend_->SetDeviceOptions(device_info->database_id_, friendly_name, icon_name, mode, format);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -840,12 +842,12 @@ void DeviceManager::DeviceTaskStarted(const int id) {
|
|||||||
if (!device) return;
|
if (!device) return;
|
||||||
|
|
||||||
for (int i = 0; i < devices_.count(); ++i) {
|
for (int i = 0; i < devices_.count(); ++i) {
|
||||||
DeviceInfo *info = devices_.value(i);
|
DeviceInfo *device_info = devices_.value(i);
|
||||||
if (info->device_ && &*info->device_ == device) {
|
if (device_info->device_ && &*device_info->device_ == device) {
|
||||||
QModelIndex index = ItemToIndex(info);
|
QModelIndex index = ItemToIndex(device_info);
|
||||||
if (!index.isValid()) continue;
|
if (!index.isValid()) continue;
|
||||||
active_tasks_[id] = index;
|
active_tasks_[id] = index;
|
||||||
info->task_percentage_ = 0;
|
device_info->task_percentage_ = 0;
|
||||||
Q_EMIT dataChanged(index, index);
|
Q_EMIT dataChanged(index, index);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -864,12 +866,12 @@ void DeviceManager::TasksChanged() {
|
|||||||
const QPersistentModelIndex idx = active_tasks_.value(task.id);
|
const QPersistentModelIndex idx = active_tasks_.value(task.id);
|
||||||
if (!idx.isValid()) continue;
|
if (!idx.isValid()) continue;
|
||||||
|
|
||||||
DeviceInfo *info = IndexToItem(idx);
|
DeviceInfo *device_info = IndexToItem(idx);
|
||||||
if (task.progress_max) {
|
if (task.progress_max) {
|
||||||
info->task_percentage_ = static_cast<int>(static_cast<float>(task.progress) / static_cast<float>(task.progress_max) * 100);
|
device_info->task_percentage_ = static_cast<int>(static_cast<float>(task.progress) / static_cast<float>(task.progress_max) * 100);
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
info->task_percentage_ = 0;
|
device_info->task_percentage_ = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
Q_EMIT dataChanged(idx, idx);
|
Q_EMIT dataChanged(idx, idx);
|
||||||
@@ -881,10 +883,10 @@ void DeviceManager::TasksChanged() {
|
|||||||
|
|
||||||
if (!idx.isValid()) continue;
|
if (!idx.isValid()) continue;
|
||||||
|
|
||||||
DeviceInfo *info = IndexToItem(idx);
|
DeviceInfo *device_info = IndexToItem(idx);
|
||||||
if (!info) continue;
|
if (!device_info) continue;
|
||||||
|
|
||||||
info->task_percentage_ = -1;
|
device_info->task_percentage_ = -1;
|
||||||
Q_EMIT dataChanged(idx, idx);
|
Q_EMIT dataChanged(idx, idx);
|
||||||
|
|
||||||
active_tasks_.remove(active_tasks_.key(idx));
|
active_tasks_.remove(active_tasks_.key(idx));
|
||||||
@@ -900,17 +902,17 @@ void DeviceManager::Unmount(const QModelIndex &idx) {
|
|||||||
|
|
||||||
if (!idx.isValid()) return;
|
if (!idx.isValid()) return;
|
||||||
|
|
||||||
DeviceInfo *info = IndexToItem(idx);
|
DeviceInfo *device_info = IndexToItem(idx);
|
||||||
if (!info) return;
|
if (!device_info) return;
|
||||||
|
|
||||||
if (info->database_id_ != -1 && !info->device_) return;
|
if (device_info->database_id_ != -1 && !device_info->device_) return;
|
||||||
|
|
||||||
if (info->device_) {
|
if (device_info->device_) {
|
||||||
info->unmount_ = true;
|
device_info->unmount_ = true;
|
||||||
Disconnect(info, idx);
|
Disconnect(device_info, idx);
|
||||||
}
|
}
|
||||||
else if (info->BestBackend() && info->BestBackend()->lister_) {
|
else if (device_info->BestBackend() && device_info->BestBackend()->lister_) {
|
||||||
info->BestBackend()->lister_->UnmountDeviceAsync(info->BestBackend()->unique_id_);
|
device_info->BestBackend()->lister_->UnmountDeviceAsync(device_info->BestBackend()->unique_id_);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -919,13 +921,13 @@ void DeviceManager::DeviceSongCountUpdated(const int count) {
|
|||||||
|
|
||||||
Q_UNUSED(count);
|
Q_UNUSED(count);
|
||||||
|
|
||||||
ConnectedDevice *device = qobject_cast<ConnectedDevice*>(sender());
|
ConnectedDevice *connected_device = qobject_cast<ConnectedDevice*>(sender());
|
||||||
if (!device) return;
|
if (!connected_device) return;
|
||||||
|
|
||||||
DeviceInfo *info = FindDeviceById(device->unique_id());
|
DeviceInfo *device_info = FindDeviceById(connected_device->unique_id());
|
||||||
if (!info) return;
|
if (!device_info) return;
|
||||||
|
|
||||||
QModelIndex idx = ItemToIndex(info);
|
QModelIndex idx = ItemToIndex(device_info);
|
||||||
if (!idx.isValid()) return;
|
if (!idx.isValid()) return;
|
||||||
|
|
||||||
Q_EMIT dataChanged(idx, idx);
|
Q_EMIT dataChanged(idx, idx);
|
||||||
@@ -934,10 +936,10 @@ void DeviceManager::DeviceSongCountUpdated(const int count) {
|
|||||||
|
|
||||||
QString DeviceManager::DeviceNameByID(const QString &unique_id) {
|
QString DeviceManager::DeviceNameByID(const QString &unique_id) {
|
||||||
|
|
||||||
DeviceInfo *info = FindDeviceById(unique_id);
|
DeviceInfo *device_info = FindDeviceById(unique_id);
|
||||||
if (!info) return QString();
|
if (!device_info) return QString();
|
||||||
|
|
||||||
QModelIndex idx = ItemToIndex(info);
|
QModelIndex idx = ItemToIndex(device_info);
|
||||||
if (!idx.isValid()) return QString();
|
if (!idx.isValid()) return QString();
|
||||||
|
|
||||||
return data(idx, DeviceManager::Role_FriendlyName).toString();
|
return data(idx, DeviceManager::Role_FriendlyName).toString();
|
||||||
|
|||||||
@@ -85,11 +85,11 @@ class DeviceManager : public SimpleTreeModel<DeviceInfo> {
|
|||||||
LastRole,
|
LastRole,
|
||||||
};
|
};
|
||||||
|
|
||||||
enum State {
|
enum class State {
|
||||||
State_Remembered,
|
Remembered,
|
||||||
State_NotMounted,
|
NotMounted,
|
||||||
State_NotConnected,
|
NotConnected,
|
||||||
State_Connected,
|
Connected,
|
||||||
};
|
};
|
||||||
|
|
||||||
static const int kDeviceIconSize;
|
static const int kDeviceIconSize;
|
||||||
@@ -104,17 +104,17 @@ class DeviceManager : public SimpleTreeModel<DeviceInfo> {
|
|||||||
DeviceLister *GetLister(const QModelIndex &idx) const;
|
DeviceLister *GetLister(const QModelIndex &idx) const;
|
||||||
DeviceInfo *GetDevice(const QModelIndex &idx) const;
|
DeviceInfo *GetDevice(const QModelIndex &idx) const;
|
||||||
SharedPtr<ConnectedDevice> GetConnectedDevice(const QModelIndex &idx) const;
|
SharedPtr<ConnectedDevice> GetConnectedDevice(const QModelIndex &idx) const;
|
||||||
SharedPtr<ConnectedDevice> GetConnectedDevice(DeviceInfo *info) const;
|
SharedPtr<ConnectedDevice> GetConnectedDevice(DeviceInfo *device_info) const;
|
||||||
|
|
||||||
DeviceInfo *FindDeviceById(const QString &id) const;
|
DeviceInfo *FindDeviceById(const QString &id) const;
|
||||||
DeviceInfo *FindDeviceByUrl(const QList<QUrl> &url) const;
|
DeviceInfo *FindDeviceByUrl(const QList<QUrl> &url) const;
|
||||||
QString DeviceNameByID(const QString &unique_id);
|
QString DeviceNameByID(const QString &unique_id);
|
||||||
DeviceInfo *FindEquivalentDevice(DeviceInfo *info) const;
|
DeviceInfo *FindEquivalentDevice(const QStringList &unique_ids) const;
|
||||||
|
|
||||||
// Actions on devices
|
// Actions on devices
|
||||||
SharedPtr<ConnectedDevice> Connect(DeviceInfo *info);
|
SharedPtr<ConnectedDevice> Connect(DeviceInfo *device_info);
|
||||||
SharedPtr<ConnectedDevice> Connect(const QModelIndex &idx);
|
SharedPtr<ConnectedDevice> Connect(const QModelIndex &idx);
|
||||||
void Disconnect(DeviceInfo *info, const QModelIndex &idx);
|
void Disconnect(DeviceInfo *device_info, const QModelIndex &idx);
|
||||||
void Forget(const QModelIndex &idx);
|
void Forget(const QModelIndex &idx);
|
||||||
void UnmountAsync(const QModelIndex &idx);
|
void UnmountAsync(const QModelIndex &idx);
|
||||||
|
|
||||||
@@ -128,9 +128,10 @@ class DeviceManager : public SimpleTreeModel<DeviceInfo> {
|
|||||||
|
|
||||||
Q_SIGNALS:
|
Q_SIGNALS:
|
||||||
void ExitFinished();
|
void ExitFinished();
|
||||||
|
void DevicesLoaded(const DeviceDatabaseBackend::DeviceList &devices);
|
||||||
void DeviceConnected(const QModelIndex idx);
|
void DeviceConnected(const QModelIndex idx);
|
||||||
void DeviceDisconnected(const QModelIndex idx);
|
void DeviceDisconnected(const QModelIndex idx);
|
||||||
void DeviceCreatedFromDB(DeviceInfo *info);
|
void DeviceCreatedFromDB(DeviceInfo *device_info);
|
||||||
void DeviceError(const QString &error);
|
void DeviceError(const QString &error);
|
||||||
|
|
||||||
private Q_SLOTS:
|
private Q_SLOTS:
|
||||||
@@ -143,7 +144,7 @@ class DeviceManager : public SimpleTreeModel<DeviceInfo> {
|
|||||||
void LoadAllDevices();
|
void LoadAllDevices();
|
||||||
void DeviceConnectFinished(const QString &id, bool success);
|
void DeviceConnectFinished(const QString &id, bool success);
|
||||||
void DeviceCloseFinished(const QString &id);
|
void DeviceCloseFinished(const QString &id);
|
||||||
void AddDeviceFromDB(DeviceInfo *info);
|
void AddDevicesFromDB(const DeviceDatabaseBackend::DeviceList &devices);
|
||||||
void BackendClosed();
|
void BackendClosed();
|
||||||
void ListerClosed();
|
void ListerClosed();
|
||||||
void DeviceDestroyed();
|
void DeviceDestroyed();
|
||||||
@@ -154,7 +155,7 @@ class DeviceManager : public SimpleTreeModel<DeviceInfo> {
|
|||||||
|
|
||||||
DeviceDatabaseBackend::Device InfoToDatabaseDevice(const DeviceInfo &info) const;
|
DeviceDatabaseBackend::Device InfoToDatabaseDevice(const DeviceInfo &info) const;
|
||||||
|
|
||||||
void RemoveFromDB(DeviceInfo *info, const QModelIndex &idx);
|
void RemoveFromDB(DeviceInfo *device_info, const QModelIndex &idx);
|
||||||
|
|
||||||
void CloseDevices();
|
void CloseDevices();
|
||||||
void CloseListers();
|
void CloseListers();
|
||||||
|
|||||||
@@ -28,7 +28,7 @@
|
|||||||
#include "devicemanager.h"
|
#include "devicemanager.h"
|
||||||
#include "devicestatefiltermodel.h"
|
#include "devicestatefiltermodel.h"
|
||||||
|
|
||||||
DeviceStateFilterModel::DeviceStateFilterModel(QObject *parent, DeviceManager::State state)
|
DeviceStateFilterModel::DeviceStateFilterModel(QObject *parent, const DeviceManager::State state)
|
||||||
: QSortFilterProxyModel(parent),
|
: QSortFilterProxyModel(parent),
|
||||||
state_(state) {
|
state_(state) {
|
||||||
|
|
||||||
@@ -40,7 +40,7 @@ DeviceStateFilterModel::DeviceStateFilterModel(QObject *parent, DeviceManager::S
|
|||||||
|
|
||||||
bool DeviceStateFilterModel::filterAcceptsRow(const int row, const QModelIndex &parent) const {
|
bool DeviceStateFilterModel::filterAcceptsRow(const int row, const QModelIndex &parent) const {
|
||||||
Q_UNUSED(parent)
|
Q_UNUSED(parent)
|
||||||
return sourceModel()->index(row, 0).data(DeviceManager::Role_State).toInt() != state_ && sourceModel()->index(row, 0).data(DeviceManager::Role_CopyMusic).toBool();
|
return sourceModel()->index(row, 0).data(DeviceManager::Role_State).value<DeviceManager::State>() != state_ && sourceModel()->index(row, 0).data(DeviceManager::Role_CopyMusic).toBool();
|
||||||
}
|
}
|
||||||
|
|
||||||
void DeviceStateFilterModel::ProxyRowCountChanged(const QModelIndex &idx, const int first, const int last) {
|
void DeviceStateFilterModel::ProxyRowCountChanged(const QModelIndex &idx, const int first, const int last) {
|
||||||
|
|||||||
@@ -37,7 +37,7 @@ class DeviceStateFilterModel : public QSortFilterProxyModel {
|
|||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
|
|
||||||
public:
|
public:
|
||||||
explicit DeviceStateFilterModel(QObject *parent, DeviceManager::State state = DeviceManager::State_Remembered);
|
explicit DeviceStateFilterModel(QObject *parent, const DeviceManager::State state = DeviceManager::State::Remembered);
|
||||||
|
|
||||||
void setSourceModel(QAbstractItemModel *sourceModel) override;
|
void setSourceModel(QAbstractItemModel *sourceModel) override;
|
||||||
|
|
||||||
@@ -52,7 +52,7 @@ class DeviceStateFilterModel : public QSortFilterProxyModel {
|
|||||||
void ProxyRowCountChanged(const QModelIndex &idx, const int first, const int last);
|
void ProxyRowCountChanged(const QModelIndex &idx, const int first, const int last);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
DeviceManager::State state_;
|
const DeviceManager::State state_;
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif // DEVICESTATEFILTERMODEL_H
|
#endif // DEVICESTATEFILTERMODEL_H
|
||||||
|
|||||||
@@ -128,19 +128,19 @@ void DeviceItemDelegate::paint(QPainter *painter, const QStyleOptionViewItem &op
|
|||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
switch (state) {
|
switch (state) {
|
||||||
case DeviceManager::State_Remembered:
|
case DeviceManager::State::Remembered:
|
||||||
status_text = tr("Not connected");
|
status_text = tr("Not connected");
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case DeviceManager::State_NotMounted:
|
case DeviceManager::State::NotMounted:
|
||||||
status_text = tr("Not mounted - double click to mount");
|
status_text = tr("Not mounted - double click to mount");
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case DeviceManager::State_NotConnected:
|
case DeviceManager::State::NotConnected:
|
||||||
status_text = tr("Double click to open");
|
status_text = tr("Double click to open");
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case DeviceManager::State_Connected:{
|
case DeviceManager::State::Connected:{
|
||||||
QVariant song_count = idx.data(DeviceManager::Role_SongCount);
|
QVariant song_count = idx.data(DeviceManager::Role_SongCount);
|
||||||
if (song_count.isValid()) {
|
if (song_count.isValid()) {
|
||||||
int count = song_count.toInt();
|
int count = song_count.toInt();
|
||||||
|
|||||||
@@ -409,5 +409,6 @@ bool GPodDevice::FinishDelete(bool success, QString &error_text) {
|
|||||||
bool GPodDevice::GetSupportedFiletypes(QList<Song::FileType> *ret) {
|
bool GPodDevice::GetSupportedFiletypes(QList<Song::FileType> *ret) {
|
||||||
*ret << Song::FileType::MP4;
|
*ret << Song::FileType::MP4;
|
||||||
*ret << Song::FileType::MPEG;
|
*ret << Song::FileType::MPEG;
|
||||||
|
*ret << Song::FileType::ALAC;
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
* Strawberry Music Player
|
* Strawberry Music Player
|
||||||
* This file was part of Clementine.
|
* This file was part of Clementine.
|
||||||
* Copyright 2010, David Sansome <me@davidsansome.com>
|
* Copyright 2010, David Sansome <me@davidsansome.com>
|
||||||
* Copyright 2018-2021, Jonas Kvinge <jonas@jkvinge.net>
|
* Copyright 2018-2025, Jonas Kvinge <jonas@jkvinge.net>
|
||||||
*
|
*
|
||||||
* Strawberry is free software: you can redistribute it and/or modify
|
* Strawberry is free software: you can redistribute it and/or modify
|
||||||
* it under the terms of the GNU General Public License as published by
|
* it under the terms of the GNU General Public License as published by
|
||||||
@@ -60,6 +60,7 @@ class MacOsDeviceLister : public DeviceLister {
|
|||||||
|
|
||||||
void UpdateDeviceFreeSpace(const QString &id);
|
void UpdateDeviceFreeSpace(const QString &id);
|
||||||
|
|
||||||
|
#ifdef HAVE_MTP
|
||||||
struct MTPDevice {
|
struct MTPDevice {
|
||||||
MTPDevice() : capacity(0), free_space(0) {}
|
MTPDevice() : capacity(0), free_space(0) {}
|
||||||
QString vendor;
|
QString vendor;
|
||||||
@@ -74,6 +75,7 @@ class MacOsDeviceLister : public DeviceLister {
|
|||||||
quint64 capacity;
|
quint64 capacity;
|
||||||
quint64 free_space;
|
quint64 free_space;
|
||||||
};
|
};
|
||||||
|
#endif // HAVE_MTP
|
||||||
|
|
||||||
void ExitAsync();
|
void ExitAsync();
|
||||||
|
|
||||||
@@ -91,11 +93,12 @@ class MacOsDeviceLister : public DeviceLister {
|
|||||||
|
|
||||||
static void DiskUnmountCallback(DADiskRef disk, DADissenterRef dissenter, void *context);
|
static void DiskUnmountCallback(DADiskRef disk, DADissenterRef dissenter, void *context);
|
||||||
|
|
||||||
void FoundMTPDevice(const MTPDevice &device, const QString &serial);
|
#ifdef HAVE_MTP
|
||||||
|
void FoundMTPDevice(const MTPDevice &mtp_device, const QString &serial);
|
||||||
void RemovedMTPDevice(const QString &serial);
|
void RemovedMTPDevice(const QString &serial);
|
||||||
|
|
||||||
quint64 GetFreeSpace(const QUrl &url);
|
quint64 GetFreeSpace(const QUrl &url);
|
||||||
quint64 GetCapacity(const QUrl &url);
|
quint64 GetCapacity(const QUrl &url);
|
||||||
|
#endif // HAVE_MTP
|
||||||
|
|
||||||
bool IsCDDevice(const QString &serial) const;
|
bool IsCDDevice(const QString &serial) const;
|
||||||
|
|
||||||
@@ -103,18 +106,23 @@ class MacOsDeviceLister : public DeviceLister {
|
|||||||
CFRunLoopRef run_loop_;
|
CFRunLoopRef run_loop_;
|
||||||
|
|
||||||
QMap<QString, QString> current_devices_;
|
QMap<QString, QString> current_devices_;
|
||||||
|
#ifdef HAVE_MTP
|
||||||
QMap<QString, MTPDevice> mtp_devices_;
|
QMap<QString, MTPDevice> mtp_devices_;
|
||||||
|
#endif
|
||||||
QSet<QString> cd_devices_;
|
QSet<QString> cd_devices_;
|
||||||
|
|
||||||
|
#ifdef HAVE_MTP
|
||||||
QMutex libmtp_mutex_;
|
QMutex libmtp_mutex_;
|
||||||
|
|
||||||
static QSet<MTPDevice> sMTPDeviceList;
|
static QSet<MTPDevice> sMTPDeviceList;
|
||||||
|
#endif
|
||||||
};
|
};
|
||||||
|
|
||||||
size_t qHash(const MacOsDeviceLister::MTPDevice &device);
|
#ifdef HAVE_MTP
|
||||||
|
size_t qHash(const MacOsDeviceLister::MTPDevice &mtp_device);
|
||||||
|
|
||||||
inline bool operator==(const MacOsDeviceLister::MTPDevice &a, const MacOsDeviceLister::MTPDevice &b) {
|
inline bool operator==(const MacOsDeviceLister::MTPDevice &a, const MacOsDeviceLister::MTPDevice &b) {
|
||||||
return (a.vendor_id == b.vendor_id) && (a.product_id == b.product_id);
|
return (a.vendor_id == b.vendor_id) && (a.product_id == b.product_id);
|
||||||
}
|
}
|
||||||
|
#endif // HAVE_MTP
|
||||||
|
|
||||||
#endif // MACDEVICELISTER_H
|
#endif // MACDEVICELISTER_H
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
* Strawberry Music Player
|
* Strawberry Music Player
|
||||||
* This file was part of Clementine.
|
* This file was part of Clementine.
|
||||||
* Copyright 2010, David Sansome <me@davidsansome.com>
|
* Copyright 2010, David Sansome <me@davidsansome.com>
|
||||||
* Copyright 2018-2021, Jonas Kvinge <jonas@jkvinge.net>
|
* Copyright 2018-2025, Jonas Kvinge <jonas@jkvinge.net>
|
||||||
*
|
*
|
||||||
* Strawberry is free software: you can redistribute it and/or modify
|
* Strawberry is free software: you can redistribute it and/or modify
|
||||||
* it under the terms of the GNU General Public License as published by
|
* it under the terms of the GNU General Public License as published by
|
||||||
@@ -21,7 +21,9 @@
|
|||||||
|
|
||||||
#include "config.h"
|
#include "config.h"
|
||||||
|
|
||||||
#include <libmtp.h>
|
#ifdef HAVE_MTP
|
||||||
|
# include <libmtp.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
#include <AvailabilityMacros.h>
|
#include <AvailabilityMacros.h>
|
||||||
#include <CoreFoundation/CFRunLoop.h>
|
#include <CoreFoundation/CFRunLoop.h>
|
||||||
@@ -41,12 +43,15 @@
|
|||||||
#include <QScopeGuard>
|
#include <QScopeGuard>
|
||||||
|
|
||||||
#include "macosdevicelister.h"
|
#include "macosdevicelister.h"
|
||||||
#include "mtpconnection.h"
|
|
||||||
#include "includes/scoped_cftyperef.h"
|
#include "includes/scoped_cftyperef.h"
|
||||||
#include "includes/scoped_nsobject.h"
|
#include "includes/scoped_nsobject.h"
|
||||||
#include "core/logging.h"
|
#include "core/logging.h"
|
||||||
#include "core/scoped_nsautorelease_pool.h"
|
#include "core/scoped_nsautorelease_pool.h"
|
||||||
|
|
||||||
|
#ifdef HAVE_MTP
|
||||||
|
# include "mtpconnection.h"
|
||||||
|
#endif
|
||||||
|
|
||||||
#import <AppKit/NSWorkspace.h>
|
#import <AppKit/NSWorkspace.h>
|
||||||
#import <Foundation/NSDictionary.h>
|
#import <Foundation/NSDictionary.h>
|
||||||
#import <Foundation/NSNotification.h>
|
#import <Foundation/NSNotification.h>
|
||||||
@@ -102,11 +107,15 @@ class ScopedIOObject {
|
|||||||
// Libgphoto2 MTP detection code:
|
// Libgphoto2 MTP detection code:
|
||||||
// http://www.sfr-fresh.com/unix/privat/libgphoto2-2.4.10.1.tar.gz:a/libgphoto2-2.4.10.1/libgphoto2_port/usb/check-mtp-device.c
|
// http://www.sfr-fresh.com/unix/privat/libgphoto2-2.4.10.1.tar.gz:a/libgphoto2-2.4.10.1/libgphoto2_port/usb/check-mtp-device.c
|
||||||
|
|
||||||
|
#ifdef HAVE_MTP
|
||||||
QSet<MacOsDeviceLister::MTPDevice> MacOsDeviceLister::sMTPDeviceList;
|
QSet<MacOsDeviceLister::MTPDevice> MacOsDeviceLister::sMTPDeviceList;
|
||||||
|
#endif
|
||||||
|
|
||||||
size_t qHash(const MacOsDeviceLister::MTPDevice &d) {
|
#ifdef HAVE_MTP
|
||||||
return qHash(d.vendor_id) ^ qHash(d.product_id);
|
size_t qHash(const MacOsDeviceLister::MTPDevice &mtp_device) {
|
||||||
|
return qHash(mtp_device.vendor_id) ^ qHash(mtp_device.product_id);
|
||||||
}
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
MacOsDeviceLister::MacOsDeviceLister(QObject *parent) : DeviceLister(parent) {}
|
MacOsDeviceLister::MacOsDeviceLister(QObject *parent) : DeviceLister(parent) {}
|
||||||
|
|
||||||
@@ -116,6 +125,7 @@ bool MacOsDeviceLister::Init() {
|
|||||||
|
|
||||||
ScopedNSAutoreleasePool pool;
|
ScopedNSAutoreleasePool pool;
|
||||||
|
|
||||||
|
#ifdef HAVE_MTP
|
||||||
// Populate MTP Device list.
|
// Populate MTP Device list.
|
||||||
if (sMTPDeviceList.empty()) {
|
if (sMTPDeviceList.empty()) {
|
||||||
LIBMTP_device_entry_t *devices = nullptr;
|
LIBMTP_device_entry_t *devices = nullptr;
|
||||||
@@ -126,25 +136,26 @@ bool MacOsDeviceLister::Init() {
|
|||||||
else {
|
else {
|
||||||
for (int i = 0; i < num; ++i) {
|
for (int i = 0; i < num; ++i) {
|
||||||
LIBMTP_device_entry_t device = devices[i];
|
LIBMTP_device_entry_t device = devices[i];
|
||||||
MTPDevice d;
|
MTPDevice mtp_device;
|
||||||
d.vendor = QString::fromLatin1(device.vendor);
|
mtp_device.vendor = QString::fromLatin1(device.vendor);
|
||||||
d.vendor_id = device.vendor_id;
|
mtp_device.vendor_id = device.vendor_id;
|
||||||
d.product = QString::fromLatin1(device.product);
|
mtp_device.product = QString::fromLatin1(device.product);
|
||||||
d.product_id = device.product_id;
|
mtp_device.product_id = device.product_id;
|
||||||
d.quirks = device.device_flags;
|
mtp_device.quirks = device.device_flags;
|
||||||
sMTPDeviceList << d;
|
sMTPDeviceList << mtp_device;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
MTPDevice d;
|
MTPDevice mtp_device;
|
||||||
d.vendor = "SanDisk"_L1;
|
mtp_device.vendor = "SanDisk"_L1;
|
||||||
d.vendor_id = 0x781;
|
mtp_device.vendor_id = 0x781;
|
||||||
d.product = "Sansa Clip+"_L1;
|
mtp_device.product = "Sansa Clip+"_L1;
|
||||||
d.product_id = 0x74d0;
|
mtp_device.product_id = 0x74d0;
|
||||||
|
|
||||||
d.quirks = 0x2 | 0x4 | 0x40 | 0x4000;
|
mtp_device.quirks = 0x2 | 0x4 | 0x40 | 0x4000;
|
||||||
sMTPDeviceList << d;
|
sMTPDeviceList << mtp_device;
|
||||||
}
|
}
|
||||||
|
#endif // HAVE_MTP
|
||||||
|
|
||||||
run_loop_ = CFRunLoopGetCurrent();
|
run_loop_ = CFRunLoopGetCurrent();
|
||||||
|
|
||||||
@@ -240,12 +251,14 @@ CFTypeRef GetUSBRegistryEntry(io_object_t device, CFStringRef key) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
QString GetUSBRegistryEntryString(io_object_t device, CFStringRef key) {
|
QString GetUSBRegistryEntryString(io_object_t device, CFStringRef key) {
|
||||||
|
|
||||||
ScopedCFTypeRef<CFStringRef> registry_string(reinterpret_cast<CFStringRef>(GetUSBRegistryEntry(device, key)));
|
ScopedCFTypeRef<CFStringRef> registry_string(reinterpret_cast<CFStringRef>(GetUSBRegistryEntry(device, key)));
|
||||||
if (registry_string) {
|
if (registry_string) {
|
||||||
return QString::fromUtf8([reinterpret_cast<NSString*>(registry_string.get()) UTF8String]);
|
return QString::fromUtf8([reinterpret_cast<NSString*>(registry_string.get()) UTF8String]);
|
||||||
}
|
}
|
||||||
|
|
||||||
return QString();
|
return QString();
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
NSObject *GetPropertyForDevice(io_object_t device, CFStringRef key) {
|
NSObject *GetPropertyForDevice(io_object_t device, CFStringRef key) {
|
||||||
@@ -277,17 +290,13 @@ NSObject *GetPropertyForDevice(io_object_t device, CFStringRef key) {
|
|||||||
|
|
||||||
int GetUSBDeviceClass(io_object_t device) {
|
int GetUSBDeviceClass(io_object_t device) {
|
||||||
|
|
||||||
ScopedCFTypeRef<CFTypeRef> interface_class(IORegistryEntrySearchCFProperty(
|
ScopedCFTypeRef<CFTypeRef> interface_class(IORegistryEntrySearchCFProperty(device, kIOServicePlane, CFSTR(kUSBInterfaceClass), kCFAllocatorDefault, kIORegistryIterateRecursively));
|
||||||
device,
|
|
||||||
kIOServicePlane,
|
|
||||||
CFSTR(kUSBInterfaceClass),
|
|
||||||
kCFAllocatorDefault,
|
|
||||||
kIORegistryIterateRecursively));
|
|
||||||
NSNumber *number = reinterpret_cast<NSNumber*>(interface_class.get());
|
NSNumber *number = reinterpret_cast<NSNumber*>(interface_class.get());
|
||||||
if (number) {
|
if (number) {
|
||||||
int ret = [number unsignedShortValue];
|
int ret = [number unsignedShortValue];
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -322,12 +331,14 @@ QString GetSerialForDevice(io_object_t device) {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#ifdef HAVE_MTP
|
||||||
QString GetSerialForMTPDevice(io_object_t device) {
|
QString GetSerialForMTPDevice(io_object_t device) {
|
||||||
|
|
||||||
scoped_nsobject<NSString> serial(reinterpret_cast<NSString*>(GetPropertyForDevice(device, CFSTR(kUSBSerialNumberString))));
|
scoped_nsobject<NSString> serial(reinterpret_cast<NSString*>(GetPropertyForDevice(device, CFSTR(kUSBSerialNumberString))));
|
||||||
return "MTP/"_L1 + QString::fromUtf8([serial UTF8String]);
|
return "MTP/"_L1 + QString::fromUtf8([serial UTF8String]);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
#endif // HAVE_MTP
|
||||||
|
|
||||||
QString FindDeviceProperty(const QString &bsd_name, CFStringRef property) {
|
QString FindDeviceProperty(const QString &bsd_name, CFStringRef property) {
|
||||||
|
|
||||||
@@ -343,6 +354,7 @@ QString FindDeviceProperty(const QString &bsd_name, CFStringRef property) {
|
|||||||
|
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
|
#ifdef HAVE_MTP
|
||||||
quint64 MacOsDeviceLister::GetFreeSpace(const QUrl &url) {
|
quint64 MacOsDeviceLister::GetFreeSpace(const QUrl &url) {
|
||||||
|
|
||||||
QMutexLocker l(&libmtp_mutex_);
|
QMutexLocker l(&libmtp_mutex_);
|
||||||
@@ -380,6 +392,8 @@ quint64 MacOsDeviceLister::GetCapacity(const QUrl &url) {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#endif // HAVE_MTP
|
||||||
|
|
||||||
void MacOsDeviceLister::DiskAddedCallback(DADiskRef disk, void *context) {
|
void MacOsDeviceLister::DiskAddedCallback(DADiskRef disk, void *context) {
|
||||||
|
|
||||||
MacOsDeviceLister *me = reinterpret_cast<MacOsDeviceLister*>(context);
|
MacOsDeviceLister *me = reinterpret_cast<MacOsDeviceLister*>(context);
|
||||||
@@ -390,12 +404,12 @@ void MacOsDeviceLister::DiskAddedCallback(DADiskRef disk, void *context) {
|
|||||||
NSString *kind = [properties objectForKey:reinterpret_cast<NSString*>(kDADiskDescriptionMediaKindKey)];
|
NSString *kind = [properties objectForKey:reinterpret_cast<NSString*>(kDADiskDescriptionMediaKindKey)];
|
||||||
if (kind && strcmp([kind UTF8String], kIOCDMediaClass) == 0) {
|
if (kind && strcmp([kind UTF8String], kIOCDMediaClass) == 0) {
|
||||||
// CD inserted.
|
// CD inserted.
|
||||||
QString bsd_name = QString::fromLatin1(DADiskGetBSDName(disk));
|
const QString bsd_name = QString::fromLatin1(DADiskGetBSDName(disk));
|
||||||
me->cd_devices_ << bsd_name;
|
me->cd_devices_ << bsd_name;
|
||||||
Q_EMIT me->DeviceAdded(bsd_name);
|
Q_EMIT me->DeviceAdded(bsd_name);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
#endif
|
#endif // HAVE_AUDIOCD
|
||||||
|
|
||||||
NSURL *volume_path = [[properties objectForKey:reinterpret_cast<NSString*>(kDADiskDescriptionVolumePathKey)] copy];
|
NSURL *volume_path = [[properties objectForKey:reinterpret_cast<NSString*>(kDADiskDescriptionVolumePathKey)] copy];
|
||||||
|
|
||||||
@@ -403,8 +417,8 @@ void MacOsDeviceLister::DiskAddedCallback(DADiskRef disk, void *context) {
|
|||||||
ScopedIOObject device(DADiskCopyIOMedia(disk));
|
ScopedIOObject device(DADiskCopyIOMedia(disk));
|
||||||
ScopedCFTypeRef<CFStringRef> class_name(IOObjectCopyClass(device.get()));
|
ScopedCFTypeRef<CFStringRef> class_name(IOObjectCopyClass(device.get()));
|
||||||
if (class_name && CFStringCompare(class_name.get(), CFSTR(kIOMediaClass), 0) == kCFCompareEqualTo) {
|
if (class_name && CFStringCompare(class_name.get(), CFSTR(kIOMediaClass), 0) == kCFCompareEqualTo) {
|
||||||
QString vendor = GetUSBRegistryEntryString(device.get(), CFSTR(kUSBVendorString));
|
const QString vendor = GetUSBRegistryEntryString(device.get(), CFSTR(kUSBVendorString));
|
||||||
QString product = GetUSBRegistryEntryString(device.get(), CFSTR(kUSBProductString));
|
const QString product = GetUSBRegistryEntryString(device.get(), CFSTR(kUSBProductString));
|
||||||
|
|
||||||
CFMutableDictionaryRef cf_properties;
|
CFMutableDictionaryRef cf_properties;
|
||||||
kern_return_t ret = IORegistryEntryCreateCFProperties(device.get(), &cf_properties, kCFAllocatorDefault, 0);
|
kern_return_t ret = IORegistryEntryCreateCFProperties(device.get(), &cf_properties, kCFAllocatorDefault, 0);
|
||||||
@@ -412,7 +426,7 @@ void MacOsDeviceLister::DiskAddedCallback(DADiskRef disk, void *context) {
|
|||||||
if (ret == KERN_SUCCESS) {
|
if (ret == KERN_SUCCESS) {
|
||||||
scoped_nsobject<NSDictionary> dict(reinterpret_cast<NSDictionary*>(cf_properties)); // Takes ownership.
|
scoped_nsobject<NSDictionary> dict(reinterpret_cast<NSDictionary*>(cf_properties)); // Takes ownership.
|
||||||
if ([[dict objectForKey:@"Removable"] intValue] == 1) {
|
if ([[dict objectForKey:@"Removable"] intValue] == 1) {
|
||||||
QString serial = GetSerialForDevice(device.get());
|
const QString serial = GetSerialForDevice(device.get());
|
||||||
if (!serial.isEmpty()) {
|
if (!serial.isEmpty()) {
|
||||||
me->current_devices_[serial] = QString::fromLatin1(DADiskGetBSDName(disk));
|
me->current_devices_[serial] = QString::fromLatin1(DADiskGetBSDName(disk));
|
||||||
Q_EMIT me->DeviceAdded(serial);
|
Q_EMIT me->DeviceAdded(serial);
|
||||||
@@ -427,10 +441,9 @@ void MacOsDeviceLister::DiskAddedCallback(DADiskRef disk, void *context) {
|
|||||||
void MacOsDeviceLister::DiskRemovedCallback(DADiskRef disk, void *context) {
|
void MacOsDeviceLister::DiskRemovedCallback(DADiskRef disk, void *context) {
|
||||||
|
|
||||||
MacOsDeviceLister *me = reinterpret_cast<MacOsDeviceLister*>(context);
|
MacOsDeviceLister *me = reinterpret_cast<MacOsDeviceLister*>(context);
|
||||||
// We cannot access the USB tree when the disk is removed but we still get
|
// We cannot access the USB tree when the disk is removed but we still get the BSD disk name.
|
||||||
// the BSD disk name.
|
|
||||||
|
|
||||||
QString bsd_name = QString::fromLatin1(DADiskGetBSDName(disk));
|
const QString bsd_name = QString::fromLatin1(DADiskGetBSDName(disk));
|
||||||
if (me->cd_devices_.remove(bsd_name)) {
|
if (me->cd_devices_.remove(bsd_name)) {
|
||||||
Q_EMIT me->DeviceRemoved(bsd_name);
|
Q_EMIT me->DeviceRemoved(bsd_name);
|
||||||
return;
|
return;
|
||||||
@@ -496,6 +509,7 @@ int GetBusNumber(io_object_t o) {
|
|||||||
void MacOsDeviceLister::USBDeviceAddedCallback(void *refcon, io_iterator_t it) {
|
void MacOsDeviceLister::USBDeviceAddedCallback(void *refcon, io_iterator_t it) {
|
||||||
|
|
||||||
MacOsDeviceLister *me = reinterpret_cast<MacOsDeviceLister*>(refcon);
|
MacOsDeviceLister *me = reinterpret_cast<MacOsDeviceLister*>(refcon);
|
||||||
|
Q_UNUSED(me)
|
||||||
|
|
||||||
io_object_t object;
|
io_object_t object;
|
||||||
while ((object = IOIteratorNext(it))) {
|
while ((object = IOIteratorNext(it))) {
|
||||||
@@ -503,30 +517,34 @@ void MacOsDeviceLister::USBDeviceAddedCallback(void *refcon, io_iterator_t it) {
|
|||||||
const QScopeGuard io_object_release = qScopeGuard([object]() { IOObjectRelease(object); });
|
const QScopeGuard io_object_release = qScopeGuard([object]() { IOObjectRelease(object); });
|
||||||
|
|
||||||
if (CFStringCompare(class_name.get(), CFSTR(kIOUSBDeviceClassName), 0) == kCFCompareEqualTo) {
|
if (CFStringCompare(class_name.get(), CFSTR(kIOUSBDeviceClassName), 0) == kCFCompareEqualTo) {
|
||||||
|
|
||||||
|
const int interface_class = GetUSBDeviceClass(object);
|
||||||
|
qLog(Debug) << "Interface class:" << interface_class;
|
||||||
|
|
||||||
|
#ifdef HAVE_MTP
|
||||||
|
|
||||||
NSString *vendor = reinterpret_cast<NSString*>(GetPropertyForDevice(object, CFSTR(kUSBVendorString)));
|
NSString *vendor = reinterpret_cast<NSString*>(GetPropertyForDevice(object, CFSTR(kUSBVendorString)));
|
||||||
NSString *product = reinterpret_cast<NSString*>(GetPropertyForDevice(object, CFSTR(kUSBProductString)));
|
NSString *product = reinterpret_cast<NSString*>(GetPropertyForDevice(object, CFSTR(kUSBProductString)));
|
||||||
NSNumber *vendor_id = reinterpret_cast<NSNumber*>(GetPropertyForDevice(object, CFSTR(kUSBVendorID)));
|
NSNumber *vendor_id = reinterpret_cast<NSNumber*>(GetPropertyForDevice(object, CFSTR(kUSBVendorID)));
|
||||||
NSNumber *product_id = reinterpret_cast<NSNumber*>(GetPropertyForDevice(object, CFSTR(kUSBProductID)));
|
NSNumber *product_id = reinterpret_cast<NSNumber*>(GetPropertyForDevice(object, CFSTR(kUSBProductID)));
|
||||||
int interface_class = GetUSBDeviceClass(object);
|
|
||||||
qLog(Debug) << "Interface class:" << interface_class;
|
|
||||||
|
|
||||||
QString serial = GetSerialForMTPDevice(object);
|
const QString serial = GetSerialForMTPDevice(object);
|
||||||
|
|
||||||
MTPDevice device;
|
MTPDevice mtp_device;
|
||||||
device.vendor = QString::fromUtf8([vendor UTF8String]);
|
mtp_device.vendor = QString::fromUtf8([vendor UTF8String]);
|
||||||
device.product = QString::fromUtf8([product UTF8String]);
|
mtp_device.product = QString::fromUtf8([product UTF8String]);
|
||||||
device.vendor_id = [vendor_id unsignedShortValue];
|
mtp_device.vendor_id = [vendor_id unsignedShortValue];
|
||||||
device.product_id = [product_id unsignedShortValue];
|
mtp_device.product_id = [product_id unsignedShortValue];
|
||||||
device.quirks = 0;
|
mtp_device.quirks = 0;
|
||||||
|
|
||||||
device.bus = -1;
|
mtp_device.bus = -1;
|
||||||
device.address = -1;
|
mtp_device.address = -1;
|
||||||
|
|
||||||
if (device.vendor_id == kAppleVendorID || // I think we can safely skip Apple products.
|
if (mtp_device.vendor_id == kAppleVendorID || // I think we can safely skip Apple products.
|
||||||
// Blacklist ilok2 as this probe may be breaking it.
|
// Blacklist ilok2 as this probe may be breaking it.
|
||||||
(device.vendor_id == 0x088e && device.product_id == 0x5036) ||
|
(mtp_device.vendor_id == 0x088e && mtp_device.product_id == 0x5036) ||
|
||||||
// Blacklist eLicenser
|
// Blacklist eLicenser
|
||||||
(device.vendor_id == 0x0819 && device.product_id == 0x0101) ||
|
(mtp_device.vendor_id == 0x0819 && mtp_device.product_id == 0x0101) ||
|
||||||
// Skip HID devices, printers and hubs.
|
// Skip HID devices, printers and hubs.
|
||||||
interface_class == kUSBHIDInterfaceClass ||
|
interface_class == kUSBHIDInterfaceClass ||
|
||||||
interface_class == kUSBPrintingInterfaceClass ||
|
interface_class == kUSBPrintingInterfaceClass ||
|
||||||
@@ -535,31 +553,28 @@ void MacOsDeviceLister::USBDeviceAddedCallback(void *refcon, io_iterator_t it) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
NSNumber *addr = reinterpret_cast<NSNumber*>(GetPropertyForDevice(object, CFSTR("USB Address")));
|
NSNumber *addr = reinterpret_cast<NSNumber*>(GetPropertyForDevice(object, CFSTR("USB Address")));
|
||||||
int bus = GetBusNumber(object);
|
const int bus = GetBusNumber(object);
|
||||||
if (!addr || bus == -1) {
|
if (!addr || bus == -1) {
|
||||||
// Failed to get bus or address number.
|
// Failed to get bus or address number.
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
device.bus = bus;
|
mtp_device.bus = bus;
|
||||||
device.address = [addr intValue];
|
mtp_device.address = [addr intValue];
|
||||||
|
|
||||||
// First check the libmtp device list.
|
// First check the libmtp device list.
|
||||||
QSet<MTPDevice>::const_iterator it2 = sMTPDeviceList.find(device);
|
QSet<MTPDevice>::const_iterator it2 = sMTPDeviceList.find(mtp_device);
|
||||||
if (it2 != sMTPDeviceList.end()) {
|
if (it2 != sMTPDeviceList.end()) {
|
||||||
// Fill in quirks flags from libmtp.
|
// Fill in quirks flags from libmtp.
|
||||||
device.quirks = it2->quirks;
|
mtp_device.quirks = it2->quirks;
|
||||||
me->FoundMTPDevice(device, GetSerialForMTPDevice(object));
|
me->FoundMTPDevice(mtp_device, GetSerialForMTPDevice(object));
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#endif // HAVE_MTP
|
||||||
|
|
||||||
IOCFPlugInInterface **plugin_interface = nullptr;
|
IOCFPlugInInterface **plugin_interface = nullptr;
|
||||||
SInt32 score;
|
SInt32 score;
|
||||||
kern_return_t err = IOCreatePlugInInterfaceForService(
|
kern_return_t err = IOCreatePlugInInterfaceForService(object, kIOUSBDeviceUserClientTypeID, kIOCFPlugInInterfaceID, &plugin_interface, &score);
|
||||||
object,
|
|
||||||
kIOUSBDeviceUserClientTypeID,
|
|
||||||
kIOCFPlugInInterfaceID,
|
|
||||||
&plugin_interface,
|
|
||||||
&score);
|
|
||||||
if (err != KERN_SUCCESS) {
|
if (err != KERN_SUCCESS) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
@@ -590,7 +605,7 @@ void MacOsDeviceLister::USBDeviceAddedCallback(void *refcon, io_iterator_t it) {
|
|||||||
bool ret = DeviceRequest(dev, kUSBIn, kUSBStandard, kUSBDevice, kUSBRqGetDescriptor, (kUSBStringDesc << 8) | 0xee, 0x0409, 2, &data);
|
bool ret = DeviceRequest(dev, kUSBIn, kUSBStandard, kUSBDevice, kUSBRqGetDescriptor, (kUSBStringDesc << 8) | 0xee, 0x0409, 2, &data);
|
||||||
if (!ret) continue;
|
if (!ret) continue;
|
||||||
|
|
||||||
UInt8 string_len = data[0];
|
const UInt8 string_len = data[0];
|
||||||
|
|
||||||
ret = DeviceRequest(dev, kUSBIn, kUSBStandard, kUSBDevice, kUSBRqGetDescriptor, (kUSBStringDesc << 8) | 0xee, 0x0409, string_len, &data);
|
ret = DeviceRequest(dev, kUSBIn, kUSBStandard, kUSBDevice, kUSBRqGetDescriptor, (kUSBStringDesc << 8) | 0xee, 0x0409, string_len, &data);
|
||||||
if (!ret) continue;
|
if (!ret) continue;
|
||||||
@@ -599,6 +614,7 @@ void MacOsDeviceLister::USBDeviceAddedCallback(void *refcon, io_iterator_t it) {
|
|||||||
// Because this was designed by MS, the characters are in UTF-16 (LE?).
|
// Because this was designed by MS, the characters are in UTF-16 (LE?).
|
||||||
QString str = QString::fromUtf16(reinterpret_cast<char16_t*>(data.data() + 2), (data.size() / 2) - 2);
|
QString str = QString::fromUtf16(reinterpret_cast<char16_t*>(data.data() + 2), (data.size() / 2) - 2);
|
||||||
|
|
||||||
|
#ifdef HAVE_MTP
|
||||||
if (str.startsWith("MSFT100"_L1)) {
|
if (str.startsWith("MSFT100"_L1)) {
|
||||||
// We got the OS descriptor!
|
// We got the OS descriptor!
|
||||||
char vendor_code = data[16];
|
char vendor_code = data[16];
|
||||||
@@ -621,8 +637,10 @@ void MacOsDeviceLister::USBDeviceAddedCallback(void *refcon, io_iterator_t it) {
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
// Hurray! We made it!
|
// Hurray! We made it!
|
||||||
me->FoundMTPDevice(device, serial);
|
me->FoundMTPDevice(mtp_device, serial);
|
||||||
}
|
}
|
||||||
|
#endif // HAVE_MTP
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -631,30 +649,39 @@ void MacOsDeviceLister::USBDeviceAddedCallback(void *refcon, io_iterator_t it) {
|
|||||||
void MacOsDeviceLister::USBDeviceRemovedCallback(void *refcon, io_iterator_t it) {
|
void MacOsDeviceLister::USBDeviceRemovedCallback(void *refcon, io_iterator_t it) {
|
||||||
|
|
||||||
MacOsDeviceLister *me = reinterpret_cast<MacOsDeviceLister*>(refcon);
|
MacOsDeviceLister *me = reinterpret_cast<MacOsDeviceLister*>(refcon);
|
||||||
|
Q_UNUSED(me)
|
||||||
|
|
||||||
io_object_t object;
|
io_object_t object;
|
||||||
while ((object = IOIteratorNext(it))) {
|
while ((object = IOIteratorNext(it))) {
|
||||||
ScopedCFTypeRef<CFStringRef> class_name(IOObjectCopyClass(object));
|
ScopedCFTypeRef<CFStringRef> class_name(IOObjectCopyClass(object));
|
||||||
const QScopeGuard io_object_release = qScopeGuard([object]() { IOObjectRelease(object); });
|
const QScopeGuard io_object_release = qScopeGuard([object]() { IOObjectRelease(object); });
|
||||||
|
|
||||||
if (CFStringCompare(class_name.get(), CFSTR(kIOUSBDeviceClassName), 0) == kCFCompareEqualTo) {
|
if (CFStringCompare(class_name.get(), CFSTR(kIOUSBDeviceClassName), 0) == kCFCompareEqualTo) {
|
||||||
|
|
||||||
|
#ifdef HAVE_MTP
|
||||||
NSString *vendor = reinterpret_cast<NSString*>(GetPropertyForDevice(object, CFSTR(kUSBVendorString)));
|
NSString *vendor = reinterpret_cast<NSString*>(GetPropertyForDevice(object, CFSTR(kUSBVendorString)));
|
||||||
NSString *product = reinterpret_cast<NSString*>(GetPropertyForDevice(object, CFSTR(kUSBProductString)));
|
NSString *product = reinterpret_cast<NSString*>(GetPropertyForDevice(object, CFSTR(kUSBProductString)));
|
||||||
NSNumber *vendor_id = reinterpret_cast<NSNumber*>(GetPropertyForDevice(object, CFSTR(kUSBVendorID)));
|
NSNumber *vendor_id = reinterpret_cast<NSNumber*>(GetPropertyForDevice(object, CFSTR(kUSBVendorID)));
|
||||||
NSNumber *product_id = reinterpret_cast<NSNumber*>(GetPropertyForDevice(object, CFSTR(kUSBProductID)));
|
NSNumber *product_id = reinterpret_cast<NSNumber*>(GetPropertyForDevice(object, CFSTR(kUSBProductID)));
|
||||||
QString serial = GetSerialForMTPDevice(object);
|
|
||||||
|
|
||||||
MTPDevice device;
|
const QString serial = GetSerialForMTPDevice(object);
|
||||||
device.vendor = QString::fromUtf8([vendor UTF8String]);
|
|
||||||
device.product = QString::fromUtf8([product UTF8String]);
|
MTPDevice mtp_device;
|
||||||
device.vendor_id = [vendor_id unsignedShortValue];
|
mtp_device.vendor = QString::fromUtf8([vendor UTF8String]);
|
||||||
device.product_id = [product_id unsignedShortValue];
|
mtp_device.product = QString::fromUtf8([product UTF8String]);
|
||||||
|
mtp_device.vendor_id = [vendor_id unsignedShortValue];
|
||||||
|
mtp_device.product_id = [product_id unsignedShortValue];
|
||||||
|
|
||||||
me->RemovedMTPDevice(serial);
|
me->RemovedMTPDevice(serial);
|
||||||
|
#endif // HAVE_MTP
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#ifdef HAVE_MTP
|
||||||
|
|
||||||
void MacOsDeviceLister::RemovedMTPDevice(const QString &serial) {
|
void MacOsDeviceLister::RemovedMTPDevice(const QString &serial) {
|
||||||
|
|
||||||
int count = mtp_devices_.remove(serial);
|
int count = mtp_devices_.remove(serial);
|
||||||
@@ -668,34 +695,40 @@ void MacOsDeviceLister::RemovedMTPDevice(const QString &serial) {
|
|||||||
void MacOsDeviceLister::FoundMTPDevice(const MTPDevice &device, const QString &serial) {
|
void MacOsDeviceLister::FoundMTPDevice(const MTPDevice &device, const QString &serial) {
|
||||||
|
|
||||||
qLog(Debug) << "New MTP device detected!" << device.bus << device.address;
|
qLog(Debug) << "New MTP device detected!" << device.bus << device.address;
|
||||||
|
|
||||||
mtp_devices_[serial] = device;
|
mtp_devices_[serial] = device;
|
||||||
QList<QUrl> urls = MakeDeviceUrls(serial);
|
const QList<QUrl> urls = MakeDeviceUrls(serial);
|
||||||
MTPDevice *d = &mtp_devices_[serial];
|
MTPDevice *mtp_device = &mtp_devices_[serial];
|
||||||
d->capacity = GetCapacity(urls[0]);
|
mtp_device->capacity = GetCapacity(urls[0]);
|
||||||
d->free_space = GetFreeSpace(urls[0]);
|
mtp_device->free_space = GetFreeSpace(urls[0]);
|
||||||
|
|
||||||
Q_EMIT DeviceAdded(serial);
|
Q_EMIT DeviceAdded(serial);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bool IsMTPSerial(const QString &serial) { return serial.startsWith("MTP"_L1); }
|
bool IsMTPSerial(const QString &serial) { return serial.startsWith("MTP"_L1); }
|
||||||
|
|
||||||
|
#endif // HAVE_MTP
|
||||||
|
|
||||||
bool MacOsDeviceLister::IsCDDevice(const QString &serial) const {
|
bool MacOsDeviceLister::IsCDDevice(const QString &serial) const {
|
||||||
return cd_devices_.contains(serial);
|
return cd_devices_.contains(serial);
|
||||||
}
|
}
|
||||||
|
|
||||||
QString MacOsDeviceLister::MakeFriendlyName(const QString &serial) {
|
QString MacOsDeviceLister::MakeFriendlyName(const QString &serial) {
|
||||||
|
|
||||||
|
#ifdef HAVE_MTP
|
||||||
if (IsMTPSerial(serial)) {
|
if (IsMTPSerial(serial)) {
|
||||||
const MTPDevice &device = mtp_devices_[serial];
|
const MTPDevice &mtp_device = mtp_devices_[serial];
|
||||||
if (device.vendor.isEmpty()) {
|
if (mtp_device.vendor.isEmpty()) {
|
||||||
return device.product;
|
return mtp_device.product;
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
return device.vendor + QLatin1Char(' ') + device.product;
|
return mtp_device.vendor + QLatin1Char(' ') + mtp_device.product;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
#endif // HAVE_MTP
|
||||||
|
|
||||||
QString bsd_name = IsCDDevice(serial) ? *cd_devices_.find(serial) : current_devices_[serial];
|
const QString bsd_name = IsCDDevice(serial) ? *cd_devices_.find(serial) : current_devices_[serial];
|
||||||
ScopedCFTypeRef<DASessionRef> session(DASessionCreate(kCFAllocatorDefault));
|
ScopedCFTypeRef<DASessionRef> session(DASessionCreate(kCFAllocatorDefault));
|
||||||
ScopedCFTypeRef<DADiskRef> disk(DADiskCreateFromBSDName(kCFAllocatorDefault, session.get(), bsd_name.toLatin1().constData()));
|
ScopedCFTypeRef<DADiskRef> disk(DADiskCreateFromBSDName(kCFAllocatorDefault, session.get(), bsd_name.toLatin1().constData()));
|
||||||
|
|
||||||
@@ -708,75 +741,86 @@ QString MacOsDeviceLister::MakeFriendlyName(const QString &serial) {
|
|||||||
|
|
||||||
ScopedIOObject device(DADiskCopyIOMedia(disk));
|
ScopedIOObject device(DADiskCopyIOMedia(disk));
|
||||||
|
|
||||||
QString vendor = GetUSBRegistryEntryString(device.get(), CFSTR(kUSBVendorString));
|
const QString vendor = GetUSBRegistryEntryString(device.get(), CFSTR(kUSBVendorString));
|
||||||
QString product = GetUSBRegistryEntryString(device.get(), CFSTR(kUSBProductString));
|
const QString product = GetUSBRegistryEntryString(device.get(), CFSTR(kUSBProductString));
|
||||||
|
|
||||||
if (vendor.isEmpty()) {
|
if (vendor.isEmpty()) {
|
||||||
return product;
|
return product;
|
||||||
}
|
}
|
||||||
|
|
||||||
return vendor + QLatin1Char(' ') + product;
|
return vendor + QLatin1Char(' ') + product;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
QList<QUrl> MacOsDeviceLister::MakeDeviceUrls(const QString &serial) {
|
QList<QUrl> MacOsDeviceLister::MakeDeviceUrls(const QString &serial) {
|
||||||
|
|
||||||
|
#ifdef HAVE_MTP
|
||||||
if (IsMTPSerial(serial)) {
|
if (IsMTPSerial(serial)) {
|
||||||
const MTPDevice &device = mtp_devices_[serial];
|
const MTPDevice &mtp_device = mtp_devices_[serial];
|
||||||
QString str = QString::asprintf("gphoto2://usb-%d-%d/", device.bus, device.address);
|
const QString str = QString::asprintf("gphoto2://usb-%d-%d/", mtp_device.bus, mtp_device.address);
|
||||||
QUrlQuery url_query;
|
QUrlQuery url_query;
|
||||||
url_query.addQueryItem(u"vendor"_s, device.vendor);
|
url_query.addQueryItem(u"vendor"_s, mtp_device.vendor);
|
||||||
url_query.addQueryItem(u"vendor_id"_s, QString::number(device.vendor_id));
|
url_query.addQueryItem(u"vendor_id"_s, QString::number(mtp_device.vendor_id));
|
||||||
url_query.addQueryItem(u"product"_s, device.product);
|
url_query.addQueryItem(u"product"_s, mtp_device.product);
|
||||||
url_query.addQueryItem(u"product_id"_s, QString::number(device.product_id));
|
url_query.addQueryItem(u"product_id"_s, QString::number(mtp_device.product_id));
|
||||||
url_query.addQueryItem(u"quirks"_s, QString::number(device.quirks));
|
url_query.addQueryItem(u"quirks"_s, QString::number(mtp_device.quirks));
|
||||||
QUrl url(str);
|
QUrl url(str);
|
||||||
url.setQuery(url_query);
|
url.setQuery(url_query);
|
||||||
return QList<QUrl>() << url;
|
return QList<QUrl>() << url;
|
||||||
}
|
}
|
||||||
|
#endif // HAVE_MTP
|
||||||
|
|
||||||
if (IsCDDevice(serial)) {
|
if (IsCDDevice(serial)) {
|
||||||
return QList<QUrl>() << QUrl(u"cdda:///dev/r"_s + serial);
|
return QList<QUrl>() << QUrl(u"cdda:///dev/r"_s + serial);
|
||||||
}
|
}
|
||||||
|
|
||||||
QString bsd_name = current_devices_[serial];
|
const QString bsd_name = current_devices_[serial];
|
||||||
ScopedCFTypeRef<DASessionRef> session(DASessionCreate(kCFAllocatorDefault));
|
ScopedCFTypeRef<DASessionRef> session(DASessionCreate(kCFAllocatorDefault));
|
||||||
ScopedCFTypeRef<DADiskRef> disk(DADiskCreateFromBSDName(kCFAllocatorDefault, session.get(), bsd_name.toLatin1().constData()));
|
ScopedCFTypeRef<DADiskRef> disk(DADiskCreateFromBSDName(kCFAllocatorDefault, session.get(), bsd_name.toLatin1().constData()));
|
||||||
|
|
||||||
scoped_nsobject<NSDictionary> properties(reinterpret_cast<NSDictionary*>(DADiskCopyDescription(disk.get())));
|
scoped_nsobject<NSDictionary> properties(reinterpret_cast<NSDictionary*>(DADiskCopyDescription(disk.get())));
|
||||||
scoped_nsobject<NSURL> volume_path([[properties objectForKey:reinterpret_cast<NSString*>(kDADiskDescriptionVolumePathKey)] copy]);
|
scoped_nsobject<NSURL> volume_path([[properties objectForKey:reinterpret_cast<NSString*>(kDADiskDescriptionVolumePathKey)] copy]);
|
||||||
|
|
||||||
QString path = QString::fromUtf8([[volume_path path] UTF8String]);
|
const QString path = QString::fromUtf8([[volume_path path] UTF8String]);
|
||||||
QUrl ret = MakeUrlFromLocalPath(path);
|
const QUrl ret = MakeUrlFromLocalPath(path);
|
||||||
|
|
||||||
return QList<QUrl>() << ret;
|
return QList<QUrl>() << ret;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
QStringList MacOsDeviceLister::DeviceUniqueIDs() {
|
QStringList MacOsDeviceLister::DeviceUniqueIDs() {
|
||||||
|
|
||||||
|
#ifdef HAVE_MTP
|
||||||
return current_devices_.keys() + mtp_devices_.keys();
|
return current_devices_.keys() + mtp_devices_.keys();
|
||||||
|
#else
|
||||||
|
return current_devices_.keys();
|
||||||
|
#endif
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
QVariantList MacOsDeviceLister::DeviceIcons(const QString &serial) {
|
QVariantList MacOsDeviceLister::DeviceIcons(const QString &serial) {
|
||||||
|
|
||||||
|
#ifdef HAVE_MTP
|
||||||
if (IsMTPSerial(serial)) {
|
if (IsMTPSerial(serial)) {
|
||||||
return QVariantList();
|
return QVariantList();
|
||||||
}
|
}
|
||||||
|
#endif // HAVE_MTP
|
||||||
|
|
||||||
if (IsCDDevice(serial)) {
|
if (IsCDDevice(serial)) {
|
||||||
return QVariantList() << u"media-optical"_s;
|
return QVariantList() << u"media-optical"_s;
|
||||||
}
|
}
|
||||||
|
|
||||||
QString bsd_name = current_devices_[serial];
|
const QString bsd_name = current_devices_[serial];
|
||||||
ScopedCFTypeRef<DASessionRef> session(DASessionCreate(kCFAllocatorDefault));
|
ScopedCFTypeRef<DASessionRef> session(DASessionCreate(kCFAllocatorDefault));
|
||||||
ScopedCFTypeRef<DADiskRef> disk(DADiskCreateFromBSDName(kCFAllocatorDefault, session.get(), bsd_name.toLatin1().constData()));
|
ScopedCFTypeRef<DADiskRef> disk(DADiskCreateFromBSDName(kCFAllocatorDefault, session.get(), bsd_name.toLatin1().constData()));
|
||||||
|
|
||||||
ScopedIOObject device(DADiskCopyIOMedia(disk.get()));
|
ScopedIOObject device(DADiskCopyIOMedia(disk.get()));
|
||||||
QString icon = GetIconForDevice(device.get());
|
const QString icon = GetIconForDevice(device.get());
|
||||||
|
|
||||||
scoped_nsobject<NSDictionary> properties(reinterpret_cast<NSDictionary*>(DADiskCopyDescription(disk)));
|
scoped_nsobject<NSDictionary> properties(reinterpret_cast<NSDictionary*>(DADiskCopyDescription(disk)));
|
||||||
scoped_nsobject<NSURL> volume_path([[properties objectForKey:reinterpret_cast<NSString*>(kDADiskDescriptionVolumePathKey)] copy]);
|
scoped_nsobject<NSURL> volume_path([[properties objectForKey:reinterpret_cast<NSString*>(kDADiskDescriptionVolumePathKey)] copy]);
|
||||||
|
|
||||||
QString path = QString::fromUtf8([[volume_path path] UTF8String]);
|
const QString path = QString::fromUtf8([[volume_path path] UTF8String]);
|
||||||
|
|
||||||
QVariantList ret;
|
QVariantList ret;
|
||||||
ret << GuessIconForPath(path);
|
ret << GuessIconForPath(path);
|
||||||
@@ -784,31 +828,45 @@ QVariantList MacOsDeviceLister::DeviceIcons(const QString &serial) {
|
|||||||
if (!icon.isEmpty()) {
|
if (!icon.isEmpty()) {
|
||||||
ret << icon;
|
ret << icon;
|
||||||
}
|
}
|
||||||
|
|
||||||
return ret;
|
return ret;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
QString MacOsDeviceLister::DeviceManufacturer(const QString &serial) {
|
QString MacOsDeviceLister::DeviceManufacturer(const QString &serial) {
|
||||||
|
|
||||||
|
#ifdef HAVE_MTP
|
||||||
if (IsMTPSerial(serial)) {
|
if (IsMTPSerial(serial)) {
|
||||||
return mtp_devices_[serial].vendor;
|
return mtp_devices_[serial].vendor;
|
||||||
}
|
}
|
||||||
|
#endif // HAVE_MTP
|
||||||
|
|
||||||
return FindDeviceProperty(current_devices_[serial], CFSTR(kUSBVendorString));
|
return FindDeviceProperty(current_devices_[serial], CFSTR(kUSBVendorString));
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
QString MacOsDeviceLister::DeviceModel(const QString &serial) {
|
QString MacOsDeviceLister::DeviceModel(const QString &serial) {
|
||||||
|
|
||||||
|
#ifdef HAVE_MTP
|
||||||
if (IsMTPSerial(serial)) {
|
if (IsMTPSerial(serial)) {
|
||||||
return mtp_devices_[serial].product;
|
return mtp_devices_[serial].product;
|
||||||
}
|
}
|
||||||
|
#endif // HAVE_MTP
|
||||||
|
|
||||||
return FindDeviceProperty(current_devices_[serial], CFSTR(kUSBProductString));
|
return FindDeviceProperty(current_devices_[serial], CFSTR(kUSBProductString));
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
quint64 MacOsDeviceLister::DeviceCapacity(const QString &serial) {
|
quint64 MacOsDeviceLister::DeviceCapacity(const QString &serial) {
|
||||||
|
|
||||||
|
#ifdef HAVE_MTP
|
||||||
if (IsMTPSerial(serial)) {
|
if (IsMTPSerial(serial)) {
|
||||||
QList<QUrl> urls = MakeDeviceUrls(serial);
|
QList<QUrl> urls = MakeDeviceUrls(serial);
|
||||||
return mtp_devices_[serial].capacity;
|
return mtp_devices_[serial].capacity;
|
||||||
}
|
}
|
||||||
QString bsd_name = current_devices_[serial];
|
#endif // HAVE_MTP
|
||||||
|
|
||||||
|
const QString bsd_name = current_devices_[serial];
|
||||||
ScopedCFTypeRef<DASessionRef> session(DASessionCreate(kCFAllocatorDefault));
|
ScopedCFTypeRef<DASessionRef> session(DASessionCreate(kCFAllocatorDefault));
|
||||||
ScopedCFTypeRef<DADiskRef> disk(DADiskCreateFromBSDName(kCFAllocatorDefault, session.get(), bsd_name.toLatin1().constData()));
|
ScopedCFTypeRef<DADiskRef> disk(DADiskCreateFromBSDName(kCFAllocatorDefault, session.get(), bsd_name.toLatin1().constData()));
|
||||||
|
|
||||||
@@ -816,7 +874,7 @@ quint64 MacOsDeviceLister::DeviceCapacity(const QString &serial) {
|
|||||||
|
|
||||||
NSNumber *capacity = reinterpret_cast<NSNumber*>(GetPropertyForDevice(device, CFSTR("Size")));
|
NSNumber *capacity = reinterpret_cast<NSNumber*>(GetPropertyForDevice(device, CFSTR("Size")));
|
||||||
|
|
||||||
quint64 ret = [capacity unsignedLongLongValue];
|
const quint64 ret = [capacity unsignedLongLongValue];
|
||||||
|
|
||||||
IOObjectRelease(device);
|
IOObjectRelease(device);
|
||||||
|
|
||||||
@@ -826,10 +884,13 @@ quint64 MacOsDeviceLister::DeviceCapacity(const QString &serial) {
|
|||||||
|
|
||||||
quint64 MacOsDeviceLister::DeviceFreeSpace(const QString &serial) {
|
quint64 MacOsDeviceLister::DeviceFreeSpace(const QString &serial) {
|
||||||
|
|
||||||
|
#ifdef HAVE_MTP
|
||||||
if (IsMTPSerial(serial)) {
|
if (IsMTPSerial(serial)) {
|
||||||
QList<QUrl> urls = MakeDeviceUrls(serial);
|
QList<QUrl> urls = MakeDeviceUrls(serial);
|
||||||
return mtp_devices_[serial].free_space;
|
return mtp_devices_[serial].free_space;
|
||||||
}
|
}
|
||||||
|
#endif // HAVE_MTP
|
||||||
|
|
||||||
QString bsd_name = current_devices_[serial];
|
QString bsd_name = current_devices_[serial];
|
||||||
ScopedCFTypeRef<DASessionRef> session(DASessionCreate(kCFAllocatorDefault));
|
ScopedCFTypeRef<DASessionRef> session(DASessionCreate(kCFAllocatorDefault));
|
||||||
ScopedCFTypeRef<DADiskRef> disk(DADiskCreateFromBSDName(kCFAllocatorDefault, session.get(), bsd_name.toLatin1().constData()));
|
ScopedCFTypeRef<DADiskRef> disk(DADiskCreateFromBSDName(kCFAllocatorDefault, session.get(), bsd_name.toLatin1().constData()));
|
||||||
@@ -857,9 +918,11 @@ bool MacOsDeviceLister::AskForScan(const QString &serial) const {
|
|||||||
|
|
||||||
void MacOsDeviceLister::UnmountDevice(const QString &serial) {
|
void MacOsDeviceLister::UnmountDevice(const QString &serial) {
|
||||||
|
|
||||||
|
#ifdef HAVE_MTP
|
||||||
if (IsMTPSerial(serial)) return;
|
if (IsMTPSerial(serial)) return;
|
||||||
|
#endif
|
||||||
|
|
||||||
QString bsd_name = current_devices_[serial];
|
const QString bsd_name = current_devices_[serial];
|
||||||
ScopedCFTypeRef<DADiskRef> disk(DADiskCreateFromBSDName(kCFAllocatorDefault, loop_session_, bsd_name.toLatin1().constData()));
|
ScopedCFTypeRef<DADiskRef> disk(DADiskCreateFromBSDName(kCFAllocatorDefault, loop_session_, bsd_name.toLatin1().constData()));
|
||||||
|
|
||||||
DADiskUnmount(disk, kDADiskUnmountOptionDefault, &DiskUnmountCallback, this);
|
DADiskUnmount(disk, kDADiskUnmountOptionDefault, &DiskUnmountCallback, this);
|
||||||
@@ -879,13 +942,16 @@ void MacOsDeviceLister::DiskUnmountCallback(DADiskRef disk, DADissenterRef disse
|
|||||||
|
|
||||||
void MacOsDeviceLister::UpdateDeviceFreeSpace(const QString &serial) {
|
void MacOsDeviceLister::UpdateDeviceFreeSpace(const QString &serial) {
|
||||||
|
|
||||||
|
#ifdef HAVE_MTP
|
||||||
if (IsMTPSerial(serial)) {
|
if (IsMTPSerial(serial)) {
|
||||||
if (mtp_devices_.contains(serial)) {
|
if (mtp_devices_.contains(serial)) {
|
||||||
QList<QUrl> urls = MakeDeviceUrls(serial);
|
QList<QUrl> urls = MakeDeviceUrls(serial);
|
||||||
MTPDevice *d = &mtp_devices_[serial];
|
MTPDevice *mtp_device = &mtp_devices_[serial];
|
||||||
d->free_space = GetFreeSpace(urls[0]);
|
mtp_device->free_space = GetFreeSpace(urls[0]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
Q_EMIT DeviceChanged(serial);
|
Q_EMIT DeviceChanged(serial);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -199,6 +199,7 @@ EditTagDialog::EditTagDialog(const SharedPtr<NetworkAccessManager> network,
|
|||||||
}
|
}
|
||||||
else if (RatingBox *ratingbox = qobject_cast<RatingBox*>(widget)) {
|
else if (RatingBox *ratingbox = qobject_cast<RatingBox*>(widget)) {
|
||||||
QObject::connect(ratingbox, &RatingWidget::RatingChanged, this, &EditTagDialog::FieldValueEdited);
|
QObject::connect(ratingbox, &RatingWidget::RatingChanged, this, &EditTagDialog::FieldValueEdited);
|
||||||
|
QObject::connect(ratingbox, &RatingBox::Reset, this, &EditTagDialog::ResetField);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -273,12 +274,18 @@ EditTagDialog::EditTagDialog(const SharedPtr<NetworkAccessManager> network,
|
|||||||
QKeySequence(QKeySequence::MoveToNextPage).toString(QKeySequence::NativeText)));
|
QKeySequence(QKeySequence::MoveToNextPage).toString(QKeySequence::NativeText)));
|
||||||
|
|
||||||
new TagCompleter(collection_backend, Playlist::Column::Artist, ui_->artist);
|
new TagCompleter(collection_backend, Playlist::Column::Artist, ui_->artist);
|
||||||
|
new TagCompleter(collection_backend, Playlist::Column::ArtistSort, ui_->artistsort);
|
||||||
new TagCompleter(collection_backend, Playlist::Column::Album, ui_->album);
|
new TagCompleter(collection_backend, Playlist::Column::Album, ui_->album);
|
||||||
|
new TagCompleter(collection_backend, Playlist::Column::AlbumSort, ui_->albumsort);
|
||||||
new TagCompleter(collection_backend, Playlist::Column::AlbumArtist, ui_->albumartist);
|
new TagCompleter(collection_backend, Playlist::Column::AlbumArtist, ui_->albumartist);
|
||||||
|
new TagCompleter(collection_backend, Playlist::Column::AlbumArtistSort, ui_->albumartistsort);
|
||||||
new TagCompleter(collection_backend, Playlist::Column::Genre, ui_->genre);
|
new TagCompleter(collection_backend, Playlist::Column::Genre, ui_->genre);
|
||||||
new TagCompleter(collection_backend, Playlist::Column::Composer, ui_->composer);
|
new TagCompleter(collection_backend, Playlist::Column::Composer, ui_->composer);
|
||||||
|
new TagCompleter(collection_backend, Playlist::Column::ComposerSort, ui_->composersort);
|
||||||
new TagCompleter(collection_backend, Playlist::Column::Performer, ui_->performer);
|
new TagCompleter(collection_backend, Playlist::Column::Performer, ui_->performer);
|
||||||
|
new TagCompleter(collection_backend, Playlist::Column::PerformerSort, ui_->performersort);
|
||||||
new TagCompleter(collection_backend, Playlist::Column::Grouping, ui_->grouping);
|
new TagCompleter(collection_backend, Playlist::Column::Grouping, ui_->grouping);
|
||||||
|
new TagCompleter(collection_backend, Playlist::Column::TitleSort, ui_->titlesort);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -492,11 +499,17 @@ void EditTagDialog::SetSongListVisibility(bool visible) {
|
|||||||
QVariant EditTagDialog::Data::value(const Song &song, const QString &id) {
|
QVariant EditTagDialog::Data::value(const Song &song, const QString &id) {
|
||||||
|
|
||||||
if (id == "title"_L1) return song.title();
|
if (id == "title"_L1) return song.title();
|
||||||
|
if (id == "titlesort"_L1) return song.titlesort();
|
||||||
if (id == "artist"_L1) return song.artist();
|
if (id == "artist"_L1) return song.artist();
|
||||||
|
if (id == "artistsort"_L1) return song.artistsort();
|
||||||
if (id == "album"_L1) return song.album();
|
if (id == "album"_L1) return song.album();
|
||||||
|
if (id == "albumsort"_L1) return song.albumsort();
|
||||||
if (id == "albumartist"_L1) return song.albumartist();
|
if (id == "albumartist"_L1) return song.albumartist();
|
||||||
|
if (id == "albumartistsort"_L1) return song.albumartistsort();
|
||||||
if (id == "composer"_L1) return song.composer();
|
if (id == "composer"_L1) return song.composer();
|
||||||
|
if (id == "composersort"_L1) return song.composersort();
|
||||||
if (id == "performer"_L1) return song.performer();
|
if (id == "performer"_L1) return song.performer();
|
||||||
|
if (id == "performersort"_L1) return song.performersort();
|
||||||
if (id == "grouping"_L1) return song.grouping();
|
if (id == "grouping"_L1) return song.grouping();
|
||||||
if (id == "genre"_L1) return song.genre();
|
if (id == "genre"_L1) return song.genre();
|
||||||
if (id == "comment"_L1) return song.comment();
|
if (id == "comment"_L1) return song.comment();
|
||||||
@@ -514,11 +527,17 @@ QVariant EditTagDialog::Data::value(const Song &song, const QString &id) {
|
|||||||
void EditTagDialog::Data::set_value(const QString &id, const QVariant &value) {
|
void EditTagDialog::Data::set_value(const QString &id, const QVariant &value) {
|
||||||
|
|
||||||
if (id == "title"_L1) current_.set_title(value.toString());
|
if (id == "title"_L1) current_.set_title(value.toString());
|
||||||
|
else if (id == "titlesort"_L1) current_.set_titlesort(value.toString());
|
||||||
else if (id == "artist"_L1) current_.set_artist(value.toString());
|
else if (id == "artist"_L1) current_.set_artist(value.toString());
|
||||||
|
else if (id == "artistsort"_L1) current_.set_artistsort(value.toString());
|
||||||
else if (id == "album"_L1) current_.set_album(value.toString());
|
else if (id == "album"_L1) current_.set_album(value.toString());
|
||||||
|
else if (id == "albumsort"_L1) current_.set_albumsort(value.toString());
|
||||||
else if (id == "albumartist"_L1) current_.set_albumartist(value.toString());
|
else if (id == "albumartist"_L1) current_.set_albumartist(value.toString());
|
||||||
|
else if (id == "albumartistsort"_L1) current_.set_albumartistsort(value.toString());
|
||||||
else if (id == "composer"_L1) current_.set_composer(value.toString());
|
else if (id == "composer"_L1) current_.set_composer(value.toString());
|
||||||
|
else if (id == "composersort"_L1) current_.set_composersort(value.toString());
|
||||||
else if (id == "performer"_L1) current_.set_performer(value.toString());
|
else if (id == "performer"_L1) current_.set_performer(value.toString());
|
||||||
|
else if (id == "performersort"_L1) current_.set_performersort(value.toString());
|
||||||
else if (id == "grouping"_L1) current_.set_grouping(value.toString());
|
else if (id == "grouping"_L1) current_.set_grouping(value.toString());
|
||||||
else if (id == "genre"_L1) current_.set_genre(value.toString());
|
else if (id == "genre"_L1) current_.set_genre(value.toString());
|
||||||
else if (id == "comment"_L1) current_.set_comment(value.toString());
|
else if (id == "comment"_L1) current_.set_comment(value.toString());
|
||||||
@@ -544,6 +563,20 @@ bool EditTagDialog::DoesValueVary(const QModelIndexList &sel, const QString &id)
|
|||||||
|
|
||||||
bool EditTagDialog::IsValueModified(const QModelIndexList &sel, const QString &id) const {
|
bool EditTagDialog::IsValueModified(const QModelIndexList &sel, const QString &id) const {
|
||||||
|
|
||||||
|
if (id == u"track"_s || id == u"disc"_s || id == u"year"_s) {
|
||||||
|
return std::any_of(sel.begin(), sel.end(), [this, id](const QModelIndex &i) {
|
||||||
|
const int original = data_[i.row()].original_value(id).toInt();
|
||||||
|
const int current = data_[i.row()].current_value(id).toInt();
|
||||||
|
return original != current && (original != -1 || current != 0);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
else if (id == u"rating"_s) {
|
||||||
|
return std::any_of(sel.begin(), sel.end(), [this, id](const QModelIndex &i) {
|
||||||
|
const float original = data_[i.row()].original_value(id).toFloat();
|
||||||
|
const float current = data_[i.row()].current_value(id).toFloat();
|
||||||
|
return original != current && (original != -1 || current != 0);
|
||||||
|
});
|
||||||
|
}
|
||||||
return std::any_of(sel.begin(), sel.end(), [this, id](const QModelIndex &i) { return data_[i.row()].original_value(id) != data_[i.row()].current_value(id); });
|
return std::any_of(sel.begin(), sel.end(), [this, id](const QModelIndex &i) { return data_[i.row()].original_value(id) != data_[i.row()].current_value(id); });
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -605,7 +638,15 @@ void EditTagDialog::UpdateModifiedField(const FieldData &field, const QModelInde
|
|||||||
QFont new_font(font());
|
QFont new_font(font());
|
||||||
new_font.setBold(modified);
|
new_font.setBold(modified);
|
||||||
field.label_->setFont(new_font);
|
field.label_->setFont(new_font);
|
||||||
if (field.editor_) field.editor_->setFont(new_font);
|
if (field.editor_) {
|
||||||
|
if (ExtendedEditor *editor = dynamic_cast<ExtendedEditor*>(field.editor_)) {
|
||||||
|
editor->set_font(new_font);
|
||||||
|
editor->set_reset_button(modified);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
field.editor_->setFont(new_font);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -652,14 +693,20 @@ void EditTagDialog::SelectionChanged() {
|
|||||||
bool art_different = false;
|
bool art_different = false;
|
||||||
bool action_different = false;
|
bool action_different = false;
|
||||||
bool albumartist_enabled = false;
|
bool albumartist_enabled = false;
|
||||||
|
bool albumartistsort_enabled = false;
|
||||||
bool composer_enabled = false;
|
bool composer_enabled = false;
|
||||||
|
bool composersort_enabled = false;
|
||||||
bool performer_enabled = false;
|
bool performer_enabled = false;
|
||||||
|
bool performersort_enabled = false;
|
||||||
bool grouping_enabled = false;
|
bool grouping_enabled = false;
|
||||||
bool genre_enabled = false;
|
bool genre_enabled = false;
|
||||||
bool compilation_enabled = false;
|
bool compilation_enabled = false;
|
||||||
bool rating_enabled = false;
|
bool rating_enabled = false;
|
||||||
bool comment_enabled = false;
|
bool comment_enabled = false;
|
||||||
bool lyrics_enabled = false;
|
bool lyrics_enabled = false;
|
||||||
|
bool titlesort_enabled = false;
|
||||||
|
bool artistsort_enabled = false;
|
||||||
|
bool albumsort_enabled = false;
|
||||||
for (const QModelIndex &idx : indexes) {
|
for (const QModelIndex &idx : indexes) {
|
||||||
if (data_.value(idx.row()).cover_action_ == UpdateCoverAction::None) {
|
if (data_.value(idx.row()).cover_action_ == UpdateCoverAction::None) {
|
||||||
data_[idx.row()].cover_result_ = AlbumCoverImageResult();
|
data_[idx.row()].cover_result_ = AlbumCoverImageResult();
|
||||||
@@ -679,12 +726,21 @@ void EditTagDialog::SelectionChanged() {
|
|||||||
if (song.albumartist_supported()) {
|
if (song.albumartist_supported()) {
|
||||||
albumartist_enabled = true;
|
albumartist_enabled = true;
|
||||||
}
|
}
|
||||||
|
if (song.albumartistsort_supported()) {
|
||||||
|
albumartistsort_enabled = true;
|
||||||
|
}
|
||||||
if (song.composer_supported()) {
|
if (song.composer_supported()) {
|
||||||
composer_enabled = true;
|
composer_enabled = true;
|
||||||
}
|
}
|
||||||
|
if (song.composersort_supported()) {
|
||||||
|
composersort_enabled = true;
|
||||||
|
}
|
||||||
if (song.performer_supported()) {
|
if (song.performer_supported()) {
|
||||||
performer_enabled = true;
|
performer_enabled = true;
|
||||||
}
|
}
|
||||||
|
if (song.performersort_supported()) {
|
||||||
|
performersort_enabled = true;
|
||||||
|
}
|
||||||
if (song.grouping_supported()) {
|
if (song.grouping_supported()) {
|
||||||
grouping_enabled = true;
|
grouping_enabled = true;
|
||||||
}
|
}
|
||||||
@@ -703,6 +759,15 @@ void EditTagDialog::SelectionChanged() {
|
|||||||
if (song.lyrics_supported()) {
|
if (song.lyrics_supported()) {
|
||||||
lyrics_enabled = true;
|
lyrics_enabled = true;
|
||||||
}
|
}
|
||||||
|
if (song.titlesort_supported()) {
|
||||||
|
titlesort_enabled = true;
|
||||||
|
}
|
||||||
|
if (song.artistsort_supported()) {
|
||||||
|
artistsort_enabled = true;
|
||||||
|
}
|
||||||
|
if (song.albumsort_supported()) {
|
||||||
|
albumsort_enabled = true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
QString summary;
|
QString summary;
|
||||||
@@ -759,14 +824,20 @@ void EditTagDialog::SelectionChanged() {
|
|||||||
album_cover_choice_controller_->set_save_embedded_cover_override(embedded_cover);
|
album_cover_choice_controller_->set_save_embedded_cover_override(embedded_cover);
|
||||||
|
|
||||||
ui_->albumartist->setEnabled(albumartist_enabled);
|
ui_->albumartist->setEnabled(albumartist_enabled);
|
||||||
|
ui_->albumartistsort->setEnabled(albumartistsort_enabled);
|
||||||
ui_->composer->setEnabled(composer_enabled);
|
ui_->composer->setEnabled(composer_enabled);
|
||||||
|
ui_->composersort->setEnabled(composersort_enabled);
|
||||||
ui_->performer->setEnabled(performer_enabled);
|
ui_->performer->setEnabled(performer_enabled);
|
||||||
|
ui_->performersort->setEnabled(performersort_enabled);
|
||||||
ui_->grouping->setEnabled(grouping_enabled);
|
ui_->grouping->setEnabled(grouping_enabled);
|
||||||
ui_->genre->setEnabled(genre_enabled);
|
ui_->genre->setEnabled(genre_enabled);
|
||||||
ui_->compilation->setEnabled(compilation_enabled);
|
ui_->compilation->setEnabled(compilation_enabled);
|
||||||
ui_->rating->setEnabled(rating_enabled);
|
ui_->rating->setEnabled(rating_enabled);
|
||||||
ui_->comment->setEnabled(comment_enabled);
|
ui_->comment->setEnabled(comment_enabled);
|
||||||
ui_->lyrics->setEnabled(lyrics_enabled);
|
ui_->lyrics->setEnabled(lyrics_enabled);
|
||||||
|
ui_->titlesort->setEnabled(titlesort_enabled);
|
||||||
|
ui_->artistsort->setEnabled(artistsort_enabled);
|
||||||
|
ui_->albumsort->setEnabled(albumsort_enabled);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -31,8 +31,8 @@
|
|||||||
<rect>
|
<rect>
|
||||||
<x>0</x>
|
<x>0</x>
|
||||||
<y>0</y>
|
<y>0</y>
|
||||||
<width>801</width>
|
<width>781</width>
|
||||||
<height>918</height>
|
<height>1047</height>
|
||||||
</rect>
|
</rect>
|
||||||
</property>
|
</property>
|
||||||
<layout class="QVBoxLayout" name="verticalLayout">
|
<layout class="QVBoxLayout" name="verticalLayout">
|
||||||
@@ -700,54 +700,35 @@
|
|||||||
<property name="sizeConstraint">
|
<property name="sizeConstraint">
|
||||||
<enum>QLayout::SetMinimumSize</enum>
|
<enum>QLayout::SetMinimumSize</enum>
|
||||||
</property>
|
</property>
|
||||||
<item row="4" column="3">
|
|
||||||
<widget class="SpinBox" name="year">
|
|
||||||
<property name="correctionMode">
|
|
||||||
<enum>QAbstractSpinBox::CorrectToNearestValue</enum>
|
|
||||||
</property>
|
|
||||||
<property name="maximum">
|
|
||||||
<number>9999</number>
|
|
||||||
</property>
|
|
||||||
<property name="has_clear_button" stdset="0">
|
|
||||||
<bool>false</bool>
|
|
||||||
</property>
|
|
||||||
<property name="has_reset_button" stdset="0">
|
|
||||||
<bool>true</bool>
|
|
||||||
</property>
|
|
||||||
</widget>
|
|
||||||
</item>
|
|
||||||
<item row="14" column="1">
|
|
||||||
<widget class="LineEdit" name="genre">
|
|
||||||
<property name="has_reset_button" stdset="0">
|
|
||||||
<bool>true</bool>
|
|
||||||
</property>
|
|
||||||
<property name="has_clear_button" stdset="0">
|
|
||||||
<bool>false</bool>
|
|
||||||
</property>
|
|
||||||
</widget>
|
|
||||||
</item>
|
|
||||||
<item row="3" column="2">
|
<item row="3" column="2">
|
||||||
<widget class="QLabel" name="label_disc">
|
<widget class="QLabel" name="label_year">
|
||||||
<property name="text">
|
<property name="text">
|
||||||
<string>Disc</string>
|
<string>Year</string>
|
||||||
</property>
|
</property>
|
||||||
<property name="buddy">
|
<property name="buddy">
|
||||||
<cstring>disc</cstring>
|
<cstring>year</cstring>
|
||||||
</property>
|
</property>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
<item row="8" column="1">
|
<item row="25" column="1">
|
||||||
<widget class="LineEdit" name="composer">
|
<widget class="QPushButton" name="fetch_tag">
|
||||||
<property name="has_reset_button" stdset="0">
|
<property name="text">
|
||||||
<bool>true</bool>
|
<string>Complete tags automatically</string>
|
||||||
</property>
|
</property>
|
||||||
<property name="has_clear_button" stdset="0">
|
<property name="icon">
|
||||||
<bool>false</bool>
|
<iconset resource="../../data/data.qrc">
|
||||||
|
<normaloff>:/pictures/musicbrainz.png</normaloff>:/pictures/musicbrainz.png</iconset>
|
||||||
|
</property>
|
||||||
|
<property name="iconSize">
|
||||||
|
<size>
|
||||||
|
<width>38</width>
|
||||||
|
<height>22</height>
|
||||||
|
</size>
|
||||||
</property>
|
</property>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
<item row="13" column="0">
|
<item row="13" column="0">
|
||||||
<widget class="QLabel" name="label_grouping">
|
<widget class="QLabel" name="label_performersort">
|
||||||
<property name="minimumSize">
|
<property name="minimumSize">
|
||||||
<size>
|
<size>
|
||||||
<width>80</width>
|
<width>80</width>
|
||||||
@@ -755,14 +736,56 @@
|
|||||||
</size>
|
</size>
|
||||||
</property>
|
</property>
|
||||||
<property name="text">
|
<property name="text">
|
||||||
<string>Grouping</string>
|
<string>Performer sort</string>
|
||||||
</property>
|
</property>
|
||||||
<property name="buddy">
|
<property name="buddy">
|
||||||
<cstring>grouping</cstring>
|
<cstring>performersort</cstring>
|
||||||
</property>
|
</property>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
<item row="5" column="0">
|
<item row="19" column="0">
|
||||||
|
<widget class="QLabel" name="label_compilation">
|
||||||
|
<property name="minimumSize">
|
||||||
|
<size>
|
||||||
|
<width>80</width>
|
||||||
|
<height>0</height>
|
||||||
|
</size>
|
||||||
|
</property>
|
||||||
|
<property name="text">
|
||||||
|
<string>Compilation</string>
|
||||||
|
</property>
|
||||||
|
<property name="buddy">
|
||||||
|
<cstring>compilation</cstring>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item row="8" column="1">
|
||||||
|
<widget class="LineEdit" name="albumartistsort">
|
||||||
|
<property name="has_reset_button" stdset="0">
|
||||||
|
<bool>true</bool>
|
||||||
|
</property>
|
||||||
|
<property name="has_clear_button" stdset="0">
|
||||||
|
<bool>false</bool>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item row="1" column="0">
|
||||||
|
<widget class="QLabel" name="label_titlesort">
|
||||||
|
<property name="minimumSize">
|
||||||
|
<size>
|
||||||
|
<width>80</width>
|
||||||
|
<height>0</height>
|
||||||
|
</size>
|
||||||
|
</property>
|
||||||
|
<property name="text">
|
||||||
|
<string>Title sort</string>
|
||||||
|
</property>
|
||||||
|
<property name="buddy">
|
||||||
|
<cstring>titlesort</cstring>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item row="7" column="0">
|
||||||
<widget class="QLabel" name="label_albumartist">
|
<widget class="QLabel" name="label_albumartist">
|
||||||
<property name="minimumSize">
|
<property name="minimumSize">
|
||||||
<size>
|
<size>
|
||||||
@@ -778,7 +801,130 @@
|
|||||||
</property>
|
</property>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
<item row="13" column="1">
|
<item row="0" column="1">
|
||||||
|
<widget class="LineEdit" name="title">
|
||||||
|
<property name="has_reset_button" stdset="0">
|
||||||
|
<bool>true</bool>
|
||||||
|
</property>
|
||||||
|
<property name="has_clear_button" stdset="0">
|
||||||
|
<bool>false</bool>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item row="12" column="0">
|
||||||
|
<widget class="QLabel" name="label_performer">
|
||||||
|
<property name="minimumSize">
|
||||||
|
<size>
|
||||||
|
<width>80</width>
|
||||||
|
<height>0</height>
|
||||||
|
</size>
|
||||||
|
</property>
|
||||||
|
<property name="text">
|
||||||
|
<string>Performer</string>
|
||||||
|
</property>
|
||||||
|
<property name="buddy">
|
||||||
|
<cstring>performer</cstring>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item row="18" column="1">
|
||||||
|
<widget class="LineEdit" name="genre">
|
||||||
|
<property name="has_reset_button" stdset="0">
|
||||||
|
<bool>true</bool>
|
||||||
|
</property>
|
||||||
|
<property name="has_clear_button" stdset="0">
|
||||||
|
<bool>false</bool>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item row="3" column="3">
|
||||||
|
<widget class="SpinBox" name="year">
|
||||||
|
<property name="correctionMode">
|
||||||
|
<enum>QAbstractSpinBox::CorrectToNearestValue</enum>
|
||||||
|
</property>
|
||||||
|
<property name="maximum">
|
||||||
|
<number>9999</number>
|
||||||
|
</property>
|
||||||
|
<property name="has_clear_button" stdset="0">
|
||||||
|
<bool>false</bool>
|
||||||
|
</property>
|
||||||
|
<property name="has_reset_button" stdset="0">
|
||||||
|
<bool>true</bool>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item row="20" column="1">
|
||||||
|
<widget class="RatingBox" name="rating" native="true">
|
||||||
|
<property name="maximumSize">
|
||||||
|
<size>
|
||||||
|
<width>140</width>
|
||||||
|
<height>16777215</height>
|
||||||
|
</size>
|
||||||
|
</property>
|
||||||
|
<property name="has_reset_button" stdset="0">
|
||||||
|
<bool>true</bool>
|
||||||
|
</property>
|
||||||
|
<property name="has_clear_button" stdset="0">
|
||||||
|
<bool>false</bool>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item row="5" column="1">
|
||||||
|
<widget class="LineEdit" name="album">
|
||||||
|
<property name="has_reset_button" stdset="0">
|
||||||
|
<bool>true</bool>
|
||||||
|
</property>
|
||||||
|
<property name="has_clear_button" stdset="0">
|
||||||
|
<bool>false</bool>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item row="26" column="1">
|
||||||
|
<widget class="TextEdit" name="comment">
|
||||||
|
<property name="sizePolicy">
|
||||||
|
<sizepolicy hsizetype="Expanding" vsizetype="Minimum">
|
||||||
|
<horstretch>0</horstretch>
|
||||||
|
<verstretch>0</verstretch>
|
||||||
|
</sizepolicy>
|
||||||
|
</property>
|
||||||
|
<property name="tabChangesFocus">
|
||||||
|
<bool>true</bool>
|
||||||
|
</property>
|
||||||
|
<property name="has_reset_button" stdset="0">
|
||||||
|
<bool>true</bool>
|
||||||
|
</property>
|
||||||
|
<property name="has_clear_button" stdset="0">
|
||||||
|
<bool>false</bool>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item row="4" column="1">
|
||||||
|
<widget class="LineEdit" name="artistsort">
|
||||||
|
<property name="has_reset_button" stdset="0">
|
||||||
|
<bool>true</bool>
|
||||||
|
</property>
|
||||||
|
<property name="has_clear_button" stdset="0">
|
||||||
|
<bool>false</bool>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item row="0" column="0">
|
||||||
|
<widget class="QLabel" name="label_title">
|
||||||
|
<property name="minimumSize">
|
||||||
|
<size>
|
||||||
|
<width>80</width>
|
||||||
|
<height>0</height>
|
||||||
|
</size>
|
||||||
|
</property>
|
||||||
|
<property name="text">
|
||||||
|
<string>Title</string>
|
||||||
|
</property>
|
||||||
|
<property name="buddy">
|
||||||
|
<cstring>title</cstring>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item row="17" column="1">
|
||||||
<widget class="LineEdit" name="grouping">
|
<widget class="LineEdit" name="grouping">
|
||||||
<property name="has_reset_button" stdset="0">
|
<property name="has_reset_button" stdset="0">
|
||||||
<bool>true</bool>
|
<bool>true</bool>
|
||||||
@@ -788,7 +934,63 @@
|
|||||||
</property>
|
</property>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
<item row="9" column="1">
|
<item row="1" column="3">
|
||||||
|
<widget class="SpinBox" name="disc">
|
||||||
|
<property name="correctionMode">
|
||||||
|
<enum>QAbstractSpinBox::CorrectToNearestValue</enum>
|
||||||
|
</property>
|
||||||
|
<property name="maximum">
|
||||||
|
<number>9999</number>
|
||||||
|
</property>
|
||||||
|
<property name="has_clear_button" stdset="0">
|
||||||
|
<bool>false</bool>
|
||||||
|
</property>
|
||||||
|
<property name="has_reset_button" stdset="0">
|
||||||
|
<bool>true</bool>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item row="19" column="1">
|
||||||
|
<widget class="CheckBox" name="compilation">
|
||||||
|
<property name="has_reset_button" stdset="0">
|
||||||
|
<bool>true</bool>
|
||||||
|
</property>
|
||||||
|
<property name="has_clear_button" stdset="0">
|
||||||
|
<bool>false</bool>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item row="1" column="1">
|
||||||
|
<widget class="LineEdit" name="titlesort">
|
||||||
|
<property name="has_reset_button" stdset="0">
|
||||||
|
<bool>true</bool>
|
||||||
|
</property>
|
||||||
|
<property name="has_clear_button" stdset="0">
|
||||||
|
<bool>false</bool>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item row="20" column="0">
|
||||||
|
<widget class="QLabel" name="label_rating">
|
||||||
|
<property name="text">
|
||||||
|
<string>Rating</string>
|
||||||
|
</property>
|
||||||
|
<property name="buddy">
|
||||||
|
<cstring>rating</cstring>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item row="1" column="2">
|
||||||
|
<widget class="QLabel" name="label_disc">
|
||||||
|
<property name="text">
|
||||||
|
<string>Disc</string>
|
||||||
|
</property>
|
||||||
|
<property name="buddy">
|
||||||
|
<cstring>disc</cstring>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item row="12" column="1">
|
||||||
<widget class="LineEdit" name="performer">
|
<widget class="LineEdit" name="performer">
|
||||||
<property name="has_reset_button" stdset="0">
|
<property name="has_reset_button" stdset="0">
|
||||||
<bool>true</bool>
|
<bool>true</bool>
|
||||||
@@ -798,26 +1000,6 @@
|
|||||||
</property>
|
</property>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
<item row="3" column="1">
|
|
||||||
<widget class="LineEdit" name="artist">
|
|
||||||
<property name="has_reset_button" stdset="0">
|
|
||||||
<bool>true</bool>
|
|
||||||
</property>
|
|
||||||
<property name="has_clear_button" stdset="0">
|
|
||||||
<bool>false</bool>
|
|
||||||
</property>
|
|
||||||
</widget>
|
|
||||||
</item>
|
|
||||||
<item row="0" column="1">
|
|
||||||
<widget class="LineEdit" name="title">
|
|
||||||
<property name="has_reset_button" stdset="0">
|
|
||||||
<bool>true</bool>
|
|
||||||
</property>
|
|
||||||
<property name="has_clear_button" stdset="0">
|
|
||||||
<bool>false</bool>
|
|
||||||
</property>
|
|
||||||
</widget>
|
|
||||||
</item>
|
|
||||||
<item row="0" column="3">
|
<item row="0" column="3">
|
||||||
<widget class="SpinBox" name="track">
|
<widget class="SpinBox" name="track">
|
||||||
<property name="correctionMode">
|
<property name="correctionMode">
|
||||||
@@ -834,7 +1016,49 @@
|
|||||||
</property>
|
</property>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
|
<item row="8" column="0">
|
||||||
|
<widget class="QLabel" name="label_albumartistsort">
|
||||||
|
<property name="minimumSize">
|
||||||
|
<size>
|
||||||
|
<width>80</width>
|
||||||
|
<height>0</height>
|
||||||
|
</size>
|
||||||
|
</property>
|
||||||
|
<property name="text">
|
||||||
|
<string>Album artist sort</string>
|
||||||
|
</property>
|
||||||
|
<property name="buddy">
|
||||||
|
<cstring>albumartistsort</cstring>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item row="3" column="1">
|
||||||
|
<widget class="LineEdit" name="artist">
|
||||||
|
<property name="has_reset_button" stdset="0">
|
||||||
|
<bool>true</bool>
|
||||||
|
</property>
|
||||||
|
<property name="has_clear_button" stdset="0">
|
||||||
|
<bool>false</bool>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
<item row="4" column="0">
|
<item row="4" column="0">
|
||||||
|
<widget class="QLabel" name="label_artistsort">
|
||||||
|
<property name="minimumSize">
|
||||||
|
<size>
|
||||||
|
<width>80</width>
|
||||||
|
<height>0</height>
|
||||||
|
</size>
|
||||||
|
</property>
|
||||||
|
<property name="text">
|
||||||
|
<string>Artist sort</string>
|
||||||
|
</property>
|
||||||
|
<property name="buddy">
|
||||||
|
<cstring>artistsort</cstring>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item row="5" column="0">
|
||||||
<widget class="QLabel" name="label_album">
|
<widget class="QLabel" name="label_album">
|
||||||
<property name="minimumSize">
|
<property name="minimumSize">
|
||||||
<size>
|
<size>
|
||||||
@@ -850,18 +1074,18 @@
|
|||||||
</property>
|
</property>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
<item row="4" column="2">
|
<item row="0" column="2">
|
||||||
<widget class="QLabel" name="label_year">
|
<widget class="QLabel" name="label_track">
|
||||||
<property name="text">
|
<property name="text">
|
||||||
<string>Year</string>
|
<string>Track</string>
|
||||||
</property>
|
</property>
|
||||||
<property name="buddy">
|
<property name="buddy">
|
||||||
<cstring>year</cstring>
|
<cstring>track</cstring>
|
||||||
</property>
|
</property>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
<item row="0" column="0">
|
<item row="10" column="0">
|
||||||
<widget class="QLabel" name="label_title">
|
<widget class="QLabel" name="label_composer">
|
||||||
<property name="minimumSize">
|
<property name="minimumSize">
|
||||||
<size>
|
<size>
|
||||||
<width>80</width>
|
<width>80</width>
|
||||||
@@ -869,10 +1093,72 @@
|
|||||||
</size>
|
</size>
|
||||||
</property>
|
</property>
|
||||||
<property name="text">
|
<property name="text">
|
||||||
<string>Title</string>
|
<string>Composer</string>
|
||||||
</property>
|
</property>
|
||||||
<property name="buddy">
|
<property name="buddy">
|
||||||
<cstring>title</cstring>
|
<cstring>composer</cstring>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item row="7" column="1">
|
||||||
|
<widget class="LineEdit" name="albumartist">
|
||||||
|
<property name="has_reset_button" stdset="0">
|
||||||
|
<bool>true</bool>
|
||||||
|
</property>
|
||||||
|
<property name="has_clear_button" stdset="0">
|
||||||
|
<bool>false</bool>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item row="11" column="1">
|
||||||
|
<widget class="LineEdit" name="composersort">
|
||||||
|
<property name="has_reset_button" stdset="0">
|
||||||
|
<bool>true</bool>
|
||||||
|
</property>
|
||||||
|
<property name="has_clear_button" stdset="0">
|
||||||
|
<bool>false</bool>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item row="13" column="1">
|
||||||
|
<widget class="LineEdit" name="performersort">
|
||||||
|
<property name="has_reset_button" stdset="0">
|
||||||
|
<bool>true</bool>
|
||||||
|
</property>
|
||||||
|
<property name="has_clear_button" stdset="0">
|
||||||
|
<bool>false</bool>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item row="17" column="0">
|
||||||
|
<widget class="QLabel" name="label_grouping">
|
||||||
|
<property name="minimumSize">
|
||||||
|
<size>
|
||||||
|
<width>80</width>
|
||||||
|
<height>0</height>
|
||||||
|
</size>
|
||||||
|
</property>
|
||||||
|
<property name="text">
|
||||||
|
<string>Grouping</string>
|
||||||
|
</property>
|
||||||
|
<property name="buddy">
|
||||||
|
<cstring>grouping</cstring>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item row="6" column="0">
|
||||||
|
<widget class="QLabel" name="label_albumsort">
|
||||||
|
<property name="minimumSize">
|
||||||
|
<size>
|
||||||
|
<width>80</width>
|
||||||
|
<height>0</height>
|
||||||
|
</size>
|
||||||
|
</property>
|
||||||
|
<property name="text">
|
||||||
|
<string>Album sort</string>
|
||||||
|
</property>
|
||||||
|
<property name="buddy">
|
||||||
|
<cstring>albumsort</cstring>
|
||||||
</property>
|
</property>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
@@ -892,66 +1178,7 @@
|
|||||||
</property>
|
</property>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
<item row="8" column="0">
|
<item row="18" column="0">
|
||||||
<widget class="QLabel" name="label_composer">
|
|
||||||
<property name="minimumSize">
|
|
||||||
<size>
|
|
||||||
<width>80</width>
|
|
||||||
<height>0</height>
|
|
||||||
</size>
|
|
||||||
</property>
|
|
||||||
<property name="text">
|
|
||||||
<string>Composer</string>
|
|
||||||
</property>
|
|
||||||
<property name="buddy">
|
|
||||||
<cstring>composer</cstring>
|
|
||||||
</property>
|
|
||||||
</widget>
|
|
||||||
</item>
|
|
||||||
<item row="21" column="1">
|
|
||||||
<widget class="QPushButton" name="fetch_tag">
|
|
||||||
<property name="text">
|
|
||||||
<string>Complete tags automatically</string>
|
|
||||||
</property>
|
|
||||||
<property name="icon">
|
|
||||||
<iconset resource="../../data/data.qrc">
|
|
||||||
<normaloff>:/pictures/musicbrainz.png</normaloff>:/pictures/musicbrainz.png</iconset>
|
|
||||||
</property>
|
|
||||||
<property name="iconSize">
|
|
||||||
<size>
|
|
||||||
<width>38</width>
|
|
||||||
<height>22</height>
|
|
||||||
</size>
|
|
||||||
</property>
|
|
||||||
</widget>
|
|
||||||
</item>
|
|
||||||
<item row="3" column="3">
|
|
||||||
<widget class="SpinBox" name="disc">
|
|
||||||
<property name="correctionMode">
|
|
||||||
<enum>QAbstractSpinBox::CorrectToNearestValue</enum>
|
|
||||||
</property>
|
|
||||||
<property name="maximum">
|
|
||||||
<number>9999</number>
|
|
||||||
</property>
|
|
||||||
<property name="has_clear_button" stdset="0">
|
|
||||||
<bool>false</bool>
|
|
||||||
</property>
|
|
||||||
<property name="has_reset_button" stdset="0">
|
|
||||||
<bool>true</bool>
|
|
||||||
</property>
|
|
||||||
</widget>
|
|
||||||
</item>
|
|
||||||
<item row="15" column="1">
|
|
||||||
<widget class="CheckBox" name="compilation">
|
|
||||||
<property name="has_reset_button" stdset="0">
|
|
||||||
<bool>false</bool>
|
|
||||||
</property>
|
|
||||||
<property name="has_clear_button" stdset="0">
|
|
||||||
<bool>false</bool>
|
|
||||||
</property>
|
|
||||||
</widget>
|
|
||||||
</item>
|
|
||||||
<item row="14" column="0">
|
|
||||||
<widget class="QLabel" name="label_genre">
|
<widget class="QLabel" name="label_genre">
|
||||||
<property name="minimumSize">
|
<property name="minimumSize">
|
||||||
<size>
|
<size>
|
||||||
@@ -967,7 +1194,17 @@
|
|||||||
</property>
|
</property>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
<item row="22" column="0">
|
<item row="10" column="1">
|
||||||
|
<widget class="LineEdit" name="composer">
|
||||||
|
<property name="has_reset_button" stdset="0">
|
||||||
|
<bool>true</bool>
|
||||||
|
</property>
|
||||||
|
<property name="has_clear_button" stdset="0">
|
||||||
|
<bool>false</bool>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item row="26" column="0">
|
||||||
<widget class="QLabel" name="label_comment">
|
<widget class="QLabel" name="label_comment">
|
||||||
<property name="minimumSize">
|
<property name="minimumSize">
|
||||||
<size>
|
<size>
|
||||||
@@ -983,44 +1220,8 @@
|
|||||||
</property>
|
</property>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
<item row="4" column="1">
|
<item row="11" column="0">
|
||||||
<widget class="LineEdit" name="album">
|
<widget class="QLabel" name="label_composersort">
|
||||||
<property name="has_reset_button" stdset="0">
|
|
||||||
<bool>true</bool>
|
|
||||||
</property>
|
|
||||||
<property name="has_clear_button" stdset="0">
|
|
||||||
<bool>false</bool>
|
|
||||||
</property>
|
|
||||||
</widget>
|
|
||||||
</item>
|
|
||||||
<item row="5" column="1">
|
|
||||||
<widget class="LineEdit" name="albumartist">
|
|
||||||
<property name="has_reset_button" stdset="0">
|
|
||||||
<bool>true</bool>
|
|
||||||
</property>
|
|
||||||
<property name="has_clear_button" stdset="0">
|
|
||||||
<bool>false</bool>
|
|
||||||
</property>
|
|
||||||
</widget>
|
|
||||||
</item>
|
|
||||||
<item row="22" column="1">
|
|
||||||
<widget class="TextEdit" name="comment">
|
|
||||||
<property name="sizePolicy">
|
|
||||||
<sizepolicy hsizetype="Expanding" vsizetype="Minimum">
|
|
||||||
<horstretch>0</horstretch>
|
|
||||||
<verstretch>0</verstretch>
|
|
||||||
</sizepolicy>
|
|
||||||
</property>
|
|
||||||
<property name="has_reset_button" stdset="0">
|
|
||||||
<bool>true</bool>
|
|
||||||
</property>
|
|
||||||
<property name="has_clear_button" stdset="0">
|
|
||||||
<bool>false</bool>
|
|
||||||
</property>
|
|
||||||
</widget>
|
|
||||||
</item>
|
|
||||||
<item row="9" column="0">
|
|
||||||
<widget class="QLabel" name="label_performer">
|
|
||||||
<property name="minimumSize">
|
<property name="minimumSize">
|
||||||
<size>
|
<size>
|
||||||
<width>80</width>
|
<width>80</width>
|
||||||
@@ -1028,52 +1229,23 @@
|
|||||||
</size>
|
</size>
|
||||||
</property>
|
</property>
|
||||||
<property name="text">
|
<property name="text">
|
||||||
<string>Performer</string>
|
<string>Composer sort</string>
|
||||||
</property>
|
</property>
|
||||||
<property name="buddy">
|
<property name="buddy">
|
||||||
<cstring>performer</cstring>
|
<cstring>composersort</cstring>
|
||||||
</property>
|
</property>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
<item row="15" column="0">
|
<item row="6" column="1">
|
||||||
<widget class="QLabel" name="label_compilation">
|
<widget class="LineEdit" name="albumsort">
|
||||||
<property name="minimumSize">
|
<property name="has_reset_button" stdset="0">
|
||||||
<size>
|
<bool>true</bool>
|
||||||
<width>80</width>
|
|
||||||
<height>0</height>
|
|
||||||
</size>
|
|
||||||
</property>
|
</property>
|
||||||
<property name="text">
|
<property name="has_clear_button" stdset="0">
|
||||||
<string>Compilation</string>
|
<bool>false</bool>
|
||||||
</property>
|
|
||||||
<property name="buddy">
|
|
||||||
<cstring>compilation</cstring>
|
|
||||||
</property>
|
</property>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
<item row="0" column="2">
|
|
||||||
<widget class="QLabel" name="label_track">
|
|
||||||
<property name="text">
|
|
||||||
<string>Track</string>
|
|
||||||
</property>
|
|
||||||
<property name="buddy">
|
|
||||||
<cstring>track</cstring>
|
|
||||||
</property>
|
|
||||||
</widget>
|
|
||||||
</item>
|
|
||||||
<item row="16" column="0">
|
|
||||||
<widget class="QLabel" name="label_rating">
|
|
||||||
<property name="text">
|
|
||||||
<string>Rating</string>
|
|
||||||
</property>
|
|
||||||
<property name="buddy">
|
|
||||||
<cstring>rating</cstring>
|
|
||||||
</property>
|
|
||||||
</widget>
|
|
||||||
</item>
|
|
||||||
<item row="16" column="1">
|
|
||||||
<widget class="RatingBox" name="rating" native="true"/>
|
|
||||||
</item>
|
|
||||||
</layout>
|
</layout>
|
||||||
</item>
|
</item>
|
||||||
<item>
|
<item>
|
||||||
@@ -1131,12 +1303,12 @@
|
|||||||
</property>
|
</property>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
<item row="21" column="1">
|
<item>
|
||||||
<widget class="QPushButton" name="fetch_lyrics">
|
<widget class="QPushButton" name="fetch_lyrics">
|
||||||
<property name="text">
|
<property name="text">
|
||||||
<string>Complete lyrics automatically</string>
|
<string>Complete lyrics automatically</string>
|
||||||
</property>
|
</property>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
</layout>
|
</layout>
|
||||||
</widget>
|
</widget>
|
||||||
@@ -1199,21 +1371,29 @@
|
|||||||
<tabstop>summary</tabstop>
|
<tabstop>summary</tabstop>
|
||||||
<tabstop>filename</tabstop>
|
<tabstop>filename</tabstop>
|
||||||
<tabstop>path</tabstop>
|
<tabstop>path</tabstop>
|
||||||
|
<tabstop>art_embedded</tabstop>
|
||||||
<tabstop>art_manual</tabstop>
|
<tabstop>art_manual</tabstop>
|
||||||
<tabstop>art_automatic</tabstop>
|
<tabstop>art_automatic</tabstop>
|
||||||
|
<tabstop>art_unset</tabstop>
|
||||||
<tabstop>playcount_reset</tabstop>
|
<tabstop>playcount_reset</tabstop>
|
||||||
<tabstop>tags_summary</tabstop>
|
<tabstop>tags_summary</tabstop>
|
||||||
<tabstop>tags_art_button</tabstop>
|
<tabstop>tags_art_button</tabstop>
|
||||||
<tabstop>checkbox_embedded_cover</tabstop>
|
<tabstop>checkbox_embedded_cover</tabstop>
|
||||||
<tabstop>title</tabstop>
|
<tabstop>title</tabstop>
|
||||||
<tabstop>track</tabstop>
|
<tabstop>track</tabstop>
|
||||||
<tabstop>artist</tabstop>
|
<tabstop>titlesort</tabstop>
|
||||||
<tabstop>disc</tabstop>
|
<tabstop>disc</tabstop>
|
||||||
<tabstop>album</tabstop>
|
<tabstop>artist</tabstop>
|
||||||
<tabstop>year</tabstop>
|
<tabstop>year</tabstop>
|
||||||
|
<tabstop>artistsort</tabstop>
|
||||||
|
<tabstop>album</tabstop>
|
||||||
|
<tabstop>albumsort</tabstop>
|
||||||
<tabstop>albumartist</tabstop>
|
<tabstop>albumartist</tabstop>
|
||||||
|
<tabstop>albumartistsort</tabstop>
|
||||||
<tabstop>composer</tabstop>
|
<tabstop>composer</tabstop>
|
||||||
|
<tabstop>composersort</tabstop>
|
||||||
<tabstop>performer</tabstop>
|
<tabstop>performer</tabstop>
|
||||||
|
<tabstop>performersort</tabstop>
|
||||||
<tabstop>grouping</tabstop>
|
<tabstop>grouping</tabstop>
|
||||||
<tabstop>genre</tabstop>
|
<tabstop>genre</tabstop>
|
||||||
<tabstop>compilation</tabstop>
|
<tabstop>compilation</tabstop>
|
||||||
|
|||||||
@@ -36,11 +36,8 @@ namespace {
|
|||||||
constexpr char kDiscordApplicationId[] = "1352351827206733974";
|
constexpr char kDiscordApplicationId[] = "1352351827206733974";
|
||||||
constexpr char kStrawberryIconResourceName[] = "embedded_cover";
|
constexpr char kStrawberryIconResourceName[] = "embedded_cover";
|
||||||
constexpr char kStrawberryIconDescription[] = "Strawberry Music Player";
|
constexpr char kStrawberryIconDescription[] = "Strawberry Music Player";
|
||||||
constexpr qint64 kDiscordPresenceUpdateRateLimitMs = 2000;
|
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
using namespace discord_rpc;
|
|
||||||
|
|
||||||
namespace discord {
|
namespace discord {
|
||||||
|
|
||||||
RichPresence::RichPresence(const SharedPtr<Player> player,
|
RichPresence::RichPresence(const SharedPtr<Player> player,
|
||||||
@@ -49,10 +46,8 @@ RichPresence::RichPresence(const SharedPtr<Player> player,
|
|||||||
: QObject(parent),
|
: QObject(parent),
|
||||||
player_(player),
|
player_(player),
|
||||||
playlist_manager_(playlist_manager),
|
playlist_manager_(playlist_manager),
|
||||||
send_presence_timestamp_(0),
|
initialized_(false),
|
||||||
enabled_(false) {
|
status_display_type_(0) {
|
||||||
|
|
||||||
Discord_Initialize(kDiscordApplicationId, nullptr, 1, nullptr);
|
|
||||||
|
|
||||||
QObject::connect(&*player_->engine(), &EngineBase::StateChanged, this, &RichPresence::EngineStateChanged);
|
QObject::connect(&*player_->engine(), &EngineBase::StateChanged, this, &RichPresence::EngineStateChanged);
|
||||||
QObject::connect(&*playlist_manager_, &PlaylistManager::CurrentSongChanged, this, &RichPresence::CurrentSongChanged);
|
QObject::connect(&*playlist_manager_, &PlaylistManager::CurrentSongChanged, this, &RichPresence::CurrentSongChanged);
|
||||||
@@ -63,7 +58,11 @@ RichPresence::RichPresence(const SharedPtr<Player> player,
|
|||||||
}
|
}
|
||||||
|
|
||||||
RichPresence::~RichPresence() {
|
RichPresence::~RichPresence() {
|
||||||
Discord_Shutdown();
|
|
||||||
|
if (initialized_) {
|
||||||
|
Discord_Shutdown();
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void RichPresence::ReloadSettings() {
|
void RichPresence::ReloadSettings() {
|
||||||
@@ -71,18 +70,25 @@ void RichPresence::ReloadSettings() {
|
|||||||
Settings s;
|
Settings s;
|
||||||
s.beginGroup(DiscordRPCSettings::kSettingsGroup);
|
s.beginGroup(DiscordRPCSettings::kSettingsGroup);
|
||||||
const bool enabled = s.value(DiscordRPCSettings::kEnabled, false).toBool();
|
const bool enabled = s.value(DiscordRPCSettings::kEnabled, false).toBool();
|
||||||
|
status_display_type_ = s.value(DiscordRPCSettings::kStatusDisplayType, static_cast<int>(DiscordRPCSettings::StatusDisplayType::App)).toInt();
|
||||||
s.endGroup();
|
s.endGroup();
|
||||||
|
|
||||||
if (enabled_ && !enabled) {
|
if (enabled && !initialized_) {
|
||||||
Discord_ClearPresence();
|
Discord_Initialize(kDiscordApplicationId, nullptr, 0);
|
||||||
|
initialized_ = true;
|
||||||
|
}
|
||||||
|
else if (!enabled && initialized_) {
|
||||||
|
Discord_ClearPresence();
|
||||||
|
Discord_Shutdown();
|
||||||
|
initialized_ = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
enabled_ = enabled;
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void RichPresence::EngineStateChanged(const EngineBase::State state) {
|
void RichPresence::EngineStateChanged(const EngineBase::State state) {
|
||||||
|
|
||||||
|
if (!initialized_) return;
|
||||||
|
|
||||||
if (state == EngineBase::State::Playing) {
|
if (state == EngineBase::State::Playing) {
|
||||||
SetTimestamp(player_->engine()->position_nanosec() / kNsecPerSec);
|
SetTimestamp(player_->engine()->position_nanosec() / kNsecPerSec);
|
||||||
SendPresenceUpdate();
|
SendPresenceUpdate();
|
||||||
@@ -95,6 +101,8 @@ void RichPresence::EngineStateChanged(const EngineBase::State state) {
|
|||||||
|
|
||||||
void RichPresence::CurrentSongChanged(const Song &song) {
|
void RichPresence::CurrentSongChanged(const Song &song) {
|
||||||
|
|
||||||
|
if (!initialized_) return;
|
||||||
|
|
||||||
SetTimestamp(0LL);
|
SetTimestamp(0LL);
|
||||||
activity_.length_secs = song.length_nanosec() / kNsecPerSec;
|
activity_.length_secs = song.length_nanosec() / kNsecPerSec;
|
||||||
activity_.title = song.title();
|
activity_.title = song.title();
|
||||||
@@ -107,34 +115,32 @@ void RichPresence::CurrentSongChanged(const Song &song) {
|
|||||||
|
|
||||||
void RichPresence::SendPresenceUpdate() {
|
void RichPresence::SendPresenceUpdate() {
|
||||||
|
|
||||||
if (!enabled_) {
|
if (!initialized_) return;
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const qint64 current_timestamp = QDateTime::currentMSecsSinceEpoch();
|
|
||||||
if (current_timestamp - send_presence_timestamp_ < kDiscordPresenceUpdateRateLimitMs) {
|
|
||||||
qLog(Info) << "Not sending rich presence due to rate limit of" << kDiscordPresenceUpdateRateLimitMs << "ms";
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
send_presence_timestamp_ = current_timestamp;
|
|
||||||
|
|
||||||
::DiscordRichPresence presence_data{};
|
::DiscordRichPresence presence_data{};
|
||||||
memset(&presence_data, 0, sizeof(presence_data));
|
memset(&presence_data, 0, sizeof(presence_data));
|
||||||
presence_data.type = 2; // Listening
|
|
||||||
|
// Listening to
|
||||||
|
presence_data.type = 2;
|
||||||
|
presence_data.status_display_type = status_display_type_;
|
||||||
|
|
||||||
presence_data.largeImageKey = kStrawberryIconResourceName;
|
presence_data.largeImageKey = kStrawberryIconResourceName;
|
||||||
presence_data.smallImageKey = kStrawberryIconResourceName;
|
presence_data.smallImageKey = kStrawberryIconResourceName;
|
||||||
presence_data.smallImageText = kStrawberryIconDescription;
|
presence_data.smallImageText = kStrawberryIconDescription;
|
||||||
presence_data.instance = 0;
|
presence_data.instance = 0;
|
||||||
|
|
||||||
|
QByteArray artist;
|
||||||
if (!activity_.artist.isEmpty()) {
|
if (!activity_.artist.isEmpty()) {
|
||||||
QByteArray artist = activity_.artist.toUtf8();
|
artist = activity_.artist.toUtf8();
|
||||||
artist.prepend(tr("by ").toUtf8());
|
if (artist.size() < 2) { // Discord activity 2 char min. fix
|
||||||
|
artist.append(" ");
|
||||||
|
}
|
||||||
presence_data.state = artist.constData();
|
presence_data.state = artist.constData();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!activity_.album.isEmpty() && activity_.album != activity_.title) {
|
QByteArray album;
|
||||||
QByteArray album = activity_.album.toUtf8();
|
if (!activity_.album.isEmpty()) {
|
||||||
|
album = activity_.album.toUtf8();
|
||||||
album.prepend(tr("on ").toUtf8());
|
album.prepend(tr("on ").toUtf8());
|
||||||
presence_data.largeImageText = album.constData();
|
presence_data.largeImageText = album.constData();
|
||||||
}
|
}
|
||||||
@@ -151,13 +157,19 @@ void RichPresence::SendPresenceUpdate() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void RichPresence::SetTimestamp(const qint64 seconds) {
|
void RichPresence::SetTimestamp(const qint64 seconds) {
|
||||||
|
|
||||||
activity_.start_timestamp = QDateTime::currentSecsSinceEpoch();
|
activity_.start_timestamp = QDateTime::currentSecsSinceEpoch();
|
||||||
activity_.seek_secs = seconds;
|
activity_.seek_secs = seconds;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void RichPresence::Seeked(const qint64 seek_microseconds) {
|
void RichPresence::Seeked(const qint64 seek_microseconds) {
|
||||||
|
|
||||||
|
if (!initialized_) return;
|
||||||
|
|
||||||
SetTimestamp(seek_microseconds / 1000LL);
|
SetTimestamp(seek_microseconds / 1000LL);
|
||||||
SendPresenceUpdate();
|
SendPresenceUpdate();
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace discord
|
} // namespace discord
|
||||||
|
|||||||
@@ -69,8 +69,8 @@ class RichPresence : public QObject {
|
|||||||
qint64 seek_secs;
|
qint64 seek_secs;
|
||||||
};
|
};
|
||||||
Activity activity_;
|
Activity activity_;
|
||||||
qint64 send_presence_timestamp_;
|
bool initialized_;
|
||||||
bool enabled_;
|
int status_display_type_;
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace discord
|
} // namespace discord
|
||||||
|
|||||||
@@ -256,3 +256,19 @@ bool EngineBase::ValidOutput(const QString &output) {
|
|||||||
return (true);
|
return (true);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void EngineBase::UpdateSpotifyAccessToken(const QString &spotify_access_token) {
|
||||||
|
|
||||||
|
#ifdef HAVE_SPOTIFY
|
||||||
|
|
||||||
|
spotify_access_token_ = spotify_access_token;
|
||||||
|
|
||||||
|
SetSpotifyAccessToken();
|
||||||
|
|
||||||
|
#else
|
||||||
|
|
||||||
|
Q_UNUSED(spotify_access_token)
|
||||||
|
|
||||||
|
#endif // HAVE_SPOTIFY
|
||||||
|
|
||||||
|
}
|
||||||
|
|||||||
@@ -127,6 +127,7 @@ class EngineBase : public QObject {
|
|||||||
virtual void ReloadSettings();
|
virtual void ReloadSettings();
|
||||||
void UpdateVolume(const uint volume);
|
void UpdateVolume(const uint volume);
|
||||||
void EmitAboutToFinish();
|
void EmitAboutToFinish();
|
||||||
|
void UpdateSpotifyAccessToken(const QString &spotify_access_token);
|
||||||
|
|
||||||
public:
|
public:
|
||||||
// Simple accessors
|
// Simple accessors
|
||||||
@@ -175,6 +176,11 @@ class EngineBase : public QObject {
|
|||||||
|
|
||||||
void Finished();
|
void Finished();
|
||||||
|
|
||||||
|
private:
|
||||||
|
#ifdef HAVE_SPOTIFY
|
||||||
|
virtual void SetSpotifyAccessToken() {}
|
||||||
|
#endif
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
bool playbin3_enabled_;
|
bool playbin3_enabled_;
|
||||||
bool exclusive_mode_;
|
bool exclusive_mode_;
|
||||||
|
|||||||
@@ -517,10 +517,20 @@ bool GstEngine::ExclusiveModeSupport(const QString &output) const {
|
|||||||
|
|
||||||
void GstEngine::ReloadSettings() {
|
void GstEngine::ReloadSettings() {
|
||||||
|
|
||||||
|
#ifdef HAVE_SPOTIFY
|
||||||
|
const QString old_spotify_access_token = spotify_access_token_;
|
||||||
|
#endif
|
||||||
|
|
||||||
EngineBase::ReloadSettings();
|
EngineBase::ReloadSettings();
|
||||||
|
|
||||||
if (output_.isEmpty()) output_ = QLatin1String(kAutoSink);
|
if (output_.isEmpty()) output_ = QLatin1String(kAutoSink);
|
||||||
|
|
||||||
|
#ifdef HAVE_SPOTIFY
|
||||||
|
if (current_pipeline_ && old_spotify_access_token != spotify_access_token_) {
|
||||||
|
current_pipeline_->set_spotify_access_token(spotify_access_token_);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void GstEngine::ConsumeBuffer(GstBuffer *buffer, const int pipeline_id, const QString &format) {
|
void GstEngine::ConsumeBuffer(GstBuffer *buffer, const int pipeline_id, const QString &format) {
|
||||||
@@ -1199,3 +1209,13 @@ bool GstEngine::AnyExclusivePipelineActive() const {
|
|||||||
return (current_pipeline_ && current_pipeline_->exclusive_mode()) || OldExclusivePipelineActive();
|
return (current_pipeline_ && current_pipeline_->exclusive_mode()) || OldExclusivePipelineActive();
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#ifdef HAVE_SPOTIFY
|
||||||
|
void GstEngine::SetSpotifyAccessToken() {
|
||||||
|
|
||||||
|
if (current_pipeline_) {
|
||||||
|
current_pipeline_->set_spotify_access_token(spotify_access_token_);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
#endif // HAVE_SPOTIFY
|
||||||
|
|||||||
@@ -146,6 +146,10 @@ class GstEngine : public EngineBase, public GstBufferConsumer {
|
|||||||
bool OldExclusivePipelineActive() const;
|
bool OldExclusivePipelineActive() const;
|
||||||
bool AnyExclusivePipelineActive() const;
|
bool AnyExclusivePipelineActive() const;
|
||||||
|
|
||||||
|
#ifdef HAVE_SPOTIFY
|
||||||
|
void SetSpotifyAccessToken() override;
|
||||||
|
#endif
|
||||||
|
|
||||||
private:
|
private:
|
||||||
SharedPtr<TaskManager> task_manager_;
|
SharedPtr<TaskManager> task_manager_;
|
||||||
GstDiscoverer *discoverer_;
|
GstDiscoverer *discoverer_;
|
||||||
|
|||||||
@@ -1369,6 +1369,12 @@ void GstEnginePipeline::AboutToFinishCallback(GstPlayBin *playbin, gpointer self
|
|||||||
qLog(Debug) << "Stream from URL" << instance->gst_url_ << "about to finish.";
|
qLog(Debug) << "Stream from URL" << instance->gst_url_ << "about to finish.";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// When playing GME files it seems playbin3 emits about-to-finish early
|
||||||
|
// This stops us from skipping when the song has just started.
|
||||||
|
if (instance->position() == 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
instance->about_to_finish_ = true;
|
instance->about_to_finish_ = true;
|
||||||
|
|
||||||
if (instance->HasNextUrl() && !instance->next_uri_set_.value()) {
|
if (instance->HasNextUrl() && !instance->next_uri_set_.value()) {
|
||||||
|
|||||||
@@ -51,7 +51,7 @@ QVariant FilterTree::DataFromColumn(const QString &column, const Song &metadata)
|
|||||||
if (column == "playcount"_L1) return metadata.playcount();
|
if (column == "playcount"_L1) return metadata.playcount();
|
||||||
if (column == "skipcount"_L1) return metadata.skipcount();
|
if (column == "skipcount"_L1) return metadata.skipcount();
|
||||||
if (column == "filename"_L1) return metadata.basefilename();
|
if (column == "filename"_L1) return metadata.basefilename();
|
||||||
if (column == "url"_L1) return metadata.effective_stream_url().toString();
|
if (column == "url"_L1) return metadata.effective_url().toString();
|
||||||
|
|
||||||
return QVariant();
|
return QVariant();
|
||||||
|
|
||||||
|
|||||||
@@ -66,7 +66,8 @@ void ChartLyricsProvider::HandleSearchReply(QNetworkReply *reply, const int id,
|
|||||||
QObject::disconnect(reply, nullptr, this, nullptr);
|
QObject::disconnect(reply, nullptr, this, nullptr);
|
||||||
reply->deleteLater();
|
reply->deleteLater();
|
||||||
|
|
||||||
const QScopeGuard search_finished = qScopeGuard([this, id]() { Q_EMIT SearchFinished(id); });
|
LyricsSearchResults results;
|
||||||
|
const QScopeGuard search_finished = qScopeGuard([this, id, &results]() { Q_EMIT SearchFinished(id, results); });
|
||||||
|
|
||||||
const ReplyDataResult reply_data_result = GetReplyData(reply);
|
const ReplyDataResult reply_data_result = GetReplyData(reply);
|
||||||
if (!reply_data_result.success()) {
|
if (!reply_data_result.success()) {
|
||||||
@@ -75,9 +76,7 @@ void ChartLyricsProvider::HandleSearchReply(QNetworkReply *reply, const int id,
|
|||||||
}
|
}
|
||||||
|
|
||||||
QXmlStreamReader reader(reply_data_result.data);
|
QXmlStreamReader reader(reply_data_result.data);
|
||||||
LyricsSearchResults results;
|
|
||||||
LyricsSearchResult result;
|
LyricsSearchResult result;
|
||||||
|
|
||||||
while (!reader.atEnd()) {
|
while (!reader.atEnd()) {
|
||||||
const QXmlStreamReader::TokenType type = reader.readNext();
|
const QXmlStreamReader::TokenType type = reader.readNext();
|
||||||
const QString name = reader.name().toString();
|
const QString name = reader.name().toString();
|
||||||
|
|||||||
@@ -31,14 +31,12 @@
|
|||||||
#include <QNetworkRequest>
|
#include <QNetworkRequest>
|
||||||
#include <QNetworkReply>
|
#include <QNetworkReply>
|
||||||
#include <QRegularExpression>
|
#include <QRegularExpression>
|
||||||
#include <QSettings>
|
|
||||||
#include <QJsonDocument>
|
#include <QJsonDocument>
|
||||||
#include <QJsonObject>
|
#include <QJsonObject>
|
||||||
#include <QJsonArray>
|
#include <QJsonArray>
|
||||||
#include <QJsonValue>
|
#include <QJsonValue>
|
||||||
#include <QJsonParseError>
|
#include <QJsonParseError>
|
||||||
#include <QMessageBox>
|
#include <QMessageBox>
|
||||||
#include <QMutexLocker>
|
|
||||||
|
|
||||||
#include "includes/shared_ptr.h"
|
#include "includes/shared_ptr.h"
|
||||||
#include "core/logging.h"
|
#include "core/logging.h"
|
||||||
@@ -148,6 +146,8 @@ void GeniusLyricsProvider::StartSearch(const int id, const LyricsSearchRequest &
|
|||||||
QNetworkReply *reply = CreateGetRequest(QUrl(QLatin1String(kUrlSearch)), url_query);
|
QNetworkReply *reply = CreateGetRequest(QUrl(QLatin1String(kUrlSearch)), url_query);
|
||||||
QObject::connect(reply, &QNetworkReply::finished, this, [this, reply, id]() { HandleSearchReply(reply, id); });
|
QObject::connect(reply, &QNetworkReply::finished, this, [this, reply, id]() { HandleSearchReply(reply, id); });
|
||||||
|
|
||||||
|
qLog(Debug) << name_ << "Sending request for" << url_query.query();
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
GeniusLyricsProvider::JsonObjectResult GeniusLyricsProvider::ParseJsonObject(QNetworkReply *reply) {
|
GeniusLyricsProvider::JsonObjectResult GeniusLyricsProvider::ParseJsonObject(QNetworkReply *reply) {
|
||||||
@@ -302,10 +302,8 @@ void GeniusLyricsProvider::HandleSearchReply(QNetworkReply *reply, const int id)
|
|||||||
const QString artist = primary_artist["name"_L1].toString();
|
const QString artist = primary_artist["name"_L1].toString();
|
||||||
const QString title = object_result["title"_L1].toString();
|
const QString title = object_result["title"_L1].toString();
|
||||||
|
|
||||||
// Ignore results where both the artist and title don't match.
|
// Ignore results where the artist or title don't begin or end the same
|
||||||
if (!artist.startsWith(search->request.albumartist, Qt::CaseInsensitive) &&
|
if (!StartsOrEndsMatch(artist, search->request.artist) || !StartsOrEndsMatch(title, search->request.title)) {
|
||||||
!artist.startsWith(search->request.artist, Qt::CaseInsensitive) &&
|
|
||||||
!title.startsWith(search->request.title, Qt::CaseInsensitive)) {
|
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -323,6 +321,12 @@ void GeniusLyricsProvider::HandleSearchReply(QNetworkReply *reply, const int id)
|
|||||||
QNetworkReply *new_reply = CreateGetRequest(url);
|
QNetworkReply *new_reply = CreateGetRequest(url);
|
||||||
QObject::connect(new_reply, &QNetworkReply::finished, this, [this, new_reply, search, url]() { HandleLyricReply(new_reply, search->id, url); });
|
QObject::connect(new_reply, &QNetworkReply::finished, this, [this, new_reply, search, url]() { HandleLyricReply(new_reply, search->id, url); });
|
||||||
|
|
||||||
|
qLog(Debug) << name_ << "Sending request for" << url;
|
||||||
|
|
||||||
|
// If full match, don't bother iterating further
|
||||||
|
if (artist == search->request.albumartist && artist == search->request.artist && title == search->request.title) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -363,12 +367,18 @@ void GeniusLyricsProvider::HandleLyricReply(QNetworkReply *reply, const int sear
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const QString content = QString::fromUtf8(data);
|
static const QRegularExpression start_tag(u"<div[^>]*>"_s);
|
||||||
QString lyrics = HtmlLyricsProvider::ParseLyricsFromHTML(content, QRegularExpression(u"<div[^>]*>"_s), QRegularExpression(u"<\\/div>"_s), QRegularExpression(u"<div data-lyrics-container=[^>]+>"_s), true);
|
static const QRegularExpression end_tag(u"<\\/div>"_s);
|
||||||
if (lyrics.isEmpty()) {
|
static const QRegularExpression lyrics_start(u"<div data-lyrics-container=[^>]+>"_s);
|
||||||
lyrics = HtmlLyricsProvider::ParseLyricsFromHTML(content, QRegularExpression(u"<div[^>]*>"_s), QRegularExpression(u"<\\/div>"_s), QRegularExpression(u"<div class=\"lyrics\">"_s), true);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
static const QRegularExpression regex_html_tag_span_trans(u"<span class=\"LyricsHeader__Translations[^>]*>[^<]*</span>"_s);
|
||||||
|
static const QRegularExpression regex_html_tag_div_ellipsis(u"<div class=\"LyricsHeader__TextEllipsis[^>]*>[^<]*</div>"_s);
|
||||||
|
static const QRegularExpression regex_html_tag_span_contribs(u"<span class=\"ContributorsCreditSong__Contributors[^>]*>[^<]*</span>"_s);
|
||||||
|
static const QRegularExpression regex_html_tag_div_bio(u"<div class=\"SongBioPreview__Container[^>]*>.*?</div>"_s);
|
||||||
|
static const QRegularExpression regex_html_tag_h2(u"<h2 [^>]*>[^<]*</h2>"_s);
|
||||||
|
static const QList<QRegularExpression> regex_removes{ regex_html_tag_span_trans, regex_html_tag_div_ellipsis, regex_html_tag_span_contribs, regex_html_tag_div_bio, regex_html_tag_h2 };
|
||||||
|
|
||||||
|
const QString lyrics = HtmlLyricsProvider::ParseLyricsFromHTML(QString::fromUtf8(data), start_tag, end_tag, lyrics_start, true, regex_removes);
|
||||||
if (!lyrics.isEmpty()) {
|
if (!lyrics.isEmpty()) {
|
||||||
LyricsSearchResult result(lyrics);
|
LyricsSearchResult result(lyrics);
|
||||||
result.artist = lyric.artist;
|
result.artist = lyric.artist;
|
||||||
@@ -404,3 +414,17 @@ void GeniusLyricsProvider::EndSearch(const int id, const LyricsSearchRequest &re
|
|||||||
Q_EMIT SearchFinished(id, results);
|
Q_EMIT SearchFinished(id, results);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool GeniusLyricsProvider::StartsOrEndsMatch(QString s, QString t) {
|
||||||
|
|
||||||
|
constexpr Qt::CaseSensitivity cs = Qt::CaseInsensitive;
|
||||||
|
|
||||||
|
static const QRegularExpression puncts_regex(u"[!,.:;]"_s);
|
||||||
|
static const QRegularExpression quotes_regex(u"[’‘´`]"_s);
|
||||||
|
|
||||||
|
s.remove(puncts_regex).replace(quotes_regex, u"'"_s);
|
||||||
|
t.remove(puncts_regex).replace(quotes_regex, u"'"_s);
|
||||||
|
|
||||||
|
return (s.compare(t, cs) == 0 && !s.isEmpty()) || (!s.isEmpty() && !t.isEmpty() && (s.startsWith(t, cs) || t.startsWith(s, cs) || s.endsWith(t, cs) || t.endsWith(s, cs)));
|
||||||
|
|
||||||
|
}
|
||||||
|
|||||||
@@ -79,6 +79,9 @@ class GeniusLyricsProvider : public JsonLyricsProvider {
|
|||||||
void HandleSearchReply(QNetworkReply *reply, const int id);
|
void HandleSearchReply(QNetworkReply *reply, const int id);
|
||||||
void HandleLyricReply(QNetworkReply *reply, const int search_id, const QUrl &url);
|
void HandleLyricReply(QNetworkReply *reply, const int search_id, const QUrl &url);
|
||||||
|
|
||||||
|
private:
|
||||||
|
static bool StartsOrEndsMatch(QString s, QString t);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
OAuthenticator *oauth_;
|
OAuthenticator *oauth_;
|
||||||
mutable QMutex mutex_access_token_;
|
mutable QMutex mutex_access_token_;
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user