Compare commits
119 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
eb6289dd1c | ||
|
|
671df6eafb | ||
|
|
579563b32c | ||
|
|
135b93a5af | ||
|
|
84c6e09c42 | ||
|
|
b4f9808d11 | ||
|
|
d96d4224a2 | ||
|
|
f65927e308 | ||
|
|
eeeea8566e | ||
|
|
54c42b276f | ||
|
|
8e4b4d6e41 | ||
|
|
ac9fd9070f | ||
|
|
6348649bc6 | ||
|
|
c95886d8db | ||
|
|
117b965a7b | ||
|
|
1b6b5f9afa | ||
|
|
83bc8d9e86 | ||
|
|
33f0421d3f | ||
|
|
661615e546 | ||
|
|
c52fc90306 | ||
|
|
eeb55fbc42 | ||
|
|
5d77eb1901 | ||
|
|
654a94fe3d | ||
|
|
4238708226 | ||
|
|
5f02072bf3 | ||
|
|
eec5b448b6 | ||
|
|
5cda756f92 | ||
|
|
e9588dd85b | ||
|
|
dda0e4aa06 | ||
|
|
2282406166 | ||
|
|
b3f0dee8e9 | ||
|
|
f9f7381247 | ||
|
|
48685325e6 | ||
|
|
e8bcaf415c | ||
|
|
c9197e8df7 | ||
|
|
2bb09cf575 | ||
|
|
7bf4ad3884 | ||
|
|
5154d7ac84 | ||
|
|
9299653722 | ||
|
|
b0f0133d29 | ||
|
|
9211b6f0c0 | ||
|
|
ab6a0ed6dd | ||
|
|
c975c1e4aa | ||
|
|
f9a593dc74 | ||
|
|
9151520d50 | ||
|
|
8f72d877bd | ||
|
|
e1990c9315 | ||
|
|
4cd5dcbfcf | ||
|
|
3ee2125e8f | ||
|
|
4652f3b449 | ||
|
|
697717eb1e | ||
|
|
cbde71f5f1 | ||
|
|
bf52afa21d | ||
|
|
2083e008ab | ||
|
|
2be0d23b1b | ||
|
|
fda56dda25 | ||
|
|
ed259781e9 | ||
|
|
0c7fcd5a7a | ||
|
|
310b7b9065 | ||
|
|
cd534bbda7 | ||
|
|
1a66eaf7bf | ||
|
|
a94d6e3dd8 | ||
|
|
54cfb2bbc4 | ||
|
|
00bc3f76cf | ||
|
|
71a6d378d9 | ||
|
|
99a5aee8b3 | ||
|
|
89d2a23dac | ||
|
|
ee1bf47f5c | ||
|
|
13ac20f8b3 | ||
|
|
adef05bbdf | ||
|
|
f03ff452b8 | ||
|
|
c39489060b | ||
|
|
002fa8f4aa | ||
|
|
d2c747258c | ||
|
|
53e3664726 | ||
|
|
26ff9f6b53 | ||
|
|
f542f1c854 | ||
|
|
33041ffa75 | ||
|
|
1493164df9 | ||
|
|
8ffef558ff | ||
|
|
8b3f44ffca | ||
|
|
2706529006 | ||
|
|
7e331a2055 | ||
|
|
505329730c | ||
|
|
1a07404c10 | ||
|
|
b02adc7758 | ||
|
|
952252ebcd | ||
|
|
eee0c40132 | ||
|
|
567bad33e1 | ||
|
|
b5c0e93989 | ||
|
|
ac17df2a86 | ||
|
|
a9a5899252 | ||
|
|
395d85c1b4 | ||
|
|
52ba1ce17f | ||
|
|
604a246fe8 | ||
|
|
e172c4871c | ||
|
|
2a9b32690d | ||
|
|
76fa4745d0 | ||
|
|
6f4d26e9d3 | ||
|
|
f40f43861d | ||
|
|
717ebbbb24 | ||
|
|
79c69e1b1e | ||
|
|
8fc95e08dc | ||
|
|
3f06528ba3 | ||
|
|
0e44b10eec | ||
|
|
d74fe92ce8 | ||
|
|
8037948f7f | ||
|
|
ab29170972 | ||
|
|
6f843e2499 | ||
|
|
6b8a816ce6 | ||
|
|
2c0541fb79 | ||
|
|
cb22890d79 | ||
|
|
5a9346cc80 | ||
|
|
1c85220ffa | ||
|
|
39d4818def | ||
|
|
db0cc66ba0 | ||
|
|
c10a64f08a | ||
|
|
5a3d60b203 | ||
|
|
28b9bf1f76 |
1
.github/FUNDING.yml
vendored
1
.github/FUNDING.yml
vendored
@@ -1,3 +1,4 @@
|
||||
github: jonaski
|
||||
patreon: jonaskvinge
|
||||
ko_fi: jonaskvinge
|
||||
custom: https://paypal.me/jonaskvinge
|
||||
|
||||
440
.github/workflows/build.yml
vendored
440
.github/workflows/build.yml
vendored
@@ -5,6 +5,7 @@ jobs:
|
||||
|
||||
build-opensuse:
|
||||
name: Build openSUSE
|
||||
if: github.repository != 'strawberrymusicplayer/strawberry-private'
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
fail-fast: false
|
||||
@@ -38,6 +39,7 @@ jobs:
|
||||
make
|
||||
cmake
|
||||
gettext-tools
|
||||
openssh-clients
|
||||
rsync
|
||||
glibc-devel
|
||||
libboost_headers-devel
|
||||
@@ -59,6 +61,7 @@ jobs:
|
||||
libgpod-devel
|
||||
libmtp-devel
|
||||
libchromaprint-devel
|
||||
fftw3-devel
|
||||
libebur128-devel
|
||||
desktop-file-utils
|
||||
update-desktop-files
|
||||
@@ -102,6 +105,7 @@ jobs:
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
submodules: recursive
|
||||
- name: Add safe git directory
|
||||
run: git config --global --add safe.directory ${GITHUB_WORKSPACE}
|
||||
- name: Create Build Environment
|
||||
@@ -139,24 +143,25 @@ jobs:
|
||||
/usr/src/packages/SRPMS/*.rpm
|
||||
/usr/src/packages/RPMS/x86_64/*.rpm
|
||||
- name: SSH key setup
|
||||
if: github.ref == 'refs/heads/master' || github.ref == 'refs/heads/ci'
|
||||
if: github.repository == 'strawberrymusicplayer/strawberry' && github.event.pull_request.head.repo.fork == false && (github.ref == 'refs/heads/master' || github.ref == 'refs/heads/ci')
|
||||
uses: shimataro/ssh-key-action@v2
|
||||
with:
|
||||
known_hosts: ${{secrets.SSH_KNOWN_HOSTS}}
|
||||
key: ${{ secrets.SSH_KEY }}
|
||||
- name: Create server path
|
||||
if: github.ref == 'refs/heads/master' || github.ref == 'refs/heads/ci'
|
||||
if: github.repository == 'strawberrymusicplayer/strawberry' && github.event.pull_request.head.repo.fork == false && (github.ref == 'refs/heads/master' || github.ref == 'refs/heads/ci')
|
||||
run: ssh -p ${{secrets.SSH_PORT}} -o StrictHostKeyChecking=no ${{secrets.SSH_USER}}@${{secrets.SSH_HOST}} mkdir -p ${{secrets.BUILDS_PATH}}/source ${{secrets.BUILDS_PATH}}/opensuse/${{env.opensuse_subdir}}
|
||||
- name: rsync source
|
||||
if: (github.ref == 'refs/heads/master' || github.ref == 'refs/heads/ci') && matrix.opensuse_version == 'tumbleweed' && matrix.qt_version == '6'
|
||||
if: github.repository == 'strawberrymusicplayer/strawberry' && github.event.pull_request.head.repo.fork == false && (github.ref == 'refs/heads/master' || github.ref == 'refs/heads/ci') && matrix.opensuse_version == 'tumbleweed' && matrix.qt_version == '6'
|
||||
run: rsync -e "ssh -p ${{secrets.SSH_PORT}} -o StrictHostKeyChecking=no" -var /usr/src/packages/SOURCES/*.xz ${{secrets.SSH_USER}}@${{secrets.SSH_HOST}}:${{secrets.BUILDS_PATH}}/source/
|
||||
- name: rsync rpms
|
||||
if: (github.ref == 'refs/heads/master' || github.ref == 'refs/heads/ci') && matrix.opensuse_version != 'tumbleweed'
|
||||
if: github.repository == 'strawberrymusicplayer/strawberry' && github.event.pull_request.head.repo.fork == false && (github.ref == 'refs/heads/master' || github.ref == 'refs/heads/ci') && matrix.opensuse_version != 'tumbleweed'
|
||||
run: rsync -e "ssh -p ${{secrets.SSH_PORT}} -o StrictHostKeyChecking=no" -var /usr/src/packages/SRPMS/*.rpm /usr/src/packages/RPMS/x86_64/*.rpm ${{secrets.SSH_USER}}@${{secrets.SSH_HOST}}:${{secrets.BUILDS_PATH}}/opensuse/${{env.opensuse_subdir}}/
|
||||
|
||||
|
||||
build-fedora:
|
||||
name: Build Fedora
|
||||
if: github.repository != 'strawberrymusicplayer/strawberry-private'
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
fail-fast: false
|
||||
@@ -221,6 +226,7 @@ jobs:
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
submodules: recursive
|
||||
- name: Add safe git directory
|
||||
run: git config --global --add safe.directory ${GITHUB_WORKSPACE}
|
||||
- name: Create Build Environment
|
||||
@@ -249,21 +255,22 @@ jobs:
|
||||
/github/home/rpmbuild/SRPMS/*.rpm
|
||||
/github/home/rpmbuild/RPMS/x86_64/*.rpm
|
||||
- name: SSH key setup
|
||||
if: github.ref == 'refs/heads/master' || github.ref == 'refs/heads/ci'
|
||||
if: github.repository == 'strawberrymusicplayer/strawberry' && github.event.pull_request.head.repo.fork == false && (github.ref == 'refs/heads/master' || github.ref == 'refs/heads/ci')
|
||||
uses: shimataro/ssh-key-action@v2
|
||||
with:
|
||||
known_hosts: ${{secrets.SSH_KNOWN_HOSTS}}
|
||||
key: ${{ secrets.SSH_KEY }}
|
||||
- name: Create server path
|
||||
if: github.ref == 'refs/heads/master' || github.ref == 'refs/heads/ci'
|
||||
if: github.repository == 'strawberrymusicplayer/strawberry' && github.event.pull_request.head.repo.fork == false && (github.ref == 'refs/heads/master' || github.ref == 'refs/heads/ci')
|
||||
run: ssh -p ${{secrets.SSH_PORT}} -o StrictHostKeyChecking=no ${{secrets.SSH_USER}}@${{secrets.SSH_HOST}} mkdir -p ${{secrets.BUILDS_PATH}}/fedora/${{matrix.fedora_version}}
|
||||
- name: rsync
|
||||
if: github.ref == 'refs/heads/master' || github.ref == 'refs/heads/ci'
|
||||
if: github.repository == 'strawberrymusicplayer/strawberry' && github.event.pull_request.head.repo.fork == false && (github.ref == 'refs/heads/master' || github.ref == 'refs/heads/ci')
|
||||
run: rsync -e "ssh -p ${{secrets.SSH_PORT}} -o StrictHostKeyChecking=no" -var /github/home/rpmbuild/SRPMS/*.rpm /github/home/rpmbuild/RPMS/x86_64/*.rpm ${{secrets.SSH_USER}}@${{secrets.SSH_HOST}}:${{secrets.BUILDS_PATH}}/fedora/${{matrix.fedora_version}}/
|
||||
|
||||
|
||||
build-openmandriva:
|
||||
name: Build OpenMandriva
|
||||
if: github.repository != 'strawberrymusicplayer/strawberry-private'
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
fail-fast: false
|
||||
@@ -319,7 +326,7 @@ jobs:
|
||||
lib64Qt6Test-devel
|
||||
qt6-cmake
|
||||
qt6-qtbase-tools
|
||||
qt6-qttools
|
||||
qt6-qttools-linguist
|
||||
desktop-file-utils
|
||||
appstream
|
||||
appstream-util
|
||||
@@ -328,6 +335,7 @@ jobs:
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
submodules: recursive
|
||||
- name: Add safe git directory
|
||||
run: git config --global --add safe.directory ${GITHUB_WORKSPACE}
|
||||
- name: Create Build Environment
|
||||
@@ -356,21 +364,22 @@ jobs:
|
||||
/github/home/rpmbuild/SRPMS/*.rpm
|
||||
/github/home/rpmbuild/RPMS/x86_64/*.rpm
|
||||
- name: SSH key setup
|
||||
if: matrix.openmandriva_version != 'cooker' && (github.ref == 'refs/heads/master' || github.ref == 'refs/heads/ci')
|
||||
if: github.repository == 'strawberrymusicplayer/strawberry' && github.event.pull_request.head.repo.fork == false && matrix.openmandriva_version != 'cooker' && (github.ref == 'refs/heads/master' || github.ref == 'refs/heads/ci')
|
||||
uses: shimataro/ssh-key-action@v2
|
||||
with:
|
||||
known_hosts: ${{secrets.SSH_KNOWN_HOSTS}}
|
||||
key: ${{ secrets.SSH_KEY }}
|
||||
- name: Create server path
|
||||
if: matrix.openmandriva_version != 'cooker' && (github.ref == 'refs/heads/master' || github.ref == 'refs/heads/ci')
|
||||
if: github.repository == 'strawberrymusicplayer/strawberry' && github.event.pull_request.head.repo.fork == false && matrix.openmandriva_version != 'cooker' && (github.ref == 'refs/heads/master' || github.ref == 'refs/heads/ci')
|
||||
run: ssh -p ${{secrets.SSH_PORT}} -o StrictHostKeyChecking=no ${{secrets.SSH_USER}}@${{secrets.SSH_HOST}} mkdir -p ${{secrets.BUILDS_PATH}}/openmandriva/${{matrix.openmandriva_version}}
|
||||
- name: rsync
|
||||
if: matrix.openmandriva_version != 'cooker' && (github.ref == 'refs/heads/master' || github.ref == 'refs/heads/ci')
|
||||
if: github.repository == 'strawberrymusicplayer/strawberry' && github.event.pull_request.head.repo.fork == false && matrix.openmandriva_version != 'cooker' && (github.ref == 'refs/heads/master' || github.ref == 'refs/heads/ci')
|
||||
run: rsync -e "ssh -p ${{secrets.SSH_PORT}} -o StrictHostKeyChecking=no" -var /github/home/rpmbuild/SRPMS/*.rpm /github/home/rpmbuild/RPMS/x86_64/*.rpm ${{secrets.SSH_USER}}@${{secrets.SSH_HOST}}:${{secrets.BUILDS_PATH}}/openmandriva/${{matrix.openmandriva_version}}/
|
||||
|
||||
|
||||
build-mageia:
|
||||
name: Build Mageia
|
||||
if: github.repository != 'strawberrymusicplayer/strawberry-private'
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
fail-fast: false
|
||||
@@ -428,6 +437,7 @@ jobs:
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
submodules: recursive
|
||||
- name: Add safe git directory
|
||||
run: git config --global --add safe.directory ${GITHUB_WORKSPACE}
|
||||
- name: Create Build Environment
|
||||
@@ -452,25 +462,26 @@ jobs:
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: mageia-${{matrix.mageia_version}}
|
||||
path:
|
||||
path: |
|
||||
/github/home/rpmbuild/SRPMS/*.rpm
|
||||
/github/home/rpmbuild/RPMS/x86_64/*.rpm
|
||||
- name: SSH key setup
|
||||
if: github.ref == 'refs/heads/master' || github.ref == 'refs/heads/ci'
|
||||
if: github.repository == 'strawberrymusicplayer/strawberry' && github.event.pull_request.head.repo.fork == false && (github.ref == 'refs/heads/master' || github.ref == 'refs/heads/ci')
|
||||
uses: shimataro/ssh-key-action@v2
|
||||
with:
|
||||
known_hosts: ${{secrets.SSH_KNOWN_HOSTS}}
|
||||
key: ${{ secrets.SSH_KEY }}
|
||||
- name: Create server path
|
||||
if: github.ref == 'refs/heads/master' || github.ref == 'refs/heads/ci'
|
||||
if: github.repository == 'strawberrymusicplayer/strawberry' && github.event.pull_request.head.repo.fork == false && (github.ref == 'refs/heads/master' || github.ref == 'refs/heads/ci')
|
||||
run: ssh -p ${{secrets.SSH_PORT}} -o StrictHostKeyChecking=no ${{secrets.SSH_USER}}@${{secrets.SSH_HOST}} mkdir -p ${{secrets.BUILDS_PATH}}/mageia/${{matrix.mageia_version}}
|
||||
- name: rsync
|
||||
if: github.ref == 'refs/heads/master' || github.ref == 'refs/heads/ci'
|
||||
if: github.repository == 'strawberrymusicplayer/strawberry' && github.event.pull_request.head.repo.fork == false && (github.ref == 'refs/heads/master' || github.ref == 'refs/heads/ci')
|
||||
run: rsync -e "ssh -p ${{secrets.SSH_PORT}} -o StrictHostKeyChecking=no" -var /github/home/rpmbuild/SRPMS/*.rpm /github/home/rpmbuild/RPMS/x86_64/*.rpm ${{secrets.SSH_USER}}@${{secrets.SSH_HOST}}:${{secrets.BUILDS_PATH}}/mageia/${{matrix.mageia_version}}/
|
||||
|
||||
|
||||
build-debian:
|
||||
name: Build Debian
|
||||
if: github.repository != 'strawberrymusicplayer/strawberry-private'
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
fail-fast: false
|
||||
@@ -534,6 +545,7 @@ jobs:
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
submodules: recursive
|
||||
- name: Add safe git directory
|
||||
run: git config --global --add safe.directory ${GITHUB_WORKSPACE}
|
||||
- name: Create Build Environment
|
||||
@@ -550,21 +562,22 @@ jobs:
|
||||
name: debian-${{matrix.debian_version}}
|
||||
path: "*.deb"
|
||||
- name: SSH key setup
|
||||
if: github.ref == 'refs/heads/master' || github.ref == 'refs/heads/ci'
|
||||
if: github.repository == 'strawberrymusicplayer/strawberry' && github.event.pull_request.head.repo.fork == false && (github.ref == 'refs/heads/master' || github.ref == 'refs/heads/ci')
|
||||
uses: shimataro/ssh-key-action@v2
|
||||
with:
|
||||
known_hosts: ${{secrets.SSH_KNOWN_HOSTS}}
|
||||
key: ${{ secrets.SSH_KEY }}
|
||||
- name: Create server path
|
||||
if: github.ref == 'refs/heads/master' || github.ref == 'refs/heads/ci'
|
||||
if: github.repository == 'strawberrymusicplayer/strawberry' && github.event.pull_request.head.repo.fork == false && (github.ref == 'refs/heads/master' || github.ref == 'refs/heads/ci')
|
||||
run: ssh -p ${{secrets.SSH_PORT}} -o StrictHostKeyChecking=no ${{secrets.SSH_USER}}@${{secrets.SSH_HOST}} mkdir -p ${{secrets.BUILDS_PATH}}/debian/${{matrix.debian_version}}
|
||||
- name: rsync
|
||||
if: github.ref == 'refs/heads/master' || github.ref == 'refs/heads/ci'
|
||||
if: github.repository == 'strawberrymusicplayer/strawberry' && github.event.pull_request.head.repo.fork == false && (github.ref == 'refs/heads/master' || github.ref == 'refs/heads/ci')
|
||||
run: rsync -e "ssh -p ${{secrets.SSH_PORT}} -o StrictHostKeyChecking=no" -var *.deb ${{secrets.SSH_USER}}@${{secrets.SSH_HOST}}:${{secrets.BUILDS_PATH}}/debian/${{matrix.debian_version}}/
|
||||
|
||||
|
||||
build-ubuntu:
|
||||
name: Build Ubuntu
|
||||
if: github.repository != 'strawberrymusicplayer/strawberry-private'
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
fail-fast: false
|
||||
@@ -631,6 +644,7 @@ jobs:
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
submodules: recursive
|
||||
- name: Add safe git directory
|
||||
run: git config --global --add safe.directory ${GITHUB_WORKSPACE}
|
||||
- name: Create Build Environment
|
||||
@@ -649,22 +663,22 @@ jobs:
|
||||
*.deb
|
||||
*.ddeb
|
||||
- name: SSH key setup
|
||||
if: github.ref == 'refs/heads/master' || github.ref == 'refs/heads/ci'
|
||||
if: github.repository == 'strawberrymusicplayer/strawberry' && github.event.pull_request.head.repo.fork == false && (github.ref == 'refs/heads/master' || github.ref == 'refs/heads/ci')
|
||||
uses: shimataro/ssh-key-action@v2
|
||||
with:
|
||||
known_hosts: ${{secrets.SSH_KNOWN_HOSTS}}
|
||||
key: ${{ secrets.SSH_KEY }}
|
||||
- name: Create server path
|
||||
if: github.ref == 'refs/heads/master' || github.ref == 'refs/heads/ci'
|
||||
if: github.repository == 'strawberrymusicplayer/strawberry' && github.event.pull_request.head.repo.fork == false && (github.ref == 'refs/heads/master' || github.ref == 'refs/heads/ci')
|
||||
run: ssh -p ${{secrets.SSH_PORT}} -o StrictHostKeyChecking=no ${{secrets.SSH_USER}}@${{secrets.SSH_HOST}} mkdir -p ${{secrets.BUILDS_PATH}}/ubuntu/${{matrix.ubuntu_version}}
|
||||
- name: rsync
|
||||
if: github.ref == 'refs/heads/master' || github.ref == 'refs/heads/ci'
|
||||
if: github.repository == 'strawberrymusicplayer/strawberry' && github.event.pull_request.head.repo.fork == false && (github.ref == 'refs/heads/master' || github.ref == 'refs/heads/ci')
|
||||
run: rsync -e "ssh -p ${{secrets.SSH_PORT}} -o StrictHostKeyChecking=no" -var *.deb *.ddeb ${{secrets.SSH_USER}}@${{secrets.SSH_HOST}}:${{secrets.BUILDS_PATH}}/ubuntu/${{matrix.ubuntu_version}}/
|
||||
|
||||
|
||||
upload-ubuntu-ppa:
|
||||
name: Upload Ubuntu PPA
|
||||
if: github.ref == 'refs/heads/master' || github.ref == 'refs/heads/ci'
|
||||
if: github.repository == 'strawberrymusicplayer/strawberry' && github.event.pull_request.head.repo.fork == false && (github.ref == 'refs/heads/master' || github.ref == 'refs/heads/ci')
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
fail-fast: false
|
||||
@@ -730,6 +744,7 @@ jobs:
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
submodules: recursive
|
||||
- name: Add safe git directory
|
||||
run: git config --global --add safe.directory ${GITHUB_WORKSPACE}
|
||||
- name: Create Build Environment
|
||||
@@ -744,24 +759,26 @@ jobs:
|
||||
gpg_private_key: ${{secrets.UBUNTU_PPA_GPG_PRIVATE_KEY}}
|
||||
- name: dpkg-buildpackage
|
||||
run: dpkg-buildpackage -S -d -k573D197B5EA20EDF
|
||||
- name: Upload Unstable PPA
|
||||
run: dput ppa:jonaski/strawberry-unstable ../*_source.changes
|
||||
- name: Set is release
|
||||
run: echo "is_release=$(grep '^\s*set\s*(\s*INCLUDE_GIT_REVISION\s\+OFF\s*)\s*$' cmake/Version.cmake >/dev/null 2>&1 && echo 1 || echo 0)" >> $GITHUB_ENV
|
||||
- name: Get release version
|
||||
run: echo "release_version=$(git describe --tags --exact-match ${GITHUB_SHA} 2>/dev/null | head -1)" >> $GITHUB_ENV
|
||||
- name: Upload Unstable PPA
|
||||
if: env.is_release != '1' && env.release_version == ''
|
||||
run: dput ppa:jonaski/strawberry-unstable ../*_source.changes
|
||||
- name: Upload Stable PPA
|
||||
if: env.is_release == '1' && env.release_version != ''
|
||||
run: dput ppa:jonaski/strawberry ../*_source.changes
|
||||
|
||||
|
||||
build-macos:
|
||||
name: Build macOS
|
||||
build-macos-public:
|
||||
name: Build macOS Public
|
||||
if: github.repository != 'strawberrymusicplayer/strawberry-private'
|
||||
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
runner: [ 'macos-11', 'macos-arm64' ]
|
||||
runner: [ 'macos-11' ]
|
||||
buildtype: [ 'release' ]
|
||||
|
||||
runs-on: ${{ matrix.runner }}
|
||||
@@ -779,7 +796,6 @@ jobs:
|
||||
run: echo "cmake_buildtype=$(echo ${{env.buildtype}} | awk '{print toupper(substr($0,0,1))tolower(substr($0,2))}')" >> $GITHUB_ENV
|
||||
|
||||
- name: Uninstall homebrew
|
||||
if: matrix.runner == 'macos-11'
|
||||
run: |
|
||||
curl -sfLO https://raw.githubusercontent.com/Homebrew/install/master/uninstall.sh
|
||||
chmod +x ./uninstall.sh
|
||||
@@ -790,6 +806,14 @@ jobs:
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
submodules: recursive
|
||||
|
||||
- name: Import certificate file
|
||||
if: github.repository == 'strawberrymusicplayer/strawberry' && github.event.pull_request.head.repo.fork == false
|
||||
uses: apple-actions/import-codesign-certs@v2
|
||||
with:
|
||||
p12-file-base64: ${{ secrets.APPLE_DEVELOPER_ID_CERTIFICATE }}
|
||||
p12-password: ${{ secrets.APPLE_DEVELOPER_ID_CERTIFICATE_PASSWORD }}
|
||||
|
||||
- name: Download macOS dependencies
|
||||
run: curl -f -O -L https://github.com/strawberrymusicplayer/strawberry-macos-dependencies/releases/latest/download/strawberry-macos-${{env.arch}}-${{env.buildtype}}.tar.xz
|
||||
@@ -803,9 +827,6 @@ jobs:
|
||||
- name: Update PATH
|
||||
run: echo "${{env.prefix_path}}/bin" >> $GITHUB_PATH
|
||||
|
||||
- name: Change rpath for lconvert
|
||||
run: sudo install_name_tool -change @rpath/QtCore.framework/Versions/A/QtCore /opt/strawberry_macos_${{env.arch}}_release/lib/QtCore.framework/QtCore /opt/strawberry_macos_${{env.arch}}_release/bin/lconvert
|
||||
|
||||
- name: Create Build Environment
|
||||
run: cmake -E make_directory build
|
||||
|
||||
@@ -827,7 +848,7 @@ jobs:
|
||||
-DENABLE_DBUS=OFF
|
||||
-DICU_ROOT="${{env.prefix_path}}"
|
||||
-DFFTW3_DIR="${{env.prefix_path}}"
|
||||
-DCREATEDMG_SKIP_JENKINS=$(test "${{matrix.runner}}" = "macos-arm64" && echo "ON" || echo "OFF")
|
||||
-DAPPLE_DEVELOPER_ID=$(test '${{github.repository}}' = 'strawberrymusicplayer/strawberry' && test '${{github.event.pull_request.base.repo.full_name}}' = '${{github.event.pull_request.head.repo.full_name}}' && echo "383J84DVB6" || echo "")
|
||||
|
||||
- name: Build
|
||||
run: cmake --build build --config Release --parallel 4
|
||||
@@ -836,30 +857,35 @@ jobs:
|
||||
working-directory: build
|
||||
run: make install
|
||||
|
||||
- name: Manually copy files not handled by macdeployqt
|
||||
working-directory: build
|
||||
run: |
|
||||
mkdir -p strawberry.app/Contents/Frameworks/
|
||||
cp ${{env.prefix_path}}/lib/libsoup-3.0.0.dylib strawberry.app/Contents/Frameworks/
|
||||
|
||||
- name: Deploy
|
||||
env:
|
||||
GIO_EXTRA_MODULES: ${{env.prefix_path}}/lib/gio/modules
|
||||
GST_PLUGIN_SCANNER: ${{env.prefix_path}}/libexec/gstreamer-1.0/gst-plugin-scanner
|
||||
GST_PLUGIN_PATH: ${{env.prefix_path}}/lib/gstreamer-1.0
|
||||
LIBSOUP_LIBRARY_PATH: ${{env.prefix_path}}/lib/libsoup-3.0.0.dylib
|
||||
working-directory: build
|
||||
run: make deploy
|
||||
|
||||
- name: Codesign libsoup
|
||||
if: github.repository == 'strawberrymusicplayer/strawberry' && github.event.pull_request.head.repo.fork == false
|
||||
working-directory: build
|
||||
run: codesign -s 383J84DVB6 -f strawberry.app/Contents/Frameworks/{libsoup-3.0.0.dylib,libnghttp2.14.dylib,libpsl.5.dylib,libpcre2-16.0.dylib,libpng16.16.dylib,libzstd.1.dylib} strawberry.app
|
||||
|
||||
- name: Deploy check
|
||||
working-directory: build
|
||||
run: make deploycheck
|
||||
|
||||
- name: Verify code-signing
|
||||
if: github.repository == 'strawberrymusicplayer/strawberry' && github.event.pull_request.head.repo.fork == false
|
||||
working-directory: build
|
||||
run: codesign --deep -v strawberry.app
|
||||
|
||||
- name: Create DMG
|
||||
working-directory: build
|
||||
run: make dmg
|
||||
|
||||
- name: SSH key setup
|
||||
if: matrix.runner == 'macos-11' && (github.ref == 'refs/heads/master' || github.ref == 'refs/heads/ci' || github.ref == 'refs/heads/macos')
|
||||
if: github.repository == 'strawberrymusicplayer/strawberry' && github.event.pull_request.head.repo.fork == false && (github.ref == 'refs/heads/master' || github.ref == 'refs/heads/ci' || github.ref == 'refs/heads/macos')
|
||||
uses: shimataro/ssh-key-action@v2
|
||||
with:
|
||||
known_hosts: ${{secrets.SSH_KNOWN_HOSTS}}
|
||||
@@ -872,6 +898,7 @@ jobs:
|
||||
run: echo "release_version=$(git describe --tags --exact-match ${GITHUB_SHA} 2>/dev/null | head -1)" >> $GITHUB_ENV
|
||||
|
||||
- name: Set Upload path
|
||||
if: github.repository == 'strawberrymusicplayer/strawberry' && github.event.pull_request.head.repo.fork == false && (github.ref == 'refs/heads/master' || github.ref == 'refs/heads/ci' || github.ref == 'refs/heads/macos')
|
||||
run: |
|
||||
if [ "${{env.is_release}}" = "1" ] && ! [ "${{env.release_version}}" = "" ]; then
|
||||
echo "upload_path=${{secrets.DOWNLOADS_PATH}}/stable_releases/macos" >> $GITHUB_ENV
|
||||
@@ -880,106 +907,52 @@ jobs:
|
||||
fi
|
||||
|
||||
- name: Create server path
|
||||
if: github.ref == 'refs/heads/master' || github.ref == 'refs/heads/ci' || github.ref == 'refs/heads/macos'
|
||||
if: github.repository == 'strawberrymusicplayer/strawberry' && github.event.pull_request.head.repo.fork == false && (github.ref == 'refs/heads/master' || github.ref == 'refs/heads/ci' || github.ref == 'refs/heads/macos')
|
||||
run: ssh -p ${{secrets.SSH_PORT}} -o StrictHostKeyChecking=no ${{secrets.SSH_USER}}@${{secrets.SSH_HOST}} mkdir -p ${{env.upload_path}}
|
||||
|
||||
- name: rsync
|
||||
if: github.ref == 'refs/heads/master' || github.ref == 'refs/heads/ci' || github.ref == 'refs/heads/macos'
|
||||
if: github.repository == 'strawberrymusicplayer/strawberry' && github.event.pull_request.head.repo.fork == false && (github.ref == 'refs/heads/master' || github.ref == 'refs/heads/ci' || github.ref == 'refs/heads/macos')
|
||||
run: rsync -e "ssh -p ${{secrets.SSH_PORT}} -o StrictHostKeyChecking=no" -var build/*.dmg ${{secrets.SSH_USER}}@${{secrets.SSH_HOST}}:${{env.upload_path}}/
|
||||
|
||||
|
||||
build-macos-homebrew:
|
||||
name: Build macOS Homebrew
|
||||
runs-on: macos-11
|
||||
build-macos-private:
|
||||
name: Build macOS Private
|
||||
if: github.repository == 'strawberrymusicplayer/strawberry-private'
|
||||
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
runner: [ 'macos-arm64' ]
|
||||
buildtype: [ 'release' ]
|
||||
|
||||
runs-on: ${{ matrix.runner }}
|
||||
|
||||
steps:
|
||||
|
||||
- name: Remove installed brew packages
|
||||
run: brew uninstall $(brew list)
|
||||
- name: Set arch
|
||||
shell: bash
|
||||
run: echo "arch=$(uname -m)" >> $GITHUB_ENV
|
||||
|
||||
- name: Update packages
|
||||
run: brew update
|
||||
- name: Set buildtype
|
||||
run: echo "buildtype=$(echo ${{matrix.buildtype}} | tr '[:upper:]' '[:lower:]')" >> $GITHUB_ENV
|
||||
|
||||
- name: Upgrade packages
|
||||
run: brew upgrade || true
|
||||
|
||||
- name: brew tap
|
||||
run: brew tap homebrew/core
|
||||
|
||||
- name: Install packages
|
||||
run: brew install pkg-config cmake ninja meson bison flex wget create-dmg gettext boost protobuf protobuf-c rsync glib glib-openssl glib-utils glib-networking gdk-pixbuf gobject-introspection orc libffi openssl sqlite fftw libmtp libplist libxml2 libsoup libogg libvorbis flac wavpack opus speex mpg123 lame twolame taglib chromaprint libebur128 libbs2b libcdio libopenmpt faad2 faac fdk-aac musepack game-music-emu qt6 || true
|
||||
|
||||
- name: Use modified gstreamer plugin formulas
|
||||
run: |
|
||||
wget https://files.strawberrymusicplayer.org/patches/gstreamer.rb
|
||||
wget https://files.strawberrymusicplayer.org/patches/gst-plugins-base.rb
|
||||
wget https://files.strawberrymusicplayer.org/patches/gst-plugins-good.rb
|
||||
wget https://files.strawberrymusicplayer.org/patches/gst-plugins-bad.rb
|
||||
wget https://files.strawberrymusicplayer.org/patches/gst-plugins-ugly.rb
|
||||
wget https://files.strawberrymusicplayer.org/patches/gst-libav.rb
|
||||
mv gstreamer.rb gst-plugins-{base,good,bad,ugly}.rb gst-libav.rb /usr/local/Homebrew/Library/Taps/homebrew/homebrew-core/Formula/g/
|
||||
|
||||
- name: Build and install gstreamer
|
||||
env:
|
||||
HOMEBREW_NO_INSTALL_FROM_API: 1
|
||||
run: brew reinstall --build-from-source gstreamer
|
||||
|
||||
- name: Build and install gst-plugins-base
|
||||
env:
|
||||
HOMEBREW_NO_INSTALL_FROM_API: 1
|
||||
run: brew reinstall --build-from-source gst-plugins-base
|
||||
|
||||
- name: Build and install gst-plugins-good
|
||||
env:
|
||||
HOMEBREW_NO_INSTALL_FROM_API: 1
|
||||
run: brew reinstall --build-from-source gst-plugins-good
|
||||
|
||||
- name: Build and install gst-plugins-bad
|
||||
env:
|
||||
HOMEBREW_NO_INSTALL_FROM_API: 1
|
||||
run: brew reinstall --build-from-source gst-plugins-bad
|
||||
|
||||
- name: Build and install gst-plugins-ugly
|
||||
env:
|
||||
HOMEBREW_NO_INSTALL_FROM_API: 1
|
||||
run: brew reinstall --build-from-source gst-plugins-ugly
|
||||
|
||||
- name: Build and install gst-libav
|
||||
env:
|
||||
HOMEBREW_NO_INSTALL_FROM_API: 1
|
||||
run: brew reinstall --build-from-source gst-libav
|
||||
|
||||
- name: Build libgpod
|
||||
env:
|
||||
PERL_MM_USE_DEFAULT: 1
|
||||
run: |
|
||||
git clone https://github.com/strawberrymusicplayer/strawberry-libgpod
|
||||
cd strawberry-libgpod
|
||||
mkdir build
|
||||
cd build
|
||||
cmake .. -DCMAKE_BUILD_TYPE=Release
|
||||
make -j 4
|
||||
sudo make install
|
||||
|
||||
- name: Replace relative library @loader_path paths - macdeployqt does not understand these.
|
||||
run: |
|
||||
for library in $(find -E /usr/local/Cellar -type f -regex '.*/lib/.*\.framework/Versions/A/Qt(Core|Concurrent|Network|Sql|Widgets|Gui|DBus)' -o -name '*.dylib'); do
|
||||
library_paths=$(otool -L "${library}" | sed -n "s/^\t\(.*\) (compatibility version [0-9]*\.[0-9]*\.[0-9]*, current version [0-9]*\.[0-9]*\.[0-9]*)/\1/p")
|
||||
for library_path in ${library_paths}; do
|
||||
if ! [ "$(echo "${library_path}" | grep "^@loader_path" || true)" = "" ]; then
|
||||
new_library_path=$(echo "${library_path}" | sed -E 's/@loader_path(\/\.\.)+\/opt/\/usr\/local\/opt/g')
|
||||
if ! [ "${new_library_path}" = "" ] && ! [ "${new_library_path}" = "${library_path}" ] && [ -e "${new_library_path}" ]; then
|
||||
sudo install_name_tool -change "${library_path}" "${new_library_path}" "${library}"
|
||||
else
|
||||
echo "${library} points to ${library_path}, could not resolve to absolute path."
|
||||
fi
|
||||
fi
|
||||
done
|
||||
done
|
||||
- name: Set cmake buildtype
|
||||
run: echo "cmake_buildtype=$(echo ${{env.buildtype}} | awk '{print toupper(substr($0,0,1))tolower(substr($0,2))}')" >> $GITHUB_ENV
|
||||
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
submodules: recursive
|
||||
|
||||
- name: Unlock keychain
|
||||
run: security unlock-keychain -p ${{ secrets.MACOS_KEYCHAIN_PASSWORD }}
|
||||
|
||||
- name: Set prefix path
|
||||
run: echo "prefix_path=/opt/strawberry_macos_${{env.arch}}_${{env.buildtype}}" >> $GITHUB_ENV
|
||||
|
||||
- name: Update PATH
|
||||
run: echo "${{env.prefix_path}}/bin" >> $GITHUB_PATH
|
||||
|
||||
- name: Create Build Environment
|
||||
run: cmake -E make_directory build
|
||||
@@ -987,8 +960,22 @@ jobs:
|
||||
- name: Configure CMake
|
||||
env:
|
||||
MACOSX_DEPLOYMENT_TARGET: 11.0
|
||||
PKG_CONFIG_PATH: /usr/local/lib/pkgconfig
|
||||
run: cmake -S . -B build -DCMAKE_BUILD_TYPE=Release -DBUILD_WITH_QT6=ON -DBUILD_WERROR=OFF -DUSE_BUNDLE=ON -DCMAKE_PREFIX_PATH=/usr/local/opt/qt6/lib/cmake -DICU_ROOT=/usr/local/opt/icu4c -DPROTOBUF_INCLUDE_DIRS=/usr/local/include
|
||||
PKG_CONFIG_PATH: ${{env.prefix_path}}/lib/pkgconfig
|
||||
LDFLAGS: -L${{env.prefix_path}}/lib -Wl,-rpath,${{env.prefix_path}}/lib
|
||||
run: >
|
||||
cmake
|
||||
--log-level="DEBUG"
|
||||
-S .
|
||||
-B build
|
||||
-DCMAKE_BUILD_TYPE="${{env.cmake_buildtype}}"
|
||||
-DCMAKE_PREFIX_PATH="${{env.prefix_path}}/lib/cmake"
|
||||
-DBUILD_WITH_QT6=ON
|
||||
-DBUILD_WERROR=OFF
|
||||
-DUSE_BUNDLE=ON
|
||||
-DENABLE_DBUS=OFF
|
||||
-DICU_ROOT="${{env.prefix_path}}"
|
||||
-DFFTW3_DIR="${{env.prefix_path}}"
|
||||
-DAPPLE_DEVELOPER_ID="383J84DVB6"
|
||||
|
||||
- name: Build
|
||||
run: cmake --build build --config Release --parallel 4
|
||||
@@ -997,22 +984,12 @@ jobs:
|
||||
working-directory: build
|
||||
run: make install
|
||||
|
||||
- name: Remove problematic files
|
||||
run: |
|
||||
sudo rm -rf /usr/local/opt/qt6/share/qt/plugins/virtualkeyboard /usr/local/opt/qt6/share/qt/plugins/platforminputcontexts
|
||||
sudo rm -f /usr/local/Cellar/qt/*/share/qt/plugins/imageformats/libqpdf.dylib
|
||||
|
||||
- name: Manually copy files not handled by macdeployqt
|
||||
working-directory: build
|
||||
run: |
|
||||
mkdir -p strawberry.app/Contents/Frameworks/
|
||||
cp /usr/local/lib/{libsoup-3.0.0.dylib,libswresample.4.dylib,libswscale.7.dylib,libpostproc.57.dylib} strawberry.app/Contents/Frameworks/
|
||||
|
||||
- name: Deploy
|
||||
env:
|
||||
GIO_EXTRA_MODULES: /usr/local/lib/gio/modules
|
||||
GST_PLUGIN_SCANNER: /usr/local/opt/gstreamer/libexec/gstreamer-1.0/gst-plugin-scanner
|
||||
GST_PLUGIN_PATH: /usr/local/lib/gstreamer-1.0
|
||||
GIO_EXTRA_MODULES: ${{env.prefix_path}}/lib/gio/modules
|
||||
GST_PLUGIN_SCANNER: ${{env.prefix_path}}/libexec/gstreamer-1.0/gst-plugin-scanner
|
||||
GST_PLUGIN_PATH: ${{env.prefix_path}}/lib/gstreamer-1.0
|
||||
LIBSOUP_LIBRARY_PATH: ${{env.prefix_path}}/lib/libsoup-3.0.0.dylib
|
||||
working-directory: build
|
||||
run: make deploy
|
||||
|
||||
@@ -1020,119 +997,54 @@ jobs:
|
||||
working-directory: build
|
||||
run: make deploycheck
|
||||
|
||||
- name: Verify code-signing
|
||||
working-directory: build
|
||||
run: codesign --deep -v strawberry.app
|
||||
|
||||
- name: Create DMG
|
||||
working-directory: build
|
||||
run: make dmg
|
||||
|
||||
- name: Set is release
|
||||
run: echo "is_release=$(grep '^\s*set\s*(\s*INCLUDE_GIT_REVISION\s\+OFF\s*)\s*$' cmake/Version.cmake >/dev/null 2>&1 && echo 1 || echo 0)" >> $GITHUB_ENV
|
||||
|
||||
build-macos-macports:
|
||||
name: Build macOS Macports
|
||||
runs-on: macos-11
|
||||
steps:
|
||||
- name: Install macports
|
||||
- name: Set Upload path
|
||||
run: |
|
||||
macports_version=$(wget -q -O- 'https://github.com/macports/macports-base' | sed -n 's,.*releases/tag/\([^"&;]*\)".*,\1,p' | sed 's/^v//g' | sort -V | tail -1)
|
||||
wget https://github.com/macports/macports-base/releases/download/v${macports_version}/MacPorts-${macports_version}-11-BigSur.pkg
|
||||
sudo installer -pkg ./MacPorts-${macports_version}-11-BigSur.pkg -target /
|
||||
if [ "${{env.is_release}}" = "1" ]; then
|
||||
echo "upload_path=${{secrets.DOWNLOADS_PATH}}/stable_releases/macos" >> $GITHUB_ENV
|
||||
else
|
||||
echo "upload_path=${{secrets.DOWNLOADS_PATH}}/development_releases/macos" >> $GITHUB_ENV
|
||||
fi
|
||||
|
||||
- name: Uninstall homebrew
|
||||
run: |
|
||||
curl -sLO https://raw.githubusercontent.com/Homebrew/install/master/uninstall.sh
|
||||
chmod +x ./uninstall.sh
|
||||
sudo ./uninstall.sh --force
|
||||
- name: Create server path
|
||||
run: ssh -p ${{secrets.SSH_PORT}} -o StrictHostKeyChecking=no ${{secrets.SSH_USER}}@${{secrets.SSH_HOST}} mkdir -p ${{env.upload_path}}
|
||||
|
||||
- name: Update macports
|
||||
run: sudo /opt/local/bin/port -v selfupdate
|
||||
|
||||
- name: Install packages
|
||||
run: sudo /opt/local/bin/port -N -p install wget gettext glib2 pkgconfig cmake boost protobuf-cpp sqlite3 chromaprint libebur128 fftw taglib libcdio libmtp
|
||||
|
||||
- name: Install gstreamer
|
||||
run: sudo /opt/local/bin/port -N -p install gstreamer1 gstreamer1-gst-plugins-base gstreamer1-gst-plugins-good gstreamer1-gst-plugins-bad gstreamer1-gst-plugins-ugly gstreamer1-gst-libav
|
||||
|
||||
- name: Install gst-plugins-bad
|
||||
run: sudo /opt/local/bin/port -N -p install gstreamer1-gst-plugins-bad +faac
|
||||
|
||||
- name: Install qt6-qtbase
|
||||
run: sudo /opt/local/bin/port -N -p install qt6-qtbase
|
||||
|
||||
- name: Install qt6-qttools
|
||||
run: sudo /opt/local/bin/port -N -p install qt6-qttools || true
|
||||
|
||||
- name: Install qt6-sqlite-plugin
|
||||
run: sudo /opt/local/bin/port -N -p install qt6-sqlite-plugin
|
||||
|
||||
- name: Install libgpod
|
||||
run: sudo /opt/local/bin/port -N -p install libgpod || true
|
||||
|
||||
- name: Add /opt/local/bin to PATH
|
||||
run: echo "/opt/local/bin" >> $GITHUB_PATH
|
||||
|
||||
- name: Install create-dmg
|
||||
shell: bash
|
||||
run: |
|
||||
mkdir -p ~/build
|
||||
cd ~/build
|
||||
git clone https://github.com/create-dmg/create-dmg
|
||||
cd create-dmg
|
||||
sudo make install
|
||||
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Create Build Environment
|
||||
run: /opt/local/bin/cmake -E make_directory build
|
||||
|
||||
- name: Configure CMake
|
||||
env:
|
||||
MACOSX_DEPLOYMENT_TARGET: 11.0
|
||||
PKG_CONFIG_PATH: /opt/local/lib/pkgconfig
|
||||
run: /opt/local/libexec/qt6/bin/qt-cmake -S . -B build -DCMAKE_BUILD_TYPE=Release -DBUILD_WITH_QT6=ON -DBUILD_WERROR=OFF -DUSE_BUNDLE=ON -DENABLE_DBUS=OFF
|
||||
|
||||
- name: Build
|
||||
run: /opt/local/bin/cmake --build build --config Release --parallel 4
|
||||
|
||||
- name: Install
|
||||
working-directory: build
|
||||
run: make install
|
||||
|
||||
- name: Deploy
|
||||
env:
|
||||
GIO_EXTRA_MODULES: /opt/local/lib/gio/modules
|
||||
GST_PLUGIN_SCANNER: /opt/local/libexec/gstreamer-1.0/gst-plugin-scanner
|
||||
GST_PLUGIN_PATH: /opt/local/lib/gstreamer-1.0
|
||||
working-directory: build
|
||||
run: make deploy
|
||||
|
||||
- name: Deploy check
|
||||
working-directory: build
|
||||
run: make deploycheck
|
||||
|
||||
- name: Create DMG
|
||||
working-directory: build
|
||||
run: make dmg
|
||||
- name: rsync
|
||||
run: rsync -e "ssh -p ${{secrets.SSH_PORT}} -o StrictHostKeyChecking=no" -var build/*.dmg ${{secrets.SSH_USER}}@${{secrets.SSH_HOST}}:${{env.upload_path}}/
|
||||
|
||||
|
||||
build-windows-mingw:
|
||||
name: Build Windows MinGW
|
||||
if: github.repository != 'strawberrymusicplayer/strawberry-private'
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
arch: [ 'i686', 'x86_64' ]
|
||||
build_type: [ 'debug', 'release' ]
|
||||
buildtype: [ 'debug', 'release' ]
|
||||
container:
|
||||
image: jonaski/strawberry-mxe-${{matrix.arch}}-${{matrix.build_type}}
|
||||
image: jonaski/strawberry-mxe-${{matrix.arch}}-${{matrix.buildtype}}
|
||||
steps:
|
||||
- name: Install rsync
|
||||
run: zypper -n --gpg-auto-import-keys in rsync
|
||||
|
||||
- name: Set cmake buildtype
|
||||
shell: bash
|
||||
run: echo "cmake_buildtype=$(echo ${{matrix.buildtype}} | sed 's/.*/\u&/')" >> $GITHUB_ENV
|
||||
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
submodules: recursive
|
||||
|
||||
- name: Add safe git directory
|
||||
run: git config --global --add safe.directory ${GITHUB_WORKSPACE}
|
||||
@@ -1143,14 +1055,6 @@ jobs:
|
||||
- name: Link MXE directory
|
||||
run: ln -s /strawberry-mxe ~/mxe-shared
|
||||
|
||||
- name: Set ENABLE_WIN32_CONSOLE (debug)
|
||||
if: matrix.build_type == 'debug'
|
||||
run: echo "win32_console=ON" >> $GITHUB_ENV
|
||||
|
||||
- name: Set ENABLE_WIN32_CONSOLE (release)
|
||||
if: matrix.build_type == 'release'
|
||||
run: echo "win32_console=OFF" >> $GITHUB_ENV
|
||||
|
||||
- name: Run CMake
|
||||
env:
|
||||
PKG_CONFIG_PATH: /strawberry-mxe/usr/${{matrix.arch}}-w64-mingw32.shared/lib/pkgconfig
|
||||
@@ -1159,12 +1063,12 @@ jobs:
|
||||
-S .
|
||||
-B build
|
||||
-DCMAKE_TOOLCHAIN_FILE="../cmake/Toolchain-${{matrix.arch}}-w64-mingw32-shared.cmake"
|
||||
-DCMAKE_BUILD_TYPE="${{matrix.build_type}}"
|
||||
-DCMAKE_BUILD_TYPE="${{env.cmake_buildtype}}"
|
||||
-DCMAKE_PREFIX_PATH="/strawberry-mxe/usr/${{matrix.arch}}-w64-mingw32.shared/qt6"
|
||||
-DBUILD_WITH_QT6=ON
|
||||
-DBUILD_WERROR=OFF
|
||||
-DARCH="${{matrix.arch}}"
|
||||
-DENABLE_WIN32_CONSOLE=${{env.win32_console}}
|
||||
-DENABLE_WIN32_CONSOLE=$(test "${{matrix.buildtype}}" = "debug" && echo "ON" || echo "OFF")
|
||||
-DENABLE_DBUS=OFF
|
||||
-DENABLE_LIBGPOD=OFF
|
||||
-DENABLE_LIBMTP=OFF
|
||||
@@ -1172,7 +1076,7 @@ jobs:
|
||||
-DProtobuf_PROTOC_EXECUTABLE="/strawberry-mxe/usr/x86_64-pc-linux-gnu/bin/protoc"
|
||||
|
||||
- name: Run Make
|
||||
run: cmake --build build --config Release --parallel $(nproc)
|
||||
run: cmake --build build --config "${{env.cmake_buildtype}}" --parallel $(nproc)
|
||||
|
||||
- name: Create directories
|
||||
working-directory: build
|
||||
@@ -1211,7 +1115,7 @@ jobs:
|
||||
run: cp /strawberry-mxe/usr/${{matrix.arch}}-w64-mingw32.shared/bin/{sqlite3.exe,gst-launch-1.0.exe,gst-play-1.0.exe,gst-discoverer-1.0.exe,libsoup-3.0-0.dll,libnghttp2.dll} .
|
||||
|
||||
- name: Copy extra binaries (debug)
|
||||
if: matrix.build_type == 'debug'
|
||||
if: matrix.buildtype == 'debug'
|
||||
working-directory: build
|
||||
run: cp /strawberry-mxe/usr/${{matrix.arch}}-w64-mingw32.shared/bin/{gdb.exe,libreadline8.dll} .
|
||||
|
||||
@@ -1231,12 +1135,12 @@ jobs:
|
||||
-R /strawberry-mxe/usr/${{matrix.arch}}-w64-mingw32.shared
|
||||
|
||||
- name: Strip binaries
|
||||
if: matrix.build_type == 'release'
|
||||
if: matrix.buildtype == 'release'
|
||||
working-directory: build
|
||||
run: find . -type f \( -iname \*.dll -o -iname \*.exe \) -exec /strawberry-mxe/usr/bin/${{matrix.arch}}-w64-mingw32.shared-strip {} \;
|
||||
|
||||
- name: Strip gdb.exe
|
||||
if: matrix.build_type == 'debug'
|
||||
if: matrix.buildtype == 'debug'
|
||||
working-directory: build
|
||||
run: /strawberry-mxe/usr/bin/${{matrix.arch}}-w64-mingw32.shared-strip gdb.exe
|
||||
|
||||
@@ -1290,37 +1194,42 @@ jobs:
|
||||
path: build/StrawberrySetup*.exe
|
||||
|
||||
- name: SSH key setup
|
||||
if: github.ref == 'refs/heads/master' || github.ref == 'refs/heads/ci'
|
||||
if: github.repository == 'strawberrymusicplayer/strawberry' && github.event.pull_request.head.repo.fork == false && (github.ref == 'refs/heads/master' || github.ref == 'refs/heads/ci')
|
||||
uses: shimataro/ssh-key-action@v2
|
||||
with:
|
||||
known_hosts: ${{secrets.SSH_KNOWN_HOSTS}}
|
||||
key: ${{ secrets.SSH_KEY }}
|
||||
|
||||
- name: Create server path
|
||||
if: github.ref == 'refs/heads/master' || github.ref == 'refs/heads/ci'
|
||||
if: github.repository == 'strawberrymusicplayer/strawberry' && github.event.pull_request.head.repo.fork == false && (github.ref == 'refs/heads/master' || github.ref == 'refs/heads/ci')
|
||||
run: ssh -p ${{secrets.SSH_PORT}} -o StrictHostKeyChecking=no ${{secrets.SSH_USER}}@${{secrets.SSH_HOST}} mkdir -p ${{secrets.BUILDS_PATH}}/windows/mingw
|
||||
|
||||
- name: rsync
|
||||
if: github.ref == 'refs/heads/master' || github.ref == 'refs/heads/ci'
|
||||
if: github.repository == 'strawberrymusicplayer/strawberry' && github.event.pull_request.head.repo.fork == false && (github.ref == 'refs/heads/master' || github.ref == 'refs/heads/ci')
|
||||
run: rsync -e "ssh -p ${{secrets.SSH_PORT}} -o StrictHostKeyChecking=no" -var build/StrawberrySetup*.exe ${{secrets.SSH_USER}}@${{secrets.SSH_HOST}}:${{secrets.BUILDS_PATH}}/windows/mingw/
|
||||
|
||||
|
||||
build-windows-msvc:
|
||||
name: Build Windows MSVC
|
||||
if: github.repository != 'strawberrymusicplayer/strawberry-private'
|
||||
runs-on: windows-2022
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
arch: [ 'x86', 'x86_64' ]
|
||||
build_type: [ 'debug', 'release' ]
|
||||
buildtype: [ 'debug', 'release' ]
|
||||
steps:
|
||||
|
||||
- name: Set prefix path
|
||||
shell: bash
|
||||
run: |
|
||||
echo "prefix_path_backslash=c:\strawberry_msvc_${{matrix.arch}}_${{matrix.build_type}}" >> $GITHUB_ENV
|
||||
echo "prefix_path_forwardslash=c:/strawberry_msvc_${{matrix.arch}}_${{matrix.build_type}}" >> $GITHUB_ENV
|
||||
echo "prefix_path_unix=/c/strawberry_msvc_${{matrix.arch}}_${{matrix.build_type}}" >> $GITHUB_ENV
|
||||
echo "prefix_path_backslash=c:\strawberry_msvc_${{matrix.arch}}_${{matrix.buildtype}}" >> $GITHUB_ENV
|
||||
echo "prefix_path_forwardslash=c:/strawberry_msvc_${{matrix.arch}}_${{matrix.buildtype}}" >> $GITHUB_ENV
|
||||
echo "prefix_path_unix=/c/strawberry_msvc_${{matrix.arch}}_${{matrix.buildtype}}" >> $GITHUB_ENV
|
||||
|
||||
- name: Set cmake buildtype
|
||||
shell: bash
|
||||
run: echo "cmake_buildtype=$(echo ${{matrix.buildtype}} | sed 's/.*/\u&/')" >> $GITHUB_ENV
|
||||
|
||||
- name: Create downloads directory
|
||||
shell: cmd
|
||||
@@ -1329,12 +1238,12 @@ jobs:
|
||||
- name: Download Windows MSVC dependencies
|
||||
shell: cmd
|
||||
working-directory: downloads
|
||||
run: curl -f -O -L https://github.com/strawberrymusicplayer/strawberry-msvc-dependencies/releases/latest/download/strawberry-msvc-${{matrix.arch}}-${{matrix.build_type}}.tar.xz
|
||||
run: curl -f -O -L https://github.com/strawberrymusicplayer/strawberry-msvc-dependencies/releases/latest/download/strawberry-msvc-${{matrix.arch}}-${{matrix.buildtype}}.tar.xz
|
||||
|
||||
- name: Extract Windows MSVC dependencies
|
||||
shell: bash
|
||||
working-directory: downloads
|
||||
run: tar -C /c -xf strawberry-msvc-${{matrix.arch}}-${{matrix.build_type}}.tar.xz
|
||||
run: tar -C /c -xf strawberry-msvc-${{matrix.arch}}-${{matrix.buildtype}}.tar.xz
|
||||
|
||||
- name: Update PATH
|
||||
run: echo "${{env.prefix_path_backslash}}\bin" | Out-File -FilePath $env:GITHUB_PATH -Encoding utf8 -Append
|
||||
@@ -1343,19 +1252,15 @@ jobs:
|
||||
shell: bash
|
||||
run: cp /c/strawberry/c/bin/{patch.exe,strip.exe,strings.exe,objdump.exe} ${{env.prefix_path_unix}}/bin
|
||||
|
||||
- name: Uninstall conflicting mingw
|
||||
shell: cmd
|
||||
run: choco uninstall mingw -y -f
|
||||
|
||||
- name: Delete conflicting files
|
||||
shell: bash
|
||||
run: rm -rf /c/strawberry/c "/c/program files/OpenSSL" "/c/program files/postgresql"
|
||||
run: rm -rf /c/msys64 /c/mingw32 /c/mingw64 /c/strawberry/c "/c/program files/OpenSSL" "/c/program files/postgresql"
|
||||
|
||||
- name: Delete conflicting icu
|
||||
shell: bash
|
||||
run: |
|
||||
find "/c/program files (x86)/windows kits/" -name 'icu*.lib' -delete
|
||||
find "/c/program files (x86)/windows kits/" -name 'icu*.h' -delete
|
||||
find "/c/program files (x86)/windows kits/" -iname 'icu*.lib' -delete
|
||||
find "/c/program files (x86)/windows kits/" -iname 'icu*.h' -delete
|
||||
|
||||
- name: Download NSIS LockedList plugin
|
||||
shell: cmd
|
||||
@@ -1409,6 +1314,7 @@ jobs:
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
submodules: recursive
|
||||
|
||||
- name: Add safe git directory
|
||||
shell: bash
|
||||
@@ -1435,10 +1341,9 @@ jobs:
|
||||
-S .
|
||||
-B build
|
||||
-G "Ninja"
|
||||
-DCMAKE_BUILD_TYPE="${{matrix.build_type}}"
|
||||
-DCMAKE_BUILD_TYPE="${{env.cmake_buildtype}}"
|
||||
-DCMAKE_PREFIX_PATH="${{env.prefix_path_forwardslash}}/lib/cmake"
|
||||
-DBUILD_WITH_QT6=ON
|
||||
-DBUILD_WERROR=OFF
|
||||
-DARCH="${{matrix.arch}}"
|
||||
-DENABLE_WIN32_CONSOLE=${{env.win32_console}}
|
||||
-DUSE_TAGLIB=ON
|
||||
@@ -1449,7 +1354,7 @@ jobs:
|
||||
shell: cmd
|
||||
env:
|
||||
CL: "/MP"
|
||||
run: cmake --build build --config ${{matrix.build_type}} --parallel 4
|
||||
run: cmake --build build --config "${{env.cmake_buildtype}}" --parallel 4
|
||||
|
||||
- name: Copy extra binaries
|
||||
shell: cmd
|
||||
@@ -1651,8 +1556,8 @@ jobs:
|
||||
|
||||
rsync-windows-msvc-builds:
|
||||
name: Rsync Windows MSVC builds
|
||||
if: github.repository == 'strawberrymusicplayer/strawberry' && github.event.pull_request.head.repo.fork == false && (github.ref == 'refs/heads/master' || github.ref == 'refs/heads/ci')
|
||||
runs-on: ubuntu-latest
|
||||
if: github.ref == 'refs/heads/master' || github.ref == 'refs/heads/ci'
|
||||
needs:
|
||||
- build-windows-msvc
|
||||
steps:
|
||||
@@ -1680,8 +1585,8 @@ jobs:
|
||||
|
||||
upload-release:
|
||||
name: Upload release
|
||||
if: github.repository == 'strawberrymusicplayer/strawberry' && github.event.pull_request.head.repo.fork == false && (github.ref == 'refs/heads/master' || github.ref == 'refs/heads/ci')
|
||||
runs-on: ubuntu-latest
|
||||
if: github.ref == 'refs/heads/master' || github.ref == 'refs/heads/ci'
|
||||
needs:
|
||||
- build-opensuse
|
||||
- build-fedora
|
||||
@@ -1691,8 +1596,11 @@ jobs:
|
||||
- build-windows-mingw
|
||||
- build-windows-msvc
|
||||
steps:
|
||||
- name: Install rsync
|
||||
run: sudo apt install -y rsync
|
||||
|
||||
- name: Install packages
|
||||
env:
|
||||
DEBIAN_FRONTEND: noninteractive
|
||||
run: sudo apt install -y git rsync hub
|
||||
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
|
||||
4
.gitmodules
vendored
Normal file
4
.gitmodules
vendored
Normal file
@@ -0,0 +1,4 @@
|
||||
[submodule "3rdparty/kdsingleapplication/KDSingleApplication"]
|
||||
path = 3rdparty/kdsingleapplication/KDSingleApplication
|
||||
url = https://github.com/KDAB/KDSingleApplication.git
|
||||
branch = master
|
||||
4
3rdparty/kdsingleapplication/CMakeLists.txt
vendored
4
3rdparty/kdsingleapplication/CMakeLists.txt
vendored
@@ -1,6 +1,6 @@
|
||||
cmake_minimum_required(VERSION 3.7)
|
||||
set(SOURCES kdsingleapplication.cpp kdsingleapplication_localsocket.cpp)
|
||||
set(HEADERS kdsingleapplication.h kdsingleapplication_localsocket_p.h)
|
||||
set(SOURCES KDSingleApplication/src/kdsingleapplication.cpp KDSingleApplication/src/kdsingleapplication_localsocket.cpp)
|
||||
set(HEADERS KDSingleApplication/src/kdsingleapplication.h KDSingleApplication/src/kdsingleapplication_localsocket_p.h)
|
||||
qt_wrap_cpp(MOC ${HEADERS})
|
||||
add_library(kdsingleapplication STATIC ${SOURCES} ${MOC})
|
||||
target_compile_definitions(kdsingleapplication PRIVATE -DKDSINGLEAPPLICATION_STATIC_BUILD)
|
||||
|
||||
1
3rdparty/kdsingleapplication/KDSingleApplication
vendored
Submodule
1
3rdparty/kdsingleapplication/KDSingleApplication
vendored
Submodule
Submodule 3rdparty/kdsingleapplication/KDSingleApplication added at ffce501f45
6
3rdparty/kdsingleapplication/LICENSE
vendored
6
3rdparty/kdsingleapplication/LICENSE
vendored
@@ -1,6 +0,0 @@
|
||||
KDSingleApplication is (C) 2019-2023, Klarälvdalens Datakonsult AB,
|
||||
and is available under the terms of the MIT license.
|
||||
|
||||
See the full license text in the LICENSES folder.
|
||||
|
||||
Contact KDAB at <info@kdab.com> to inquire about commercial licensing.
|
||||
@@ -1,11 +0,0 @@
|
||||
Copyright (c) <year> <owner>.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
|
||||
|
||||
1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
|
||||
|
||||
2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
|
||||
|
||||
3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
@@ -1,9 +0,0 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) <year> <copyright holders>
|
||||
|
||||
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.
|
||||
53
3rdparty/kdsingleapplication/README.md
vendored
53
3rdparty/kdsingleapplication/README.md
vendored
@@ -1,53 +0,0 @@
|
||||
# KDSingleApplication
|
||||
|
||||
`KDSingleApplication` is a helper class for single-instance policy applications
|
||||
written by [KDAB](https://www.kdab.com).
|
||||
|
||||
## Usage
|
||||
|
||||
Currently the documentation is woefully lacking, but see the examples or tests
|
||||
for inspiration. Basically it involves:
|
||||
|
||||
1. Create a `Q(Core|Gui)Application` object.
|
||||
2. Create a `KDSingleApplication` object.
|
||||
3. Check if the current instance is *primary* (or "master") or
|
||||
*secondary* (or "slave") by calling `isPrimaryInstance`:
|
||||
* the *primary* instance needs to listen from messages coming from the
|
||||
secondary instances, by connecting a slot to the `messageReceived` signal;
|
||||
* the *secondary* instances can send messages to the primary instance
|
||||
by calling `sendMessage`.
|
||||
|
||||
## Licensing
|
||||
|
||||
KDSingleApplication is (C) 2019-2023, Klarälvdalens Datakonsult AB, and is available
|
||||
under the terms of the [MIT license](LICENSES/MIT.txt).
|
||||
|
||||
Contact KDAB at <info@kdab.com> if you need different licensing options.
|
||||
|
||||
## Get Involved
|
||||
|
||||
KDAB will happily accept external contributions.
|
||||
|
||||
Please submit your contributions or issue reports from our GitHub space at
|
||||
<https://github.com/KDAB/KDSingleApplication>.
|
||||
|
||||
## About KDAB
|
||||
|
||||
KDSingleApplication is supported and maintained by Klarälvdalens Datakonsult AB (KDAB).
|
||||
|
||||
The KDAB Group is the global No.1 software consultancy for Qt, C++ and
|
||||
OpenGL applications across desktop, embedded and mobile platforms.
|
||||
|
||||
The KDAB Group provides consulting and mentoring for developing Qt applications
|
||||
from scratch and in porting from all popular and legacy frameworks to Qt.
|
||||
We continue to help develop parts of Qt and are one of the major contributors
|
||||
to the Qt Project. We can give advanced or standard trainings anywhere
|
||||
around the globe on Qt as well as C++, OpenGL, 3D and more.
|
||||
|
||||
Please visit <https://www.kdab.com> to meet the people who write code like this.
|
||||
|
||||
Stay up-to-date with KDAB product announcements:
|
||||
|
||||
* [KDAB Newsletter](https://news.kdab.com)
|
||||
* [KDAB Blogs](https://www.kdab.com/category/blogs)
|
||||
* [KDAB on Twitter](https://twitter.com/KDABQt)
|
||||
106
3rdparty/kdsingleapplication/kdsingleapplication.cpp
vendored
106
3rdparty/kdsingleapplication/kdsingleapplication.cpp
vendored
@@ -1,106 +0,0 @@
|
||||
/*
|
||||
This file is part of KDSingleApplication.
|
||||
|
||||
SPDX-FileCopyrightText: 2019-2023 Klarälvdalens Datakonsult AB, a KDAB Group company <info@kdab.com>
|
||||
|
||||
SPDX-License-Identifier: MIT
|
||||
|
||||
Contact KDAB at <info@kdab.com> for commercial licensing options.
|
||||
*/
|
||||
|
||||
#include "kdsingleapplication.h"
|
||||
|
||||
#include <QtCore/QCoreApplication>
|
||||
#include <QtCore/QFileInfo>
|
||||
|
||||
// TODO: make this pluggable.
|
||||
#include "kdsingleapplication_localsocket_p.h"
|
||||
|
||||
// Avoiding dragging in Qt private APIs for now, so this does not inherit
|
||||
// from QObjectPrivate.
|
||||
class KDSingleApplicationPrivate
|
||||
{
|
||||
public:
|
||||
explicit KDSingleApplicationPrivate(const QString &name, KDSingleApplication *q);
|
||||
|
||||
QString name() const
|
||||
{
|
||||
return m_name;
|
||||
}
|
||||
|
||||
bool isPrimaryInstance() const
|
||||
{
|
||||
return m_impl.isPrimaryInstance();
|
||||
}
|
||||
|
||||
bool sendMessage(const QByteArray &message, int timeout)
|
||||
{
|
||||
return m_impl.sendMessage(message, timeout);
|
||||
}
|
||||
|
||||
private:
|
||||
Q_DECLARE_PUBLIC(KDSingleApplication)
|
||||
|
||||
KDSingleApplication *q_ptr;
|
||||
QString m_name;
|
||||
|
||||
KDSingleApplicationLocalSocket m_impl;
|
||||
};
|
||||
|
||||
KDSingleApplicationPrivate::KDSingleApplicationPrivate(const QString &name, KDSingleApplication *q)
|
||||
: q_ptr(q)
|
||||
, m_name(name)
|
||||
, m_impl(name)
|
||||
{
|
||||
if (Q_UNLIKELY(name.isEmpty()))
|
||||
qFatal("KDSingleApplication requires a non-empty application name");
|
||||
|
||||
if (isPrimaryInstance()) {
|
||||
QObject::connect(&m_impl, &KDSingleApplicationLocalSocket::messageReceived,
|
||||
q, &KDSingleApplication::messageReceived);
|
||||
}
|
||||
}
|
||||
|
||||
static QString extractExecutableName(const QString &applicationFilePath)
|
||||
{
|
||||
return QFileInfo(applicationFilePath).fileName();
|
||||
}
|
||||
|
||||
KDSingleApplication::KDSingleApplication(QObject *parent)
|
||||
: KDSingleApplication(extractExecutableName(QCoreApplication::applicationFilePath()), parent)
|
||||
{
|
||||
}
|
||||
|
||||
KDSingleApplication::KDSingleApplication(const QString &name, QObject *parent)
|
||||
: QObject(parent)
|
||||
, d_ptr(new KDSingleApplicationPrivate(name, this))
|
||||
{
|
||||
}
|
||||
|
||||
QString KDSingleApplication::name() const
|
||||
{
|
||||
Q_D(const KDSingleApplication);
|
||||
return d->name();
|
||||
}
|
||||
|
||||
bool KDSingleApplication::isPrimaryInstance() const
|
||||
{
|
||||
Q_D(const KDSingleApplication);
|
||||
return d->isPrimaryInstance();
|
||||
}
|
||||
|
||||
bool KDSingleApplication::sendMessage(const QByteArray &message)
|
||||
{
|
||||
return sendMessageWithTimeout(message, 5000);
|
||||
}
|
||||
|
||||
bool KDSingleApplication::sendMessageWithTimeout(const QByteArray &message, int timeout)
|
||||
{
|
||||
Q_ASSERT(!isPrimaryInstance());
|
||||
|
||||
Q_D(KDSingleApplication);
|
||||
return d->sendMessage(message, timeout);
|
||||
}
|
||||
|
||||
|
||||
KDSingleApplication::~KDSingleApplication() = default;
|
||||
@@ -1,48 +0,0 @@
|
||||
/*
|
||||
This file is part of KDSingleApplication.
|
||||
|
||||
SPDX-FileCopyrightText: 2019-2023 Klarälvdalens Datakonsult AB, a KDAB Group company <info@kdab.com>
|
||||
|
||||
SPDX-License-Identifier: MIT
|
||||
|
||||
Contact KDAB at <info@kdab.com> for commercial licensing options.
|
||||
*/
|
||||
#ifndef KDSINGLEAPPLICATION_H
|
||||
#define KDSINGLEAPPLICATION_H
|
||||
|
||||
#include <QtCore/QObject>
|
||||
|
||||
#include <memory>
|
||||
|
||||
#include "kdsingleapplication_lib.h"
|
||||
|
||||
class KDSingleApplicationPrivate;
|
||||
|
||||
class KDSINGLEAPPLICATION_EXPORT KDSingleApplication : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
Q_PROPERTY(QString name READ name CONSTANT)
|
||||
Q_PROPERTY(bool isPrimaryInstance READ isPrimaryInstance CONSTANT)
|
||||
|
||||
public:
|
||||
explicit KDSingleApplication(QObject *parent = nullptr);
|
||||
explicit KDSingleApplication(const QString &name, QObject *parent = nullptr);
|
||||
~KDSingleApplication();
|
||||
|
||||
QString name() const;
|
||||
bool isPrimaryInstance() const;
|
||||
|
||||
public Q_SLOTS:
|
||||
// avoid default arguments and overloads, as they don't mix with connections
|
||||
bool sendMessage(const QByteArray &message);
|
||||
bool sendMessageWithTimeout(const QByteArray &message, int timeout);
|
||||
|
||||
Q_SIGNALS:
|
||||
void messageReceived(const QByteArray &message);
|
||||
|
||||
private:
|
||||
Q_DECLARE_PRIVATE(KDSingleApplication)
|
||||
std::unique_ptr<KDSingleApplicationPrivate> d_ptr;
|
||||
};
|
||||
|
||||
#endif // KDSINGLEAPPLICATION_H
|
||||
@@ -1,23 +0,0 @@
|
||||
/*
|
||||
This file is part of KDSingleApplication.
|
||||
|
||||
SPDX-FileCopyrightText: 2019-2023 Klarälvdalens Datakonsult AB, a KDAB Group company <info@kdab.com>
|
||||
|
||||
SPDX-License-Identifier: MIT
|
||||
|
||||
Contact KDAB at <info@kdab.com> for commercial licensing options.
|
||||
*/
|
||||
#ifndef KDSINGLEAPPLICATION_LIB_H
|
||||
#define KDSINGLEAPPLICATION_LIB_H
|
||||
|
||||
#include <QtCore/QtGlobal>
|
||||
|
||||
#if defined(KDSINGLEAPPLICATION_STATIC_BUILD)
|
||||
#define KDSINGLEAPPLICATION_EXPORT
|
||||
#elif defined(KDSINGLEAPPLICATION_SHARED_BUILD)
|
||||
#define KDSINGLEAPPLICATION_EXPORT Q_DECL_EXPORT
|
||||
#else
|
||||
#define KDSINGLEAPPLICATION_EXPORT Q_DECL_IMPORT
|
||||
#endif
|
||||
|
||||
#endif // KDSINGLEAPPLICATION_LIB_H
|
||||
@@ -1,324 +0,0 @@
|
||||
/*
|
||||
This file is part of KDSingleApplication.
|
||||
|
||||
SPDX-FileCopyrightText: 2019-2023 Klarälvdalens Datakonsult AB, a KDAB Group company <info@kdab.com>
|
||||
|
||||
SPDX-License-Identifier: MIT
|
||||
|
||||
Contact KDAB at <info@kdab.com> for commercial licensing options.
|
||||
*/
|
||||
|
||||
#include "kdsingleapplication_localsocket_p.h"
|
||||
|
||||
#include <QtCore/QDir>
|
||||
#include <QtCore/QDeadlineTimer>
|
||||
#include <QtCore/QTimer>
|
||||
#include <QtCore/QLockFile>
|
||||
#include <QtCore/QDataStream>
|
||||
|
||||
#include <QtCore/QtDebug>
|
||||
#include <QtCore/QLoggingCategory>
|
||||
|
||||
#include <QtNetwork/QLocalServer>
|
||||
#include <QtNetwork/QLocalSocket>
|
||||
|
||||
#include <chrono>
|
||||
#include <algorithm>
|
||||
|
||||
#ifdef Q_OS_UNIX
|
||||
// for ::getuid()
|
||||
# include <sys/types.h>
|
||||
# include <unistd.h>
|
||||
#endif
|
||||
|
||||
#ifdef Q_OS_WIN
|
||||
# include <qt_windows.h>
|
||||
#endif
|
||||
|
||||
namespace {
|
||||
static constexpr auto LOCALSOCKET_CONNECTION_TIMEOUT = std::chrono::seconds(5);
|
||||
static constexpr char LOCALSOCKET_PROTOCOL_VERSION = 2;
|
||||
} // namespace
|
||||
|
||||
#ifdef __GNUC__
|
||||
#pragma GCC diagnostic push
|
||||
#pragma GCC diagnostic ignored "-Wmissing-declarations"
|
||||
#endif
|
||||
Q_LOGGING_CATEGORY(kdsaLocalSocket, "kdsingleapplication.localsocket", QtWarningMsg)
|
||||
#ifdef __GNUC__
|
||||
#pragma GCC diagnostic pop
|
||||
#endif
|
||||
|
||||
#ifdef __clang__
|
||||
#pragma clang diagnostic push
|
||||
#pragma clang diagnostic ignored "-Wgnu-zero-variadic-macro-arguments"
|
||||
#endif
|
||||
|
||||
KDSingleApplicationLocalSocket::KDSingleApplicationLocalSocket(const QString &name, QObject *parent)
|
||||
: QObject(parent)
|
||||
{
|
||||
#if defined(Q_OS_UNIX)
|
||||
/* cppcheck-suppress useInitializationList */
|
||||
m_socketName = QStringLiteral("kdsingleapp-%1-%2")
|
||||
.arg(::getuid())
|
||||
.arg(name);
|
||||
#elif defined(Q_OS_WIN)
|
||||
// I'm not sure of a "global session identifier" on Windows; are
|
||||
// multiple logins from the same user a possibility? For now, following this:
|
||||
// https://docs.microsoft.com/en-us/windows/desktop/devnotes/getting-the-session-id-of-the-current-process
|
||||
|
||||
DWORD sessionId;
|
||||
BOOL haveSessionId = ProcessIdToSessionId(GetCurrentProcessId(), &sessionId);
|
||||
|
||||
m_socketName = QString::fromUtf8("kdsingleapp-%1-%2")
|
||||
.arg(haveSessionId ? sessionId : 0)
|
||||
.arg(name);
|
||||
#else
|
||||
#error "KDSingleApplication has not been ported to this platform"
|
||||
#endif
|
||||
|
||||
const QString lockFilePath =
|
||||
QDir::tempPath() + QLatin1Char('/') + m_socketName + QLatin1String(".lock");
|
||||
|
||||
qCDebug(kdsaLocalSocket) << "Socket name is" << m_socketName;
|
||||
qCDebug(kdsaLocalSocket) << "Lock file path is" << lockFilePath;
|
||||
|
||||
std::unique_ptr<QLockFile> lockFile(new QLockFile(lockFilePath));
|
||||
lockFile->setStaleLockTime(0);
|
||||
|
||||
if (!lockFile->tryLock()) {
|
||||
// someone else has the lock => we're secondary
|
||||
qCDebug(kdsaLocalSocket) << "Secondary instance";
|
||||
return;
|
||||
}
|
||||
|
||||
qCDebug(kdsaLocalSocket) << "Primary instance";
|
||||
|
||||
std::unique_ptr<QLocalServer> server = std::make_unique<QLocalServer>();
|
||||
if (!server->listen(m_socketName)) {
|
||||
// maybe the primary crashed, leaving a stale socket; delete it and try again
|
||||
QLocalServer::removeServer(m_socketName);
|
||||
if (!server->listen(m_socketName)) {
|
||||
// TODO: better error handling.
|
||||
qWarning("KDSingleApplication: unable to make the primary instance listen on %ls: %ls",
|
||||
qUtf16Printable(m_socketName),
|
||||
qUtf16Printable(server->errorString()));
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
connect(server.get(), &QLocalServer::newConnection,
|
||||
this, &KDSingleApplicationLocalSocket::handleNewConnection);
|
||||
|
||||
m_lockFile = std::move(lockFile);
|
||||
m_localServer = std::move(server);
|
||||
}
|
||||
|
||||
KDSingleApplicationLocalSocket::~KDSingleApplicationLocalSocket() = default;
|
||||
|
||||
bool KDSingleApplicationLocalSocket::isPrimaryInstance() const
|
||||
{
|
||||
return m_localServer != nullptr;
|
||||
}
|
||||
|
||||
bool KDSingleApplicationLocalSocket::sendMessage(const QByteArray &message, int timeout)
|
||||
{
|
||||
Q_ASSERT(!isPrimaryInstance());
|
||||
QLocalSocket socket;
|
||||
|
||||
qCDebug(kdsaLocalSocket) << "Preparing to send message" << message << "with timeout" << timeout;
|
||||
|
||||
QDeadlineTimer deadline(timeout);
|
||||
|
||||
// There is an inherent race here with the setup of the server side.
|
||||
// Even if the socket lock is held by the server, the server may not
|
||||
// be listening yet. So this connection may fail; keep retrying
|
||||
// until we hit the timeout.
|
||||
do {
|
||||
socket.connectToServer(m_socketName);
|
||||
if (socket.waitForConnected(static_cast<int>(deadline.remainingTime())))
|
||||
break;
|
||||
} while (!deadline.hasExpired());
|
||||
|
||||
qCDebug(kdsaLocalSocket) << "Socket state:" << socket.state() << "Timer remaining" << deadline.remainingTime() << "Expired?" << deadline.hasExpired();
|
||||
|
||||
if (deadline.hasExpired()) {
|
||||
qCWarning(kdsaLocalSocket) << "Connection timed out";
|
||||
return false;
|
||||
}
|
||||
|
||||
socket.write(&LOCALSOCKET_PROTOCOL_VERSION, 1);
|
||||
|
||||
{
|
||||
QByteArray encodedMessage;
|
||||
QDataStream ds(&encodedMessage, QIODevice::WriteOnly);
|
||||
ds << message;
|
||||
socket.write(encodedMessage);
|
||||
}
|
||||
|
||||
qCDebug(kdsaLocalSocket) << "Wrote message in the socket"
|
||||
<< "Timer remaining" << deadline.remainingTime() << "Expired?" << deadline.hasExpired();
|
||||
|
||||
// There is no acknowledgement mechanism here.
|
||||
// Should there be one?
|
||||
|
||||
while (socket.bytesToWrite() > 0) {
|
||||
if (!socket.waitForBytesWritten(static_cast<int>(deadline.remainingTime()))) {
|
||||
qCWarning(kdsaLocalSocket) << "Message to primary timed out";
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
qCDebug(kdsaLocalSocket) << "Bytes written, now disconnecting"
|
||||
<< "Timer remaining" << deadline.remainingTime() << "Expired?" << deadline.hasExpired();
|
||||
|
||||
socket.disconnectFromServer();
|
||||
|
||||
if (socket.state() == QLocalSocket::UnconnectedState) {
|
||||
qCDebug(kdsaLocalSocket) << "Disconnected -- success!";
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!socket.waitForDisconnected(static_cast<int>(deadline.remainingTime()))) {
|
||||
qCWarning(kdsaLocalSocket) << "Disconnection from primary timed out";
|
||||
return false;
|
||||
}
|
||||
|
||||
qCDebug(kdsaLocalSocket) << "Disconnected -- success!";
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void KDSingleApplicationLocalSocket::handleNewConnection()
|
||||
{
|
||||
Q_ASSERT(m_localServer);
|
||||
|
||||
QLocalSocket *socket = nullptr;
|
||||
while ((socket = m_localServer->nextPendingConnection())) {
|
||||
qCDebug(kdsaLocalSocket) << "Got new connection on" << m_socketName << "state" << socket->state();
|
||||
|
||||
Connection c(socket);
|
||||
socket = c.socket.get();
|
||||
|
||||
c.readDataConnection = QObjectConnectionHolder(
|
||||
connect(socket, &QLocalSocket::readyRead,
|
||||
this, &KDSingleApplicationLocalSocket::readDataFromSecondary));
|
||||
|
||||
c.secondaryDisconnectedConnection = QObjectConnectionHolder(
|
||||
connect(socket, &QLocalSocket::disconnected,
|
||||
this, &KDSingleApplicationLocalSocket::secondaryDisconnected));
|
||||
|
||||
c.abortConnection = QObjectConnectionHolder(
|
||||
connect(c.timeoutTimer.get(), &QTimer::timeout,
|
||||
this, &KDSingleApplicationLocalSocket::abortConnectionToSecondary));
|
||||
|
||||
m_clients.push_back(std::move(c));
|
||||
|
||||
// Note that by the time we get here, the socket could've already been closed,
|
||||
// and no signals emitted (hello, Windows!). Read what's already in the socket.
|
||||
if (readDataFromSecondarySocket(socket))
|
||||
return;
|
||||
|
||||
if (socket->state() == QLocalSocket::UnconnectedState)
|
||||
secondarySocketDisconnected(socket);
|
||||
}
|
||||
}
|
||||
|
||||
template<typename Container>
|
||||
static auto findConnectionBySocket(Container &container, QLocalSocket *socket)
|
||||
{
|
||||
auto i = std::find_if(container.begin(),
|
||||
container.end(),
|
||||
[socket](const auto &c) { return c.socket.get() == socket; });
|
||||
Q_ASSERT(i != container.end());
|
||||
return i;
|
||||
}
|
||||
|
||||
template<typename Container>
|
||||
static auto findConnectionByTimer(Container &container, QTimer *timer)
|
||||
{
|
||||
auto i = std::find_if(container.begin(),
|
||||
container.end(),
|
||||
[timer](const auto &c) { return c.timeoutTimer.get() == timer; });
|
||||
Q_ASSERT(i != container.end());
|
||||
return i;
|
||||
}
|
||||
|
||||
void KDSingleApplicationLocalSocket::readDataFromSecondary()
|
||||
{
|
||||
QLocalSocket *socket = static_cast<QLocalSocket *>(sender());
|
||||
readDataFromSecondarySocket(socket);
|
||||
}
|
||||
|
||||
bool KDSingleApplicationLocalSocket::readDataFromSecondarySocket(QLocalSocket *socket)
|
||||
{
|
||||
auto i = findConnectionBySocket(m_clients, socket);
|
||||
Connection &c = *i;
|
||||
c.readData.append(socket->readAll());
|
||||
|
||||
qCDebug(kdsaLocalSocket) << "Got more data from a secondary. Data read so far:" << c.readData;
|
||||
|
||||
const QByteArray &data = c.readData;
|
||||
|
||||
if (data.size() >= 1) {
|
||||
if (data[0] != LOCALSOCKET_PROTOCOL_VERSION) {
|
||||
qCDebug(kdsaLocalSocket) << "Got an invalid protocol version";
|
||||
m_clients.erase(i);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
QDataStream ds(data);
|
||||
ds.skipRawData(1);
|
||||
|
||||
ds.startTransaction();
|
||||
QByteArray message;
|
||||
ds >> message;
|
||||
|
||||
if (ds.commitTransaction()) {
|
||||
qCDebug(kdsaLocalSocket) << "Got a complete message:" << message;
|
||||
Q_EMIT messageReceived(message);
|
||||
m_clients.erase(i);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void KDSingleApplicationLocalSocket::secondaryDisconnected()
|
||||
{
|
||||
QLocalSocket *socket = static_cast<QLocalSocket *>(sender());
|
||||
secondarySocketDisconnected(socket);
|
||||
}
|
||||
|
||||
void KDSingleApplicationLocalSocket::secondarySocketDisconnected(QLocalSocket *socket)
|
||||
{
|
||||
auto i = findConnectionBySocket(m_clients, socket);
|
||||
Connection c = std::move(*i);
|
||||
m_clients.erase(i);
|
||||
|
||||
qCDebug(kdsaLocalSocket) << "Secondary disconnected. Data read:" << c.readData;
|
||||
}
|
||||
|
||||
void KDSingleApplicationLocalSocket::abortConnectionToSecondary()
|
||||
{
|
||||
QTimer *timer = static_cast<QTimer *>(sender());
|
||||
|
||||
auto i = findConnectionByTimer(m_clients, timer);
|
||||
Connection c = std::move(*i);
|
||||
m_clients.erase(i);
|
||||
|
||||
qCDebug(kdsaLocalSocket) << "Secondary timed out. Data read:" << c.readData;
|
||||
}
|
||||
|
||||
KDSingleApplicationLocalSocket::Connection::Connection(QLocalSocket *_socket)
|
||||
: socket(_socket)
|
||||
, timeoutTimer(new QTimer)
|
||||
{
|
||||
timeoutTimer->start(LOCALSOCKET_CONNECTION_TIMEOUT);
|
||||
}
|
||||
|
||||
#ifdef __clang__
|
||||
#pragma clang diagnostic pop
|
||||
#endif
|
||||
@@ -1,126 +0,0 @@
|
||||
/*
|
||||
This file is part of KDSingleApplication.
|
||||
|
||||
SPDX-FileCopyrightText: 2019-2023 Klarälvdalens Datakonsult AB, a KDAB Group company <info@kdab.com>
|
||||
|
||||
SPDX-License-Identifier: MIT
|
||||
|
||||
Contact KDAB at <info@kdab.com> for commercial licensing options.
|
||||
*/
|
||||
|
||||
#ifndef KDSINGLEAPPLICATION_LOCALSOCKET_P_H
|
||||
#define KDSINGLEAPPLICATION_LOCALSOCKET_P_H
|
||||
|
||||
#include <QtCore/QObject>
|
||||
#include <QtCore/QByteArray>
|
||||
#include <QtCore/QString>
|
||||
|
||||
QT_BEGIN_NAMESPACE
|
||||
class QLockFile;
|
||||
class QLocalServer;
|
||||
class QLocalSocket;
|
||||
class QTimer;
|
||||
QT_END_NAMESPACE
|
||||
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
|
||||
struct QObjectDeleteLater
|
||||
{
|
||||
void operator()(QObject *o)
|
||||
{
|
||||
o->deleteLater();
|
||||
}
|
||||
};
|
||||
|
||||
class QObjectConnectionHolder
|
||||
{
|
||||
Q_DISABLE_COPY(QObjectConnectionHolder)
|
||||
QMetaObject::Connection c;
|
||||
|
||||
public:
|
||||
QObjectConnectionHolder()
|
||||
{
|
||||
}
|
||||
|
||||
explicit QObjectConnectionHolder(QMetaObject::Connection _c)
|
||||
: c(std::move(_c))
|
||||
{
|
||||
}
|
||||
|
||||
~QObjectConnectionHolder()
|
||||
{
|
||||
QObject::disconnect(c);
|
||||
}
|
||||
|
||||
QObjectConnectionHolder(QObjectConnectionHolder &&other) noexcept
|
||||
: c(std::exchange(other.c, {}))
|
||||
{
|
||||
}
|
||||
|
||||
QObjectConnectionHolder &operator=(QObjectConnectionHolder &&other) noexcept
|
||||
{
|
||||
QObjectConnectionHolder moved(std::move(other));
|
||||
swap(moved);
|
||||
return *this;
|
||||
}
|
||||
|
||||
void swap(QObjectConnectionHolder &other) noexcept
|
||||
{
|
||||
using std::swap;
|
||||
swap(c, other.c);
|
||||
}
|
||||
};
|
||||
|
||||
class KDSingleApplicationLocalSocket : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit KDSingleApplicationLocalSocket(const QString &name,
|
||||
QObject *parent = nullptr);
|
||||
~KDSingleApplicationLocalSocket();
|
||||
|
||||
bool isPrimaryInstance() const;
|
||||
|
||||
public Q_SLOTS:
|
||||
bool sendMessage(const QByteArray &message, int timeout);
|
||||
|
||||
Q_SIGNALS:
|
||||
void messageReceived(const QByteArray &message);
|
||||
|
||||
private:
|
||||
void handleNewConnection();
|
||||
void readDataFromSecondary();
|
||||
bool readDataFromSecondarySocket(QLocalSocket *socket);
|
||||
void secondaryDisconnected();
|
||||
void secondarySocketDisconnected(QLocalSocket *socket);
|
||||
void abortConnectionToSecondary();
|
||||
|
||||
QString m_socketName;
|
||||
|
||||
std::unique_ptr<QLockFile> m_lockFile; // protects m_localServer
|
||||
std::unique_ptr<QLocalServer> m_localServer;
|
||||
|
||||
struct Connection
|
||||
{
|
||||
explicit Connection(QLocalSocket *s);
|
||||
|
||||
std::unique_ptr<QLocalSocket, QObjectDeleteLater> socket;
|
||||
std::unique_ptr<QTimer, QObjectDeleteLater> timeoutTimer;
|
||||
QByteArray readData;
|
||||
|
||||
// socket/timeoutTimer are deleted via deleteLater (as we delete them
|
||||
// in slots connected to their signals). Before the deleteLater is acted upon,
|
||||
// they may emit further signals, triggering logic that it's not supposed
|
||||
// to be triggered (as the Connection has already been destroyed).
|
||||
// Use this Holder to break the connections.
|
||||
QObjectConnectionHolder readDataConnection;
|
||||
QObjectConnectionHolder secondaryDisconnectedConnection;
|
||||
QObjectConnectionHolder abortConnection;
|
||||
};
|
||||
|
||||
std::vector<Connection> m_clients;
|
||||
};
|
||||
|
||||
#endif // KDSINGLEAPPLICATION_LOCALSOCKET_P_H
|
||||
@@ -102,12 +102,12 @@ endif()
|
||||
option(USE_ICU "Use ICU" ON)
|
||||
|
||||
find_package(PkgConfig REQUIRED)
|
||||
find_package(Boost REQUIRED)
|
||||
find_package(Threads)
|
||||
find_package(Threads REQUIRED)
|
||||
find_package(Backtrace)
|
||||
if(Backtrace_FOUND)
|
||||
set(HAVE_BACKTRACE ON)
|
||||
endif()
|
||||
find_package(Boost REQUIRED)
|
||||
if(USE_ICU)
|
||||
find_package(ICU COMPONENTS uc i18n REQUIRED)
|
||||
if(ICU_FOUND)
|
||||
@@ -300,10 +300,33 @@ if(NOT TAGLIB_FOUND AND NOT TAGPARSER_FOUND)
|
||||
endif()
|
||||
|
||||
# SingleApplication
|
||||
add_subdirectory(3rdparty/kdsingleapplication)
|
||||
set(SINGLEAPPLICATION_INCLUDE_DIRS ${CMAKE_CURRENT_SOURCE_DIR}/3rdparty/kdsingleapplication)
|
||||
set(SINGLEAPPLICATION_LIBRARIES kdsingleapplication)
|
||||
add_definitions(-DKDSINGLEAPPLICATION_STATIC_BUILD)
|
||||
if(QT_VERSION_MAJOR EQUAL 5)
|
||||
set(KDSINGLEAPPLICATION_NAME "KDSingleApplication")
|
||||
else()
|
||||
set(KDSINGLEAPPLICATION_NAME "KDSingleApplication-qt${QT_VERSION_MAJOR}")
|
||||
endif()
|
||||
find_package(${KDSINGLEAPPLICATION_NAME})
|
||||
if(TARGET KDAB::kdsingleapplication)
|
||||
if(QT_VERSION_MAJOR EQUAL 5)
|
||||
set(KDSINGLEAPPLICATION_VERSION "${KDSingleApplication_VERSION}")
|
||||
elseif(QT_VERSION_MAJOR EQUAL 6)
|
||||
set(KDSINGLEAPPLICATION_VERSION "${KDSingleApplication-qt6_VERSION}")
|
||||
endif()
|
||||
if(KDSINGLEAPPLICATION_VERSION VERSION_GREATER_EQUAL 1.0.95)
|
||||
set(HAVE_KDSINGLEAPPLICATION_OPTIONS ON)
|
||||
else()
|
||||
set(HAVE_KDSINGLEAPPLICATION_OPTIONS OFF)
|
||||
endif()
|
||||
message(STATUS "Using system KDSingleApplication (Version ${KDSINGLEAPPLICATION_VERSION})")
|
||||
set(SINGLEAPPLICATION_LIBRARIES KDAB::kdsingleapplication)
|
||||
else()
|
||||
message(STATUS "Using 3rdparty KDSingleApplication")
|
||||
set(HAVE_KDSINGLEAPPLICATION_OPTIONS ON)
|
||||
add_subdirectory(3rdparty/kdsingleapplication)
|
||||
set(SINGLEAPPLICATION_INCLUDE_DIRS ${CMAKE_CURRENT_SOURCE_DIR}/3rdparty/kdsingleapplication/KDSingleApplication/src)
|
||||
set(SINGLEAPPLICATION_LIBRARIES kdsingleapplication)
|
||||
add_definitions(-DKDSINGLEAPPLICATION_STATIC_BUILD)
|
||||
endif()
|
||||
|
||||
if(APPLE)
|
||||
add_subdirectory(3rdparty/SPMediaKeyTap)
|
||||
@@ -440,19 +463,11 @@ optional_component(EBUR128 ON "EBU R 128 loudness normalization"
|
||||
)
|
||||
|
||||
if(APPLE OR WIN32)
|
||||
option(USE_BUNDLE "Bundle dependencies" ON)
|
||||
set(USE_BUNDLE_DEFAULT ON)
|
||||
else()
|
||||
option(USE_BUNDLE "Bundle dependencies" OFF)
|
||||
endif()
|
||||
|
||||
if(USE_BUNDLE AND NOT USE_BUNDLE_DIR)
|
||||
if(LINUX)
|
||||
set(USE_BUNDLE_DIR "../plugins")
|
||||
endif()
|
||||
if(APPLE)
|
||||
set(USE_BUNDLE_DIR "../PlugIns")
|
||||
endif()
|
||||
set(USE_BUNDLE_DEFAULT OFF)
|
||||
endif()
|
||||
option(USE_BUNDLE "Bundle dependencies" ${USE_BUNDLE_DEFAULT})
|
||||
|
||||
if(NOT CMAKE_CROSSCOMPILING)
|
||||
# Check that we have Qt with sqlite driver
|
||||
|
||||
38
Changelog
38
Changelog
@@ -2,6 +2,44 @@ Strawberry Music Player
|
||||
=======================
|
||||
ChangeLog
|
||||
|
||||
Version 1.0.22 (2023.12.09):
|
||||
|
||||
Bugfixes:
|
||||
* Fixed KDSingleApplication cmake version check.
|
||||
* Fixed KDSingleApplication Qt 5 detection (#1299).
|
||||
* Fixed timer started in wrong thread (#1302).
|
||||
* Fixed erratic seeking behaviour if buffer duration is set to zero (#1302).
|
||||
* Fixed SCollection related crash on exit with Qt 5 (#1316).
|
||||
* Fixed track about to end related crash on playback failure (#1332).
|
||||
* Fixed playlist column widths not remembered if stretch mode is off with Qt 6.6.1 and higher (#1328).
|
||||
* (Windows) Properly handle silent uninstall (#1323).
|
||||
|
||||
Enhancements:
|
||||
* Increase thread priority for playback threads.
|
||||
* Allow drag and drop of songs to favorite playlists.
|
||||
|
||||
Version 1.0.21 (2023.10.21):
|
||||
|
||||
Bugfixes:
|
||||
* Fixed seekbar position resetting to zero before showing actual position when seeking.
|
||||
* Fixed compressed files showing up in collection (#1274).
|
||||
* Fixed connecting devices (#1288).
|
||||
* Fixed device schema missing ebur128 fields.
|
||||
* Fixed collection search by tag not working with space between colon and search term (#1290).
|
||||
* Fixed seeking when 5 seconds is remaining of the song resetting position to beginning (#1258).
|
||||
* Fixed intermittent crash when seeking with Auto as output (#1123).
|
||||
* (Windows) Fixed playlist header colors in dark mode (#1275).
|
||||
|
||||
Enhancements:
|
||||
* Support using system KDSingleApplication when available.
|
||||
* Improved lyrics matching.
|
||||
* (macOS) Fully codesign binaries and DMG.
|
||||
|
||||
Version 1.0.20 (2023.09.24):
|
||||
|
||||
Bugfixes:
|
||||
* Fixed appdata validation.
|
||||
|
||||
Version 1.0.19 (2023.09.24):
|
||||
|
||||
Bugfixes:
|
||||
|
||||
11
README.md
11
README.md
@@ -21,7 +21,7 @@ Resources:
|
||||
* Ubuntu Unstable PPA: https://launchpad.net/~jonaski/+archive/ubuntu/strawberry-unstable
|
||||
* Translations: https://translate.zanata.org/iteration/view/strawberry/master
|
||||
|
||||
### :bangbang: Opening an issue:
|
||||
### :bangbang: Opening an issue
|
||||
|
||||
* Read the FAQ: https://wiki.strawberrymusicplayer.org/wiki/FAQ
|
||||
* Search for the issue to see if it is already solved, or if there is an open issue for it already. If there is an open issue already, you can comment on it if you have additional information that could be useful to us.
|
||||
@@ -29,18 +29,19 @@ Resources:
|
||||
* We do not take feature requests from users on GitHub. Any issues related to feature requests will be closed. This does not necessarily mean that we won't add new features, but we don't have time to take feature requests or answer questions about new features from users. It is still possible to suggest or discuss new features on the forum (https://forum.strawberrymusicplayer.org/).
|
||||
* We do not maintain the Flatpak package. Do not report issues related to Flatpak unless the issue can be reproduced with a native package, use Flatpak support instead https://flatpak.org/about/
|
||||
|
||||
### :moneybag: Sponsoring:
|
||||
### :moneybag: Sponsoring
|
||||
|
||||
The program is free software, released under GPL. If you like this program and can make use of it, consider sponsoring or donating to help fund the project.
|
||||
There are currently 3 options for sponsoring:
|
||||
|
||||
1. [GitHub Sponsors](https://github.com/sponsors/jonaski)
|
||||
2. [Patreon](https://www.patreon.com/jonaskvinge)
|
||||
3. [PayPal](https://paypal.me/jonaskvinge)
|
||||
3. [Ko-fi](https://ko-fi.com/jonaskvinge)
|
||||
4. [PayPal](https://paypal.me/jonaskvinge)
|
||||
|
||||
Funding developers is a way to contribute to open source projects you appreciate, it helps developers get the resources they need, and recognize contributors working behind the scenes to make open source better for everyone.
|
||||
|
||||
### :heavy_check_mark: Features:
|
||||
### :heavy_check_mark: Features
|
||||
|
||||
* Play and organize music
|
||||
* Supports WAV, FLAC, WavPack, Ogg FLAC, Ogg Vorbis, Ogg Opus, Ogg Speex, MPC, TrueAudio, AIFF, MP4, MP3, ASF and Monkey's Audio.
|
||||
@@ -100,7 +101,7 @@ You should also install the gstreamer plugins base and good, and optionally bad,
|
||||
|
||||
### Get the code:
|
||||
|
||||
git clone https://github.com/strawberrymusicplayer/strawberry
|
||||
git clone --recursive https://github.com/strawberrymusicplayer/strawberry
|
||||
|
||||
### Compile and install:
|
||||
|
||||
|
||||
@@ -27,7 +27,7 @@ if(MACDEPLOYQT_EXECUTABLE)
|
||||
COMMAND cp -v ${CMAKE_SOURCE_DIR}/dist/macos/Info.plist ${CMAKE_BINARY_DIR}/strawberry.app/Contents/
|
||||
COMMAND cp -v ${CMAKE_SOURCE_DIR}/dist/macos/strawberry.icns ${CMAKE_BINARY_DIR}/strawberry.app/Contents/Resources/
|
||||
COMMAND ${CMAKE_SOURCE_DIR}/dist/macos/macgstcopy.sh ${CMAKE_BINARY_DIR}/strawberry.app
|
||||
COMMAND ${MACDEPLOYQT_EXECUTABLE} strawberry.app -verbose=3 -executable=${CMAKE_BINARY_DIR}/strawberry.app/Contents/PlugIns/strawberry-tagreader -executable=${CMAKE_BINARY_DIR}/strawberry.app/Contents/PlugIns/gst-plugin-scanner -executable=strawberry.app/Contents/PlugIns/gio-modules/libgioopenssl.so -executable=strawberry.app/Contents/PlugIns/gio-modules/libgiognutls.so ${MACDEPLOYQT_CODESIGN}
|
||||
COMMAND ${MACDEPLOYQT_EXECUTABLE} strawberry.app -verbose=3 -executable=${CMAKE_BINARY_DIR}/strawberry.app/Contents/PlugIns/strawberry-tagreader -executable=${CMAKE_BINARY_DIR}/strawberry.app/Contents/PlugIns/gst-plugin-scanner ${MACDEPLOYQT_CODESIGN}
|
||||
WORKING_DIRECTORY ${CMAKE_BINARY_DIR}
|
||||
DEPENDS strawberry strawberry-tagreader
|
||||
)
|
||||
|
||||
@@ -59,7 +59,7 @@ macro(add_po outfiles po_prefix)
|
||||
# Convert the .po files to .qm files
|
||||
add_custom_command(
|
||||
OUTPUT ${_qm_filepath}
|
||||
COMMAND ${QT_LCONVERT_EXECUTABLE} ARGS ${_po_filepath} -o ${_qm_filepath} -of qm
|
||||
COMMAND ${QT_LCONVERT_EXECUTABLE} ARGS ${_po_filepath} -o ${_qm_filepath} -of qm -target-language ${_lang}
|
||||
DEPENDS ${_po_filepath} ${_po_filepath}
|
||||
)
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
set(STRAWBERRY_VERSION_MAJOR 1)
|
||||
set(STRAWBERRY_VERSION_MINOR 0)
|
||||
set(STRAWBERRY_VERSION_PATCH 19)
|
||||
set(STRAWBERRY_VERSION_PATCH 22)
|
||||
#set(STRAWBERRY_VERSION_PRERELEASE rc1)
|
||||
|
||||
set(INCLUDE_GIT_REVISION OFF)
|
||||
@@ -25,7 +25,7 @@ if(INCLUDE_GIT_REVISION AND EXISTS "${CMAKE_SOURCE_DIR}/.git")
|
||||
|
||||
find_program(GIT_EXECUTABLE git)
|
||||
if(NOT GIT_EXECUTABLE OR GIT_EXECUTABLE-NOTFOUND)
|
||||
message(FATAL_ERROR "Missing GIT executable." )
|
||||
message(FATAL_ERROR "Missing Git executable." )
|
||||
endif()
|
||||
|
||||
# Get the current working branch
|
||||
@@ -48,7 +48,7 @@ if(INCLUDE_GIT_REVISION AND EXISTS "${CMAKE_SOURCE_DIR}/.git")
|
||||
)
|
||||
|
||||
if(NOT ${GIT_CMD_RESULT_REVISION} EQUAL 0)
|
||||
message(FATAL_ERROR "GIT command failed to get revision string '${GIT_REVISION}'")
|
||||
message(FATAL_ERROR "Git command failed to get revision string '${GIT_REVISION}'")
|
||||
endif()
|
||||
|
||||
endif()
|
||||
@@ -67,7 +67,7 @@ if(GIT_REVISION)
|
||||
|
||||
list(LENGTH GIT_PARTS GIT_PARTS_LENGTH)
|
||||
if(NOT GIT_PARTS_LENGTH EQUAL 3)
|
||||
message(FATAL_ERROR "Failed to parse git revision string '${GIT_REVISION}'")
|
||||
set(GIT_PARTS "${majorminorpatch};0;${GIT_REVISION}")
|
||||
endif()
|
||||
|
||||
list(GET GIT_PARTS 0 GIT_TAGNAME)
|
||||
|
||||
@@ -18,7 +18,7 @@ CREATE TABLE device_%deviceid_songs (
|
||||
track INTEGER NOT NULL DEFAULT -1,
|
||||
disc INTEGER NOT NULL DEFAULT -1,
|
||||
year INTEGER NOT NULL DEFAULT -1,
|
||||
originalyear INTEGER NOT NULL DEFAULT 0,
|
||||
originalyear INTEGER NOT NULL DEFAULT -1,
|
||||
genre TEXT,
|
||||
compilation INTEGER NOT NULL DEFAULT 0,
|
||||
composer TEXT,
|
||||
@@ -83,7 +83,10 @@ CREATE TABLE device_%deviceid_songs (
|
||||
musicbrainz_track_id TEXT,
|
||||
musicbrainz_disc_id TEXT,
|
||||
musicbrainz_release_group_id TEXT,
|
||||
musicbrainz_work_id TEXT
|
||||
musicbrainz_work_id TEXT,
|
||||
|
||||
ebur128_integrated_loudness_lufs REAL,
|
||||
ebur128_loudness_range_lu REAL
|
||||
|
||||
);
|
||||
|
||||
@@ -96,4 +99,4 @@ CREATE VIRTUAL TABLE device_%deviceid_fts USING fts5(
|
||||
tokenize = "unicode61 remove_diacritics 1"
|
||||
);
|
||||
|
||||
UPDATE devices SET schema_version=4 WHERE ROWID=%deviceid;
|
||||
UPDATE devices SET schema_version=5 WHERE ROWID=%deviceid;
|
||||
|
||||
3
debian/control.in
vendored
3
debian/control.in
vendored
@@ -25,7 +25,8 @@ Build-Depends: debhelper (>= 11),
|
||||
libgpod-dev,
|
||||
libmtp-dev,
|
||||
libchromaprint-dev,
|
||||
libfftw3-dev
|
||||
libfftw3-dev,
|
||||
libebur128-dev
|
||||
Standards-Version: 4.6.1
|
||||
|
||||
Package: strawberry
|
||||
|
||||
167
dist/macos/macgstcopy.sh
vendored
167
dist/macos/macgstcopy.sh
vendored
@@ -23,6 +23,26 @@ if [ "${GST_PLUGIN_PATH}" = "" ]; then
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if ! [ -e "${GIO_EXTRA_MODULES}/libgiognutls.so" ] && ! [ -e "${GIO_EXTRA_MODULES}/libgioopenssl.so" ]; then
|
||||
echo "Error: Missing ${GIO_EXTRA_MODULES}/libgiognutls.so or ${GIO_EXTRA_MODULES}/libgioopenssl.so."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if ! [ -e "${GST_PLUGIN_SCANNER}" ]; then
|
||||
echo "Error: Missing ${GST_PLUGIN_SCANNER}"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if ! [ -d "${GST_PLUGIN_PATH}" ]; then
|
||||
echo "Error: GStreamer plugins path ${GST_PLUGIN_PATH} does not exist."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if ! [ -e "${GST_PLUGIN_PATH}/libgstcoreelements.so" ] && ! [ -e "${GST_PLUGIN_PATH}/libgstcoreelements.dylib" ]; then
|
||||
echo "Error: Missing libgstcoreelements.{so,dylib} in GStreamer plugins path ${GST_PLUGIN_PATH}."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
mkdir -p "${bundledir}/Contents/PlugIns/gio-modules" || exit 1
|
||||
mkdir -p "${bundledir}/Contents/PlugIns/gstreamer" || exit 1
|
||||
|
||||
@@ -38,82 +58,91 @@ else
|
||||
echo "Warning: Missing ${GIO_EXTRA_MODULES}/libgioopenssl.so"
|
||||
fi
|
||||
|
||||
if ! [ -e "${GST_PLUGIN_SCANNER}" ]; then
|
||||
echo "Error: Missing ${GST_PLUGIN_SCANNER}"
|
||||
exit 1
|
||||
fi
|
||||
cp -v -f "${GST_PLUGIN_SCANNER}" "${bundledir}/Contents/PlugIns/" || exit 1
|
||||
|
||||
if ! [ -d "${GST_PLUGIN_PATH}" ]; then
|
||||
echo "Error: GStreamer plugins path ${GST_PLUGIN_PATH} does not exist."
|
||||
exit 1
|
||||
fi
|
||||
install_name_tool -add_rpath "@loader_path/../Frameworks" "${bundledir}/Contents/PlugIns/$(basename ${GST_PLUGIN_SCANNER})" || exit 1
|
||||
|
||||
gst_plugins="
|
||||
libgstaes.dylib
|
||||
libgstaiff.dylib
|
||||
libgstapetag.dylib
|
||||
libgstapp.dylib
|
||||
libgstasf.dylib
|
||||
libgstasfmux.dylib
|
||||
libgstaudioconvert.dylib
|
||||
libgstaudiofx.dylib
|
||||
libgstaudiomixer.dylib
|
||||
libgstaudioparsers.dylib
|
||||
libgstaudiorate.dylib
|
||||
libgstaudioresample.dylib
|
||||
libgstaudiotestsrc.dylib
|
||||
libgstautodetect.dylib
|
||||
libgstbs2b.dylib
|
||||
libgstcdio.dylib
|
||||
libgstcoreelements.dylib
|
||||
libgstdash.dylib
|
||||
libgstequalizer.dylib
|
||||
libgstfaac.dylib
|
||||
libgstfaad.dylib
|
||||
libgstfdkaac.dylib
|
||||
libgstflac.dylib
|
||||
libgstgio.dylib
|
||||
libgsthls.dylib
|
||||
libgsticydemux.dylib
|
||||
libgstid3demux.dylib
|
||||
libgstid3tag.dylib
|
||||
libgstisomp4.dylib
|
||||
libgstlame.dylib
|
||||
libgstlibav.dylib
|
||||
libgstmpg123.dylib
|
||||
libgstmusepack.dylib
|
||||
libgstogg.dylib
|
||||
libgstopenmpt.dylib
|
||||
libgstopus.dylib
|
||||
libgstopusparse.dylib
|
||||
libgstosxaudio.dylib
|
||||
libgstpbtypes.dylib
|
||||
libgstplayback.dylib
|
||||
libgstreplaygain.dylib
|
||||
libgstrtp.dylib
|
||||
libgstrtsp.dylib
|
||||
libgstsoup.dylib
|
||||
libgstspectrum.dylib
|
||||
libgstspeex.dylib
|
||||
libgsttaglib.dylib
|
||||
libgsttcp.dylib
|
||||
libgsttwolame.dylib
|
||||
libgsttypefindfunctions.dylib
|
||||
libgstudp.dylib
|
||||
libgstvolume.dylib
|
||||
libgstvorbis.dylib
|
||||
libgstwavenc.dylib
|
||||
libgstwavpack.dylib
|
||||
libgstwavparse.dylib
|
||||
libgstxingmux.dylib
|
||||
libgstaes
|
||||
libgstaiff
|
||||
libgstapetag
|
||||
libgstapp
|
||||
libgstasf
|
||||
libgstasfmux
|
||||
libgstaudioconvert
|
||||
libgstaudiofx
|
||||
libgstaudiomixer
|
||||
libgstaudioparsers
|
||||
libgstaudiorate
|
||||
libgstaudioresample
|
||||
libgstaudiotestsrc
|
||||
libgstautodetect
|
||||
libgstbs2b
|
||||
libgstcdio
|
||||
libgstcoreelements
|
||||
libgstdash
|
||||
libgstequalizer
|
||||
libgstfaac
|
||||
libgstfaad
|
||||
libgstfdkaac
|
||||
libgstflac
|
||||
libgstgio
|
||||
libgsthls
|
||||
libgsticydemux
|
||||
libgstid3demux
|
||||
libgstid3tag
|
||||
libgstisomp4
|
||||
libgstlame
|
||||
libgstlibav
|
||||
libgstmpg123
|
||||
libgstmusepack
|
||||
libgstogg
|
||||
libgstopenmpt
|
||||
libgstopus
|
||||
libgstopusparse
|
||||
libgstosxaudio
|
||||
libgstpbtypes
|
||||
libgstplayback
|
||||
libgstreplaygain
|
||||
libgstrtp
|
||||
libgstrtsp
|
||||
libgstsoup
|
||||
libgstspectrum
|
||||
libgstspeex
|
||||
libgsttaglib
|
||||
libgsttcp
|
||||
libgsttwolame
|
||||
libgsttypefindfunctions
|
||||
libgstudp
|
||||
libgstvolume
|
||||
libgstvorbis
|
||||
libgstwavenc
|
||||
libgstwavpack
|
||||
libgstwavparse
|
||||
libgstxingmux
|
||||
"
|
||||
|
||||
gst_plugins=$(echo "$gst_plugins" | tr '\n' ' ' | sed -e 's/^ //g' | sed -e 's/ / /g')
|
||||
for gst_plugin in $gst_plugins; do
|
||||
if [ -e "${GST_PLUGIN_PATH}/${gst_plugin}" ]; then
|
||||
cp -v -f "${GST_PLUGIN_PATH}/${gst_plugin}" "${bundledir}/Contents/PlugIns/gstreamer/" || exit 1
|
||||
if [ -e "${GST_PLUGIN_PATH}/${gst_plugin}.dylib" ]; then
|
||||
cp -v -f "${GST_PLUGIN_PATH}/${gst_plugin}.dylib" "${bundledir}/Contents/PlugIns/gstreamer/" || exit 1
|
||||
install_name_tool -id "@rpath/${gst_plugin}.dylib" "${bundledir}/Contents/PlugIns/gstreamer/${gst_plugin}.dylib"
|
||||
elif [ -e "${GST_PLUGIN_PATH}/${gst_plugin}.so" ]; then
|
||||
cp -v -f "${GST_PLUGIN_PATH}/${gst_plugin}.so" "${bundledir}/Contents/PlugIns/gstreamer/" || exit 1
|
||||
install_name_tool -id "@rpath/${gst_plugin}.so" "${bundledir}/Contents/PlugIns/gstreamer/${gst_plugin}.so"
|
||||
else
|
||||
echo "Warning: Missing gstreamer plugin ${gst_plugin}"
|
||||
echo "Warning: Missing gstreamer plugin ${gst_plugin}."
|
||||
fi
|
||||
done
|
||||
|
||||
# libsoup is dynamically loaded by gstreamer, so it needs to be copied.
|
||||
if [ "${LIBSOUP_LIBRARY_PATH}" = "" ]; then
|
||||
echo "Warning: Set the LIBSOUP_LIBRARY_PATH environment variable for copying libsoup."
|
||||
else
|
||||
if [ -e "${LIBSOUP_LIBRARY_PATH}" ]; then
|
||||
mkdir -p "${bundledir}/Contents/Frameworks" || exit 1
|
||||
cp -v -f "${LIBSOUP_LIBRARY_PATH}" "${bundledir}/Contents/Frameworks/" || exit 1
|
||||
install_name_tool -id "@rpath/$(basename ${LIBSOUP_LIBRARY_PATH})" "${bundledir}/Contents/Frameworks/$(basename ${LIBSOUP_LIBRARY_PATH})"
|
||||
else
|
||||
echo "Warning: Missing libsoup ${LIBSOUP_LIBRARY_PATH}."
|
||||
fi
|
||||
fi
|
||||
|
||||
@@ -50,7 +50,10 @@
|
||||
</screenshots>
|
||||
<update_contact>eclipseo@fedoraproject.org</update_contact>
|
||||
<releases>
|
||||
<release version="1.0.18" date="2023-07-02"/>
|
||||
<release version="1.0.22" date="2023-12-09"/>
|
||||
<release version="1.0.21" date="2023-10-21"/>
|
||||
<release version="1.0.20" date="2023-09-24"/>
|
||||
<release version="1.0.19" date="2023-09-24"/>
|
||||
<release version="1.0.18" date="2023-07-02"/>
|
||||
</releases>
|
||||
</component>
|
||||
|
||||
78
dist/unix/strawberry.spec.in
vendored
78
dist/unix/strawberry.spec.in
vendored
@@ -1,9 +1,9 @@
|
||||
Name: strawberry
|
||||
Version: @STRAWBERRY_VERSION_RPM_V@
|
||||
%if 0%{?fedora} || 0%{?rhel_version} || 0%{?centos} || "%{?_vendor}" == "openmandriva"
|
||||
Release: @STRAWBERRY_VERSION_RPM_R@%{?dist}
|
||||
%else
|
||||
%if 0%{?suse_version}
|
||||
Release: @STRAWBERRY_VERSION_RPM_R@.@RPM_DISTRO@
|
||||
%else
|
||||
Release: @STRAWBERRY_VERSION_RPM_R@%{?dist}
|
||||
%endif
|
||||
Summary: A music player and music collection organizer
|
||||
Group: Productivity/Multimedia/Sound/Players
|
||||
@@ -11,7 +11,7 @@ License: GPL-3.0+
|
||||
URL: https://www.strawberrymusicplayer.org/
|
||||
Source0: %{name}-@STRAWBERRY_VERSION_PACKAGE@.tar.xz
|
||||
|
||||
%if 0%{?suse_version} && 0%{?suse_version} > 1325
|
||||
%if 0%{?suse_version}
|
||||
BuildRequires: libboost_headers-devel
|
||||
%else
|
||||
BuildRequires: boost-devel
|
||||
@@ -25,15 +25,13 @@ BuildRequires: gettext
|
||||
BuildRequires: desktop-file-utils
|
||||
%if 0%{?suse_version}
|
||||
BuildRequires: update-desktop-files
|
||||
%endif
|
||||
%if 0%{?suse_version}
|
||||
BuildRequires: appstream-glib
|
||||
%else
|
||||
%if 0%{?fedora} || 0%{?rhel_version} || 0%{?centos}
|
||||
%endif
|
||||
%if 0%{?fedora} || 0%{?rhel_version} || 0%{?centos}
|
||||
BuildRequires: libappstream-glib
|
||||
%else
|
||||
%endif
|
||||
%if 0%{?mageia} || "%{?_vendor}" == "openmandriva"
|
||||
BuildRequires: appstream-util
|
||||
%endif
|
||||
%endif
|
||||
BuildRequires: pkgconfig
|
||||
BuildRequires: pkgconfig(glib-2.0)
|
||||
@@ -44,48 +42,31 @@ BuildRequires: pkgconfig(dbus-1)
|
||||
BuildRequires: pkgconfig(alsa)
|
||||
BuildRequires: pkgconfig(protobuf)
|
||||
BuildRequires: pkgconfig(sqlite3) >= 3.9
|
||||
%if ! 0%{?centos} && ! 0%{?mageia}
|
||||
BuildRequires: pkgconfig(taglib)
|
||||
%endif
|
||||
BuildRequires: pkgconfig(fftw3)
|
||||
BuildRequires: pkgconfig(icu-uc)
|
||||
BuildRequires: pkgconfig(icu-i18n)
|
||||
%if "@QT_VERSION_MAJOR@" == "5" && ( 0%{?fedora} || 0%{?rhel_version} || 0%{?centos} )
|
||||
BuildRequires: pkgconfig(Qt@QT_VERSION_MAJOR@Core)
|
||||
BuildRequires: pkgconfig(Qt@QT_VERSION_MAJOR@Gui)
|
||||
BuildRequires: pkgconfig(Qt@QT_VERSION_MAJOR@Widgets)
|
||||
BuildRequires: pkgconfig(Qt@QT_VERSION_MAJOR@Concurrent)
|
||||
BuildRequires: pkgconfig(Qt@QT_VERSION_MAJOR@Network)
|
||||
BuildRequires: pkgconfig(Qt@QT_VERSION_MAJOR@Sql)
|
||||
BuildRequires: pkgconfig(Qt@QT_VERSION_MAJOR@DBus)
|
||||
BuildRequires: pkgconfig(Qt@QT_VERSION_MAJOR@Test)
|
||||
BuildRequires: pkgconfig(Qt@QT_VERSION_MAJOR@X11Extras)
|
||||
%else
|
||||
BuildRequires: cmake(Qt@QT_VERSION_MAJOR@Core)
|
||||
BuildRequires: cmake(Qt@QT_VERSION_MAJOR@Gui)
|
||||
BuildRequires: cmake(Qt@QT_VERSION_MAJOR@Widgets)
|
||||
BuildRequires: cmake(Qt@QT_VERSION_MAJOR@Concurrent)
|
||||
BuildRequires: cmake(Qt@QT_VERSION_MAJOR@Network)
|
||||
BuildRequires: cmake(Qt@QT_VERSION_MAJOR@Sql)
|
||||
BuildRequires: cmake(Qt@QT_VERSION_MAJOR@DBus)
|
||||
BuildRequires: cmake(Qt@QT_VERSION_MAJOR@Gui)
|
||||
BuildRequires: cmake(Qt@QT_VERSION_MAJOR@Widgets)
|
||||
BuildRequires: cmake(Qt@QT_VERSION_MAJOR@Test)
|
||||
BuildRequires: cmake(Qt@QT_VERSION_MAJOR@LinguistTools)
|
||||
%if "@QT_VERSION_MAJOR@" == "5"
|
||||
BuildRequires: cmake(Qt@QT_VERSION_MAJOR@X11Extras)
|
||||
%endif
|
||||
%endif
|
||||
%if 0%{?suse_version} || 0%{?fedora_version} || 0%{?mageia}
|
||||
BuildRequires: cmake(Qt@QT_VERSION_MAJOR@LinguistTools)
|
||||
%endif
|
||||
BuildRequires: pkgconfig(gstreamer-1.0)
|
||||
BuildRequires: pkgconfig(gstreamer-app-1.0)
|
||||
BuildRequires: pkgconfig(gstreamer-audio-1.0)
|
||||
BuildRequires: pkgconfig(gstreamer-base-1.0)
|
||||
BuildRequires: pkgconfig(gstreamer-tag-1.0)
|
||||
%if ! 0%{?centos}
|
||||
BuildRequires: pkgconfig(libchromaprint)
|
||||
%endif
|
||||
BuildRequires: pkgconfig(libpulse)
|
||||
BuildRequires: pkgconfig(libcdio)
|
||||
BuildRequires: pkgconfig(libebur128)
|
||||
BuildRequires: pkgconfig(libgpod-1.0)
|
||||
BuildRequires: pkgconfig(libmtp)
|
||||
%if 0%{?suse_version} || 0%{?fedora_version}
|
||||
@@ -138,22 +119,19 @@ Features:
|
||||
%if 0%{?fedora} || 0%{?rhel_version} || 0%{?centos}
|
||||
export CXXFLAGS="-fPIC $RPM_OPT_FLAGS"
|
||||
%endif
|
||||
%{cmake} -DQT_VERSION_MAJOR=@QT_VERSION_MAJOR@
|
||||
%if 0%{?centos} || (0%{?mageia} && 0%{?mageia} <= 7) || "%{?_vendor}" == "openmandriva"
|
||||
%make_build
|
||||
%if "%{?_vendor}" == "openmandriva"
|
||||
%{cmake} -DQT_VERSION_MAJOR=@QT_VERSION_MAJOR@ -DENABLE_TRANSLATIONS=OFF
|
||||
%make_build
|
||||
%else
|
||||
%cmake_build
|
||||
%{cmake} -DQT_VERSION_MAJOR=@QT_VERSION_MAJOR@
|
||||
%cmake_build
|
||||
%endif
|
||||
|
||||
%install
|
||||
%if 0%{?centos}
|
||||
%make_install
|
||||
%if "%{?_vendor}" == "openmandriva"
|
||||
%make_install -C build
|
||||
%else
|
||||
%if 0%{?mageia} || "%{?_vendor}" == "openmandriva"
|
||||
%make_install -C build
|
||||
%else
|
||||
%cmake_install
|
||||
%endif
|
||||
%cmake_install
|
||||
%endif
|
||||
|
||||
%if 0%{?suse_version}
|
||||
@@ -162,10 +140,10 @@ Features:
|
||||
|
||||
%check
|
||||
desktop-file-validate %{buildroot}%{_datadir}/applications/org.strawberrymusicplayer.strawberry.desktop
|
||||
%if 0%{?fedora_version}
|
||||
appstream-util validate-relax --nonet %{buildroot}%{_metainfodir}/org.strawberrymusicplayer.strawberry.appdata.xml
|
||||
%else
|
||||
%if 0%{?suse_version}
|
||||
appstream-util validate-relax --nonet %{buildroot}%{_datadir}/metainfo/org.strawberrymusicplayer.strawberry.appdata.xml
|
||||
%else
|
||||
appstream-util validate-relax --nonet %{buildroot}%{_metainfodir}/org.strawberrymusicplayer.strawberry.appdata.xml
|
||||
%endif
|
||||
|
||||
%files
|
||||
@@ -176,13 +154,13 @@ desktop-file-validate %{buildroot}%{_datadir}/applications/org.strawberrymusicpl
|
||||
%{_bindir}/strawberry-tagreader
|
||||
%{_datadir}/applications/*.desktop
|
||||
%{_datadir}/icons/hicolor/*/apps/strawberry.*
|
||||
%if 0%{?fedora_version}
|
||||
%{_metainfodir}/*.appdata.xml
|
||||
%else
|
||||
%{_datadir}/metainfo/*.appdata.xml
|
||||
%endif
|
||||
%{_mandir}/man1/%{name}.1.*
|
||||
%{_mandir}/man1/%{name}-tagreader.1.*
|
||||
%if 0%{?suse_version}
|
||||
%{_datadir}/metainfo/*.appdata.xml
|
||||
%else
|
||||
%{_metainfodir}/*.appdata.xml
|
||||
%endif
|
||||
|
||||
%changelog
|
||||
* @RPM_DATE@ Jonas Kvinge <jonas@jkvinge.net> - @STRAWBERRY_VERSION_RPM_V@
|
||||
|
||||
55
dist/windows/strawberry.nsi.in
vendored
55
dist/windows/strawberry.nsi.in
vendored
@@ -136,8 +136,6 @@ SetCompressor /SOLID lzma
|
||||
ReserveFile "${NSISDIR}\Plugins\x86-unicode\INetC.dll"
|
||||
!endif
|
||||
|
||||
!define LockedListPATH $InstallDir
|
||||
|
||||
; Installer pages
|
||||
!insertmacro MUI_PAGE_WELCOME
|
||||
!insertmacro MUI_PAGE_LICENSE COPYING
|
||||
@@ -184,19 +182,22 @@ FunctionEnd
|
||||
Function CheckPreviousInstall
|
||||
|
||||
ReadRegStr $R0 ${PRODUCT_UNINST_ROOT_KEY} ${PRODUCT_UNINST_KEY} "UninstallString"
|
||||
StrCmp $R0 "" done
|
||||
StrCmp $R0 "" Done
|
||||
|
||||
MessageBox MB_OKCANCEL|MB_ICONEXCLAMATION \
|
||||
"${PRODUCT_NAME} is already installed. $\n$\nClick `OK` to remove the \
|
||||
previous version or `Cancel` to cancel this upgrade." \
|
||||
IDOK uninst
|
||||
${IfNot} ${Silent}
|
||||
MessageBox MB_OKCANCEL|MB_ICONEXCLAMATION "${PRODUCT_NAME} is already installed. $\n$\nClick `OK` to remove the previous version or `Cancel` to cancel this upgrade." IDOK Uninstall
|
||||
Abort
|
||||
; Run the uninstaller
|
||||
uninst:
|
||||
ClearErrors
|
||||
ExecWait '$R0' ; Do not copy the uninstaller to a temp file
|
||||
${EndIf}
|
||||
|
||||
done:
|
||||
Uninstall:
|
||||
ClearErrors
|
||||
${If} ${Silent}
|
||||
ExecWait '$R0 /S'
|
||||
${Else}
|
||||
ExecWait '$R0'
|
||||
${EndIf}
|
||||
|
||||
Done:
|
||||
|
||||
FunctionEnd
|
||||
|
||||
@@ -299,6 +300,7 @@ Section "Strawberry" Strawberry
|
||||
File "libidn2-0.dll"
|
||||
File "libintl-8.dll"
|
||||
File "libjpeg-9.dll"
|
||||
File "libkdsingleapplication-qt6.dll"
|
||||
File "liblzma-5.dll"
|
||||
File "libmp3lame-0.dll"
|
||||
File "libmpcdec.dll"
|
||||
@@ -343,6 +345,7 @@ Section "Strawberry" Strawberry
|
||||
File "libabsl_examine_stack.dll"
|
||||
File "libabsl_hash.dll"
|
||||
File "libabsl_int128.dll"
|
||||
File "libabsl_kernel_timeout_internal.dll"
|
||||
File "libabsl_log_globals.dll"
|
||||
File "libabsl_log_internal_check_op.dll"
|
||||
File "libabsl_log_internal_conditions.dll"
|
||||
@@ -406,7 +409,7 @@ Section "Strawberry" Strawberry
|
||||
File "brotlidec.dll"
|
||||
File "chromaprint.dll"
|
||||
File "ebur128.dll"
|
||||
File "faad.dll"
|
||||
File "faad-2.dll"
|
||||
File "fdk-aac.dll"
|
||||
File "ffi-7.dll"
|
||||
File "gio-2.0-0.dll"
|
||||
@@ -435,6 +438,7 @@ Section "Strawberry" Strawberry
|
||||
File "harfbuzz.dll"
|
||||
File "intl-8.dll"
|
||||
File "jpeg62.dll"
|
||||
File "kdsingleapplication-qt6.dll"
|
||||
File "libbs2b.dll"
|
||||
File "libfaac_dll.dll"
|
||||
File "liblzma.dll"
|
||||
@@ -489,7 +493,7 @@ Section "Strawberry" Strawberry
|
||||
|
||||
; Common files
|
||||
|
||||
File "icudt73.dll"
|
||||
File "icudt74.dll"
|
||||
File "libfftw3-3.dll"
|
||||
!ifdef debug
|
||||
File "libprotobufd.dll"
|
||||
@@ -497,8 +501,8 @@ Section "Strawberry" Strawberry
|
||||
File "libprotobuf.dll"
|
||||
!endif
|
||||
!ifdef msvc && debug
|
||||
File "icuin73d.dll"
|
||||
File "icuuc73d.dll"
|
||||
File "icuin74d.dll"
|
||||
File "icuuc74d.dll"
|
||||
File "Qt6Concurrentd.dll"
|
||||
File "Qt6Cored.dll"
|
||||
File "Qt6Guid.dll"
|
||||
@@ -506,8 +510,8 @@ Section "Strawberry" Strawberry
|
||||
File "Qt6Sqld.dll"
|
||||
File "Qt6Widgetsd.dll"
|
||||
!else
|
||||
File "icuin73.dll"
|
||||
File "icuuc73.dll"
|
||||
File "icuin74.dll"
|
||||
File "icuuc74.dll"
|
||||
File "Qt6Concurrent.dll"
|
||||
File "Qt6Core.dll"
|
||||
File "Qt6Gui.dll"
|
||||
@@ -853,6 +857,7 @@ Section "Uninstall"
|
||||
Delete "$INSTDIR\libidn2-0.dll"
|
||||
Delete "$INSTDIR\libintl-8.dll"
|
||||
Delete "$INSTDIR\libjpeg-9.dll"
|
||||
Delete "$INSTDIR\libkdsingleapplication-qt6.dll"
|
||||
Delete "$INSTDIR\liblzma-5.dll"
|
||||
Delete "$INSTDIR\libmp3lame-0.dll"
|
||||
Delete "$INSTDIR\libmpcdec.dll"
|
||||
@@ -897,6 +902,7 @@ Section "Uninstall"
|
||||
Delete "$INSTDIR\libabsl_examine_stack.dll"
|
||||
Delete "$INSTDIR\libabsl_hash.dll"
|
||||
Delete "$INSTDIR\libabsl_int128.dll"
|
||||
Delete "$INSTDIR\libabsl_kernel_timeout_internal.dll"
|
||||
Delete "$INSTDIR\libabsl_log_globals.dll"
|
||||
Delete "$INSTDIR\libabsl_log_internal_check_op.dll"
|
||||
Delete "$INSTDIR\libabsl_log_internal_conditions.dll"
|
||||
@@ -960,7 +966,7 @@ Section "Uninstall"
|
||||
Delete "$INSTDIR\brotlidec.dll"
|
||||
Delete "$INSTDIR\chromaprint.dll"
|
||||
Delete "$INSTDIR\ebur128.dll"
|
||||
Delete "$INSTDIR\faad.dll"
|
||||
Delete "$INSTDIR\faad-2.dll"
|
||||
Delete "$INSTDIR\fdk-aac.dll"
|
||||
Delete "$INSTDIR\ffi-7.dll"
|
||||
Delete "$INSTDIR\gio-2.0-0.dll"
|
||||
@@ -989,6 +995,7 @@ Section "Uninstall"
|
||||
Delete "$INSTDIR\harfbuzz.dll"
|
||||
Delete "$INSTDIR\intl-8.dll"
|
||||
Delete "$INSTDIR\jpeg62.dll"
|
||||
Delete "$INSTDIR\kdsingleapplication-qt6.dll"
|
||||
Delete "$INSTDIR\libbs2b.dll"
|
||||
Delete "$INSTDIR\libfaac_dll.dll"
|
||||
Delete "$INSTDIR\liblzma.dll"
|
||||
@@ -1042,7 +1049,7 @@ Section "Uninstall"
|
||||
|
||||
; Common files
|
||||
|
||||
Delete "$INSTDIR\icudt73.dll"
|
||||
Delete "$INSTDIR\icudt74.dll"
|
||||
Delete "$INSTDIR\libfftw3-3.dll"
|
||||
!ifdef debug
|
||||
Delete "$INSTDIR\libprotobufd.dll"
|
||||
@@ -1050,8 +1057,8 @@ Section "Uninstall"
|
||||
Delete "$INSTDIR\libprotobuf.dll"
|
||||
!endif
|
||||
!ifdef msvc && debug
|
||||
Delete "$INSTDIR\icuin73d.dll"
|
||||
Delete "$INSTDIR\icuuc73d.dll"
|
||||
Delete "$INSTDIR\icuin74d.dll"
|
||||
Delete "$INSTDIR\icuuc74d.dll"
|
||||
Delete "$INSTDIR\Qt6Concurrentd.dll"
|
||||
Delete "$INSTDIR\Qt6Cored.dll"
|
||||
Delete "$INSTDIR\Qt6Guid.dll"
|
||||
@@ -1059,8 +1066,8 @@ Section "Uninstall"
|
||||
Delete "$INSTDIR\Qt6Sqld.dll"
|
||||
Delete "$INSTDIR\Qt6Widgetsd.dll"
|
||||
!else
|
||||
Delete "$INSTDIR\icuin73.dll"
|
||||
Delete "$INSTDIR\icuuc73.dll"
|
||||
Delete "$INSTDIR\icuin74.dll"
|
||||
Delete "$INSTDIR\icuuc74.dll"
|
||||
Delete "$INSTDIR\Qt6Concurrent.dll"
|
||||
Delete "$INSTDIR\Qt6Core.dll"
|
||||
Delete "$INSTDIR\Qt6Gui.dll"
|
||||
|
||||
@@ -31,6 +31,7 @@
|
||||
#include <QMutex>
|
||||
#include <QLocalServer>
|
||||
#include <QProcess>
|
||||
#include <QDir>
|
||||
#include <QFile>
|
||||
#include <QList>
|
||||
#include <QQueue>
|
||||
@@ -245,8 +246,8 @@ void WorkerPool<HandlerType>::DoStart() {
|
||||
search_path << "/usr/libexec";
|
||||
search_path << "/usr/local/libexec";
|
||||
#endif
|
||||
#if defined(Q_OS_MACOS) && defined(USE_BUNDLE)
|
||||
search_path << QCoreApplication::applicationDirPath() + "/" + USE_BUNDLE_DIR;
|
||||
#if defined(Q_OS_MACOS)
|
||||
search_path << QDir::cleanPath(QCoreApplication::applicationDirPath() + "/../PlugIns");
|
||||
#endif
|
||||
|
||||
for (const QString &path_prefix : search_path) {
|
||||
|
||||
@@ -34,7 +34,7 @@ namespace GME {
|
||||
bool IsSupportedFormat(const QFileInfo &file_info);
|
||||
bool ReadFile(const QFileInfo &file_info, spb::tagreader::SongMetadata *song_info);
|
||||
|
||||
uint32_t UnpackBytes32(const char *const arr, size_t length);
|
||||
uint32_t UnpackBytes32(const char *const bytes, size_t length);
|
||||
|
||||
namespace SPC {
|
||||
// SPC SPEC: http://vspcplay.raphnet.net/spc_file_format.txt
|
||||
|
||||
@@ -113,6 +113,7 @@ set(SOURCES
|
||||
playlist/playlisttabbar.cpp
|
||||
playlist/playlistundocommands.cpp
|
||||
playlist/playlistview.cpp
|
||||
playlist/playlistproxystyle.cpp
|
||||
playlist/songloaderinserter.cpp
|
||||
playlist/songplaylistitem.cpp
|
||||
playlist/dynamicplaylistcontrols.cpp
|
||||
@@ -359,6 +360,7 @@ set(HEADERS
|
||||
playlist/playlistsequence.h
|
||||
playlist/playlisttabbar.h
|
||||
playlist/playlistview.h
|
||||
playlist/playlistproxystyle.h
|
||||
playlist/playlistitemmimedata.h
|
||||
playlist/songloaderinserter.h
|
||||
playlist/songmimedata.h
|
||||
|
||||
@@ -45,7 +45,6 @@
|
||||
#include "scrobbler/lastfmimport.h"
|
||||
#include "settings/collectionsettingspage.h"
|
||||
|
||||
using std::make_unique;
|
||||
using std::make_shared;
|
||||
|
||||
const char *SCollection::kSongsTable = "songs";
|
||||
@@ -93,14 +92,14 @@ SCollection::~SCollection() {
|
||||
|
||||
void SCollection::Init() {
|
||||
|
||||
watcher_ = make_unique<CollectionWatcher>(Song::Source::Collection);
|
||||
watcher_ = new CollectionWatcher(Song::Source::Collection);
|
||||
watcher_thread_ = new Thread(this);
|
||||
|
||||
watcher_thread_->SetIoPriority(Utilities::IoPriority::IOPRIO_CLASS_IDLE);
|
||||
|
||||
watcher_->moveToThread(watcher_thread_);
|
||||
|
||||
qLog(Debug) << &*watcher_ << "moved to thread" << watcher_thread_;
|
||||
qLog(Debug) << watcher_ << "moved to thread" << watcher_thread_;
|
||||
|
||||
watcher_thread_->start(QThread::IdlePriority);
|
||||
|
||||
@@ -108,20 +107,20 @@ void SCollection::Init() {
|
||||
watcher_->set_task_manager(app_->task_manager());
|
||||
|
||||
QObject::connect(&*backend_, &CollectionBackend::Error, this, &SCollection::Error);
|
||||
QObject::connect(&*backend_, &CollectionBackend::DirectoryDiscovered, &*watcher_, &CollectionWatcher::AddDirectory);
|
||||
QObject::connect(&*backend_, &CollectionBackend::DirectoryDeleted, &*watcher_, &CollectionWatcher::RemoveDirectory);
|
||||
QObject::connect(&*backend_, &CollectionBackend::DirectoryDiscovered, watcher_, &CollectionWatcher::AddDirectory);
|
||||
QObject::connect(&*backend_, &CollectionBackend::DirectoryDeleted, watcher_, &CollectionWatcher::RemoveDirectory);
|
||||
QObject::connect(&*backend_, &CollectionBackend::SongsRatingChanged, this, &SCollection::SongsRatingChanged);
|
||||
QObject::connect(&*backend_, &CollectionBackend::SongsStatisticsChanged, this, &SCollection::SongsPlaycountChanged);
|
||||
|
||||
QObject::connect(&*watcher_, &CollectionWatcher::NewOrUpdatedSongs, &*backend_, &CollectionBackend::AddOrUpdateSongs);
|
||||
QObject::connect(&*watcher_, &CollectionWatcher::SongsMTimeUpdated, &*backend_, &CollectionBackend::UpdateMTimesOnly);
|
||||
QObject::connect(&*watcher_, &CollectionWatcher::SongsDeleted, &*backend_, &CollectionBackend::DeleteSongs);
|
||||
QObject::connect(&*watcher_, &CollectionWatcher::SongsUnavailable, &*backend_, &CollectionBackend::MarkSongsUnavailable);
|
||||
QObject::connect(&*watcher_, &CollectionWatcher::SongsReadded, &*backend_, &CollectionBackend::MarkSongsUnavailable);
|
||||
QObject::connect(&*watcher_, &CollectionWatcher::SubdirsDiscovered, &*backend_, &CollectionBackend::AddOrUpdateSubdirs);
|
||||
QObject::connect(&*watcher_, &CollectionWatcher::SubdirsMTimeUpdated, &*backend_, &CollectionBackend::AddOrUpdateSubdirs);
|
||||
QObject::connect(&*watcher_, &CollectionWatcher::CompilationsNeedUpdating, &*backend_, &CollectionBackend::CompilationsNeedUpdating);
|
||||
QObject::connect(&*watcher_, &CollectionWatcher::UpdateLastSeen, &*backend_, &CollectionBackend::UpdateLastSeen);
|
||||
QObject::connect(watcher_, &CollectionWatcher::NewOrUpdatedSongs, &*backend_, &CollectionBackend::AddOrUpdateSongs);
|
||||
QObject::connect(watcher_, &CollectionWatcher::SongsMTimeUpdated, &*backend_, &CollectionBackend::UpdateMTimesOnly);
|
||||
QObject::connect(watcher_, &CollectionWatcher::SongsDeleted, &*backend_, &CollectionBackend::DeleteSongs);
|
||||
QObject::connect(watcher_, &CollectionWatcher::SongsUnavailable, &*backend_, &CollectionBackend::MarkSongsUnavailable);
|
||||
QObject::connect(watcher_, &CollectionWatcher::SongsReadded, &*backend_, &CollectionBackend::MarkSongsUnavailable);
|
||||
QObject::connect(watcher_, &CollectionWatcher::SubdirsDiscovered, &*backend_, &CollectionBackend::AddOrUpdateSubdirs);
|
||||
QObject::connect(watcher_, &CollectionWatcher::SubdirsMTimeUpdated, &*backend_, &CollectionBackend::AddOrUpdateSubdirs);
|
||||
QObject::connect(watcher_, &CollectionWatcher::CompilationsNeedUpdating, &*backend_, &CollectionBackend::CompilationsNeedUpdating);
|
||||
QObject::connect(watcher_, &CollectionWatcher::UpdateLastSeen, &*backend_, &CollectionBackend::UpdateLastSeen);
|
||||
|
||||
QObject::connect(&*app_->lastfm_import(), &LastFMImport::UpdateLastPlayed, &*backend_, &CollectionBackend::UpdateLastPlayed);
|
||||
QObject::connect(&*app_->lastfm_import(), &LastFMImport::UpdatePlayCount, &*backend_, &CollectionBackend::UpdatePlayCount);
|
||||
@@ -133,13 +132,13 @@ void SCollection::Init() {
|
||||
|
||||
void SCollection::Exit() {
|
||||
|
||||
wait_for_exit_ << &*backend_ << &*watcher_;
|
||||
wait_for_exit_ << &*backend_ << watcher_;
|
||||
|
||||
QObject::disconnect(&*backend_, nullptr, &*watcher_, nullptr);
|
||||
QObject::disconnect(&*watcher_, nullptr, &*backend_, nullptr);
|
||||
QObject::disconnect(&*backend_, nullptr, watcher_, nullptr);
|
||||
QObject::disconnect(watcher_, nullptr, &*backend_, nullptr);
|
||||
|
||||
QObject::connect(&*backend_, &CollectionBackend::ExitFinished, this, &SCollection::ExitReceived);
|
||||
QObject::connect(&*watcher_, &CollectionWatcher::ExitFinished, this, &SCollection::ExitReceived);
|
||||
QObject::connect(watcher_, &CollectionWatcher::ExitFinished, this, &SCollection::ExitReceived);
|
||||
backend_->ExitAsync();
|
||||
watcher_->Abort();
|
||||
watcher_->ExitAsync();
|
||||
|
||||
@@ -29,7 +29,6 @@
|
||||
#include <QHash>
|
||||
#include <QString>
|
||||
|
||||
#include "core/scoped_ptr.h"
|
||||
#include "core/shared_ptr.h"
|
||||
#include "core/song.h"
|
||||
|
||||
@@ -91,7 +90,7 @@ class SCollection : public QObject {
|
||||
SharedPtr<CollectionBackend> backend_;
|
||||
CollectionModel *model_;
|
||||
|
||||
ScopedPtr<CollectionWatcher> watcher_;
|
||||
CollectionWatcher *watcher_;
|
||||
Thread *watcher_thread_;
|
||||
QThread *original_thread_;
|
||||
|
||||
|
||||
@@ -401,7 +401,7 @@ SongList CollectionBackend::FindSongsInDirectory(const int id) {
|
||||
QSqlDatabase db(db_->Connect());
|
||||
|
||||
SqlQuery q(db);
|
||||
q.prepare(QString("SELECT ROWID, " + Song::kColumnSpec + " FROM %1 WHERE directory_id = :directory_id").arg(songs_table_));
|
||||
q.prepare(QString("SELECT ROWID, %1 FROM %2 WHERE directory_id = :directory_id").arg(Song::kColumnSpec, songs_table_));
|
||||
q.BindValue(":directory_id", id);
|
||||
if (!q.Exec()) {
|
||||
db_->ReportErrors(q);
|
||||
@@ -424,7 +424,7 @@ SongList CollectionBackend::SongsWithMissingFingerprint(const int id) {
|
||||
QSqlDatabase db(db_->Connect());
|
||||
|
||||
SqlQuery q(db);
|
||||
q.prepare(QString("SELECT ROWID, " + Song::kColumnSpec + " FROM %1 WHERE directory_id = :directory_id AND unavailable = 0 AND (fingerprint IS NULL OR fingerprint = '')").arg(songs_table_));
|
||||
q.prepare(QString("SELECT ROWID, %1 FROM %2 WHERE directory_id = :directory_id AND unavailable = 0 AND (fingerprint IS NULL OR fingerprint = '')").arg(Song::kColumnSpec, songs_table_));
|
||||
q.BindValue(":directory_id", id);
|
||||
if (!q.Exec()) {
|
||||
db_->ReportErrors(q);
|
||||
@@ -447,7 +447,7 @@ SongList CollectionBackend::SongsWithMissingLoudnessCharacteristics(const int id
|
||||
QSqlDatabase db(db_->Connect());
|
||||
|
||||
SqlQuery q(db);
|
||||
q.prepare(QString("SELECT ROWID, " + Song::kColumnSpec + " FROM %1 WHERE directory_id = :directory_id AND unavailable = 0 AND (ebur128_integrated_loudness_lufs IS NULL OR ebur128_loudness_range_lu IS NULL)").arg(songs_table_));
|
||||
q.prepare(QString("SELECT ROWID, %1 FROM %2 WHERE directory_id = :directory_id AND unavailable = 0 AND (ebur128_integrated_loudness_lufs IS NULL OR ebur128_loudness_range_lu IS NULL)").arg(Song::kColumnSpec, songs_table_));
|
||||
q.BindValue(":directory_id", id);
|
||||
if (!q.Exec()) {
|
||||
db_->ReportErrors(q);
|
||||
@@ -548,7 +548,7 @@ SongList CollectionBackend::GetAllSongs() {
|
||||
QSqlDatabase db(db_->Connect());
|
||||
|
||||
SqlQuery q(db);
|
||||
q.prepare(QString("SELECT ROWID, " + Song::kColumnSpec + " FROM %1").arg(songs_table_));
|
||||
q.prepare(QString("SELECT ROWID, %1 FROM %2").arg(Song::kColumnSpec, songs_table_));
|
||||
if (!q.Exec()) {
|
||||
db_->ReportErrors(q);
|
||||
return SongList();
|
||||
@@ -604,7 +604,7 @@ void CollectionBackend::AddOrUpdateSongs(const SongList &songs) {
|
||||
// Update
|
||||
{
|
||||
SqlQuery q(db);
|
||||
q.prepare(QString("UPDATE %1 SET " + Song::kUpdateSpec + " WHERE ROWID = :id").arg(songs_table_));
|
||||
q.prepare(QString("UPDATE %1 SET %2 WHERE ROWID = :id").arg(songs_table_, Song::kUpdateSpec));
|
||||
song.BindToQuery(&q);
|
||||
q.BindValue(":id", song.id());
|
||||
if (!q.Exec()) {
|
||||
@@ -615,7 +615,7 @@ void CollectionBackend::AddOrUpdateSongs(const SongList &songs) {
|
||||
|
||||
{
|
||||
SqlQuery q(db);
|
||||
q.prepare(QString("UPDATE %1 SET " + Song::kFtsUpdateSpec + " WHERE ROWID = :id").arg(fts_table_));
|
||||
q.prepare(QString("UPDATE %1 SET %2 WHERE ROWID = :id").arg(fts_table_, Song::kFtsUpdateSpec));
|
||||
song.BindToFtsQuery(&q);
|
||||
q.BindValue(":id", song.id());
|
||||
if (!q.Exec()) {
|
||||
@@ -643,7 +643,7 @@ void CollectionBackend::AddOrUpdateSongs(const SongList &songs) {
|
||||
// Update
|
||||
{
|
||||
SqlQuery q(db);
|
||||
q.prepare(QString("UPDATE %1 SET " + Song::kUpdateSpec + " WHERE ROWID = :id").arg(songs_table_));
|
||||
q.prepare(QString("UPDATE %1 SET %2 WHERE ROWID = :id").arg(songs_table_, Song::kUpdateSpec));
|
||||
new_song.BindToQuery(&q);
|
||||
q.BindValue(":id", new_song.id());
|
||||
if (!q.Exec()) {
|
||||
@@ -654,7 +654,7 @@ void CollectionBackend::AddOrUpdateSongs(const SongList &songs) {
|
||||
|
||||
{
|
||||
SqlQuery q(db);
|
||||
q.prepare(QString("UPDATE %1 SET " + Song::kFtsUpdateSpec + " WHERE ROWID = :id").arg(fts_table_));
|
||||
q.prepare(QString("UPDATE %1 SET %2 WHERE ROWID = :id").arg(fts_table_, Song::kFtsUpdateSpec));
|
||||
new_song.BindToFtsQuery(&q);
|
||||
q.BindValue(":id", new_song.id());
|
||||
if (!q.Exec()) {
|
||||
@@ -675,7 +675,7 @@ void CollectionBackend::AddOrUpdateSongs(const SongList &songs) {
|
||||
int id = -1;
|
||||
{ // Insert the row and create a new ID
|
||||
SqlQuery q(db);
|
||||
q.prepare(QString("INSERT INTO %1 (" + Song::kColumnSpec + ") VALUES (" + Song::kBindSpec + ")").arg(songs_table_));
|
||||
q.prepare(QString("INSERT INTO %1 (%2) VALUES (%3)").arg(songs_table_, Song::kColumnSpec, Song::kBindSpec));
|
||||
song.BindToQuery(&q);
|
||||
if (!q.Exec()) {
|
||||
db_->ReportErrors(q);
|
||||
@@ -689,7 +689,7 @@ void CollectionBackend::AddOrUpdateSongs(const SongList &songs) {
|
||||
|
||||
{ // Add to the FTS index
|
||||
SqlQuery q(db);
|
||||
q.prepare(QString("INSERT INTO %1 (ROWID, " + Song::kFtsColumnSpec + ") VALUES (:id, " + Song::kFtsBindSpec + ")").arg(fts_table_));
|
||||
q.prepare(QString("INSERT INTO %1 (ROWID, %2) VALUES (:id, %3)").arg(fts_table_, Song::kFtsColumnSpec, Song::kFtsBindSpec));
|
||||
q.BindValue(":id", id);
|
||||
song.BindToFtsQuery(&q);
|
||||
if (!q.Exec()) {
|
||||
@@ -750,7 +750,7 @@ void CollectionBackend::UpdateSongsBySongID(const SongMap &new_songs) {
|
||||
|
||||
{
|
||||
SqlQuery q(db);
|
||||
q.prepare(QString("UPDATE %1 SET " + Song::kUpdateSpec + " WHERE ROWID = :id").arg(songs_table_));
|
||||
q.prepare(QString("UPDATE %1 SET %2 WHERE ROWID = :id").arg(songs_table_, Song::kUpdateSpec));
|
||||
new_song.BindToQuery(&q);
|
||||
q.BindValue(":id", old_song.id());
|
||||
if (!q.Exec()) {
|
||||
@@ -760,7 +760,7 @@ void CollectionBackend::UpdateSongsBySongID(const SongMap &new_songs) {
|
||||
}
|
||||
{
|
||||
SqlQuery q(db);
|
||||
q.prepare(QString("UPDATE %1 SET " + Song::kFtsUpdateSpec + " WHERE ROWID = :id").arg(fts_table_));
|
||||
q.prepare(QString("UPDATE %1 SET %2 WHERE ROWID = :id").arg(fts_table_, Song::kFtsUpdateSpec));
|
||||
new_song.BindToFtsQuery(&q);
|
||||
q.BindValue(":id", old_song.id());
|
||||
if (!q.Exec()) {
|
||||
@@ -781,7 +781,7 @@ void CollectionBackend::UpdateSongsBySongID(const SongMap &new_songs) {
|
||||
int id = -1;
|
||||
{
|
||||
SqlQuery q(db);
|
||||
q.prepare(QString("INSERT INTO %1 (" + Song::kColumnSpec + ") VALUES (" + Song::kBindSpec + ")").arg(songs_table_));
|
||||
q.prepare(QString("INSERT INTO %1 (%2) VALUES (%3)").arg(songs_table_, Song::kColumnSpec, Song::kBindSpec));
|
||||
new_song.BindToQuery(&q);
|
||||
if (!q.Exec()) {
|
||||
db_->ReportErrors(q);
|
||||
@@ -795,7 +795,7 @@ void CollectionBackend::UpdateSongsBySongID(const SongMap &new_songs) {
|
||||
|
||||
{ // Add to the FTS index
|
||||
SqlQuery q(db);
|
||||
q.prepare(QString("INSERT INTO %1 (ROWID, " + Song::kFtsColumnSpec + ") VALUES (:id, " + Song::kFtsBindSpec + ")").arg(fts_table_));
|
||||
q.prepare(QString("INSERT INTO %1 (ROWID, %2) VALUES (:id, %3)").arg(fts_table_, Song::kFtsColumnSpec, Song::kFtsBindSpec));
|
||||
q.BindValue(":id", id);
|
||||
new_song.BindToFtsQuery(&q);
|
||||
if (!q.Exec()) {
|
||||
@@ -1133,7 +1133,7 @@ SongList CollectionBackend::GetSongsByForeignId(const QStringList &ids, const QS
|
||||
QString in = ids.join(",");
|
||||
|
||||
SqlQuery q(db);
|
||||
q.prepare(QString("SELECT %2.ROWID, " + Song::kColumnSpec + ", %2.%3 FROM %2, %1 WHERE %2.%3 IN (%4) AND %1.ROWID = %2.ROWID AND unavailable = 0").arg(songs_table_, table, column, in));
|
||||
q.prepare(QString("SELECT %3.ROWID, %2, %3.%4 FROM %3, %1 WHERE %3.%4 IN (in) AND %1.ROWID = %3.ROWID AND unavailable = 0").arg(songs_table_, Song::kColumnSpec, table, column, in));
|
||||
if (!q.Exec()) {
|
||||
db_->ReportErrors(q);
|
||||
return SongList();
|
||||
@@ -1164,7 +1164,7 @@ SongList CollectionBackend::GetSongsById(const QStringList &ids, QSqlDatabase &d
|
||||
QString in = ids.join(",");
|
||||
|
||||
SqlQuery q(db);
|
||||
q.prepare(QString("SELECT ROWID, " + Song::kColumnSpec + " FROM %1 WHERE ROWID IN (%2)").arg(songs_table_, in));
|
||||
q.prepare(QString("SELECT ROWID, %1 FROM %2 WHERE ROWID IN (%3)").arg(Song::kColumnSpec, songs_table_, in));
|
||||
if (!q.Exec()) {
|
||||
db_->ReportErrors(q);
|
||||
return SongList();
|
||||
@@ -1186,7 +1186,7 @@ Song CollectionBackend::GetSongByUrl(const QUrl &url, const qint64 beginning) {
|
||||
QSqlDatabase db(db_->Connect());
|
||||
|
||||
SqlQuery q(db);
|
||||
q.prepare(QString("SELECT ROWID, " + Song::kColumnSpec + " FROM %1 WHERE (url = :url1 OR url = :url2 OR url = :url3 OR url = :url4) AND beginning = :beginning AND unavailable = 0").arg(songs_table_));
|
||||
q.prepare(QString("SELECT ROWID, %1 FROM %2 WHERE (url = :url1 OR url = :url2 OR url = :url3 OR url = :url4) AND beginning = :beginning AND unavailable = 0").arg(Song::kColumnSpec, songs_table_));
|
||||
|
||||
q.BindValue(":url1", url);
|
||||
q.BindValue(":url2", url.toString());
|
||||
@@ -1216,7 +1216,7 @@ SongList CollectionBackend::GetSongsByUrl(const QUrl &url, const bool unavailabl
|
||||
QSqlDatabase db(db_->Connect());
|
||||
|
||||
SqlQuery q(db);
|
||||
q.prepare(QString("SELECT ROWID, " + Song::kColumnSpec + " FROM %1 WHERE (url = :url1 OR url = :url2 OR url = :url3 OR url = :url4) AND unavailable = :unavailable").arg(songs_table_));
|
||||
q.prepare(QString("SELECT ROWID, %1 FROM %2 WHERE (url = :url1 OR url = :url2 OR url = :url3 OR url = :url4) AND unavailable = :unavailable").arg(Song::kColumnSpec, songs_table_));
|
||||
|
||||
q.BindValue(":url1", url);
|
||||
q.BindValue(":url2", url.toString());
|
||||
@@ -1276,7 +1276,7 @@ SongList CollectionBackend::GetSongsBySongId(const QStringList &song_ids, QSqlDa
|
||||
QString in = song_ids2.join(",");
|
||||
|
||||
SqlQuery q(db);
|
||||
q.prepare(QString("SELECT ROWID, " + Song::kColumnSpec + " FROM %1 WHERE SONG_ID IN (%2)").arg(songs_table_, in));
|
||||
q.prepare(QString("SELECT ROWID, %1 FROM %2 WHERE SONG_ID IN (%3)").arg(Song::kColumnSpec, songs_table_, in));
|
||||
if (!q.Exec()) {
|
||||
db_->ReportErrors(q);
|
||||
return SongList();
|
||||
@@ -1299,7 +1299,7 @@ SongList CollectionBackend::GetSongsByFingerprint(const QString &fingerprint) {
|
||||
QSqlDatabase db(db_->Connect());
|
||||
|
||||
SqlQuery q(db);
|
||||
q.prepare(QString("SELECT ROWID, " + Song::kColumnSpec + " FROM %1 WHERE fingerprint = :fingerprint").arg(songs_table_));
|
||||
q.prepare(QString("SELECT ROWID, %1 FROM %2 WHERE fingerprint = :fingerprint").arg(Song::kColumnSpec, songs_table_));
|
||||
q.BindValue(":fingerprint", fingerprint);
|
||||
if (!q.Exec()) {
|
||||
db_->ReportErrors(q);
|
||||
@@ -1422,7 +1422,7 @@ bool CollectionBackend::UpdateCompilations(const QSqlDatabase &db, SongList &del
|
||||
|
||||
{ // Get song, so we can tell the model its updated
|
||||
SqlQuery q(db);
|
||||
q.prepare(QString("SELECT ROWID, " + Song::kColumnSpec + " FROM %1 WHERE (url = :url1 OR url = :url2 OR url = :url3 OR url = :url4) AND unavailable = 0").arg(songs_table_));
|
||||
q.prepare(QString("SELECT ROWID, %1 FROM %2 WHERE (url = :url1 OR url = :url2 OR url = :url3 OR url = :url4) AND unavailable = 0").arg(Song::kColumnSpec, songs_table_));
|
||||
q.BindValue(":url1", url);
|
||||
q.BindValue(":url2", url.toString());
|
||||
q.BindValue(":url3", url.toString(QUrl::FullyEncoded));
|
||||
@@ -2043,10 +2043,10 @@ SongList CollectionBackend::GetSongsBy(const QString &artist, const QString &alb
|
||||
SongList songs;
|
||||
SqlQuery q(db);
|
||||
if (album.isEmpty()) {
|
||||
q.prepare(QString("SELECT ROWID, " + Song::kColumnSpec + " FROM %1 WHERE artist = :artist COLLATE NOCASE AND title = :title COLLATE NOCASE").arg(songs_table_));
|
||||
q.prepare(QString("SELECT ROWID, %1 FROM %2 WHERE artist = :artist COLLATE NOCASE AND title = :title COLLATE NOCASE").arg(Song::kColumnSpec, songs_table_));
|
||||
}
|
||||
else {
|
||||
q.prepare(QString("SELECT ROWID, " + Song::kColumnSpec + " FROM %1 WHERE artist = :artist COLLATE NOCASE AND album = :album COLLATE NOCASE AND title = :title COLLATE NOCASE").arg(songs_table_));
|
||||
q.prepare(QString("SELECT ROWID, %1 FROM %2 WHERE artist = :artist COLLATE NOCASE AND album = :album COLLATE NOCASE AND title = :title COLLATE NOCASE").arg(Song::kColumnSpec, songs_table_));
|
||||
}
|
||||
q.BindValue(":artist", artist);
|
||||
if (!album.isEmpty()) q.BindValue(":album", album);
|
||||
|
||||
@@ -54,59 +54,48 @@ CollectionQuery::CollectionQuery(const QSqlDatabase &db, const QString &songs_ta
|
||||
// 3) Remove colons which don't correspond to column names.
|
||||
|
||||
// Split on whitespace
|
||||
QString filter_text = filter_options.filter_text().replace(QRegularExpression(":\\s+"), ":");
|
||||
#if QT_VERSION >= QT_VERSION_CHECK(5, 14, 0)
|
||||
QStringList tokens(filter_options.filter_text().split(QRegularExpression("\\s+"), Qt::SkipEmptyParts));
|
||||
QStringList tokens(filter_text.split(QRegularExpression("\\s+"), Qt::SkipEmptyParts));
|
||||
#else
|
||||
QStringList tokens(filter_options.filter_text().split(QRegularExpression("\\s+"), QString::SkipEmptyParts));
|
||||
QStringList tokens(filter_text.split(QRegularExpression("\\s+"), QString::SkipEmptyParts));
|
||||
#endif
|
||||
QString query;
|
||||
for (QString token : tokens) {
|
||||
token.remove('(');
|
||||
token.remove(')');
|
||||
token.remove('"');
|
||||
token.replace('-', ' ');
|
||||
token.remove('(')
|
||||
.remove(')')
|
||||
.remove('"')
|
||||
.replace('-', ' ');
|
||||
|
||||
if (token.contains(':')) {
|
||||
// Only prefix fts if the token is a valid column name.
|
||||
if (Song::kFtsColumns.contains("fts" + token.section(':', 0, 0), Qt::CaseInsensitive)) {
|
||||
// Account for multiple colons.
|
||||
QString columntoken = token.section(':', 0, 0, QString::SectionIncludeTrailingSep);
|
||||
QString subtoken = token.section(':', 1, -1);
|
||||
subtoken.replace(":", " ");
|
||||
subtoken = subtoken.trimmed();
|
||||
if (!subtoken.isEmpty()) {
|
||||
if (!query.isEmpty()) query.append(" ");
|
||||
query += "fts" + columntoken + "\"" + subtoken + "\"*";
|
||||
}
|
||||
}
|
||||
else if (Song::kNumericalColumns.contains(token.section(':', 0, 0), Qt::CaseInsensitive)) {
|
||||
// Account for multiple colons.
|
||||
QString columntoken = token.section(':', 0, 0);
|
||||
QString subtoken = token.section(':', 1, -1);
|
||||
subtoken = subtoken.trimmed();
|
||||
if (!subtoken.isEmpty()) {
|
||||
QString comparator = RemoveSqlOperator(subtoken);
|
||||
if (columntoken.compare("rating", Qt::CaseInsensitive) == 0) {
|
||||
subtoken.replace(":", " ");
|
||||
AddWhereRating(subtoken, comparator);
|
||||
}
|
||||
else if (columntoken.compare("length", Qt::CaseInsensitive) == 0) {
|
||||
// time is saved in nanoseconds, so add 9 0's
|
||||
QString parsedTime = QString::number(Utilities::ParseSearchTime(subtoken)) + "000000000";
|
||||
AddWhere(columntoken, parsedTime, comparator);
|
||||
}
|
||||
else {
|
||||
subtoken.replace(":", " ");
|
||||
AddWhere(columntoken, subtoken, comparator);
|
||||
}
|
||||
}
|
||||
}
|
||||
// not a valid filter, remove
|
||||
else {
|
||||
token.replace(":", " ");
|
||||
token = token.trimmed();
|
||||
const QString columntoken = token.section(':', 0, 0);
|
||||
QString subtoken = token.section(':', 1, -1).replace(":", " ").trimmed();
|
||||
if (subtoken.isEmpty()) continue;
|
||||
if (Song::kFtsColumns.contains("fts" + columntoken, Qt::CaseInsensitive)) {
|
||||
if (!query.isEmpty()) query.append(" ");
|
||||
query += "\"" + token + "\"*";
|
||||
query += "fts" + columntoken + ":\"" + subtoken + "\"*";
|
||||
}
|
||||
else if (Song::kNumericalColumns.contains(columntoken, Qt::CaseInsensitive)) {
|
||||
QString comparator = RemoveSqlOperator(subtoken);
|
||||
if (columntoken.compare("rating", Qt::CaseInsensitive) == 0) {
|
||||
AddWhereRating(subtoken, comparator);
|
||||
}
|
||||
else if (columntoken.compare("length", Qt::CaseInsensitive) == 0) {
|
||||
// Time is saved in nanoseconds, so add 9 0's
|
||||
QString parsedTime = QString::number(Utilities::ParseSearchTime(subtoken)) + "000000000";
|
||||
AddWhere(columntoken, parsedTime, comparator);
|
||||
}
|
||||
else {
|
||||
AddWhere(columntoken, subtoken, comparator);
|
||||
}
|
||||
}
|
||||
// Not a valid filter, remove
|
||||
else {
|
||||
token = token.replace(":", " ").trimmed();
|
||||
if (!token.isEmpty()) {
|
||||
if (!query.isEmpty()) query.append(" ");
|
||||
query += "\"" + token + "\"*";
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
@@ -155,6 +144,7 @@ QString CollectionQuery::RemoveSqlOperator(QString &token) {
|
||||
if (op == "!=") {
|
||||
op = "<>";
|
||||
}
|
||||
|
||||
return op;
|
||||
|
||||
}
|
||||
@@ -214,7 +204,7 @@ void CollectionQuery::AddWhereRating(const QVariant &value, const QString &op) {
|
||||
|
||||
// You can't query the database for a float, due to float precision errors,
|
||||
// So we have to use a certain tolerance, so that the searched value is definetly included.
|
||||
const float tolerance = 0.001;
|
||||
const float tolerance = 0.001F;
|
||||
if (op == "<") {
|
||||
AddWhere("rating", parsed_rating-tolerance, "<");
|
||||
}
|
||||
|
||||
@@ -30,7 +30,7 @@ class CollectionQueryOptions {
|
||||
explicit CollectionQueryOptions();
|
||||
|
||||
struct Where {
|
||||
explicit Where(const QString _column = QString(), const QVariant _value = QString(), const QString _op = QString()) : column(_column), value(_value), op(_op) {}
|
||||
explicit Where(const QString &_column = QString(), const QVariant &_value = QString(), const QString &_op = QString()) : column(_column), value(_value), op(_op) {}
|
||||
QString column;
|
||||
QVariant value;
|
||||
QString op;
|
||||
|
||||
@@ -71,6 +71,7 @@
|
||||
using namespace std::chrono_literals;
|
||||
|
||||
QStringList CollectionWatcher::sValidImages = QStringList() << "jpg" << "png" << "gif" << "jpeg";
|
||||
QStringList CollectionWatcher::kIgnoredExtensions = QStringList() << "tmp" << "tar" << "gz" << "bz2" << "xz" << "tbz" << "tgz" << "z" << "zip" << "rar";
|
||||
|
||||
CollectionWatcher::CollectionWatcher(Song::Source source, QObject *parent)
|
||||
: QObject(parent),
|
||||
@@ -509,7 +510,7 @@ void CollectionWatcher::ScanSubdirectory(const QString &path, const CollectionSu
|
||||
else {
|
||||
QString ext_part(ExtensionPart(child));
|
||||
QString dir_part(DirectoryPart(child));
|
||||
if (child_info.suffix() == "tmp" || child_info.baseName() == "qt_temp") {
|
||||
if (kIgnoredExtensions.contains(child_info.suffix(), Qt::CaseInsensitive) || child_info.baseName() == "qt_temp") {
|
||||
t->AddToProgress(1);
|
||||
}
|
||||
else if (sValidImages.contains(ext_part)) {
|
||||
|
||||
@@ -245,6 +245,7 @@ class CollectionWatcher : public QObject {
|
||||
CueParser *cue_parser_;
|
||||
|
||||
static QStringList sValidImages;
|
||||
static QStringList kIgnoredExtensions;
|
||||
|
||||
qint64 last_scan_time_;
|
||||
|
||||
|
||||
@@ -41,7 +41,6 @@
|
||||
#cmakedefine HAVE_TAGLIB_DSDIFFFILE
|
||||
|
||||
#cmakedefine USE_BUNDLE
|
||||
#define USE_BUNDLE_DIR "${USE_BUNDLE_DIR}"
|
||||
|
||||
#cmakedefine HAVE_TRANSLATIONS
|
||||
#cmakedefine INSTALL_TRANSLATIONS
|
||||
@@ -59,4 +58,6 @@
|
||||
|
||||
#cmakedefine HAVE_EBUR128
|
||||
|
||||
#cmakedefine HAVE_KDSINGLEAPPLICATION_OPTIONS
|
||||
|
||||
#endif // CONFIG_H_IN
|
||||
|
||||
@@ -87,8 +87,8 @@ class ContextAlbum : public QWidget {
|
||||
void AutomaticCoverSearchDone();
|
||||
void FadeCurrentCover(const qreal value);
|
||||
void FadeCurrentCoverFinished();
|
||||
void FadePreviousCover(SharedPtr<PreviousCover> previouscover);
|
||||
void FadePreviousCoverFinished(SharedPtr<PreviousCover> previouscover);
|
||||
void FadePreviousCover(SharedPtr<PreviousCover> previous_cover);
|
||||
void FadePreviousCoverFinished(SharedPtr<PreviousCover> previous_cover);
|
||||
|
||||
public slots:
|
||||
void SearchCoverInProgress();
|
||||
|
||||
@@ -88,6 +88,7 @@ void RegisterMetaTypes() {
|
||||
qRegisterMetaType<QVector<int>>("QVector<int>");
|
||||
qRegisterMetaType<QList<QUrl>>("QList<QUrl>");
|
||||
qRegisterMetaType<QFileInfo>("QFileInfo");
|
||||
qRegisterMetaType<QAbstractSocket::SocketState>("QAbstractSocket::SocketState");
|
||||
qRegisterMetaType<QNetworkReply*>("QNetworkReply*");
|
||||
qRegisterMetaType<QNetworkReply**>("QNetworkReply**");
|
||||
qRegisterMetaType<QItemSelection>("QItemSelection");
|
||||
@@ -110,6 +111,7 @@ void RegisterMetaTypes() {
|
||||
#ifdef HAVE_GSTREAMER
|
||||
qRegisterMetaType<GstBuffer*>("GstBuffer*");
|
||||
qRegisterMetaType<GstElement*>("GstElement*");
|
||||
qRegisterMetaType<GstState>("GstState");
|
||||
qRegisterMetaType<GstEnginePipeline*>("GstEnginePipeline*");
|
||||
#endif
|
||||
qRegisterMetaType<CollectionDirectory>("CollectionDirectory");
|
||||
|
||||
@@ -367,6 +367,7 @@ double Mpris2::Rating() const {
|
||||
}
|
||||
|
||||
void Mpris2::SetRating(double rating) {
|
||||
|
||||
if (rating > 1.0) {
|
||||
rating = 1.0;
|
||||
}
|
||||
@@ -374,7 +375,8 @@ void Mpris2::SetRating(double rating) {
|
||||
rating = -1.0;
|
||||
}
|
||||
|
||||
app_->playlist_manager()->RateCurrentSong(rating);
|
||||
app_->playlist_manager()->RateCurrentSong(static_cast<float>(rating));
|
||||
|
||||
}
|
||||
|
||||
QString Mpris2::current_track_id() const {
|
||||
|
||||
@@ -385,7 +385,7 @@ void Player::NextItem(const EngineBase::TrackChangeFlags change, const Playlist:
|
||||
Playlist *active_playlist = app_->playlist_manager()->active();
|
||||
|
||||
// If we received too many errors in auto change, with repeat enabled, we stop
|
||||
if (change == EngineBase::TrackChangeType::Auto) {
|
||||
if (change & EngineBase::TrackChangeType::Auto) {
|
||||
const PlaylistSequence::RepeatMode repeat_mode = active_playlist->sequence()->repeat_mode();
|
||||
if (repeat_mode != PlaylistSequence::RepeatMode::Off) {
|
||||
if ((repeat_mode == PlaylistSequence::RepeatMode::Track && nb_errors_received_ >= 3) || (nb_errors_received_ >= app_->playlist_manager()->active()->filter()->rowCount())) {
|
||||
@@ -706,7 +706,7 @@ void Player::PlayAt(const int index, const quint64 offset_nanosec, EngineBase::T
|
||||
pause_time_ = QDateTime();
|
||||
play_offset_nanosec_ = offset_nanosec;
|
||||
|
||||
if (current_item_ && change == EngineBase::TrackChangeType::Manual && engine_->position_nanosec() != engine_->length_nanosec()) {
|
||||
if (current_item_ && change & EngineBase::TrackChangeType::Manual && engine_->position_nanosec() != engine_->length_nanosec()) {
|
||||
emit TrackSkipped(current_item_);
|
||||
}
|
||||
|
||||
@@ -867,6 +867,9 @@ void Player::TrackAboutToEnd() {
|
||||
if (engine_->is_autocrossfade_enabled()) {
|
||||
// Crossfade is on, so just start playing the next track. The current one will fade out, and the new one will fade in
|
||||
|
||||
// If the decoding failed, current_item_ will be null
|
||||
if (!current_item_) return;
|
||||
|
||||
// But, if there's no next track, and we don't want to fade out, then do nothing and just let the track finish to completion.
|
||||
if (!engine_->is_fadeout_enabled() && !has_next_row) return;
|
||||
|
||||
|
||||
@@ -32,10 +32,9 @@ class PoTranslator : public QTranslator {
|
||||
public:
|
||||
PoTranslator(QObject *parent = nullptr) : QTranslator(parent) {}
|
||||
QString translate(const char *context, const char *source_text, const char *disambiguation = nullptr, int n = -1) const override {
|
||||
Q_UNUSED(n);
|
||||
QString ret = QTranslator::translate(context, source_text, disambiguation);
|
||||
QString ret = QTranslator::translate(context, source_text, disambiguation, n);
|
||||
if (!ret.isEmpty()) return ret;
|
||||
return QTranslator::translate(nullptr, source_text, disambiguation);
|
||||
return QTranslator::translate(nullptr, source_text, disambiguation, n);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -952,7 +952,7 @@ QString Song::TextForFiletype(const FileType filetype) {
|
||||
case FileType::OggVorbis: return "Ogg Vorbis";
|
||||
case FileType::OggOpus: return "Ogg Opus";
|
||||
case FileType::OggSpeex: return "Ogg Speex";
|
||||
case FileType::MPEG: return "MP3";
|
||||
case FileType::MPEG: return "MPEG";
|
||||
case FileType::MP4: return "MP4 AAC";
|
||||
case FileType::ASF: return "Windows Media audio";
|
||||
case FileType::AIFF: return "AIFF";
|
||||
@@ -1073,6 +1073,7 @@ Song::FileType Song::FiletypeByMimetype(const QString &mimetype) {
|
||||
else if (mimetype.compare("audio/aac", Qt::CaseInsensitive) == 0) return FileType::MP4;
|
||||
else if (mimetype.compare("audio/x-wma", Qt::CaseInsensitive) == 0) return FileType::ASF;
|
||||
else if (mimetype.compare("audio/aiff", Qt::CaseInsensitive) == 0 || mimetype.compare("audio/x-aiff", Qt::CaseInsensitive) == 0) return FileType::AIFF;
|
||||
else if (mimetype.compare("audio/x-musepack", Qt::CaseInsensitive) == 0) return FileType::MPC;
|
||||
else if (mimetype.compare("application/x-project", Qt::CaseInsensitive) == 0) return FileType::MPC;
|
||||
else if (mimetype.compare("audio/x-dsf", Qt::CaseInsensitive) == 0) return FileType::DSF;
|
||||
else if (mimetype.compare("audio/x-dsd", Qt::CaseInsensitive) == 0) return FileType::DSDIFF;
|
||||
@@ -1094,11 +1095,13 @@ Song::FileType Song::FiletypeByDescription(const QString &text) {
|
||||
else if (text.compare("Vorbis", Qt::CaseInsensitive) == 0) return FileType::OggVorbis;
|
||||
else if (text.compare("Opus", Qt::CaseInsensitive) == 0) return FileType::OggOpus;
|
||||
else if (text.compare("Speex", Qt::CaseInsensitive) == 0) return FileType::OggSpeex;
|
||||
else if (text.compare("MPEG-1 Layer 2 (MP2)", Qt::CaseInsensitive) == 0) return FileType::MPEG;
|
||||
else if (text.compare("MPEG-1 Layer 3 (MP3)", Qt::CaseInsensitive) == 0) return FileType::MPEG;
|
||||
else if (text.compare("MPEG-4 AAC", Qt::CaseInsensitive) == 0) return FileType::MP4;
|
||||
else if (text.compare("WMA", Qt::CaseInsensitive) == 0) return FileType::ASF;
|
||||
else if (text.compare("Audio Interchange File Format", Qt::CaseInsensitive) == 0) return FileType::AIFF;
|
||||
else if (text.compare("MPC", Qt::CaseInsensitive) == 0) return FileType::MPC;
|
||||
else if (text.compare("Musepack (MPC)", Qt::CaseInsensitive) == 0) return FileType::MPC;
|
||||
else if (text.compare("audio/x-dsf", Qt::CaseInsensitive) == 0) return FileType::DSF;
|
||||
else if (text.compare("audio/x-dsd", Qt::CaseInsensitive) == 0) return FileType::DSDIFF;
|
||||
else if (text.compare("audio/x-ffmpeg-parsed-ape", Qt::CaseInsensitive) == 0) return FileType::APE;
|
||||
@@ -1118,6 +1121,7 @@ Song::FileType Song::FiletypeByExtension(const QString &ext) {
|
||||
else if (ext.compare("ogg", Qt::CaseInsensitive) == 0 || ext.compare("oga", Qt::CaseInsensitive) == 0) return FileType::OggVorbis;
|
||||
else if (ext.compare("opus", Qt::CaseInsensitive) == 0) return FileType::OggOpus;
|
||||
else if (ext.compare("speex", Qt::CaseInsensitive) == 0 || ext.compare("spx", Qt::CaseInsensitive) == 0) return FileType::OggSpeex;
|
||||
else if (ext.compare("mp2", Qt::CaseInsensitive) == 0) return FileType::MPEG;
|
||||
else if (ext.compare("mp3", Qt::CaseInsensitive) == 0) return FileType::MPEG;
|
||||
else if (ext.compare("mp4", Qt::CaseInsensitive) == 0 || ext.compare("m4a", Qt::CaseInsensitive) == 0 || ext.compare("aac", Qt::CaseInsensitive) == 0) return FileType::MP4;
|
||||
else if (ext.compare("asf", Qt::CaseInsensitive) == 0 || ext.compare("wma", Qt::CaseInsensitive) == 0) return FileType::ASF;
|
||||
|
||||
@@ -251,7 +251,7 @@ void AlbumCoverLoader::InitArt(TaskPtr task) {
|
||||
|
||||
}
|
||||
|
||||
AlbumCoverLoader::LoadImageResult AlbumCoverLoader::LoadImage(TaskPtr task, const AlbumCoverLoaderOptions::Type &type) {
|
||||
AlbumCoverLoader::LoadImageResult AlbumCoverLoader::LoadImage(TaskPtr task, const AlbumCoverLoaderOptions::Type type) {
|
||||
|
||||
switch (type) {
|
||||
case AlbumCoverLoaderOptions::Type::Unset:{
|
||||
|
||||
@@ -109,7 +109,7 @@ class AlbumCoverLoader : public QObject {
|
||||
quint64 EnqueueTask(TaskPtr task);
|
||||
void ProcessTask(TaskPtr task);
|
||||
void InitArt(TaskPtr task);
|
||||
LoadImageResult LoadImage(TaskPtr task, const AlbumCoverLoaderOptions::Type &type);
|
||||
LoadImageResult LoadImage(TaskPtr task, const AlbumCoverLoaderOptions::Type type);
|
||||
LoadImageResult LoadEmbeddedImage(TaskPtr task);
|
||||
LoadImageResult LoadUrlImage(TaskPtr task, const AlbumCoverLoaderResult::Type result_type, const QUrl &cover_url);
|
||||
LoadImageResult LoadLocalUrlImage(TaskPtr task, const AlbumCoverLoaderResult::Type result_type, const QUrl &cover_url);
|
||||
|
||||
@@ -41,10 +41,10 @@ class AlbumCoverLoaderResult {
|
||||
|
||||
explicit AlbumCoverLoaderResult(const bool _success = false,
|
||||
const Type _type = Type::None,
|
||||
AlbumCoverImageResult _album_cover = AlbumCoverImageResult(),
|
||||
const AlbumCoverImageResult &_album_cover = AlbumCoverImageResult(),
|
||||
const QImage &_image_scaled = QImage(),
|
||||
const QUrl _art_manual_updated = QUrl(),
|
||||
const QUrl _art_automatic_updated = QUrl()) :
|
||||
const QUrl &_art_manual_updated = QUrl(),
|
||||
const QUrl &_art_automatic_updated = QUrl()) :
|
||||
success(_success),
|
||||
type(_type),
|
||||
album_cover(_album_cover),
|
||||
|
||||
@@ -36,7 +36,7 @@
|
||||
#include "core/scopedtransaction.h"
|
||||
#include "devicedatabasebackend.h"
|
||||
|
||||
const int DeviceDatabaseBackend::kDeviceSchemaVersion = 4;
|
||||
const int DeviceDatabaseBackend::kDeviceSchemaVersion = 5;
|
||||
|
||||
DeviceDatabaseBackend::DeviceDatabaseBackend(QObject *parent)
|
||||
: QObject(parent),
|
||||
|
||||
@@ -120,7 +120,7 @@ void DeviceInfo::LoadIcon(const QVariantList &icons, const QString &name_hint) {
|
||||
if (!icon.isNull() && icon.userType() == QMetaType::QString) {
|
||||
QString icon_name = icon.toString();
|
||||
if (!icon_name.isEmpty()) {
|
||||
QString hint = QString(icons.first().toString() + name_hint).toLower();
|
||||
QString hint = icons.first().toString().toLower() + name_hint.toLower();
|
||||
if (hint.contains("phone")) icon_name_ = "device-phone";
|
||||
else if (hint.contains("ipod") || hint.contains("apple")) icon_name_ = "device-ipod";
|
||||
else if ((hint.contains("usb")) && (hint.contains("reader"))) icon_name_ = "device-usb-flash";
|
||||
|
||||
@@ -638,7 +638,7 @@ SharedPtr<ConnectedDevice> DeviceManager::Connect(DeviceInfo *info) {
|
||||
Q_ARG(QUrl, device_url),
|
||||
Q_ARG(DeviceLister*, info->BestBackend()->lister_),
|
||||
Q_ARG(QString, info->BestBackend()->unique_id_),
|
||||
Q_ARG(DeviceManager*, this),
|
||||
Q_ARG(SharedPtr<DeviceManager>, app_->device_manager()),
|
||||
Q_ARG(Application*, app_),
|
||||
Q_ARG(int, info->database_id_),
|
||||
Q_ARG(bool, first_time));
|
||||
|
||||
@@ -31,7 +31,7 @@
|
||||
|
||||
#include "playlist/playlist.h"
|
||||
|
||||
SavePlaylistsDialog::SavePlaylistsDialog(const QStringList &types, const QString &default_extension) : ui_(new Ui_SavePlaylistsDialog) {
|
||||
SavePlaylistsDialog::SavePlaylistsDialog(const QStringList &types, const QString &default_extension, QWidget *parent) : QDialog(parent), ui_(new Ui_SavePlaylistsDialog) {
|
||||
|
||||
ui_->setupUi(this);
|
||||
|
||||
|
||||
@@ -31,7 +31,7 @@ class SavePlaylistsDialog : public QDialog {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit SavePlaylistsDialog(const QStringList &types, const QString &default_extension);
|
||||
explicit SavePlaylistsDialog(const QStringList &types, const QString &default_extension, QWidget *parent = nullptr);
|
||||
~SavePlaylistsDialog();
|
||||
|
||||
QString path() const { return ui_->lineedit_path->text(); }
|
||||
|
||||
@@ -75,7 +75,7 @@ class EngineBase : public QObject {
|
||||
Error
|
||||
};
|
||||
|
||||
enum TrackChangeType {
|
||||
enum class TrackChangeType {
|
||||
// One of:
|
||||
First = 0x01,
|
||||
Manual = 0x02,
|
||||
@@ -254,5 +254,6 @@ Q_DECLARE_METATYPE(EngineBase::Type)
|
||||
Q_DECLARE_METATYPE(EngineBase::State)
|
||||
Q_DECLARE_METATYPE(EngineBase::TrackChangeType)
|
||||
Q_DECLARE_METATYPE(EngineBase::OutputDetails)
|
||||
Q_DECLARE_OPERATORS_FOR_FLAGS(EngineBase::TrackChangeFlags)
|
||||
|
||||
#endif // ENGINEBASE_H
|
||||
|
||||
@@ -21,6 +21,8 @@
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#include <QtGlobal>
|
||||
|
||||
#include <cstdint>
|
||||
#include <cstring>
|
||||
#include <cmath>
|
||||
@@ -30,7 +32,13 @@
|
||||
#include <gst/gst.h>
|
||||
#include <gst/audio/audio.h>
|
||||
|
||||
#include <QtGlobal>
|
||||
#ifdef Q_OS_UNIX
|
||||
# include <pthread.h>
|
||||
#endif
|
||||
#ifdef Q_OS_WIN32
|
||||
# include <windows.h>
|
||||
#endif
|
||||
|
||||
#include <QObject>
|
||||
#include <QCoreApplication>
|
||||
#include <QtConcurrent>
|
||||
@@ -94,11 +102,12 @@ GstEnginePipeline::GstEnginePipeline(QObject *parent)
|
||||
next_end_offset_nanosec_(-1),
|
||||
ignore_next_seek_(false),
|
||||
ignore_tags_(false),
|
||||
pipeline_is_initialized_(false),
|
||||
pipeline_is_active_(false),
|
||||
pipeline_is_connected_(false),
|
||||
pending_seek_nanosec_(-1),
|
||||
last_known_position_ns_(0),
|
||||
next_uri_set_(false),
|
||||
next_uri_reset_(false),
|
||||
ebur128_loudness_normalizing_gain_db_(0.0),
|
||||
volume_set_(false),
|
||||
volume_internal_(-1.0),
|
||||
@@ -281,6 +290,25 @@ void GstEnginePipeline::set_fading_enabled(const bool enabled) {
|
||||
fading_enabled_ = enabled;
|
||||
}
|
||||
|
||||
QString GstEnginePipeline::GstStateText(const GstState state) {
|
||||
|
||||
switch (state) {
|
||||
case GST_STATE_VOID_PENDING:
|
||||
return "Pending";
|
||||
case GST_STATE_NULL:
|
||||
return "Null";
|
||||
case GST_STATE_READY:
|
||||
return "Ready";
|
||||
case GST_STATE_PAUSED:
|
||||
return "Paused";
|
||||
case GST_STATE_PLAYING:
|
||||
return "Playing";
|
||||
default:
|
||||
return "Unknown";
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
GstElement *GstEnginePipeline::CreateElement(const QString &factory_name, const QString &name, GstElement *bin, QString &error) const {
|
||||
|
||||
QString unique_name = QString("pipeline") + "-" + QString::number(id_) + "-" + (name.isEmpty() ? factory_name : name);
|
||||
@@ -322,22 +350,6 @@ bool GstEnginePipeline::InitFromUrl(const QUrl &media_url, const QUrl &stream_ur
|
||||
|
||||
if (!InitAudioBin(error)) return false;
|
||||
|
||||
#ifdef Q_OS_WIN32
|
||||
if (volume_enabled_ && !volume_ && volume_sw_) {
|
||||
SetupVolume(volume_sw_);
|
||||
}
|
||||
#else
|
||||
if (volume_enabled_ && !volume_) {
|
||||
if (output_ == GstEngine::kAutoSink) {
|
||||
element_added_cb_id_ = CHECKED_GCONNECT(G_OBJECT(audiobin_), "deep-element-added", &ElementAddedCallback, this);
|
||||
}
|
||||
else if (volume_sw_) {
|
||||
qLog(Debug) << output_ << "does not have volume, using own volume.";
|
||||
SetupVolume(volume_sw_);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
// Set playbin's sink to be our custom audio-sink.
|
||||
g_object_set(GST_OBJECT(pipeline_), "audio-sink", audiobin_, nullptr);
|
||||
|
||||
@@ -507,6 +519,8 @@ bool GstEnginePipeline::InitAudioBin(QString &error) {
|
||||
if (!volume_sw_) {
|
||||
return false;
|
||||
}
|
||||
qLog(Debug) << output_ << "does not have volume, using own volume.";
|
||||
SetupVolume(volume_sw_);
|
||||
}
|
||||
|
||||
if (fading_enabled_) {
|
||||
@@ -644,11 +658,18 @@ bool GstEnginePipeline::InitAudioBin(QString &error) {
|
||||
// We set this on this queue instead of the playbin because setting it on the playbin only affects network sources.
|
||||
// Disable the default buffer and byte limits, so we only buffer based on time.
|
||||
|
||||
qLog(Debug) << "Setting buffer duration:" << buffer_duration_nanosec_ << "low watermark:" << buffer_low_watermark_ << "high watermark:" << buffer_high_watermark_;
|
||||
g_object_set(G_OBJECT(audioqueue_), "use-buffering", true, nullptr);
|
||||
g_object_set(G_OBJECT(audioqueue_), "max-size-buffers", 0, nullptr);
|
||||
g_object_set(G_OBJECT(audioqueue_), "max-size-bytes", 0, nullptr);
|
||||
g_object_set(G_OBJECT(audioqueue_), "max-size-time", buffer_duration_nanosec_, nullptr);
|
||||
|
||||
if (buffer_duration_nanosec_ > 0) {
|
||||
qLog(Debug) << "Setting buffer duration:" << buffer_duration_nanosec_ << "low watermark:" << buffer_low_watermark_ << "high watermark:" << buffer_high_watermark_;
|
||||
g_object_set(G_OBJECT(audioqueue_), "max-size-buffers", 0, nullptr);
|
||||
g_object_set(G_OBJECT(audioqueue_), "max-size-bytes", 0, nullptr);
|
||||
g_object_set(G_OBJECT(audioqueue_), "max-size-time", buffer_duration_nanosec_, nullptr);
|
||||
}
|
||||
else {
|
||||
qLog(Debug) << "Setting low watermark:" << buffer_low_watermark_ << "high watermark:" << buffer_high_watermark_;
|
||||
}
|
||||
|
||||
g_object_set(G_OBJECT(audioqueue_), "low-watermark", buffer_low_watermark_, nullptr);
|
||||
g_object_set(G_OBJECT(audioqueue_), "high-watermark", buffer_high_watermark_, nullptr);
|
||||
|
||||
@@ -929,7 +950,7 @@ void GstEnginePipeline::PadAddedCallback(GstElement *element, GstPad *pad, gpoin
|
||||
instance->playbin_probe_cb_id_ = gst_pad_add_probe(pad, static_cast<GstPadProbeType>(GST_PAD_PROBE_TYPE_BUFFER | GST_PAD_PROBE_TYPE_EVENT_DOWNSTREAM | GST_PAD_PROBE_TYPE_EVENT_FLUSH), PlaybinProbeCallback, instance, nullptr);
|
||||
|
||||
instance->pipeline_is_connected_ = true;
|
||||
if (instance->pending_seek_nanosec_ != -1 && instance->pipeline_is_initialized_) {
|
||||
if (instance->pending_seek_nanosec_ != -1 && instance->pipeline_is_active_) {
|
||||
QMetaObject::invokeMethod(instance, "Seek", Qt::QueuedConnection, Q_ARG(qint64, instance->pending_seek_nanosec_));
|
||||
}
|
||||
|
||||
@@ -1252,6 +1273,7 @@ void GstEnginePipeline::StreamStartMessageReceived() {
|
||||
if (next_uri_set_) {
|
||||
qLog(Debug) << "Stream changed from URL" << gst_url_ << "to" << next_gst_url_;
|
||||
next_uri_set_ = false;
|
||||
next_uri_reset_ = false;
|
||||
about_to_finish_ = false;
|
||||
media_url_ = next_media_url_;
|
||||
stream_url_ = next_stream_url_;
|
||||
@@ -1274,16 +1296,17 @@ void GstEnginePipeline::TaskEnterCallback(GstTask *task, GThread *thread, gpoint
|
||||
Q_UNUSED(thread)
|
||||
Q_UNUSED(self)
|
||||
|
||||
// Bump the priority of the thread only on macOS
|
||||
|
||||
#ifdef Q_OS_MACOS
|
||||
sched_param param;
|
||||
#ifdef Q_OS_UNIX
|
||||
sched_param param{};
|
||||
memset(¶m, 0, sizeof(param));
|
||||
|
||||
param.sched_priority = 99;
|
||||
param.sched_priority = 40;
|
||||
pthread_setschedparam(pthread_self(), SCHED_RR, ¶m);
|
||||
#endif
|
||||
|
||||
#ifdef Q_OS_WIN32
|
||||
SetThreadPriority(GetCurrentThread(), THREAD_PRIORITY_HIGHEST);
|
||||
#endif
|
||||
|
||||
}
|
||||
|
||||
void GstEnginePipeline::ElementMessageReceived(GstMessage *msg) {
|
||||
@@ -1312,7 +1335,7 @@ void GstEnginePipeline::ErrorMessageReceived(GstMessage *msg) {
|
||||
g_error_free(error);
|
||||
g_free(debugs);
|
||||
|
||||
if (pipeline_is_initialized_ && next_uri_set_ && (domain == GST_CORE_ERROR || domain == GST_RESOURCE_ERROR || domain == GST_STREAM_ERROR)) {
|
||||
if (pipeline_is_active_ && next_uri_set_ && (domain == GST_CORE_ERROR || domain == GST_RESOURCE_ERROR || domain == GST_STREAM_ERROR)) {
|
||||
// A track is still playing and the next uri is not playable. We ignore the error here so it can play until the end.
|
||||
// But there is no message send to the bus when the current track finishes, we have to add an EOS ourself.
|
||||
qLog(Info) << "Ignoring error" << domain << code << message << debugstr << "when loading next track";
|
||||
@@ -1430,26 +1453,44 @@ void GstEnginePipeline::StateChangedMessageReceived(GstMessage *msg) {
|
||||
GstState old_state = GST_STATE_NULL, new_state = GST_STATE_NULL, pending = GST_STATE_NULL;
|
||||
gst_message_parse_state_changed(msg, &old_state, &new_state, &pending);
|
||||
|
||||
if (!pipeline_is_initialized_ && (new_state == GST_STATE_PAUSED || new_state == GST_STATE_PLAYING)) {
|
||||
qLog(Debug) << "Pipeline initialized: State changed from" << old_state << "to" << new_state;
|
||||
pipeline_is_initialized_ = true;
|
||||
if (!volume_set_) {
|
||||
SetVolume(volume_percent_);
|
||||
}
|
||||
if (pending_seek_nanosec_ != -1 && pipeline_is_connected_) {
|
||||
QMetaObject::invokeMethod(this, "Seek", Qt::QueuedConnection, Q_ARG(qint64, pending_seek_nanosec_));
|
||||
qLog(Debug) << "Pipeline state changed from" << GstStateText(old_state) << "to" << GstStateText(new_state);
|
||||
|
||||
if (!pipeline_is_active_ && (new_state == GST_STATE_PAUSED || new_state == GST_STATE_PLAYING)) {
|
||||
qLog(Debug) << "Pipeline is active";
|
||||
pipeline_is_active_ = true;
|
||||
if (pipeline_is_connected_) {
|
||||
if (!volume_set_) {
|
||||
SetVolume(volume_percent_);
|
||||
}
|
||||
if (pending_seek_nanosec_ != -1) {
|
||||
if (next_uri_reset_ && new_state == GST_STATE_PAUSED) {
|
||||
qLog(Debug) << "Reverting next uri and going to playing state.";
|
||||
next_uri_reset_ = false;
|
||||
SeekDelayed(pending_seek_nanosec_);
|
||||
SetStateDelayed(GST_STATE_PLAYING);
|
||||
}
|
||||
else {
|
||||
SeekQueued(pending_seek_nanosec_);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (pipeline_is_initialized_ && new_state != GST_STATE_PAUSED && new_state != GST_STATE_PLAYING) {
|
||||
qLog(Debug) << "Pipeline uninitialized: State changed from" << old_state << "to" << new_state;
|
||||
pipeline_is_initialized_ = false;
|
||||
|
||||
else if (pipeline_is_active_ && new_state != GST_STATE_PAUSED && new_state != GST_STATE_PLAYING) {
|
||||
qLog(Debug) << "Pipeline is inactive";
|
||||
pipeline_is_active_ = false;
|
||||
if (next_uri_set_ && new_state == GST_STATE_READY) {
|
||||
// Revert uri and go back to PLAY state again
|
||||
next_uri_set_ = false;
|
||||
g_object_set(G_OBJECT(pipeline_), "uri", gst_url_.constData(), nullptr);
|
||||
SetState(GST_STATE_PLAYING);
|
||||
if (pending_seek_nanosec_ == -1) {
|
||||
qLog(Debug) << "Reverting next uri and going to playing state.";
|
||||
SetState(GST_STATE_PLAYING);
|
||||
}
|
||||
else {
|
||||
qLog(Debug) << "Reverting next uri and going to paused state.";
|
||||
next_uri_reset_ = true;
|
||||
SetState(GST_STATE_PAUSED);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1487,8 +1528,11 @@ void GstEnginePipeline::BufferingMessageReceived(GstMessage *msg) {
|
||||
|
||||
qint64 GstEnginePipeline::position() const {
|
||||
|
||||
if (pipeline_is_initialized_) {
|
||||
gst_element_query_position(pipeline_, GST_FORMAT_TIME, &last_known_position_ns_);
|
||||
if (pipeline_is_active_) {
|
||||
gint64 current_position = 0;
|
||||
if (gst_element_query_position(pipeline_, GST_FORMAT_TIME, ¤t_position)) {
|
||||
last_known_position_ns_ = current_position;
|
||||
}
|
||||
}
|
||||
|
||||
return last_known_position_ns_;
|
||||
@@ -1516,7 +1560,18 @@ GstState GstEnginePipeline::state() const {
|
||||
}
|
||||
|
||||
QFuture<GstStateChangeReturn> GstEnginePipeline::SetState(const GstState state) {
|
||||
|
||||
qLog(Debug) << "Setting pipeline state to" << GstStateText(state);
|
||||
return QtConcurrent::run(&set_state_threadpool_, &gst_element_set_state, pipeline_, state);
|
||||
|
||||
}
|
||||
|
||||
void GstEnginePipeline::SetStateDelayed(const GstState state) {
|
||||
|
||||
QMetaObject::invokeMethod(this, [this, state]() {
|
||||
QTimer::singleShot(300, this, [this, state]() { SetState(state); });
|
||||
}, Qt::QueuedConnection);
|
||||
|
||||
}
|
||||
|
||||
bool GstEnginePipeline::Seek(const qint64 nanosec) {
|
||||
@@ -1526,7 +1581,7 @@ bool GstEnginePipeline::Seek(const qint64 nanosec) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!pipeline_is_connected_ || !pipeline_is_initialized_) {
|
||||
if (!pipeline_is_connected_ || !pipeline_is_active_) {
|
||||
pending_seek_nanosec_ = nanosec;
|
||||
return true;
|
||||
}
|
||||
@@ -1539,10 +1594,27 @@ bool GstEnginePipeline::Seek(const qint64 nanosec) {
|
||||
|
||||
pending_seek_nanosec_ = -1;
|
||||
last_known_position_ns_ = nanosec;
|
||||
|
||||
qLog(Debug) << "Seeking to" << nanosec;
|
||||
|
||||
return gst_element_seek_simple(pipeline_, GST_FORMAT_TIME, GST_SEEK_FLAG_FLUSH, nanosec);
|
||||
|
||||
}
|
||||
|
||||
void GstEnginePipeline::SeekQueued(const qint64 nanosec) {
|
||||
|
||||
QMetaObject::invokeMethod(this, "Seek", Qt::QueuedConnection, Q_ARG(qint64, nanosec));
|
||||
|
||||
}
|
||||
|
||||
void GstEnginePipeline::SeekDelayed(const qint64 nanosec) {
|
||||
|
||||
QMetaObject::invokeMethod(this, [this, nanosec]() {
|
||||
QTimer::singleShot(100, this, [this, nanosec]() { Seek(nanosec); });
|
||||
}, Qt::QueuedConnection);
|
||||
|
||||
}
|
||||
|
||||
void GstEnginePipeline::SetEBUR128LoudnessNormalizingGain_dB(const double ebur128_loudness_normalizing_gain_db) {
|
||||
|
||||
ebur128_loudness_normalizing_gain_db_ = ebur128_loudness_normalizing_gain_db;
|
||||
@@ -1567,7 +1639,7 @@ void GstEnginePipeline::SetVolume(const uint volume_percent) {
|
||||
if (!volume_set_ || volume_internal != volume_internal_) {
|
||||
volume_internal_ = volume_internal;
|
||||
g_object_set(G_OBJECT(volume_), "volume", volume_internal, nullptr);
|
||||
if (pipeline_is_initialized_) {
|
||||
if (pipeline_is_active_) {
|
||||
volume_set_ = true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -85,8 +85,11 @@ class GstEnginePipeline : public QObject {
|
||||
void RemoveAllBufferConsumers();
|
||||
|
||||
// Control the music playback
|
||||
QFuture<GstStateChangeReturn> SetState(const GstState state);
|
||||
Q_INVOKABLE QFuture<GstStateChangeReturn> SetState(const GstState state);
|
||||
void SetStateDelayed(const GstState state);
|
||||
Q_INVOKABLE bool Seek(const qint64 nanosec);
|
||||
void SeekQueued(const qint64 nanosec);
|
||||
void SeekDelayed(const qint64 nanosec);
|
||||
void SetEBUR128LoudnessNormalizingGain_dB(const double ebur128_loudness_normalizing_gain_db);
|
||||
void SetVolume(const uint volume_percent);
|
||||
void SetStereoBalance(const float value);
|
||||
@@ -148,6 +151,7 @@ class GstEnginePipeline : public QObject {
|
||||
void timerEvent(QTimerEvent*) override;
|
||||
|
||||
private:
|
||||
static QString GstStateText(const GstState state);
|
||||
GstElement *CreateElement(const QString &factory_name, const QString &name, GstElement *bin, QString &error) const;
|
||||
bool InitAudioBin(QString &error);
|
||||
void SetupVolume(GstElement *element);
|
||||
@@ -277,7 +281,7 @@ class GstEnginePipeline : public QObject {
|
||||
|
||||
// Seeking while the pipeline is in the READY state doesn't work, so we have to wait until it goes to PAUSED or PLAYING.
|
||||
// Also, we have to wait for the playbin to be connected.
|
||||
bool pipeline_is_initialized_;
|
||||
bool pipeline_is_active_;
|
||||
bool pipeline_is_connected_;
|
||||
qint64 pending_seek_nanosec_;
|
||||
|
||||
@@ -288,6 +292,7 @@ class GstEnginePipeline : public QObject {
|
||||
|
||||
// Complete the transition to the next song when it starts playing
|
||||
bool next_uri_set_;
|
||||
bool next_uri_reset_;
|
||||
|
||||
double ebur128_loudness_normalizing_gain_db_;
|
||||
bool volume_set_;
|
||||
|
||||
@@ -110,26 +110,33 @@ void GstStartup::SetEnvironment() {
|
||||
|
||||
#ifdef USE_BUNDLE
|
||||
|
||||
QString app_path = QCoreApplication::applicationDirPath();
|
||||
QString bundle_path = app_path + "/" + USE_BUNDLE_DIR;
|
||||
const QString app_path = QCoreApplication::applicationDirPath();
|
||||
|
||||
QString gio_module_path;
|
||||
QString gst_plugin_scanner;
|
||||
QString gst_plugin_path;
|
||||
QString libsoup_library_path;
|
||||
|
||||
# if defined(Q_OS_WIN32) || defined(Q_OS_MACOS)
|
||||
gio_module_path = bundle_path + "/gio-modules";
|
||||
# endif
|
||||
# if defined(Q_OS_LINUX) || defined(Q_OS_MACOS)
|
||||
gst_plugin_scanner = bundle_path + "/gst-plugin-scanner";
|
||||
gst_plugin_path = bundle_path + "/gstreamer";
|
||||
# endif
|
||||
# if defined(Q_OS_WIN32)
|
||||
gst_plugin_path = bundle_path + "/gstreamer-plugins";
|
||||
# endif
|
||||
// Set plugin root path
|
||||
QString plugin_root_path;
|
||||
# if defined(Q_OS_MACOS)
|
||||
libsoup_library_path = app_path + "/../Frameworks/libsoup-3.0.0.dylib";
|
||||
plugin_root_path = QDir::cleanPath(app_path + "/../PlugIns");
|
||||
# elif defined(Q_OS_UNIX)
|
||||
plugin_root_path = QDir::cleanPath(app_path + "/../plugins");
|
||||
# elif defined(Q_OS_WIN32)
|
||||
plugin_root_path = app_path;
|
||||
# endif
|
||||
|
||||
// Set GIO module path
|
||||
const QString gio_module_path = plugin_root_path + "/gio-modules";
|
||||
|
||||
// Set GStreamer plugin scanner path
|
||||
QString gst_plugin_scanner;
|
||||
# if defined(Q_OS_UNIX)
|
||||
gst_plugin_scanner = plugin_root_path + "/gst-plugin-scanner";
|
||||
# endif
|
||||
|
||||
// Set GStreamer plugin path
|
||||
QString gst_plugin_path;
|
||||
# if defined(Q_OS_WIN32)
|
||||
gst_plugin_path = plugin_root_path + "/gstreamer-plugins";
|
||||
# else
|
||||
gst_plugin_path = plugin_root_path + "/gstreamer";
|
||||
# endif
|
||||
|
||||
if (!gio_module_path.isEmpty()) {
|
||||
@@ -138,7 +145,7 @@ void GstStartup::SetEnvironment() {
|
||||
Utilities::SetEnv("GIO_EXTRA_MODULES", gio_module_path);
|
||||
}
|
||||
else {
|
||||
qLog(Debug) << "GIO module path does not exist:" << gio_module_path;
|
||||
qLog(Error) << "GIO module path" << gio_module_path << "does not exist.";
|
||||
}
|
||||
}
|
||||
|
||||
@@ -148,7 +155,7 @@ void GstStartup::SetEnvironment() {
|
||||
Utilities::SetEnv("GST_PLUGIN_SCANNER", gst_plugin_scanner);
|
||||
}
|
||||
else {
|
||||
qLog(Debug) << "GStreamer plugin scanner does not exist:" << gst_plugin_scanner;
|
||||
qLog(Error) << "GStreamer plugin scanner" << gst_plugin_scanner << "does not exist.";
|
||||
}
|
||||
}
|
||||
|
||||
@@ -160,16 +167,7 @@ void GstStartup::SetEnvironment() {
|
||||
Utilities::SetEnv("GST_PLUGIN_SYSTEM_PATH", gst_plugin_path);
|
||||
}
|
||||
else {
|
||||
qLog(Debug) << "GStreamer plugin path does not exist:" << gst_plugin_path;
|
||||
}
|
||||
}
|
||||
|
||||
if (!libsoup_library_path.isEmpty()) {
|
||||
if (QFile::exists(libsoup_library_path)) {
|
||||
Utilities::SetEnv("LIBSOUP3_LIBRARY_PATH", libsoup_library_path);
|
||||
}
|
||||
else {
|
||||
qLog(Debug) << "libsoup path does not exist:" << libsoup_library_path;
|
||||
qLog(Error) << "GStreamer plugin path" << gst_plugin_path << "does not exist.";
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -26,6 +26,7 @@
|
||||
|
||||
#include "core/shared_ptr.h"
|
||||
#include "core/networkaccessmanager.h"
|
||||
#include "utilities/transliterate.h"
|
||||
#include "lyricssearchrequest.h"
|
||||
#include "azlyricscomlyricsprovider.h"
|
||||
|
||||
@@ -43,8 +44,8 @@ QUrl AzLyricsComLyricsProvider::Url(const LyricsSearchRequest &request) {
|
||||
|
||||
}
|
||||
|
||||
QString AzLyricsComLyricsProvider::StringFixup(QString string) {
|
||||
QString AzLyricsComLyricsProvider::StringFixup(const QString &text) {
|
||||
|
||||
return string.remove(QRegularExpression("[^\\w0-9\\-]", QRegularExpression::UseUnicodePropertiesOption)).simplified().toLower();
|
||||
return Utilities::Transliterate(text).remove(QRegularExpression("[^\\w0-9\\-]")).toLower();
|
||||
|
||||
}
|
||||
|
||||
@@ -42,7 +42,7 @@ class AzLyricsComLyricsProvider : public HtmlLyricsProvider {
|
||||
QUrl Url(const LyricsSearchRequest &request) override;
|
||||
|
||||
private:
|
||||
QString StringFixup(QString string);
|
||||
QString StringFixup(const QString &text);
|
||||
|
||||
private:
|
||||
static const char kUrl[];
|
||||
|
||||
@@ -26,6 +26,7 @@
|
||||
|
||||
#include "core/shared_ptr.h"
|
||||
#include "core/networkaccessmanager.h"
|
||||
#include "utilities/transliterate.h"
|
||||
#include "lyricssearchrequest.h"
|
||||
#include "elyricsnetlyricsprovider.h"
|
||||
|
||||
@@ -43,12 +44,13 @@ QUrl ElyricsNetLyricsProvider::Url(const LyricsSearchRequest &request) {
|
||||
|
||||
}
|
||||
|
||||
QString ElyricsNetLyricsProvider::StringFixup(QString string) {
|
||||
QString ElyricsNetLyricsProvider::StringFixup(const QString &text) {
|
||||
|
||||
return string
|
||||
.replace(' ', '-')
|
||||
.replace(QRegularExpression("[^\\w0-9_-]", QRegularExpression::UseUnicodePropertiesOption), "_")
|
||||
return Utilities::Transliterate(text)
|
||||
.replace(QRegularExpression("[^\\w0-9_,&\\-\\(\\) ]"), "_")
|
||||
.replace(QRegularExpression(" {2,}"), " ")
|
||||
.simplified()
|
||||
.replace(' ', '-')
|
||||
.toLower();
|
||||
|
||||
}
|
||||
|
||||
@@ -42,7 +42,7 @@ class ElyricsNetLyricsProvider : public HtmlLyricsProvider {
|
||||
QUrl Url(const LyricsSearchRequest &request) override;
|
||||
|
||||
private:
|
||||
QString StringFixup(QString string);
|
||||
QString StringFixup(const QString &text);
|
||||
|
||||
private:
|
||||
static const char kUrl[];
|
||||
|
||||
@@ -297,7 +297,7 @@ bool GeniusLyricsProvider::StartSearch(const int id, const LyricsSearchRequest &
|
||||
requests_search_.insert(id, search);
|
||||
|
||||
QUrlQuery url_query;
|
||||
url_query.addQueryItem("q", QUrl::toPercentEncoding(QString(request.artist + " " + request.title)));
|
||||
url_query.addQueryItem("q", QUrl::toPercentEncoding(QString("%1 %2").arg(request.artist, request.title)));
|
||||
|
||||
QUrl url(kUrlSearch);
|
||||
url.setQuery(url_query);
|
||||
|
||||
@@ -43,12 +43,14 @@ QUrl LyricsModeComLyricsProvider::Url(const LyricsSearchRequest &request) {
|
||||
|
||||
}
|
||||
|
||||
QString LyricsModeComLyricsProvider::StringFixup(QString string) {
|
||||
QString LyricsModeComLyricsProvider::StringFixup(QString text) {
|
||||
|
||||
return string
|
||||
.replace(' ', '_')
|
||||
.remove(QRegularExpression("[^\\w0-9_-]", QRegularExpression::UseUnicodePropertiesOption))
|
||||
return text
|
||||
.remove(QRegularExpression("[^\\w0-9_\\- ]"))
|
||||
.replace(QRegularExpression(" {2,}"), " ")
|
||||
.simplified()
|
||||
.replace(' ', '_')
|
||||
.replace('-', '_')
|
||||
.toLower();
|
||||
|
||||
}
|
||||
|
||||
@@ -42,7 +42,7 @@ class LyricsModeComLyricsProvider : public HtmlLyricsProvider {
|
||||
QUrl Url(const LyricsSearchRequest &request) override;
|
||||
|
||||
private:
|
||||
QString StringFixup(QString string);
|
||||
QString StringFixup(QString text);
|
||||
|
||||
private:
|
||||
static const char kUrl[];
|
||||
|
||||
@@ -43,14 +43,15 @@ QUrl SongLyricsComLyricsProvider::Url(const LyricsSearchRequest &request) {
|
||||
|
||||
}
|
||||
|
||||
QString SongLyricsComLyricsProvider::StringFixup(QString string) {
|
||||
QString SongLyricsComLyricsProvider::StringFixup(QString text) {
|
||||
|
||||
return string.replace('/', '-')
|
||||
.replace('\'', '-')
|
||||
.remove(QRegularExpression("[^\\w0-9\\- ]", QRegularExpression::UseUnicodePropertiesOption))
|
||||
.simplified()
|
||||
.replace(' ', '-')
|
||||
.replace(QRegularExpression("(-)\\1+"), "-")
|
||||
.toLower();
|
||||
return text.replace('/', '-')
|
||||
.replace('\'', '-')
|
||||
.remove(QRegularExpression("[^\\w0-9\\- ]"))
|
||||
.replace(QRegularExpression(" {2,}"), " ")
|
||||
.simplified()
|
||||
.replace(' ', '-')
|
||||
.replace(QRegularExpression("(-)\\1+"), "-")
|
||||
.toLower();
|
||||
|
||||
}
|
||||
|
||||
@@ -42,7 +42,7 @@ class SongLyricsComLyricsProvider : public HtmlLyricsProvider {
|
||||
QUrl Url(const LyricsSearchRequest &request) override;
|
||||
|
||||
private:
|
||||
QString StringFixup(QString string);
|
||||
QString StringFixup(QString text);
|
||||
|
||||
private:
|
||||
static const char kUrl[];
|
||||
|
||||
26
src/main.cpp
26
src/main.cpp
@@ -70,6 +70,8 @@
|
||||
#include "core/scoped_ptr.h"
|
||||
#include "core/shared_ptr.h"
|
||||
|
||||
#include "utilities/envutils.h"
|
||||
|
||||
#include <kdsingleapplication.h>
|
||||
|
||||
#ifdef HAVE_QTSPARKLE
|
||||
@@ -154,7 +156,11 @@ int main(int argc, char *argv[]) {
|
||||
// Only start a core application now, so we can check if there's another instance without requiring an X server.
|
||||
// This MUST be done before parsing the commandline options so QTextCodec gets the right system locale for filenames.
|
||||
QCoreApplication core_app(argc, argv);
|
||||
#ifdef HAVE_KDSINGLEAPPLICATION_OPTIONS
|
||||
KDSingleApplication single_app(QCoreApplication::applicationName(), KDSingleApplication::Option::IncludeUsernameInSocketName);
|
||||
#else
|
||||
KDSingleApplication single_app(QCoreApplication::applicationName());
|
||||
#endif
|
||||
// Parse commandline options - need to do this before starting the full QApplication, so it works without an X server
|
||||
if (!options.Parse()) return 1;
|
||||
logging::SetLevels(options.log_levels());
|
||||
@@ -171,7 +177,7 @@ int main(int argc, char *argv[]) {
|
||||
|
||||
#ifdef Q_OS_MACOS
|
||||
// Must happen after QCoreApplication::setOrganizationName().
|
||||
setenv("XDG_CONFIG_HOME", QStandardPaths::writableLocation(QStandardPaths::AppConfigLocation).toLocal8Bit().constData(), 1);
|
||||
Utilities::SetEnv("XDG_CONFIG_HOME", QStandardPaths::writableLocation(QStandardPaths::AppConfigLocation));
|
||||
#endif
|
||||
|
||||
// Output the version, so when people attach log output to bug reports they don't have to tell us which version they're using.
|
||||
@@ -191,7 +197,11 @@ int main(int argc, char *argv[]) {
|
||||
QGuiApplication::setQuitOnLastWindowClosed(false);
|
||||
|
||||
QApplication a(argc, argv);
|
||||
#ifdef HAVE_KDSINGLEAPPLICATION_OPTIONS
|
||||
KDSingleApplication single_app(QCoreApplication::applicationName(), KDSingleApplication::Option::IncludeUsernameInSocketName);
|
||||
#else
|
||||
KDSingleApplication single_app(QCoreApplication::applicationName());
|
||||
#endif
|
||||
if (!single_app.isPrimaryInstance()) {
|
||||
if (options.is_empty()) {
|
||||
qLog(Info) << "Strawberry is already running - activating existing window (2)";
|
||||
@@ -205,19 +215,7 @@ int main(int argc, char *argv[]) {
|
||||
QGuiApplication::setWindowIcon(IconLoader::Load("strawberry"));
|
||||
|
||||
#if defined(USE_BUNDLE)
|
||||
{
|
||||
QStringList library_paths;
|
||||
#ifdef Q_OS_MACOS
|
||||
library_paths.append(QCoreApplication::applicationDirPath() + "/../Frameworks");
|
||||
#endif
|
||||
#if defined(Q_OS_LINUX) || defined(Q_OS_MACOS)
|
||||
library_paths.append(QCoreApplication::applicationDirPath() + "/" + USE_BUNDLE_DIR);
|
||||
#endif
|
||||
if (!library_paths.isEmpty()) {
|
||||
qLog(Debug) << "Looking for resources in" << library_paths;
|
||||
QCoreApplication::setLibraryPaths(library_paths);
|
||||
}
|
||||
}
|
||||
qLog(Debug) << "Looking for resources in" << QCoreApplication::libraryPaths();
|
||||
#endif
|
||||
|
||||
// Gnome on Ubuntu has menu icons disabled by default. I think that's a bad idea, and makes some menus in Strawberry look confusing.
|
||||
|
||||
@@ -1286,10 +1286,10 @@ QMimeData *Playlist::mimeData(const QModelIndexList &indexes) const {
|
||||
|
||||
}
|
||||
|
||||
bool Playlist::CompareItems(const int column, const Qt::SortOrder order, SharedPtr<PlaylistItem> _a, SharedPtr<PlaylistItem> _b) {
|
||||
bool Playlist::CompareItems(const int column, const Qt::SortOrder order, PlaylistItemPtr _a, PlaylistItemPtr _b) {
|
||||
|
||||
SharedPtr<PlaylistItem> a = order == Qt::AscendingOrder ? _a : _b;
|
||||
SharedPtr<PlaylistItem> b = order == Qt::AscendingOrder ? _b : _a;
|
||||
PlaylistItemPtr a = order == Qt::AscendingOrder ? _a : _b;
|
||||
PlaylistItemPtr b = order == Qt::AscendingOrder ? _b : _a;
|
||||
|
||||
#define cmp(field) return a->Metadata().field() < b->Metadata().field()
|
||||
#define strcmp(field) return QString::localeAwareCompare(a->Metadata().field().toLower(), b->Metadata().field().toLower()) < 0;
|
||||
@@ -1345,10 +1345,10 @@ bool Playlist::CompareItems(const int column, const Qt::SortOrder order, SharedP
|
||||
|
||||
}
|
||||
|
||||
bool Playlist::ComparePathDepths(const Qt::SortOrder order, SharedPtr<PlaylistItem> _a, SharedPtr<PlaylistItem> _b) {
|
||||
bool Playlist::ComparePathDepths(const Qt::SortOrder order, PlaylistItemPtr _a, PlaylistItemPtr _b) {
|
||||
|
||||
SharedPtr<PlaylistItem> a = order == Qt::AscendingOrder ? _a : _b;
|
||||
SharedPtr<PlaylistItem> b = order == Qt::AscendingOrder ? _b : _a;
|
||||
PlaylistItemPtr a = order == Qt::AscendingOrder ? _a : _b;
|
||||
PlaylistItemPtr b = order == Qt::AscendingOrder ? _b : _a;
|
||||
|
||||
qint64 a_dir_level = a->Url().path().count('/');
|
||||
qint64 b_dir_level = b->Url().path().count('/');
|
||||
|
||||
@@ -48,6 +48,7 @@
|
||||
#include "playlistitem.h"
|
||||
#include "playlistsequence.h"
|
||||
#include "smartplaylists/playlistgenerator_fwd.h"
|
||||
#include <internet/internetservice.h>
|
||||
|
||||
class QMimeData;
|
||||
class QUndoStack;
|
||||
@@ -58,7 +59,6 @@ class PlaylistBackend;
|
||||
class PlaylistFilter;
|
||||
class Queue;
|
||||
class TaskManager;
|
||||
class InternetService;
|
||||
class RadioService;
|
||||
|
||||
namespace PlaylistUndoCommands {
|
||||
@@ -237,7 +237,7 @@ class Playlist : public QAbstractListModel {
|
||||
void InsertSongs(const SongList &songs, const int pos = -1, const bool play_now = false, const bool enqueue = false, const bool enqueue_next = false);
|
||||
void InsertSongsOrCollectionItems(const SongList &songs, const int pos = -1, const bool play_now = false, const bool enqueue = false, const bool enqueue_next = false);
|
||||
void InsertSmartPlaylist(PlaylistGeneratorPtr gen, const int pos = -1, const bool play_now = false, const bool enqueue = false, const bool enqueue_next = false);
|
||||
void InsertInternetItems(SharedPtr<InternetService> service, const SongList &songs, const int pos = -1, const bool play_now = false, const bool enqueue = false, const bool enqueue_next = false);
|
||||
void InsertInternetItems(InternetServicePtr service, const SongList &songs, const int pos = -1, const bool play_now = false, const bool enqueue = false, const bool enqueue_next = false);
|
||||
void InsertRadioItems(const SongList &songs, const int pos = -1, const bool play_now = false, const bool enqueue = false, const bool enqueue_next = false);
|
||||
|
||||
void ReshuffleIndices();
|
||||
|
||||
@@ -67,9 +67,6 @@ class PlaylistContainer : public QWidget {
|
||||
bool eventFilter(QObject *objectWatched, QEvent *event) override;
|
||||
|
||||
signals:
|
||||
void TabChanged(const int id);
|
||||
void Rename(const int id, const QString &new_name);
|
||||
|
||||
void UndoRedoActionsChanged(QAction *undo, QAction *redo);
|
||||
void ViewSelectionModelChanged();
|
||||
|
||||
|
||||
@@ -131,7 +131,7 @@ class FloatEqComparator : public SearchTermComparator {
|
||||
|
||||
class FloatNeComparator : public SearchTermComparator {
|
||||
public:
|
||||
explicit FloatNeComparator(const float &value) : search_term_(value) {}
|
||||
explicit FloatNeComparator(const float value) : search_term_(value) {}
|
||||
bool Matches(const QString &element) const override {
|
||||
return search_term_ != element.toFloat();
|
||||
}
|
||||
@@ -141,7 +141,7 @@ class FloatNeComparator : public SearchTermComparator {
|
||||
|
||||
class FloatGtComparator : public SearchTermComparator {
|
||||
public:
|
||||
explicit FloatGtComparator(const float &value) : search_term_(value) {}
|
||||
explicit FloatGtComparator(const float value) : search_term_(value) {}
|
||||
bool Matches(const QString &element) const override {
|
||||
return element.toFloat() > search_term_;
|
||||
}
|
||||
@@ -151,7 +151,7 @@ class FloatGtComparator : public SearchTermComparator {
|
||||
|
||||
class FloatGeComparator : public SearchTermComparator {
|
||||
public:
|
||||
explicit FloatGeComparator(const float &value) : search_term_(value) {}
|
||||
explicit FloatGeComparator(const float value) : search_term_(value) {}
|
||||
bool Matches(const QString &element) const override {
|
||||
return element.toFloat() >= search_term_;
|
||||
}
|
||||
@@ -161,7 +161,7 @@ class FloatGeComparator : public SearchTermComparator {
|
||||
|
||||
class FloatLtComparator : public SearchTermComparator {
|
||||
public:
|
||||
explicit FloatLtComparator(const float &value) : search_term_(value) {}
|
||||
explicit FloatLtComparator(const float value) : search_term_(value) {}
|
||||
bool Matches(const QString &element) const override {
|
||||
return element.toFloat() < search_term_;
|
||||
}
|
||||
@@ -171,7 +171,7 @@ class FloatLtComparator : public SearchTermComparator {
|
||||
|
||||
class FloatLeComparator : public SearchTermComparator {
|
||||
public:
|
||||
explicit FloatLeComparator(const float &value) : search_term_(value) {}
|
||||
explicit FloatLeComparator(const float value) : search_term_(value) {}
|
||||
bool Matches(const QString &element) const override {
|
||||
return element.toFloat() <= search_term_;
|
||||
}
|
||||
|
||||
@@ -44,6 +44,7 @@
|
||||
#include <QToolButton>
|
||||
#include <QShowEvent>
|
||||
#include <QContextMenuEvent>
|
||||
#include <QMimeData>
|
||||
|
||||
#include "core/application.h"
|
||||
#include "core/iconloader.h"
|
||||
@@ -110,6 +111,7 @@ PlaylistListContainer::PlaylistListContainer(QWidget *parent)
|
||||
|
||||
QObject::connect(ui_->tree, &PlaylistListView::ItemsSelectedChanged, this, &PlaylistListContainer::ItemsSelectedChanged);
|
||||
QObject::connect(ui_->tree, &PlaylistListView::doubleClicked, this, &PlaylistListContainer::ItemDoubleClicked);
|
||||
QObject::connect(ui_->tree, &PlaylistListView::ItemMimeDataDroppedSignal, this, &PlaylistListContainer::ItemMimeDataDropped);
|
||||
|
||||
model_->invisibleRootItem()->setData(PlaylistListModel::Type_Folder, PlaylistListModel::Role_Type);
|
||||
|
||||
@@ -348,6 +350,19 @@ void PlaylistListContainer::ItemDoubleClicked(const QModelIndex &proxy_idx) {
|
||||
|
||||
}
|
||||
|
||||
void PlaylistListContainer::ItemMimeDataDropped(const QModelIndex &proxy_idx, const QMimeData *q_mimedata) {
|
||||
|
||||
const QModelIndex idx = proxy_->mapToSource(proxy_idx);
|
||||
if (!idx.isValid()) return;
|
||||
|
||||
// Drop playlist rows if type is playlist and it's not active, to prevent selfcopy
|
||||
int playlis_id = idx.data(PlaylistListModel::Role_PlaylistId).toInt();
|
||||
if (idx.data(PlaylistListModel::Role_Type).toInt() == PlaylistListModel::Type_Playlist && playlis_id != app_->playlist_manager()->active_id()) {
|
||||
app_->playlist_manager()->playlist(playlis_id)->dropMimeData(q_mimedata, Qt::CopyAction, -1, 0, QModelIndex());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
void PlaylistListContainer::CopyToDevice() {
|
||||
|
||||
#ifndef Q_OS_WIN
|
||||
|
||||
@@ -63,6 +63,7 @@ class PlaylistListContainer : public QWidget {
|
||||
// From the UI
|
||||
void NewFolderClicked();
|
||||
void ItemDoubleClicked(const QModelIndex &proxy_idx);
|
||||
void ItemMimeDataDropped(const QModelIndex &proxy_idx, const QMimeData *q_mimedata);
|
||||
|
||||
// From the model
|
||||
void PlaylistPathChanged(const int id, const QString &new_path);
|
||||
|
||||
@@ -23,6 +23,7 @@
|
||||
|
||||
#include <QWidget>
|
||||
#include <QFont>
|
||||
#include <QMimeData>
|
||||
#include <QPainter>
|
||||
#include <QPalette>
|
||||
#include <QRect>
|
||||
@@ -30,6 +31,7 @@
|
||||
|
||||
#include "widgets/autoexpandingtreeview.h"
|
||||
#include "playlistlistview.h"
|
||||
#include "playlist.h"
|
||||
|
||||
PlaylistListView::PlaylistListView(QWidget *parent) : AutoExpandingTreeView(parent) {}
|
||||
|
||||
@@ -64,3 +66,72 @@ bool PlaylistListView::ItemsSelected() const {
|
||||
void PlaylistListView::selectionChanged(const QItemSelection&, const QItemSelection&) {
|
||||
emit ItemsSelectedChanged(selectionModel()->selectedRows().count() > 0);
|
||||
}
|
||||
|
||||
void PlaylistListView::dragEnterEvent(QDragEnterEvent *e) {
|
||||
|
||||
if (e->mimeData()->hasFormat(Playlist::kRowsMimetype)) {
|
||||
e->acceptProposedAction();
|
||||
}
|
||||
else {
|
||||
AutoExpandingTreeView::dragEnterEvent(e);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
void PlaylistListView::dragMoveEvent(QDragMoveEvent *e) {
|
||||
|
||||
#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
|
||||
QModelIndex drag_hover_tab_ = indexAt(e->position().toPoint());
|
||||
#else
|
||||
QModelIndex drag_hover_tab_ = indexAt(e->pos());
|
||||
#endif
|
||||
|
||||
if (e->mimeData()->hasFormat(Playlist::kRowsMimetype)) {
|
||||
if (drag_hover_tab_ != currentIndex()) {
|
||||
e->setDropAction(Qt::CopyAction);
|
||||
e->accept(visualRect(drag_hover_tab_));
|
||||
setCurrentIndex(drag_hover_tab_);
|
||||
if (drag_hover_timer_.isActive()) {
|
||||
drag_hover_timer_.stop();
|
||||
}
|
||||
drag_hover_timer_.start(kDragHoverTimeout, this);
|
||||
}
|
||||
}
|
||||
else {
|
||||
AutoExpandingTreeView::dragMoveEvent(e);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
void PlaylistListView::dragLeaveEvent(QDragLeaveEvent *e) {
|
||||
|
||||
if (drag_hover_timer_.isActive()) {
|
||||
drag_hover_timer_.stop();
|
||||
}
|
||||
AutoExpandingTreeView::dragLeaveEvent(e);
|
||||
|
||||
}
|
||||
|
||||
void PlaylistListView::timerEvent(QTimerEvent *e) {
|
||||
|
||||
QTreeView::timerEvent(e);
|
||||
if (e->timerId() == drag_hover_timer_.timerId()) {
|
||||
drag_hover_timer_.stop();
|
||||
emit doubleClicked(currentIndex());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
void PlaylistListView::dropEvent(QDropEvent *e) {
|
||||
|
||||
if (e->mimeData()->hasFormat(Playlist::kRowsMimetype)) {
|
||||
if (drag_hover_timer_.isActive()) {
|
||||
drag_hover_timer_.stop();
|
||||
}
|
||||
emit ItemMimeDataDroppedSignal(currentIndex(), e->mimeData());
|
||||
}
|
||||
else {
|
||||
AutoExpandingTreeView::dropEvent(e);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -24,6 +24,7 @@
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#include <QBasicTimer>
|
||||
#include <QObject>
|
||||
#include <QWidget>
|
||||
#include <QString>
|
||||
@@ -31,6 +32,11 @@
|
||||
#include "widgets/autoexpandingtreeview.h"
|
||||
|
||||
class QPaintEvent;
|
||||
class QDragEnterEvent;
|
||||
class QDragLeaveEvent;
|
||||
class QDragMoveEvent;
|
||||
class QDropEvent;
|
||||
class QTimerEvent;
|
||||
|
||||
class PlaylistListView : public AutoExpandingTreeView {
|
||||
Q_OBJECT
|
||||
@@ -42,11 +48,23 @@ class PlaylistListView : public AutoExpandingTreeView {
|
||||
|
||||
signals:
|
||||
void ItemsSelectedChanged(const bool);
|
||||
void ItemMimeDataDroppedSignal(const QModelIndex &proxy_idx, const QMimeData *q_mimedata);
|
||||
|
||||
protected:
|
||||
// QWidget
|
||||
void paintEvent(QPaintEvent *event) override;
|
||||
void selectionChanged(const QItemSelection&, const QItemSelection&) override;
|
||||
|
||||
void dragEnterEvent(QDragEnterEvent *e) override;
|
||||
void dragMoveEvent(QDragMoveEvent *e) override;
|
||||
void dragLeaveEvent(QDragLeaveEvent *e) override;
|
||||
void dropEvent(QDropEvent *e) override;
|
||||
void timerEvent(QTimerEvent *e) override;
|
||||
|
||||
private:
|
||||
static const int kDragHoverTimeout = 500;
|
||||
|
||||
QBasicTimer drag_hover_timer_;
|
||||
};
|
||||
|
||||
#endif // PLAYLISTVIEW_H
|
||||
|
||||
@@ -449,8 +449,7 @@ void PlaylistManager::UpdateSummaryText() {
|
||||
nanoseconds = current()->GetTotalLength();
|
||||
}
|
||||
|
||||
// TODO: Make the plurals translatable
|
||||
summary += tracks == 1 ? tr("1 track") : tr("%1 tracks").arg(tracks);
|
||||
summary += tr("%n track(s)", "", tracks);
|
||||
|
||||
if (nanoseconds > 0) {
|
||||
summary += " - [ " + Utilities::WordyTimeNanosec(nanoseconds) + " ]";
|
||||
|
||||
71
src/playlist/playlistproxystyle.cpp
Normal file
71
src/playlist/playlistproxystyle.cpp
Normal file
@@ -0,0 +1,71 @@
|
||||
/*
|
||||
* Strawberry Music Player
|
||||
* This file was part of Clementine.
|
||||
* Copyright 2010, David Sansome <me@davidsansome.com>
|
||||
* Copyright 2018-2023, Jonas Kvinge <jonas@jkvinge.net>
|
||||
*
|
||||
* Strawberry is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Strawberry is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with Strawberry. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#include <QProxyStyle>
|
||||
#include <QString>
|
||||
#include <QPainter>
|
||||
#include <QStyleOptionHeader>
|
||||
#include <QFontMetrics>
|
||||
|
||||
#include "playlistproxystyle.h"
|
||||
#include "playlist.h"
|
||||
|
||||
PlaylistProxyStyle::PlaylistProxyStyle(const QString &style) : QProxyStyle(style), common_style_(new QCommonStyle) {}
|
||||
|
||||
void PlaylistProxyStyle::drawControl(ControlElement element, const QStyleOption *option, QPainter *painter, const QWidget *widget) const {
|
||||
|
||||
if (element == CE_HeaderLabel) {
|
||||
const QStyleOptionHeader *header_option = qstyleoption_cast<const QStyleOptionHeader*>(option);
|
||||
const QRect &rect = header_option->rect;
|
||||
const QString &text = header_option->text;
|
||||
const QFontMetrics &font_metrics = header_option->fontMetrics;
|
||||
|
||||
// Spaces added to make transition less abrupt
|
||||
if (rect.width() < font_metrics.horizontalAdvance(text + " ")) {
|
||||
const Playlist::Column column = static_cast<Playlist::Column>(header_option->section);
|
||||
QStyleOptionHeader new_option(*header_option);
|
||||
new_option.text = Playlist::abbreviated_column_name(column);
|
||||
QProxyStyle::drawControl(element, &new_option, painter, widget);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (element == CE_ItemViewItem) {
|
||||
common_style_->drawControl(element, option, painter, widget);
|
||||
}
|
||||
else {
|
||||
QProxyStyle::drawControl(element, option, painter, widget);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
void PlaylistProxyStyle::drawPrimitive(PrimitiveElement element, const QStyleOption *option, QPainter *painter, const QWidget *widget) const {
|
||||
|
||||
if (element == QStyle::PE_PanelItemViewRow || element == QStyle::PE_PanelItemViewItem) {
|
||||
common_style_->drawPrimitive(element, option, painter, widget);
|
||||
}
|
||||
else {
|
||||
QProxyStyle::drawPrimitive(element, option, painter, widget);
|
||||
}
|
||||
|
||||
}
|
||||
53
src/playlist/playlistproxystyle.h
Normal file
53
src/playlist/playlistproxystyle.h
Normal file
@@ -0,0 +1,53 @@
|
||||
/*
|
||||
* Strawberry Music Player
|
||||
* This file was part of Clementine.
|
||||
* Copyright 2010, David Sansome <me@davidsansome.com>
|
||||
* Copyright 2018-2023, Jonas Kvinge <jonas@jkvinge.net>
|
||||
*
|
||||
* Strawberry is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Strawberry is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with Strawberry. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef PLAYLISTPROXYSTYLE_H
|
||||
#define PLAYLISTPROXYSTYLE_H
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#include <QProxyStyle>
|
||||
#include <QCommonStyle>
|
||||
#include <QString>
|
||||
|
||||
#include "core/scoped_ptr.h"
|
||||
|
||||
class QPainter;
|
||||
|
||||
// This proxy style works around a bug/feature introduced in Qt 4.7's QGtkStyle
|
||||
// that uses Gtk to paint row backgrounds, ignoring any custom brush or palette the caller set in the QStyleOption.
|
||||
// That breaks our currently playing track animation, which relies on the background painted by Qt to be transparent.
|
||||
// This proxy style uses QCommonStyle to paint the affected elements.
|
||||
|
||||
class PlaylistProxyStyle : public QProxyStyle {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit PlaylistProxyStyle(const QString &style);
|
||||
|
||||
void drawControl(ControlElement element, const QStyleOption *option, QPainter *painter, const QWidget *widget) const override;
|
||||
void drawPrimitive(PrimitiveElement element, const QStyleOption *option, QPainter *painter, const QWidget *widget) const override;
|
||||
|
||||
private:
|
||||
ScopedPtr<QCommonStyle> common_style_;
|
||||
};
|
||||
|
||||
#endif // PLAYLISTPROXYSTYLE_H
|
||||
@@ -32,8 +32,6 @@
|
||||
#include <QTreeView>
|
||||
#include <QHeaderView>
|
||||
#include <QClipboard>
|
||||
#include <QCommonStyle>
|
||||
#include <QFontMetrics>
|
||||
#include <QKeySequence>
|
||||
#include <QMimeData>
|
||||
#include <QMetaType>
|
||||
@@ -55,9 +53,7 @@
|
||||
#include <QPoint>
|
||||
#include <QRect>
|
||||
#include <QRegion>
|
||||
#include <QStyleOptionHeader>
|
||||
#include <QStyleOptionViewItem>
|
||||
#include <QProxyStyle>
|
||||
#include <QLinearGradient>
|
||||
#include <QScrollBar>
|
||||
#include <QtEvents>
|
||||
@@ -73,6 +69,7 @@
|
||||
#include "playlistheader.h"
|
||||
#include "playlistview.h"
|
||||
#include "playlistfilter.h"
|
||||
#include "playlistproxystyle.h"
|
||||
#include "covermanager/currentalbumcoverloader.h"
|
||||
#include "covermanager/albumcoverloaderresult.h"
|
||||
#include "settings/appearancesettingspage.h"
|
||||
@@ -88,50 +85,14 @@ const int PlaylistView::kAutoscrollGraceTimeout = 30; // seconds
|
||||
const int PlaylistView::kDropIndicatorWidth = 2;
|
||||
const int PlaylistView::kDropIndicatorGradientWidth = 5;
|
||||
|
||||
PlaylistProxyStyle::PlaylistProxyStyle(QObject*) : QProxyStyle(nullptr), common_style_(new QCommonStyle) {}
|
||||
|
||||
void PlaylistProxyStyle::drawControl(ControlElement element, const QStyleOption *option, QPainter *painter, const QWidget *widget) const {
|
||||
|
||||
if (element == CE_Header) {
|
||||
const QStyleOptionHeader *header_option = qstyleoption_cast<const QStyleOptionHeader*>(option);
|
||||
const QRect &rect = header_option->rect;
|
||||
const QString &text = header_option->text;
|
||||
const QFontMetrics &font_metrics = header_option->fontMetrics;
|
||||
|
||||
// Spaces added to make transition less abrupt
|
||||
if (rect.width() < font_metrics.horizontalAdvance(text + " ")) {
|
||||
const Playlist::Column column = static_cast<Playlist::Column>(header_option->section);
|
||||
QStyleOptionHeader new_option(*header_option);
|
||||
new_option.text = Playlist::abbreviated_column_name(column);
|
||||
QProxyStyle::drawControl(element, &new_option, painter, widget);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (element == CE_ItemViewItem) {
|
||||
common_style_->drawControl(element, option, painter, widget);
|
||||
}
|
||||
else {
|
||||
QProxyStyle::drawControl(element, option, painter, widget);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
void PlaylistProxyStyle::drawPrimitive(PrimitiveElement element, const QStyleOption *option, QPainter *painter, const QWidget *widget) const {
|
||||
|
||||
if (element == QStyle::PE_PanelItemViewRow || element == QStyle::PE_PanelItemViewItem) {
|
||||
common_style_->drawPrimitive(element, option, painter, widget);
|
||||
}
|
||||
else {
|
||||
QProxyStyle::drawPrimitive(element, option, painter, widget);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
PlaylistView::PlaylistView(QWidget *parent)
|
||||
: QTreeView(parent),
|
||||
app_(nullptr),
|
||||
style_(new PlaylistProxyStyle()),
|
||||
#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
|
||||
style_(new PlaylistProxyStyle(QApplication::style()->name())),
|
||||
#else
|
||||
style_(new PlaylistProxyStyle(QApplication::style()->objectName())),
|
||||
#endif
|
||||
playlist_(nullptr),
|
||||
header_(new PlaylistHeader(Qt::Horizontal, this, this)),
|
||||
background_image_type_(AppearanceSettingsPage::BackgroundImageType::Default),
|
||||
|
||||
@@ -40,12 +40,9 @@
|
||||
#include <QRect>
|
||||
#include <QRegion>
|
||||
#include <QStyleOption>
|
||||
#include <QProxyStyle>
|
||||
#include <QPoint>
|
||||
#include <QBasicTimer>
|
||||
#include <QCommonStyle>
|
||||
|
||||
#include "core/scoped_ptr.h"
|
||||
#include "core/song.h"
|
||||
#include "covermanager/albumcoverloaderresult.h"
|
||||
#include "settings/appearancesettingspage.h"
|
||||
@@ -72,27 +69,10 @@ class QTimerEvent;
|
||||
class Application;
|
||||
class CollectionBackend;
|
||||
class PlaylistHeader;
|
||||
class PlaylistProxyStyle;
|
||||
class DynamicPlaylistControls;
|
||||
class RatingItemDelegate;
|
||||
|
||||
// This proxy style works around a bug/feature introduced in Qt 4.7's QGtkStyle
|
||||
// that uses Gtk to paint row backgrounds, ignoring any custom brush or palette the caller set in the QStyleOption.
|
||||
// That breaks our currently playing track animation, which relies on the background painted by Qt to be transparent.
|
||||
// This proxy style uses QCommonStyle to paint the affected elements.
|
||||
// This class is used by internet search view as well.
|
||||
class PlaylistProxyStyle : public QProxyStyle {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit PlaylistProxyStyle(QObject *parent = nullptr);
|
||||
|
||||
void drawControl(ControlElement element, const QStyleOption *option, QPainter *painter, const QWidget *widget) const override;
|
||||
void drawPrimitive(PrimitiveElement element, const QStyleOption *option, QPainter *painter, const QWidget *widget) const override;
|
||||
|
||||
private:
|
||||
ScopedPtr<QCommonStyle> common_style_;
|
||||
};
|
||||
|
||||
class PlaylistView : public QTreeView {
|
||||
Q_OBJECT
|
||||
|
||||
|
||||
@@ -55,7 +55,7 @@ SongList ASXParser::Load(QIODevice *device, const QString &playlist_path, const
|
||||
QString url = re_match.captured(2);
|
||||
url.replace(QRegularExpression("&(?!amp;|quot;|apos;|lt;|gt;)"), "&");
|
||||
|
||||
QByteArray replacement = QString(re_match.captured(1) + url + "\"").toLocal8Bit();
|
||||
QByteArray replacement = QString("%1%2\"").arg(re_match.captured(1), url).toLocal8Bit();
|
||||
data.replace(re_match.captured(0).toLocal8Bit(), replacement);
|
||||
index += replacement.length();
|
||||
}
|
||||
|
||||
@@ -25,14 +25,15 @@
|
||||
const char *MusixmatchProvider::kApiUrl = "https://api.musixmatch.com/ws/1.1";
|
||||
const char *MusixmatchProvider::kApiKey = "Y2FhMDRlN2Y4OWE5OTIxYmZlOGMzOWQzOGI3ZGU4MjE=";
|
||||
|
||||
QString MusixmatchProvider::StringFixup(QString string) {
|
||||
QString MusixmatchProvider::StringFixup(QString text) {
|
||||
|
||||
return string.replace('/', '-')
|
||||
.replace('\'', '-')
|
||||
.remove(QRegularExpression("[^\\w0-9\\- ]", QRegularExpression::UseUnicodePropertiesOption))
|
||||
.simplified()
|
||||
.replace(' ', '-')
|
||||
.replace(QRegularExpression("(-)\\1+"), "-")
|
||||
.toLower();
|
||||
return text.replace('/', '-')
|
||||
.replace('\'', '-')
|
||||
.remove(QRegularExpression("[^\\w0-9\\- ]", QRegularExpression::UseUnicodePropertiesOption))
|
||||
.replace(QRegularExpression(" {2,}"), " ")
|
||||
.simplified()
|
||||
.replace(' ', '-')
|
||||
.replace(QRegularExpression("(-)\\1+"), "-")
|
||||
.toLower();
|
||||
|
||||
}
|
||||
|
||||
@@ -25,7 +25,7 @@
|
||||
class MusixmatchProvider {
|
||||
|
||||
protected:
|
||||
QString StringFixup(QString string);
|
||||
QString StringFixup(QString text);
|
||||
|
||||
protected:
|
||||
static const char *kApiUrl;
|
||||
|
||||
@@ -143,7 +143,7 @@ class QobuzService : public InternetService {
|
||||
using Param = QPair<QString, QString>;
|
||||
using ParamList = QList<Param>;
|
||||
|
||||
QString DecodeAppSecret(const QString &app_secret_encoded) const;
|
||||
QString DecodeAppSecret(const QString &app_secret_base64) const;
|
||||
void SendSearch();
|
||||
void LoginError(const QString &error = QString(), const QVariant &debug = QVariant());
|
||||
|
||||
|
||||
@@ -158,7 +158,7 @@ QVariant Queue::data(const QModelIndex &proxy_index, int role) const {
|
||||
const QString title = source_index.sibling(source_index.row(), Playlist::Column_Title).data().toString();
|
||||
|
||||
if (artist.isEmpty()) return title;
|
||||
return QString(artist + " - " + title);
|
||||
return QString("%1 - %2").arg(artist, title);
|
||||
}
|
||||
|
||||
default:
|
||||
@@ -249,7 +249,7 @@ void Queue::UpdateSummaryText() {
|
||||
int tracks = ItemCount();
|
||||
quint64 nanoseconds = GetTotalLength();
|
||||
|
||||
summary += tracks == 1 ? tr("1 track") : tr("%1 tracks").arg(tracks);
|
||||
summary += tr("%n track(s)", "", tracks);
|
||||
|
||||
if (nanoseconds > 0) {
|
||||
summary += " - [ " + Utilities::WordyTimeNanosec(nanoseconds) + " ]";
|
||||
|
||||
@@ -111,7 +111,7 @@ class TidalRequest : public TidalBaseRequest {
|
||||
void ArtistsReplyReceived(QNetworkReply *reply, const int limit_requested, const int offset_requested);
|
||||
|
||||
void AlbumsReplyReceived(QNetworkReply *reply, const int limit_requested, const int offset_requested);
|
||||
void AlbumsReceived(QNetworkReply *reply, const Artist &artist_artist, const int limit_requested, const int offset_requested, const bool auto_login);
|
||||
void AlbumsReceived(QNetworkReply *reply, const Artist &artist_requested, const int limit_requested, const int offset_requested, const bool auto_login);
|
||||
|
||||
void SongsReplyReceived(QNetworkReply *reply, const int limit_requested, const int offset_requested);
|
||||
void SongsReceived(QNetworkReply *reply, const Artist &artist, const Album &album, const int limit_requested, const int offset_requested, const bool auto_login = false);
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user